Skip to content

Commit a3ad59a

Browse files
ivokubCopilotyelhousni
authored
fix: GLV subscalar range checks (#1778)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Youssef El Housni <youssef.elhousni@consensys.net>
1 parent 235d0b6 commit a3ad59a

16 files changed

Lines changed: 562 additions & 272 deletions

File tree

internal/stats/latest_stats.csv

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,24 +95,24 @@ pairing_bn254,bn254,groth16,506052,823961
9595
pairing_bn254,bn254,plonk,1646819,1573151
9696
pairing_bw6761,bn254,groth16,1589471,2646707
9797
pairing_bw6761,bn254,plonk,5318762,5097941
98-
scalar_mul_G1_bn254,bn254,groth16,108168,163915
99-
scalar_mul_G1_bn254,bn254,plonk,355353,340385
100-
scalar_mul_G1_bn254_incomplete,bn254,groth16,51579,81902
101-
scalar_mul_G1_bn254_incomplete,bn254,plonk,185916,179316
102-
scalar_mul_G2_bls12381,bn254,groth16,303414,456759
103-
scalar_mul_G2_bls12381,bn254,plonk,989717,947171
104-
scalar_mul_G2_bn254,bn254,groth16,217155,326138
105-
scalar_mul_G2_bn254,bn254,plonk,721207,689834
106-
scalar_mul_G2_bw6761,bn254,groth16,389209,617382
107-
scalar_mul_G2_bw6761,bn254,plonk,1244592,1192510
108-
scalar_mul_P256,bn254,groth16,96724,151768
109-
scalar_mul_P256,bn254,plonk,328895,315729
110-
scalar_mul_P256_incomplete,bn254,groth16,75542,121798
111-
scalar_mul_P256_incomplete,bn254,plonk,263160,253523
112-
scalar_mul_secp256k1,bn254,groth16,108204,163975
113-
scalar_mul_secp256k1,bn254,plonk,355502,340530
114-
scalar_mul_secp256k1_incomplete,bn254,groth16,51619,81970
115-
scalar_mul_secp256k1_incomplete,bn254,plonk,186082,179475
98+
scalar_mul_G1_bn254,bn254,groth16,107499,163170
99+
scalar_mul_G1_bn254,bn254,plonk,353793,338853
100+
scalar_mul_G1_bn254_incomplete,bn254,groth16,50892,81121
101+
scalar_mul_G1_bn254_incomplete,bn254,plonk,184266,177694
102+
scalar_mul_G2_bls12381,bn254,groth16,302753,456022
103+
scalar_mul_G2_bls12381,bn254,plonk,988292,945775
104+
scalar_mul_G2_bn254,bn254,groth16,216495,325402
105+
scalar_mul_G2_bn254,bn254,plonk,719650,688306
106+
scalar_mul_G2_bw6761,bn254,groth16,387972,616049
107+
scalar_mul_G2_bw6761,bn254,plonk,1241833,1189810
108+
scalar_mul_P256,bn254,groth16,96434,151466
109+
scalar_mul_P256,bn254,plonk,328264,315107
110+
scalar_mul_P256_incomplete,bn254,groth16,75252,121496
111+
scalar_mul_P256_incomplete,bn254,plonk,262529,252901
112+
scalar_mul_secp256k1,bn254,groth16,107536,163231
113+
scalar_mul_secp256k1,bn254,plonk,353942,338998
114+
scalar_mul_secp256k1_incomplete,bn254,groth16,50932,81189
115+
scalar_mul_secp256k1_incomplete,bn254,plonk,184432,177853
116116
selector/binaryMux_4,bn254,groth16,5,3
117117
selector/binaryMux_4,bls12_377,groth16,5,3
118118
selector/binaryMux_4,bls12_381,groth16,5,3

std/algebra/emulated/sw_bls12381/g2.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
693693
if err != nil {
694694
panic(err)
695695
}
696+
var st ScalarField
697+
// LLL Hermite bound: u_i, v_i < γ₄·r^(1/4), fits in (BitLen+3)/4 + 2 bits.
698+
nbits := (st.Modulus().BitLen()+3)/4 + 2
696699

697700
// handle 0-scalar and (-1)-scalar cases
698701
var isScalarZero, isScalarZeroOrMinusOne, isScalarOne, isScalarMinusOne frontend.Variable
@@ -708,15 +711,16 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
708711

709712
// Decompose s into (u1, u2, v1, v2) via LLL: s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0
710713
// (mod r), with each sub-scalar bounded by ~r^(1/4).
711-
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue})
714+
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue},
715+
emulated.WithHintOutputRangeCheckBits(map[int]int{4: nbits, 5: nbits, 6: nbits, 7: nbits}))
712716
if err != nil {
713717
panic(fmt.Sprintf("rationalReconstructExtG2 hint: %v", err))
714718
}
715719
u1, u2, v1, v2 := sd[0], sd[1], sd[2], sd[3]
716720
isNegu1, isNegu2, isNegv1, isNegv2 := signs[0], signs[1], signs[2], signs[3]
717721

