Skip to content

Commit b53af63

Browse files
ivokublucasmenendezCopilot
authored
feat: implement flag based recursive groth16 and EdDSA check (#1647)
Co-authored-by: Lucas Menendez <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 7ec673b commit b53af63

File tree

10 files changed

+274
-63
lines changed

10 files changed

+274
-63
lines changed

std/algebra/native/fields_bls12377/e12.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,11 @@ func (e *E12) AssertIsEqual(api frontend.API, other E12) {
518518
e.C0.AssertIsEqual(api, other.C0)
519519
e.C1.AssertIsEqual(api, other.C1)
520520
}
521+
522+
// IsEqual returns a variable that is 1 if self == other, 0 otherwise
523+
func (e *E12) IsEqual(api frontend.API, other E12) frontend.Variable {
524+
return api.And(
525+
e.C0.IsEqual(api, other.C0),
526+
e.C1.IsEqual(api, other.C1),
527+
)
528+
}

std/algebra/native/fields_bls12377/e12_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func TestDivFp12(t *testing.T) {
350350
witness.C.Assign(&c)
351351

352352
assert := test.NewAssert(t)
353-
assert.SolvingSucceeded(&e12Div{}, &witness, test.WithCurves(ecc.BW6_761))
353+
assert.CheckCircuit(&e12Div{}, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
354354
}
355355

356356
type fp12FixedExpo struct {
@@ -427,3 +427,34 @@ func TestFp12MulBy034(t *testing.T) {
427427
assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
428428

429429
}
430+
431+
type fp12IsEqual struct {
432+
A, B E12
433+
Eq frontend.Variable `gnark:",public"`
434+
}
435+
436+
func (circuit *fp12IsEqual) Define(api frontend.API) error {
437+
isEqual := circuit.A.IsEqual(api, circuit.B)
438+
api.AssertIsEqual(isEqual, circuit.Eq)
439+
return nil
440+
}
441+
442+
func TestE12IsEqual(t *testing.T) {
443+
444+
// witness values
445+
var a, b bls12377.E12
446+
_, _ = a.SetRandom()
447+
_, _ = b.SetRandom()
448+
449+
var witness, witness2 fp12IsEqual
450+
witness.A.Assign(&a)
451+
witness.B.Assign(&a)
452+
witness.Eq = 1
453+
454+
witness2.A.Assign(&a)
455+
witness2.B.Assign(&b)
456+
witness2.Eq = 0
457+
458+
assert := test.NewAssert(t)
459+
assert.CheckCircuit(&fp12IsEqual{}, test.WithValidAssignment(&witness), test.WithValidAssignment(&witness2), test.WithCurves(ecc.BW6_761))
460+
}

std/algebra/native/fields_bls12377/e2.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ func (e *E2) AssertIsEqual(api frontend.API, other E2) {
182182
api.AssertIsEqual(e.A1, other.A1)
183183
}
184184

185+
// IsEqual returns 1 if e is equal to other, 0 otherwise
186+
func (e *E2) IsEqual(api frontend.API, other E2) frontend.Variable {
187+
return api.And(
188+
api.IsZero(api.Sub(e.A0, other.A0)),
189+
api.IsZero(api.Sub(e.A1, other.A1)),
190+
)
191+
}
192+
185193
// Select sets e to r1 if b=1, r2 otherwise
186194
func (e *E2) Select(api frontend.API, b frontend.Variable, r1, r2 E2) *E2 {
187195

std/algebra/native/fields_bls12377/e2_test.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestAddFp2(t *testing.T) {
3939
witness.C.Assign(&c)
4040

4141
assert := test.NewAssert(t)
42-
assert.SolvingSucceeded(&e2Add{}, &witness, test.WithCurves(ecc.BW6_761))
42+
assert.CheckCircuit(&e2Add{}, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
4343

4444
}
4545

@@ -98,7 +98,7 @@ func TestMulFp2(t *testing.T) {
9898
witness.C.Assign(&c)
9999

100100
assert := test.NewAssert(t)
101-
assert.SolvingSucceeded(&e2Mul{}, &witness, test.WithCurves(ecc.BW6_761))
101+
assert.CheckCircuit(&e2Mul{}, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
102102

103103
}
104104

@@ -128,7 +128,7 @@ func TestDivFp2(t *testing.T) {
128128
witness.C.Assign(&c)
129129

130130
assert := test.NewAssert(t)
131-
assert.SolvingSucceeded(&e2Div{}, &witness, test.WithCurves(ecc.BW6_761))
131+
assert.CheckCircuit(&e2Div{}, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
132132

133133
}
134134

@@ -228,3 +228,35 @@ func TestInverseFp2(t *testing.T) {
228228
assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
229229

230230
}
231+
232+
type fp2IsEqual struct {
233+
A E2
234+
B E2 `gnark:",public"`
235+
Result frontend.Variable `gnark:",public"`
236+
}
237+
238+
func (c *fp2IsEqual) Define(api frontend.API) error {
239+
res := c.A.IsEqual(api, c.B)
240+
api.AssertIsEqual(res, c.Result)
241+
return nil
242+
}
243+
244+
func TestIsEqualFp2(t *testing.T) {
245+
assert := test.NewAssert(t)
246+
247+
var circuit, validWitness, validWitness2 fp2IsEqual
248+
var a bls12377.E2
249+
var b bls12377.E2
250+
a.SetRandom()
251+
b.SetRandom()
252+
253+
validWitness.A.Assign(&a)
254+
validWitness.B.Assign(&a)
255+
validWitness.Result = 1
256+
257+
validWitness2.A.Assign(&a)
258+
validWitness2.B.Assign(&b)
259+
validWitness2.Result = 0
260+
261+
assert.CheckCircuit(&circuit, test.WithValidAssignment(&validWitness), test.WithValidAssignment(&validWitness2), test.WithCurves(ecc.BW6_761))
262+
}

std/algebra/native/fields_bls12377/e6.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,17 @@ func (e *E6) AssertIsEqual(api frontend.API, other E6) {
345345
e.B2.AssertIsEqual(api, other.B2)
346346
}
347347

348+
// IsEqual returns 1 if e is equal to other, 0 otherwise
349+
func (e *E6) IsEqual(api frontend.API, other E6) frontend.Variable {
350+
b0Equal := e.B0.IsEqual(api, other.B0)
351+
b1Equal := e.B1.IsEqual(api, other.B1)
352+
b2Equal := e.B2.IsEqual(api, other.B2)
353+
// inputs are already boolean, so multiplication suffices.
354+
isEqual := api.Mul(b0Equal, b1Equal, b2Equal)
355+
api.Compiler().MarkBoolean(isEqual)
356+
return isEqual
357+
}
358+
348359
// MulByE2 multiplies an element in E6 by an element in E2
349360
func (e *E6) MulByE2(api frontend.API, e1 E6, e2 E2) *E6 {
350361
e.B0.Mul(api, e1.B0, e2)

std/algebra/native/fields_bls12377/e6_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,5 +271,36 @@ func TestDivFp6(t *testing.T) {
271271
witness.C.Assign(&c)
272272

273273
assert := test.NewAssert(t)
274-
assert.SolvingSucceeded(&e6Div{}, &witness, test.WithCurves(ecc.BW6_761))
274+
assert.CheckCircuit(&e6Div{}, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761))
275+
}
276+
277+
type e6IsEqual struct {
278+
A, B E6
279+
Eq frontend.Variable `gnark:",public"`
280+
}
281+
282+
func (circuit *e6IsEqual) Define(api frontend.API) error {
283+
isEqual := circuit.A.IsEqual(api, circuit.B)
284+
api.AssertIsEqual(isEqual, circuit.Eq)
285+
return nil
286+
}
287+
288+
func TestE6IsEqual(t *testing.T) {
289+
290+
// witness values
291+
var a, b bls12377.E6
292+
_, _ = a.SetRandom()
293+
_, _ = b.SetRandom()
294+
295+
var witness, witness2 e6IsEqual
296+
witness.A.Assign(&a)
297+
witness.B.Assign(&a)
298+
witness.Eq = 1
299+
300+
witness2.A.Assign(&a)
301+
witness2.B.Assign(&b)
302+
witness2.Eq = 0
303+
304+
assert := test.NewAssert(t)
305+
assert.CheckCircuit(&e6IsEqual{}, test.WithValidAssignment(&witness), test.WithValidAssignment(&witness2), test.WithCurves(ecc.BW6_761))
275306
}

std/algebra/native/sw_bls12377/pairing2.go

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -111,42 +111,6 @@ func (c *Curve) AssertIsEqual(P, Q *G1Affine) {
111111
P.AssertIsEqual(c.api, *Q)
112112
}
113113

114-
func (pr *Pairing) IsEqual(x, y *GT) frontend.Variable {
115-
diff0 := pr.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0)
116-
diff1 := pr.api.Sub(&x.C0.B0.A1, &y.C0.B0.A1)
117-
diff2 := pr.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0)
118-
diff3 := pr.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1)
119-
diff4 := pr.api.Sub(&x.C0.B1.A0, &y.C0.B1.A0)
120-
diff5 := pr.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1)
121-
diff6 := pr.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0)
122-
diff7 := pr.api.Sub(&x.C1.B0.A1, &y.C1.B0.A1)
123-
diff8 := pr.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0)
124-
diff9 := pr.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1)
125-
diff10 := pr.api.Sub(&x.C1.B1.A0, &y.C1.B1.A0)
126-
diff11 := pr.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1)
127-
128-
isZero0 := pr.api.IsZero(diff0)
129-
isZero1 := pr.api.IsZero(diff1)
130-
isZero2 := pr.api.IsZero(diff2)
131-
isZero3 := pr.api.IsZero(diff3)
132-
isZero4 := pr.api.IsZero(diff4)
133-
isZero5 := pr.api.IsZero(diff5)
134-
isZero6 := pr.api.IsZero(diff6)
135-
isZero7 := pr.api.IsZero(diff7)
136-
isZero8 := pr.api.IsZero(diff8)
137-
isZero9 := pr.api.IsZero(diff9)
138-
isZero10 := pr.api.IsZero(diff10)
139-
isZero11 := pr.api.IsZero(diff11)
140-
141-
return pr.api.And(
142-
pr.api.And(
143-
pr.api.And(pr.api.And(isZero0, isZero1), pr.api.And(isZero2, isZero3)),
144-
pr.api.And(pr.api.And(isZero4, isZero5), pr.api.And(isZero6, isZero7)),
145-
),
146-
pr.api.And(pr.api.And(isZero8, isZero9), pr.api.And(isZero10, isZero11)),
147-
)
148-
}
149-
150114
// Neg negates P and returns the result. Does not modify P.
151115
func (c *Curve) Neg(P *G1Affine) *G1Affine {
152116
res := &G1Affine{
@@ -367,6 +331,11 @@ func (pr *Pairing) AssertIsEqual(e1, e2 *GT) {
367331
e1.AssertIsEqual(pr.api, *e2)
368332
}
369333

334+
// IsEqual returns 1 if x is equal to y, 0 otherwise.
335+
func (pr *Pairing) IsEqual(x, y *GT) frontend.Variable {
336+
return x.IsEqual(pr.api, *y)
337+
}
338+
370339
func (pr *Pairing) MuxG2(sel frontend.Variable, inputs ...*G2Affine) *G2Affine {
371340
if len(inputs) == 0 {
372341
return nil

std/recursion/groth16/verifier.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -606,16 +606,26 @@ func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.
606606
// AssertProof asserts that the SNARK proof holds for the given witness and
607607
// verifying key.
608608
func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, GtEl], proof Proof[G1El, G2El], witness Witness[FR], opts ...VerifierOption) error {
609+
isValid, err := v.IsValidProof(vk, proof, witness, opts...)
610+
if err != nil {
611+
return err
612+
}
613+
v.api.AssertIsEqual(isValid, 1)
614+
return nil
615+
}
616+
617+
// IsValidProof returns a variable that is 1 if the proof is valid and 0 otherwise.
618+
func (v *Verifier[FR, G1El, G2El, GtEl]) IsValidProof(vk VerifyingKey[G1El, G2El, GtEl], proof Proof[G1El, G2El], witness Witness[FR], opts ...VerifierOption) (frontend.Variable, error) {
609619
if len(vk.CommitmentKeys) != len(proof.Commitments) {
610-
return fmt.Errorf("invalid number of commitments, got %d, expected %d", len(proof.Commitments), len(vk.CommitmentKeys))
620+
return 0, fmt.Errorf("invalid number of commitments, got %d, expected %d", len(proof.Commitments), len(vk.CommitmentKeys))
611621
}
612622
if len(vk.CommitmentKeys) != len(vk.PublicAndCommitmentCommitted) {
613-
return fmt.Errorf("invalid number of commitment keys, got %d, expected %d", len(vk.CommitmentKeys), len(vk.PublicAndCommitmentCommitted))
623+
return 0, fmt.Errorf("invalid number of commitment keys, got %d, expected %d", len(vk.CommitmentKeys), len(vk.PublicAndCommitmentCommitted))
614624
}
615625
var fr FR
616626
nbPublicVars := len(vk.G1.K) - len(vk.PublicAndCommitmentCommitted)
617627
if len(witness.Public) != nbPublicVars-1 {
618-
return fmt.Errorf("invalid witness size, got %d, expected %d (public - ONE_WIRE)", len(witness.Public), len(vk.G1.K)-1)
628+
return 0, fmt.Errorf("invalid witness size, got %d, expected %d (public - ONE_WIRE)", len(witness.Public), len(vk.G1.K)-1)
619629
}
620630

621631
inP := make([]*G1El, len(vk.G1.K)-1) // first is for the one wire, we add it manually after MSM
@@ -629,11 +639,11 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El,
629639

630640
opt, err := newCfg(opts...)
631641
if err != nil {
632-
return fmt.Errorf("apply options: %w", err)
642+
return 0, fmt.Errorf("apply options: %w", err)
633643
}
634644
hashToField, err := recursion.NewHash(v.api, fr.Modulus(), true)
635645
if err != nil {
636-
return fmt.Errorf("hash to field: %w", err)
646+
return 0, fmt.Errorf("hash to field: %w", err)
637647
}
638648

639649
maxNbPublicCommitted := 0
@@ -662,16 +672,16 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El,
662672
// explicitly do not verify the commitment as there is nothing
663673
case 1:
664674
if err = v.commitment.AssertCommitment(proof.Commitments[0], proof.CommitmentPok, vk.CommitmentKeys[0], opt.pedopt...); err != nil {
665-
return fmt.Errorf("assert commitment: %w", err)
675+
return 0, fmt.Errorf("assert commitment: %w", err)
666676
}
667677
default:
668678
// TODO: we support only a single commitment in the recursion for now
669-
return fmt.Errorf("multiple commitments are not supported")
679+
return 0, fmt.Errorf("multiple commitments are not supported")
670680
}
671681

672682
kSum, err := v.curve.MultiScalarMul(inP, inS, opt.algopt...)
673683
if err != nil {
674-
return fmt.Errorf("multi scalar mul: %w", err)
684+
return 0, fmt.Errorf("multi scalar mul: %w", err)
675685
}
676686
kSum = v.curve.Add(kSum, &vk.G1.K[0])
677687

@@ -686,10 +696,9 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El,
686696
}
687697
pairing, err := v.pairing.Pair([]*G1El{kSum, &proof.Krs, &proof.Ar}, []*G2El{&vk.G2.GammaNeg, &vk.G2.DeltaNeg, &proof.Bs})
688698
if err != nil {
689-
return fmt.Errorf("pairing: %w", err)
699+
return 0, fmt.Errorf("pairing: %w", err)
690700
}
691-
v.pairing.AssertIsEqual(pairing, &vk.E)
692-
return nil
701+
return v.pairing.IsEqual(pairing, &vk.E), nil
693702
}
694703

695704
// SwitchVerification key switches the verification key based on the provided

0 commit comments

Comments
 (0)