diff --git a/docs/Concepts/circuits.md b/docs/Concepts/circuits.md index f49d7f1..889bce2 100644 --- a/docs/Concepts/circuits.md +++ b/docs/Concepts/circuits.md @@ -16,4 +16,4 @@ An important point is that every component of a constraint (variables, inputs an On the other hand, a circuit reasoning on variables which live in $\mathbb{F}_r$ where $r\neq p$, has a high number of constraints because of the algebraic constraints needed to emulate the arithmetic modulo $r$ on a field of characteristic $p$. -Finally, the number of constraints in a circuit is limited; you cannot write arbitrarily large circuits. For example, using Groth16 on BN254, you cannot exceed ~$250M$ constraints. +Finally, the number of constraints in a circuit is limited by proving time, memory, and the parameters of the proving system. Measure and profile a representative circuit before assuming it fits your deployment budget. diff --git a/docs/Concepts/schemes_curves.md b/docs/Concepts/schemes_curves.md index 2d466ba..341ada3 100644 --- a/docs/Concepts/schemes_curves.md +++ b/docs/Concepts/schemes_curves.md @@ -4,9 +4,9 @@ description: Proving schemes and curves sidebar_position: 3 --- -# Prove schemes and curves +# Proving schemes and curves -`gnark` supports two proving schemes [Groth16](https://eprint.iacr.org/2016/260.pdf) and [PlonK](https://eprint.iacr.org/2019/953.pdf). These schemes can be instantiated with any of the following elliptic curves: _BN254_, _BLS12-381_, _BLS12-377_, _BLS24-315_, _BW6-633_ or _BW6-761_. +`gnark` supports two proving schemes: [Groth16](https://eprint.iacr.org/2016/260.pdf) and [PlonK](https://eprint.iacr.org/2019/953.pdf) with KZG commitments. These schemes can be instantiated with any of the following elliptic curves: _BN254_, _BLS12-381_, _BLS12-377_, or _BW6-761_. An ID is supplied to `gnark` to choose the proving scheme and the instantiating curve. @@ -73,7 +73,7 @@ Currently, `gnark` supports PlonK with KZG polynomial commitment. ## Choosing an elliptic curve -Both Groth16 and PlonK (with KZG scheme) need to be instantiated with an elliptic curve. `gnark` supports six elliptic curves: BN254, BLS12-381, BLS12-377, BW6-761, BLS24-315, and BW6-633. All these curves are defined over a finite field $\mathbb{F}_p$ and have an equation of the form $y^2=x^3+b$ ($b\in \mathbb{F}_p$). +Both Groth16 and PlonK (with KZG) need to be instantiated with an elliptic curve. `gnark` supports BN254, BLS12-381, BLS12-377, and BW6-761 for these proving backends. All these curves are defined over a finite field $\mathbb{F}_p$ and have an equation of the form $y^2=x^3+b$ ($b\in \mathbb{F}_p$). To work with Groth16 and PlonK, the curves must: @@ -126,14 +126,8 @@ Some applications that use one-layer proof composition include ZEXE, Celo, Aleo, ::: -### BLS24-315 and BW6-633 curves +## Small and emulated fields -In Groth16, elliptic curve operations take place in three different groups: $G_1$, $G_2$ and $G_T$, whereas in PlonK (with KZG) operations take place only in $G_1$ and $G_T$. While BN254, BLS12-381 and BLS12-377 are optimized for all the three groups, BLS24-315 is better optimized for $G_1$ only while still competitively optimized for $G_T$. Moreover, it comes in a 2-chain setting with BW6-633 to enable PlonK one-layer proof composition efficiently. +`gnark` also has constraint-system implementations for several small fields, including Babybear, Koalabear, Grumpkin, and Tinyfield. These are useful for specialized systems and some recursion workloads, but they are not the pairing-friendly Groth16/PlonK proving curves above. -In summary, (BLS24-315, BW6-633) is a pair of elliptic curves that: - -- Are secure, for proof soundness. -- Are pairing-friendly, for proof verification. -- Are optimized for KZG-based SNARKs (for example, PlonK). -- Have a highly 2-adic subgroup order, for efficient proof generation. -- For efficient proof composition, BW6-633 has a subgroup order equal to BLS24-315's field characteristic. +For arithmetic over a field different from the circuit's native field, use [`std/math/emulated`](https://pkg.go.dev/github.com/consensys/gnark/std/math/emulated). Emulated arithmetic is the foundation for gadgets such as ECDSA, pairing checks, and EVM precompiles. diff --git a/docs/HowTo/debug_test.md b/docs/HowTo/debug_test.md index 647ad11..1b8ea92 100644 --- a/docs/HowTo/debug_test.md +++ b/docs/HowTo/debug_test.md @@ -56,7 +56,7 @@ With solving errors and `api.Println`, `gnark` outputs a stack trace which conta ## Test -You can implement tests as Go unit tests, in a `_test.go` file. For example: +You can implement tests as Go unit tests in a `_test.go` file. The preferred pattern is `assert.CheckCircuit`, which can exercise both valid and invalid assignments and limit the test matrix to the curves and backends you care about: ```go // assert object wrapping testing.T @@ -65,18 +65,30 @@ assert := test.NewAssert(t) // declare the circuit var cubicCircuit Circuit -assert.ProverFailed(&cubicCircuit, &Circuit{ - PreImage: 42, - Hash: 42, -}) +assert.CheckCircuit(&cubicCircuit, + test.WithValidAssignment(&Circuit{ + PreImage: "16130099170765464552823636852555369511329944820189892919423002775646948828469", + Hash: "12886436712380113721405259596386800092738845035233065858332878701083870690753", + }), + test.WithInvalidAssignment(&Circuit{ + PreImage: 42, + Hash: 42, + }), + test.WithCurves(ecc.BN254), +) +``` + +The older `assert.ProverSucceeded` and `assert.ProverFailed` helpers remain useful for simple cases: +```go assert.ProverSucceeded(&cubicCircuit, &Circuit{ - PreImage: 35, - Hash: "16130099170765464552823636852555369511329944820189892919423002775646948828469", + PreImage: "16130099170765464552823636852555369511329944820189892919423002775646948828469", + Hash: "12886436712380113721405259596386800092738845035233065858332878701083870690753", }, test.WithCurves(ecc.BN254)) - ``` -See the [test package documentation](https://pkg.go.dev/github.com/consensys/gnark/test@v0.7.0) for more details. +Use `test.WithBackends(backend.GROTH16, backend.PLONK)` to restrict the backends under test. The `debug` build tag is still available for more verbose circuit diagnostics. There are also `prover_checks` and `release_checks` build tags that enable additional prover-side checks while debugging. + +See the [test package documentation](https://pkg.go.dev/github.com/consensys/gnark/test) for more details. In particular, the default behavior of the assert helper is to test the circuit across all supported curves and backends, ensure correct serialization, and cross-test the constraint system solver against a `big.Int` test execution engine. diff --git a/docs/HowTo/get_started.md b/docs/HowTo/get_started.md index 35f3849..27b8b7d 100644 --- a/docs/HowTo/get_started.md +++ b/docs/HowTo/get_started.md @@ -17,14 +17,8 @@ sidebar_position: 1 go get github.com/consensys/gnark@latest ``` -:::note - -If you use Go modules, in `go.mod` the module path is case sensitive (use `consensys` and not `ConsenSys`). - -::: - :::info -`gnark` is optimized for `amd64` targets (`x86 64bits`) and tested on Unix (Linux / macOS). +`gnark` targets Go 1.25 or newer. It is optimized for `amd64` and also supports experimental GPU acceleration for Groth16 through the ICICLE backend. ::: diff --git a/docs/HowTo/prove.md b/docs/HowTo/prove.md index 2a426ae..28b4b59 100644 --- a/docs/HowTo/prove.md +++ b/docs/HowTo/prove.md @@ -18,7 +18,7 @@ Once the [circuit](write/circuit_structure.md) is [compiled](compile.md), you ca :::note -Supported zk-SNARK backends are under `gnark/backend`. `gnark` currently implements `Groth16` and an experimental version of `PlonK`. +Supported zk-SNARK backends are under `gnark/backend`. `gnark` currently implements `Groth16` and `PlonK` with KZG commitments. ::: @@ -36,21 +36,38 @@ proof, err := groth16.Prove(cs, pk, witness) // 3. Proof verification err := groth16.Verify(proof, vk, publicWitness) - ``` ```go +// Compile a circuit with the PlonK arithmetization. +ccs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &circuit) +if err != nil { + return err +} + +// For development and tests only. In production, load a securely generated SRS. +srs, srsLagrange, err := unsafekzg.NewSRS(ccs.(*cs.SparseR1CS)) +if err != nil { + return err +} + // 1. One time setup -publicData, _ := plonk.Setup(cs, ...) // WIP +pk, vk, err := plonk.Setup(ccs, srs, srsLagrange) +if err != nil { + return err +} // 2. Proof creation -proof, err := plonk.Prove(r1cs, publicData, witness) +proof, err := plonk.Prove(ccs, pk, witness) +if err != nil { + return err +} // 3. Proof verification -err := plonk.Verify(proof, publicData, publicWitness) +return plonk.Verify(proof, vk, publicWitness) ``` @@ -73,10 +90,21 @@ assignment := &Circuit { X: 3, Y: 35, } -witness, _ := frontend.NewWitness(assignment, ecc.BN254) +witness, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField()) +if err != nil { + return err +} +publicWitness, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField(), frontend.PublicOnly()) +if err != nil { + return err +} + // use the witness directly in zk-SNARK backend APIs -groth16.Prove(cs, pk, witness) -// test file --> assert.ProverSucceeded(cs, &witness) +proof, err := groth16.Prove(cs, pk, witness) +if err != nil { + return err +} +return groth16.Verify(proof, vk, publicWitness) ``` :::tip @@ -87,17 +115,28 @@ If witness is not built within the same process, or in another programming langu ## Verify a `Proof` on Ethereum -On `ecc.BN254` + `Groth16`, `gnark` can export the `groth16.VerifyingKey` as a solidity smart contract. - -Refer to [the code example](https://github.com/Consensys-Incorporated/gnark-tests/blob/main/solidity/contract/main.go) and [end-to-end integration test](https://github.com/Consensys-Incorporated/gnark-tests/blob/47873ce8e146c1f74477a15972ec63cbfd73c888/solidity/solidity_test.go#L81) using a `geth` simulated blockchain. +On `ecc.BN254` + `Groth16`, `gnark` can export the `groth16.VerifyingKey` as a Solidity smart contract. Solidity export is also available for `PlonK` on BN254. ```go // 1. Compile (Groth16 + BN254) -cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &myCircuit) +cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &myCircuit) +if err != nil { + return err +} // 2. Setup pk, vk, err := groth16.Setup(cs) +if err != nil { + return err +} -// 3. Write solidity smart contract into a file -err = vk.ExportSolidity(f) +// 3. Write a Solidity smart contract into a file. +// +// In production, create proofs and verify native proofs with the Solidity-compatible +// options: +// proof, err := groth16.Prove(cs, pk, witness, +// solidity.WithProverTargetSolidityVerifier(backend.GROTH16)) +// err := groth16.Verify(proof, vk, publicWitness, +// solidity.WithVerifierTargetSolidityVerifier(backend.GROTH16)) +return vk.ExportSolidity(f, solidity.WithPragmaVersion("^0.8.0")) ``` diff --git a/docs/HowTo/serialize.md b/docs/HowTo/serialize.md index 91273cb..fec59af 100644 --- a/docs/HowTo/serialize.md +++ b/docs/HowTo/serialize.md @@ -14,7 +14,7 @@ To serialize a `gnark` object: ```go // compile a circuit -cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &circuit) +cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) // cs implements io.WriterTo var buf bytes.Buffer @@ -115,29 +115,57 @@ If the witness creation and proof creation live in the same process, refer to [C ::: -```go title="Full witness in Go" +```go title="Witness binary encoding" // witness var assignment cubic.Circuit assignment.X = 3 assignment.Y = 35 -witness, _ := frontend.NewWitness(&assignment, ecc.BN254) +witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) +if err != nil { + return err +} -// Binary marshalling +// Binary encoding data, err := witness.MarshalBinary() - -// JSON marshalling -json, err := witness.MarshalJSON() +if err != nil { + return err +} ... -// recreate a witness -witness, err := witness.New(ecc.BN254, ccs.GetSchema()) // note that schema is optional for binary encoding +// Recreate an empty witness for the circuit's field and decode it. +decoded, err := witness.New(ecc.BN254.ScalarField()) +if err != nil { + return err +} +if err := decoded.UnmarshalBinary(data); err != nil { + return err +} -// Binary unmarshalling -err := witness.UnmarshalBinary(data) +// extract the public part only +publicWitness, err := decoded.Public() +if err != nil { + return err +} +``` -// JSON unmarshalling -err := witness.UnmarshalJSON(json) +JSON encoding and decoding require the circuit schema. Build it directly from the circuit structure: -// extract the public part only -publicWitness, _ := witness.Public() +```go title="Witness JSON encoding" +schema, err := frontend.NewSchema(ecc.BN254.ScalarField(), &assignment) +if err != nil { + return err +} + +json, err := witness.ToJSON(schema) +if err != nil { + return err +} + +decoded, err := witness.New(ecc.BN254.ScalarField()) +if err != nil { + return err +} +if err := decoded.FromJSON(schema, json); err != nil { + return err +} ``` diff --git a/docs/HowTo/write/hints.md b/docs/HowTo/write/hints.md index 0372d07..92ce791 100644 --- a/docs/HowTo/write/hints.md +++ b/docs/HowTo/write/hints.md @@ -6,29 +6,64 @@ sidebar_position: 6 # Compiler hints -Instead of computing some value within a circuit, it's sometimes more optimal to compute the value off-circuit and only verify the correctness of the computation in a circuit. `gnark` allows you to do this through _hints_. From the prover's point of view, hints are essentially input variables provided by a hint function instead of the user. +Instead of computing every value inside a circuit, it is sometimes more efficient to compute a value outside the circuit and verify only the properties required for soundness. `gnark` calls these off-circuit computations _hints_. During solving, the prover obtains hint outputs from a hint function and then uses those outputs in constraints. -For example, consider a decomposition of an integer `a` into bits. A naive way to decompose it is to look at the binary representation of the integer and extract bits from this representation, but `gnark` doesn't currently provide native bit operations. Instead, the hint function `hint.IthBit` can provide the bits as variables and the user must constrain these variables as a weighted sum which equals to `a`. In a circuit it would look like: +For example, the circuit API can decompose an integer into bits directly: ```go - var b []frontend.Variable - var Σbi frontend.Variable - base := 1 - for i := 0; i < nBits; i++ { - b[i] = cs.NewHint(hint.IthBit, a, i) - cs.AssertIsBoolean(b[i]) - Σbi = api.Add(Σbi, api.Mul(b[i], base)) - base = base << 1 - } - cs.AssertIsEqual(Σbi, a) +bits := api.ToBinary(circuit.Value, nBits) ``` -This method is also implemented in the front end as `ToBinary(...)` interface method. +This is preferable to hand-rolling a decomposition with a hint. -`gnark` provides a [list of built-in hint functions](https://pkg.go.dev/github.com/consensys/gnark/backend/hint#Function). +## Implement a hint -## Implement hint functions +A hint is a Go function of type `solver.Hint`: -You can define your own hint functions in addition to built-in hint functions. You can provide any instance satisfying the [`hint.Function`](https://pkg.go.dev/github.com/consensys/gnark/backend/hint#Function) to `api.NewHint(...)` method to compute the hint value. Additionally, you must provide the hint function as a [`backend.WithHints`](https://pkg.go.dev/github.com/consensys/gnark/backend#WithHints) option to the back end, so the back end can access the hint function. +```go +func bitsHint(field *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 1 { + return fmt.Errorf("unexpected hint arity") + } + + a := inputs[0] + for i := range outputs { + outputs[i].SetUint64(uint64(a.Bit(i))) + } + return nil +} +``` + +Use `api.Compiler().NewHint` in the circuit to request outputs, then constrain them: + +```go +b, err := api.Compiler().NewHint(bitsHint, nBits, circuit.Value) +if err != nil { + return err +} + +var weightedSum frontend.Variable + +for i, bit := range b { + api.AssertIsBoolean(bit) + weightedSum = api.Add(weightedSum, api.Mul(bit, 1< ```go +type mimcCircuit struct { + Data frontend.Variable + Hash frontend.Variable `gnark:",public"` +} + func (circuit *mimcCircuit) Define(api frontend.API) error { - // ... - hFunc, _ := mimc.NewMiMC(api.Curve()) - computedHash := hFunc.Hash(cs, circuit.Data) - // ... + hFunc, err := mimc.NewMiMC(api) + if err != nil { + return err + } + hFunc.Write(circuit.Data) + api.AssertIsEqual(circuit.Hash, hFunc.Sum()) + return nil } ``` @@ -27,17 +35,24 @@ func (circuit *mimcCircuit) Define(api frontend.API) error { ```go type eddsaCircuit struct { - PublicKey eddsa.PublicKey `gnark:",public"` - Signature eddsa.Signature `gnark:",public"` - Message frontend.Variable `gnark:",public"` + curveID tedwards.ID + PublicKey eddsa.PublicKey `gnark:",public"` + Signature eddsa.Signature `gnark:",public"` + Message frontend.Variable `gnark:",public"` } func (circuit *eddsaCircuit) Define(api frontend.API) error { - edCurve, _ := twistededwards.NewEdCurve(api.Curve()) - circuit.PublicKey.Curve = edCurve + curve, err := twistededwards.NewEdCurve(api, circuit.curveID) + if err != nil { + return err + } + + hFunc, err := mimc.NewMiMC(api) + if err != nil { + return err + } - eddsa.Verify(cs, circuit.Signature, circuit.Message, circuit.PublicKey) - return nil + return eddsa.Verify(curve, circuit.Signature, circuit.Message, circuit.PublicKey, &hFunc) } ``` @@ -46,14 +61,20 @@ func (circuit *eddsaCircuit) Define(api frontend.API) error { ```go type merkleCircuit struct { - RootHash frontend.Variable `gnark:",public"` - Path, Helper []frontend.Variable + MerkleProof merkle.MerkleProof + Leaf frontend.Variable } func (circuit *merkleCircuit) Define(api frontend.API) error { - hFunc, _ := mimc.NewMiMC(api.Curve()) - merkle.VerifyProof(cs, hFunc, circuit.RootHash, circuit.Path, circuit.Helper) - return nil + hFunc, err := mimc.NewMiMC(api) + if err != nil { + return err + } + + // Path[0] is the leaf. The proof index is encoded in little-endian bit + // order in Path[1:]. + circuit.MerkleProof.VerifyProof(api, &hFunc, circuit.Leaf) + return nil } ``` @@ -64,18 +85,27 @@ Enables verifying a _BLS12_377_ Groth16 `Proof` inside a _BW6_761_ circuit ```go type verifierCircuit struct { - InnerProof Proof - InnerVk VerifyingKey - Hash frontend.Variable + Proof recursion_groth16.Proof[sw_bls12377.G1Affine, sw_bls12377.G2Affine] + VerifyingKey recursion_groth16.VerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT] + InnerWitness recursion_groth16.Witness[sw_bls12377.ScalarField] `gnark:",public"` } func (circuit *verifierCircuit) Define(api frontend.API) error { - - groth16.Verify(api, circuit.InnerVk, circuit.InnerProof, []frontend.Variable{circuit.Hash}) - - return nil + verifier, err := recursion_groth16.NewVerifier[ + sw_bls12377.ScalarField, + sw_bls12377.G1Affine, + sw_bls12377.G2Affine, + sw_bls12377.GT, + ](api) + if err != nil { + return err + } + + return verifier.AssertProof(circuit.VerifyingKey, circuit.Proof, circuit.InnerWitness) } ``` +For a complete flow, see [`std/recursion/groth16/verifier_test.go`](https://github.com/Consensys-Incorporated/gnark/blob/v0.16.3/std/recursion/groth16/verifier_test.go). It shows how to compile the inner circuit, prove it natively, convert the proof, verifying key, and public witness with `ValueOf*`, and solve the outer circuit. + diff --git a/docs/Reference/api.md b/docs/Reference/api.md index 209538f..ea53ffd 100644 --- a/docs/Reference/api.md +++ b/docs/Reference/api.md @@ -10,5 +10,7 @@ Refer to the GoDoc for API documentation and examples. - [Front end](https://pkg.go.dev/github.com/consensys/gnark/frontend) - [Back end](https://pkg.go.dev/github.com/consensys/gnark/backend) +- [Standard library](https://pkg.go.dev/github.com/consensys/gnark/std) +- [Circuit test engine](https://pkg.go.dev/github.com/consensys/gnark/test) Note that the elliptic curve, field arithmetic, FFT, polynomial commitment schemes, hash function, and signature verification is provided by the [`gnark-crypto` package](https://github.com/Consensys-Incorporated/gnark-crypto) diff --git a/docs/Tutorials/eddsa.md b/docs/Tutorials/eddsa.md index cac19ea..e3c8a0d 100644 --- a/docs/Tutorials/eddsa.md +++ b/docs/Tutorials/eddsa.md @@ -1,376 +1,152 @@ --- title: EdDSA -description: How to check an EdDSA signature inside a zkSNARK circuit +description: Verify an EdDSA signature inside a zk-SNARK circuit sidebar_position: 1 --- # EdDSA -This tutorial walks through the implementation of a circuit asserting that an [EdDSA signature](https://en.wikipedia.org/wiki/EdDSA) is correct. +This tutorial verifies an EdDSA signature inside a zk-SNARK circuit. It is useful in zk-rollup-style applications where an operator proves that a batch of user signatures is valid. -If you are interested in how to use EdDSA in a zk-SNARK, refer to [Test the circuit](#test-the-circuit). - -:::note EdDSA in a zk-SNARK is of particular interest for zk-Rollups - -A zk-Rollup operator batch processes many signed transactions from its users, and updates their state accordingly. - -zk-Rollup operator creates a zk-SNARK proof attesting all the transactions are valid, and must verify that the signatures are correct inside a zk-SNARK circuit. - -::: +EdDSA in `gnark` uses twisted Edwards curves defined over the SNARK field. These companion curves are commonly known as JubJub for BLS12-381 and Baby JubJub for BN254. Using a companion curve avoids the high cost of emulating an external curve inside the native SNARK field. ## Write the circuit -:::info - -The EdDSA signature scheme does not use standard curves such as ed1559. In a zk-SNARK circuit, variables live in $\mathbb{F}_r$, which is different from the ed1559's field of definition. This is further explained in the [Circuit section](../Concepts/circuits.md). - -To settle this issue, special twisted Edwards curves have been created which are defined on $\mathbb{F}_r$. They have been called [JubJub](https://z.cash/technology/jubjub/) for _BLS12_381_ companion curves, and [Baby JubJub](https://github.com/ethereum/EIPs/pull/2494) for _BN254_ companion curves. - -In [`gnark-crypto`](https://github.com/consensys/gnark-crypto), they are defined under `gnark-crypto/ecc/bn254{bls12381,...}/twistededwards`. - -::: - -The EdDSA workflow is as follows: - -1. Sign a message, this happens outside of the zk-SNARK circuit: - - ```go - privateKey, publicKey := eddsa.New(..) - signature := privateKey.Sign(message) - ``` - -1. Verify the EdDSA signature inside the zk-SNARK circuit: - - ```go - assert(isValid(signature, message, publicKey)) - ``` - -### Witness and data structures - -What variables are needed (the witness) to verify the EdDSA signature? - -1. The signer's public key - - The public key is a point on the twisted Edward curve, so a tuple $(x,y)$. We also need to store the parameters of the twisted Edwards curve in the public key, so that when accessing a public key you can access to the corresponding curve. - - Let's create the `struct` containing the twisted Edwards curve parameter: - - ```go - // CurveParams twisted edwards curve parameters ax^2 + y^2 = 1 + d*x^2*y^2 - // Matches gnark-crypto curve specific params - type CurveParams struct { - A, D, Cofactor, Order *big.Int - Base [2]*big.Int // base point coordinates - } - ``` - - :::note - - `gnark` supports different curves and provides an ID to specify the curve to use. - - ::: - - Now you can define the `struct` storing the public key: - - ```go - package eddsa - - import "github.com/consensys/gnark/std/algebra/twistededwards" - - type PublicKey struct { - A twistededwards.Point - } - ``` - - :::note - - The package `twistededwards` defines `twistededwards.Point` as a tuple $(x,y)$ of `frontend.Variable`. - - This structure has the associated methods to the elliptic curve group structure, like scalar multiplication. - - ::: - -1. The signature - - An EdDSA signature of a message (which we assume is already hashed) is a tuple $(R,S)$ where $R$ is a point $(x,y)$ on the twisted Edwards curve, and $S$ is a scalar. The scalar $S$ is used to perform a scalar multiplication on the twisted Edwards curve. - - Now you can define the structure for storing a signature: - - ```go - import "github.com/consensys/gnark/frontend" - - // Signature stores a signature (to be used in gnark circuit) - // An EdDSA signature is a tuple (R,S) where R is a point on the twisted Edwards curve - // and S a scalar. Since the base field of the twisted Edwards is Fr, the number of points - // N on the Edwards is < r+1+2sqrt(r)+2 (since the curve has 2 points of multiplicity 2). - // The subgroup l used in eddsa is <1/2N, so the reduction - // mod l ensures S < r, therefore there is no risk of overflow. - type Signature struct { - R twistededwards.Point - S frontend.Variable - } - ``` - -### Circuit definition - -Now that the `Signature` and `PublicKey` structures are created, you can write the core of the EdDSA verification algorithm. - -Let's recall the operations of the signature verification. Let $G$ be the base point of the twisted Edward curve, that is the point such that $[k]G=A$, where $k$ is the secret key of the signer, and $A$ its public key. Given a message $M$, a signature $(R,S)$, a public key $A$, and a hash function $H$ (the same that has been used for signing), the verifier must check that the following relation holds: - -$$ -[2^c*S]G = [2^c]R +[2^cH(R,A,M)]A -$$ - -:::info - -$c$ is either $2$ or $3$, depending on the twisted Edwards curve. - -::: - -First, define the signature of the `Verify` function. This function needs a signature, a message and a public key. It also needs a `frontend.ConstraintSystem` object, on which the functions from the [gnark API](../HowTo/write/circuit_api.md) are called. +The circuit carries the public key, signature, and message. An untagged `curveID` field selects the companion curve: ```go -func Verify(api frontend.API, sig Signature, msg frontend.Variable, pubKey PublicKey) error { - // ... +type eddsaCircuit struct { + curveID tedwards.ID + PublicKey eddsa.PublicKey `gnark:",public"` + Signature eddsa.Signature `gnark:",public"` + Message frontend.Variable `gnark:",public"` } ``` -The first operation is to compute $H(R,A,M)$. The hash function is not given as parameters here, because only a specific snark-friendly hash function can be used, you therefore hard code the use of the `mimc` hash function: +`Define` constructs the companion curve and a MiMC hasher, then delegates verification to the maintained `eddsa` gadget: ```go -import ( - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/algebra/twistededwards" - "github.com/consensys/gnark/std/hash/mimc" -) - -func Verify(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pubKey PublicKey, hash hash.Hash) error { - - // compute H(R, A, M) - data := []frontend.Variable{ - sig.R.A.X, - sig.R.A.Y, - pubKey.A.X, - pubKey.A.Y, - msg, - } - hramConstant := hash.Hash(cs, data...) - - return nil +func (circuit *eddsaCircuit) Define(api frontend.API) error { + curve, err := twistededwards.NewEdCurve(api, circuit.curveID) + if err != nil { + return err + } + + hFunc, err := mimc.NewMiMC(api) + if err != nil { + return err + } + + return eddsa.Verify( + curve, + circuit.Signature, + circuit.Message, + circuit.PublicKey, + &hFunc, + ) } ``` -Next you compute the left-hand side of the equality, that is $[2^c*S]G$: - -```go - // [2^basis*S1]G - lhs.ScalarMulFixedBase(cs, pubKey.Curve.BaseX, pubKey.Curve.BaseY, sig.S1, pubKey.Curve). - ScalarMulNonFixedBase(cs, &lhs, basis, pubKey.Curve) - - // [S2]G - tmp := twistededwards.Point{} - tmp.ScalarMulFixedBase(cs, pubKey.Curve.BaseX, pubKey.Curve.BaseY, sig.S2, pubKey.Curve) - - // [2^basis*S1 + S2]G - lhs.AddGeneric(cs, &lhs, &tmp, pubKey.Curve) - - // [2^c*(2^basis*S1 + S2)]G - lhs.ScalarMulNonFixedBase(cs, &lhs, cofactorConstant, pubKey.Curve) - - lhs.MustBeOnCurve(cs, pubKey.Curve) -``` - -:::note - -Notice the use of `ScalarMulFixedBase` when the point coordinates are in `big.Int`, and `ScalarMulNonFixedBase` when the point coordinates are in `frontend.Variable`. The former costs less constraints, so it should be used whenever the coordinates of the point to multiply are not of type `frontend.Variable`. +The gadget verifies that the scalar is in the group order, performs the required double-base scalar multiplication, checks the resulting point, applies the cofactor, and asserts the verification equation. -::: +## Generate an out-of-circuit signature -Next, continue the implementation with the computation of the right-hand side: +Generate the key and signature with `gnark-crypto`. For BN254, use the matching MiMC hasher: ```go - //rhs = [2^c]R+[2^cH(R,A,M)]A - // M: message - // A: public key - // R: from the signature (R,S) - rhs := twistededwards.Point{} - rhs.ScalarMulNonFixedBase(cs, &pubKey.A, hramConstant, pubKey.Curve). - AddGeneric(cs, &rhs, &sig.R.A, pubKey.Curve). - ScalarMulNonFixedBase(cs, &rhs, cofactorConstant, pubKey.Curve) - rhs.MustBeOnCurve(cs, pubKey.Curve) -``` +randomness := rand.New(rand.NewSource(seed)) //#nosec G404 -- deterministic tutorial input -:::tip Debugging - -You can print values using `api.Println` that behaves like `fmt.Println`, except it will output the values at proving time (when they are solved). - -```go -api.Println("A.X", pubKey.A.X) -``` - -::: +privateKey, err := eddsa.New(tedwards.BN254, randomness) +if err != nil { + return err +} -Until now, you have only used objects which are defined in the `gnark` standard library, for example, the `twistededwards` library and the `mimc` library. For all the methods that you used, you passed the `cs` parameter, of type `*frontend.ConstraintSystem`, which contains the description of the constraint system. However, you never actually used the [gnark API](../HowTo/write/circuit_api.md). +hFunc := hash.MIMC_BN254.New() +msg := []byte{0xde, 0xad, 0xf0, 0x0d} -Use the gnark API, to assert that the left-hand side is equal to the right-hand side: +signature, err := privateKey.Sign(msg, hFunc) +if err != nil { + return err +} -```go - // ensures that lhs==rhs - api.AssertIsEqual(lhs.X, rhs.X) - api.AssertIsEqual(lhs.Y, rhs.Y) +publicKey := privateKey.Public() +valid, err := publicKey.Verify(signature, msg, hFunc) +if err != nil { + return err +} +if !valid { + return errors.New("out-of-circuit verification failed") +} ``` -:::info - -Currently, `AssertIsEqual` doesn't work on arbitrary structure. - -Therefore to enforce equality between the left-hand side (lhs) and the right-hand side (rhs), you must use `AssertIsEqual` on the X and Y part of the `lhs` and the `rhs` individually. - -::: - -## Test the circuit - -You successfully implemented EdDSA in a zkSNARK, next you need to test it. +## Assign and test the circuit -You need a structure implementing a `Define` function as described in [the circuit structure page](../HowTo/write/circuit_structure.md). The structure should contain the witnesses as `frontend.Variable` that are needed for an EdDSA signature verification. You need a public key, a signature $(R,S)$, and a message: +The `Assign` helpers decode the compressed public key and signature into circuit-friendly values: ```go -import ( - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/signature/eddsa" +var circuit eddsaCircuit +circuit.curveID = tedwards.BN254 + +var assignment eddsaCircuit +assignment.curveID = tedwards.BN254 +assignment.Message = msg +assignment.PublicKey.Assign(tedwards.BN254, publicKey.Bytes()) +assignment.Signature.Assign(tedwards.BN254, signature) + +assert := test.NewAssert(t) +assert.CheckCircuit(&circuit, + test.WithValidAssignment(&assignment), + test.WithCurves(ecc.BN254), ) - -type eddsaCircuit struct { - PublicKey eddsa.PublicKey `gnark:",public"` - Signature eddsa.Signature `gnark:",public"` - Message frontend.Variable `gnark:",public"` -} ``` -Notice that all the witnesses are public. - -You need a `Define` function describing the mathematical statement that must be verified. You did most of this job with the `Verify` implementation, now you have to assemble the parts. +You can also add an invalid assignment, for example the same signature with a different message: ```go -import ( - "github.com/consensys/gnark/std/algebra/twistededwards" - "github.com/consensys/gnark-crypto/ecc" +var invalidAssignment eddsaCircuit +invalidAssignment.curveID = tedwards.BN254 +invalidAssignment.Message = []byte{0xde, 0xad, 0xf0, 0x0e} +invalidAssignment.PublicKey.Assign(tedwards.BN254, publicKey.Bytes()) +invalidAssignment.Signature.Assign(tedwards.BN254, signature) + +assert.CheckCircuit(&circuit, + test.WithValidAssignment(&assignment), + test.WithInvalidAssignment(&invalidAssignment), + test.WithCurves(ecc.BN254), ) - -func (circuit *eddsaCircuit) Define(api frontend.API) error { - curve, err := twistededwards.NewEdCurve(api, circuit.curveID) - if err != nil { - return err - } - - mimc, err := mimc.NewMiMC(api) - if err != nil { - return err - } - - // verify the signature in the cs - return eddsa.Verify(curve, circuit.Signature, circuit.Message, circuit.PublicKey, &mimc) - - return nil -} ``` -To test the circuit, you need to generate an EdDSA signature, assign the signature on the circuit's witnesses, and verify that the circuit has been correctly solved. - -To generate the signature, use the `github.com/consensys/gnark-crypto/signature/eddsa` package. +## Prove and verify -Implementations of EdDSA exist for several curves, here you will choose BN254. +Compile, create witnesses, and run Groth16: ```go -func main() { - // instantiate hash function - hFunc := hash.MIMC_BN254.New() - - // create a eddsa key pair - privateKey, err := eddsa.New(twistededwards.BN254, crand.Reader) - publicKey := privateKey.Public() - - // note that the message is on 4 bytes - msg := []byte{0xde, 0xad, 0xf0, 0x0d} - - // sign the message - signature, err := privateKey.Sign(msg, hFunc) - - // verifies signature - isValid, err := publicKey.Verify(signature, msg, hFunc) - if !isValid { - fmt.Println("1. invalid signature") - } else { - fmt.Println("1. valid signature") - } +ccs, err := frontend.Compile( + ecc.BN254.ScalarField(), + r1cs.NewBuilder, + &circuit, +) +if err != nil { + return err } -``` - -Compile the circuit: -```go - var circuit eddsaCircuit - r1cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &circuit) -``` - -:::note - -`r1cs` is the arithmetized version of the circuit. - -It is a list of constraints that the prover needs to fulfill by providing satisfying inputs, namely a correct signature on a message. - -::: - -Run the Groth16 setup to get the `ProvingKey` and `VerifyingKey` linked to the circuit. - -```go - // generating pk, vk - pk, vk, err := groth16.Setup(r1cs) -``` - -Create the witness (the data needed to verify a signature inside the zk-SNARK), from the previously computed `signature`. - -```go - // declare the witness - var assignment eddsaCircuit - - // assign message value - assignment.Message = msg - - // public key bytes - _publicKey := publicKey.Bytes() - - // assign public key values - assignment.PublicKey.Assign(ecc.BN254, _publicKey[:32]) - - // assign signature values - assignment.Signature.Assign(ecc.BN254, signature) -``` - -Last step is to generate the proof and verify it. - -```go - // witness - witness, err := frontend.NewWitness(&assignment, ecc.BN254) - publicWitness, err := witness.Public() - // generate the proof - proof, err := groth16.Prove(r1cs, pk, witness) - - // verify the proof - err = groth16.Verify(proof, vk, publicWitness) - if err != nil { - // invalid proof - } -``` - -:::tip Unit tests +witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) +if err != nil { + return err +} +publicWitness, err := witness.Public() +if err != nil { + return err +} -In a `_test.go` file, you can use `gnark/backend/groth16/assert.go` as follows: +pk, vk, err := groth16.Setup(ccs) +if err != nil { + return err +} +proof, err := groth16.Prove(ccs, pk, witness) +if err != nil { + return err +} -```go -assert := groth16.NewAssert(t) -var witness Circuit -assert.ProverFailed(&circuit, &assignment) // .ProverSucceeded +return groth16.Verify(proof, vk, publicWitness) ``` - -::: diff --git a/docs/index.md b/docs/index.md index 9892be3..871790a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ In a typical workflow: :::warning -`gnark` has been [audited](https://github.com/Consensys-Incorporated/gnark/blob/master/audits/2022-10%20-%20Kudelski%20-%20gnark-crypto.pdf) and is provided as-is, use at your own risk. +`gnark` and `gnark-crypto` have been independently audited. Reports are available in the [`gnark` audits directory](https://github.com/Consensys-Incorporated/gnark/tree/master/audits). The library is provided as-is; use it at your own risk. In particular, `gnark` makes no security guarantees such as constant time implementation or side-channel attack resistance. @@ -52,20 +52,23 @@ Users write their zk-SNARK circuits in plain Go. `gnark` uses Go because: // Circuit defines a pre-image knowledge proof // mimc(secret preImage) = public hash type Circuit struct { - PreImage frontend.Variable - Hash frontend.Variable `gnark:",public"` + PreImage frontend.Variable + Hash frontend.Variable `gnark:",public"` } // Define declares the circuit's constraints func (circuit *Circuit) Define(api frontend.API) error { - // hash function - mimc, err := mimc.NewMiMC(api.Curve()) + // specify constraints + mimc, err := mimc.NewMiMC(api) + if err != nil { + return err + } - // specify constraints - // mimc(preImage) == hash - api.AssertIsEqual(circuit.Hash, mimc.Hash(cs, circuit.PreImage)) + // mimc(preImage) == hash + mimc.Write(circuit.PreImage) + api.AssertIsEqual(circuit.Hash, mimc.Sum()) - return nil + return nil } ``` @@ -74,7 +77,10 @@ func (circuit *Circuit) Define(api frontend.API) error { ```go var mimcCircuit Circuit -r1cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &mimcCircuit) +ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &mimcCircuit) +if err != nil { + return err +} ``` @@ -83,38 +89,50 @@ r1cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &mimcCircuit) ```go // witness assignment := &Circuit{ - Hash: "16130099170765464552823636852555369511329944820189892919423002775646948828469", - PreImage: 35, + Hash: "12886436712380113721405259596386800092738845035233065858332878701083870690753", + PreImage: "16130099170765464552823636852555369511329944820189892919423002775646948828469", +} +witness, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField()) +if err != nil { + return err } -witness, _ := frontend.NewWitness(assignment, ecc.BN254.ScalarField()) -publicWitness, _ := witness.Public() -pk, vk, err := groth16.Setup(r1cs) -proof, err := groth16.Prove(r1cs, pk, witness) -err := groth16.Verify(proof, vk, publicWitness) +publicWitness, err := witness.Public() +if err != nil { + return err +} + +pk, vk, err := groth16.Setup(ccs) +if err != nil { + return err +} +proof, err := groth16.Prove(ccs, pk, witness) +if err != nil { + return err +} +return groth16.Verify(proof, vk, publicWitness) ``` ```go -assert := groth16.NewAssert(t) +assert := test.NewAssert(t) var mimcCircuit Circuit { - assert.ProverFailed(&mimcCircuit, &Circuit{ - Hash: 42, - PreImage: 42, - }) + assert.ProverFailed(&mimcCircuit, &Circuit{ + Hash: 42, + PreImage: 42, + }) } { - assert.ProverSucceeded(&mimcCircuit, &Circuit{ - Hash: "16130099170765464552823636852555369511329944820189892919423002775646948828469", - PreImage: 35, - }) + assert.ProverSucceeded(&mimcCircuit, &Circuit{ + PreImage: "16130099170765464552823636852555369511329944820189892919423002775646948828469", + Hash: "12886436712380113721405259596386800092738845035233065858332878701083870690753", + }, test.WithCurves(ecc.BN254)) } - ```