Zero-Knowledge Proofs with Circom and Noir

Zero-knowledge proofs (ZKPs) are a powerful cryptographic tool that allow you to prove that something is true without revealing any additional information. ZKPs are finding a wide range of applications in blockchain, including privacy-preserving transactions, scaling solutions, and decentralized gaming. In the previous article, we've discussed about some of the fundamentals behind SNARKs and STARKs, so make sure to read those if you want more details behind the tech.

ZKP Languages

One of the most important components of a ZKP is the circuit. A circuit is a mathematical representation of the computation that is being performed by the ZKP. Circuits are typically written in a specialized language, such as Circom or Noir.

Circom is a circuit description language that is developed by the Zcash project. It is a powerful and flexible language that allows users to write circuits for a wide range of applications. However, Circom can be difficult to learn and use, and it requires a good understanding of cryptography.

Noir is a newer circuit description language that is developed by the Aztec Network. Noir is designed to be easier to learn and use than Circom, and it does not require users to have a deep understanding of cryptography. It's a domain-specific language (DSL) that is specifically designed for building ZKPs. Noir is simpler to learn and use than Circom, but it is not as powerful.

ZKP Recap

Very very briefly, ZKPs consist of circuits, witness, and a constraint system to hold everything together (for more details follow the links above). A circuit is a mathematical model of a computation and a representation of the problem that you want to prove. It consists of a set of gates, each of which performs a simple operation, such as addition or multiplication. The gates are connected to each other by logical wires, which represent the flow of data through the circuit.

A constraint system is a set of constraints that must be satisfied by a circuit in order for it to be valid. For example, a constraint might be that the output of a circuit must be equal to the sum of its inputs.

A witness is a set of values that satisfy the constraints of a constraint system. In the context of ZKPs, a witness is used to prove that a statement is true without revealing any information about the witness itself.

To write a ZKP in either Circom, you first need to define the circuit. Once you have defined the circuit, you can use the Circom compiler to generate a proof.

In contrast, when you use Noir, you simply need to define the problem that you want to prove in a declarative way. Noir will automatically generate the circuit and the proof for you.

Example - Proof of Reserves

One of the most important use cases for ZKPs is proof of reserves. Proof of reserves is a way to verify that a Centralized Crypto Exchange (CEX) has sufficient reserves to cover all of its customer deposits. To get more details about proof of reserves and solvency, read my previous post.

To implement proof of reserves using ZKPs, the CEX would first need to generate a circuit that represents the state of its reserves. The circuit would have inputs for the total amount of reserves and the total amount of customer deposits. The circuit would also have outputs for the difference between the two amounts. The CEX would then need to generate a ZKP that proves that the output of the circuit is non-negative. This would prove that there are sufficient reserves to cover all of its customer deposits - while not revealing any of the individual balances.

Here is an example of how this ZKP could be used in a real-world proof of reserves scenario:

  • A CEX wants to prove that it has sufficient reserves to cover all of its customer deposits.
  • The CEX generates a circuit that represents the state of its reserves. The circuit has inputs for the total amount of reserves and the total amount of customer deposits. The circuit also has outputs for the difference between the two amounts.
  • The CEX uses the a list of the balances of all its customers balances to calculate the total amount of customer deposits.
  • The CEX generates a ZKP that proves that the output of the circuit is non-negative.
  • The CEX publishes the ZKP to its customers or relevant authority.

Here is a simple Circom script snippet for proving that the overall amount of reserves for all the customers is equal or greater than input X, without revealing individual account balances:

// Circom
// Define the public inputs
public input X;

// Define the public output
public output C;

// Define the circuit
circuit ReservesProof {

    // Declare the variables
    private var totalReserves;
    private var totalDeposits;

    // Initialize the variables
    totalReserves = X;
    totalDeposits = 0;

    // Iterate over all of the accounts
    for (var i = 0; i < accounts.length; i++) {

        // Add the account balance to the total deposits
        totalDeposits += accounts[i].balance;
    }

    // Calculate the difference between the total reserves and the total deposits
    C = totalReserves - totalDeposits;
}

// Generate the ZKP
generate_proof();

And the Noir equivalent - notice how shorter the script is in comparison to its Circom analogy:

// Define the public inputs
public X;

// Define the public output
public C;

// Define the circuit
constrain C = X - sum(accounts, balance);

// Generate the ZKP
generate_proof();

We've talked about the input and output, but notice the accounts variables in both scripts, being neither input nor output. The accounts variable is a list of all of the accounts in the CEX. It is initialized when the CEX is created and managed by the CEX - being updated whenever an account is created, deleted, or updated.

The variable is used to calculate the total amount of customer deposits. For each account, it contains the account balance. The total amount of customer deposits is calculated by summing the account balances of all of the accounts in the variable.

Here is a more detailed explanation of how the accounts variable is initialized and managed:

  • When the CEX is created, an empty accounts variable is created.
  • Whenever a new account is created, the account is added to the variable.
  • Whenever an existing account is deleted, the account is removed from the variable.
  • Whenever an existing account is updated, the account balance in the variable is updated.

This variable is a critical part of the proof of reserves protocol. By proving that the overall amount of reserves for all the customers is equal or greater than input X, the CEX can demonstrate that it has sufficient reserves to cover all of its customer deposits.

Using ZKPs the CEX's customers can then verify the published proof to ensure that there are sufficient reserves to cover all of their deposits.

Conclusion

Circom and Noir are two popular languages for building ZKPs and both languages can be used to implement proof of reserves or any other real use cases - both in web3 and web2 worlds.

Circom and Noir are both powerful tools for writing ZKPs. Circom is more flexible, but it is also more difficult to learn. Noir is easier to learn, but it is less flexible. The Circom script snippet above is more concise and easier to read than the Noir script snippet. However, the Noir script snippet is more efficient, meaning that it can generate ZKPs that are smaller and faster to verify. The best choice for you will depend on your specific needs. If you are a developer who is already familiar with cryptography, then Circom is a good choice. If you are a developer who is new to cryptography, then Noir is a good choice. Which language you should use depends on your experience with ZKPs and the specific requirements of your application.

I am excited about the potential of ZKPs to revolutionize many different industries. For example, ZKPs can be used to create privacy-preserving financial applications, such as private transactions and exchanges. ZKPs can also be used to scale blockchain networks and to create new types of decentralized applications.

Circom and Noir are making it easier than ever for developers to write ZKPs. This is a very positive development, and it is accelerating the adoption of ZKPs in many different industries.

I am particularly excited about the potential of Noir to make ZKPs more accessible to a wider range of developers. Noir is designed to be easy to use, even for developers who do not have a deep understanding of cryptography. This means that more developers will be able to build ZKP-based applications, which will lead to a more diverse and innovative ecosystem.

Overall, I believe that Circom and Noir are two of the most important tools for writing ZKPs. They are making ZKPs more accessible to developers, and they are accelerating the adoption of ZKPs in many different industries.

Comments

Popular posts from this blog

CAP Theorem and blockchain

Length extension attack

Contract upgrade anti-patterns