718722
// Verify s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r).
719-
var st ScalarField
723+
720724
sv1 := g2.fr.Mul(_s, v1)
721725
sλv2 := g2.fr.Mul(_s, g2.fr.Mul(g2.eigenvalue, v2))
722726
λu2 := g2.fr.Mul(g2.eigenvalue, u2)
@@ -834,8 +838,6 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
834838
g2GenPoint := &G2Affine{P: *g2.g2Gen}
835839
Acc = addFn(Acc, g2GenPoint)
836840

837-
// LLL Hermite bound: u_i, v_i < γ₄·r^(1/4), fits in (BitLen+3)/4 + 2 bits.
838-
nbits := (st.Modulus().BitLen()+3)/4 + 2
839841
u1bits := g2.fr.ToBits(u1)
840842
u2bits := g2.fr.ToBits(u2)
841843
v1bits := g2.fr.ToBits(v1)

std/algebra/emulated/sw_bls12381/hints.go

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"math/big"
77

88
"github.com/consensys/gnark-crypto/algebra/lattice"
9-
"github.com/consensys/gnark-crypto/ecc"
109
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
1110
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
1211
"github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve"
@@ -24,7 +23,6 @@ func GetHints() []solver.Hint {
2423
finalExpHint,
2524
pairingCheckHint,
2625
millerLoopAndCheckFinalExpHint,
27-
decomposeScalarG1,
2826
scalarMulG2Hint,
2927
rationalReconstructExtG2,
3028
g1SqrtRatioHint,
@@ -287,49 +285,6 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp
287285
})
288286
}
289287

290-
func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
291-
return emulated.UnwrapHintContext(mod, inputs, outputs, func(hc emulated.HintContext) error {
292-
moduli := hc.EmulatedModuli()
293-
if len(moduli) != 1 {
294-
return fmt.Errorf("expecting one moduli, got %d", len(moduli))
295-
}
296-
_, nativeOutputs := hc.NativeInputsOutputs()
297-
if len(nativeOutputs) != 2 {
298-
return fmt.Errorf("expecting two outputs, got %d", len(nativeOutputs))
299-
}
300-
emuInputs, emuOutputs := hc.InputsOutputs(moduli[0])
301-
if len(emuInputs) != 2 {
302-
return fmt.Errorf("expecting two inputs, got %d", len(emuInputs))
303-
}
304-
if len(emuOutputs) != 2 {
305-
return fmt.Errorf("expecting two outputs, got %d", len(emuOutputs))
306-
}
307-
308-
glvBasis := new(ecc.Lattice)
309-
ecc.PrecomputeLattice(moduli[0], emuInputs[1], glvBasis)
310-
sp := ecc.SplitScalar(emuInputs[0], glvBasis)
311-
emuOutputs[0].Set(&sp[0])
312-
emuOutputs[1].Set(&sp[1])
313-
nativeOutputs[0].SetUint64(0)
314-
nativeOutputs[1].SetUint64(0)
315-
// we need the absolute values for the in-circuit computations,
316-
// otherwise the negative values will be reduced modulo the SNARK scalar
317-
// field and not the emulated field.
318-
// output0 = |s0| mod r
319-
// output1 = |s1| mod r
320-
if emuOutputs[0].Sign() == -1 {
321-
emuOutputs[0].Neg(emuOutputs[0])
322-
nativeOutputs[0].SetUint64(1)
323-
}
324-
if emuOutputs[1].Sign() == -1 {
325-
emuOutputs[1].Neg(emuOutputs[1])
326-
nativeOutputs[1].SetUint64(1)
327-
}
328-
329-
return nil
330-
})
331-
}
332-
333288
// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue
334289
// if not, we get sqrt(Z * u / v). Recall that Z is non-residue
335290
// If v = 0, u/v is meaningless and the output is unspecified, without raising an error.

