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))
}
-
```