(Why) Bringing Privacy to Smart Contracts is Non-trivial

(Why) Bringing Privacy to Smart Contracts is Non-trivial

Private transactions are basically a solved problem for the consumer (although some challenges still remain for researchers). If we want to transfer cryptocurrency without revealing our balance or the transfer amount, we have acceptable options available like Zcash and Monero. The same cannot be said for private decentralized applications or private smart contracts.

What makes smart contracts with input/output (I/O) privacy any different from transactions with I/O privacy?

In this article, we'll demystify the challenges that arise from trying to extend private transactions to private smart contracts. To do this, we'll look at the common cryptographic tools used to make a private coin and explore why these tools don't quite suffice for more complex private applications. We'll conclude with a brief look at some recently proposed private smart contract schemes.

Defining privacy

What does it even mean to be "private"?

We'll talk about privacy in the language of functions. For example, we can think of a transaction as some function that takes as inputs balances and a transfer amount. It then outputs updated balances.

Figure 1: Representing a transaction as a function.

We can consider hiding the inputs and outputs of a function. In terms of transactions, this allows us to hide how much money we currently have and how much money we're transferring. Would you feel comfortable revealing your bank account's balance and transaction history to everyone? Probably not. At the absolute minimum, we'll want to support I/O privacy with regards to functions (and subsequently smart contracts).

Figure 2: Hiding the I/O of a function.

We can also consider hiding who called the function. Sometimes the inputs may give hints about the identity of the function caller. In practice, hiding the inputs and outputs of a function is usually combined with hiding the identity of the function caller.

Figure 3: Hiding who called the function.

Finally, we can consider hiding what the function itself does. This one is less common in the cryptocurrency space and often combined with hiding the inputs/outputs of the function.

Figure 4: Hiding the function itself.

When you see the word "private" in this article, think of it as catch-all term to describe something that supports at least I/O privacy.

Ok, but where might we need privacy?

We can think of private transactions as a solved problem (in terms of feasibility at least, scalability is a different story) so let's move to private smart contracts.

But, first, we'll take a quick detour and review Ethereum...

Ethereum supports user-defined contracts that are enforceable by code (aka "smart contracts"). These contracts are written in Ethereum's own Turing-complete language with each operation having some (pre-determined) cost. Fees are attached to each transaction to encourage miners to work on the transaction.

Private applications

Smart contracts have allowed for a rich variety of applications to be performed on the blockchain—from decentralized exchanges (DEXs) in which users can exchange various cryptocurrencies and their derivatives to decentralized autonomous organizations (DAOs) that allow stakeholders to vote on proposals.

I doubt that I have to make a strong case for why privacy is needed in DAOs; voting is often done privately in real life so it's pretty reasonable to want to hide our votes virtually too.

On the other hand, privacy for decentralized exchanges needs a bit of an explanation. Front-running is an issue for both centralized and decentralized exchanges. In the cryptocurrency setting, front-runners observe orders being submitted and then front-run them by placing their own orders with higher fees attached (to bribe the miners). This allows a front-runner Eve to potentially buy a security she knows Bob wants and then sell it back to him at a higher price. Hiding bids through sealed-bid auctions is a potential solution to this issue. For the interested reader, more info on front-running on decentralized exchanges can be found here.

Unfortunately, Ethereum's smart contracts do not provide any form of privacy out of the box. All information is publicly viewable—the inputs/outputs of the contract, what the contract does, the users involved, etc. Adding privacy to Ethereum's smart contracts is no easy feat as Ethereum wasn't designed to support privacy from inception. Private transactions are possible on Ethereum (via 1, 2) but more complicated private operations are often too expensive to implement or exceed Ethereum's maximum cost (i.e. gas limit) per block.

Can't we just design a new cryptocurrency that supports privacy for arbitrary functions? After all, this is the route Zcash and Monero took.

It's not immediately clear how to best support I/O privacy for arbitrary functions (such as those needed in voting and exchanges) in the cryptocurrency setting. To understand these challenges, we'll need to look at how cryptocurrency schemes support private transactions.

The road to privacy

