Financial institutions, big corporations, and governments have been reluctant to use public blockchains due to their lack of privacy and confidentiality. Blockchain transparency makes it easy for anyone to link addresses to companies, facilitating access to supposedly private financial transactions, partnerships, and data. Since the ledger of transactions is public, there is no way to hide an account’s activity. In addition, the immutability and impossibility to erase records make blockchains unforgiving in the case of information leaks. The only minor hurdle is that accounts are pseudonymous rather than directly linked to the official name of the organization or individual behind them.
Many, if not most, applications require privacy, such as asset transfers. But we are only beginning to understand what this might look like. An increasingly common way of achieving privacy on public blockchains is through zero-knowledge proofs (ZKPs). With ZKPs, one party (the prover) can convince another (the verifier) that some conditions are fulfilled without revealing the related information. For example, Zcash’s ZKP proves that a transfer of tokens was done soundly without revealing the sender address, the recipient address, and the amount transferred.
Aztec is an emerging layer 2 technology that implements ZKPs to enable a configurable privacy layer on top of public blockchains, such as Ethereum. This article explores how Aztec achieves privacy and how to build smart contracts that leverage these privacy properties. We showcase an implementation example that Taurus has built on top of this technology, allowing for the creation of privacy-preserving tokens. This implementation is based on the CMTA framework and follows the CMTAT Solidity version.
Why do we need blockchains with privacy?
Without privacy, every transaction is transparent on a public blockchain; everyone knows everything happening at all times: the addresses involved, the input and output values, and the operations performed. It is of utmost importance for a financial system to have some kind of privacy; otherwise governments, institutions, and anyone who cares about their own data will not use it. Where money is spent and how it is spent is co nfidential information and belongs to the private realm of an organization or individual. However, certain data, like the total supply of a token, should be kept public for utility or compliance reasons. This is why we define on-chain privacy according to the following three dimensions:
-
Data privacy: The ability of applications to have private (encrypted) states owned by a user and not readable by the external world, except for their size.
-
Confidentiality: The ability of applications to process and transform a private state internally, without revealing them to the public, including the network nodes (“validators”, “miners”).
-
Privacy configuration: The process of deciding which information to leave public, which information to keep private, and which parties are authorized to access (“decrypt”) it.
Current privacy implementations like mix network (specifically Tornado Cash) and private blockchains are insufficient. On the one hand, the lack of compliance of the former got it sanctioned by the US administration. Moreover, these kinds of implementations face bottlenecks as privacy needs to be built at the application layer, increasing the complexity and development efforts. On the other hand, private and closed blockchains lead to substantial fragmentation. This leads to interoperability challenges, making it difficult for different systems to communicate and work together. Ideally, you would like to use a system that has privacy implemented at the infrastructure level, all while being based on a public blockchain. This is what Aztec is striving to achieve.
The Aztec Network and its tooling stand out for their programmability and relative ease of implementing privacy-preserving smart contracts. Next, we explore how Aztec works at the infrastructure layer.
How does Aztec implement privacy on the blockchain?
To achieves the above three notions, we need a mix of different technologies. On one side, we need ZKPs to transfer knowledge across a network—remember, this happens without revealing any of the knowledge. This fulfills the data privacy requirement. To achieve confidentiality, we need a separation of public and private execution, as well as public and private storage. Finally, we need a network able to link everything together: a privacy-focused layer 2 protocol such as Aztec.
Aztec has been in the making since 2018. They have built the foundations of a developer-friendly and scalable way of creating private smart contracts. Among all the tooling, the Noir programming language, the Aztec layer 2 network, the PlonK proving system, and the Barretenberg backend proving system stand out. Let’s explore these technologies.
The core component: Aztec layer 2
At first glance, the Aztec Network is similar to any ZK-rollup (validity rollup in better terms). Transactions are sent to a sequencer which executes, compresses, and sends a single transaction to Ethereum. The compression property of zero-knowledge proofs enables a smart contract on Ethereum to verify the state of the layer 2 with much less computational cost than the one needed to execute these transactions. Thus, at first, we achieve scalability, as also achieved with similar techniques by Starknet or ZkSync.
To achieve privacy, a whole new paradigm has to be implemented. The Aztec Network separates public and private execution. Private execution is done in a Private eXecution Environment (PXE), running on the user's device. This means that when you execute a function using data that can be publicly available, it is publicly executed by a sequencer. However, when you execute a function with private inputs, it is privately executed in your own personal environment, revealing no private data to the network. The same paradigm applies to storage: your public data is stored in public storage, while your private data is stored in personal storage.
Why we need private/public programmability on Aztec
Let’s look at a simple example of a token with both public and private data. The token has a circulating supply, which needs to be public to determine the value of the token from the estimated value of its underlying asset. Thus, the supply variable is stored on public storage. The new circulating supply, which changes when tokens are minted, is computed in a public function. Conversely, you want to keep the amount of tokens you hold and send private. Therefore, your token holdings are kept in personal (local) storage, and the transfer of tokens, along with the computation of your new token holdings, are performed locally, on your personal device.
What is the secret sauce that enables a private state while maintaining consistency throughout the network? How is double spending avoided?
In Aztec, the answers lie in the implementation of zero-knowledge proofs, Pedersen commitments, and Poseidon hashes.
Indeed, when you execute a private function, you submit a ZKP to the network that proves correct execution. The Aztec network can verify that the function was correctly executed and that a state difference was computed. The PXE sends more than a simple proof of execution; it also sends private and public data, in the form of Pedersen commitments, encrypted logs, and nullifiers (Poseidon hashes), which we will explore later on. Let’s further explain with a diagram.
Example of end-to-end private transaction on Aztec
A private Aztec transaction goes as follows:
-
On their local computer, a user sends a transaction request to their PXE. This could be a private token transfer from their address to another.
-
The user’s PXE takes up the request and executes the smart contract functions, retrieves private data values from the private database, and proves the correct execution of the functions. Note that we are still on the user's computer device. The PXE then sends the proof, along with new commitments, new nullifiers, new encrypted data, and instructions to the sequencer to execute public functions. We will see afterward what all this data means.
-
The sequencer picks up all the transaction data, executes public functions through the public virtual machine (VM), and proves correct execution of the public functions. It also uses public data that it retrieves from the archiver, a component of the sequencer.
-
The sequencer compresses multiple transactions and proofs together with some ZK magic and sends a single rollup block to Ethereum layer 1. This is the part that a usual ZK-rollup does.
-
The verifier contract verifies the sequencer’s proof. If the verification is successful, the state contract changes the state root, effectively completing the state transition on layer 1.
-
The archiver pulls the result from Ethereum and updates the public state on which the Aztec Network relies. We will discuss which data structures are stored in this public state in the next part.
-
Each synchronizer, from each PXE, pulls the archiver’s public state and looks for any encrypted data or nullifier that might belong to the PXE’s linked address. For example, the recipient of the token transfer should receive a new Aztec note, while the sender should receive a nullifier and an Aztec note if there is a UTXO. This is necessary because the receiver will store their amount of tokens in their own PXE, so they must look for messages meant for them.
-
The synchronizer decrypts the data and sends it to the private database. Now, the sender can see their account balance decrease, while the receiver can see it increase.
-
Finally, a receipt of correct execution is sent to the user who initiated the transaction.
Note: You might wonder what an Aztec “note“ is (no pun intended). Private data within Aztec is stored in encrypted UTXOs, or “notes”. These notes are represented by their hash within the “commitment hash tree”. For example, the number of tokens you hold is stored in multiple notes, on your own PXE. We will discuss this in the next section.
Noir - the Aztec programming language
The Noir programming language was first released in October 2022. It is a Rust-based domain-specific language (DSL) for creating and verifying ZKPs. Noir enables writing ZK programs with a simple syntax, requiring no knowledge of underlying mathematics or cryptography. Noir programs are then compiled into arithmetic circuits. We can think of an arithmetic circuit as an electric circuit, with sums and products instead of electronic gates.
You may have guessed it, Noir helps us fulfill privacy configuration, the third dimension mentioned earlier. Indeed, it helps us define at a certain granularity level the data and computations that we want to keep private or share publicly. We’ll explore how Noir facilitates our implementation of privacy-preserving smart contracts in the next section.
The proving backend - PlonK and Barretenberg
PlonK is one of the zero-knowledge proving systems that belong to the SNARK group. It offers advantages such as a universal and updatable trusted setup.
Aztec Network has a proving system backend called Barretenberg that runs on PlonK. Noir is backend-agnostic, meaning you can use any SNARK-based backend to prove and verify computations, thanks to Noir’s IR, called ACIR (Abstract Circuit Intermediate Representation). A simplified workflow is as follows:
-
A Noir program is compiled to ACIR in the frontend.
-
The ACIR instance is then converted to the required format by a backend.
-
The backend generates and verifies proofs.
Technical specifications of private environment
Before jumping into our private token implementation, let’s clarify some concepts. Recall that the Aztec Network has two states, and thus two types of storage. Private and public storage are separated but deeply linked.
Notes
They are cryptographic representations of data. When notes are created, they are encrypted to maintain privacy and reside in private storage on the user’s PXE. The user’s PXE can decrypt its own notes as well as those that someone else has encrypted for them. These notes are then represented by their commitment within the public 'commitment Tree'. In fact, each note has a commitment in public storage.
Commitments
A note’s data is used to create commitments. These commitments are stored in public storage and assess publicly the existence of a private note while hiding its value.
Nullifiers
Nullifiers are pieces of data used to mark privately notes as consumed. They are publicly stored in a nullifier tree.
When you mint a token amount privately, a commitment to that value is created. It holds a time proof that you actually hold a certain number of tokens privately. This commitment is stored by the Aztec Network in public storage. A note, represented by an encrypted value of that token amount, is stored in private storage, so you can decrypt it. No one can link the public commitment to your address. When you transfer the token, you have to provide a nullifier and a ZKP. The nullifier is a unique ID that is associated with the commitment. It is the only way to indicate that you have spent that UTXO, as you cannot remove a commitment from the tree. The ZKP proves the connection between the commitment and the nullifier, but nobody knows which nullifier is assigned to which commitment (except the owner of the commitment).
[Source: https://azt3c-st.webflow.io/blog/wtf-is-aztec - Explanation: separation of private and public storage]
Taurus private token example
Now that we have introduced most of the concepts, let’s get into our private token example. This private token is being built according to the CMTAT implementation on Ethereum. CMTAT is a standard security token framework used by various financial institutions to tokenize instruments such as equity shares, bonds, or structured products.
The end goal of this new private token is to offer everything that the CMTAT offers, in a private and compliant manner. In this section, we look into storage, note management, keys, and the private functionalities of our token that will enable us to reproduce the CMTAT private implementation.
Storage
Our token has some public values and some private ones. In fact, the only private values are the balances. The balances are a map from an AztecAddress to a set of notes. Since notes reside in the private realm, each user’s balance is stored and computed in private storage.
Private mint
The private mint allows an issuer to send some tokens to a recipient without any network participant knowing either the amount or the recipient’s address.
An issuer can privately mint tokens to an address. This is how the mint() function works:
- We perform some checks on the inputs. This is mainly done for compliance.
- We call the add() function, implemented by the TokenNote. This function will create a note, with the value addend and the nullifier key of the recipient. Doing this last step ensures that only the recipient will be able to spend that note afterward by posting a new nullifier signed with the nullifier secret key.
- The mint() function will enqueue a public function that increases the circulating supply of the token publicly. The public function is enqueued as it is not executed by the PXE but by the sequencer at a later stage.
Private transfer
Let’s show how to privately send funds from one address to another without revealing any of the addresses or the amounts involved in the transfer. This is complete privacy with compliance at its core. We can also enforce some compliance checks.
The private transfer between two users works as follows:
-
Checks are done for compliance measures. Here, we have chosen to refuse blacklisted addresses. We could also decide to only allow whitelisted ones.
-
We subtract the number of tokens from the sender’s address. UTXO is abstracted away in this transfer function. However, under the hood, we create nullifiers for the notes that we are spending and create new notes for the remaining UTXO. These notes and nullifiers are encrypted with the sender’s public keys and public nullifier keys. Keep in mind that different key pairs are needed for this, but they are almost all abstracted away by the Aztec Network and the Noir language.
-
We create a new encrypted note with the receiver’s public keys so that only they can decrypt it.
Note: We could also enforce keys that are viewable by a central issuer authority, making auditing by a trusted party as easy as in a public blockchain.
End-to-end example of private token transfer
Now, we’ll see how a private transfer operates at the application and protocol layer. It is important to note that private and sensitive inputs, such as the number of tokens to transfer and the parties involved, have not been revealed to the public. Only the proof, the commitment, and the nullifiers have entered public space.
-
User A initiates a transfer request for “transferring 10 tokens to user B”.
-
A proof of correct execution of the private transfer function is generated on User A’s device. Nullifiers for two of their notes (N1, N2) are generated (NUL(N1), NUL(N2)) to publicly assess that the notes have been spent (as they have two notes worth 5 each, they need to spend both). An encrypted note and a public commitment to the note are created (ENC(N3), COM(N3)). The note is encrypted with User B’s keys, and with 10 as a value.
-
The public sequencer does not execute any function, as none are public. However, it stores the nullifiers in the nullifier tree and stores the new commitment in the commitment tree. The image shows the state before the transfer, not after.
-
Upon receiving an L1 confirmation, the archiver sends, among other data, ENC(N3) to User B’s PXE.
-
User A receives a receipt of correct execution.
-
User B’s PXE is able to decrypt ENC(N3). User B’s wallet token balance is now 10, effectively finalizing the private transfer.
Conclusion
Although Aztec’s technology is not fully fledged, we could start experimenting with it, in hopes that it will ultimately help its users access public blockchains in a privacy-preserving manner. As the cryptographic landscape continues to evolve, we will stay attuned of the latest developments and consider the most viable solutions for future production deployment.
Aztec is a promising technology, allowing anyone to use protocol-level libraries to program privacy efficiently, without needing to know all the cryptographic primitives used. It is one of the first solutions to offer this level of programmability, allowing for compliance and endless features.
Their roadmap is ambitious, aiming for a testnet launch in Q4 2024 and a mainnet launch in 2025. Thus, we can expect clearer indications of blockchain privacy possibilities in the short to medium term.
Disclosure: This project is being supported by a grant from Aztec Labs.