Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
28 changes: 20 additions & 8 deletions internal/stats/latest_stats.csv
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ math/emulated/secp256k1_64,bn254,plonk,4025,3923
math/emulated/secp256k1_64,bls12_377,plonk,4025,3923
math/emulated/secp256k1_64,bls12_381,plonk,4025,3923
math/emulated/secp256k1_64,bw6_761,plonk,4025,3923
msm_G1_bn254_2,bn254,groth16,208925,312617
msm_G1_bn254_2,bn254,plonk,688811,658743
msm_P256_2,bn254,groth16,185846,288056
msm_P256_2,bn254,plonk,635297,608874
msm_babyjubjub_2,bn254,groth16,5269,5683
msm_babyjubjub_2,bn254,plonk,12389,11848
msm_bandersnatch_2,bls12_381,groth16,5532,6301
msm_bandersnatch_2,bls12_381,plonk,13470,12918
msm_jubjub_2,bls12_381,groth16,5276,5754
msm_jubjub_2,bls12_381,plonk,12332,11855
msm_secp256k1_2,bn254,groth16,208997,312737
msm_secp256k1_2,bn254,plonk,689104,659028
pairing_bls12377,bw6_761,groth16,11876,11876
pairing_bls12377,bw6_761,plonk,45565,45565
pairing_bls12381,bn254,groth16,756837,1242260
Expand All @@ -95,18 +107,18 @@ 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,115934,175413
scalar_mul_G1_bn254,bn254,plonk,381171,365027
scalar_mul_G1_bn254_incomplete,bn254,groth16,55441,87984
scalar_mul_G1_bn254_incomplete,bn254,plonk,200004,192882
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_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,117264,177389
scalar_mul_secp256k1,bn254,plonk,385623,369279
scalar_mul_secp256k1_incomplete,bn254,groth16,56125,89066
scalar_mul_secp256k1_incomplete,bn254,plonk,202518,195302
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
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
140 changes: 140 additions & 0 deletions internal/stats/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import (

"github.com/consensys/gnark"
"github.com/consensys/gnark-crypto/ecc"
twistededwardsCryptoID "github.com/consensys/gnark-crypto/ecc/twistededwards"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/algebra/algopts"
"github.com/consensys/gnark/std/algebra/emulated/sw_bls12381"
"github.com/consensys/gnark/std/algebra/emulated/sw_bn254"
"github.com/consensys/gnark/std/algebra/emulated/sw_bw6761"
"github.com/consensys/gnark/std/algebra/emulated/sw_emulated"
"github.com/consensys/gnark/std/algebra/native/sw_bls12377"
"github.com/consensys/gnark/std/algebra/native/twistededwards"
"github.com/consensys/gnark/std/hash/mimc"
"github.com/consensys/gnark/std/math/bits"
"github.com/consensys/gnark/std/math/emulated"
Expand Down Expand Up @@ -412,6 +414,144 @@ func initSnippets() {

}, ecc.BN254)

// MSM(2, n) snippets for the four curve classes — used to evaluate which
// MSM-size variant is best in complete-arithmetic mode (Phase 4 of plan).
// Baselines: existing scalar_mul_* divided by 2 gives lower bound for
// MSM(2, n) via two ScalarMul + Add.
registerSnippet("msm_secp256k1_2", func(api frontend.API, newVariable func() frontend.Variable) {
cr, err := sw_emulated.New[emulated.Secp256k1Fp, emulated.Secp256k1Fr](api, sw_emulated.GetCurveParams[emulated.Secp256k1Fp]())
if err != nil {
panic(err)
}
fr, _ := emulated.NewField[emulated.Secp256k1Fr](api)
newFr := func() *emulated.Element[emulated.Secp256k1Fr] {
n, _ := emulated.GetEffectiveFieldParams[emulated.Secp256k1Fr](api.Compiler().Field())
limbs := make([]frontend.Variable, n)
for i := range limbs {
limbs[i] = newVariable()
}
return fr.NewElement(limbs)
}
fp, _ := emulated.NewField[emulated.Secp256k1Fp](api)
newPoint := func() *sw_emulated.AffinePoint[emulated.Secp256k1Fp] {
n, _ := emulated.GetEffectiveFieldParams[emulated.Secp256k1Fp](api.Compiler().Field())
x := make([]frontend.Variable, n)
y := make([]frontend.Variable, n)
for i := range x {
x[i] = newVariable()
y[i] = newVariable()
}
return &sw_emulated.AffinePoint[emulated.Secp256k1Fp]{X: *fp.NewElement(x), Y: *fp.NewElement(y)}
}
_, _ = cr.MultiScalarMul(
[]*sw_emulated.AffinePoint[emulated.Secp256k1Fp]{newPoint(), newPoint()},
[]*emulated.Element[emulated.Secp256k1Fr]{newFr(), newFr()},
)
}, ecc.BN254)

