ZoKrates: Getting Started

In this guide we show how to get started with zero-knowledge proofs on Ethereum, using the ZoKrates language and toolset.

Setup

Installing is easy provided you’re comfortable with their automated install script (though you may be out of luck for the time being if you’re on Windows):

$ curl -LSfs get.zokrat.es | sh

If the installation is successful, you should get a suggestion to add ZoKrates to your path:

$ export PATH=$PATH:$HOME/.zokrates/bin
$ export ZOKRATES_HOME=$HOME/.zokrates/stdlib

If all is fine, you should be good to go. This guide is tested with the following version

$ zokrates --version
ZoKrates 0.4.9

ZoKrates program

Let’s write a simple program in ZoKrates that proves knowledge of the result of a (field) division x/y. In other words, knowledge of a field element z such that zy=x.

def main(field x, field y, private field z) -> (field):
  field result = if z * y == x then 1 else 0 fi
  return result

Save this to a file div.code. Compile this with

$ zokrates compile -i div.code

This generates a file out.code containing the R1CS (Rank-1 Constraint System) constraints - or at least, a close variant of this - corresponding to our program. Let’s take a look:

def main(_0, _1, _2) -> (1):                        
  (1 * _2) * (1 * _1) == 1 * _5               
  # _3, _4 = Rust::ConditionEq((-1) * _0 + 1 * _5)
  ((-1) * _0 + 1 * _5) * (1 * _4) == 1 * _3   
  (1 * ~one + (-1) * _3) * ((-1) * _0 + 1 * _5) == 0
  (1 * ~one + (-1) * _3) * (1 * ~one + (-1) * _3) == 1 * ~one + (-1) * _3
  (1 * _3) * (0) == 1 * _13                   
  (1 * ~one) * (1 * ~one + (-1) * _3 + 1 * _13) == 1 * ~out_0
  return ~out_0

To demystify this a bit, let’s look at the first constraint as an example:

  (1 * _2) * (1 * _1) == 1 * _5

First note that the variables _0, _1 and _2 correspond to x, y and z respectively. Then the constraint corresponds to an equality between zy and another (intermediate) variable _5. When compiling the program, ZoKrates generates all the necessary intermediate variables and constraints.

Verification contract

Next run the setup

$ zokrates setup

which generates the keys proving.key and verification.key. The prover P will need the former later to generate a proof. For now, we need just the latter to generate a verifier contract

$ zokrates export-verifier

This creates a Solidity contract verifier.sol that (conveniently) contains the verification key. Now the contract is deployed, and proving.key is given to P.

Notice that so far P hasn’t yet played any part, so the contract does not contain any proof data as such at this stage.

Proof submission

Let’s assume the prover P and verifier V agree on the public inputs x=8 and y=2. Given this, P (equipped with proving.key as mentioned above) computes a witness z=4:

$ zokrates compute-witness -a 8 2 4

This produces witness as follows

~out_0 1                                            
~one 1
_0 8
_1 2
_2 4
_3 0
_4 1
_5 8
_13 0

Notice that the witness includes not only the values for x, y and z but also all the intermediate variables _3, _4, etc. Finally, P can generate a proof with this witness:

$ zokrates generate-proof

giving us the proof object rendered in proof.json

{                                                   
  "proof": {                                  
    "a": [
      "0x156a5830f50b1682ec4f669e3e74b6ff5c02266f9279bc7c2231c03f4ba110ab",
      "0x0154cd9d51436ea60d70b81a5ecd6548b4cb2e849aada2f0accb4ae830043d7a"
    ],                                                     
    "b": [
      ["0x0f5d0a0a45e969e38ee3c0beb744c83f45c33321af837c199278ad480f76acb5", 
       "0x1486424972757d1ee3b2187780c9999b27fbbc0acaeaee7992bc603a853617cb"],
      ["0x0cecaa7f1f2448f8595d8a8783ecf27fe81050e7fa72a3f442b96bb57f1d2b2e",
       "0x17d5d238c965d5c0266a94e3d9de4efce1f28d1add21b416adce853b1b7d3b97"]
    ],              
    "c": [
      "0x20d68643704e84a4d2e2cb51b8e6f7c63f293978a66c8c5cca88612cb212a34e",
      "0x1e075b9e726ca91cdc8835bfda4ea09c6c5f3d585798e16a90e1fc1cc60ab128"
    ]                                                      
  },                                          
  "inputs": [
    "0x0000000000000000000000000000000000000000000000000000000000000008",
    "0x0000000000000000000000000000000000000000000000000000000000000002",
    "0x0000000000000000000000000000000000000000000000000000000000000001"
  ]                                   
}

The proof consists of the parameters (a,b,c) making up the zk-SNARK proof (in this case, G16 scheme - though this can be configured on the ZoKrates CLI). It also contains the public inputs x,y and the return value ~out_0 (in this case 1) of our program. Notice that the private input z is not included!

In order for V to check this proof, P should now submit the above to the deployed verification contract. In particular, the contract contains a function verifyTx

function verifyTx(uint[2] memory a,
                  uint[2][2] memory b,
                  uint[2] memory c,
                  uint[3] memory input) public returns (bool r) {
  // ...
}

that takes as parameters the data contained in proof.json. How does V get notified of this? By monitoring the event Verified also contained in the contract. This event emits when the transaction sent by P has been verified, by which point V will see that this was sent from P's (public) address. When this happens, V can be sure that P knows z such that zy=x without learning anything about z.

This completes the rough overview. We’ve covered how to get set up with ZoKrates, as well as the basic workflow. We’ve had to skip a lot of details. The next in this series will cover some of the parts (e.g. the verification contract and the proof object) a bit more closely, and we’ll also show some practical steps for testing out the contract!