Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c1f1d10
feat: add option for range checking hint outputs
ivokub Jun 8, 2026
f7027a7
feat: enforceWidth allow taking other nbs of bits
ivokub Jun 8, 2026
f3b5144
feat: add packLimbsWidthWidth for exact limb packing
ivokub Jun 8, 2026
6a98ab2
feat: take hint options for generic hints
ivokub Jun 8, 2026
0f983e4
fix: return empty config when no options
ivokub Jun 8, 2026
02b48fa
feat: conditional range checking of the outputs of non-native hints
ivokub Jun 8, 2026
09492e0
test: add test for custom range checks
ivokub Jun 8, 2026
65a5671
test: add more test cases for hint output range check options
ivokub Jun 8, 2026
06afaa2
feat: make bits=0 to disable range checks
ivokub Jun 8, 2026
079ab33
fix: sw_emulated range check GLV output
ivokub Jun 8, 2026
cd0efe7
chore: remove unused hint functions
ivokub Jun 8, 2026
0c6b15d
fix: sw_bls12381 range check GLV subscalars
ivokub Jun 8, 2026
a35848d
fix: sw_bn254 range check GLV subscalars
ivokub Jun 8, 2026
e95d1d6
fix: sw_bw6761 range check GLV subscalars
ivokub Jun 8, 2026
cc27c3a
chore: sw_grumpkin simplify callDecompose
ivokub Jun 8, 2026
978c0c6
fix: sw_grumpkin range check subscalars
ivokub Jun 8, 2026
2f88252
fix: grumpking scalar decomposition calling with generic hint
ivokub Jun 8, 2026
df9ba21
chore: update stats
ivokub Jun 8, 2026
a06909b
docs: more consice doc
ivokub Jun 9, 2026
98407e2
docs: clarify doc
ivokub Jun 9, 2026
27b8af3
feat: add sanity check for range check override
ivokub Jun 9, 2026
32b5b57
docs: clarify comment
ivokub Jun 9, 2026
3069171
fix: native range check bound
ivokub Jun 9, 2026
083e86e
fix: restore line
ivokub Jun 9, 2026
b7c69f4
chore: handle errors instead of panic
ivokub Jun 9, 2026
97e6f2f
feat: make non-range checked values non-internal
ivokub Jun 9, 2026
3f2b272
Potential fix for pull request finding
ivokub Jun 9, 2026
4728f06
fix: more precise small optmization range checking
ivokub Jun 9, 2026
e7e27d9
Merge branch 'master' into fix/glv-subscalars
yelhousni Jun 11, 2026
02db779
chore: update bound comment
ivokub Jun 12, 2026
852a650
chore: remove dead comment
ivokub Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions internal/stats/latest_stats.csv
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,24 @@ pairing_bn254,bn254,groth16,506052,823961
pairing_bn254,bn254,plonk,1646819,1573151
pairing_bw6761,bn254,groth16,1589471,2646707
pairing_bw6761,bn254,plonk,5318762,5097941
scalar_mul_G1_bn254,bn254,groth16,108168,163915
scalar_mul_G1_bn254,bn254,plonk,355353,340385
scalar_mul_G1_bn254_incomplete,bn254,groth16,51579,81902
scalar_mul_G1_bn254_incomplete,bn254,plonk,185916,179316
scalar_mul_G2_bls12381,bn254,groth16,303414,456759
scalar_mul_G2_bls12381,bn254,plonk,989717,947171
scalar_mul_G2_bn254,bn254,groth16,217155,326138
scalar_mul_G2_bn254,bn254,plonk,721207,689834
scalar_mul_G2_bw6761,bn254,groth16,389209,617382
scalar_mul_G2_bw6761,bn254,plonk,1244592,1192510
scalar_mul_P256,bn254,groth16,96724,151768
scalar_mul_P256,bn254,plonk,328895,315729
scalar_mul_P256_incomplete,bn254,groth16,75542,121798
scalar_mul_P256_incomplete,bn254,plonk,263160,253523
scalar_mul_secp256k1,bn254,groth16,108204,163975
scalar_mul_secp256k1,bn254,plonk,355502,340530
scalar_mul_secp256k1_incomplete,bn254,groth16,51619,81970
scalar_mul_secp256k1_incomplete,bn254,plonk,186082,179475
scalar_mul_G1_bn254,bn254,groth16,107499,163170
scalar_mul_G1_bn254,bn254,plonk,353793,338853
scalar_mul_G1_bn254_incomplete,bn254,groth16,50892,81121
scalar_mul_G1_bn254_incomplete,bn254,plonk,184266,177694
scalar_mul_G2_bls12381,bn254,groth16,302753,456022
scalar_mul_G2_bls12381,bn254,plonk,988292,945775
scalar_mul_G2_bn254,bn254,groth16,216495,325402
scalar_mul_G2_bn254,bn254,plonk,719650,688306
scalar_mul_G2_bw6761,bn254,groth16,387972,616049
scalar_mul_G2_bw6761,bn254,plonk,1241833,1189810
scalar_mul_P256,bn254,groth16,96434,151466
scalar_mul_P256,bn254,plonk,328264,315107
scalar_mul_P256_incomplete,bn254,groth16,75252,121496
scalar_mul_P256_incomplete,bn254,plonk,262529,252901
scalar_mul_secp256k1,bn254,groth16,107536,163231
scalar_mul_secp256k1,bn254,plonk,353942,338998
scalar_mul_secp256k1_incomplete,bn254,groth16,50932,81189
scalar_mul_secp256k1_incomplete,bn254,plonk,184432,177853
selector/binaryMux_4,bn254,groth16,5,3
selector/binaryMux_4,bls12_377,groth16,5,3
selector/binaryMux_4,bls12_381,groth16,5,3
Expand Down
10 changes: 6 additions & 4 deletions std/algebra/emulated/sw_bls12381/g2.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
if err != nil {
panic(err)
}
var st ScalarField
// LLL Hermite bound: u_i, v_i < γ₄·r^(1/4), fits in (BitLen+3)/4 + 2 bits.
nbits := (st.Modulus().BitLen()+3)/4 + 2

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

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