registerSnippet("msm_P256_2", func(api frontend.API, newVariable func() frontend.Variable) {
cr, err := sw_emulated.New[emulated.P256Fp, emulated.P256Fr](api, sw_emulated.GetCurveParams[emulated.P256Fp]())
if err != nil {
panic(err)
}
fr, _ := emulated.NewField[emulated.P256Fr](api)
newFr := func() *emulated.Element[emulated.P256Fr] {
n, _ := emulated.GetEffectiveFieldParams[emulated.P256Fr](api.Compiler().Field())
limbs := make([]frontend.Variable, n)
for i := range limbs {
limbs[i] = newVariable()
}
return fr.NewElement(limbs)
}
fp, _ := emulated.NewField[emulated.P256Fp](api)
newPoint := func() *sw_emulated.AffinePoint[emulated.P256Fp] {
n, _ := emulated.GetEffectiveFieldParams[emulated.P256Fp](api.Compiler().Field())
x := make([]frontend.Variable, n)
y := make([]frontend.Variable, n)
for i := range x {
x[i] = newVariable()
y[i] = newVariable()
}
return &sw_emulated.AffinePoint[emulated.P256Fp]{X: *fp.NewElement(x), Y: *fp.NewElement(y)}
}
_, _ = cr.MultiScalarMul(
[]*sw_emulated.AffinePoint[emulated.P256Fp]{newPoint(), newPoint()},
[]*emulated.Element[emulated.P256Fr]{newFr(), newFr()},
)
}, ecc.BN254)

registerSnippet("msm_G1_bn254_2", func(api frontend.API, newVariable func() frontend.Variable) {
cr, err := sw_emulated.New[emulated.BN254Fp, emulated.BN254Fr](api, sw_emulated.GetCurveParams[emulated.BN254Fp]())
if err != nil {
panic(err)
}
fr, _ := emulated.NewField[emulated.BN254Fr](api)
newFr := func() *emulated.Element[emulated.BN254Fr] {
n, _ := emulated.GetEffectiveFieldParams[emulated.BN254Fr](api.Compiler().Field())
limbs := make([]frontend.Variable, n)
for i := range limbs {
limbs[i] = newVariable()
}
return fr.NewElement(limbs)
}
fp, _ := emulated.NewField[emulated.BN254Fp](api)
newPoint := func() *sw_emulated.AffinePoint[emulated.BN254Fp] {
n, _ := emulated.GetEffectiveFieldParams[emulated.BN254Fp](api.Compiler().Field())
x := make([]frontend.Variable, n)
y := make([]frontend.Variable, n)
for i := range x {
x[i] = newVariable()
y[i] = newVariable()
}
return &sw_emulated.AffinePoint[emulated.BN254Fp]{X: *fp.NewElement(x), Y: *fp.NewElement(y)}
}
_, _ = cr.MultiScalarMul(
[]*sw_emulated.AffinePoint[emulated.BN254Fp]{newPoint(), newPoint()},
[]*emulated.Element[emulated.BN254Fr]{newFr(), newFr()},
)
}, ecc.BN254)

// Twisted Edwards DoubleBaseScalarMul snippets — exercise the new
// MSM(3, 2n/3) (no GLV) and MSM(6, n/3) (GLV) variants from PR #1697.
registerSnippet("msm_babyjubjub_2", func(api frontend.API, newVariable func() frontend.Variable) {
curve, err := twistededwards.NewEdCurve(api, twistededwardsCryptoID.BN254)
if err != nil {
panic(err)
}
var P1, P2 twistededwards.Point
P1.X = newVariable()
P1.Y = newVariable()
P2.X = newVariable()
P2.Y = newVariable()
_ = curve.DoubleBaseScalarMulNonZero(P1, P2, newVariable(), newVariable())
}, ecc.BN254)

