gnark

License Go Report Card PkgGoDev

gnark is a framework to execute (and verify) algorithms in zero-knowledge. It offers a high-level API to easily design circuits and fast implementation of state of the art ZKP schemes.

gnark has not been audited and is provided as-is, use at your own risk. In particular, gnark makes no security guarantees such as constant time implementation or side-channel attack resistance.

gnark is optimized for amd64 targets (x86 64bits) and tested on Unix (Linux / macOS).

Get in touch: [email protected]

Proving systems

Curves

  • BLS377
  • BLS381
  • BN256
  • BW761

Getting started

Prerequisites

You’ll need to install Go.

Install gnark

go get github.com/consensys/gnark

Note that if you use go modules, in go.mod the module path is case sensitive (use consensys and not ConsenSys).

Workflow

Our blog post is a good place to start. In short:

  1. Implement the algorithm using gnark API (written in Go)
  2. r1cs, err := frontend.Compile(&circuit) to compile the circuit into a R1CS
  3. pk, vk := groth16.Setup(r1cs) to generate proving and verifying keys
  4. groth16.Prove(...) to generate a proof
  5. groth16.Verify(...) to verify a proof

Documentation

You can find the documentation here. In particular:

Examples and gnark usage

Examples are located in /examples.

/examples/cubic

  1. To define a circuit, one must implement the frontend.Circuit interface:
// Circuit must be implemented by user-defined circuits
type Circuit interface {
	// Define declares the circuit's Constraints
	Define(curveID gurvy.ID, cs *ConstraintSystem) error
}
  1. Here is what x**3 + x + 5 = y looks like
// CubicCircuit defines a simple circuit
// x**3 + x + 5 == y
type CubicCircuit struct {
	// struct tags on a variable is optional
	// default uses variable name and secret visibility.
	X frontend.Variable `gnark:"x"`
	Y frontend.Variable `gnark:",public"`
}

// Define declares the circuit constraints
// x**3 + x + 5 == y
func (circuit *CubicCircuit) Define(curveID gurvy.ID, cs *frontend.ConstraintSystem) error {
	x3 := cs.Mul(circuit.X, circuit.X, circuit.X)
	cs.AssertIsEqual(circuit.Y, cs.Add(x3, circuit.X, 5))
	return nil
}
  1. The circuit is then compiled (into a R1CS)
var circuit CubicCircuit

// compiles our circuit into a R1CS
r1cs, err := frontend.Compile(gurvy.BN256, &circuit)

Using struct tags attributes (similarly to json or xml encoders in Golang), frontend.Compile() will parse the circuit structure and allocate the user secret and public inputs [TODO add godoc link for details].

  1. The circuit can be tested like so:
assert := groth16.NewAssert(t)

{
	var witness CubicCircuit
	witness.X.Assign(42)
	witness.Y.Assign(42)

	assert.ProverFailed(r1cs, &witness)
}

{
	var witness CubicCircuit
	witness.X.Assign(3)
	witness.Y.Assign(35)
	assert.ProverSucceeded(r1cs, &witness)
}
  1. The APIs to call Groth16 algorithms:
pk, vk := groth16.Setup(r1cs)
proof, err := groth16.Prove(r1cs, pk, solution)
err := groth16.Verify(proof, vk, solution)

API vs DSL

While several ZKP projects chose to develop their own language and compiler for the frontend, we designed a high-level API, in plain Go.

Relying on Go —a mature and widely used language— and its toolchain, has several benefits.

Developers can debug, document, test and benchmark their circuits as they would with any other Go program. Circuits can be versionned, unit tested and used into standard continuous delivery workflows. IDE integration (we use VSCode) and all these features come for free and are stable across platforms.

Moreover, gnark is not a black box and exposes APIs like a conventional cryptographic library (think aes.encrypt([]byte)). Complex solutions need this flexibility — gRPC/REST APIs, serialization protocols, monitoring, logging, … are all few lines of code away.

Designing your circuit

Caveats

Three points to keep in mind when designing a circuit (which is close to constraint system programming):

  1. Under the hood, there is only one variable type (field element). TODO
  2. A for loop must have fix bounds. TODO
  3. if statements (named cs.Select() like in Prolog). TODO.

gnark standard library

Currently gnark provides the following components (see gnark/std):

  • The Mimc hash function
  • Merkle tree (binary, without domain separation)
  • Twisted Edwards curve arithmetic (for bn256 and bls381)
  • Signature (EdDSA Algorithm, following https://tools.ietf.org/html/rfc8032)
  • Groth16 verifier (1 layer recursive SNARK with BW761)

Benchmarks

It is difficult to fairly and precisely compare benchmarks between libraries. Some implementations may excel in conditions where others may not (available CPUs, RAM or instruction set, WebAssembly target, …). Nonetheless, it appears that gnark, is about three time faster than existing state-of-the-art.

Here are our measurements for the Prover. These benchmarks ran on a AWS c5a.24xlarge instance, with hyperthreading disabled.

The same circuit (computing 2^(2^x)) is benchmarked using gnark, bellman (bls381, ZCash), bellman_ce (bn256, matterlabs).

BN256

number of constraints 100000 32000000 64000000
bellman_ce (s/op) 0.43 106 214.8
gnark (s/op) 0.16 33.9 63.4
speedup x2.6 x3.1 x3.4

On large circuits, that’s over 1M constraints per second.

BLS381

number of constraints 100000 32000000 64000000
bellman (s/op) 0.6 158 316.8
gnark (s/op) 0.23 47.6 90.7
speedup x2.7 x3.3 x3.5

Resources requirements

Depending on the topology of your circuit(s), you’ll need from 1 to 2GB of RAM per million constraint. Algorithms are very memory intensive, so hyperthreading won’t help. Many physical cores will help, but at a point, throughput per core is decreasing.


Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

License

This project is licensed under the Apache 2 License – see the LICENSE file for details

GitHub

https://github.com/ConsenSys/gnark