// Verify s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r).
var st ScalarField

sv1 := g2.fr.Mul(_s, v1)
sλv2 := g2.fr.Mul(_s, g2.fr.Mul(g2.eigenvalue, v2))
λu2 := g2.fr.Mul(g2.eigenvalue, u2)
Expand Down Expand Up @@ -834,8 +838,6 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
g2GenPoint := &G2Affine{P: *g2.g2Gen}
Acc = addFn(Acc, g2GenPoint)

// LLL Hermite bound: u_i, v_i < γ₄·r^(1/4), fits in (BitLen+3)/4 + 2 bits.
nbits := (st.Modulus().BitLen()+3)/4 + 2
u1bits := g2.fr.ToBits(u1)
u2bits := g2.fr.ToBits(u2)
v1bits := g2.fr.ToBits(v1)
Expand Down
45 changes: 0 additions & 45 deletions std/algebra/emulated/sw_bls12381/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"math/big"

"github.com/consensys/gnark-crypto/algebra/lattice"
"github.com/consensys/gnark-crypto/ecc"
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
"github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve"
Expand All @@ -24,7 +23,6 @@ func GetHints() []solver.Hint {
finalExpHint,
pairingCheckHint,
millerLoopAndCheckFinalExpHint,
decomposeScalarG1,
scalarMulG2Hint,
rationalReconstructExtG2,
g1SqrtRatioHint,
Expand Down Expand Up @@ -287,49 +285,6 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp
})
}

func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
return emulated.UnwrapHintContext(mod, inputs, outputs, func(hc emulated.HintContext) error {
moduli := hc.EmulatedModuli()
if len(moduli) != 1 {
return fmt.Errorf("expecting one moduli, got %d", len(moduli))
}
_, nativeOutputs := hc.NativeInputsOutputs()
if len(nativeOutputs) != 2 {
return fmt.Errorf("expecting two outputs, got %d", len(nativeOutputs))
}
emuInputs, emuOutputs := hc.InputsOutputs(moduli[0])
if len(emuInputs) != 2 {
return fmt.Errorf("expecting two inputs, got %d", len(emuInputs))
}
if len(emuOutputs) != 2 {
return fmt.Errorf("expecting two outputs, got %d", len(emuOutputs))
}

glvBasis := new(ecc.Lattice)
ecc.PrecomputeLattice(moduli[0], emuInputs[1], glvBasis)
sp := ecc.SplitScalar(emuInputs[0], glvBasis)
emuOutputs[0].Set(&sp[0])
emuOutputs[1].Set(&sp[1])
nativeOutputs[0].SetUint64(0)
nativeOutputs[1].SetUint64(0)
// we need the absolute values for the in-circuit computations,
// otherwise the negative values will be reduced modulo the SNARK scalar
// field and not the emulated field.
// output0 = |s0| mod r
// output1 = |s1| mod r
if emuOutputs[0].Sign() == -1 {
emuOutputs[0].Neg(emuOutputs[0])
nativeOutputs[0].SetUint64(1)
}
if emuOutputs[1].Sign() == -1 {
emuOutputs[1].Neg(emuOutputs[1])
nativeOutputs[1].SetUint64(1)
}

return nil
})
}

// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue
// if not, we get sqrt(Z * u / v). Recall that Z is non-residue
// If v = 0, u/v is meaningless and the output is unspecified, without raising an error.
Expand Down
10 changes: 6 additions & 4 deletions std/algebra/emulated/sw_bn254/g2.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
if err != nil {
panic(err)
}
var st ScalarField
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
nbits := (st.Modulus().BitLen()+3)/4 + 2

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

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

// Verify s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r).
var st ScalarField

sv1 := g2.fr.Mul(_s, v1)
sλv2 := g2.fr.Mul(_s, g2.fr.Mul(g2.eigenvalue, v2))
λu2 := g2.fr.Mul(g2.eigenvalue, u2)
Expand Down Expand Up @@ -629,8 +633,6 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
g2GenPoint := &G2Affine{P: *g2.g2Gen}
Acc = addFn(Acc, g2GenPoint)

// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
nbits := (st.Modulus().BitLen()+3)/4 + 2
u1bits := g2.fr.ToBits(u1)
u2bits := g2.fr.ToBits(u2)
v1bits := g2.fr.ToBits(v1)
Expand Down
9 changes: 5 additions & 4 deletions std/algebra/emulated/sw_bw6761/g2.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ func (g2 *G2) scalarMulGLVAndFakeGLV(Q *G2Affine, s *Scalar, opts ...algopts.Alg
if err != nil {
panic(err)
}
var st ScalarField
// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
nbits := (st.Modulus().BitLen()+3)/4 + 2

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

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

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

// u1, u2, v1, v2 < c*r^{1/4} where c ≈ 1.25
nbits := (st.Modulus().BitLen()+3)/4 + 2
u1bits := g2.fr.ToBits(u1)
u2bits := g2.fr.ToBits(u2)
v1bits := g2.fr.ToBits(v1)
Expand Down
45 changes: 0 additions & 45 deletions std/algebra/emulated/sw_bw6761/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"math/big"

"github.com/consensys/gnark-crypto/algebra/lattice"
"github.com/consensys/gnark-crypto/ecc"
bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761"
"github.com/consensys/gnark/constraint/solver"
"github.com/consensys/gnark/std/math/emulated"
Expand All @@ -20,7 +19,6 @@ func GetHints() []solver.Hint {
return []solver.Hint{
finalExpHint,
pairingCheckHint,
decomposeScalarG1,
scalarMulG2Hint,
rationalReconstructExtG2,
}
Expand Down Expand Up @@ -118,49 +116,6 @@ func finalExpWitness(millerLoop *bw6761.E6, mInv *big.Int) (residueWitness bw676
return residueWitness
}

func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
return emulated.UnwrapHintContext(mod, inputs, outputs, func(hc emulated.HintContext) error {
moduli := hc.EmulatedModuli()
if len(moduli) != 1 {
return fmt.Errorf("expecting one moduli, got %d", len(moduli))
}
_, nativeOutputs := hc.NativeInputsOutputs()
if len(nativeOutputs) != 2 {
return fmt.Errorf("expecting two outputs, got %d", len(nativeOutputs))
}
emuInputs, emuOutputs := hc.InputsOutputs(moduli[0])
if len(emuInputs) != 2 {
return fmt.Errorf("expecting two inputs, got %d", len(emuInputs))
}
if len(emuOutputs) != 2 {
return fmt.Errorf("expecting two outputs, got %d", len(emuOutputs))
}

glvBasis := new(ecc.Lattice)
ecc.PrecomputeLattice(moduli[0], emuInputs[1], glvBasis)
sp := ecc.SplitScalar(emuInputs[0], glvBasis)
emuOutputs[0].Set(&sp[0])
emuOutputs[1].Set(&sp[1])
nativeOutputs[0].SetUint64(0)
nativeOutputs[1].SetUint64(0)
// we need the absolute values for the in-circuit computations,
// otherwise the negative values will be reduced modulo the SNARK scalar
// field and not the emulated field.
// output0 = |s0| mod r
// output1 = |s1| mod r
if emuOutputs[0].Sign() == -1 {
emuOutputs[0].Neg(emuOutputs[0])
nativeOutputs[0].SetUint64(1)
}
if emuOutputs[1].Sign() == -1 {
emuOutputs[1].Neg(emuOutputs[1])
nativeOutputs[1].SetUint64(1)
}

return nil
})
}

func scalarMulG2Hint(field *big.Int, inputs []*big.Int, outputs []*big.Int) error {
return emulated.UnwrapHintContext(field, inputs, outputs, func(hc emulated.HintContext) error {
moduli := hc.EmulatedModuli()
Expand Down
Loading
Loading