registerSnippet("msm_jubjub_2", func(api frontend.API, newVariable func() frontend.Variable) {
curve, err := twistededwards.NewEdCurve(api, twistededwardsCryptoID.BLS12_381)
if err != nil {
panic(err)
}
var P1, P2 twistededwards.Point
P1.X = newVariable()
P1.Y = newVariable()
P2.X = newVariable()
P2.Y = newVariable()
_ = curve.DoubleBaseScalarMulNonZero(P1, P2, newVariable(), newVariable())
}, ecc.BLS12_381)

registerSnippet("msm_bandersnatch_2", func(api frontend.API, newVariable func() frontend.Variable) {
curve, err := twistededwards.NewEdCurve(api, twistededwardsCryptoID.BLS12_381_BANDERSNATCH)
if err != nil {
panic(err)
}
var P1, P2 twistededwards.Point
P1.X = newVariable()
P1.Y = newVariable()
P2.X = newVariable()
P2.Y = newVariable()
_ = curve.DoubleBaseScalarMulNonZero(P1, P2, newVariable(), newVariable())
}, ecc.BLS12_381)

registerSnippet("selector/mux_3", func(api frontend.API, newVariable func() frontend.Variable) {
selector.Mux(api, newVariable(), newVariable(), newVariable(), newVariable())
})
Expand Down
111 changes: 64 additions & 47 deletions std/algebra/emulated/sw_emulated/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"fmt"
"math/big"

"github.com/consensys/gnark-crypto/algebra/eisenstein"
"github.com/consensys/gnark-crypto/algebra/lattice"
"github.com/consensys/gnark-crypto/ecc"
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
bls12381_fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
Expand All @@ -32,8 +32,8 @@ func GetHints() []solver.Hint {
return []solver.Hint{
decomposeScalarG1,
scalarMulHint,
halfGCD,
halfGCDEisenstein,
rationalReconstruct,
rationalReconstructExt,
}
}

Expand Down Expand Up @@ -160,7 +160,15 @@ func scalarMulHint(field *big.Int, inputs []*big.Int, outputs []*big.Int) error
})
}

