gnark
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.
Get in touch: zkteam@consensys.net
Proving systems
Curves
- BLS377
- BLS381
- BN256
- BW761
Getting started
Prerequisites
gnark
is optimized for amd64
targets (x86 64bits) and tested on Unix (Linux / macOS).
You'll need to install Go.
Install gnark
go install github.com/consensys/gnark
Note that if you use go.mod
, the module path is github.com/consensys/gnark
(case sensitive).
Workflow
Our blog post is a good place to start. In short:
- Implement the algorithm using gnark API (written in Go)
r1cs, err := frontend.Compile(&circuit)
to compile the circuit into a R1CS
pk, vk := groth16.Setup(r1cs)
to generate proving and verifying keys
groth16.Prove(...)
to generate a proof
groth16.Verify(...)
to verify a proof
gnark
offers APIs and a CLI tool (gnark -h
for more information about setup
, prove
and verify
subcommands)
Documentation
You can find the documentation here. In particular:
- frontend (writing a circuit)
- backend (running zero-knowledge proof algorithms on the circuit)
Examples and gnark
usage
Examples are located in /examples
.
/examples/cubic
- 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
}
- 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
}
- 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].
- The circuit can be tested like so:
{
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)
}
- The APIs to call Groth16 algorithms:
pk, vk := groth16.Setup(r1cs)
proof, err := groth16.Prove(r1cs, pk, solution)
err := groth16.Verify(proof, vk, solution)
- Using the CLI
cd examples/cubic
go run cubic.go
gnark setup circuit.r1cs
gnark prove circuit.r1cs --pk circuit.pk --input input.json
gnark verify circuit.proof --vk circuit.vk --input input.json
The input file has a the following JSON format:
{
"x":"3",
"Y":"0xdeff12"
}
Values can be base10 or hexadecimal strings.
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.
Developpers 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 continious delivery workflows. IDE integration (we use VSCode) and all these features come for free and are stable accross 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):
- Under the hood, there is only one variable type (field element). TODO
- A
for
loop must have fix bounds. TODO
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 aglorithm, 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 twice faster than existing state-of-the-art.
Here are our measurements for the Prover. These benchmarks ran on a AWS z1d.6xlarge instance, with hyperthreading disabled (12 cores, 192GB RAM).
The same circuit is benchmarked using gnark
, bellman
(bls381, ZCash), bellman_ce
(bn256, matterlabs).
nb constraints |
65537 |
262145 |
1048577 |
4194305 |
16777217 |
33554433 |
bellman (ms/op) |
|
|
|
|
|
|
gnark (ms/op) |
|
|
|
|
|
|
gain |
|
|
|
|
|
|
In some settings, gnark
performance could be significantly improved. Get in touch if you'd like to know more: zkteam@consensys.net.
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