We'll start by looking at the cryptographic tools needed to perform transactions with I/O privacy. We'll focus on cryptocurrencies using the account-based model rather than UTXOs. Accounts are particularly useful in the smart contract setting but are by no means necessary for performing private computation (as we'll see later on).

Tool #1: Homomorphic addition

Most private cryptocurrency schemes rely on an encryption or commitment scheme that is additively homomorphic. For simplicity, we'll focus on encryption schemes but the same principles apply for commitment schemes.

With an additively homomorphic encryption scheme, we have the following equality:‌‌Enc(a) + Enc(b) = Enc(a + b).

Thus, an additively homomorphic encryption scheme allows transactions to be computed by anyone since:‌‌Enc(balance) + Enc(transfer amount) = Enc(balance + transfer amount)

Figure 5: Alice wants to send currency without revealing the balance or transfer amount.

Alice has her own public key—pka. She encrypts her account balance bala under her public key (that way only she knows how much money she has). Let's refer to her encrypted balance as βa = Enc(pka, bala). Anyone can view Alice's public key pka and her encrypted balance βa since they will be published online.

Bob, likewise, does the same; he has his own public key pkb, an unencrypted balance balb that only he knows, and a balance encrypted under his public key βb = Enc(pkb, balb).

If Alice wants to send currency to Bob without revealing the transfer amount (amnt) or her unencrypted balance, all she needs to do is publish the transfer amount encrypted under her public key and the transfer amount encrypted under Bob's public key. Let these values be ca = Enc(pka, amnt) and cb = Enc(pkb, amnt) respectively.

The updated balances can now be computed by anyone. Alice's updated balance is βa - ca and Bob's updated balance is βb + cb.

But wait! If all these values are encrypted, how do we know Alice has enough money in her account to send amnt? How do we know that the ciphertexts ca and cb even encrypt the same amount?

This leads us into our next tool—zero-knowledge proofs.

Tool #2: Zero-knowledge proofs (ZKPs)

To ensure Alice hasn't cheated in the transaction above, she will need to attach a proof to the transaction. The proof needs to show that she has enough currency in her account to perform the transfer, that she hasn't sent some negative value to Bob (whether accidentally or maliciously), and that the two ciphertexts ca and cb do in fact encrypt the same value value.

Alice, of course, doesn't want to reveal what her unencrypted account balance is or what the transfer value is; thus, she attaches a zero-knowledge proof π that convinces all the other users that the necessary conditions are satisfied without revealing any additional information (such as what her balance is or what the transfer value is).

Putting it all together now...

Figure 6: Private transaction with the necessary ZKP now attached.

Alice encrypts the transfer amount under both her and Bob's public key, getting ca and cb respectively. She provides a ZKP, π, showing that she hasn't cheated in the transaction. Miners will verify that all necessary conditions are met and that the ZKP is valid. Then, they'll use homomorphic addition to update Alice's and Bob's encrypted balances to βa - ca and βb + cb, respectively. Notice that while the user provides the secret inputs and a ZKP, the miners are responsible for performing the computation and updating the encrypted balances. In blockchain, we assume that a majority of miners are honest so we know that they've updated Alice's and Bob's balances correctly.

Note: This is a vastly simplified explanation (e.g. leaving out the randomness needed to ensure security in encryption).

Challenges in extending private transactions to private smart contracts

So we've just seen that we can perform transactions that hide the inputs and outputs. Can we use techniques from private transactions to support applications with I/O privacy? In other words:

Is private computation different from private transactions? If so, why?

Problem #1

Notice that private transactions need to satisfy set conditions in order to be correct (i.e. sender has enough currency, transfer amount is positive, etc.). How do we figure out what conditions an arbitrary contract needs to satisfy? The conditions obviously will depend on the particular application. In voting, we may want to show that our hidden vote is in the correct range, whereas, for auctions, we may want to show we have enough money in our account for the sealed bid.

Potential solutions to #1

This problem isn't too bad actually; it just requires more work for the users. The creator of the decentralized application will have to determine what conditions need to be satisfied for their particular application and communicate this to users. To allow for a wide variety of conditions to be specified and proved, we may want to support some general purpose ZKP in the scheme. By this, we mean a ZKP that is universal so that we can prove arbitrary statements (unlike those currently used in Zcash, for example, which are non-universal).

Problem #2

In transactions, we are only ever operating on values belonging to the same user (i.e. encrypted under the same key). Notice that in Figure 6 the miners are adding together Alice's balance encrypted under her key and the transfer amount encrypted under her key. What if we want to perform private computations on inputs belonging to different users? This isn't so far-fetched when we're looking at private voting for example.

Potential solutions to #2

It's not clear how to support computations on inputs belonging to different users without the users revealing their plaintext inputs to one another. There are some advanced cryptographic primitives (like secure multiparty computation and multi-key FHE) that allow users to perform computations on inputs encrypted under different keys. Unfortunately, these are incredibly costly solutions with quite a few drawbacks. No one currently seems to have a good solution to this problem in the cryptocurrency setting (apart from just sharing the plaintext with the other user(s) involved in the computation and then encrypting it under the same key).

Problem #3

Transactions only require homomorphic addition since we only need to add the encrypted transfer amounts to encrypted balances. What if we want to perform more complex computations, possibly involving multiplication?

Potential solutions to #3

‌Homomorphic multiplication would allow us to multiply encrypted inputs together such that Enc(a) * Enc(b) = Enc(a * b). With both homomorphic addition and homomorphic multiplication, we could represent any polynomial function. So, a natural question arises:

Can we support homomorphic multiplication?

An encryption scheme that is both additively homomorphic and multiplicatively homomorphic is a fully homomorphic encryption (FHE) scheme. Using FHE, we could follow the same blueprint as described in Figure 6. That is, the user specifies the encrypted inputs, the function to run, and a ZKP with the necessary conditions that the encrypted inputs need to satisfy. Miners can check the ZKP. They use homomorphic addition and homomorphic multiplication to operate on the provided ciphertexts directly.

Unfortunately, FHE schemes use lattice-based cryptography which is not immediately compatible with the super efficient ZKPs used in cryptocurrency. We've previously written about FHE and its issues here. Currently, no one has proposed an FHE-based solution due to its drawbacks.

This leaves us with the two current approaches to solving problem #3.

  • Accept that we can only support homomorphic addition and follow the same blueprint as private transactions.
Figure 7: Following the private transaction blueprint.

‌Here, users provide encrypted inputs and a ZKP showing that certain application-specific conditions have been met for their inputs. Miners verify the proof and then use homomorphic addition to operate on the inputs. Note that the function being applied to the inputs must be represented solely via addition. Thus, as long as the function requires only homomorphic addition, we can ask the miners to perform various functions on our encrypted inputs. This is the approach taken by Zether.

  • Ask the user to do computations offline. This prevents us from needing to support homomorphic multiplication for encryption/commitments.
Figure 8: Outsourcing the work to the user.

Here, we ask the user Alice to perform almost all the computations offline on the plaintext. She'll publish the encrypted inputs and the encrypted outputs for the computation. Since the computation was done offline (and we don't know if the Alice is behaving honestly), she'll also need to provide a ZKP showing that the computation itself was performed correctly. Note that this wasn't needed for private transactions since miners perform the computation and we assume a majority of them are honest. Depending on the application, she will likely need another ZKP showing that application-specific conditions have been satisfied. All miners do in this approach is check that the ZKP is valid and then approve the Alice's proposed state changes. This is the approach taken by Zexe and Zkay.

