Introduction

ZoKrates is a toolbox for zkSNARKs on Ethereum. It helps you use verifiable computation in your DApp, from the specification of your program in a high level language to generating proofs of computation to verifying those proofs in Solidity.

Background on zkSNARKs

Zero-knowledge proofs (ZKPs) are a family of probabilistic protocols, first described by Goldwasser, Micali and Rackoff in 1985.

One particular family of ZKPs is described as zero-knowledge Succinct Non-interactive ARguments of Knowledge, a.k.a. zkSNARKs. zkSNARKs are the most widely used zero-knowledge protocols, with the anonymous cryptocurrency Zcash and the smart-contract platform Ethereum among the notable early adopters.

For further details we refer the reader to some introductory material provided by the community: [1],[2], [3].

Motivation

Ethereum runs computations on all nodes of the network, resulting in high costs, limits in complexity, and low privacy. zkSNARKs have been enabling to only verify computations on-chain for a fraction of the cost of running them, but are hard to grasp and work with.

ZoKrates bridges this gap. It helps you create off-chain programs and link them to the Ethereum blockchain, expanding the possibilities for your DApp.

License

ZoKrates is released under the GNU Lesser General Public License v3.

Getting Started

Installation

One-line install

We provide a one-line install for Linux, MacOS and FreeBSD:

curl -LSfs get.zokrat.es | sh

Docker

ZoKrates is available on Dockerhub.

docker run -ti zokrates/zokrates /bin/bash

From there on, you can use the zokrates CLI.

From source

You can build the container yourself from source with the following commands:

git clone https://github.com/ZoKrates/ZoKrates
cd ZoKrates
cargo +nightly build --release
cd target/release

Hello ZoKrates!

First, create the text-file root.code and implement your program. In this example, we will prove knowledge of the square root a of a number b:

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

Some observations:

  • The keyword field is the basic type we use, which is an element of a given prime field.
  • The keyword private signals that we do not want to reveal this input, but still prove that we know its value.

Then run the different phases of the protocol:

# compile
./zokrates compile -i root.code
# perform the setup phase
./zokrates setup
# execute the program
./zokrates compute-witness -a 337 113569
# generate a proof of computation
./zokrates generate-proof
# export a solidity verifier
./zokrates export-verifier

The CLI commands are explained in more detail in the CLI reference.

Programming Concepts

Variables

Variables can have any name which does not start with a number. Underscores are not allowed in variable names. Variables are mutable, and always passed by value to functions.

Shadowing

Shadowing is not allowed.

def main() -> (field):
    field a = 2
    // field a = 3 <- not allowed
    for field i in 0..5 do
        // field a = 7 <- not allowed
    endfor
    return a

Scope

Function

Functions have their own scope

def foo() -> (field):
    // return myGlobal <- not allowed
    return 42

def main() -> (field):
    field myGlobal = 42
    return foo()

For-loop

For-loops have their own scope

def main() -> (field):
    field a = 0
    for field i in 0..5 do
        a = a + i
    endfor
    // return i <- not allowed
    return a

Types

ZoKrates currently exposes three types:

field

This is the most basic type in ZoKrates, and it represents a positive integer in [0, p - 1] where p is a (large) prime number.

The prime p is set to 21888242871839275222246405745257275088548364400416034343698204186575808495617 as imposed by the pairing curve supported by Ethereum.

While field values mostly behave like unsigned integers, one should keep in mind that they overflow at p and not some power of 2, so that we have:

def main() -> (field):
    field pMinusOne = 21888242871839275222246405745257275088548364400416034343698204186575808495616
    0 - 1 == pMinusOne
    return 1

bool

ZoKrates has limited support for booleans, to the extent that they can only be used as the condition in if ... else ... endif expressions.

You can use them for equality checks, inequality checks and inequality checks between field values.

Note that while equality checks are cheap, inequality checks should be use wisely as they are orders of magnitude more expensive.

field[n]

Static arrays of field can be instantiated with a constant size, and their elements can be accessed and updated:

def main() -> (field):
    field[3] a = [1, 2, 3] // initialize an array with values
    a[2] = 4               // set a member to a value
    field[4] b = [42; 4]   // initialize an array of 4 values all equal to 42
    field[4] c = [...a, 4] // initialize an array copying values from `a`, followed by 4
    field[2] d = a[1..3]   // initialize an array copying a slice from `a`
    return a[0] + b[1] + c[2]

