From 59e25eebb7f86ba1a358cbb2567c27e2b5d833f0 Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Mon, 24 Feb 2025 13:43:00 +0100 Subject: [PATCH 1/6] new IsEqual, IsZero and IsOnCurve on some in-circuit algebra fields --- std/algebra/emulated/fields_bw6761/e6.go | 16 +++++++++++++++- std/algebra/native/fields_bls12377/e12.go | 14 ++++++++++++++ std/algebra/native/fields_bls24315/e24.go | 11 +++++++++++ std/algebra/native/twistededwards/curve.go | 5 +++++ std/algebra/native/twistededwards/point.go | 10 ++++++++-- .../native/twistededwards/twistededwards.go | 1 + 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/fields_bw6761/e6.go b/std/algebra/emulated/fields_bw6761/e6.go index 125c902d26..852d1f3bb1 100644 --- a/std/algebra/emulated/fields_bw6761/e6.go +++ b/std/algebra/emulated/fields_bw6761/e6.go @@ -118,6 +118,21 @@ func (e Ext6) Sub(x, y *E6) *E6 { } } +func (e Ext6) IsZero(x *E6) frontend.Variable { + isZero := e.fp.IsZero(&x.A0) + isZero = e.api.And(isZero, e.fp.IsZero(&x.A1)) + isZero = e.api.And(isZero, e.fp.IsZero(&x.A2)) + isZero = e.api.And(isZero, e.fp.IsZero(&x.A3)) + isZero = e.api.And(isZero, e.fp.IsZero(&x.A4)) + isZero = e.api.And(isZero, e.fp.IsZero(&x.A5)) + return isZero +} + +func (e Ext6) IsEqual(x, y *E6) frontend.Variable { + diff := e.Sub(x, y) + return e.IsZero(diff) +} + func (e Ext6) Double(x *E6) *E6 { two := big.NewInt(2) a0 := e.fp.MulConst(&x.A0, two) @@ -1106,7 +1121,6 @@ func (e Ext6) AssertIsEqual(a, b *E6) { e.fp.AssertIsEqual(&a.A3, &b.A3) e.fp.AssertIsEqual(&a.A4, &b.A4) e.fp.AssertIsEqual(&a.A5, &b.A5) - } func (e Ext6) Copy(x *E6) *E6 { diff --git a/std/algebra/native/fields_bls12377/e12.go b/std/algebra/native/fields_bls12377/e12.go index d04890bab8..ec977f14b1 100644 --- a/std/algebra/native/fields_bls12377/e12.go +++ b/std/algebra/native/fields_bls12377/e12.go @@ -153,6 +153,20 @@ func (e *E12) Mul(api frontend.API, e1, e2 E12) *E12 { return e } +func (e *E12) IsZero(api frontend.API) frontend.Variable { + isZero := e.C0.B0.IsZero(api) + isZero = api.And(isZero, e.C0.B1.IsZero(api)) + isZero = api.And(isZero, e.C0.B2.IsZero(api)) + isZero = api.And(isZero, e.C1.B0.IsZero(api)) + isZero = api.And(isZero, e.C1.B1.IsZero(api)) + isZero = api.And(isZero, e.C1.B2.IsZero(api)) + return isZero +} + +func (e *E12) IsEqual(api frontend.API, x, y *E12) frontend.Variable { + return e.Sub(api, *x, *y).IsZero(api) +} + // Square squares an element in Fp12 func (e *E12) Square(api frontend.API, x E12) *E12 { diff --git a/std/algebra/native/fields_bls24315/e24.go b/std/algebra/native/fields_bls24315/e24.go index 362c5536a0..10e35c13e6 100644 --- a/std/algebra/native/fields_bls24315/e24.go +++ b/std/algebra/native/fields_bls24315/e24.go @@ -152,6 +152,17 @@ func (e *E24) Mul(api frontend.API, e1, e2 E24) *E24 { return e } +func (e *E24) IsZero(api frontend.API) frontend.Variable { + isZero := e.D0.C0.IsZero(api) + isZero = api.And(isZero, e.D0.C1.IsZero(api)) + isZero = api.And(isZero, e.D0.C2.IsZero(api)) + isZero = api.And(isZero, e.D1.C0.IsZero(api)) + isZero = api.And(isZero, e.D1.C1.IsZero(api)) + isZero = api.And(isZero, e.D1.C2.IsZero(api)) + return isZero +} + + // Square squares an element in Fp24 func (e *E24) Square(api frontend.API, x E24) *E24 { diff --git a/std/algebra/native/twistededwards/curve.go b/std/algebra/native/twistededwards/curve.go index 9349e276e5..ee53c05cb1 100644 --- a/std/algebra/native/twistededwards/curve.go +++ b/std/algebra/native/twistededwards/curve.go @@ -41,6 +41,11 @@ func (c *curve) Neg(p1 Point) Point { p.neg(c.api, &p1) return p } + +func (c *curve) IsOnCurve(p1 Point) frontend.Variable { + return p1.isOnCurve(c.api, c.params) +} + func (c *curve) AssertIsOnCurve(p1 Point) { p1.assertIsOnCurve(c.api, c.params) } diff --git a/std/algebra/native/twistededwards/point.go b/std/algebra/native/twistededwards/point.go index fde04e192c..f5f8850f04 100644 --- a/std/algebra/native/twistededwards/point.go +++ b/std/algebra/native/twistededwards/point.go @@ -15,6 +15,13 @@ func (p *Point) neg(api frontend.API, p1 *Point) *Point { // assertIsOnCurve checks if a point is on the reduced twisted Edwards curve // a*x² + y² = 1 + d*x²*y². func (p *Point) assertIsOnCurve(api frontend.API, curve *CurveParams) { + flag := p.isOnCurve(api, curve) + api.AssertIsEqual(flag, 1) +} + +// isOnCurve returns 1 if a point is on the reduced twisted Edwards curve +// a*x² + y² = 1 + d*x²*y², 0 otherwise. +func (p *Point) isOnCurve(api frontend.API, curve *CurveParams) frontend.Variable { xx := api.Mul(p.X, p.X) yy := api.Mul(p.Y, p.Y) @@ -25,8 +32,7 @@ func (p *Point) assertIsOnCurve(api frontend.API, curve *CurveParams) { dxxyy := api.Mul(dxx, yy) rhs := api.Add(dxxyy, 1) - api.AssertIsEqual(lhs, rhs) - + return api.IsZero(api.Sub(lhs, rhs)) } // add Adds two points on a twisted edwards curve (eg jubjub) diff --git a/std/algebra/native/twistededwards/twistededwards.go b/std/algebra/native/twistededwards/twistededwards.go index f50ae666d4..268355c495 100644 --- a/std/algebra/native/twistededwards/twistededwards.go +++ b/std/algebra/native/twistededwards/twistededwards.go @@ -27,6 +27,7 @@ type Curve interface { Add(p1, p2 Point) Point Double(p1 Point) Point Neg(p1 Point) Point + IsOnCurve(p1 Point) frontend.Variable AssertIsOnCurve(p1 Point) ScalarMul(p1 Point, scalar frontend.Variable) Point DoubleBaseScalarMul(p1, p2 Point, s1, s2 frontend.Variable) Point From eb050815cda3e2f58000ca3aa7a927b541e8aa06 Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Mon, 24 Feb 2025 13:44:59 +0100 Subject: [PATCH 2/6] in-circuit Pairing interface updated with new IsEqual, same as AsserIsEqual but it returns the result of the assertion instead to force it, pairings updated to implement the new method --- std/algebra/emulated/sw_bls12381/pairing.go | 5 +++++ std/algebra/emulated/sw_bw6761/pairing.go | 4 ++++ std/algebra/interfaces.go | 3 +++ std/algebra/native/sw_bls12377/pairing2.go | 4 ++++ std/algebra/native/sw_bls24315/pairing2.go | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 183bde4f30..ad63b970bb 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -185,6 +185,11 @@ func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext12.AssertIsEqual(x, y) } +func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { + diff := pr.Ext12.Sub(x, y) + return pr.Ext12.IsEqual(diff, pr.Ext12.Zero()) +} + func (pr Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } diff --git a/std/algebra/emulated/sw_bw6761/pairing.go b/std/algebra/emulated/sw_bw6761/pairing.go index 7ca01b1b76..0fee6eec29 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -227,6 +227,10 @@ func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext6.AssertIsEqual(x, y) } +func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { + return pr.Ext6.IsEqual(x, y) +} + func (pr Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index 3775d0f922..81baff1319 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -100,6 +100,9 @@ type Pairing[G1El G1ElementT, G2El G2ElementT, GtEl GtElementT] interface { // AssertIsEqual asserts the equality of the inputs. AssertIsEqual(*GtEl, *GtEl) + // IsEqual returns 1 if both inputs are equal, 0 otherwise. + IsEqual(*GtEl, *GtEl) frontend.Variable + // AssertIsOnG1 asserts that the input is on the G1 curve. AssertIsOnG1(*G1El) diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 5a06360318..8b1af0ec45 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -326,6 +326,10 @@ func (p *Pairing) AssertIsEqual(e1, e2 *GT) { e1.AssertIsEqual(p.api, *e2) } +func (pr *Pairing) IsEqual(e1, e2 *GT) frontend.Variable { + return e1.IsEqual(pr.api, e1, e2) +} + // AssertIsOnCurve asserts if p belongs to the curve. It doesn't modify p. func (c *Pairing) AssertIsOnCurve(p *G1Affine) { // (X,Y) ∈ {Y² == X³ + 1} U (0,0) diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index c213b4b5a3..485fc498be 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -319,6 +319,10 @@ func (p *Pairing) AssertIsEqual(e1, e2 *GT) { e1.AssertIsEqual(p.api, *e2) } +func (pr *Pairing) IsEqual(e1, e2 *GT) frontend.Variable { + return e1.Sub(pr.api, *e1, *e2).IsZero(pr.api) +} + func (p *Pairing) AssertIsOnG1(P *G1Affine) { panic("not implemented") } From 6b4e21b6d1538f363f45c96810f0fd45b6091206 Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Mon, 24 Feb 2025 13:45:58 +0100 Subject: [PATCH 3/6] new SignIsValid method for ecdsa and eddsa in-circuit signatures, same as Verify but it returns the result of the assertion instead to force it --- std/signature/ecdsa/ecdsa.go | 19 ++++++++++++++++++- std/signature/eddsa/eddsa.go | 28 +++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/std/signature/ecdsa/ecdsa.go b/std/signature/ecdsa/ecdsa.go index 201a802323..31ccb1a4da 100644 --- a/std/signature/ecdsa/ecdsa.go +++ b/std/signature/ecdsa/ecdsa.go @@ -19,6 +19,16 @@ type PublicKey[Base, Scalar emulated.FieldParams] sw_emulated.AffinePoint[Base] // // We assume that the message msg is already hashed to the scalar field. func (pk PublicKey[T, S]) Verify(api frontend.API, params sw_emulated.CurveParams, msg *emulated.Element[S], sig *Signature[S]) { + flag := pk.SignIsValid(api, params, msg, sig) + api.AssertIsEqual(flag, 1) +} + +// SignIsValid returns 1 if the signature sig verifies for the message msg and +// public key pk or 0 if not. The curve parameters params define the elliptic +// curve. +// +// We assume that the message msg is already hashed to the scalar field. +func (pk PublicKey[T, S]) SignIsValid(api frontend.API, params sw_emulated.CurveParams, msg *emulated.Element[S], sig *Signature[S]) frontend.Variable { cr, err := sw_emulated.New[T, S](api, params) if err != nil { panic(err) @@ -43,7 +53,14 @@ func (pk PublicKey[T, S]) Verify(api frontend.API, params sw_emulated.CurveParam if len(rbits) != len(qxBits) { panic("non-equal lengths") } + // store 1 to expect equality + res := frontend.Variable(1) for i := range rbits { - api.AssertIsEqual(rbits[i], qxBits[i]) + // calc the difference between the bits + diff := api.Sub(rbits[i], qxBits[i]) + // update the result with the AND of the previous result and the + // equality between the bits (diff == 0) + res = api.And(res, api.IsZero(diff)) } + return res } diff --git a/std/signature/eddsa/eddsa.go b/std/signature/eddsa/eddsa.go index e4a3a41f77..5c49cb9ac6 100644 --- a/std/signature/eddsa/eddsa.go +++ b/std/signature/eddsa/eddsa.go @@ -36,10 +36,22 @@ type Signature struct { S frontend.Variable } -// Verify verifies an eddsa signature using MiMC hash function +// Verify checks that an eddsa signature verifies for the message msg and +// public key pk provided using MiMC hash function. // cf https://en.wikipedia.org/wiki/EdDSA func Verify(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pubKey PublicKey, hash hash.FieldHasher) error { + isValid, err := SignIsValid(curve, sig, msg, pubKey, hash) + if err != nil { + return err + } + curve.API().AssertIsEqual(isValid, 1) + return nil +} +// SignIsValid returns 1 if the signature sig verifies an eddsa signature +// using MiMC hash function for the message msg and public key pk or 0 if not. +// cf https://en.wikipedia.org/wiki/EdDSA +func SignIsValid(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pubKey PublicKey, hash hash.FieldHasher) (frontend.Variable, error) { // compute H(R, A, M) hash.Write(sig.R.X) hash.Write(sig.R.Y) @@ -56,7 +68,9 @@ func Verify(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pu //[S]G-[H(R,A,M)]*A _A := curve.Neg(pubKey.A) Q := curve.DoubleBaseScalarMul(base, _A, sig.S, hRAM) - curve.AssertIsOnCurve(Q) + // check if Q is on the curve, if not multiply by 0 + isOnCurve := curve.IsOnCurve(Q) + Q = curve.ScalarMul(Q, isOnCurve) //[S]G-[H(R,A,M)]*A-R Q = curve.Add(curve.Neg(Q), sig.R) @@ -66,7 +80,7 @@ func Verify(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pu if !curve.Params().Cofactor.IsUint64() { err := errors.New("invalid cofactor") log.Err(err).Str("cofactor", curve.Params().Cofactor.String()).Send() - return err + return nil, err } cofactor := curve.Params().Cofactor.Uint64() switch cofactor { @@ -78,10 +92,10 @@ func Verify(curve twistededwards.Curve, sig Signature, msg frontend.Variable, pu log.Warn().Str("cofactor", curve.Params().Cofactor.String()).Msg("curve cofactor is not implemented") } - curve.API().AssertIsEqual(Q.X, 0) - curve.API().AssertIsEqual(Q.Y, 1) - - return nil + zeroX := curve.API().IsZero(Q.X) + oneY := curve.API().IsZero(curve.API().Sub(Q.Y, 1)) + expectedPoint := curve.API().And(zeroX, oneY) + return curve.API().And(isOnCurve, expectedPoint), nil } // Assign is a helper to assigned a compressed binary public key representation into its uncompressed form From fe5716d7a20153d68b6219509267ebb5d0cdc3f7 Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Mon, 24 Feb 2025 13:46:49 +0100 Subject: [PATCH 4/6] new ProofIsValid method for in-circuit groth16 proof verifier, same as AssertProof but it returns the result of the assertion instead to force it --- std/recursion/groth16/verifier.go | 32 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/std/recursion/groth16/verifier.go b/std/recursion/groth16/verifier.go index 77ace9073e..65da1c1ad9 100644 --- a/std/recursion/groth16/verifier.go +++ b/std/recursion/groth16/verifier.go @@ -602,16 +602,27 @@ func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra. // AssertProof asserts that the SNARK proof holds for the given witness and // verifying key. func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, GtEl], proof Proof[G1El, G2El], witness Witness[FR], opts ...VerifierOption) error { + flag, err := v.ProofIsValid(vk, proof, witness, opts...) + if err != nil { + return err + } + v.api.AssertIsEqual(flag, 1) + return nil +} + +// ProofIsValid returns 1 if the SNARK proof holds for the given witness and +// verifying key, and 0 otherwise. +func (v *Verifier[FR, G1El, G2El, GtEl]) ProofIsValid(vk VerifyingKey[G1El, G2El, GtEl], proof Proof[G1El, G2El], witness Witness[FR], opts ...VerifierOption) (frontend.Variable, error) { if len(vk.CommitmentKeys) != len(proof.Commitments) { - return fmt.Errorf("invalid number of commitments, got %d, expected %d", len(proof.Commitments), len(vk.CommitmentKeys)) + return 0, fmt.Errorf("invalid number of commitments, got %d, expected %d", len(proof.Commitments), len(vk.CommitmentKeys)) } if len(vk.CommitmentKeys) != len(vk.PublicAndCommitmentCommitted) { - return fmt.Errorf("invalid number of commitment keys, got %d, expected %d", len(vk.CommitmentKeys), len(vk.PublicAndCommitmentCommitted)) + return 0, fmt.Errorf("invalid number of commitment keys, got %d, expected %d", len(vk.CommitmentKeys), len(vk.PublicAndCommitmentCommitted)) } var fr FR nbPublicVars := len(vk.G1.K) - len(vk.PublicAndCommitmentCommitted) if len(witness.Public) != nbPublicVars-1 { - return fmt.Errorf("invalid witness size, got %d, expected %d (public - ONE_WIRE)", len(witness.Public), len(vk.G1.K)-1) + return 0, fmt.Errorf("invalid witness size, got %d, expected %d (public - ONE_WIRE)", len(witness.Public), len(vk.G1.K)-1) } inP := make([]*G1El, len(vk.G1.K)-1) // first is for the one wire, we add it manually after MSM @@ -625,11 +636,11 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, opt, err := newCfg(opts...) if err != nil { - return fmt.Errorf("apply options: %w", err) + return 0, fmt.Errorf("apply options: %w", err) } hashToField, err := recursion.NewHash(v.api, fr.Modulus(), true) if err != nil { - return fmt.Errorf("hash to field: %w", err) + return 0, fmt.Errorf("hash to field: %w", err) } maxNbPublicCommitted := 0 @@ -658,16 +669,16 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, // explicitly do not verify the commitment as there is nothing case 1: if err = v.commitment.AssertCommitment(proof.Commitments[0], proof.CommitmentPok, vk.CommitmentKeys[0], opt.pedopt...); err != nil { - return fmt.Errorf("assert commitment: %w", err) + return 0, fmt.Errorf("assert commitment: %w", err) } default: // TODO: we support only a single commitment in the recursion for now - return fmt.Errorf("multiple commitments are not supported") + return 0, fmt.Errorf("multiple commitments are not supported") } kSum, err := v.curve.MultiScalarMul(inP, inS, opt.algopt...) if err != nil { - return fmt.Errorf("multi scalar mul: %w", err) + return 0, fmt.Errorf("multi scalar mul: %w", err) } kSum = v.curve.Add(kSum, &vk.G1.K[0]) @@ -682,8 +693,7 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, } pairing, err := v.pairing.Pair([]*G1El{kSum, &proof.Krs, &proof.Ar}, []*G2El{&vk.G2.GammaNeg, &vk.G2.DeltaNeg, &proof.Bs}) if err != nil { - return fmt.Errorf("pairing: %w", err) + return 0, fmt.Errorf("pairing: %w", err) } - v.pairing.AssertIsEqual(pairing, &vk.E) - return nil + return v.pairing.IsEqual(pairing, &vk.E), nil } From 72dff569646adc937b43290b6dafb2c523b7486d Mon Sep 17 00:00:00 2001 From: Arya Tabaie Date: Mon, 24 Feb 2025 08:38:20 -0600 Subject: [PATCH 5/6] Perf: Poseidon2 GKR circuit (#1410) Co-authored-by: Ivo Kubjas --- go.mod | 10 +- go.sum | 20 +- std/gkr/api.go | 5 - std/gkr/api_test.go | 142 +++---- std/gkr/testing.go | 234 ++++++++++++ std/permutation/poseidon2/gkr.go | 398 ++++++++++++++++++++ std/permutation/poseidon2/gkr_test.go | 66 ++++ std/permutation/poseidon2/poseidon2.go | 8 +- std/permutation/poseidon2/poseidon2_test.go | 6 +- 9 files changed, 795 insertions(+), 94 deletions(-) create mode 100644 std/gkr/testing.go create mode 100644 std/permutation/poseidon2/gkr.go create mode 100644 std/permutation/poseidon2/gkr_test.go diff --git a/go.mod b/go.mod index b7b70697ee..8296d75b3d 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.20.0 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.27 + github.com/consensys/bavard v0.1.29 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca + github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 @@ -19,9 +19,9 @@ require ( github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.33.0 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 - golang.org/x/sync v0.10.0 + golang.org/x/sync v0.11.0 ) require ( @@ -31,7 +31,7 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 200c8c71ea..c31ddf141c 100644 --- a/go.sum +++ b/go.sum @@ -57,12 +57,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= -github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/bavard v0.1.29 h1:fobxIYksIQ+ZSrTJUuQgu+HIJwclrAPcdXqd7H2hh1k= +github.com/consensys/bavard v0.1.29/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk= github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= -github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca h1:u6iXwMBfbXODF+hDSwKSTBg6yfD3+eMX6o3PILAK474= -github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85 h1:3ht4gGH3smFGVLFhpFTKvDbEdagC6eSaPXnHjCQGh94= +github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85/go.mod h1:A2URlMHUT81ifJ0UlLzSlm7TmnE3t7VxEThApdMukJw= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -304,8 +304,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -410,8 +410,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -462,8 +462,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/std/gkr/api.go b/std/gkr/api.go index 03d8d8f71a..eb1acd2afe 100644 --- a/std/gkr/api.go +++ b/std/gkr/api.go @@ -43,8 +43,3 @@ func (api *API) Sub(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable) func (api *API) Mul(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { return api.namedGate2PlusIn("mul", i1, i2, in...) } - -// TODO @Tabaie This can be useful -func (api *API) Println(a ...constraint.GkrVariable) { - panic("not implemented") -} diff --git a/std/gkr/api_test.go b/std/gkr/api_test.go index 1dec782f71..10817fed7b 100644 --- a/std/gkr/api_test.go +++ b/std/gkr/api_test.go @@ -16,8 +16,6 @@ import ( bw6761 "github.com/consensys/gnark/constraint/bw6-761" "github.com/consensys/gnark/test" - "github.com/consensys/gnark-crypto/kzg" - "github.com/consensys/gnark/backend/plonk" bn254 "github.com/consensys/gnark/constraint/bn254" "github.com/stretchr/testify/require" @@ -26,15 +24,12 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254/fr/gkr" bn254MiMC "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" "github.com/consensys/gnark/backend/groth16" - "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/frontend/cs/scs" stdHash "github.com/consensys/gnark/std/hash" "github.com/consensys/gnark/std/hash/mimc" test_vector_utils "github.com/consensys/gnark/std/internal/test_vectors_utils" - "github.com/consensys/gnark/test/unsafekzg" ) // compressThreshold --> if linear expressions are larger than this, the frontend will introduce @@ -69,6 +64,7 @@ func (c *doubleNoDependencyCircuit) Define(api frontend.API) error { } func TestDoubleNoDependencyCircuit(t *testing.T) { + assert := test.NewAssert(t) xValuess := [][]frontend.Variable{ {1, 1}, @@ -77,12 +73,13 @@ func TestDoubleNoDependencyCircuit(t *testing.T) { hashes := []string{"-1", "-20"} - for _, xValues := range xValuess { + for i, xValues := range xValuess { for _, hashName := range hashes { assignment := doubleNoDependencyCircuit{X: xValues} circuit := doubleNoDependencyCircuit{X: make([]frontend.Variable, len(xValues)), hashName: hashName} - - test.NewAssert(t).CheckCircuit(&circuit, test.WithValidAssignment(&assignment)) + assert.Run(func(assert *test.Assert) { + assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BN254)) + }, fmt.Sprintf("xValue=%d/hash=%s", i, hashName)) } } @@ -115,6 +112,7 @@ func (c *sqNoDependencyCircuit) Define(api frontend.API) error { } func TestSqNoDependencyCircuit(t *testing.T) { + assert := test.NewAssert(t) xValuess := [][]frontend.Variable{ {1, 1}, @@ -123,12 +121,13 @@ func TestSqNoDependencyCircuit(t *testing.T) { hashes := []string{"-1", "-20"} - for _, xValues := range xValuess { + for i, xValues := range xValuess { for _, hashName := range hashes { assignment := sqNoDependencyCircuit{X: xValues} circuit := sqNoDependencyCircuit{X: make([]frontend.Variable, len(xValues)), hashName: hashName} - testGroth16(t, &circuit, &assignment) - testPlonk(t, &circuit, &assignment) + assert.Run(func(assert *test.Assert) { + assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BN254)) + }, fmt.Sprintf("xValues=%d/hash=%s", i, hashName)) } } } @@ -168,6 +167,7 @@ func (c *mulNoDependencyCircuit) Define(api frontend.API) error { } func TestMulNoDependency(t *testing.T) { + assert := test.NewAssert(t) xValuess := [][]frontend.Variable{ {1, 2}, } @@ -189,9 +189,9 @@ func TestMulNoDependency(t *testing.T) { Y: make([]frontend.Variable, len(yValuess[i])), hashName: hashName, } - - testGroth16(t, &circuit, &assignment) - testPlonk(t, &circuit, &assignment) + assert.Run(func(assert *test.Assert) { + assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BN254)) + }, fmt.Sprintf("xValues=%d/hash=%s", i, hashName)) } } } @@ -240,14 +240,13 @@ func (c *mulWithDependencyCircuit) Define(api frontend.API) error { } func TestSolveMulWithDependency(t *testing.T) { + assert := test.NewAssert(t) assignment := mulWithDependencyCircuit{ XLast: 1, Y: []frontend.Variable{3, 2}, } circuit := mulWithDependencyCircuit{Y: make([]frontend.Variable, len(assignment.Y)), hashName: "-20"} - - testGroth16(t, &circuit, &assignment) - testPlonk(t, &circuit, &assignment) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BN254)) } func TestApiMul(t *testing.T) { @@ -386,53 +385,6 @@ func (c *benchMiMCMerkleTreeCircuit) Define(api frontend.API) error { return solution.Verify("-20", challenge) } -func testGroth16(t *testing.T, circuit, assignment frontend.Circuit) { - cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, circuit, frontend.WithCompressThreshold(compressThreshold)) - require.NoError(t, err) - var ( - fullWitness witness.Witness - publicWitness witness.Witness - pk groth16.ProvingKey - vk groth16.VerifyingKey - proof groth16.Proof - ) - fullWitness, err = frontend.NewWitness(assignment, ecc.BN254.ScalarField()) - require.NoError(t, err) - publicWitness, err = fullWitness.Public() - require.NoError(t, err) - pk, vk, err = groth16.Setup(cs) - require.NoError(t, err) - proof, err = groth16.Prove(cs, pk, fullWitness) - require.NoError(t, err) - err = groth16.Verify(proof, vk, publicWitness) - require.NoError(t, err) -} - -func testPlonk(t *testing.T, circuit, assignment frontend.Circuit) { - cs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, circuit, frontend.WithCompressThreshold(compressThreshold)) - require.NoError(t, err) - var ( - fullWitness witness.Witness - publicWitness witness.Witness - pk plonk.ProvingKey - vk plonk.VerifyingKey - proof plonk.Proof - kzgSrs kzg.SRS - ) - fullWitness, err = frontend.NewWitness(assignment, ecc.BN254.ScalarField()) - require.NoError(t, err) - publicWitness, err = fullWitness.Public() - require.NoError(t, err) - kzgSrs, srsLagrange, err := unsafekzg.NewSRS(cs) - require.NoError(t, err) - pk, vk, err = plonk.Setup(cs, kzgSrs, srsLagrange) - require.NoError(t, err) - proof, err = plonk.Prove(cs, pk, fullWitness) - require.NoError(t, err) - err = plonk.Verify(proof, vk, publicWitness) - require.NoError(t, err) -} - func registerMiMC() { bn254.RegisterHashBuilder("mimc", func() hash.Hash { return bn254MiMC.NewMiMC() @@ -644,19 +596,21 @@ func BenchmarkMiMCNoGkrFullDepthSolve(b *testing.B) { } func TestMiMCFullDepthNoDepSolve(t *testing.T) { + assert := test.NewAssert(t) registerMiMC() for i := 0; i < 100; i++ { circuit, assignment := mimcNoDepCircuits(5, 1<<2, "-20") - testGroth16(t, circuit, assignment) - testPlonk(t, circuit, assignment) + assert.Run(func(assert *test.Assert) { + assert.CheckCircuit(circuit, test.WithValidAssignment(assignment), test.WithCurves(ecc.BN254)) + }, fmt.Sprintf("i=%d", i)) } } func TestMiMCFullDepthNoDepSolveWithMiMCHash(t *testing.T) { + assert := test.NewAssert(t) registerMiMC() circuit, assignment := mimcNoDepCircuits(5, 1<<2, "mimc") - testGroth16(t, circuit, assignment) - testPlonk(t, circuit, assignment) + assert.CheckCircuit(circuit, test.WithValidAssignment(assignment), test.WithCurves(ecc.BN254)) } func mimcNoGkrCircuits(mimcDepth, nbInstances int) (circuit, assignment frontend.Circuit) { @@ -677,3 +631,55 @@ func mimcNoGkrCircuits(mimcDepth, nbInstances int) (circuit, assignment frontend } return } + +func TestSolveInTestEngine(t *testing.T) { + assignment := testSolveInTestEngineCircuit{ + X: []frontend.Variable{2, 3, 4, 5, 6, 7, 8, 9}, + } + circuit := testSolveInTestEngineCircuit{ + X: make([]frontend.Variable, len(assignment.X)), + } + + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BN254.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BLS24_315.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BLS12_381.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BLS24_317.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BW6_633.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BW6_761.ScalarField())) + require.NoError(t, test.IsSolved(&circuit, &assignment, ecc.BLS12_377.ScalarField())) +} + +type testSolveInTestEngineCircuit struct { + X []frontend.Variable +} + +func (c *testSolveInTestEngineCircuit) Define(api frontend.API) error { + gkr := NewApi() + x, err := gkr.Import(c.X) + if err != nil { + return err + } + Y := make([]frontend.Variable, len(c.X)) + Y[0] = 1 + y, err := gkr.Import(Y) + if err != nil { + return err + } + + z := gkr.Mul(x, y) + + for i := range len(c.X) - 1 { + gkr.Series(y, z, i+1, i) + } + + assignments := gkr.SolveInTestEngine(api) + + product := frontend.Variable(1) + for i := range c.X { + api.AssertIsEqual(assignments[y][i], product) + product = api.Mul(product, c.X[i]) + api.AssertIsEqual(assignments[z][i], product) + } + + return nil +} diff --git a/std/gkr/testing.go b/std/gkr/testing.go new file mode 100644 index 0000000000..50111a60e4 --- /dev/null +++ b/std/gkr/testing.go @@ -0,0 +1,234 @@ +package gkr + +import ( + "errors" + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + frBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + gkrBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/gkr" + frBls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + gkrBls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/gkr" + frBls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + gkrBls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/gkr" + frBls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + gkrBls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/gkr" + frBn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" + gkrBn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr/gkr" + frBw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + gkrBw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/gkr" + frBw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + gkrBw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/gkr" + hint "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/frontend" +) + +// SolveInTestEngine solves the defined circuit directly inside the SNARK circuit. This means that the method does not compute the GKR proof of the circuit and does not embed the GKR proof verifier inside a SNARK. +// The output is the values of all variables, across all instances; i.e. indexed variable-first, instance-second. +// This method only works under the test engine and should only be called to debug a GKR circuit, as the GKR prover's errors can be obscure. +func (api *API) SolveInTestEngine(parentApi frontend.API) [][]frontend.Variable { + res := make([][]frontend.Variable, len(api.toStore.Circuit)) + degreeTestedGates := make(map[string]struct{}) + for i, w := range api.toStore.Circuit { + res[i] = make([]frontend.Variable, api.nbInstances()) + copy(res[i], api.assignments[i]) + if len(w.Inputs) == 0 { + continue + } + degree := Gates[w.Gate].Degree() + var degreeFr int + if parentApi.Compiler().Field().Cmp(ecc.BLS12_377.ScalarField()) == 0 { + degreeFr = gkrBls12377.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BN254.ScalarField()) == 0 { + degreeFr = gkrBn254.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BLS24_315.ScalarField()) == 0 { + degreeFr = gkrBls24315.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BW6_761.ScalarField()) == 0 { + degreeFr = gkrBw6761.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BLS12_381.ScalarField()) == 0 { + degreeFr = gkrBls12381.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BLS24_317.ScalarField()) == 0 { + degreeFr = gkrBls24317.Gates[w.Gate].Degree() + } else if parentApi.Compiler().Field().Cmp(ecc.BW6_633.ScalarField()) == 0 { + degreeFr = gkrBw6633.Gates[w.Gate].Degree() + } else { + panic("field not yet supported") + } + if degree != degreeFr { + panic(fmt.Errorf("gate \"%s\" degree mismatch: SNARK %d, Raw %d", w.Gate, degree, degreeFr)) + } + } + for instanceI := range api.nbInstances() { + for wireI, w := range api.toStore.Circuit { + if len(w.Dependencies) != 0 && len(w.Inputs) != 0 { + panic(fmt.Errorf("non-input wire %d should not have dependencies", wireI)) + } + for _, dep := range w.Dependencies { + if dep.InputInstance == instanceI { + if dep.OutputInstance >= instanceI { + panic(fmt.Errorf("out of order dependency not yet supported in SolveInTestEngine; (wire %d, instance %d) depends on (wire %d, instance %d)", wireI, instanceI, dep.OutputWire, dep.OutputInstance)) + } + if res[wireI][instanceI] != nil { + panic(fmt.Errorf("dependency (wire %d, instance %d) <- (wire %d, instance %d) attempting to override existing value assignment", wireI, instanceI, dep.OutputWire, dep.OutputInstance)) + } + res[wireI][instanceI] = res[dep.OutputWire][dep.OutputInstance] + } + } + + if res[wireI][instanceI] == nil { // no assignment or dependency + if len(w.Inputs) == 0 { + panic(fmt.Errorf("input wire %d, instance %d has no dependency or explicit assignment", wireI, instanceI)) + } + ins := make([]frontend.Variable, len(w.Inputs)) + for i, in := range w.Inputs { + ins[i] = res[in][instanceI] + } + expectedV, err := parentApi.Compiler().NewHint(frGateHint(w.Gate, degreeTestedGates), 1, ins...) + if err != nil { + panic(err) + } + res[wireI][instanceI] = Gates[w.Gate].Evaluate(parentApi, ins...) + parentApi.AssertIsEqual(expectedV[0], res[wireI][instanceI]) // snark and raw gate evaluations must agree + } + } + } + return res +} + +func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hint { + return func(mod *big.Int, ins, outs []*big.Int) error { + if len(outs) != 1 { + return errors.New("gate must have one output") + } + if ecc.BLS12_377.ScalarField().Cmp(mod) == 0 { + gate := gkrBls12377.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBls12377.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBls12377.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BN254.ScalarField().Cmp(mod) == 0 { + gate := gkrBn254.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBn254.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBn254.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BLS24_315.ScalarField().Cmp(mod) == 0 { + gate := gkrBls24315.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBls24315.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBls24315.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BW6_761.ScalarField().Cmp(mod) == 0 { + gate := gkrBw6761.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBw6761.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBw6761.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BLS12_381.ScalarField().Cmp(mod) == 0 { + gate := gkrBls12381.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBls12381.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBls12381.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BLS24_317.ScalarField().Cmp(mod) == 0 { + gate := gkrBls24317.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBls24317.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + + x := make([]frBls24317.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else if ecc.BW6_633.ScalarField().Cmp(mod) == 0 { + gate := gkrBw6633.Gates[gateName] + if gate == nil { + return fmt.Errorf("gate \"%s\" not found", gateName) + } + if _, ok := degreeTestedGates[gateName]; !ok { + if err := gkrBw6633.TestGateDegree(gate, len(ins)); err != nil { + return fmt.Errorf("gate %s: %w", gateName, err) + } + degreeTestedGates[gateName] = struct{}{} + } + x := make([]frBw6633.Element, len(ins)) + for i := range ins { + x[i].SetBigInt(ins[i]) + } + y := gate.Evaluate(x...) + y.BigInt(outs[0]) + } else { + return errors.New("field not supported") + } + return nil + } +} diff --git a/std/permutation/poseidon2/gkr.go b/std/permutation/poseidon2/gkr.go new file mode 100644 index 0000000000..f52d750bf0 --- /dev/null +++ b/std/permutation/poseidon2/gkr.go @@ -0,0 +1,398 @@ +package poseidon2 + +import ( + "errors" + "fmt" + "github.com/consensys/gnark/constraint/solver" + "hash" + "math/big" + "sync" + + "github.com/consensys/gnark-crypto/ecc" + frBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + mimcBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/mimc" + poseidon2Bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/poseidon2" + gkrPoseidon2Bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/poseidon2/gkrgates" + "github.com/consensys/gnark/constraint" + csBls12377 "github.com/consensys/gnark/constraint/bls12-377" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/gkr" + stdHash "github.com/consensys/gnark/std/hash" + "github.com/consensys/gnark/std/hash/mimc" +) + +// extKeyGate applies the external matrix mul, then adds the round key +// because of its symmetry, we don't need to define distinct x1 and x2 versions of it +type extKeyGate struct { + roundKey *big.Int +} + +func (g *extKeyGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[0], 2), x[1], g.roundKey) +} + +func (g *extKeyGate) Degree() int { + return 1 +} + +// pow4Gate computes a -> a⁴ +type pow4Gate struct{} + +func (g pow4Gate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 1 { + panic("expected 1 input") + } + y := api.Mul(x[0], x[0]) + y = api.Mul(y, y) + + return y +} + +func (g pow4Gate) Degree() int { + return 4 +} + +// pow4Gate computes a, b -> a⁴ * b +type pow4TimesGate struct{} + +func (g pow4TimesGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 1 input") + } + y := api.Mul(x[0], x[0]) + y = api.Mul(y, y) + + return api.Mul(y, x[1]) +} + +func (g pow4TimesGate) Degree() int { + return 5 +} + +type pow2Gate struct{} + +func (g pow2Gate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 1 { + panic("expected 1 input") + } + return api.Mul(x[0], x[0]) +} + +func (g pow2Gate) Degree() int { + return 2 +} + +type pow2TimesGate struct{} + +func (g pow2TimesGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Mul(x[0], x[0], x[1]) +} + +func (g pow2TimesGate) Degree() int { + return 3 +} + +// for x1, the partial round gates are identical to full round gates +// for x2, the partial round gates are just a linear combination +// TODO @Tabaie try eliminating the x2 partial round gates and have the x1 gates depend on i - rf/2 or so previous x1's + +// extGate2 applies the external matrix mul, outputting the second element of the result +type extGate2 struct { +} + +func (g *extGate2) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[1], 2), x[0]) +} + +func (g *extGate2) Degree() int { + return 1 +} + +// intKeyGate2 applies the internal matrix mul, then adds the round key +type intKeyGate2 struct { + roundKey *big.Int +} + +func (g *intKeyGate2) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[1], 3), x[0], g.roundKey) +} + +func (g *intKeyGate2) Degree() int { + return 1 +} + +type extGate struct{} + +func (g extGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[0], 2), x[1]) +} + +func (g extGate) Degree() int { + return 1 +} + +type GkrPermutations struct { + api frontend.API + ins1 []frontend.Variable + ins2 []frontend.Variable + outs []frontend.Variable +} + +// NewGkrPermutations returns an object that can compute the Poseidon2 permutation (currently only for BLS12-377) +// The correctness of the permutations is proven using GKR +// Note that the solver will need the function RegisterGkrSolverOptions to be called with the desired curves +func NewGkrPermutations(api frontend.API) *GkrPermutations { + res := GkrPermutations{ + api: api, + } + api.Compiler().Defer(res.finalize) + return &res +} + +func (p *GkrPermutations) Permute(a, b frontend.Variable) frontend.Variable { + s, err := p.api.Compiler().NewHint(permuteHint, 1, a, b) + if err != nil { + panic(err) + } + p.ins1 = append(p.ins1, a) + p.ins2 = append(p.ins2, b) + p.outs = append(p.outs, s[0]) + return s[0] +} + +func frToInt(x *frBls12377.Element) *big.Int { + var res big.Int + x.BigInt(&res) + return &res +} + +// defineCircuit defines the GKR circuit for the Poseidon2 permutation over BLS12-377 +// insLeft and insRight are the inputs to the permutation +// they must be padded to a power of 2 +func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint.GkrVariable, error) { + // variable indexes + const ( + xI = iota + yI + ) + + // poseidon2 parameters + roundKeysFr := poseidon2Bls12377.GetDefaultParameters().RoundKeys + params := poseidon2Bls12377.GetDefaultParameters().String() + rF := poseidon2Bls12377.GetDefaultParameters().NbFullRounds + rP := poseidon2Bls12377.GetDefaultParameters().NbPartialRounds + halfRf := rF / 2 + + gkrApi := gkr.NewApi() + + x, err := gkrApi.Import(insLeft) + if err != nil { + return nil, -1, err + } + y, err := gkrApi.Import(insRight) + if err != nil { + return nil, -1, err + } + + // unique names for linear rounds + gateNameLinear := func(varI, round int) string { + return fmt.Sprintf("x%d-l-op-round=%d;%s", varI, round, params) + } + + // the s-Box gates: u¹⁷ = (u⁴)⁴ * u + gkr.Gates["pow4"] = pow4Gate{} + gkr.Gates["pow4Times"] = pow4TimesGate{} + + // *** helper functions to register and apply gates *** + + // Poseidon2 is a sequence of additions, exponentiations (s-Box), and linear operations + // but here we group the operations so that every round consists of a degree-1 operation followed by the s-Box + // this allows for more efficient result sharing among the gates + // but also breaks the uniformity of the circuit a bit, in that the matrix operation + // in every round comes from the previous (canonical) round. + + // apply the s-Box to u + sBox := func(u constraint.GkrVariable) constraint.GkrVariable { + v := gkrApi.NamedGate("pow4", u) // u⁴ + return gkrApi.NamedGate("pow4Times", v, u) // u¹⁷ + } + + // register and apply external matrix multiplication and round key addition + // round dependent due to the round key + extKeySBox := func(round, varI int, a, b constraint.GkrVariable) constraint.GkrVariable { + gate := gateNameLinear(varI, round) + gkr.Gates[gate] = &extKeyGate{ + roundKey: frToInt(&roundKeysFr[round][varI]), + } + return sBox(gkrApi.NamedGate(gate, a, b)) + } + + // register and apply external matrix multiplication and round key addition + // then apply the s-Box + // for the second variable + // round independent due to the round key + intKeySBox2 := func(round int, a, b constraint.GkrVariable) constraint.GkrVariable { + gate := gateNameLinear(yI, round) + gkr.Gates[gate] = &intKeyGate2{ + roundKey: frToInt(&roundKeysFr[round][1]), + } + return sBox(gkrApi.NamedGate(gate, a, b)) + } + + // apply a full round + fullRound := func(i int) { + x1 := extKeySBox(i, xI, x, y) // TODO inline this + x, y = x1, extKeySBox(i, yI, y, x) // the external matrix is symmetric so we can use the same gate with inputs swapped + } + + // *** construct the circuit *** + + for i := range halfRf { + fullRound(i) + } + + { + // i = halfRf: first partial round + // still using the external matrix, since the linear operation still belongs to a full (canonical) round + x1 := extKeySBox(halfRf, xI, x, y) + + gate := gateNameLinear(yI, halfRf) + gkr.Gates[gate] = &extGate2{} + x, y = x1, gkrApi.NamedGate(gate, x, y) + } + + zero := new(big.Int) + for i := halfRf + 1; i < halfRf+rP; i++ { + x1 := extKeySBox(i, xI, x, y) // the first row of the internal matrix is the same as that of the external matrix + + gate := gateNameLinear(yI, i) + gkr.Gates[gate] = &intKeyGate2{ + roundKey: zero, + } + x, y = x1, gkrApi.NamedGate(gate, x, y) + } + + { + i := halfRf + rP + // first iteration of the final batch of full rounds + // still using the internal matrix, since the linear operation still belongs to a partial (canonical) round + x1 := extKeySBox(i, xI, x, y) + x, y = x1, intKeySBox2(i, x, y) + } + + for i := halfRf + rP + 1; i < rP+rF; i++ { + fullRound(i) + } + + // apply the external matrix one last time to obtain the final value of y + gate := gateNameLinear(yI, rP+rF) + gkr.Gates[gate] = extGate{} + y = gkrApi.NamedGate(gate, y, x) + + return gkrApi, y, nil +} + +func (p *GkrPermutations) finalize(api frontend.API) error { + if p.api != api { + panic("unexpected API") + } + + // register MiMC to be used as a random oracle in the GKR proof + stdHash.Register("mimc", func(api frontend.API) (stdHash.FieldHasher, error) { + m, err := mimc.NewMiMC(api) + return &m, err + }) + + // pad instances into a power of 2 + // TODO @Tabaie the GKR API to do this automatically? + ins1Padded := make([]frontend.Variable, ecc.NextPowerOfTwo(uint64(len(p.ins1)))) + ins2Padded := make([]frontend.Variable, len(ins1Padded)) + copy(ins1Padded, p.ins1) + copy(ins2Padded, p.ins2) + for i := len(p.ins1); i < len(ins1Padded); i++ { + ins1Padded[i] = 0 + ins2Padded[i] = 0 + } + + gkrApi, y, err := defineCircuit(ins1Padded, ins2Padded) + if err != nil { + return err + } + + // connect to output + // TODO can we save 1 constraint per instance by giving the desired outputs to the gkr api? + solution, err := gkrApi.Solve(api) + if err != nil { + return err + } + yVals := solution.Export(y) + for i := range p.outs { + api.AssertIsEqual(yVals[i], p.outs[i]) + } + + // verify GKR proof + allVals := make([]frontend.Variable, 0, 3*len(p.ins1)) + allVals = append(allVals, p.ins1...) + allVals = append(allVals, p.ins2...) + allVals = append(allVals, p.outs...) + challenge, err := p.api.(frontend.Committer).Commit(allVals...) + if err != nil { + return err + } + return solution.Verify("mimc", challenge) +} + +func permuteHint(m *big.Int, ins, outs []*big.Int) error { + if m.Cmp(ecc.BLS12_377.ScalarField()) != 0 { + return errors.New("only bls12-377 supported") + } + if len(ins) != 2 || len(outs) != 1 { + return errors.New("expected 2 inputs and 1 output") + } + var x [2]frBls12377.Element + x[0].SetBigInt(ins[0]) + x[1].SetBigInt(ins[1]) + + err := bls12377Permutation().Permutation(x[:]) + x[1].BigInt(outs[0]) + return err +} + +var bls12377Permutation = sync.OnceValue(func() *poseidon2Bls12377.Permutation { + params := poseidon2Bls12377.GetDefaultParameters() + return poseidon2Bls12377.NewPermutation(2, params.NbFullRounds, params.NbPartialRounds) // TODO @Tabaie add NewDefaultPermutation to gnark-crypto +}) + +// RegisterGkrSolverOptions registers the GKR gates corresponding to the given curves for the solver +func RegisterGkrSolverOptions(curves ...ecc.ID) { + if len(curves) == 0 { + panic("expected at least one curve") + } + solver.RegisterHint(permuteHint) + for _, curve := range curves { + switch curve { + case ecc.BLS12_377: + csBls12377.RegisterHashBuilder("mimc", func() hash.Hash { + return mimcBls12377.NewMiMC() + }) + gkrPoseidon2Bls12377.RegisterGkrGates() + default: + panic(fmt.Sprintf("curve %s not currently supported", curve)) + } + } +} diff --git a/std/permutation/poseidon2/gkr_test.go b/std/permutation/poseidon2/gkr_test.go new file mode 100644 index 0000000000..1cc39c4053 --- /dev/null +++ b/std/permutation/poseidon2/gkr_test.go @@ -0,0 +1,66 @@ +package poseidon2 + +import ( + "fmt" + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/test" + "github.com/stretchr/testify/require" + "testing" +) + +func TestGkrPermutation(t *testing.T) { + const n = 2 + var k int64 + ins := make([][2]frontend.Variable, n) + outs := make([]frontend.Variable, n) + for i := range n { + var x [2]fr.Element + ins[i] = [2]frontend.Variable{k, k + 1} + + x[0].SetInt64(k) + x[1].SetInt64(k + 1) + + require.NoError(t, bls12377Permutation().Permutation(x[:])) + outs[i] = x[1] + + k += 2 + } + + circuit := testGkrPermutationCircuit{ + Ins: ins, + Outs: outs, + } + + RegisterGkrSolverOptions(ecc.BLS12_377) + + test.NewAssert(t).CheckCircuit(&testGkrPermutationCircuit{Ins: make([][2]frontend.Variable, len(ins)), Outs: make([]frontend.Variable, len(outs))}, test.WithValidAssignment(&circuit), test.WithCurves(ecc.BLS12_377)) +} + +type testGkrPermutationCircuit struct { + Ins [][2]frontend.Variable + Outs []frontend.Variable +} + +func (c *testGkrPermutationCircuit) Define(api frontend.API) error { + + pos2 := NewGkrPermutations(api) + api.AssertIsEqual(len(c.Ins), len(c.Outs)) + for i := range c.Ins { + api.AssertIsEqual(c.Outs[i], pos2.Permute(c.Ins[i][0], c.Ins[i][1])) + } + + return nil +} + +func TestGkrPermutationCompiles(t *testing.T) { + // just measure the number of constraints + cs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &testGkrPermutationCircuit{ + Ins: make([][2]frontend.Variable, 52000), + Outs: make([]frontend.Variable, 52000), + }) + require.NoError(t, err) + fmt.Println(cs.GetNbConstraints(), "constraints") +} diff --git a/std/permutation/poseidon2/poseidon2.go b/std/permutation/poseidon2/poseidon2.go index 02a90aeb87..f7a8d4068a 100644 --- a/std/permutation/poseidon2/poseidon2.go +++ b/std/permutation/poseidon2/poseidon2.go @@ -1,4 +1,4 @@ -package poseidon +package poseidon2 import ( "errors" @@ -40,7 +40,7 @@ type parameters struct { // number of partial rounds nbPartialRounds int - // round keys + // round keys: ordered by round then variable roundKeys [][]big.Int } @@ -49,7 +49,7 @@ type parameters struct { func NewPoseidon2(api frontend.API) (*Permutation, error) { switch utils.FieldToCurve(api.Compiler().Field()) { // TODO: assumes pairing based builder, reconsider when supporting other backends case ecc.BLS12_377: - params := poseidonbls12377.NewDefaultParameters() + params := poseidonbls12377.GetDefaultParameters() return NewPoseidon2FromParameters(api, 2, params.NbFullRounds, params.NbPartialRounds) // TODO: we don't have default parameters for other curves yet. Update this when we do. default: @@ -236,7 +236,7 @@ func (h *Permutation) matMulExternalInPlace(input []frontend.Variable) { } } -// when t=2,3 the matrix are respectibely [[2,1][1,3]] and [[2,1,1][1,2,1][1,1,3]] +// when t=2,3 the matrix are respectively [[2,1][1,3]] and [[2,1,1][1,2,1][1,1,3]] // otherwise the matrix is filled with ones except on the diagonal, func (h *Permutation) matMulInternalInPlace(input []frontend.Variable) { if h.params.width == 2 { diff --git a/std/permutation/poseidon2/poseidon2_test.go b/std/permutation/poseidon2/poseidon2_test.go index ed0a3b807c..3bfd3d04ab 100644 --- a/std/permutation/poseidon2/poseidon2_test.go +++ b/std/permutation/poseidon2/poseidon2_test.go @@ -1,4 +1,4 @@ -package poseidon +package poseidon2 import ( "fmt" @@ -47,7 +47,9 @@ func (c *Poseidon2Circuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("could not create poseidon2 hasher: %w", err) } - h.Permutation(c.Input) + if err := h.Permutation(c.Input); err != nil { + return fmt.Errorf("could not apply permutation: %w", err) + } for i := 0; i < len(c.Input); i++ { api.AssertIsEqual(c.Output[i], c.Input[i]) } From 56f68baa9b16d8b118158f56614a612e08152438 Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Wed, 30 Apr 2025 11:01:34 +0200 Subject: [PATCH 6/6] conflicts solved --- std/algebra/emulated/fields_bw6761/e6.go | 5 -- std/algebra/emulated/sw_bls12381/pairing.go | 5 -- std/algebra/emulated/sw_bw6761/pairing.go | 4 -- std/algebra/interfaces.go | 3 - std/algebra/native/sw_bls12377/pairing2.go | 36 ----------- std/algebra/native/sw_bls24315/pairing2.go | 69 --------------------- 6 files changed, 122 deletions(-) diff --git a/std/algebra/emulated/fields_bw6761/e6.go b/std/algebra/emulated/fields_bw6761/e6.go index 229a69825d..804a7d3546 100644 --- a/std/algebra/emulated/fields_bw6761/e6.go +++ b/std/algebra/emulated/fields_bw6761/e6.go @@ -128,11 +128,6 @@ func (e Ext6) IsZero(x *E6) frontend.Variable { return isZero } -func (e Ext6) IsEqual(x, y *E6) frontend.Variable { - diff := e.Sub(x, y) - return e.IsZero(diff) -} - func (e Ext6) Double(x *E6) *E6 { two := big.NewInt(2) a0 := e.fp.MulConst(&x.A0, two) diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 509d752378..8ebb8386e2 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -187,11 +187,6 @@ func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext12.AssertIsEqual(x, y) } -func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { - diff := pr.Ext12.Sub(x, y) - return pr.Ext12.IsEqual(diff, pr.Ext12.Zero()) -} - func (pr Pairing) MuxG2(sel frontend.Variable, inputs ...*G2Affine) *G2Affine { if len(inputs) == 0 { return nil diff --git a/std/algebra/emulated/sw_bw6761/pairing.go b/std/algebra/emulated/sw_bw6761/pairing.go index 013d2d55c6..7baaeb32d3 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -231,10 +231,6 @@ func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext6.AssertIsEqual(x, y) } -func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { - return pr.Ext6.IsEqual(x, y) -} - func (pr Pairing) MuxG2(sel frontend.Variable, inputs ...*G2Affine) *G2Affine { if len(inputs) == 0 { return nil diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index 2d9c735eaf..555cb03ff9 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -100,9 +100,6 @@ type Pairing[G1El G1ElementT, G2El G2ElementT, GtEl GtElementT] interface { // AssertIsEqual asserts the equality of the inputs. AssertIsEqual(*GtEl, *GtEl) - // IsEqual returns 1 if both inputs are equal, 0 otherwise. - IsEqual(*GtEl, *GtEl) frontend.Variable - // AssertIsOnG1 asserts that the input is on the G1 curve. AssertIsOnG1(*G1El) diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 4eb48971d4..46db52b84b 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -106,42 +106,6 @@ func (c *Curve) AssertIsEqual(P, Q *G1Affine) { P.AssertIsEqual(c.api, *Q) } -func (c *Pairing) IsEqual(x, y *GT) frontend.Variable { - diff0 := c.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0) - diff1 := c.api.Sub(&x.C0.B0.A1, &y.C0.B0.A1) - diff2 := c.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0) - diff3 := c.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1) - diff4 := c.api.Sub(&x.C0.B1.A0, &y.C0.B1.A0) - diff5 := c.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1) - diff6 := c.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0) - diff7 := c.api.Sub(&x.C1.B0.A1, &y.C1.B0.A1) - diff8 := c.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0) - diff9 := c.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1) - diff10 := c.api.Sub(&x.C1.B1.A0, &y.C1.B1.A0) - diff11 := c.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1) - - isZero0 := c.api.IsZero(diff0) - isZero1 := c.api.IsZero(diff1) - isZero2 := c.api.IsZero(diff2) - isZero3 := c.api.IsZero(diff3) - isZero4 := c.api.IsZero(diff4) - isZero5 := c.api.IsZero(diff5) - isZero6 := c.api.IsZero(diff6) - isZero7 := c.api.IsZero(diff7) - isZero8 := c.api.IsZero(diff8) - isZero9 := c.api.IsZero(diff9) - isZero10 := c.api.IsZero(diff10) - isZero11 := c.api.IsZero(diff11) - - return c.api.And( - c.api.And( - c.api.And(c.api.And(isZero0, isZero1), c.api.And(isZero2, isZero3)), - c.api.And(c.api.And(isZero4, isZero5), c.api.And(isZero6, isZero7)), - ), - c.api.And(c.api.And(isZero8, isZero9), c.api.And(isZero10, isZero11)), - ) -} - // Neg negates P and returns the result. Does not modify P. func (c *Curve) Neg(P *G1Affine) *G1Affine { res := &G1Affine{ diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index 5eab225430..926bdd5790 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -255,75 +255,6 @@ func NewPairing(api frontend.API) *Pairing { } } -func (c *Pairing) IsEqual(x, y *GT) frontend.Variable { - diff0 := c.api.Sub(&x.D0.C0.B0.A0, &y.D0.C0.B0.A0) - diff1 := c.api.Sub(&x.D0.C0.B0.A1, &y.D0.C0.B0.A1) - diff2 := c.api.Sub(&x.D0.C0.B0.A0, &y.D0.C0.B0.A0) - diff3 := c.api.Sub(&x.D0.C0.B1.A1, &y.D0.C0.B1.A1) - diff4 := c.api.Sub(&x.D0.C0.B1.A0, &y.D0.C0.B1.A0) - diff5 := c.api.Sub(&x.D0.C0.B1.A1, &y.D0.C0.B1.A1) - diff6 := c.api.Sub(&x.D0.C1.B0.A0, &y.D0.C1.B0.A0) - diff7 := c.api.Sub(&x.D0.C1.B0.A1, &y.D0.C1.B0.A1) - diff8 := c.api.Sub(&x.D0.C1.B0.A0, &y.D0.C1.B0.A0) - diff9 := c.api.Sub(&x.D0.C1.B1.A1, &y.D0.C1.B1.A1) - diff10 := c.api.Sub(&x.D0.C1.B1.A0, &y.D0.C1.B1.A0) - diff11 := c.api.Sub(&x.D0.C1.B1.A1, &y.D0.C1.B1.A1) - diff12 := c.api.Sub(&x.D1.C0.B0.A0, &y.D1.C0.B0.A0) - diff13 := c.api.Sub(&x.D1.C0.B0.A1, &y.D1.C0.B0.A1) - diff14 := c.api.Sub(&x.D1.C0.B0.A0, &y.D1.C0.B0.A0) - diff15 := c.api.Sub(&x.D1.C0.B1.A1, &y.D1.C0.B1.A1) - diff16 := c.api.Sub(&x.D1.C0.B1.A0, &y.D1.C0.B1.A0) - diff17 := c.api.Sub(&x.D1.C0.B1.A1, &y.D1.C0.B1.A1) - diff18 := c.api.Sub(&x.D1.C1.B0.A0, &y.D1.C1.B0.A0) - diff19 := c.api.Sub(&x.D1.C1.B0.A1, &y.D1.C1.B0.A1) - diff20 := c.api.Sub(&x.D1.C1.B0.A0, &y.D1.C1.B0.A0) - diff21 := c.api.Sub(&x.D1.C1.B1.A1, &y.D1.C1.B1.A1) - diff22 := c.api.Sub(&x.D1.C1.B1.A0, &y.D1.C1.B1.A0) - diff23 := c.api.Sub(&x.D1.C1.B1.A1, &y.D1.C1.B1.A1) - - isZero0 := c.api.IsZero(diff0) - isZero1 := c.api.IsZero(diff1) - isZero2 := c.api.IsZero(diff2) - isZero3 := c.api.IsZero(diff3) - isZero4 := c.api.IsZero(diff4) - isZero5 := c.api.IsZero(diff5) - isZero6 := c.api.IsZero(diff6) - isZero7 := c.api.IsZero(diff7) - isZero8 := c.api.IsZero(diff8) - isZero9 := c.api.IsZero(diff9) - isZero10 := c.api.IsZero(diff10) - isZero11 := c.api.IsZero(diff11) - isZero12 := c.api.IsZero(diff12) - isZero13 := c.api.IsZero(diff13) - isZero14 := c.api.IsZero(diff14) - isZero15 := c.api.IsZero(diff15) - isZero16 := c.api.IsZero(diff16) - isZero17 := c.api.IsZero(diff17) - isZero18 := c.api.IsZero(diff18) - isZero19 := c.api.IsZero(diff19) - isZero20 := c.api.IsZero(diff20) - isZero21 := c.api.IsZero(diff21) - isZero22 := c.api.IsZero(diff22) - isZero23 := c.api.IsZero(diff23) - - return c.api.And( - c.api.And( - c.api.And( - c.api.And(c.api.And(isZero0, isZero1), c.api.And(isZero2, isZero3)), - c.api.And(c.api.And(isZero4, isZero5), c.api.And(isZero6, isZero7)), - ), - c.api.And( - c.api.And(c.api.And(isZero8, isZero9), c.api.And(isZero10, isZero11)), - c.api.And(c.api.And(isZero12, isZero13), c.api.And(isZero14, isZero15)), - ), - ), - c.api.And( - c.api.And(c.api.And(isZero16, isZero17), c.api.And(isZero18, isZero19)), - c.api.And(c.api.And(isZero20, isZero21), c.api.And(isZero22, isZero23)), - ), - ) -} - // MillerLoop computes the Miller loop between the pairs of inputs. It doesn't // modify the inputs. It returns an error if there is a mismatch between the // lengths of the inputs.