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, G16scheme — 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 zis 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!