Functions

A function has to be declared at the top level before it is called.

def foo() -> (field):
    return 1

def main() -> (field):
    return foo()

A function's signature has to be explicitly provided. Functions can return many values by providing them as a comma-separated list.

def main() -> (field, field[3]):
    return 1, [2, 3, 4]

Inference

When defining a variable as the return value of a function, types are optional:

def foo() -> (field, field):
    return 21, 42

def main() -> (field):
    a, b = foo()
    return 1

If there is an ambiguity, providing the types of some of the assigned variables is necessary.

def foo() -> (field, field[3]):
    return 1, [2, 3, 4]

def foo() -> (field, field):
    return 1, 2

def main() -> (field):
    a, field[3] b = foo()
    return 1

Control Flow

ZoKrates provide a single thread of execution with two control flow constructs.

Function calls

Function calls can help make programs clearer and more modular. However, using function calls is not always zero-cost, so deep call chains should be avoided.

Arguments are passed by value.

def incr(field a) -> (field):
    a = a + 1
    return a

def main() -> (field):
    field x = 1
    field res = incr(x)
    x == 1 // x has not changed
    return 1

If expressions

An if expression allows you to branch your code depending on a condition.

def main(field x) -> (field):
  field y = if x + 2 == 3 then 1 else 5 fi
  return y

The condition supports <, <=, >, >=, ==, which can be combined with the boolean operators &&, || and !.

When it comes to inequality checks, there is a caveat: when executing a < b, both a and b will be asserted to be strictly lower than the biggest power of 2 lower than p/2. This means that a and b are both asserted to be between 0 and 2**252 - 1. The same applies to other inequality checks.

For loops

For loops are available with the following syntax:

def main() -> (field):
    field res = 0
    for field i in 0..4 do
        res = res + i
    endfor
    return res

The bounds have to be known at compile time, so only constants are allowed. For-loops define their own scope.

Imports

You can separate your code into multiple ZoKrates files using import statements:

Relative Imports

You can import a resource in the same folder directly, like this:

import "./mycode.code"

There also is a handy syntax to import from the parent directory:

import "../mycode.code"

Also imports further up the file-system are supported:

import "../../../mycode.code"

You can also choose to rename the imported resource, like so:

import "./mycode.code" as abc

Absolute Imports

Absolute imports don't start with ./ or ../ in the path and are used to import components from the ZoKrates standard library. Please check the according section for more details. `

Comments

Comments can be added with double-slashes.

def main() -> (field):
    field a = 42 // this is an end of line comment
    // this is a full line comment
    return a

Standard library

ZoKrates comes with a number of reusable components which are defined at ./stdlib/ in the ZoKrates root repository. In order to import the standard library as described in the imports section the $ZOKRATES_HOME environment variable needs to be set to the stdlib folder. The standard library is solely based on the ZoKrates DSL and can be easily extended.

The following section highlights a subset of available imports:

Hashes

sha256

import "hashes/sha256/512Padded.code"

A function that takes 2 field[256] arrays as inputs and returns their sha256 compression function as an array of 256 field elements.

sha256compression

import "hashes/sha256/512bit.code"

A function that takes 2 field[256] arrays as inputs and returns their sha256 compression function as an array of 256 field elements. The difference with sha256 is that no padding is added at the end of the message, which makes it more efficient but also less compatible with Solidity.

There also is support for 2-round (1024-bit input) and and 3-round (1536-bit input) variants, using hashes/1024bit.code or hashes/1536bit.code respectively.

sha256packed

import "hashes/sha256/512bitPacked.code"

A function that takes an array of 4 field elements as inputs, unpacks each of them to 128 bits (big endian), concatenates them and applies sha256. It then returns an array of 2 field elements, each representing 128 bits of the result.

Public-key Cryptography

Proof of private-key ownership

import "ecc/proofOfOwnership.code"

Verifies match of a given public/private keypair. Checks if the following equation holds for the provided keypair:
pk = sk*G
where G is the chosen base point of the subgroup and * denotes scalar multiplication in the subgroup.