std/algebra/emulated/sw_bn254/g2.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
478478
if err != nil {
479479
panic(err)
480480
}
481+
var st ScalarField
482+
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
483+
nbits := (st.Modulus().BitLen()+3)/4 + 2
481484

482485
// handle 0-scalar and (-1)-scalar cases
483486
var isScalarZero, isScalarZeroOrMinusOne, isScalarOne, isScalarMinusOne frontend.Variable
@@ -493,15 +496,16 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
493496

494497
// Decompose s into (u1, u2, v1, v2) via LLL: s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0
495498
// (mod r), with each sub-scalar bounded by ~r^(1/4).
496-
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue})
499+
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue},
500+
emulated.WithHintOutputRangeCheckBits(map[int]int{4: nbits, 5: nbits, 6: nbits, 7: nbits}))
497501
if err != nil {
498502
panic(fmt.Sprintf("rationalReconstructExtG2 hint: %v", err))
499503
}
500504
u1, u2, v1, v2 := sd[0], sd[1], sd[2], sd[3]
501505
isNegu1, isNegu2, isNegv1, isNegv2 := signs[0], signs[1], signs[2], signs[3]
502506

503507
// Verify s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r).
504-
var st ScalarField
508+
505509
sv1 := g2.fr.Mul(_s, v1)
506510
sλv2 := g2.fr.Mul(_s, g2.fr.Mul(g2.eigenvalue, v2))
507511
λu2 := g2.fr.Mul(g2.eigenvalue, u2)
@@ -629,8 +633,6 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
629633
g2GenPoint := &G2Affine{P: *g2.g2Gen}
630634
Acc = addFn(Acc, g2GenPoint)
631635

632-
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
633-
nbits := (st.Modulus().BitLen()+3)/4 + 2
634636
u1bits := g2.fr.ToBits(u1)
635637
u2bits := g2.fr.ToBits(u2)
636638
v1bits := g2.fr.ToBits(v1)

std/algebra/emulated/sw_bw6761/g2.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
408408
if err != nil {
409409
panic(err)
410410
}
411+
var st ScalarField
412+
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
413+
nbits := (st.Modulus().BitLen()+3)/4 + 2
411414

412415
// handle 0-scalar and (-1)-scalar cases
413416
var isScalarZero, isScalarZeroOrMinusOne, isScalarOne, isScalarMinusOne frontend.Variable
@@ -423,15 +426,15 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
423426

424427
// Decompose s into (u1, u2, v1, v2) via LLL: s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0
425428
// (mod r), with each sub-scalar bounded by ~r^(1/4).
426-
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue})
429+
signs, sd, err := g2.fr.NewHintGeneric(rationalReconstructExtG2, 4, 4, nil, []*emulated.Element[ScalarField]{_s, g2.eigenvalue},
430+
emulated.WithHintOutputRangeCheckBits(map[int]int{4: nbits, 5: nbits, 6: nbits, 7: nbits}))
427431
if err != nil {
428432
panic(fmt.Sprintf("rationalReconstructExtG2 hint: %v", err))
429433
}
430434
u1, u2, v1, v2 := sd[0], sd[1], sd[2], sd[3]
431435
isNegu1, isNegu2, isNegv1, isNegv2 := signs[0], signs[1], signs[2], signs[3]
432436