I won't argue here which approach (Figure 7 vs. Figure 8) is superior; they're just different.

Private smart contracts

Now that we've touched upon some of the issues that come with supporting privacy for arbitrary functions in blockchain, let's look at some constructions.

If it's not already clear, this area is by no means a solved problem. The papers for these constructions (i.e. Zether, Zkay, Zexe) were published in the last two years.

Zether is a private transaction scheme built on top of Ethereum. It can be extended to support a limited class of smart contracts with I/O privacy—namely those that can be represented solely via homomorphic addition. This allows us to perform simple sealed-bid auctions (assuming bidders buy all units on offer) and private voting (assuming the votes are binary). Unfortunately, only transactions hiding the users' balances and the transfer amount can currently be implemented on Ethereum due to gas constraints. Unlike the next two constructions, Zether uses a "transparent" ZKP (i.e. a ZKP with no trusted setup).

Zkay also extends Ethereum's design to support smart contracts with I/O privacy. They rely on the power of ZKPs for private computation, thus offloading the bulk of work to the user to perform offline. However, this design choice also allows them to support a larger class of functions than in Zether.

Zexe instead attempts to extend Zcash's design to support arbitrary coin scripts. Unlike the previous two, Zexe can also support function privacy.

Comparing the approaches

Scheme Privacy type Approach Expressivity Extends the design of
Zether I/O Figure 7 Additive functions Ethereum
Zkay* I/O Figure 8 Arbitrary functions Ethereum
Zexe* I/O, function** Figure 8 Arbitrary functions Zcash

* Zkay and Zexe (as presented) use ZKPs with trusted setups. However, these ZKPs could certainly be replaced with those that do not require a trusted setup.

** I/O privacy appears to be more relevant than function privacy in blockchain since a user will likely want to audit a contract before deciding whether to participate in it.

Please note that there are other private smart contract constructions (namely Ekiden, Hawk, Arbitrum) but these require some sort of semi-trusted manager or trusted hardware.

Most private smart contract schemes require additional trust assumptions—whether trusted setup, semi-trusted manager, or trusted hardware. However, ZKPs are a rapidly evolving field and more efficient constructions that are transparent will likely be created.

Looking forward

Private smart contracts present plenty of interesting theoretical and practical challenges when it comes to expressivity, trust, and efficiency. Right now, it's hard to say which (if either) approach—Figure 7 vs. Figure 8—might win out for private computation on blockchain. It'll also be interesting to see if advances in fully homomorphic encryption translate to the blockchain setting to solve problem #3.