Signature verification

import "signatures/verifyEddsa.code"

Verifies an EdDSA Signature. Checks the correctness of a given EdDSA Signature (R,S) for the provided public key A and message (M0, M1). Check out this python repository for tooling to create valid signatures.

Packing / Unpacking

pack128

import "utils/pack/pack128"

Packs 128 field elements as one.

unpack128

import "utils/pack/unpack128"

Unpacks a field element to 128 field elements.

unpack256

import "utils/pack/unpack256"

Unpacks a field element to 256 field elements.

ZoKrates Reference

The reference covers the details of various areas of ZoKrates.

Command Line Tool

ZoKrates provides a command line interface. You can see an overview of the available subcommands by running

./zokrates

compile

./zokrates compile -i /path/to/add.code

Compiles a .code file into ZoKrates internal representation of arithmetic circuits.

Creates a compiled .code file at ./out.code.

compute-witness

./zokrates compute-witness -a 1 2 3

Computes a witness for the compiled program found at ./out.code and arguments to the program. A witness is a valid assignment of the variables, which include the results of the computation. Arguments to the program are passed as a space-separated list with the -a flag, or over stdin.

Creates a witness file at ./witness

setup

./zokrates setup

Generates a trusted setup for the compiled program found at ./out.code.

Creates a proving key and a verifying key at ./proving.key and ./verifying.key. These keys are derived from a source of randomness, commonly referred to as “toxic waste”. Anyone having access to the source of randomness can produce fake proofs that will be accepted by a verifier following the protocol.

export-verifier

./zokrates export-verifier

Using the verifying key at ./verifying.key, generates a Solidity contract which contains the generated verification key and a public function to verify a solution to the compiled program at ./out.code.

Creates a verifier contract at ./verifier.sol.

generate-proof

./zokrates generate-proof

Using the proving key at ./proving.key, generates a proof for a computation of the compiled program ./out.code resulting in ./witness.

Returns the proof, for example:

A = 0x45582d7906c967b1fd1cac0aad3efefa526e4cd888b8ecb5907b46c2eb1f781, 0x8158089a63a6aafa4afc3bbfd5ebf392e5ef61d0c5faf2e2445c9112450f29c
A_p = 0x5e4fe0bfa79a571b8918138ee5d7b3d0ad394c9bb8f7d2e1549f7e3c3bab7e9, 0x1708b5ba3d138e433406c792f679ae6902fc9f7c6131305a9a5f826dbe2d71fb
B = [0x34f5c5b7518597452e55a69bf9171a63837a98a1c1c1870b610b2cfe79c4573, 0x18e56afd179d67960db838a8fdb128eb78d5dd2c1ffcd564f9d0dada928ed71f], [0xf160ea8d2dc33b564a45c0998309b4bf5a050cc8f6288793b7401b37d1eb1a2, 0x23ade8ba2c64300b5ff90e18641516407054a21179829252fd87f1bd61a3be34]
B_p = 0xc88b87d45f90da42b9c455da16dad76996ef5b1e859a4f0db7dcef4f7e3b2fd, 0x20ed7c62dd8c6c47506e6db1d4837daa42ae80b931227153054539dcbf6f3778
C = 0x2c230cbffbcb6211d2cf8f434df291a413721e3bef5ada4030d532d14b6ea504, 0x21421565f75429d0922c8cf00b68e4da23c61670e787ce6a5de14a5a86ebdcb0
C_p = 0xce11fe724ce1ce183c15c4f5405d9607d6c769422aa9f62f4868478324a2f5, 0x1e585b35ed22ef32fd70ef960818f1514d1dd94b3517c127e782de24173c69f9
H = 0x2306e74a1a7e318d2d3c40cbea708b0e0b91cd1548c9db6261fc2bd815740978, 0xde538e4e99b0e20e84cdbbd3bc08c37bca0af21edd67faf52bc4027a9b00f7c
K = 0x1868436121f271e9fbf78a8f75bb4077e2d4f208891793fd5b468afc3b05c0e4, 0x1021c3ecb15c3fd7340d4eb5bf446e1ad457020e4f8b7cc82f8af64507a35fbe

Passed to the verifier contract, this proof can be checked. For example, using web3, a call would look like the following:

Verifier.at(<verifier contract address>).verifyTx(A, A_p, B, B_p, C, C_p, H, K, [...publicInputs, ...outputs])

Where A, ..., K are defined as above (adding brackets and quotes: A = ["0x123", "0x345"]), publicInputs are the public inputs supplied to witness generation and outputs are the results of the computation.

Proving schemes

ZoKrates supports different proving schemes. All of the available schemes rely on the ALT_BN128 curve, which means that they're all compatible with Ethereum.

We identify the schemes by the reference to the paper that introduced them. Currently the options available are:

Name Paper CLI flag Requires libsnark
PGHR13 Here --proving-scheme pghr13 Yes
G16 Here --proving-scheme g16 No
GM17 Here --proving-scheme gm17 Yes

The default proving scheme is G16.

When not using the default, the CLI flag has to be provided for the following commands:

  • setup
  • export-verifier
  • generate-proof

G16 malleability

When using G16, developers should pay attention to the fact that an attacker seeing a valid proof can very easily generate a different but still valid proof. Therefore, depending on the use case, making sure on chain that the same proof cannot be submitted twice may not be enough to guarantee that attackers cannot replay proofs. Mechanisms to solve this issue include:

  • signed proofs
  • nullifiers
  • usage of an ethereum address as a public input to the program
  • usage of non-malleable schemes such as GM17

Verification

Passed to the verifier contract, this proof can be checked. For example, using web3, a call would look like the following:

Verifier.at(<verifier contract address>).verifyTx(A, A_p, B, B_p, C, C_p, H, K, [...publicInputs, ...outputs])

Where A, ..., K are defined as above (adding brackets and quotes: A = ["0x123", "0x345"]), publicInputs are the public inputs supplied to witness generation and outputs are the results of the computation.

ZIR

ZIR is the intermediate representation ZoKrates uses to represent programs. It is close to R1CS but still encapsulates witness generation.

Note that ZIR is still in development and can change without notice.

When generating R1CS constraints, very large numbers are often used, which can make reading ZIR hard for humans. To mitigate this, ZIR applies an isomorphism when displaying field elements: they are shown as members of the interval [- (p - 1)/2, (p - 1)/2]. In other words, the following mapping is used:

  • elements in [0, (p - 1)/2] map to themselves
  • elements in [(p + 1)/2, p - 1] map to themselves minus p

Therefore, instead of writing p - 1 as:

21888242871839275222246405745257275088548364400416034343698204186575808495616

... in ZIR, we simply write:

-1

Tutorial: Proving knowledge of a hash preimage

Let’s jump into ZoKrates by working through a hands-on project together!

We’ll implement an operation that's very typical in blockchain use-cases: proving knowledge of the preimage for a given hash digest.
In particular, we'll show how ZoKrates and the Ethereum blockchain can be used to allow a prover, let’s call her Peggy, to demonstrate beyond any reasonable doubt to a verifier, let’s call him Victor, that she knows a hash preimage for a digest chosen by Victor, without revealing what the preimage is.

Pre-requisites

Make sure you have followed the instructions in the Getting Started chapter and are able to run the "Hello World" example described there.

Computing a Hash using ZoKrates

We will start this tutorial by using ZoKrates to compute the hash for an arbitrarily chosen preimage, being the number 5 in this example.

First, we create a new file named hashexample.code with the following content:

import "hashes/sha256/512bitPacked.code" as sha256packed

def main(private field a, private field b, private field c, private field d) -> (field[2]):
    h = sha256packed([a, b, c, d])
    return h

The first line imports the sha256packed function from the ZoKrates standard library.

sha256packed is a SHA256 implementation that is optimized for the use in the ZoKrates DSL. Here is how it works: We want to pass 512 bits of input to sha256. However, a field value can only hold 254 bits due to the size of the underlying prime field we are using. As a consequence, we use four field elements, each one encoding 128 bits, to represent our input. The four elements are then concatenated in ZoKrates and passed to SHA256. Given that the resulting hash is 256 bit long, we split it in two and return each value as a 128 bit number.

In case you are interested in an example that is fully compliant with existing SHA256 implementations in Python or Solidity you can have a look at this blog post.

Our code is really just using the sha256packed, returning the computed hash.