433437
// Verify s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r).
434-
var st ScalarField
435438
sv1 := g2.fr.Mul(_s, v1)
436439
sλv2 := g2.fr.Mul(_s, g2.fr.Mul(g2.eigenvalue, v2))
437440
λu2 := g2.fr.Mul(g2.eigenvalue, u2)
@@ -559,8 +562,6 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
559562
g2GenPoint := &G2Affine{P: *g2.g2Gen}
560563
Acc = addFn(Acc, g2GenPoint)
561564

562-
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
563-
nbits := (st.Modulus().BitLen()+3)/4 + 2
564565
u1bits := g2.fr.ToBits(u1)
565566
u2bits := g2.fr.ToBits(u2)
566567
v1bits := g2.fr.ToBits(v1)

std/algebra/emulated/sw_bw6761/hints.go

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"math/big"
66

77
"github.com/consensys/gnark-crypto/algebra/lattice"
8-
"github.com/consensys/gnark-crypto/ecc"
98
bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761"
109
"github.com/consensys/gnark/constraint/solver"
1110
"github.com/consensys/gnark/std/math/emulated"
@@ -20,7 +19,6 @@ func GetHints() []solver.Hint {
2019
return []solver.Hint{
2120
finalExpHint,
2221
pairingCheckHint,
23-
decomposeScalarG1,
2422
scalarMulG2Hint,
2523
rationalReconstructExtG2,
2624
}
@@ -118,49 +116,6 @@ func finalExpWitness(millerLoop *bw6761.E6, mInv *big.Int) (residueWitness bw676
118116
return residueWitness
119117
}
120118

121-
func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
122-
return emulated.UnwrapHintContext(mod, inputs, outputs, func(hc emulated.HintContext) error {
123-
moduli := hc.EmulatedModuli()
124-
if len(moduli) != 1 {
125-
return fmt.Errorf("expecting one moduli, got %d", len(moduli))
126-
}
127-
_, nativeOutputs := hc.NativeInputsOutputs()
128-
if len(nativeOutputs) != 2 {
129-
return fmt.Errorf("expecting two outputs, got %d", len(nativeOutputs))
130-
}
131-
emuInputs, emuOutputs := hc.InputsOutputs(moduli[0])
132-
if len(emuInputs) != 2 {
133-
return fmt.Errorf("expecting two inputs, got %d", len(emuInputs))
134-
}
135-
if len(emuOutputs) != 2 {
136-
return fmt.Errorf("expecting two outputs, got %d", len(emuOutputs))
137-
}
138-
139-
glvBasis := new(ecc.Lattice)
140-
ecc.PrecomputeLattice(moduli[0], emuInputs[1], glvBasis)
141-
sp := ecc.SplitScalar(emuInputs[0], glvBasis)
142-
emuOutputs[0].Set(&sp[0])
143-
emuOutputs[1].Set(&sp[1])
144-
nativeOutputs[0].SetUint64(0)
145-
nativeOutputs[1].SetUint64(0)
146-
// we need the absolute values for the in-circuit computations,
147-
// otherwise the negative values will be reduced modulo the SNARK scalar
148-
// field and not the emulated field.
149-
// output0 = |s0| mod r
150-
// output1 = |s1| mod r
151-
if emuOutputs[0].Sign() == -1 {
152-
emuOutputs[0].Neg(emuOutputs[0])
153-
nativeOutputs[0].SetUint64(1)
154-
}
155-
if emuOutputs[1].Sign() == -1 {
156-
emuOutputs[1].Neg(emuOutputs[1])
157-
nativeOutputs[1].SetUint64(1)
158-
}
159-
160-
return nil
161-
})
162-
}
163-
164119
func scalarMulG2Hint(field *big.Int, inputs []*big.Int, outputs []*big.Int) error {
165120
return emulated.UnwrapHintContext(field, inputs, outputs, func(hc emulated.HintContext) error {
166121
moduli := hc.EmulatedModuli()

0 commit comments

Comments
 (0)