func halfGCD(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
// rationalReconstruct decomposes a scalar s ∈ Fr into (s1, |s2|, signBit) such
// that s1 ≡ s2·s (mod r), with |s1|, |s2| < γ₂·√r ≈ 1.15·√r (proven LLL/Hermite
// bound from gnark-crypto/algebra/lattice). Replaces the older heuristic
// HalfGCD-based decomposition.
//
// In-circuit: 1 native sign bit + 2 emulated outputs (s1, |s2|). The caller
// reconstructs the signed s2 as ±|s2| based on the sign bit and asserts
// s1 + s·s2 ≡ 0 (mod r).
func rationalReconstruct(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 {
Expand All @@ -177,25 +185,38 @@ func halfGCD(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
if len(emuOutputs) != 2 {
return fmt.Errorf("expecting two outputs, got %d", len(emuOutputs))
}
glvBasis := new(ecc.Lattice)
ecc.PrecomputeLattice(moduli[0], emuInputs[0], glvBasis)
emuOutputs[0].Set(&glvBasis.V1[0])
emuOutputs[1].Set(&glvBasis.V1[1])
// 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
// lattice.RationalReconstruct returns (x, z) with x ≡ z·s (mod r),
// i.e., x − z·s ≡ 0 (mod r). The circuit expects: s1 + s·_s2 ≡ 0
// (mod r), so s1 = x and _s2 = −z.
rc := lattice.NewReconstructor(moduli[0])
res := rc.RationalReconstruct(emuInputs[0])
x, z := new(big.Int).Set(res[0]), new(big.Int).Set(res[1])

// Normalise so s1 ≥ 0; flipping (x, z) preserves x ≡ z·s mod r.
if x.Sign() < 0 {
x.Neg(x)
z.Neg(z)
}
emuOutputs[0].Set(x)
emuOutputs[1].Abs(z)

// signBit = 1 iff −z < 0 iff z > 0 (so the in-circuit code negates
// |z| to recover s2 = −z).
nativeOutputs[0].SetUint64(0)
if emuOutputs[1].Sign() == -1 {
emuOutputs[1].Neg(emuOutputs[1])
nativeOutputs[0].SetUint64(1) // we return the sign of the second subscalar
if z.Sign() > 0 {
nativeOutputs[0].SetUint64(1)
}
return nil
})
}

func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error {
// rationalReconstructExt is the 4-D Eisenstein-style decomposition: given a
// scalar s and GLV eigenvalue λ, finds (u1, u2, v1, v2) such that
// s·(v1 + λ·v2) + u1 + λ·u2 ≡ 0 (mod r), with |u_i|, |v_i| < γ₄·r^(1/4) ≈
// 1.25·r^(1/4) (proven LLL bound). Replaces the older Eisenstein HalfGCD.
//
// In-circuit: 4 native sign bits + 4 emulated absolute values.
func rationalReconstructExt(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 {
Expand All @@ -213,47 +234,43 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro
return fmt.Errorf("expecting four outputs, got %d", len(emuOutputs))
}

glvBasis := new(ecc.Lattice)
ecc.PrecomputeLattice(moduli[0], emuInputs[1], glvBasis)
r := eisenstein.ComplexNumber{
A0: glvBasis.V1[0],
A1: glvBasis.V1[1],
}
sp := ecc.SplitScalar(emuInputs[0], glvBasis)
// in-circuit we check that Q - [s]P = 0 or equivalently Q + [-s]P = 0
// so here we return -s instead of s.
s := eisenstein.ComplexNumber{
A0: sp[0],
A1: sp[1],
}
s.Neg(&s)
// Inputs: emuInputs[0] = s, emuInputs[1] = λ.
// In-circuit we check Q − [s]P = 0, equivalently [−s]P + Q = 0, so we
// negate the scalar before reconstruction (matches the previous
// halfGCDEisenstein convention).
k := new(big.Int).Neg(emuInputs[0])
k.Mod(k, moduli[0])

rc := lattice.NewReconstructor(moduli[0]).SetLambda(emuInputs[1])
res := rc.RationalReconstructExt(k)
// res = (x, y, z, t) with k = (x + λ·y)/(z + λ·t) mod r,
// i.e., (x + λ·y) − k·(z + λ·t) ≡ 0 (mod r).
// Mapping onto our convention u1 + λ·u2 + s·(v1 + λ·v2) ≡ 0 with k = −s:
// u1 = x, u2 = y, v1 = z, v2 = t.
u1 := new(big.Int).Set(res[0])
u2 := new(big.Int).Set(res[1])
v1 := new(big.Int).Set(res[2])
v2 := new(big.Int).Set(res[3])

emuOutputs[0].Abs(u1)
emuOutputs[1].Abs(u2)
emuOutputs[2].Abs(v1)
emuOutputs[3].Abs(v2)

res := eisenstein.HalfGCD(&r, &s)
// values
emuOutputs[0].Set(&res[0].A0)
emuOutputs[1].Set(&res[0].A1)
emuOutputs[2].Set(&res[1].A0)
emuOutputs[3].Set(&res[1].A1)
// signs
nativeOutputs[0].SetUint64(0)
nativeOutputs[1].SetUint64(0)
nativeOutputs[2].SetUint64(0)
nativeOutputs[3].SetUint64(0)

if res[0].A0.Sign() == -1 {
emuOutputs[0].Neg(emuOutputs[0])
if u1.Sign() < 0 {
nativeOutputs[0].SetUint64(1)
}
if res[0].A1.Sign() == -1 {
emuOutputs[1].Neg(emuOutputs[1])
if u2.Sign() < 0 {
nativeOutputs[1].SetUint64(1)
}
if res[1].A0.Sign() == -1 {
emuOutputs[2].Neg(emuOutputs[2])
if v1.Sign() < 0 {
nativeOutputs[2].SetUint64(1)
}
if res[1].A1.Sign() == -1 {
emuOutputs[3].Neg(emuOutputs[3])
if v2.Sign() < 0 {
nativeOutputs[3].SetUint64(1)
}
return nil
Expand Down
Loading
Loading