Having our problem described in ZoKrates' DSL, we can now continue using ZoKrates for the rest of our workflow.

First, we compile the program into an arithmetic circuit using the compile command.

./zokrates compile -i hashexample.code

As a next step we can create a witness file using the following command:

./zokrates compute-witness -a 0 0 0 5

Using the flag -a we pass arguments to the program. Recall that our goal is to compute the hash for the number 5. Consequently we set a, b and c to 0 and d to 5.

Still here? Great! At this point, we can check the witness file for the return values:

grep '~out' witness

which should lead to the following output:

~out_0 263561599766550617289250058199814760685
~out_1 65303172752238645975888084098459749904

Hence, by concatenating the outputs as 128 bit numbers, we arrive at the following value as the hash for our selected pre-image : 0xc6481e22c5ff4164af680b8cfaa5e8ed3120eeff89c4f307c4a6faaae059ce10

Prove knowledge of pre-image

For now, we have seen that we can compute a hash using ZoKrates.

Let's recall our goal: Peggy wants to prove that she knows a preimage for a digest chosen by Victor, without revealing what the preimage is. Without loss of generality, let's now assume that Victor choses the digest to be the one we found in our example above.

To make it work, the two parties have to follow their roles in the protocol:

First, Victor has to specify what hash he is interested in. Therefore, we have to adjust the zkSNARK circuit, compiled by ZoKrates, such that in addition to computing the digest, it also validates it against the digest of interest, provided by Victor. This leads to the following update for hashexample.code:

import "hashes/sha256/512bitPacked.code" as sha256packed

def main(private field a, private field b, private field c, private field d) -> (field):
    h = sha256packed([a, b, c, d])
    h[0] == 263561599766550617289250058199814760685
    h[1] == 65303172752238645975888084098459749904
    return 1

Note that we now compare the result of sha256packed with the hard-coded correct solution defined by Victor. The lines which we added are treated as assertions: the verifier will not accept a proof where these constraints were not satisfied. Clearly, this program only returns 1 if all of the computed bits are equal.

So, having defined the program, Victor is now ready to compile the code:

./zokrates compile -i hashexample.code

Based on that Victor can run the setup phase and export verifier smart contract as a Solidity file:

./zokrates setup
./zokrates export-verifier

setup creates a verifiation.key file and a proving.key file. Victor gives the proving key to Peggy.

export-verifier creates a verifier.sol contract that contains our verification key and a function verifyTx. Victor deploys this smart contract to the Ethereum network.

Peggy provides the correct pre-image as an argument to the program.

./zokrates compute-witness -a 0 0 0 5

Finally, Peggy can run the command to construct the proof:

./zokrates generate-proof

As the inputs were declared as private in the program, they do not appear in the proof thanks to the zero knowledge property of the protocol.

ZoKrates creates a file, proof.json, consisting of the eight variables that make up the zkSNARKs proof. The verifyTx function in the smart contract deployed by Victor accepts these eight values, along with an array of public inputs. The array of public inputs consists of:

  • any public inputs to the main function, declared without the private keyword
  • the return values of the ZoKrates function

In the example we're considering, all inputs are private and there is a single return value of 1, hence Peggy has to define her public input array as follows: [1]

Peggy can then submit her proof by calling verifyTx.

Victor monitors the verification smart contract for the Verified event, which is emitted upon successful verification of a transaction. As soon as he observes the event triggered by a transaction from Peggy's public address, he can be sure that Peggy has a valid pre-image for the hash he set in the smart contract.

Conclusion

At this point, you’ve successfully ran you first zkSNARK on the Ethereum blockchain. Congratulations!

Remember that in this example only two parties were involved. This special case makes it easy to deal with the trust assumptions of zkSNARKs: only Victor was interested in verifying the claim by Peggy, hence he can trust his execution of the setup phase.

In general, multiple parties may be interested in verifying the correctness of Peggy's statement. For example, in the zero-knowledge based cryptocurrency Zcash, each node needs to be able to validate the correctness of transactions. In order to generalize the setup phase to these multi-party use-cases a tricky process, commonly referred to as “trusted setup” or "ceremony" needs to be conducted.

ZoKrates would welcome ideas to add support for such ceremonies!