From 3b76bd0124dada7226d2d0defa1a4530f2def111 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 15 Aug 2024 15:50:50 -0400 Subject: [PATCH 01/60] feat: fake glv working when sub-scalars > 0 --- std/algebra/native/sw_bls12377/g1.go | 119 ++++++++++++++++++++++ std/algebra/native/sw_bls12377/g1_test.go | 37 +++++++ std/algebra/native/sw_bls12377/hints.go | 99 ++++++++++++++++++ 3 files changed, 255 insertions(+) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 8297880fcb..f137fc5fe0 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls12377 import ( + "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -671,3 +672,121 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] return P } + +// ---- +// scalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. +// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (P *G1Affine) scalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + // The context we are working is based on the `outer` curve. However, the + // points and the operations on the points are performed on the `inner` + // curve of the outer curve. We require some parameters from the inner + // curve. + cc := getInnerCurveConfig(api.Compiler().Field()) + + // first find the sub-salars + s0, s1 := callHalfGCD(api, s) + + // then compute the hinted scalar mul R = [s]Q + R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + // For BLS12 λ bitsize is 127 equal to half of r bitsize + nbits := cc.lambda.BitLen() + s0bits := api.ToBinary(s0, nbits) + s1bits := api.ToBinary(s1, nbits) + + // store Q, -Q, R, -R in a table + var tableQ, tableR [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + tableR[1] = G1Affine{X: R[0], Y: R[1]} + tableR[0].Neg(api, tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + var B G1Affine + Acc := tableQ[1] + Acc.AddAssign(api, tableR[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + R + B1 := Acc + // B2 = -Q - R + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - R + B3 := tableQ[1] + B3.AddAssign(api, tableR[0]) + // B4 = -Q + R + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen + // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 + // to it to avoid incomplete additions in the loop by forcing Acc to be + // different than the stored B. Normally, the point H should be "killed + // out" by the first doubling in the loop and the result will remain + // unchanged. However, we are using affine coordinates that do not encode + // the infinity point. Given the affine formulae, doubling (0,1) results in + // (0,-1). Since the loop size N=nbits-1 is even we need to subtract + // [2^N]H = (0,1) from the result at the end. + // + // Acc = Q + R + H + Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means + // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + } + + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) + + P.X = R[0] + P.Y = R[1] + + return P +} diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index e628cf309a..5f967d8a5d 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -933,3 +933,40 @@ func TestMultiScalarMulFolded(t *testing.T) { }, &assignment, ecc.BW6_761.ScalarField()) assert.NoError(err) } + +// -- Fake GLV test -- +type g1varScalarMulFakeGLV struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { + expected := G1Affine{} + expected.scalarMulFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestVarScalarMulG1FakeGLV(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c bls12377.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index d59ef955ef..278d0d3f7d 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -5,7 +5,11 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" ) func GetHints() []solver.Hint { @@ -13,6 +17,9 @@ func GetHints() []solver.Hint { decomposeScalarG1, decomposeScalarG1Simple, decomposeScalarG2, + decompose, + halfGCD, + scalarMulHint, } } @@ -88,3 +95,95 @@ func decomposeScalarG2(scalarField *big.Int, inputs []*big.Int, outputs []*big.I return nil } + +func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 3 { + return fmt.Errorf("expecting three inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + + // compute the resulting point [s]Q + var R bls12377.G1Affine + R.X.SetBigInt(inputs[0]) + R.Y.SetBigInt(inputs[1]) + R.ScalarMultiplication(&R, inputs[2]) + + R.X.BigInt(outputs[0]) + R.Y.BigInt(outputs[1]) + + return nil +} + +func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { + if len(nninputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(nnOutputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + glvBasis := new(ecc.Lattice) + cc := getInnerCurveConfig(nativeMod) + ecc.PrecomputeLattice(cc.fr, nninputs[0], glvBasis) + nnOutputs[0].Set(&(glvBasis.V1[0])) + nnOutputs[1].Set(&(glvBasis.V1[1])) + + return nil + }) +} + +func callHalfGCD(api frontend.API, s frontend.Variable) (s1, s2 frontend.Variable) { + var fr emparams.BLS12377Fr + sapi, err := emulated.NewField[emparams.BLS12377Fr](api) + if err != nil { + panic(err) + } + + // compute the decomposition using a hint. We have to use the emulated + // version which takes native input and outputs non-native outputs. + // + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + s * s2 == 0 mod r, + // where λ is third root of one in 𝔽_r. + sd, err := sapi.NewHintWithNativeInput(halfGCD, 2, s) + if err != nil { + panic(err) + } + // the scalar as nonnative element. We need to split at 64 bits. + limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) + if err != nil { + panic(err) + } + semu := sapi.NewElement(limbs) + // s * s2 == -s1 mod r + lhs := sapi.MulNoReduce(sd[1], semu) + rhs := sapi.Neg(sd[0]) + + sapi.AssertIsEqual(lhs, rhs) + + s1 = 0 + s2 = 0 + b := big.NewInt(1) + for i := range sd[0].Limbs { + s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) + s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) + b.Lsh(b, 64) + } + + return s1, s2 +} + +func decompose(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 && len(outputs) != 4 { + return fmt.Errorf("input/output length mismatch") + } + tmp := new(big.Int).Set(inputs[0]) + mask := new(big.Int).SetUint64(^uint64(0)) + for i := 0; i < 4; i++ { + outputs[i].And(tmp, mask) + tmp.Rsh(tmp, 64) + } + return nil +} From 0b0140d593d1f422bd913b3fcc5b4110f105c431 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 15 Aug 2024 16:39:27 -0400 Subject: [PATCH 02/60] feat(bls24): fake glv working when sub-scalars > 0 --- std/algebra/native/sw_bls24315/g1.go | 114 +++++++++++++++++++++- std/algebra/native/sw_bls24315/g1_test.go | 57 +++++++++++ std/algebra/native/sw_bls24315/hints.go | 82 ++++++++++++++++ 3 files changed, 252 insertions(+), 1 deletion(-) diff --git a/std/algebra/native/sw_bls24315/g1.go b/std/algebra/native/sw_bls24315/g1.go index afb8cec716..a6a3ea1c69 100644 --- a/std/algebra/native/sw_bls24315/g1.go +++ b/std/algebra/native/sw_bls24315/g1.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls24315 import ( + "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -225,7 +226,7 @@ func (P *G1Affine) varScalarMul(api frontend.API, Q G1Affine, s frontend.Variabl // hence have the same X coordinates. // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 + // that Acc==B or -B. So we add the point H=(0,1) on BLS24-315 of order 2 // to it to avoid incomplete additions in the loop by forcing Acc to be // different than the stored B. Normally, the point H should be "killed // out" by the first doubling in the loop and the result will remain @@ -610,3 +611,114 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] return P } + +// ---- +// scalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. +// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (P *G1Affine) scalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + // first find the sub-salars + s0, s1 := callHalfGCD(api, s) + + // then compute the hinted scalar mul R = [s]Q + R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + nbits := 127 + s0bits := api.ToBinary(s0, nbits) + s1bits := api.ToBinary(s1, nbits) + + // store Q, -Q, R, -R in a table + var tableQ, tableR [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + tableR[1] = G1Affine{X: R[0], Y: R[1]} + tableR[0].Neg(api, tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + var B G1Affine + Acc := tableQ[1] + Acc.AddAssign(api, tableR[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + R + B1 := Acc + // B2 = -Q - R + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - R + B3 := tableQ[1] + B3.AddAssign(api, tableR[0]) + // B4 = -Q + R + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen + // that Acc==B or -B. So we add the point H=(0,1) on BLS24-315 of order 2 + // to it to avoid incomplete additions in the loop by forcing Acc to be + // different than the stored B. Normally, the point H should be "killed + // out" by the first doubling in the loop and the result will remain + // unchanged. However, we are using affine coordinates that do not encode + // the infinity point. Given the affine formulae, doubling (0,1) results in + // (0,-1). Since the loop size N=nbits-1 is even we need to subtract + // [2^N]H = (0,1) from the result at the end. + // + // Acc = Q + R + H + Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means + // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + } + + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) + + P.X = R[0] + P.Y = R[1] + + return P +} diff --git a/std/algebra/native/sw_bls24315/g1_test.go b/std/algebra/native/sw_bls24315/g1_test.go index 4387f94c05..e4aa249fc3 100644 --- a/std/algebra/native/sw_bls24315/g1_test.go +++ b/std/algebra/native/sw_bls24315/g1_test.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls24315 import ( + "fmt" "math/big" "testing" @@ -24,6 +25,8 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls24-315/fp" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -933,3 +936,57 @@ func TestMultiScalarMulFolded(t *testing.T) { }, &assignment, ecc.BW6_633.ScalarField()) assert.NoError(err) } + +// -- Fake GLV test -- +type g1varScalarMulFakeGLV struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { + expected := G1Affine{} + expected.scalarMulFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestVarScalarMulG1FakeGLV(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c bls24315.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) +} + +// bench +func BenchmarkGLV(b *testing.B) { + var c g1varScalarMul + p := profile.Start() + _, _ = frontend.Compile(ecc.BW6_633.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("GLV: ", p.NbConstraints()) +} + +func BenchmarkFakeGLV(b *testing.B) { + var c g1varScalarMulFakeGLV + p := profile.Start() + _, _ = frontend.Compile(ecc.BW6_633.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("fake GLV: ", p.NbConstraints()) +} diff --git a/std/algebra/native/sw_bls24315/hints.go b/std/algebra/native/sw_bls24315/hints.go index d20fac537c..b47b36209a 100644 --- a/std/algebra/native/sw_bls24315/hints.go +++ b/std/algebra/native/sw_bls24315/hints.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" @@ -16,6 +17,8 @@ func GetHints() []solver.Hint { decomposeScalar, decomposeScalarSimple, decompose, + halfGCD, + scalarMulHint, } } @@ -113,6 +116,47 @@ func callDecomposeScalar(api frontend.API, s frontend.Variable, simple bool) (s1 return s1, s2 } +func callHalfGCD(api frontend.API, s frontend.Variable) (s1, s2 frontend.Variable) { + var fr emparams.BLS24315Fr + sapi, err := emulated.NewField[emparams.BLS24315Fr](api) + if err != nil { + panic(err) + } + + // compute the decomposition using a hint. We have to use the emulated + // version which takes native input and outputs non-native outputs. + // + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + s * s2 == 0 mod r, + // where λ is third root of one in 𝔽_r. + sd, err := sapi.NewHintWithNativeInput(halfGCD, 2, s) + if err != nil { + panic(err) + } + // the scalar as nonnative element. We need to split at 64 bits. + limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) + if err != nil { + panic(err) + } + semu := sapi.NewElement(limbs) + // s * s2 == -s1 mod r + lhs := sapi.MulNoReduce(sd[1], semu) + rhs := sapi.Neg(sd[0]) + + sapi.AssertIsEqual(lhs, rhs) + + s1 = 0 + s2 = 0 + b := big.NewInt(1) + for i := range sd[0].Limbs { + s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) + s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) + b.Lsh(b, 64) + } + + return s1, s2 +} + func decompose(mod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 1 && len(outputs) != 4 { return fmt.Errorf("input/output length mismatch") @@ -125,3 +169,41 @@ func decompose(mod *big.Int, inputs, outputs []*big.Int) error { } return nil } + +func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 3 { + return fmt.Errorf("expecting three inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + + // compute the resulting point [s]Q + var R bls24315.G1Affine + R.X.SetBigInt(inputs[0]) + R.Y.SetBigInt(inputs[1]) + R.ScalarMultiplication(&R, inputs[2]) + + R.X.BigInt(outputs[0]) + R.Y.BigInt(outputs[1]) + + return nil +} + +func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { + if len(nninputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(nnOutputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + glvBasis := new(ecc.Lattice) + cc := getInnerCurveConfig(nativeMod) + ecc.PrecomputeLattice(cc.fr, nninputs[0], glvBasis) + nnOutputs[0].Set(&(glvBasis.V1[0])) + nnOutputs[1].Set(&(glvBasis.V1[1])) + + return nil + }) +} From 6a5757119263627085905419c1c7134b80fe3abb Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 16 Aug 2024 16:28:04 -0400 Subject: [PATCH 03/60] fix: precompute lattice --- std/algebra/native/sw_bls24315/hints.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/std/algebra/native/sw_bls24315/hints.go b/std/algebra/native/sw_bls24315/hints.go index b47b36209a..4b8ba99d5c 100644 --- a/std/algebra/native/sw_bls24315/hints.go +++ b/std/algebra/native/sw_bls24315/hints.go @@ -198,11 +198,11 @@ func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { if len(nnOutputs) != 2 { return fmt.Errorf("expecting two outputs") } - glvBasis := new(ecc.Lattice) + var v0, v1 big.Int cc := getInnerCurveConfig(nativeMod) - ecc.PrecomputeLattice(cc.fr, nninputs[0], glvBasis) - nnOutputs[0].Set(&(glvBasis.V1[0])) - nnOutputs[1].Set(&(glvBasis.V1[1])) + ecc.HalfGCD(cc.fr, nninputs[0], &v0, &v1) + nnOutputs[0].Set(&v0) + nnOutputs[1].Set(&v1) return nil }) From 4ef2bff93ac74000290958abd113aa23373fb397 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 16 Aug 2024 16:36:10 -0400 Subject: [PATCH 04/60] fix: precompute lattice bls377 --- std/algebra/native/sw_bls12377/hints.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index 278d0d3f7d..765708f4fa 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -124,11 +124,11 @@ func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { if len(nnOutputs) != 2 { return fmt.Errorf("expecting two outputs") } - glvBasis := new(ecc.Lattice) + var v0, v1 big.Int cc := getInnerCurveConfig(nativeMod) - ecc.PrecomputeLattice(cc.fr, nninputs[0], glvBasis) - nnOutputs[0].Set(&(glvBasis.V1[0])) - nnOutputs[1].Set(&(glvBasis.V1[1])) + ecc.HalfGCD(cc.fr, nninputs[0], &v0, &v1) + nnOutputs[0].Set(&v0) + nnOutputs[1].Set(&v1) return nil }) From 6637cb4fbcb71f02735f8a4afc8a21613075b9ad Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 16 Aug 2024 18:49:51 -0400 Subject: [PATCH 05/60] perf(bls12-377): native decomposition --- std/algebra/native/sw_bls12377/g1.go | 7 +- std/algebra/native/sw_bls12377/g1_test.go | 20 ++++++ std/algebra/native/sw_bls12377/hints.go | 80 +++-------------------- 3 files changed, 36 insertions(+), 71 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index f137fc5fe0..7a2b87dd07 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -701,7 +701,12 @@ func (P *G1Affine) scalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Var cc := getInnerCurveConfig(api.Compiler().Field()) // first find the sub-salars - s0, s1 := callHalfGCD(api, s) + sd, err := api.Compiler().NewHint(halfGCD, 2, s) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + s0, s1 := sd[0], sd[1] // then compute the hinted scalar mul R = [s]Q R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 5f967d8a5d..02a36b1dcf 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls12377 import ( + "fmt" "math/big" "testing" @@ -24,6 +25,8 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" "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/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -970,3 +973,20 @@ func TestVarScalarMulG1FakeGLV(t *testing.T) { assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } + +// bench +func BenchmarkGLV(b *testing.B) { + var c g1varScalarMul + p := profile.Start() + _, _ = frontend.Compile(ecc.BW6_761.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("GLV: ", p.NbConstraints()) +} + +func BenchmarkFakeGLV(b *testing.B) { + var c g1varScalarMulFakeGLV + p := profile.Start() + _, _ = frontend.Compile(ecc.BW6_761.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("fake GLV: ", p.NbConstraints()) +} diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index 765708f4fa..a6600b7a43 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -7,9 +7,6 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark/constraint/solver" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/math/emulated" - "github.com/consensys/gnark/std/math/emulated/emparams" ) func GetHints() []solver.Hint { @@ -17,7 +14,6 @@ func GetHints() []solver.Hint { decomposeScalarG1, decomposeScalarG1Simple, decomposeScalarG2, - decompose, halfGCD, scalarMulHint, } @@ -116,74 +112,18 @@ func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return nil } -func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { - if len(nninputs) != 1 { - return fmt.Errorf("expecting one input") - } - if len(nnOutputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - var v0, v1 big.Int - cc := getInnerCurveConfig(nativeMod) - ecc.HalfGCD(cc.fr, nninputs[0], &v0, &v1) - nnOutputs[0].Set(&v0) - nnOutputs[1].Set(&v1) - - return nil - }) -} - -func callHalfGCD(api frontend.API, s frontend.Variable) (s1, s2 frontend.Variable) { - var fr emparams.BLS12377Fr - sapi, err := emulated.NewField[emparams.BLS12377Fr](api) - if err != nil { - panic(err) - } - - // compute the decomposition using a hint. We have to use the emulated - // version which takes native input and outputs non-native outputs. - // - // the hints allow to decompose the scalar s into s1 and s2 such that - // s1 + s * s2 == 0 mod r, - // where λ is third root of one in 𝔽_r. - sd, err := sapi.NewHintWithNativeInput(halfGCD, 2, s) - if err != nil { - panic(err) - } - // the scalar as nonnative element. We need to split at 64 bits. - limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) - if err != nil { - panic(err) +func halfGCD(nativeMod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 { + return fmt.Errorf("expecting one input") } - semu := sapi.NewElement(limbs) - // s * s2 == -s1 mod r - lhs := sapi.MulNoReduce(sd[1], semu) - rhs := sapi.Neg(sd[0]) - - sapi.AssertIsEqual(lhs, rhs) - - s1 = 0 - s2 = 0 - b := big.NewInt(1) - for i := range sd[0].Limbs { - s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) - s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) - b.Lsh(b, 64) + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") } + var v0, v1 big.Int + cc := getInnerCurveConfig(nativeMod) + ecc.HalfGCD(cc.fr, inputs[0], &v0, &v1) + outputs[0].Set(&v0) + outputs[1].Set(&v1) - return s1, s2 -} - -func decompose(mod *big.Int, inputs, outputs []*big.Int) error { - if len(inputs) != 1 && len(outputs) != 4 { - return fmt.Errorf("input/output length mismatch") - } - tmp := new(big.Int).Set(inputs[0]) - mask := new(big.Int).SetUint64(^uint64(0)) - for i := 0; i < 4; i++ { - outputs[i].And(tmp, mask) - tmp.Rsh(tmp, 64) - } return nil } From 935225a212618d334c2c5c0e7e0a653a194e5a54 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 20 Aug 2024 18:18:01 -0400 Subject: [PATCH 06/60] refactor(bls12-377, bls24-315): clean fake GLV code --- std/algebra/native/sw_bls12377/g1.go | 259 ++++++++++++---------- std/algebra/native/sw_bls12377/g1_test.go | 127 ++++++----- std/algebra/native/sw_bls24315/g1.go | 234 +++++++++---------- std/algebra/native/sw_bls24315/g1_test.go | 127 ++++++----- 4 files changed, 399 insertions(+), 348 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 7a2b87dd07..f09144677e 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -410,6 +410,142 @@ func (P *G1Affine) constScalarMul(api frontend.API, Q G1Affine, s *big.Int, opts return P } +// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. +// It implements the fake GLV explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (P *G1Affine) varScalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + + // The context we are working is based on the `outer` curve. However, the + // points and the operations on the points are performed on the `inner` + // curve of the outer curve. We require some parameters from the inner + // curve. + cc := getInnerCurveConfig(api.Compiler().Field()) + + // first find the sub-salars + sd, err := api.Compiler().NewHint(halfGCD, 2, s) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + s0, s1 := sd[0], sd[1] + + // then compute the hinted scalar mul R = [s]Q + R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + // For BLS12 λ bitsize is 127 equal to half of r bitsize + nbits := cc.lambda.BitLen() + s0bits := api.ToBinary(s0, nbits) + s1bits := api.ToBinary(s1, nbits) + + // store Q, -Q, R, -R in a table + var tableQ, tableR [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + tableR[1] = G1Affine{X: R[0], Y: R[1]} + tableR[0].Neg(api, tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + var B G1Affine + Acc := tableQ[1] + if cfg.CompleteArithmetic { + // R=(0,0) when Q=(0,0) or s=0 + // so we need unified addition. + Acc.AddUnified(api, tableR[1]) + } else { + Acc.AddAssign(api, tableR[1]) + } + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + R + B1 := Acc + // B2 = -Q - R + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - R + B3 := tableQ[1] + B3.AddAssign(api, tableR[0]) + // B4 = -Q + R + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen + // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 + // to it to avoid incomplete additions in the loop by forcing Acc to be + // different than the stored B. Normally, the point H should be "killed + // out" by the first doubling in the loop and the result will remain + // unchanged. However, we are using affine coordinates that do not encode + // the infinity point. Given the affine formulae, doubling (0,1) results in + // (0,-1). Since the loop size N=nbits-1 is even we need to subtract + // [2^N]H = (0,1) from the result at the end. + // + // Acc = Q + R + H + Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means + // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + } + + if cfg.CompleteArithmetic { + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AddUnified(api, G1Affine{X: 0, Y: -1}) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 0}) + } else { + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) + } + + P.X = R[0] + P.Y = R[1] + + return P +} + // Assign a value to self (witness assignment) func (p *G1Affine) Assign(p1 *bls12377.G1Affine) { p.X = (fr.Element)(p1.X) @@ -672,126 +808,3 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] return P } - -// ---- -// scalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. -// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. -// -// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. -// (0,0) is not on the curve but we conventionally take it as the -// neutral/infinity point as per the [EVM]. -// -// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (P *G1Affine) scalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(err) - } - var selector frontend.Variable - if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and continue - selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) - Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) - } - - // The context we are working is based on the `outer` curve. However, the - // points and the operations on the points are performed on the `inner` - // curve of the outer curve. We require some parameters from the inner - // curve. - cc := getInnerCurveConfig(api.Compiler().Field()) - - // first find the sub-salars - sd, err := api.Compiler().NewHint(halfGCD, 2, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s0, s1 := sd[0], sd[1] - - // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) - if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) - } - - // For BLS12 λ bitsize is 127 equal to half of r bitsize - nbits := cc.lambda.BitLen() - s0bits := api.ToBinary(s0, nbits) - s1bits := api.ToBinary(s1, nbits) - - // store Q, -Q, R, -R in a table - var tableQ, tableR [2]G1Affine - tableQ[1] = Q - tableQ[0].Neg(api, Q) - tableR[1] = G1Affine{X: R[0], Y: R[1]} - tableR[0].Neg(api, tableR[1]) - - // we suppose that the first bits of the sub-scalars are 1 and set: - // Acc = Q + R - var B G1Affine - Acc := tableQ[1] - Acc.AddAssign(api, tableR[1]) - - // At each iteration we need to compute: - // [2]Acc ± Q ± R. - // We can compute [2]Acc and look up the (precomputed) point B from: - // B1 = +Q + R - B1 := Acc - // B2 = -Q - R - B2 := G1Affine{} - B2.Neg(api, B1) - // B3 = +Q - R - B3 := tableQ[1] - B3.AddAssign(api, tableR[0]) - // B4 = -Q + R - B4 := G1Affine{} - B4.Neg(api, B3) - // - // Note that half the points are negatives of the other half, - // hence have the same X coordinates. - - // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 - // to it to avoid incomplete additions in the loop by forcing Acc to be - // different than the stored B. Normally, the point H should be "killed - // out" by the first doubling in the loop and the result will remain - // unchanged. However, we are using affine coordinates that do not encode - // the infinity point. Given the affine formulae, doubling (0,1) results in - // (0,-1). Since the loop size N=nbits-1 is even we need to subtract - // [2^N]H = (0,1) from the result at the end. - // - // Acc = Q + R + H - Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) - - for i := nbits - 1; i > 0; i-- { - B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) - B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) - // Acc = [2]Acc + B - Acc.DoubleAndAdd(api, &Acc, &B) - } - - // i = 0 - // subtract the Q, R if the first bits are 0. - // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means - // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - if cfg.CompleteArithmetic { - tableQ[0].AddUnified(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddUnified(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - } else { - tableQ[0].AddAssign(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddAssign(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - } - - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) - - P.X = R[0] - P.Y = R[1] - - return P -} diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 02a36b1dcf..4b566b9406 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -17,7 +17,6 @@ limitations under the License. package sw_bls12377 import ( - "fmt" "math/big" "testing" @@ -25,8 +24,6 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" "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/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -460,6 +457,76 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } +type g1varScalarMulFakeGLV struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { + expected := G1Affine{} + expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestVarScalarMulG1FakeGLV(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c bls12377.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} + +type g1varScalarMulFakeGLVEdgeCases struct { + A G1Affine + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + infinity := G1Affine{X: 0, Y: 0} + expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) + expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, infinity) + expected2.AssertIsEqual(api, infinity) + return nil +} + +func TestVarScalarMulFakeGLVG1EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + var a bls12377.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLVEdgeCases + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} + type MultiScalarMulEdgeCasesTest struct { Points []G1Affine Scalars []emulated.Element[ScalarField] @@ -936,57 +1003,3 @@ func TestMultiScalarMulFolded(t *testing.T) { }, &assignment, ecc.BW6_761.ScalarField()) assert.NoError(err) } - -// -- Fake GLV test -- -type g1varScalarMulFakeGLV struct { - A G1Affine - C G1Affine `gnark:",public"` - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { - expected := G1Affine{} - expected.scalarMulFakeGLV(api, circuit.A, circuit.R) - expected.AssertIsEqual(api, circuit.C) - return nil -} - -func TestVarScalarMulG1FakeGLV(t *testing.T) { - // sample random point - _a := randomPointG1() - var a, c bls12377.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLV - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - // compute the result - var br big.Int - _a.ScalarMultiplication(&_a, r.BigInt(&br)) - c.FromJacobian(&_a) - witness.C.Assign(&c) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) -} - -// bench -func BenchmarkGLV(b *testing.B) { - var c g1varScalarMul - p := profile.Start() - _, _ = frontend.Compile(ecc.BW6_761.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("GLV: ", p.NbConstraints()) -} - -func BenchmarkFakeGLV(b *testing.B) { - var c g1varScalarMulFakeGLV - p := profile.Start() - _, _ = frontend.Compile(ecc.BW6_761.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("fake GLV: ", p.NbConstraints()) -} diff --git a/std/algebra/native/sw_bls24315/g1.go b/std/algebra/native/sw_bls24315/g1.go index a6a3ea1c69..7ffd42a6a1 100644 --- a/std/algebra/native/sw_bls24315/g1.go +++ b/std/algebra/native/sw_bls24315/g1.go @@ -379,6 +379,129 @@ func (p *G1Affine) AssertIsEqual(api frontend.API, other G1Affine) { api.AssertIsEqual(p.Y, other.Y) } +// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. +// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (P *G1Affine) varScalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + // first find the sub-salars + s0, s1 := callHalfGCD(api, s) + + // then compute the hinted scalar mul R = [s]Q + R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + nbits := 127 + s0bits := api.ToBinary(s0, nbits) + s1bits := api.ToBinary(s1, nbits) + + // store Q, -Q, R, -R in a table + var tableQ, tableR [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + tableR[1] = G1Affine{X: R[0], Y: R[1]} + tableR[0].Neg(api, tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + var B G1Affine + Acc := tableQ[1] + if cfg.CompleteArithmetic { + // R=(0,0) when Q=(0,0) or s=0 + // so we need unified addition. + Acc.AddUnified(api, tableR[1]) + } else { + Acc.AddAssign(api, tableR[1]) + } + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + R + B1 := Acc + // B2 = -Q - R + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - R + B3 := tableQ[1] + B3.AddAssign(api, tableR[0]) + // B4 = -Q + R + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen + // that Acc==B or -B. So we add the point H=(0,1) on BLS24-315 of order 2 + // to it to avoid incomplete additions in the loop by forcing Acc to be + // different than the stored B. Normally, the point H should be "killed + // out" by the first doubling in the loop and the result will remain + // unchanged. However, we are using affine coordinates that do not encode + // the infinity point. Given the affine formulae, doubling (0,1) results in + // (0,-1). Since the loop size N=nbits-1 is even we need to subtract + // [2^N]H = (0,1) from the result at the end. + // + // Acc = Q + R + H + Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means + // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + } + + if cfg.CompleteArithmetic { + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AddUnified(api, G1Affine{X: 0, Y: -1}) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 0}) + } else { + // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning + Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) + } + + P.X = R[0] + P.Y = R[1] + + return P +} + // DoubleAndAdd computes 2*p1+p in affine coords func (p *G1Affine) DoubleAndAdd(api frontend.API, p1, p2 *G1Affine) *G1Affine { @@ -611,114 +734,3 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] return P } - -// ---- -// scalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. -// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. -// -// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. -// (0,0) is not on the curve but we conventionally take it as the -// neutral/infinity point as per the [EVM]. -// -// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (P *G1Affine) scalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(err) - } - var selector frontend.Variable - if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and continue - selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) - Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) - } - - // first find the sub-salars - s0, s1 := callHalfGCD(api, s) - - // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) - if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) - } - - nbits := 127 - s0bits := api.ToBinary(s0, nbits) - s1bits := api.ToBinary(s1, nbits) - - // store Q, -Q, R, -R in a table - var tableQ, tableR [2]G1Affine - tableQ[1] = Q - tableQ[0].Neg(api, Q) - tableR[1] = G1Affine{X: R[0], Y: R[1]} - tableR[0].Neg(api, tableR[1]) - - // we suppose that the first bits of the sub-scalars are 1 and set: - // Acc = Q + R - var B G1Affine - Acc := tableQ[1] - Acc.AddAssign(api, tableR[1]) - - // At each iteration we need to compute: - // [2]Acc ± Q ± R. - // We can compute [2]Acc and look up the (precomputed) point B from: - // B1 = +Q + R - B1 := Acc - // B2 = -Q - R - B2 := G1Affine{} - B2.Neg(api, B1) - // B3 = +Q - R - B3 := tableQ[1] - B3.AddAssign(api, tableR[0]) - // B4 = -Q + R - B4 := G1Affine{} - B4.Neg(api, B3) - // - // Note that half the points are negatives of the other half, - // hence have the same X coordinates. - - // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS24-315 of order 2 - // to it to avoid incomplete additions in the loop by forcing Acc to be - // different than the stored B. Normally, the point H should be "killed - // out" by the first doubling in the loop and the result will remain - // unchanged. However, we are using affine coordinates that do not encode - // the infinity point. Given the affine formulae, doubling (0,1) results in - // (0,-1). Since the loop size N=nbits-1 is even we need to subtract - // [2^N]H = (0,1) from the result at the end. - // - // Acc = Q + R + H - Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) - - for i := nbits - 1; i > 0; i-- { - B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) - B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) - // Acc = [2]Acc + B - Acc.DoubleAndAdd(api, &Acc, &B) - } - - // i = 0 - // subtract the Q, R if the first bits are 0. - // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means - // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - if cfg.CompleteArithmetic { - tableQ[0].AddUnified(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddUnified(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - } else { - tableQ[0].AddAssign(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddAssign(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - } - - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) - - P.X = R[0] - P.Y = R[1] - - return P -} diff --git a/std/algebra/native/sw_bls24315/g1_test.go b/std/algebra/native/sw_bls24315/g1_test.go index e4aa249fc3..a458c98d48 100644 --- a/std/algebra/native/sw_bls24315/g1_test.go +++ b/std/algebra/native/sw_bls24315/g1_test.go @@ -17,7 +17,6 @@ limitations under the License. package sw_bls24315 import ( - "fmt" "math/big" "testing" @@ -25,8 +24,6 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls24-315/fp" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/scs" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -460,6 +457,76 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633), test.NoProverChecks()) } +type g1varScalarMulFakeGLV struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { + expected := G1Affine{} + expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestVarScalarMulG1FakeGLV(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c bls24315.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) +} + +type g1varScalarMulFakeGLVEdgeCases struct { + A G1Affine + R frontend.Variable +} + +func (circuit *g1varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + infinity := G1Affine{X: 0, Y: 0} + expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) + expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, infinity) + expected2.AssertIsEqual(api, infinity) + return nil +} + +func TestVarScalarMulFakeGLVG1EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + var a bls24315.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMulFakeGLVEdgeCases + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) +} + type MultiScalarMulEdgeCasesTest struct { Points []G1Affine Scalars []emulated.Element[ScalarField] @@ -936,57 +1003,3 @@ func TestMultiScalarMulFolded(t *testing.T) { }, &assignment, ecc.BW6_633.ScalarField()) assert.NoError(err) } - -// -- Fake GLV test -- -type g1varScalarMulFakeGLV struct { - A G1Affine - C G1Affine `gnark:",public"` - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { - expected := G1Affine{} - expected.scalarMulFakeGLV(api, circuit.A, circuit.R) - expected.AssertIsEqual(api, circuit.C) - return nil -} - -func TestVarScalarMulG1FakeGLV(t *testing.T) { - // sample random point - _a := randomPointG1() - var a, c bls24315.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLV - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - // compute the result - var br big.Int - _a.ScalarMultiplication(&_a, r.BigInt(&br)) - c.FromJacobian(&_a) - witness.C.Assign(&c) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) -} - -// bench -func BenchmarkGLV(b *testing.B) { - var c g1varScalarMul - p := profile.Start() - _, _ = frontend.Compile(ecc.BW6_633.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("GLV: ", p.NbConstraints()) -} - -func BenchmarkFakeGLV(b *testing.B) { - var c g1varScalarMulFakeGLV - p := profile.Start() - _, _ = frontend.Compile(ecc.BW6_633.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("fake GLV: ", p.NbConstraints()) -} From c45d70e5d10aec5ee303c4f0715d5fa502413127 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 22 Aug 2024 12:06:09 -0400 Subject: [PATCH 07/60] perf(bls12-377): G2 fake GLV --- std/algebra/native/sw_bls12377/g1.go | 6 +- std/algebra/native/sw_bls12377/g2.go | 157 ++++++++++++++++++++++ std/algebra/native/sw_bls12377/g2_test.go | 71 ++++++++++ std/algebra/native/sw_bls12377/hints.go | 29 +++- 4 files changed, 258 insertions(+), 5 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index f09144677e..cf222ed905 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -306,11 +306,11 @@ func (P *G1Affine) varScalarMul(api frontend.API, Q G1Affine, s frontend.Variabl } if cfg.CompleteArithmetic { - // subtract [2^N]G = (0,1) since we added H at the beginning + // subtract [2^N]H = (0,1) since we added H at the beginning Acc.AddUnified(api, G1Affine{X: 0, Y: -1}) Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) } else { - // subtract [2^N]G = (0,1) since we added H at the beginning + // subtract [2^N]H = (0,1) since we added H at the beginning Acc.AddAssign(api, G1Affine{X: 0, Y: -1}) } @@ -439,7 +439,7 @@ func (P *G1Affine) varScalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend. s0, s1 := sd[0], sd[1] // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) + R, err := api.Compiler().NewHint(scalarMulG1Hint, 2, Q.X, Q.Y, s) if err != nil { panic(fmt.Sprintf("scalar mul hint: %v", err)) } diff --git a/std/algebra/native/sw_bls12377/g2.go b/std/algebra/native/sw_bls12377/g2.go index 3b8e273dc5..a9ca888c6f 100644 --- a/std/algebra/native/sw_bls12377/g2.go +++ b/std/algebra/native/sw_bls12377/g2.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls12377 import ( + "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -433,6 +434,162 @@ func (P *g2AffP) constScalarMul(api frontend.API, Q g2AffP, s *big.Int, opts ... return P } +// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. +// It implements the fake GLV explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (P *g2AffP) varScalarMulFakeGLV(api frontend.API, Q g2AffP, s frontend.Variable, opts ...algopts.AlgebraOption) *g2AffP { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + + zero := fields_bls12377.E2{A0: 0, A1: 0} + one := fields_bls12377.E2{A0: 1, A1: 0} + // The context we are working is based on the `outer` curve. However, the + // points and the operations on the points are performed on the `inner` + // curve of the outer curve. We require some parameters from the inner + // curve. + cc := getInnerCurveConfig(api.Compiler().Field()) + + // first find the sub-salars + sd, err := api.Compiler().NewHint(halfGCD, 2, s) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + s0, s1 := sd[0], sd[1] + + // then compute the hinted scalar mul R = [s]Q + R, err := api.Compiler().NewHint(scalarMulG2Hint, 4, Q.X.A0, Q.X.A1, Q.Y.A0, Q.Y.A1, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(Q.X.IsZero(api), Q.Y.IsZero(api)) + Q.Select(api, selector, g2AffP{X: one, Y: one}, Q) + } + + // For BLS12 λ bitsize is 127 equal to half of r bitsize + nbits := cc.lambda.BitLen() + s0bits := api.ToBinary(s0, nbits) + s1bits := api.ToBinary(s1, nbits) + + // store Q, -Q, R, -R in a table + var tableQ, tableR [2]g2AffP + tableQ[1] = Q + tableQ[0].Neg(api, Q) + tableR[1] = g2AffP{ + X: fields_bls12377.E2{A0: R[0], A1: R[1]}, + Y: fields_bls12377.E2{A0: R[2], A1: R[3]}, + } + tableR[0].Neg(api, tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + var B g2AffP + Acc := tableQ[1] + if cfg.CompleteArithmetic { + // R=(0,0) when Q=(0,0) or s=0 + // so we need unified addition. + Acc.AddUnified(api, tableR[1]) + } else { + Acc.AddAssign(api, tableR[1]) + } + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + R + B1 := Acc + // B2 = -Q - R + B2 := g2AffP{} + B2.Neg(api, B1) + // B3 = +Q - R + B3 := tableQ[1] + B3.AddAssign(api, tableR[0]) + // B4 = -Q + R + B4 := g2AffP{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen + // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 + // to it to avoid incomplete additions in the loop by forcing Acc to be + // different than the stored B. Normally, the point H should be "killed + // out" by the first doubling in the loop and the result will remain + // unchanged. However, we are using affine coordinates that do not encode + // the infinity point. Given the affine formulae, doubling (0,1) results in + // (0,-1). Since the loop size N=nbits-1 is even we need to subtract + // [2^N]H = (0,1) from the result at the end. + // + // Acc = Q + Φ(Q) + G + points := getTwistPoints() + Acc.AddAssign(api, + g2AffP{ + X: fields_bls12377.E2{A0: points.G2x[0], A1: points.G2x[1]}, + Y: fields_bls12377.E2{A0: points.G2y[0], A1: points.G2y[1]}, + }, + ) + + for i := nbits - 1; i > 0; i-- { + B.X.Select(api, api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) + B.Y.Lookup2(api, s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means + // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + Acc.Select(api, selector, g2AffP{X: zero, Y: zero}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s0bits[0], Acc, tableQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableR[0]) + } + + B.X = fields_bls12377.E2{ + A0: points.G2m[nbits-1][0], + A1: points.G2m[nbits-1][1], + } + B.Y = fields_bls12377.E2{ + A0: points.G2m[nbits-1][2], + A1: points.G2m[nbits-1][3], + } + if cfg.CompleteArithmetic { + // Acc should be equal infinity + [2^N]G = B since we added G at the beginning + B.Y.Neg(api, B.Y) + Acc.AddUnified(api, B) + Acc.Select(api, selector, g2AffP{X: zero, Y: zero}, Acc) + Acc.AssertIsEqual(api, g2AffP{X: zero, Y: zero}) + } else { + // Acc should be equal infinity + [2^N]G = [2^N]G since we added H at the beginning + Acc.AssertIsEqual(api, B) + } + + P.X = fields_bls12377.E2{A0: R[0], A1: R[1]} + P.Y = fields_bls12377.E2{A0: R[2], A1: R[3]} + + return P +} + // Assign a value to self (witness assignment) func (p *g2AffP) Assign(p1 *bls12377.G2Affine) { p.X.Assign(&p1.X) diff --git a/std/algebra/native/sw_bls12377/g2_test.go b/std/algebra/native/sw_bls12377/g2_test.go index bd471b5b28..e92defdc24 100644 --- a/std/algebra/native/sw_bls12377/g2_test.go +++ b/std/algebra/native/sw_bls12377/g2_test.go @@ -374,6 +374,77 @@ func TestVarScalarMulBaseG2(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } +type g2varScalarMulFakeGLV struct { + A g2AffP + C g2AffP `gnark:",public"` + R frontend.Variable +} + +func (circuit *g2varScalarMulFakeGLV) Define(api frontend.API) error { + expected := g2AffP{} + expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestVarScalarMulG2FakeGLV(t *testing.T) { + // sample random point + _a := randomPointG2() + var a, c bls12377.G2Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g2varScalarMulFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} + +type g2varScalarMulFakeGLVEdgeCases struct { + A g2AffP + R frontend.Variable +} + +func (circuit *g2varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { + expected1 := g2AffP{} + expected2 := g2AffP{} + zero := fields_bls12377.E2{A0: 0, A1: 0} + infinity := g2AffP{X: zero, Y: zero} + expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) + expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, infinity) + expected2.AssertIsEqual(api, infinity) + return nil +} + +func TestVarScalarMulFakeGLVG2EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG2() + var a bls12377.G2Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g2varScalarMulFakeGLVEdgeCases + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} + func randomPointG2() bls12377.G2Jac { _, p2, _, _ := bls12377.Generators() diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index a6600b7a43..bf539b8c6b 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -15,7 +15,8 @@ func GetHints() []solver.Hint { decomposeScalarG1Simple, decomposeScalarG2, halfGCD, - scalarMulHint, + scalarMulG1Hint, + scalarMulG2Hint, } } @@ -92,7 +93,7 @@ func decomposeScalarG2(scalarField *big.Int, inputs []*big.Int, outputs []*big.I return nil } -func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { +func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { if len(inputs) != 3 { return fmt.Errorf("expecting three inputs") } @@ -112,6 +113,30 @@ func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return nil } +func scalarMulG2Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 5 { + return fmt.Errorf("expecting five inputs") + } + if len(outputs) != 4 { + return fmt.Errorf("expecting four outputs") + } + + // compute the resulting point [s]Q + var R bls12377.G2Affine + R.X.A0.SetBigInt(inputs[0]) + R.X.A1.SetBigInt(inputs[1]) + R.Y.A0.SetBigInt(inputs[2]) + R.Y.A1.SetBigInt(inputs[3]) + R.ScalarMultiplication(&R, inputs[4]) + + R.X.A0.BigInt(outputs[0]) + R.X.A1.BigInt(outputs[1]) + R.Y.A0.BigInt(outputs[2]) + R.Y.A1.BigInt(outputs[3]) + + return nil +} + func halfGCD(nativeMod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 1 { return fmt.Errorf("expecting one input") From 21de4a6cb09690004e10714f3cd11e9ec1857b1e Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 22 Aug 2024 23:47:25 -0400 Subject: [PATCH 08/60] feat: non-native fake GLV --- std/algebra/emulated/sw_emulated/hints.go | 79 ++++-- std/algebra/emulated/sw_emulated/point.go | 227 +++++++++++++++++- .../emulated/sw_emulated/point_test.go | 85 +++++++ 3 files changed, 370 insertions(+), 21 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 06c15d07a4..87536e4281 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" ) @@ -14,7 +15,13 @@ func init() { } func GetHints() []solver.Hint { - return []solver.Hint{decomposeScalarG1, decomposeScalarG1Signs, decomposeScalarG1Subscalars} + return []solver.Hint{ + decomposeScalarG1Signs, + decomposeScalarG1Subscalars, + scalarMulG1Hint, + halfGCDSigns, + halfGCD, + } } func decomposeScalarG1Subscalars(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { @@ -70,42 +77,74 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) }) } -func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { +func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 3 { - return fmt.Errorf("expecting two inputs") + return fmt.Errorf("expecting three inputs") } - if len(outputs) != 6 { + if len(outputs) != 2 { return fmt.Errorf("expecting two outputs") } + + // compute the resulting point [s]Q + var R bn254.G1Affine + R.X.SetBigInt(inputs[0]) + R.Y.SetBigInt(inputs[1]) + R.ScalarMultiplication(&R, inputs[2]) + + R.X.BigInt(outputs[0]) + R.Y.BigInt(outputs[1]) + + return nil + }) +} + +func halfGCDSigns(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHintWithNativeOutput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(outputs) != 1 { + return fmt.Errorf("expecting one output") + } + var modulus big.Int + modulus.SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(inputs[2], inputs[1], glvBasis) - sp := ecc.SplitScalar(inputs[0], glvBasis) - outputs[0].Set(&(sp[0])) - outputs[1].Set(&(sp[1])) - // we need the negative values for to check that s0+λ*s1 == s mod r - // output4 = s0 mod r - // output5 = s1 mod r - outputs[4].Set(outputs[0]) - outputs[5].Set(outputs[1]) + ecc.PrecomputeLattice(&modulus, inputs[0], glvBasis) + outputs[0].SetUint64(0) + if glvBasis.V1[1].Sign() == -1 { + outputs[0].SetUint64(1) + } + + return nil + }) +} + +func halfGCD(mod *big.Int, inputs, outputs []*big.Int) error { + return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + var modulus big.Int + modulus.SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(&modulus, inputs[0], glvBasis) + outputs[0].Set(&glvBasis.V1[0]) + outputs[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 - // output2 = 1 if s0 is positive, 0 if s0 is negative - // output3 = 1 if s1 is positive, 0 if s0 is negative - outputs[2].SetUint64(1) if outputs[0].Sign() == -1 { outputs[0].Neg(outputs[0]) - outputs[2].SetUint64(0) } - outputs[3].SetUint64(1) if outputs[1].Sign() == -1 { outputs[1].Neg(outputs[1]) - outputs[3].SetUint64(0) } - return nil }) } diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index e930a58d3a..16bcf21093 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -574,7 +574,7 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op var st S nbits := st.Modulus().BitLen()>>1 + 2 - // precompute -Q, -Φ(Q), Φ(Q) + // precompute -Q, Q, 3Q, -Φ(Q), Φ(Q), 3Φ(Q) var tableQ, tablePhiQ [3]*AffinePoint[B] negQY := c.baseApi.Neg(&Q.Y) tableQ[1] = &AffinePoint[B]{ @@ -1255,3 +1255,228 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ return res, nil } } + +// Fake GLV +func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[B], opts ...algopts.AlgebraOption) *AffinePoint[B] { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + + // first find the sub-salars + sd, err := c.baseApi.NewHint(halfGCD, 2, s) + if err != nil { + panic(fmt.Sprintf("halfGCD hint: %v", err)) + } + s1, s2 := sd[0], sd[1] + sign, err := c.baseApi.NewHintWithNativeOutput(halfGCDSigns, 1, s) + if err != nil { + panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) + } + // _s2 := c.baseApi.Select(sign[0], c.baseApi.Neg(s2), s2) + // check that s1 + s*_s2 == 0 + // This is only true in scalarApi (mod r) not baseApi (mod p)! + // c.baseApi.AssertIsEqual( + // c.baseApi.Add(s1, c.baseApi.Mul(s, _s2)), + // c.baseApi.Zero(), + // ) + + // then compute the hinted scalar mul R = [s]Q + R, err := c.baseApi.NewHint(scalarMulG1Hint, 2, &Q.X, &Q.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) + one := c.baseApi.One() + Q = c.Select(selector, &AffinePoint[B]{X: *one, Y: *one}, Q) + } + var st S + nbits := st.Modulus().BitLen()>>1 + 2 + s1bits := c.baseApi.ToBits(s1) + s2bits := c.baseApi.ToBits(s2) + + // store Q, -Q, 3Q, R, -R, 3R in a table + var tableQ, tableR [3]*AffinePoint[B] + tableQ[1] = Q + tableQ[0] = c.Neg(Q) + tableQ[2] = c.triple(tableQ[1]) + tableR[1] = &AffinePoint[B]{ + X: *R[0], + Y: *c.baseApi.Select(sign[0], c.baseApi.Neg(R[1]), R[1]), + } + tableR[0] = c.Neg(tableR[1]) + tableR[2] = c.triple(tableR[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + R + Acc := c.Add(tableQ[1], tableR[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± R. + // We can compute [2]Acc and look up the (precomputed) point P from: + // B1 = Q+R + // B2 = -Q-R + // B3 = Q-R + // B4 = -Q+R + // + // If we extend this by merging two iterations, we need to look up P and P' + // both from {B1, B2, B3, B4} and compute: + // [2]([2]Acc+P)+P' = [4]Acc + T + // where T = [2]P+P'. So at each (merged) iteration, we can compute [4]Acc + // and look up T from the precomputed list of points: + // + // T = [3](Q + R) + // P = B1 and P' = B1 + T1 := c.Add(tableQ[2], tableR[2]) + // T = Q + R + // P = B1 and P' = B2 + T2 := Acc + // T = [3]Q + R + // P = B1 and P' = B3 + T3 := c.Add(tableQ[2], tableR[1]) + // T = Q + [3]R + // P = B1 and P' = B4 + T4 := c.Add(tableQ[1], tableR[2]) + // T = -Q - R + // P = B2 and P' = B1 + T5 := c.Neg(T2) + // T = -[3](Q + R) + // P = B2 and P' = B2 + T6 := c.Neg(T1) + // T = -Q - [3]R + // P = B2 and P' = B3 + T7 := c.Neg(T4) + // T = -[3]Q - R + // P = B2 and P' = B4 + T8 := c.Neg(T3) + // T = [3]Q - R + // P = B3 and P' = B1 + T9 := c.Add(tableQ[2], tableR[0]) + // T = Q - [3]R + // P = B3 and P' = B2 + T11 := c.Neg(tableR[2]) + T10 := c.Add(tableQ[1], T11) + // T = [3](Q - R) + // P = B3 and P' = B3 + T11 = c.Add(tableQ[2], T11) + // T = -R + Q + // P = B3 and P' = B4 + T12 := c.Add(tableR[0], tableQ[1]) + // T = [3]R - Q + // P = B4 and P' = B1 + T13 := c.Neg(T10) + // T = R - [3]Q + // P = B4 and P' = B2 + T14 := c.Neg(T9) + // T = R - Q + // P = B4 and P' = B3 + T15 := c.Neg(T12) + // T = [3](R - Q) + // P = B4 and P' = B4 + T16 := c.Neg(T11) + // note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // when nbits is odd, we need to handle the first iteration separately + if nbits%2 == 0 { + // Acc = [2]Acc ± Q ± R + T := &AffinePoint[B]{ + X: *c.baseApi.Select(c.api.Xor(s1bits[nbits-1], s2bits[nbits-1]), &T12.X, &T5.X), + Y: *c.baseApi.Lookup2(s1bits[nbits-1], s2bits[nbits-1], &T5.Y, &T12.Y, &T15.Y, &T2.Y), + } + // We don't use doubleAndAdd here as it would involve edge cases + // when bits are 00 (T==-Acc) or 11 (T==Acc). + Acc = c.double(Acc) + Acc = c.add(Acc, T) + } else { + // when nbits is even we start the main loop at normally nbits - 1 + nbits++ + } + for i := nbits - 2; i > 2; i -= 2 { + // selectorY takes values in [0,15] + selectorY := c.api.Add( + s1bits[i], + c.api.Mul(s2bits[i], 2), + c.api.Mul(s1bits[i-1], 4), + c.api.Mul(s2bits[i-1], 8), + ) + // selectorX takes values in [0,7] s.t.: + // - when selectorY < 8: selectorX = selectorY + // - when selectorY >= 8: selectorX = 15 - selectorY + selectorX := c.api.Add( + c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(s2bits[i-1], 2))), + c.api.Mul(s2bits[i-1], 15), + ) + // Bi.Y are distincts so we need a 16-to-1 multiplexer, + // but only half of the Bi.X are distinct so we need a 8-to-1. + T := &AffinePoint[B]{ + X: *c.baseApi.Mux(selectorX, + &T6.X, &T10.X, &T14.X, &T2.X, &T7.X, &T11.X, &T15.X, &T3.X, + ), + Y: *c.baseApi.Mux(selectorY, + &T6.Y, &T10.Y, &T14.Y, &T2.Y, &T7.Y, &T11.Y, &T15.Y, &T3.Y, + &T8.Y, &T12.Y, &T16.Y, &T4.Y, &T5.Y, &T9.Y, &T13.Y, &T1.Y, + ), + } + // Acc = [4]Acc + T + Acc = c.double(Acc) + Acc = c.doubleAndAdd(Acc, T) + } + + // i = 2 + // selectorY takes values in [0,15] + selectorY := c.api.Add( + s1bits[2], + c.api.Mul(s2bits[2], 2), + c.api.Mul(s1bits[1], 4), + c.api.Mul(s2bits[1], 8), + ) + // selectorX takes values in [0,7] s.t.: + // - when selectorY < 8: selectorX = selectorY + // - when selectorY >= 8: selectorX = 15 - selectorY + selectorX := c.api.Add( + c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(s2bits[1], 2))), + c.api.Mul(s2bits[1], 15), + ) + // Bi.Y are distincts so we need a 16-to-1 multiplexer, + // but only half of the Bi.X are distinct so we need a 8-to-1. + T := &AffinePoint[B]{ + X: *c.baseApi.Mux(selectorX, + &T6.X, &T10.X, &T14.X, &T2.X, &T7.X, &T11.X, &T15.X, &T3.X, + ), + Y: *c.baseApi.Mux(selectorY, + &T6.Y, &T10.Y, &T14.Y, &T2.Y, &T7.Y, &T11.Y, &T15.Y, &T3.Y, + &T8.Y, &T12.Y, &T16.Y, &T4.Y, &T5.Y, &T9.Y, &T13.Y, &T1.Y, + ), + } + // Acc = [4]Acc + T + Acc = c.double(Acc) + Acc = c.double(Acc) + Acc = c.AddUnified(Acc, T) + + // i = 0 + // subtract the Q, R if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. + // This means when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + tableQ[0] = c.AddUnified(tableQ[0], Acc) + Acc = c.Select(s1bits[0], Acc, tableQ[0]) + tableR[0] = c.AddUnified(tableR[0], Acc) + Acc = c.Select(s2bits[0], Acc, tableR[0]) + + zero := c.baseApi.Zero() + if cfg.CompleteArithmetic { + Acc = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, Acc) + c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) + } else { + c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) + } + + return &AffinePoint[B]{ + X: *R[0], + Y: *R[1], + } +} diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 2605407348..f9a675e196 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -3,6 +3,7 @@ package sw_emulated import ( "crypto/elliptic" "crypto/rand" + "fmt" "math/big" "testing" @@ -18,6 +19,8 @@ import ( fp_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" fr_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -1912,3 +1915,85 @@ func TestMux(t *testing.T) { err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) assert.NoError(err) } + +// fake GLV +type ScalarMulFakeGLVTest[T, S emulated.FieldParams] struct { + Q, R AffinePoint[T] + S emulated.Element[T] +} + +func (c *ScalarMulFakeGLVTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulFakeGLV(&c.Q, &c.S) + cr.AssertIsEqual(res, &c.R) + return nil +} + +func TestScalaFakeGLVMul(t *testing.T) { + assert := test.NewAssert(t) + _, _, Q, _ := bn254.Generators() + var r fr_bn.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var R bn254.G1Affine + R.ScalarMultiplication(&Q, s) + + circuit := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{} + witness := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fp](s), + Q: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](Q.X), + Y: emulated.ValueOf[emulated.BN254Fp](Q.Y), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](R.X), + Y: emulated.ValueOf[emulated.BN254Fp](R.Y), + }, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + +type ScalarMulJoyeTest[T, S emulated.FieldParams] struct { + P, Q AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulGeneric(&c.P, &c.S) + cr.AssertIsEqual(res, &c.Q) + return nil +} + +// bench +func BenchmarkBN254GLV(b *testing.B) { + c := ScalarMulTest[emulated.BN254Fp, emulated.BN254Fr]{} + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BN254 G1 GLV: ", p.NbConstraints()) +} + +func BenchmarkBN254FakeGLV(b *testing.B) { + c := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{} + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BN254 G1 fake GLV: ", p.NbConstraints()) +} + +func BenchmarkBN254Joye(b *testing.B) { + c := ScalarMulJoyeTest[emulated.BN254Fp, emulated.BN254Fr]{} + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BN254 G1 Joye: ", p.NbConstraints()) +} From 6c3044a7f353dcfbe196e68f7f8ee7359546e5b5 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 10:06:55 -0400 Subject: [PATCH 09/60] feat: fake GLV for P-256 --- std/algebra/emulated/sw_emulated/hints.go | 24 +++------ .../emulated/sw_emulated/point_test.go | 51 ++++++++----------- 2 files changed, 27 insertions(+), 48 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 87536e4281..71732d250c 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -1,11 +1,11 @@ package sw_emulated import ( + "crypto/elliptic" "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" ) @@ -87,13 +87,8 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { } // compute the resulting point [s]Q - var R bn254.G1Affine - R.X.SetBigInt(inputs[0]) - R.Y.SetBigInt(inputs[1]) - R.ScalarMultiplication(&R, inputs[2]) - - R.X.BigInt(outputs[0]) - R.Y.BigInt(outputs[1]) + p256 := elliptic.P256() + outputs[0], outputs[1] = p256.ScalarMult(inputs[0], inputs[1], inputs[2].Bytes()) return nil }) @@ -107,10 +102,8 @@ func halfGCDSigns(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { if len(outputs) != 1 { return fmt.Errorf("expecting one output") } - var modulus big.Int - modulus.SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(&modulus, inputs[0], glvBasis) + ecc.PrecomputeLattice(elliptic.P256().Params().N, inputs[0], glvBasis) outputs[0].SetUint64(0) if glvBasis.V1[1].Sign() == -1 { outputs[0].SetUint64(1) @@ -128,23 +121,20 @@ func halfGCD(mod *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 2 { return fmt.Errorf("expecting two outputs") } - var modulus big.Int - modulus.SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(&modulus, inputs[0], glvBasis) + ecc.PrecomputeLattice(elliptic.P256().Params().N, inputs[0], glvBasis) outputs[0].Set(&glvBasis.V1[0]) outputs[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 - if outputs[0].Sign() == -1 { - outputs[0].Neg(outputs[0]) - } if outputs[1].Sign() == -1 { outputs[1].Neg(outputs[1]) } + return nil }) } diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index f9a675e196..e1dabdc3c3 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1934,27 +1934,24 @@ func (c *ScalarMulFakeGLVTest[T, S]) Define(api frontend.API) error { func TestScalaFakeGLVMul(t *testing.T) { assert := test.NewAssert(t) - _, _, Q, _ := bn254.Generators() - var r fr_bn.Element - _, _ = r.SetRandom() - s := new(big.Int) - r.BigInt(s) - var R bn254.G1Affine - R.ScalarMultiplication(&Q, s) + p256 := elliptic.P256() + s, err := rand.Int(rand.Reader, p256.Params().N) + assert.NoError(err) + px, py := p256.ScalarBaseMult(s.Bytes()) - circuit := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{} - witness := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{ - S: emulated.ValueOf[emulated.BN254Fp](s), - Q: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](Q.X), - Y: emulated.ValueOf[emulated.BN254Fp](Q.Y), + circuit := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{} + witness := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{ + S: emulated.ValueOf[emulated.P256Fp](s), + Q: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](p256.Params().Gx), + Y: emulated.ValueOf[emulated.P256Fp](p256.Params().Gy), }, - R: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](R.X), - Y: emulated.ValueOf[emulated.BN254Fp](R.Y), + R: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](px), + Y: emulated.ValueOf[emulated.P256Fp](py), }, } - err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + err = test.IsSolved(&circuit, &witness, testCurve.ScalarField()) assert.NoError(err) } @@ -1974,26 +1971,18 @@ func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { } // bench -func BenchmarkBN254GLV(b *testing.B) { - c := ScalarMulTest[emulated.BN254Fp, emulated.BN254Fr]{} - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BN254 G1 GLV: ", p.NbConstraints()) -} - -func BenchmarkBN254FakeGLV(b *testing.B) { - c := ScalarMulFakeGLVTest[emulated.BN254Fp, emulated.BN254Fr]{} +func BenchmarkP256ScalarMul(b *testing.B) { + c := ScalarMulTest[emulated.P256Fp, emulated.P256Fr]{} p := profile.Start() _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) p.Stop() - fmt.Println("BN254 G1 fake GLV: ", p.NbConstraints()) + fmt.Println("P256 scalar mul: ", p.NbConstraints()) } -func BenchmarkBN254Joye(b *testing.B) { - c := ScalarMulJoyeTest[emulated.BN254Fp, emulated.BN254Fr]{} +func BenchmarkP256FakeGLV(b *testing.B) { + c := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{} p := profile.Start() _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) p.Stop() - fmt.Println("BN254 G1 Joye: ", p.NbConstraints()) + fmt.Println("P256 fake GLV: ", p.NbConstraints()) } From 114ff1daa49ab5e4207057299b8b2cfa95a615e5 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 10:58:32 -0400 Subject: [PATCH 10/60] fix(fake-glv): scalar with scalarApi --- std/algebra/emulated/sw_emulated/hints.go | 27 +++++++++++++++---- std/algebra/emulated/sw_emulated/point.go | 27 ++++++++++--------- .../emulated/sw_emulated/point_test.go | 4 +-- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 71732d250c..69cb3d612f 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -7,7 +7,9 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/constraint/solver" + limbs "github.com/consensys/gnark/std/internal/limbcomposition" "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" ) func init() { @@ -78,17 +80,32 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) } func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { - if len(inputs) != 3 { - return fmt.Errorf("expecting three inputs") - } + return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 2 { return fmt.Errorf("expecting two outputs") } + var fp emparams.P256Fp + var fr emparams.P256Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } // compute the resulting point [s]Q p256 := elliptic.P256() - outputs[0], outputs[1] = p256.ScalarMult(inputs[0], inputs[1], inputs[2].Bytes()) + outputs[0], outputs[1] = p256.ScalarMult(Px, Py, S.Bytes()) return nil }) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 16bcf21093..425c12e5d7 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1257,32 +1257,35 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ } // Fake GLV -func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[B], opts ...algopts.AlgebraOption) *AffinePoint[B] { +func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { cfg, err := algopts.NewConfig(opts...) if err != nil { panic(err) } // first find the sub-salars - sd, err := c.baseApi.NewHint(halfGCD, 2, s) + sd, err := c.scalarApi.NewHint(halfGCD, 2, s) if err != nil { panic(fmt.Sprintf("halfGCD hint: %v", err)) } s1, s2 := sd[0], sd[1] - sign, err := c.baseApi.NewHintWithNativeOutput(halfGCDSigns, 1, s) + sign, err := c.scalarApi.NewHintWithNativeOutput(halfGCDSigns, 1, s) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } - // _s2 := c.baseApi.Select(sign[0], c.baseApi.Neg(s2), s2) + _s2 := c.scalarApi.Select(sign[0], c.scalarApi.Neg(s2), s2) // check that s1 + s*_s2 == 0 - // This is only true in scalarApi (mod r) not baseApi (mod p)! - // c.baseApi.AssertIsEqual( - // c.baseApi.Add(s1, c.baseApi.Mul(s, _s2)), - // c.baseApi.Zero(), - // ) + c.scalarApi.AssertIsEqual( + c.scalarApi.Add(s1, c.scalarApi.Mul(s, _s2)), + c.scalarApi.Zero(), + ) // then compute the hinted scalar mul R = [s]Q - R, err := c.baseApi.NewHint(scalarMulG1Hint, 2, &Q.X, &Q.Y, s) + var inps []frontend.Variable + inps = append(inps, Q.X.Limbs...) + inps = append(inps, Q.Y.Limbs...) + inps = append(inps, s.Limbs...) + R, err := c.baseApi.NewHintWithNativeInput(scalarMulG1Hint, 2, inps...) if err != nil { panic(fmt.Sprintf("scalar mul hint: %v", err)) } @@ -1296,8 +1299,8 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[B] } var st S nbits := st.Modulus().BitLen()>>1 + 2 - s1bits := c.baseApi.ToBits(s1) - s2bits := c.baseApi.ToBits(s2) + s1bits := c.scalarApi.ToBits(s1) + s2bits := c.scalarApi.ToBits(s2) // store Q, -Q, 3Q, R, -R, 3R in a table var tableQ, tableR [3]*AffinePoint[B] diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index e1dabdc3c3..f3d5c97565 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1919,7 +1919,7 @@ func TestMux(t *testing.T) { // fake GLV type ScalarMulFakeGLVTest[T, S emulated.FieldParams] struct { Q, R AffinePoint[T] - S emulated.Element[T] + S emulated.Element[S] } func (c *ScalarMulFakeGLVTest[T, S]) Define(api frontend.API) error { @@ -1941,7 +1941,7 @@ func TestScalaFakeGLVMul(t *testing.T) { circuit := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{} witness := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{ - S: emulated.ValueOf[emulated.P256Fp](s), + S: emulated.ValueOf[emulated.P256Fr](s), Q: AffinePoint[emulated.P256Fp]{ X: emulated.ValueOf[emulated.P256Fp](p256.Params().Gx), Y: emulated.ValueOf[emulated.P256Fp](p256.Params().Gy), From e3310560701f94f9f82f3fab27efb27b473c5e70 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 12:01:43 -0400 Subject: [PATCH 11/60] perf: optimize fake glv incomplete add handling --- std/algebra/emulated/sw_emulated/point.go | 56 ++++++++++++++++------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 425c12e5d7..d70f4d5d29 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1263,24 +1263,27 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] panic(err) } - // first find the sub-salars + // first find the sub-salars s1, s2 s.t. s1 + s2*s = 0 mod r and s1, s2 < sqrt(r) sd, err := c.scalarApi.NewHint(halfGCD, 2, s) if err != nil { panic(fmt.Sprintf("halfGCD hint: %v", err)) } s1, s2 := sd[0], sd[1] + // s2 can be negative. If so, we return in halfGCD hint -s2 + // and here compute _s2 = -s2 mod r sign, err := c.scalarApi.NewHintWithNativeOutput(halfGCDSigns, 1, s) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } _s2 := c.scalarApi.Select(sign[0], c.scalarApi.Neg(s2), s2) - // check that s1 + s*_s2 == 0 + // we check that s1 + s*_s2 == 0 mod r c.scalarApi.AssertIsEqual( c.scalarApi.Add(s1, c.scalarApi.Mul(s, _s2)), c.scalarApi.Zero(), ) - // then compute the hinted scalar mul R = [s]Q + // Q coordinates are in Fp and the scalar s in Fr + // we decompose Q.X, Q.Y, s into limbs and recompose them in the hint. var inps []frontend.Variable inps = append(inps, Q.X.Limbs...) inps = append(inps, Q.Y.Limbs...) @@ -1291,18 +1294,28 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } var selector frontend.Variable + one := c.baseApi.One() + dummy := &AffinePoint[B]{X: *one, Y: *one} + addFn := c.Add if cfg.CompleteArithmetic { + addFn = c.AddUnified // if Q=(0,0) we assign a dummy (1,1) to Q and continue selector = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) - one := c.baseApi.One() - Q = c.Select(selector, &AffinePoint[B]{X: *one, Y: *one}, Q) + Q = c.Select(selector, dummy, Q) } + var st S nbits := st.Modulus().BitLen()>>1 + 2 s1bits := c.scalarApi.ToBits(s1) s2bits := c.scalarApi.ToBits(s2) - // store Q, -Q, 3Q, R, -R, 3R in a table + // precomputations: + // tableQ[0] = -Q + // tableQ[1] = Q + // tableQ[2] = [3]Q + // tableR[0] = -R or R if s2 is negative + // tableR[1] = R or -R if s2 is negative + // tableR[2] = [3]R or [-3]R if s2 is negative var tableQ, tableR [3]*AffinePoint[B] tableQ[1] = Q tableQ[0] = c.Neg(Q) @@ -1314,7 +1327,9 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] tableR[0] = c.Neg(tableR[1]) tableR[2] = c.triple(tableR[1]) - // we suppose that the first bits of the sub-scalars are 1 and set: + // we should start the accumulator by the infinity point, but since affine + // formulae are incomplete we suppose that the first bits of the + // sub-scalars s1 and s2 are 1, and set: // Acc = Q + R Acc := c.Add(tableQ[1], tableR[1]) @@ -1381,7 +1396,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] // T = [3](R - Q) // P = B4 and P' = B4 T16 := c.Neg(T11) - // note that half the points are negatives of the other half, + // note that half of these points are negatives of the other half, // hence have the same X coordinates. // when nbits is odd, we need to handle the first iteration separately @@ -1431,6 +1446,8 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } // i = 2 + // we isolate the last iteration to avoid falling into incomplete additions + // // selectorY takes values in [0,15] selectorY := c.api.Add( s1bits[2], @@ -1456,26 +1473,33 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] &T8.Y, &T12.Y, &T16.Y, &T4.Y, &T5.Y, &T9.Y, &T13.Y, &T1.Y, ), } - // Acc = [4]Acc + T + // to avoid incomplete additions we add [3]R to the precomputed T before computing [4]Acc+T + // Acc = [4]Acc + T + [3]R + T = c.add(T, tableR[2]) Acc = c.double(Acc) - Acc = c.double(Acc) - Acc = c.AddUnified(Acc, T) + Acc = c.doubleAndAdd(Acc, T) // i = 0 - // subtract the Q, R if the first bits are 0. + // subtract Q and R if the first bits are 0. // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. // This means when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - tableQ[0] = c.AddUnified(tableQ[0], Acc) + tableQ[0] = addFn(tableQ[0], Acc) Acc = c.Select(s1bits[0], Acc, tableQ[0]) - tableR[0] = c.AddUnified(tableR[0], Acc) + tableR[0] = addFn(tableR[0], Acc) Acc = c.Select(s2bits[0], Acc, tableR[0]) - zero := c.baseApi.Zero() if cfg.CompleteArithmetic { + zero := c.baseApi.Zero() Acc = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, Acc) c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) } else { - c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) + // we added [3]R at the last iteration so the result should be + // Acc = [s1]Q + [s2]R + [3]R + // = [s1]Q + [s2*s]Q + [3]R + // = [s1+s2*s]Q + [3]R + // = [0]Q + [3]R + // = [3]R + c.AssertIsEqual(Acc, tableR[2]) } return &AffinePoint[B]{ From 30c6830579d31ad11fd6b8797e04b26e2d58e2d0 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 12:15:24 -0400 Subject: [PATCH 12/60] refactor: halfGCD hint agnostic wrt curve --- std/algebra/emulated/sw_emulated/hints.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 69cb3d612f..701c78ef23 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -120,7 +120,7 @@ func halfGCDSigns(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { return fmt.Errorf("expecting one output") } glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(elliptic.P256().Params().N, inputs[0], glvBasis) + ecc.PrecomputeLattice(field, inputs[0], glvBasis) outputs[0].SetUint64(0) if glvBasis.V1[1].Sign() == -1 { outputs[0].SetUint64(1) @@ -139,7 +139,7 @@ func halfGCD(mod *big.Int, inputs, outputs []*big.Int) error { return fmt.Errorf("expecting two outputs") } glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(elliptic.P256().Params().N, inputs[0], glvBasis) + ecc.PrecomputeLattice(field, inputs[0], glvBasis) outputs[0].Set(&glvBasis.V1[0]) outputs[1].Set(&glvBasis.V1[1]) From 0603663983deb15ebbf2d3cabe83123ec476315a Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 13:17:09 -0400 Subject: [PATCH 13/60] refactor: fakeGLV typed for P256 and P384 --- std/algebra/emulated/sw_emulated/hints.go | 69 +++++++++++++------ std/algebra/emulated/sw_emulated/point.go | 12 +++- .../emulated/sw_emulated/point_test.go | 40 ++++++----- 3 files changed, 82 insertions(+), 39 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 701c78ef23..fa5dda2e33 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -79,33 +79,60 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) }) } +// TODO @yelhousni: generalize for any supported curve as it currently works +// only for P-256 and P-384. func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 2 { return fmt.Errorf("expecting two outputs") } - var fp emparams.P256Fp - var fr emparams.P256Fr - PXLimbs := inputs[:fp.NbLimbs()] - PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] - SLimbs := inputs[2*fp.NbLimbs():] - Px, Py, S := new(big.Int), new(big.Int), new(big.Int) - if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { - return err - - } - if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { - return err - + if field.Cmp(elliptic.P256().Params().P) == 0 { + var fp emparams.P256Fp + var fr emparams.P256Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + curve := elliptic.P256() + // compute the resulting point [s]Q + outputs[0], outputs[1] = curve.ScalarMult(Px, Py, S.Bytes()) + } else if field.Cmp(elliptic.P384().Params().P) == 0 { + var fp emparams.P384Fp + var fr emparams.P384Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + curve := elliptic.P384() + // compute the resulting point [s]Q + outputs[0], outputs[1] = curve.ScalarMult(Px, Py, S.Bytes()) + } else { + return fmt.Errorf("unsupported curve") } - if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { - return err - - } - - // compute the resulting point [s]Q - p256 := elliptic.P256() - outputs[0], outputs[1] = p256.ScalarMult(Px, Py, S.Bytes()) return nil }) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index d70f4d5d29..57610c6dd1 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1256,7 +1256,17 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ } } -// Fake GLV +// scalarMulFakeGLV computes [s]Q and returns it. It doesn't modify Q nor s. +// It implements the "fake GLV" explained in: https://hackmd.io/@yelhousni/Hy-aWld50. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// TODO @yelhousni: generalize for any supported curve as it currently works +// only for P-256 and P-384 because of the scalarMulG1Hint. +// +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { cfg, err := algopts.NewConfig(opts...) if err != nil { diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index f3d5c97565..44dd8c767d 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1955,6 +1955,29 @@ func TestScalaFakeGLVMul(t *testing.T) { assert.NoError(err) } +func TestScalaFakeGLVMul2(t *testing.T) { + assert := test.NewAssert(t) + p384 := elliptic.P384() + s, err := rand.Int(rand.Reader, p384.Params().N) + assert.NoError(err) + px, py := p384.ScalarBaseMult(s.Bytes()) + + circuit := ScalarMulFakeGLVTest[emulated.P384Fp, emulated.P384Fr]{} + witness := ScalarMulFakeGLVTest[emulated.P384Fp, emulated.P384Fr]{ + S: emulated.ValueOf[emulated.P384Fr](s), + Q: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](p384.Params().Gx), + Y: emulated.ValueOf[emulated.P384Fp](p384.Params().Gy), + }, + R: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](px), + Y: emulated.ValueOf[emulated.P384Fp](py), + }, + } + err = test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + type ScalarMulJoyeTest[T, S emulated.FieldParams] struct { P, Q AffinePoint[T] S emulated.Element[S] @@ -1969,20 +1992,3 @@ func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { cr.AssertIsEqual(res, &c.Q) return nil } - -// bench -func BenchmarkP256ScalarMul(b *testing.B) { - c := ScalarMulTest[emulated.P256Fp, emulated.P256Fr]{} - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("P256 scalar mul: ", p.NbConstraints()) -} - -func BenchmarkP256FakeGLV(b *testing.B) { - c := ScalarMulFakeGLVTest[emulated.P256Fp, emulated.P256Fr]{} - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("P256 fake GLV: ", p.NbConstraints()) -} From 3ab8cddce8c95c0bf48d1417aee84cb81fce700c Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 14:56:58 -0400 Subject: [PATCH 14/60] refactor: remove fake GLV for native bls12/bls24 --- std/algebra/native/sw_bls12377/g1.go | 137 ------------------- std/algebra/native/sw_bls12377/g1_test.go | 70 ---------- std/algebra/native/sw_bls12377/g2.go | 157 ---------------------- std/algebra/native/sw_bls12377/g2_test.go | 71 ---------- std/algebra/native/sw_bls12377/hints.go | 64 --------- std/algebra/native/sw_bls24315/g1.go | 124 ----------------- std/algebra/native/sw_bls24315/g1_test.go | 70 ---------- std/algebra/native/sw_bls24315/hints.go | 82 ----------- 8 files changed, 775 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index cf222ed905..4961063ec3 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -17,7 +17,6 @@ limitations under the License. package sw_bls12377 import ( - "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -410,142 +409,6 @@ func (P *G1Affine) constScalarMul(api frontend.API, Q G1Affine, s *big.Int, opts return P } -// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. -// It implements the fake GLV explained in: https://hackmd.io/@yelhousni/Hy-aWld50. -// -// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. -// (0,0) is not on the curve but we conventionally take it as the -// neutral/infinity point as per the [EVM]. -// -// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (P *G1Affine) varScalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(err) - } - - // The context we are working is based on the `outer` curve. However, the - // points and the operations on the points are performed on the `inner` - // curve of the outer curve. We require some parameters from the inner - // curve. - cc := getInnerCurveConfig(api.Compiler().Field()) - - // first find the sub-salars - sd, err := api.Compiler().NewHint(halfGCD, 2, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s0, s1 := sd[0], sd[1] - - // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulG1Hint, 2, Q.X, Q.Y, s) - if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) - } - - var selector frontend.Variable - if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and continue - selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) - Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) - } - - // For BLS12 λ bitsize is 127 equal to half of r bitsize - nbits := cc.lambda.BitLen() - s0bits := api.ToBinary(s0, nbits) - s1bits := api.ToBinary(s1, nbits) - - // store Q, -Q, R, -R in a table - var tableQ, tableR [2]G1Affine - tableQ[1] = Q - tableQ[0].Neg(api, Q) - tableR[1] = G1Affine{X: R[0], Y: R[1]} - tableR[0].Neg(api, tableR[1]) - - // we suppose that the first bits of the sub-scalars are 1 and set: - // Acc = Q + R - var B G1Affine - Acc := tableQ[1] - if cfg.CompleteArithmetic { - // R=(0,0) when Q=(0,0) or s=0 - // so we need unified addition. - Acc.AddUnified(api, tableR[1]) - } else { - Acc.AddAssign(api, tableR[1]) - } - - // At each iteration we need to compute: - // [2]Acc ± Q ± R. - // We can compute [2]Acc and look up the (precomputed) point B from: - // B1 = +Q + R - B1 := Acc - // B2 = -Q - R - B2 := G1Affine{} - B2.Neg(api, B1) - // B3 = +Q - R - B3 := tableQ[1] - B3.AddAssign(api, tableR[0]) - // B4 = -Q + R - B4 := G1Affine{} - B4.Neg(api, B3) - // - // Note that half the points are negatives of the other half, - // hence have the same X coordinates. - - // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 - // to it to avoid incomplete additions in the loop by forcing Acc to be - // different than the stored B. Normally, the point H should be "killed - // out" by the first doubling in the loop and the result will remain - // unchanged. However, we are using affine coordinates that do not encode - // the infinity point. Given the affine formulae, doubling (0,1) results in - // (0,-1). Since the loop size N=nbits-1 is even we need to subtract - // [2^N]H = (0,1) from the result at the end. - // - // Acc = Q + R + H - Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) - - for i := nbits - 1; i > 0; i-- { - B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) - B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) - // Acc = [2]Acc + B - Acc.DoubleAndAdd(api, &Acc, &B) - } - - // i = 0 - // subtract the Q, R if the first bits are 0. - // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means - // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - if cfg.CompleteArithmetic { - tableQ[0].AddUnified(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddUnified(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - } else { - tableQ[0].AddAssign(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddAssign(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - } - - if cfg.CompleteArithmetic { - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AddUnified(api, G1Affine{X: 0, Y: -1}) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 0}) - } else { - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) - } - - P.X = R[0] - P.Y = R[1] - - return P -} - // Assign a value to self (witness assignment) func (p *G1Affine) Assign(p1 *bls12377.G1Affine) { p.X = (fr.Element)(p1.X) diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 4b566b9406..e628cf309a 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -457,76 +457,6 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } -type g1varScalarMulFakeGLV struct { - A G1Affine - C G1Affine `gnark:",public"` - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { - expected := G1Affine{} - expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) - expected.AssertIsEqual(api, circuit.C) - return nil -} - -func TestVarScalarMulG1FakeGLV(t *testing.T) { - // sample random point - _a := randomPointG1() - var a, c bls12377.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLV - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - // compute the result - var br big.Int - _a.ScalarMultiplication(&_a, r.BigInt(&br)) - c.FromJacobian(&_a) - witness.C.Assign(&c) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) -} - -type g1varScalarMulFakeGLVEdgeCases struct { - A G1Affine - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { - expected1 := G1Affine{} - expected2 := G1Affine{} - infinity := G1Affine{X: 0, Y: 0} - expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) - expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) - return nil -} - -func TestVarScalarMulFakeGLVG1EdgeCases(t *testing.T) { - // sample random point - _a := randomPointG1() - var a bls12377.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLVEdgeCases - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) -} - type MultiScalarMulEdgeCasesTest struct { Points []G1Affine Scalars []emulated.Element[ScalarField] diff --git a/std/algebra/native/sw_bls12377/g2.go b/std/algebra/native/sw_bls12377/g2.go index a9ca888c6f..3b8e273dc5 100644 --- a/std/algebra/native/sw_bls12377/g2.go +++ b/std/algebra/native/sw_bls12377/g2.go @@ -17,7 +17,6 @@ limitations under the License. package sw_bls12377 import ( - "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -434,162 +433,6 @@ func (P *g2AffP) constScalarMul(api frontend.API, Q g2AffP, s *big.Int, opts ... return P } -// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. -// It implements the fake GLV explained in: https://hackmd.io/@yelhousni/Hy-aWld50. -// -// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. -// (0,0) is not on the curve but we conventionally take it as the -// neutral/infinity point as per the [EVM]. -// -// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (P *g2AffP) varScalarMulFakeGLV(api frontend.API, Q g2AffP, s frontend.Variable, opts ...algopts.AlgebraOption) *g2AffP { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(err) - } - - zero := fields_bls12377.E2{A0: 0, A1: 0} - one := fields_bls12377.E2{A0: 1, A1: 0} - // The context we are working is based on the `outer` curve. However, the - // points and the operations on the points are performed on the `inner` - // curve of the outer curve. We require some parameters from the inner - // curve. - cc := getInnerCurveConfig(api.Compiler().Field()) - - // first find the sub-salars - sd, err := api.Compiler().NewHint(halfGCD, 2, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s0, s1 := sd[0], sd[1] - - // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulG2Hint, 4, Q.X.A0, Q.X.A1, Q.Y.A0, Q.Y.A1, s) - if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) - } - - var selector frontend.Variable - if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and continue - selector = api.And(Q.X.IsZero(api), Q.Y.IsZero(api)) - Q.Select(api, selector, g2AffP{X: one, Y: one}, Q) - } - - // For BLS12 λ bitsize is 127 equal to half of r bitsize - nbits := cc.lambda.BitLen() - s0bits := api.ToBinary(s0, nbits) - s1bits := api.ToBinary(s1, nbits) - - // store Q, -Q, R, -R in a table - var tableQ, tableR [2]g2AffP - tableQ[1] = Q - tableQ[0].Neg(api, Q) - tableR[1] = g2AffP{ - X: fields_bls12377.E2{A0: R[0], A1: R[1]}, - Y: fields_bls12377.E2{A0: R[2], A1: R[3]}, - } - tableR[0].Neg(api, tableR[1]) - - // we suppose that the first bits of the sub-scalars are 1 and set: - // Acc = Q + R - var B g2AffP - Acc := tableQ[1] - if cfg.CompleteArithmetic { - // R=(0,0) when Q=(0,0) or s=0 - // so we need unified addition. - Acc.AddUnified(api, tableR[1]) - } else { - Acc.AddAssign(api, tableR[1]) - } - - // At each iteration we need to compute: - // [2]Acc ± Q ± R. - // We can compute [2]Acc and look up the (precomputed) point B from: - // B1 = +Q + R - B1 := Acc - // B2 = -Q - R - B2 := g2AffP{} - B2.Neg(api, B1) - // B3 = +Q - R - B3 := tableQ[1] - B3.AddAssign(api, tableR[0]) - // B4 = -Q + R - B4 := g2AffP{} - B4.Neg(api, B3) - // - // Note that half the points are negatives of the other half, - // hence have the same X coordinates. - - // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 - // to it to avoid incomplete additions in the loop by forcing Acc to be - // different than the stored B. Normally, the point H should be "killed - // out" by the first doubling in the loop and the result will remain - // unchanged. However, we are using affine coordinates that do not encode - // the infinity point. Given the affine formulae, doubling (0,1) results in - // (0,-1). Since the loop size N=nbits-1 is even we need to subtract - // [2^N]H = (0,1) from the result at the end. - // - // Acc = Q + Φ(Q) + G - points := getTwistPoints() - Acc.AddAssign(api, - g2AffP{ - X: fields_bls12377.E2{A0: points.G2x[0], A1: points.G2x[1]}, - Y: fields_bls12377.E2{A0: points.G2y[0], A1: points.G2y[1]}, - }, - ) - - for i := nbits - 1; i > 0; i-- { - B.X.Select(api, api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) - B.Y.Lookup2(api, s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) - // Acc = [2]Acc + B - Acc.DoubleAndAdd(api, &Acc, &B) - } - - // i = 0 - // subtract the Q, R if the first bits are 0. - // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means - // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - if cfg.CompleteArithmetic { - tableQ[0].AddUnified(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddUnified(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - Acc.Select(api, selector, g2AffP{X: zero, Y: zero}, Acc) - } else { - tableQ[0].AddAssign(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddAssign(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - } - - B.X = fields_bls12377.E2{ - A0: points.G2m[nbits-1][0], - A1: points.G2m[nbits-1][1], - } - B.Y = fields_bls12377.E2{ - A0: points.G2m[nbits-1][2], - A1: points.G2m[nbits-1][3], - } - if cfg.CompleteArithmetic { - // Acc should be equal infinity + [2^N]G = B since we added G at the beginning - B.Y.Neg(api, B.Y) - Acc.AddUnified(api, B) - Acc.Select(api, selector, g2AffP{X: zero, Y: zero}, Acc) - Acc.AssertIsEqual(api, g2AffP{X: zero, Y: zero}) - } else { - // Acc should be equal infinity + [2^N]G = [2^N]G since we added H at the beginning - Acc.AssertIsEqual(api, B) - } - - P.X = fields_bls12377.E2{A0: R[0], A1: R[1]} - P.Y = fields_bls12377.E2{A0: R[2], A1: R[3]} - - return P -} - // Assign a value to self (witness assignment) func (p *g2AffP) Assign(p1 *bls12377.G2Affine) { p.X.Assign(&p1.X) diff --git a/std/algebra/native/sw_bls12377/g2_test.go b/std/algebra/native/sw_bls12377/g2_test.go index e92defdc24..bd471b5b28 100644 --- a/std/algebra/native/sw_bls12377/g2_test.go +++ b/std/algebra/native/sw_bls12377/g2_test.go @@ -374,77 +374,6 @@ func TestVarScalarMulBaseG2(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } -type g2varScalarMulFakeGLV struct { - A g2AffP - C g2AffP `gnark:",public"` - R frontend.Variable -} - -func (circuit *g2varScalarMulFakeGLV) Define(api frontend.API) error { - expected := g2AffP{} - expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) - expected.AssertIsEqual(api, circuit.C) - return nil -} - -func TestVarScalarMulG2FakeGLV(t *testing.T) { - // sample random point - _a := randomPointG2() - var a, c bls12377.G2Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g2varScalarMulFakeGLV - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - // compute the result - var br big.Int - _a.ScalarMultiplication(&_a, r.BigInt(&br)) - c.FromJacobian(&_a) - witness.C.Assign(&c) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) -} - -type g2varScalarMulFakeGLVEdgeCases struct { - A g2AffP - R frontend.Variable -} - -func (circuit *g2varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { - expected1 := g2AffP{} - expected2 := g2AffP{} - zero := fields_bls12377.E2{A0: 0, A1: 0} - infinity := g2AffP{X: zero, Y: zero} - expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) - expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) - return nil -} - -func TestVarScalarMulFakeGLVG2EdgeCases(t *testing.T) { - // sample random point - _a := randomPointG2() - var a bls12377.G2Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g2varScalarMulFakeGLVEdgeCases - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) -} - func randomPointG2() bls12377.G2Jac { _, p2, _, _ := bls12377.Generators() diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index bf539b8c6b..d59ef955ef 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -5,7 +5,6 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" - bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark/constraint/solver" ) @@ -14,9 +13,6 @@ func GetHints() []solver.Hint { decomposeScalarG1, decomposeScalarG1Simple, decomposeScalarG2, - halfGCD, - scalarMulG1Hint, - scalarMulG2Hint, } } @@ -92,63 +88,3 @@ func decomposeScalarG2(scalarField *big.Int, inputs []*big.Int, outputs []*big.I return nil } - -func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - if len(inputs) != 3 { - return fmt.Errorf("expecting three inputs") - } - if len(outputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - - // compute the resulting point [s]Q - var R bls12377.G1Affine - R.X.SetBigInt(inputs[0]) - R.Y.SetBigInt(inputs[1]) - R.ScalarMultiplication(&R, inputs[2]) - - R.X.BigInt(outputs[0]) - R.Y.BigInt(outputs[1]) - - return nil -} - -func scalarMulG2Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - if len(inputs) != 5 { - return fmt.Errorf("expecting five inputs") - } - if len(outputs) != 4 { - return fmt.Errorf("expecting four outputs") - } - - // compute the resulting point [s]Q - var R bls12377.G2Affine - R.X.A0.SetBigInt(inputs[0]) - R.X.A1.SetBigInt(inputs[1]) - R.Y.A0.SetBigInt(inputs[2]) - R.Y.A1.SetBigInt(inputs[3]) - R.ScalarMultiplication(&R, inputs[4]) - - R.X.A0.BigInt(outputs[0]) - R.X.A1.BigInt(outputs[1]) - R.Y.A0.BigInt(outputs[2]) - R.Y.A1.BigInt(outputs[3]) - - return nil -} - -func halfGCD(nativeMod *big.Int, inputs, outputs []*big.Int) error { - if len(inputs) != 1 { - return fmt.Errorf("expecting one input") - } - if len(outputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - var v0, v1 big.Int - cc := getInnerCurveConfig(nativeMod) - ecc.HalfGCD(cc.fr, inputs[0], &v0, &v1) - outputs[0].Set(&v0) - outputs[1].Set(&v1) - - return nil -} diff --git a/std/algebra/native/sw_bls24315/g1.go b/std/algebra/native/sw_bls24315/g1.go index 7ffd42a6a1..062dbf23ab 100644 --- a/std/algebra/native/sw_bls24315/g1.go +++ b/std/algebra/native/sw_bls24315/g1.go @@ -17,7 +17,6 @@ limitations under the License. package sw_bls24315 import ( - "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -379,129 +378,6 @@ func (p *G1Affine) AssertIsEqual(api frontend.API, other G1Affine) { api.AssertIsEqual(p.Y, other.Y) } -// varScalarMulFakeGLV sets P = [s]Q and returns P. It doesn't modify Q nor s. -// It implements the fake GLV + GLV trick explained in: https://hackmd.io/@yelhousni/Hy-aWld50. -// -// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. -// (0,0) is not on the curve but we conventionally take it as the -// neutral/infinity point as per the [EVM]. -// -// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (P *G1Affine) varScalarMulFakeGLV(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(err) - } - // first find the sub-salars - s0, s1 := callHalfGCD(api, s) - - // then compute the hinted scalar mul R = [s]Q - R, err := api.Compiler().NewHint(scalarMulHint, 2, Q.X, Q.Y, s) - if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) - } - - var selector frontend.Variable - if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and continue - selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) - Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) - } - - nbits := 127 - s0bits := api.ToBinary(s0, nbits) - s1bits := api.ToBinary(s1, nbits) - - // store Q, -Q, R, -R in a table - var tableQ, tableR [2]G1Affine - tableQ[1] = Q - tableQ[0].Neg(api, Q) - tableR[1] = G1Affine{X: R[0], Y: R[1]} - tableR[0].Neg(api, tableR[1]) - - // we suppose that the first bits of the sub-scalars are 1 and set: - // Acc = Q + R - var B G1Affine - Acc := tableQ[1] - if cfg.CompleteArithmetic { - // R=(0,0) when Q=(0,0) or s=0 - // so we need unified addition. - Acc.AddUnified(api, tableR[1]) - } else { - Acc.AddAssign(api, tableR[1]) - } - - // At each iteration we need to compute: - // [2]Acc ± Q ± R. - // We can compute [2]Acc and look up the (precomputed) point B from: - // B1 = +Q + R - B1 := Acc - // B2 = -Q - R - B2 := G1Affine{} - B2.Neg(api, B1) - // B3 = +Q - R - B3 := tableQ[1] - B3.AddAssign(api, tableR[0]) - // B4 = -Q + R - B4 := G1Affine{} - B4.Neg(api, B3) - // - // Note that half the points are negatives of the other half, - // hence have the same X coordinates. - - // However when doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen - // that Acc==B or -B. So we add the point H=(0,1) on BLS24-315 of order 2 - // to it to avoid incomplete additions in the loop by forcing Acc to be - // different than the stored B. Normally, the point H should be "killed - // out" by the first doubling in the loop and the result will remain - // unchanged. However, we are using affine coordinates that do not encode - // the infinity point. Given the affine formulae, doubling (0,1) results in - // (0,-1). Since the loop size N=nbits-1 is even we need to subtract - // [2^N]H = (0,1) from the result at the end. - // - // Acc = Q + R + H - Acc.AddAssign(api, G1Affine{X: 0, Y: 1}) - - for i := nbits - 1; i > 0; i-- { - B.X = api.Select(api.Xor(s0bits[i], s1bits[i]), B3.X, B2.X) - B.Y = api.Lookup2(s0bits[i], s1bits[i], B2.Y, B3.Y, B4.Y, B1.Y) - // Acc = [2]Acc + B - Acc.DoubleAndAdd(api, &Acc, &B) - } - - // i = 0 - // subtract the Q, R if the first bits are 0. - // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. This means - // when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). - if cfg.CompleteArithmetic { - tableQ[0].AddUnified(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddUnified(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - } else { - tableQ[0].AddAssign(api, Acc) - Acc.Select(api, s0bits[0], Acc, tableQ[0]) - tableR[0].AddAssign(api, Acc) - Acc.Select(api, s1bits[0], Acc, tableR[0]) - } - - if cfg.CompleteArithmetic { - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AddUnified(api, G1Affine{X: 0, Y: -1}) - Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 0}) - } else { - // Acc should be equal infinity + [2^N]G = (0,1) since we added H at the beginning - Acc.AssertIsEqual(api, G1Affine{X: 0, Y: 1}) - } - - P.X = R[0] - P.Y = R[1] - - return P -} - // DoubleAndAdd computes 2*p1+p in affine coords func (p *G1Affine) DoubleAndAdd(api frontend.API, p1, p2 *G1Affine) *G1Affine { diff --git a/std/algebra/native/sw_bls24315/g1_test.go b/std/algebra/native/sw_bls24315/g1_test.go index a458c98d48..4387f94c05 100644 --- a/std/algebra/native/sw_bls24315/g1_test.go +++ b/std/algebra/native/sw_bls24315/g1_test.go @@ -457,76 +457,6 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633), test.NoProverChecks()) } -type g1varScalarMulFakeGLV struct { - A G1Affine - C G1Affine `gnark:",public"` - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLV) Define(api frontend.API) error { - expected := G1Affine{} - expected.varScalarMulFakeGLV(api, circuit.A, circuit.R) - expected.AssertIsEqual(api, circuit.C) - return nil -} - -func TestVarScalarMulG1FakeGLV(t *testing.T) { - // sample random point - _a := randomPointG1() - var a, c bls24315.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLV - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - // compute the result - var br big.Int - _a.ScalarMultiplication(&_a, r.BigInt(&br)) - c.FromJacobian(&_a) - witness.C.Assign(&c) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) -} - -type g1varScalarMulFakeGLVEdgeCases struct { - A G1Affine - R frontend.Variable -} - -func (circuit *g1varScalarMulFakeGLVEdgeCases) Define(api frontend.API) error { - expected1 := G1Affine{} - expected2 := G1Affine{} - infinity := G1Affine{X: 0, Y: 0} - expected1.varScalarMulFakeGLV(api, circuit.A, 0, algopts.WithCompleteArithmetic()) - expected2.varScalarMulFakeGLV(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) - return nil -} - -func TestVarScalarMulFakeGLVG1EdgeCases(t *testing.T) { - // sample random point - _a := randomPointG1() - var a bls24315.G1Affine - a.FromJacobian(&_a) - - // create the cs - var circuit, witness g1varScalarMulFakeGLVEdgeCases - var r fr.Element - _, _ = r.SetRandom() - witness.R = r.String() - // assign the inputs - witness.A.Assign(&a) - - assert := test.NewAssert(t) - assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633)) -} - type MultiScalarMulEdgeCasesTest struct { Points []G1Affine Scalars []emulated.Element[ScalarField] diff --git a/std/algebra/native/sw_bls24315/hints.go b/std/algebra/native/sw_bls24315/hints.go index 4b8ba99d5c..d20fac537c 100644 --- a/std/algebra/native/sw_bls24315/hints.go +++ b/std/algebra/native/sw_bls24315/hints.go @@ -5,7 +5,6 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" - bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" @@ -17,8 +16,6 @@ func GetHints() []solver.Hint { decomposeScalar, decomposeScalarSimple, decompose, - halfGCD, - scalarMulHint, } } @@ -116,47 +113,6 @@ func callDecomposeScalar(api frontend.API, s frontend.Variable, simple bool) (s1 return s1, s2 } -func callHalfGCD(api frontend.API, s frontend.Variable) (s1, s2 frontend.Variable) { - var fr emparams.BLS24315Fr - sapi, err := emulated.NewField[emparams.BLS24315Fr](api) - if err != nil { - panic(err) - } - - // compute the decomposition using a hint. We have to use the emulated - // version which takes native input and outputs non-native outputs. - // - // the hints allow to decompose the scalar s into s1 and s2 such that - // s1 + s * s2 == 0 mod r, - // where λ is third root of one in 𝔽_r. - sd, err := sapi.NewHintWithNativeInput(halfGCD, 2, s) - if err != nil { - panic(err) - } - // the scalar as nonnative element. We need to split at 64 bits. - limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) - if err != nil { - panic(err) - } - semu := sapi.NewElement(limbs) - // s * s2 == -s1 mod r - lhs := sapi.MulNoReduce(sd[1], semu) - rhs := sapi.Neg(sd[0]) - - sapi.AssertIsEqual(lhs, rhs) - - s1 = 0 - s2 = 0 - b := big.NewInt(1) - for i := range sd[0].Limbs { - s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) - s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) - b.Lsh(b, 64) - } - - return s1, s2 -} - func decompose(mod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 1 && len(outputs) != 4 { return fmt.Errorf("input/output length mismatch") @@ -169,41 +125,3 @@ func decompose(mod *big.Int, inputs, outputs []*big.Int) error { } return nil } - -func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - if len(inputs) != 3 { - return fmt.Errorf("expecting three inputs") - } - if len(outputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - - // compute the resulting point [s]Q - var R bls24315.G1Affine - R.X.SetBigInt(inputs[0]) - R.Y.SetBigInt(inputs[1]) - R.ScalarMultiplication(&R, inputs[2]) - - R.X.BigInt(outputs[0]) - R.Y.BigInt(outputs[1]) - - return nil -} - -func halfGCD(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { - if len(nninputs) != 1 { - return fmt.Errorf("expecting one input") - } - if len(nnOutputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - var v0, v1 big.Int - cc := getInnerCurveConfig(nativeMod) - ecc.HalfGCD(cc.fr, nninputs[0], &v0, &v1) - nnOutputs[0].Set(&v0) - nnOutputs[1].Set(&v1) - - return nil - }) -} From 675c28a15d5388a8f1d55efdd487d31b43113082 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 16:37:52 -0400 Subject: [PATCH 15/60] fix: edge case with inf point --- std/algebra/emulated/sw_emulated/point.go | 9 ++- .../emulated/sw_emulated/point_test.go | 63 +++++++++++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 57610c6dd1..8182008a9c 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1302,6 +1302,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] if err != nil { panic(fmt.Sprintf("scalar mul hint: %v", err)) } + r0, r1 := R[0], R[1] var selector frontend.Variable one := c.baseApi.One() @@ -1309,9 +1310,11 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] addFn := c.Add if cfg.CompleteArithmetic { addFn = c.AddUnified - // if Q=(0,0) we assign a dummy (1,1) to Q and continue + // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue selector = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) Q = c.Select(selector, dummy, Q) + r0 = c.baseApi.Select(selector, c.baseApi.Zero(), r0) + r1 = c.baseApi.Select(selector, &dummy.Y, r1) } var st S @@ -1331,8 +1334,8 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] tableQ[0] = c.Neg(Q) tableQ[2] = c.triple(tableQ[1]) tableR[1] = &AffinePoint[B]{ - X: *R[0], - Y: *c.baseApi.Select(sign[0], c.baseApi.Neg(R[1]), R[1]), + X: *r0, + Y: *c.baseApi.Select(sign[0], c.baseApi.Neg(r1), r1), } tableR[0] = c.Neg(tableR[1]) tableR[2] = c.triple(tableR[1]) diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 44dd8c767d..622e5a67e0 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -3,7 +3,6 @@ package sw_emulated import ( "crypto/elliptic" "crypto/rand" - "fmt" "math/big" "testing" @@ -19,8 +18,6 @@ import ( fp_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" fr_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/scs" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -834,8 +831,8 @@ func TestScalarMulEdgeCasesEdgeCases(t *testing.T) { witness2 := ScalarMulEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ S: emulated.ValueOf[emulated.BN254Fr](new(big.Int)), P: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](S.X), - Y: emulated.ValueOf[emulated.BN254Fp](S.Y), + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y), }, R: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](infinity.X), @@ -1978,6 +1975,62 @@ func TestScalaFakeGLVMul2(t *testing.T) { assert.NoError(err) } +type ScalarMulFakeGLVEdgeCasesTest[T, S emulated.FieldParams] struct { + P, R AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulFakeGLVEdgeCasesTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulFakeGLV(&c.P, &c.S, algopts.WithCompleteArithmetic()) + cr.AssertIsEqual(res, &c.R) + return nil +} + +func TestScalarMulFakeGLVEdgeCasesEdgeCases(t *testing.T) { + assert := test.NewAssert(t) + p256 := elliptic.P256() + s, err := rand.Int(rand.Reader, p256.Params().N) + assert.NoError(err) + px, py := p256.ScalarBaseMult(s.Bytes()) + _, _ = p256.ScalarMult(px, py, s.Bytes()) + + circuit := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{ + S: emulated.ValueOf[emulated.P256Fr](s), + P: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](0), + Y: emulated.ValueOf[emulated.P256Fp](0), + }, + R: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](0), + Y: emulated.ValueOf[emulated.P256Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // // 0 * P == (0,0) + // witness2 := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{ + // S: emulated.ValueOf[emulated.P256Fr](new(big.Int)), + // P: AffinePoint[emulated.P256Fp]{ + // X: emulated.ValueOf[emulated.P256Fp](px), + // Y: emulated.ValueOf[emulated.P256Fp](py), + // }, + // R: AffinePoint[emulated.P256Fp]{ + // X: emulated.ValueOf[emulated.P256Fp](0), + // Y: emulated.ValueOf[emulated.P256Fp](0), + // }, + // } + // err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + // assert.NoError(err) +} + type ScalarMulJoyeTest[T, S emulated.FieldParams] struct { P, Q AffinePoint[T] S emulated.Element[S] From 38325096484077dbad75cdfe4d014aad0b487177 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 17:02:03 -0400 Subject: [PATCH 16/60] fix: edge case with 0 scalar --- std/algebra/emulated/sw_emulated/point.go | 32 +++++-- .../emulated/sw_emulated/point_test.go | 91 ++++++++++++++----- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 8182008a9c..2b7218c18d 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1273,22 +1273,29 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] panic(err) } + var selector1 frontend.Variable + _s := s + if cfg.CompleteArithmetic { + selector1 = c.scalarApi.IsZero(s) + _s = c.scalarApi.Select(selector1, c.scalarApi.One(), s) + } + // first find the sub-salars s1, s2 s.t. s1 + s2*s = 0 mod r and s1, s2 < sqrt(r) - sd, err := c.scalarApi.NewHint(halfGCD, 2, s) + sd, err := c.scalarApi.NewHint(halfGCD, 2, _s) if err != nil { panic(fmt.Sprintf("halfGCD hint: %v", err)) } s1, s2 := sd[0], sd[1] // s2 can be negative. If so, we return in halfGCD hint -s2 // and here compute _s2 = -s2 mod r - sign, err := c.scalarApi.NewHintWithNativeOutput(halfGCDSigns, 1, s) + sign, err := c.scalarApi.NewHintWithNativeOutput(halfGCDSigns, 1, _s) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } _s2 := c.scalarApi.Select(sign[0], c.scalarApi.Neg(s2), s2) // we check that s1 + s*_s2 == 0 mod r c.scalarApi.AssertIsEqual( - c.scalarApi.Add(s1, c.scalarApi.Mul(s, _s2)), + c.scalarApi.Add(s1, c.scalarApi.Mul(_s, _s2)), c.scalarApi.Zero(), ) // then compute the hinted scalar mul R = [s]Q @@ -1304,17 +1311,17 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } r0, r1 := R[0], R[1] - var selector frontend.Variable + var selector2 frontend.Variable one := c.baseApi.One() dummy := &AffinePoint[B]{X: *one, Y: *one} addFn := c.Add if cfg.CompleteArithmetic { addFn = c.AddUnified // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue - selector = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) - Q = c.Select(selector, dummy, Q) - r0 = c.baseApi.Select(selector, c.baseApi.Zero(), r0) - r1 = c.baseApi.Select(selector, &dummy.Y, r1) + selector2 = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) + Q = c.Select(selector2, dummy, Q) + r0 = c.baseApi.Select(selector2, c.baseApi.Zero(), r0) + r1 = c.baseApi.Select(selector2, &dummy.Y, r1) } var st S @@ -1338,7 +1345,12 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] Y: *c.baseApi.Select(sign[0], c.baseApi.Neg(r1), r1), } tableR[0] = c.Neg(tableR[1]) - tableR[2] = c.triple(tableR[1]) + if cfg.CompleteArithmetic { + tableR[2] = c.AddUnified(tableR[1], tableR[1]) + tableR[2] = c.AddUnified(tableR[2], tableR[1]) + } else { + tableR[2] = c.triple(tableR[1]) + } // we should start the accumulator by the infinity point, but since affine // formulae are incomplete we suppose that the first bits of the @@ -1503,7 +1515,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] if cfg.CompleteArithmetic { zero := c.baseApi.Zero() - Acc = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, Acc) + Acc = c.Select(c.api.Or(selector1, selector2), &AffinePoint[B]{X: *zero, Y: *zero}, Acc) c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) } else { // we added [3]R at the last iteration so the result should be diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 622e5a67e0..45142ac59b 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1913,6 +1913,21 @@ func TestMux(t *testing.T) { assert.NoError(err) } +type ScalarMulJoyeTest[T, S emulated.FieldParams] struct { + P, Q AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulGeneric(&c.P, &c.S) + cr.AssertIsEqual(res, &c.Q) + return nil +} + // fake GLV type ScalarMulFakeGLVTest[T, S emulated.FieldParams] struct { Q, R AffinePoint[T] @@ -2015,33 +2030,59 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases(t *testing.T) { err = test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) assert.NoError(err) - // // 0 * P == (0,0) - // witness2 := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{ - // S: emulated.ValueOf[emulated.P256Fr](new(big.Int)), - // P: AffinePoint[emulated.P256Fp]{ - // X: emulated.ValueOf[emulated.P256Fp](px), - // Y: emulated.ValueOf[emulated.P256Fp](py), - // }, - // R: AffinePoint[emulated.P256Fp]{ - // X: emulated.ValueOf[emulated.P256Fp](0), - // Y: emulated.ValueOf[emulated.P256Fp](0), - // }, - // } - // err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) - // assert.NoError(err) + // 0 * P == (0,0) + witness2 := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{ + S: emulated.ValueOf[emulated.P256Fr](new(big.Int)), + P: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](px), + Y: emulated.ValueOf[emulated.P256Fp](py), + }, + R: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](0), + Y: emulated.ValueOf[emulated.P256Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) } -type ScalarMulJoyeTest[T, S emulated.FieldParams] struct { - P, Q AffinePoint[T] - S emulated.Element[S] -} +func TestScalarMulFakeGLVEdgeCasesEdgeCases2(t *testing.T) { + assert := test.NewAssert(t) + p384 := elliptic.P384() + s, err := rand.Int(rand.Reader, p384.Params().N) + assert.NoError(err) + px, py := p384.ScalarBaseMult(s.Bytes()) + _, _ = p384.ScalarMult(px, py, s.Bytes()) -func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { - cr, err := New[T, S](api, GetCurveParams[T]()) - if err != nil { - return err + circuit := ScalarMulFakeGLVEdgeCasesTest[emulated.P384Fp, emulated.P384Fr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulFakeGLVEdgeCasesTest[emulated.P384Fp, emulated.P384Fr]{ + S: emulated.ValueOf[emulated.P384Fr](s), + P: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](0), + Y: emulated.ValueOf[emulated.P384Fp](0), + }, + R: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](0), + Y: emulated.ValueOf[emulated.P384Fp](0), + }, } - res := cr.scalarMulGeneric(&c.P, &c.S) - cr.AssertIsEqual(res, &c.Q) - return nil + err = test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // 0 * P == (0,0) + witness2 := ScalarMulFakeGLVEdgeCasesTest[emulated.P384Fp, emulated.P384Fr]{ + S: emulated.ValueOf[emulated.P384Fr](new(big.Int)), + P: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](px), + Y: emulated.ValueOf[emulated.P384Fp](py), + }, + R: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](0), + Y: emulated.ValueOf[emulated.P384Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) } From 5fd4a85387676308b6521e9a182d1de24b650bb6 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 17:35:16 -0400 Subject: [PATCH 17/60] perf: use fake GLV instead of Joye07 when no endo --- std/algebra/emulated/sw_emulated/point.go | 16 ++++------------ std/algebra/emulated/sw_emulated/point_test.go | 4 ++-- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 2b7218c18d..4db7699e55 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -513,7 +513,7 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S], opts return c.scalarMulGLV(p, s, opts...) } else { - return c.scalarMulGeneric(p, s, opts...) + return c.scalarMulFakeGLV(p, s, opts...) } } @@ -814,17 +814,9 @@ func (c *Curve[B, S]) jointScalarMul(p1, p2 *AffinePoint[B], s1, s2 *emulated.El // // ⚠️ The scalars s1, s2 must be nonzero and the point p1, p2 different from (0,0), unless [algopts.WithCompleteArithmetic] option is set. func (c *Curve[B, S]) jointScalarMulGeneric(p1, p2 *AffinePoint[B], s1, s2 *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { - cfg, err := algopts.NewConfig(opts...) - if err != nil { - panic(fmt.Sprintf("parse opts: %v", err)) - } - if cfg.CompleteArithmetic { - res1 := c.scalarMulGeneric(p1, s1, opts...) - res2 := c.scalarMulGeneric(p2, s2, opts...) - return c.AddUnified(res1, res2) - } else { - return c.jointScalarMulGenericUnsafe(p1, p2, s1, s2) - } + sm1 := c.scalarMulFakeGLV(p1, s1, opts...) + sm2 := c.scalarMulFakeGLV(p2, s2, opts...) + return c.AddUnified(sm1, sm2) } // jointScalarMulGenericUnsafe computes [s1]p1 + [s2]p2 using Shamir's trick and returns it. It doesn't modify p1, p2 nor s1, s2. diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 45142ac59b..3ea800490d 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1944,7 +1944,7 @@ func (c *ScalarMulFakeGLVTest[T, S]) Define(api frontend.API) error { return nil } -func TestScalaFakeGLVMul(t *testing.T) { +func TestScalarMulFakeGLV(t *testing.T) { assert := test.NewAssert(t) p256 := elliptic.P256() s, err := rand.Int(rand.Reader, p256.Params().N) @@ -1967,7 +1967,7 @@ func TestScalaFakeGLVMul(t *testing.T) { assert.NoError(err) } -func TestScalaFakeGLVMul2(t *testing.T) { +func TestScalarMulFakeGLV2(t *testing.T) { assert := test.NewAssert(t) p384 := elliptic.P384() s, err := rand.Int(rand.Reader, p384.Params().N) From a40cf1bb1b03fa052dc2c6ab057759bbba28043f Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 23 Aug 2024 18:24:20 -0400 Subject: [PATCH 18/60] fix: edge case --- std/algebra/emulated/sw_emulated/point.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 4db7699e55..61186e79cc 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1506,18 +1506,15 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] Acc = c.Select(s2bits[0], Acc, tableR[0]) if cfg.CompleteArithmetic { - zero := c.baseApi.Zero() - Acc = c.Select(c.api.Or(selector1, selector2), &AffinePoint[B]{X: *zero, Y: *zero}, Acc) - c.AssertIsEqual(Acc, &AffinePoint[B]{X: *zero, Y: *zero}) - } else { - // we added [3]R at the last iteration so the result should be - // Acc = [s1]Q + [s2]R + [3]R - // = [s1]Q + [s2*s]Q + [3]R - // = [s1+s2*s]Q + [3]R - // = [0]Q + [3]R - // = [3]R - c.AssertIsEqual(Acc, tableR[2]) - } + Acc = c.Select(c.api.Or(selector1, selector2), tableR[2], Acc) + } + // we added [3]R at the last iteration so the result should be + // Acc = [s1]Q + [s2]R + [3]R + // = [s1]Q + [s2*s]Q + [3]R + // = [s1+s2*s]Q + [3]R + // = [0]Q + [3]R + // = [3]R + c.AssertIsEqual(Acc, tableR[2]) return &AffinePoint[B]{ X: *R[0], From d436f4399122348dba0c0462a604068b5457ef59 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 24 Aug 2024 17:14:46 -0400 Subject: [PATCH 19/60] perf: reduce fake glv loop size --- std/algebra/emulated/sw_emulated/point.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 61186e79cc..dd9ef7697f 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -2,6 +2,7 @@ package sw_emulated import ( "fmt" + "math" "math/big" "slices" @@ -1317,7 +1318,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } var st S - nbits := st.Modulus().BitLen()>>1 + 2 + nbits := int(math.Ceil(float64(st.Modulus().BitLen()) / 2)) s1bits := c.scalarApi.ToBits(s1) s2bits := c.scalarApi.ToBits(s2) From c05016294e7eb66e7674457d02c99ba93bce8c06 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 5 Sep 2024 11:10:35 -0400 Subject: [PATCH 20/60] fix: check that all-zero malicuous hint is not possible --- std/algebra/emulated/sw_emulated/point.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index dd9ef7697f..da3ab19629 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1273,25 +1273,29 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] _s = c.scalarApi.Select(selector1, c.scalarApi.One(), s) } - // first find the sub-salars s1, s2 s.t. s1 + s2*s = 0 mod r and s1, s2 < sqrt(r) + // First we find the sub-salars s1, s2 s.t. s1 + s2*s = 0 mod r and s1, s2 < sqrt(r). sd, err := c.scalarApi.NewHint(halfGCD, 2, _s) if err != nil { panic(fmt.Sprintf("halfGCD hint: %v", err)) } s1, s2 := sd[0], sd[1] - // s2 can be negative. If so, we return in halfGCD hint -s2 + // s2 can be negative. If so, we return in the halfGCD hint -s2 // and here compute _s2 = -s2 mod r sign, err := c.scalarApi.NewHintWithNativeOutput(halfGCDSigns, 1, _s) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } _s2 := c.scalarApi.Select(sign[0], c.scalarApi.Neg(s2), s2) - // we check that s1 + s*_s2 == 0 mod r + // We check that s1 + s*_s2 == 0 mod r c.scalarApi.AssertIsEqual( c.scalarApi.Add(s1, c.scalarApi.Mul(_s, _s2)), c.scalarApi.Zero(), ) - // then compute the hinted scalar mul R = [s]Q + // A malicious hint can provide s1=s2=0 mod r + // So we check that _s2 is non-zero otherwise [0]([s]Q = ∀R) is always true + c.api.AssertIsEqual(c.scalarApi.IsZero(_s2), 0) + + // Then we compute the hinted scalar mul R = [s]Q // Q coordinates are in Fp and the scalar s in Fr // we decompose Q.X, Q.Y, s into limbs and recompose them in the hint. var inps []frontend.Variable @@ -1322,7 +1326,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] s1bits := c.scalarApi.ToBits(s1) s2bits := c.scalarApi.ToBits(s2) - // precomputations: + // Precomputations: // tableQ[0] = -Q // tableQ[1] = Q // tableQ[2] = [3]Q @@ -1345,7 +1349,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] tableR[2] = c.triple(tableR[1]) } - // we should start the accumulator by the infinity point, but since affine + // We should start the accumulator by the infinity point, but since affine // formulae are incomplete we suppose that the first bits of the // sub-scalars s1 and s2 are 1, and set: // Acc = Q + R @@ -1417,7 +1421,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] // note that half of these points are negatives of the other half, // hence have the same X coordinates. - // when nbits is odd, we need to handle the first iteration separately + // When nbits is odd, we need to handle the first iteration separately if nbits%2 == 0 { // Acc = [2]Acc ± Q ± R T := &AffinePoint[B]{ From ff5ad3ab30acc4e852f616016e795623b45e2e01 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 5 Sep 2024 12:46:30 -0400 Subject: [PATCH 21/60] test: add stark curve to fake glv test --- std/algebra/emulated/sw_emulated/hints.go | 32 +++++- std/algebra/emulated/sw_emulated/params.go | 34 +++++-- .../emulated/sw_emulated/params_compute.go | 27 ++++++ std/algebra/emulated/sw_emulated/point.go | 2 +- .../emulated/sw_emulated/point_test.go | 97 +++++++++++++++++++ std/math/emulated/emparams/emparams.go | 28 ++++++ std/math/emulated/params.go | 31 +++--- 7 files changed, 228 insertions(+), 23 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index fa5dda2e33..57ec896351 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -6,6 +6,8 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" + stark_fp "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" "github.com/consensys/gnark/constraint/solver" limbs "github.com/consensys/gnark/std/internal/limbcomposition" "github.com/consensys/gnark/std/math/emulated" @@ -79,8 +81,8 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) }) } -// TODO @yelhousni: generalize for any supported curve as it currently works -// only for P-256 and P-384. +// TODO @yelhousni: generalize for any supported curve. +// as it currently works only for P-256, P-384 and STARK curve. func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 2 { @@ -130,6 +132,32 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { curve := elliptic.P384() // compute the resulting point [s]Q outputs[0], outputs[1] = curve.ScalarMult(Px, Py, S.Bytes()) + } else if field.Cmp(stark_fp.Modulus()) == 0 { + var fp emparams.STARKCurveFp + var fr emparams.STARKCurveFr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + // compute the resulting point [s]Q + var P stark_curve.G1Affine + P.X.SetBigInt(Px) + P.Y.SetBigInt(Py) + P.ScalarMultiplication(&P, S) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) } else { return fmt.Errorf("unsupported curve") } diff --git a/std/algebra/emulated/sw_emulated/params.go b/std/algebra/emulated/sw_emulated/params.go index bf917e04df..97095f86aa 100644 --- a/std/algebra/emulated/sw_emulated/params.go +++ b/std/algebra/emulated/sw_emulated/params.go @@ -8,6 +8,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254" bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761" "github.com/consensys/gnark-crypto/ecc/secp256k1" + stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" "github.com/consensys/gnark/std/math/emulated" ) @@ -133,6 +134,23 @@ func GetBW6761Params() CurveParams { } } +// GetStarkCurveParams returns the curve parameters for the STARK curve. +// When initialising new curve, use the base field [emulated.STARKCurveFp] and scalar +// field [emulated.STARKCurveFr]. +func GetStarkCurveParams() CurveParams { + _, g1aff := stark_curve.Generators() + b, _ := new(big.Int).SetString("3141592653589793238462643383279502884197169399375105820974944592307816406665", 10) + return CurveParams{ + A: big.NewInt(1), + B: b, + Gx: g1aff.X.BigInt(new(big.Int)), + Gy: g1aff.Y.BigInt(new(big.Int)), + Gm: computeStarkCurveTable(), + Eigenvalue: nil, + ThirdRootOne: nil, + } +} + // GetCurveParams returns suitable curve parameters given the parametric type // Base as base field. It caches the parameters and modifying the values in the // parameters struct leads to undefined behaviour. @@ -151,18 +169,21 @@ func GetCurveParams[Base emulated.FieldParams]() CurveParams { return p384Params case emulated.BW6761Fp{}.Modulus().String(): return bw6761Params + case emulated.STARKCurveFp{}.Modulus().String(): + return starkCurveParams default: panic("no stored parameters") } } var ( - secp256k1Params CurveParams - bn254Params CurveParams - bls12381Params CurveParams - p256Params CurveParams - p384Params CurveParams - bw6761Params CurveParams + secp256k1Params CurveParams + bn254Params CurveParams + bls12381Params CurveParams + p256Params CurveParams + p384Params CurveParams + bw6761Params CurveParams + starkCurveParams CurveParams ) func init() { @@ -172,4 +193,5 @@ func init() { p256Params = GetP256Params() p384Params = GetP384Params() bw6761Params = GetBW6761Params() + starkCurveParams = GetStarkCurveParams() } diff --git a/std/algebra/emulated/sw_emulated/params_compute.go b/std/algebra/emulated/sw_emulated/params_compute.go index 88a514c7bc..af969423f6 100644 --- a/std/algebra/emulated/sw_emulated/params_compute.go +++ b/std/algebra/emulated/sw_emulated/params_compute.go @@ -8,6 +8,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254" bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761" "github.com/consensys/gnark-crypto/ecc/secp256k1" + stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" ) func computeSecp256k1Table() [][2]*big.Int { @@ -157,3 +158,29 @@ func computeBW6761Table() [][2]*big.Int { } return table } + +func computeStarkCurveTable() [][2]*big.Int { + Gjac, _ := stark_curve.Generators() + table := make([][2]*big.Int, 256) + tmp := new(stark_curve.G1Jac).Set(&Gjac) + aff := new(stark_curve.G1Affine) + jac := new(stark_curve.G1Jac) + for i := 1; i < 256; i++ { + tmp = tmp.Double(tmp) + switch i { + case 1, 2: + jac.Set(tmp).AddAssign(&Gjac) + aff.FromJacobian(jac) + table[i-1] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + case 3: + jac.Set(tmp).SubAssign(&Gjac) + aff.FromJacobian(jac) + table[i-1] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + fallthrough + default: + aff.FromJacobian(tmp) + table[i] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + } + } + return table[:] +} diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index da3ab19629..0f87156279 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1257,7 +1257,7 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ // neutral/infinity point as per the [EVM]. // // TODO @yelhousni: generalize for any supported curve as it currently works -// only for P-256 and P-384 because of the scalarMulG1Hint. +// only for P-256, P-384 and STARK curve because of the scalarMulG1Hint. // // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 3ea800490d..051224bf1b 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -17,6 +17,8 @@ import ( "github.com/consensys/gnark-crypto/ecc/secp256k1" fp_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" fr_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" + stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" + fr_stark "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" @@ -784,6 +786,32 @@ func TestScalarMul6(t *testing.T) { assert.NoError(err) } +func TestScalarMul7(t *testing.T) { + assert := test.NewAssert(t) + var r fr_stark.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var res stark_curve.G1Affine + _, gen := stark_curve.Generators() + res.ScalarMultiplication(&gen, s) + + circuit := ScalarMulTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{} + witness := ScalarMulTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{ + S: emulated.ValueOf[emulated.STARKCurveFr](s), + P: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](gen.X), + Y: emulated.ValueOf[emulated.STARKCurveFp](gen.Y), + }, + Q: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](res.X), + Y: emulated.ValueOf[emulated.STARKCurveFp](res.Y), + }, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + type ScalarMulEdgeCasesTest[T, S emulated.FieldParams] struct { P, R AffinePoint[T] S emulated.Element[S] @@ -1990,6 +2018,32 @@ func TestScalarMulFakeGLV2(t *testing.T) { assert.NoError(err) } +func TestScalarMulFakeGLV3(t *testing.T) { + assert := test.NewAssert(t) + _, g := stark_curve.Generators() + var r fr_stark.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S stark_curve.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulFakeGLVTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{} + witness := ScalarMulFakeGLVTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{ + S: emulated.ValueOf[emulated.STARKCurveFr](s), + Q: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](g.X), + Y: emulated.ValueOf[emulated.STARKCurveFp](g.Y), + }, + R: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](S.X), + Y: emulated.ValueOf[emulated.STARKCurveFp](S.Y), + }, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + type ScalarMulFakeGLVEdgeCasesTest[T, S emulated.FieldParams] struct { P, R AffinePoint[T] S emulated.Element[S] @@ -2086,3 +2140,46 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases2(t *testing.T) { err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) } + +func TestScalarMulFakeGLVEdgeCasesEdgeCases3(t *testing.T) { + assert := test.NewAssert(t) + _, g := stark_curve.Generators() + var r fr_stark.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S stark_curve.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulFakeGLVEdgeCasesTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulFakeGLVEdgeCasesTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{ + S: emulated.ValueOf[emulated.STARKCurveFr](s), + P: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](0), + Y: emulated.ValueOf[emulated.STARKCurveFp](0), + }, + R: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](0), + Y: emulated.ValueOf[emulated.STARKCurveFp](0), + }, + } + err := test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // 0 * P == (0,0) + witness2 := ScalarMulFakeGLVEdgeCasesTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{ + S: emulated.ValueOf[emulated.STARKCurveFr](new(big.Int)), + P: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](S.X), + Y: emulated.ValueOf[emulated.STARKCurveFp](S.X), + }, + R: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](0), + Y: emulated.ValueOf[emulated.STARKCurveFp](0), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) +} diff --git a/std/math/emulated/emparams/emparams.go b/std/math/emulated/emparams/emparams.go index 22e7872203..5b6fe62db1 100644 --- a/std/math/emulated/emparams/emparams.go +++ b/std/math/emulated/emparams/emparams.go @@ -282,6 +282,34 @@ type BLS24315Fr struct{ fourLimbPrimeField } func (fr BLS24315Fr) Modulus() *big.Int { return ecc.BLS24_315.ScalarField() } +// STARKCurveFp provides type parametrization for field emulation: +// - limbs: 4 +// - limb width: 64 bits +// +// The prime modulus for type parametrisation is: +// +// 0x800000000000011000000000000000000000000000000000000000000000001 (base 16) +// 361850278866613121369732278309507010562310721533159669997309205613587202048196699973092056135872020481 (base 10) +// +// This is the base field of the STARK curve. +type STARKCurveFp struct{ fourLimbPrimeField } + +func (fp STARKCurveFp) Modulus() *big.Int { return ecc.STARK_CURVE.BaseField() } + +// STARKCurveFr provides type parametrization for field emulation: +// - limbs: 4 +// - limb width: 64 bits +// +// The prime modulus for type parametrisation is: +// +// 0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f (base 16) +// 3618502788666131213697322783095070105526743751716087489154079457884512865583 (base 10) +// +// This is the scalar field of the STARK curve. +type STARKCurveFr struct{ fourLimbPrimeField } + +func (fp STARKCurveFr) Modulus() *big.Int { return ecc.STARK_CURVE.ScalarField() } + // Mod1e4096 provides type parametrization for emulated aritmetic: // - limbs: 64 // - limb width: 64 bits diff --git a/std/math/emulated/params.go b/std/math/emulated/params.go index 892d141d38..2b0dc9d179 100644 --- a/std/math/emulated/params.go +++ b/std/math/emulated/params.go @@ -17,6 +17,7 @@ import ( // - [BLS12381Fp] and [BLS12381Fr] // - [P256Fp] and [P256Fr] // - [P384Fp] and [P384Fr] +// - [STARKCurveFp] and [STARKCurveFr] type FieldParams interface { NbLimbs() uint // number of limbs to represent field element BitsPerLimb() uint // number of bits per limb. Top limb may contain less than limbSize bits. @@ -25,18 +26,20 @@ type FieldParams interface { } type ( - Goldilocks = emparams.Goldilocks - Secp256k1Fp = emparams.Secp256k1Fp - Secp256k1Fr = emparams.Secp256k1Fr - BN254Fp = emparams.BN254Fp - BN254Fr = emparams.BN254Fr - BLS12377Fp = emparams.BLS12377Fp - BLS12381Fp = emparams.BLS12381Fp - BLS12381Fr = emparams.BLS12381Fr - P256Fp = emparams.P256Fp - P256Fr = emparams.P256Fr - P384Fp = emparams.P384Fp - P384Fr = emparams.P384Fr - BW6761Fp = emparams.BW6761Fp - BW6761Fr = emparams.BW6761Fr + Goldilocks = emparams.Goldilocks + Secp256k1Fp = emparams.Secp256k1Fp + Secp256k1Fr = emparams.Secp256k1Fr + BN254Fp = emparams.BN254Fp + BN254Fr = emparams.BN254Fr + BLS12377Fp = emparams.BLS12377Fp + BLS12381Fp = emparams.BLS12381Fp + BLS12381Fr = emparams.BLS12381Fr + P256Fp = emparams.P256Fp + P256Fr = emparams.P256Fr + P384Fp = emparams.P384Fp + P384Fr = emparams.P384Fr + BW6761Fp = emparams.BW6761Fp + BW6761Fr = emparams.BW6761Fr + STARKCurveFp = emparams.STARKCurveFp + STARKCurveFr = emparams.STARKCurveFr ) From 62c89cb10cff1413e9d68cce054c7e711d04c726 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 7 Sep 2024 19:03:28 -0400 Subject: [PATCH 22/60] feat: fake GLV for twisted edwards --- std/algebra/native/twistededwards/curve.go | 9 +- .../native/twistededwards/curve_test.go | 48 ++++- std/algebra/native/twistededwards/hints.go | 168 ++++++++++++++++++ std/algebra/native/twistededwards/point.go | 150 +++++++++++++++- .../native/twistededwards/scalarmul_glv.go | 135 -------------- 5 files changed, 359 insertions(+), 151 deletions(-) create mode 100644 std/algebra/native/twistededwards/hints.go delete mode 100644 std/algebra/native/twistededwards/scalarmul_glv.go diff --git a/std/algebra/native/twistededwards/curve.go b/std/algebra/native/twistededwards/curve.go index bcc5f36119..9349e276e5 100644 --- a/std/algebra/native/twistededwards/curve.go +++ b/std/algebra/native/twistededwards/curve.go @@ -46,14 +46,7 @@ func (c *curve) AssertIsOnCurve(p1 Point) { } func (c *curve) ScalarMul(p1 Point, scalar frontend.Variable) Point { var p Point - if c.endo != nil { - // TODO restore - // this is disabled until this issue is solved https://github.com/ConsenSys/gnark/issues/268 - // p.scalarMulGLV(c.api, &p1, scalar, c.params, c.endo) - p.scalarMul(c.api, &p1, scalar, c.params) - } else { - p.scalarMul(c.api, &p1, scalar, c.params) - } + p.scalarMul(c.api, &p1, scalar, c.params, c.endo) return p } func (c *curve) DoubleBaseScalarMul(p1, p2 Point, s1, s2 frontend.Variable) Point { diff --git a/std/algebra/native/twistededwards/curve_test.go b/std/algebra/native/twistededwards/curve_test.go index a8492cd26b..f43e9efbaf 100644 --- a/std/algebra/native/twistededwards/curve_test.go +++ b/std/algebra/native/twistededwards/curve_test.go @@ -18,9 +18,11 @@ package twistededwards import ( "crypto/rand" + "fmt" "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc" tbls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/twistededwards" tbls12381_bandersnatch "github.com/consensys/gnark-crypto/ecc/bls12-381/bandersnatch" tbls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/twistededwards" @@ -32,7 +34,9 @@ import ( "github.com/consensys/gnark-crypto/ecc/twistededwards" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/internal/utils" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/test" ) @@ -204,7 +208,7 @@ func TestCurve(t *testing.T) { // testData generates random test data for given curve // returns p1, p2 and r, d such that p1 + p2 == r and p1 + p1 == d // returns rs1, rs12, s1, s2 such that rs1 = p2 * s2 and rs12 = p1*s1 + p2 * s2 -// retunrs n such that n = -p2 +// returns n such that n = -p2 func testData(params *CurveParams, curveID twistededwards.ID) ( _p1, _p2, @@ -419,3 +423,45 @@ func (p *CurveParams) randomScalar() *big.Int { r, _ := rand.Int(rand.Reader, p.Order) return r } + +type varScalarMul struct { + curveID twistededwards.ID + P Point + R Point + S frontend.Variable +} + +func (circuit *varScalarMul) Define(api frontend.API) error { + + // get edwards curve curve + curve, err := NewEdCurve(api, circuit.curveID) + if err != nil { + return err + } + + // scalar mul + res := curve.ScalarMul(circuit.P, circuit.S) + api.AssertIsEqual(res.X, circuit.R.X) + api.AssertIsEqual(res.Y, circuit.R.Y) + + return nil +} + +// bench +func BenchmarkBandersnatch(b *testing.B) { + var c varScalarMul + c.curveID = twistededwards.BLS12_381_BANDERSNATCH + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &c) + p.Stop() + fmt.Println("Bandersnatch GLV: ", p.NbConstraints()) +} + +func BenchmarkJubjub(b *testing.B) { + var c varScalarMul + c.curveID = twistededwards.BLS12_381 + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &c) + p.Stop() + fmt.Println("Jubjub 2-bit double-and-add: ", p.NbConstraints()) +} diff --git a/std/algebra/native/twistededwards/hints.go b/std/algebra/native/twistededwards/hints.go new file mode 100644 index 0000000000..90a59619cd --- /dev/null +++ b/std/algebra/native/twistededwards/hints.go @@ -0,0 +1,168 @@ +package twistededwards + +import ( + "errors" + "fmt" + "math/big" + "sync" + + "github.com/consensys/gnark-crypto/ecc" + edbls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/twistededwards" + "github.com/consensys/gnark-crypto/ecc/bls12-381/bandersnatch" + jubjub "github.com/consensys/gnark-crypto/ecc/bls12-381/twistededwards" + edbls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/twistededwards" + edbls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/twistededwards" + babyjubjub "github.com/consensys/gnark-crypto/ecc/bn254/twistededwards" + edbw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/twistededwards" + edbw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/twistededwards" + "github.com/consensys/gnark/constraint/solver" +) + +func GetHints() []solver.Hint { + return []solver.Hint{ + halfGCD, + scalarMulHint, + decomposeScalar, + } +} + +func init() { + solver.RegisterHint(GetHints()...) +} + +type glvParams struct { + lambda, order big.Int + glvBasis ecc.Lattice +} + +func decomposeScalar(scalarField *big.Int, inputs []*big.Int, res []*big.Int) error { + // the efficient endomorphism exists on Bandersnatch only + if scalarField.Cmp(ecc.BLS12_381.ScalarField()) != 0 { + return errors.New("no efficient endomorphism is available on this curve") + } + var glv glvParams + var init sync.Once + init.Do(func() { + glv.lambda.SetString("8913659658109529928382530854484400854125314752504019737736543920008458395397", 10) + glv.order.SetString("13108968793781547619861935127046491459309155893440570251786403306729687672801", 10) + ecc.PrecomputeLattice(&glv.order, &glv.lambda, &glv.glvBasis) + }) + + // sp[0] is always negative because, in SplitScalar(), we always round above + // the determinant/2 computed in PrecomputeLattice() which is negative for Bandersnatch. + // Thus taking -sp[0] here and negating the point in ScalarMul(). + // If we keep -sp[0] it will be reduced mod r (the BLS12-381 prime order) + // and not the Bandersnatch prime order (Order) and the result will be incorrect. + // Also, if we reduce it mod Order here, we can't use api.ToBinary(sp[0], 129) + // and hence we can't reduce optimally the number of constraints. + sp := ecc.SplitScalar(inputs[0], &glv.glvBasis) + res[0].Neg(&(sp[0])) + res[1].Set(&(sp[1])) + + // figure out how many times we have overflowed + res[2].Mul(res[1], &glv.lambda).Sub(res[2], res[0]) + res[2].Sub(res[2], inputs[0]) + res[2].Div(res[2], &glv.order) + + return nil +} + +func halfGCD(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two inputs") + } + if len(outputs) != 4 { + return fmt.Errorf("expecting four outputs") + } + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(inputs[1], inputs[0], glvBasis) + outputs[0].Set(&glvBasis.V1[0]) + outputs[1].Set(&glvBasis.V1[1]) + + // figure out how many times we have overflowed + // s2 * s + s1 = k*r + outputs[3].Mul(outputs[1], inputs[0]). + Add(outputs[3], outputs[0]). + Div(outputs[3], inputs[1]) + + outputs[2].SetUint64(0) + if outputs[1].Sign() == -1 { + outputs[1].Neg(outputs[1]) + outputs[2].SetUint64(1) + } + + return nil +} + +func scalarMulHint(field *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 4 { + return fmt.Errorf("expecting four inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + // compute the resulting point [s]Q + if field.Cmp(ecc.BLS12_381.ScalarField()) == 0 { + order, _ := new(big.Int).SetString("13108968793781547619861935127046491459309155893440570251786403306729687672801", 10) + if inputs[3].Cmp(order) == 0 { + var P bandersnatch.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else { + var P jubjub.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } + } else if field.Cmp(ecc.BN254.ScalarField()) == 0 { + var P babyjubjub.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(ecc.BLS12_377.ScalarField()) == 0 { + var P edbls12377.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(ecc.BLS24_315.ScalarField()) == 0 { + var P edbls24315.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(ecc.BLS24_317.ScalarField()) == 0 { + var P edbls24317.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(ecc.BW6_761.ScalarField()) == 0 { + var P edbw6761.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(ecc.BW6_633.ScalarField()) == 0 { + var P edbw6633.PointAffine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else { + return fmt.Errorf("scalarMulHint: unknown curve") + } + return nil +} diff --git a/std/algebra/native/twistededwards/point.go b/std/algebra/native/twistededwards/point.go index dbacdb30d5..6410b79367 100644 --- a/std/algebra/native/twistededwards/point.go +++ b/std/algebra/native/twistededwards/point.go @@ -18,6 +18,7 @@ package twistededwards import ( "github.com/consensys/gnark/frontend" + "math" ) // neg computes the negative of a point in SNARK coordinates @@ -95,17 +96,12 @@ func (p *Point) double(api frontend.API, p1 *Point, curve *CurveParams) *Point { return p } -// scalarMul computes the scalar multiplication of a point on a twisted Edwards curve +// scalarMulGeneric computes the scalar multiplication of a point on a twisted Edwards curve // p1: base point (as snark point) // curve: parameters of the Edwards curve // scal: scalar as a SNARK constraint // Standard left to right double and add -func (p *Point) scalarMul(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams, endo ...*EndoParams) *Point { - if len(endo) == 1 && endo[0] != nil { - // use glv - return p.scalarMulGLV(api, p1, scalar, curve, endo[0]) - } - +func (p *Point) scalarMulGeneric(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams, endo ...*EndoParams) *Point { // first unpack the scalar b := api.ToBinary(scalar) @@ -142,6 +138,15 @@ func (p *Point) scalarMul(api frontend.API, p1 *Point, scalar frontend.Variable, return p } +// scalarMul computes the scalar multiplication of a point on a twisted Edwards curve +// p1: base point (as snark point) +// curve: parameters of the Edwards curve +// scal: scalar as a SNARK constraint +// Standard left to right double and add +func (p *Point) scalarMul(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams, endo ...*EndoParams) *Point { + return p.scalarMulFakeGLV(api, p1, scalar, curve) +} + // doubleBaseScalarMul computes s1*P1+s2*P2 // where P1 and P2 are points on a twisted Edwards curve // and s1, s2 scalars. @@ -172,3 +177,134 @@ func (p *Point) doubleBaseScalarMul(api frontend.API, p1, p2 *Point, s1, s2 fron return p } + +// GLV + +// phi endomorphism √-2 ∈ 𝒪₋₈ +// (x,y) → λ × (x,y) s.t. λ² = -2 mod Order +func (p *Point) phi(api frontend.API, p1 *Point, curve *CurveParams, endo *EndoParams) *Point { + + xy := api.Mul(p1.X, p1.Y) + yy := api.Mul(p1.Y, p1.Y) + f := api.Sub(1, yy) + f = api.Mul(f, endo.Endo[1]) + g := api.Add(yy, endo.Endo[0]) + g = api.Mul(g, endo.Endo[0]) + h := api.Sub(yy, endo.Endo[0]) + + p.X = api.DivUnchecked(f, xy) + p.Y = api.DivUnchecked(g, h) + + return p +} + +// scalarMulGLV computes the scalar multiplication of a point on a twisted +// Edwards curve à la GLV. +// p1: base point (as snark point) +// curve: parameters of the Edwards curve +// scal: scalar as a SNARK constraint +// Standard left to right double and add +func (p *Point) scalarMulGLV(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams, endo *EndoParams) *Point { + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + λ * s2 == s mod Order, + // with λ s.t. λ² = -2 mod Order. + sd, err := api.NewHint(decomposeScalar, 3, scalar) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + s1, s2 := sd[0], sd[1] + + // -s1 + λ * s2 == s + k*Order + api.AssertIsEqual(api.Sub(api.Mul(s2, endo.Lambda), s1), api.Add(scalar, api.Mul(curve.Order, sd[2]))) + + // Normally s1 and s2 are of the max size sqrt(Order) = 128 + // But in a circuit, we force s1 to be negative by rounding always above. + // This changes the size bounds to 2*sqrt(Order) = 129. + n := 129 + + b1 := api.ToBinary(s1, n) + b2 := api.ToBinary(s2, n) + + var res, _p1, p2, p3, tmp Point + _p1.neg(api, p1) + p2.phi(api, p1, curve, endo) + p3.add(api, &_p1, &p2, curve) + + res.X = api.Lookup2(b1[n-1], b2[n-1], 0, _p1.X, p2.X, p3.X) + res.Y = api.Lookup2(b1[n-1], b2[n-1], 1, _p1.Y, p2.Y, p3.Y) + + for i := n - 2; i >= 0; i-- { + res.double(api, &res, curve) + tmp.X = api.Lookup2(b1[i], b2[i], 0, _p1.X, p2.X, p3.X) + tmp.Y = api.Lookup2(b1[i], b2[i], 1, _p1.Y, p2.Y, p3.Y) + res.add(api, &res, &tmp, curve) + } + + p.X = res.X + p.Y = res.Y + + return p +} + +// scalarMulFakeGLV computes the scalar multiplication of a point on a twisted +// Edwards curve following https://hackmd.io/@yelhousni/Hy-aWld50 +// +// [s]p1 == q is equivalent to [s2]([s]p1 - q) = (0,1) which is [s1]p1 + [s2]q = (0,1) +// with s1, s2 < sqrt(Order) and s1 + s2 * s = 0 mod Order. +// +// p1: base point (as snark point) +// curve: parameters of the Edwards curve +// scal: scalar as a SNARK constraint +// Standard left to right double and add +func (p *Point) scalarMulFakeGLV(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams) *Point { + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + s * s2 == 0 mod Order, + s, err := api.NewHint(halfGCD, 4, scalar, curve.Order) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + s1, s2, bit, k := s[0], s[1], s[2], s[3] + + // check that s1 + s2 * s == k*Order + _s2 := api.Mul(s2, scalar) + _k := api.Mul(k, curve.Order) + lhs := api.Select(bit, s1, api.Add(s1, _s2)) + rhs := api.Select(bit, api.Add(_k, _s2), _k) + api.AssertIsEqual(lhs, rhs) + + n := int(math.Ceil(float64(curve.Order.BitLen()) / 2)) + b1 := api.ToBinary(s1, n) + b2 := api.ToBinary(s2, n) + + var res, p2, p3, tmp Point + q, err := api.NewHint(scalarMulHint, 2, p1.X, p1.Y, scalar, curve.Order) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + p2.X = api.Select(bit, api.Neg(q[0]), q[0]) + p2.Y = q[1] + + p3.add(api, p1, &p2, curve) + + res.X = api.Lookup2(b1[n-1], b2[n-1], 0, p1.X, p2.X, p3.X) + res.Y = api.Lookup2(b1[n-1], b2[n-1], 1, p1.Y, p2.Y, p3.Y) + + for i := n - 2; i >= 0; i-- { + res.double(api, &res, curve) + tmp.X = api.Lookup2(b1[i], b2[i], 0, p1.X, p2.X, p3.X) + tmp.Y = api.Lookup2(b1[i], b2[i], 1, p1.Y, p2.Y, p3.Y) + res.add(api, &res, &tmp, curve) + } + + api.AssertIsEqual(res.X, 0) + api.AssertIsEqual(res.Y, 1) + + p.X = q[0] + p.Y = q[1] + + return p +} diff --git a/std/algebra/native/twistededwards/scalarmul_glv.go b/std/algebra/native/twistededwards/scalarmul_glv.go deleted file mode 100644 index 7b959a2db4..0000000000 --- a/std/algebra/native/twistededwards/scalarmul_glv.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright © 2022 ConsenSys Software Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package twistededwards - -import ( - "errors" - "math/big" - "sync" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/constraint/solver" - "github.com/consensys/gnark/frontend" -) - -// phi endomorphism √-2 ∈ 𝒪₋₈ -// (x,y) → λ × (x,y) s.t. λ² = -2 mod Order -func (p *Point) phi(api frontend.API, p1 *Point, curve *CurveParams, endo *EndoParams) *Point { - - xy := api.Mul(p1.X, p1.Y) - yy := api.Mul(p1.Y, p1.Y) - f := api.Sub(1, yy) - f = api.Mul(f, endo.Endo[1]) - g := api.Add(yy, endo.Endo[0]) - g = api.Mul(g, endo.Endo[0]) - h := api.Sub(yy, endo.Endo[0]) - - p.X = api.DivUnchecked(f, xy) - p.Y = api.DivUnchecked(g, h) - - return p -} - -type glvParams struct { - lambda, order big.Int - glvBasis ecc.Lattice -} - -var DecomposeScalar = func(scalarField *big.Int, inputs []*big.Int, res []*big.Int) error { - // the efficient endomorphism exists on Bandersnatch only - if scalarField.Cmp(ecc.BLS12_381.ScalarField()) != 0 { - return errors.New("no efficient endomorphism is available on this curve") - } - var glv glvParams - var init sync.Once - init.Do(func() { - glv.lambda.SetString("8913659658109529928382530854484400854125314752504019737736543920008458395397", 10) - glv.order.SetString("13108968793781547619861935127046491459309155893440570251786403306729687672801", 10) - ecc.PrecomputeLattice(&glv.order, &glv.lambda, &glv.glvBasis) - }) - - // sp[0] is always negative because, in SplitScalar(), we always round above - // the determinant/2 computed in PrecomputeLattice() which is negative for Bandersnatch. - // Thus taking -sp[0] here and negating the point in ScalarMul(). - // If we keep -sp[0] it will be reduced mod r (the BLS12-381 prime order) - // and not the Bandersnatch prime order (Order) and the result will be incorrect. - // Also, if we reduce it mod Order here, we can't use api.ToBinary(sp[0], 129) - // and hence we can't reduce optimally the number of constraints. - sp := ecc.SplitScalar(inputs[0], &glv.glvBasis) - res[0].Neg(&(sp[0])) - res[1].Set(&(sp[1])) - - // figure out how many times we have overflowed - res[2].Mul(res[1], &glv.lambda).Sub(res[2], res[0]) - res[2].Sub(res[2], inputs[0]) - res[2].Div(res[2], &glv.order) - - return nil -} - -func init() { - solver.RegisterHint(DecomposeScalar) -} - -// ScalarMul computes the scalar multiplication of a point on a twisted Edwards curve -// p1: base point (as snark point) -// curve: parameters of the Edwards curve -// scal: scalar as a SNARK constraint -// Standard left to right double and add -func (p *Point) scalarMulGLV(api frontend.API, p1 *Point, scalar frontend.Variable, curve *CurveParams, endo *EndoParams) *Point { - // the hints allow to decompose the scalar s into s1 and s2 such that - // s1 + λ * s2 == s mod Order, - // with λ s.t. λ² = -2 mod Order. - sd, err := api.NewHint(DecomposeScalar, 3, scalar) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - s1, s2 := sd[0], sd[1] - - // -s1 + λ * s2 == s + k*Order - api.AssertIsEqual(api.Sub(api.Mul(s2, endo.Lambda), s1), api.Add(scalar, api.Mul(curve.Order, sd[2]))) - - // Normally s1 and s2 are of the max size sqrt(Order) = 128 - // But in a circuit, we force s1 to be negative by rounding always above. - // This changes the size bounds to 2*sqrt(Order) = 129. - n := 129 - - b1 := api.ToBinary(s1, n) - b2 := api.ToBinary(s2, n) - - var res, _p1, p2, p3, tmp Point - _p1.neg(api, p1) - p2.phi(api, p1, curve, endo) - p3.add(api, &_p1, &p2, curve) - - res.X = api.Lookup2(b1[n-1], b2[n-1], 0, _p1.X, p2.X, p3.X) - res.Y = api.Lookup2(b1[n-1], b2[n-1], 1, _p1.Y, p2.Y, p3.Y) - - for i := n - 2; i >= 0; i-- { - res.double(api, &res, curve) - tmp.X = api.Lookup2(b1[i], b2[i], 0, _p1.X, p2.X, p3.X) - tmp.Y = api.Lookup2(b1[i], b2[i], 1, _p1.Y, p2.Y, p3.Y) - res.add(api, &res, &tmp, curve) - } - - p.X = res.X - p.Y = res.Y - - return p -} From 491fa482652a9dc67dea46a15c1a1828f2a91f13 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 26 Sep 2024 08:56:22 -0400 Subject: [PATCH 23/60] feat: glv+fake-glv in sw_emulated --- std/algebra/emulated/sw_emulated/hints.go | 200 +++++++++++++- std/algebra/emulated/sw_emulated/point.go | 248 +++++++++++++++++- .../emulated/sw_emulated/point_test.go | 65 ++++- 3 files changed, 504 insertions(+), 9 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 57ec896351..1fd5b73bc9 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -6,8 +6,15 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + bn_fp "github.com/consensys/gnark-crypto/ecc/bn254/fp" + bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761" + bw6_fp "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" + "github.com/consensys/gnark-crypto/ecc/secp256k1" + secp_fp "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" stark_fp "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/consensys/gnark-crypto/field/eisenstein" "github.com/consensys/gnark/constraint/solver" limbs "github.com/consensys/gnark/std/internal/limbcomposition" "github.com/consensys/gnark/std/math/emulated" @@ -23,8 +30,11 @@ func GetHints() []solver.Hint { decomposeScalarG1Signs, decomposeScalarG1Subscalars, scalarMulG1Hint, - halfGCDSigns, + scalarMulGLVG1Hint, halfGCD, + halfGCDSigns, + halfGCDEisenstein, + halfGCDEisensteinSigns, } } @@ -166,6 +176,98 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { }) } +func scalarMulGLVG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + + if field.Cmp(bn_fp.Modulus()) == 0 { + var fp emparams.BN254Fp + var fr emparams.BN254Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + // compute the resulting point [s]Q + var P bn254.G1Affine + P.X.SetBigInt(Px) + P.Y.SetBigInt(Py) + P.ScalarMultiplication(&P, S) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(secp_fp.Modulus()) == 0 { + var fp emparams.Secp256k1Fp + var fr emparams.Secp256k1Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + // compute the resulting point [s]Q + var P secp256k1.G1Affine + P.X.SetBigInt(Px) + P.Y.SetBigInt(Py) + P.ScalarMultiplication(&P, S) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else if field.Cmp(bw6_fp.Modulus()) == 0 { + var fp emparams.BW6761Fp + var fr emparams.BW6761Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + // compute the resulting point [s]Q + var P bw6761.G1Affine + P.X.SetBigInt(Px) + P.Y.SetBigInt(Py) + P.ScalarMultiplication(&P, S) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + } else { + return fmt.Errorf("unsupported curve") + } + + return nil + }) +} + func halfGCDSigns(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeOutput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 1 { @@ -210,3 +312,99 @@ func halfGCD(mod *big.Int, inputs, outputs []*big.Int) error { return nil }) } + +func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { + return emulated.UnwrapHintWithNativeOutput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two input") + } + if len(outputs) != 6 { + return fmt.Errorf("expecting six outputs") + } + var r, s eisenstein.ComplexNumber + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(field, inputs[1], glvBasis) + r.A0.Set(&glvBasis.V1[0]) + r.A1.Set(&glvBasis.V1[1]) + sp := ecc.SplitScalar(inputs[0], glvBasis) + s.A0.Set(&sp[0]) + s.A1.Set(&sp[1]) + + outputs[0].SetUint64(0) + outputs[1].SetUint64(0) + outputs[2].SetUint64(0) + outputs[3].SetUint64(0) + outputs[4].SetUint64(0) + outputs[5].SetUint64(0) + res := eisenstein.HalfGCD(&r, &s) + if res[0].A0.Sign() == -1 { + outputs[0].SetUint64(1) + } + if res[0].A1.Sign() == -1 { + outputs[1].SetUint64(1) + } + if res[1].A0.Sign() == -1 { + outputs[2].SetUint64(1) + } + if res[1].A1.Sign() == -1 { + outputs[3].SetUint64(1) + } + if res[2].A0.Sign() == -1 { + outputs[4].SetUint64(1) + } + if res[2].A1.Sign() == -1 { + outputs[5].SetUint64(1) + } + + return nil + }) +} + +func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two input") + } + if len(outputs) != 6 { + return fmt.Errorf("expecting six outputs") + } + var r, s eisenstein.ComplexNumber + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(field, inputs[1], glvBasis) + r.A0.Set(&glvBasis.V1[0]) + r.A1.Set(&glvBasis.V1[1]) + sp := ecc.SplitScalar(inputs[0], glvBasis) + s.A0.Set(&sp[0]) + s.A1.Set(&sp[1]) + + res := eisenstein.HalfGCD(&r, &s) + outputs[0].Set(&res[0].A0) + outputs[1].Set(&res[0].A1) + outputs[2].Set(&res[1].A0) + outputs[3].Set(&res[1].A1) + outputs[4].Set(&res[2].A0) + outputs[5].Set(&res[2].A1) + if outputs[0].Sign() == -1 { + outputs[0].Neg(outputs[0]) + } + if outputs[1].Sign() == -1 { + outputs[1].Neg(outputs[1]) + } + if outputs[2].Sign() == -1 { + outputs[2].Neg(outputs[2]) + } + if outputs[3].Sign() == -1 { + outputs[3].Neg(outputs[3]) + } + if outputs[4].Sign() == -1 { + outputs[4].Neg(outputs[4]) + } + if outputs[5].Sign() == -1 { + outputs[5].Neg(outputs[5]) + } + fmt.Println(outputs[0].BitLen(), outputs[1].BitLen(), outputs[2].BitLen(), outputs[3].BitLen(), outputs[4].BitLen(), outputs[5].BitLen()) + + return nil + + }) +} diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index cd7f42f85a..796de3cc9b 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -508,7 +508,7 @@ func (c *Curve[B, S]) Mux(sel frontend.Variable, inputs ...*AffinePoint[B]) *Aff // ScalarMul computes [s]p and returns it. It doesn't modify p nor s. // This function doesn't check that the p is on the curve. See AssertIsOnCurve. // -// ScalarMul calls scalarMulGeneric or scalarMulGLV depending on whether an efficient endomorphism is available. +// ScalarMul calls scalarMulFakeGLV or scalarMulGLV depending on whether an efficient endomorphism is available. func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { if c.eigenvalue != nil && c.thirdRootOne != nil { return c.scalarMulGLV(p, s, opts...) @@ -727,7 +727,7 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op return Acc } -// scalarMulGeneric computes [s]p and returns it. It doesn't modify p nor s. +// scalarMulJoye computes [s]p and returns it. It doesn't modify p nor s. // This function doesn't check that the p is on the curve. See AssertIsOnCurve. // // ⚠️ p must not be (0,0) and s must not be 0, unless [algopts.WithCompleteArithmetic] option is set. @@ -746,7 +746,7 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op // [ELM03]: https://arxiv.org/pdf/math/0208038.pdf // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf // [Joye07]: https://www.iacr.org/archive/ches2007/47270135/47270135.pdf -func (c *Curve[B, S]) scalarMulGeneric(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { +func (c *Curve[B, S]) scalarMulJoye(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { cfg, err := algopts.NewConfig(opts...) if err != nil { panic(fmt.Sprintf("parse opts: %v", err)) @@ -806,15 +806,15 @@ func (c *Curve[B, S]) jointScalarMul(p1, p2 *AffinePoint[B], s1, s2 *emulated.El return c.jointScalarMulGLV(p1, p2, s1, s2, opts...) } else { - return c.jointScalarMulGeneric(p1, p2, s1, s2, opts...) + return c.jointScalarMulFakeGLV(p1, p2, s1, s2, opts...) } } -// jointScalarMulGeneric computes [s1]p1 + [s2]p2. It doesn't modify p1, p2 nor s1, s2. +// jointScalarMulFakeGLV computes [s1]p1 + [s2]p2. It doesn't modify p1, p2 nor s1, s2. // // ⚠️ The scalars s1, s2 must be nonzero and the point p1, p2 different from (0,0), unless [algopts.WithCompleteArithmetic] option is set. -func (c *Curve[B, S]) jointScalarMulGeneric(p1, p2 *AffinePoint[B], s1, s2 *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { +func (c *Curve[B, S]) jointScalarMulFakeGLV(p1, p2 *AffinePoint[B], s1, s2 *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { sm1 := c.scalarMulFakeGLV(p1, s1, opts...) sm2 := c.scalarMulFakeGLV(p2, s2, opts...) return c.AddUnified(sm1, sm2) @@ -1526,3 +1526,239 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] Y: *R[1], } } + +func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Element[S]) *AffinePoint[B] { + + // First we compute the hinted scalar mul Q = [s]P + // P coordinates are in Fp and the scalar s in Fr + // we decompose Q.X, Q.Y, s into limbs and recompose them in the hint. + var inps []frontend.Variable + inps = append(inps, P.X.Limbs...) + inps = append(inps, P.Y.Limbs...) + inps = append(inps, s.Limbs...) + point, err := c.baseApi.NewHintWithNativeInput(scalarMulGLVG1Hint, 2, inps...) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} + + // We check that Q - [s]P = 0 or Q + [s](-P) = 0, so we negate P. + P = c.Neg(P) + + // Checking Q + [s](-P) = 0 is equivalent to [v]Q + [v*s](-P) = 0 for some nonzero v. + // + // The GLV curves supported in gnark have j-invariant 0, which means the eigenvalue + // of the GLV endomorphism is a primitive cube root of unity. If we write + // v, s and r as Eisenstein integers we can express the check as: + // + // [v1 + λ*v2]Q + [u1 + λ*u2](-P) = 0 + // [v1]Q + [v2]phi(Q) + [u1]phi(-P) + [u2]phi(-P) = 0 + // + // where (v1 + λ*v2)*(s1 + λ*s2) = u1 + λu2 mod (r1 + λ*r2) and u1, u2, v1, v2 < r^{1/4}. + // + // This can be done as follows: + // 1. decompose s into s1 + λ*s2 mod r s.t. s1, s2 < sqrt(r) (hinted GLV decomposition). + // 2. decompose r into r1 + λ*r2 s.t. r1, r2 < sqrt(r) (hardcoded half-GCD of λ mod r). + // 3. find u1, u2, v1, v2 < r^{1/4} s.t. (v1 + λ*v2)*(s1 + λ*s2) = (u1 + λ*u2) mod (r1 + λ*r2). + // This can be done through a hinted half-GCD in the number field + // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of + // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. + sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 6, s, c.eigenvalue) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + u1, u2, v1, v2, _, _ := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5] + + // integers can be negative. So we return positive integers in the hint and negate the corresponsing points here. + signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 6, s, c.eigenvalue) + if err != nil { + panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) + } + selector1, selector2, selector3, selector4, _, _ := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5] + + // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0. + // (s1*v1 - s2*v2 + r1*w1 - r2*w2 - u1) + j(s1*v2 + s2*v1 + r1*w2 + r2*w1 - u2) = 0. + // TODO: implement this check + // c.scalarApi.AssertIsEqual( + // c.scalarApi.Add(s3, c.scalarApi.Mul(s4, c.eigenvalue)), + // s, + // ) + + // precompute -P, -Φ(P), Φ(P) + var tableP, tablePhiP [2]*AffinePoint[B] + negPY := c.baseApi.Neg(&P.Y) + tableP[1] = &AffinePoint[B]{ + X: P.X, + Y: *c.baseApi.Select(selector1, negPY, &P.Y), + } + tableP[0] = c.Neg(tableP[1]) + tablePhiP[1] = &AffinePoint[B]{ + X: *c.baseApi.Mul(&P.X, c.thirdRootOne), + Y: *c.baseApi.Select(selector2, negPY, &P.Y), + } + tablePhiP[0] = c.Neg(tablePhiP[1]) + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [2]*AffinePoint[B] + negQY := c.baseApi.Neg(&Q.Y) + tableQ[1] = &AffinePoint[B]{ + X: Q.X, + Y: *c.baseApi.Select(selector3, negQY, &Q.Y), + } + tableQ[0] = c.Neg(tableQ[1]) + tablePhiQ[1] = &AffinePoint[B]{ + X: *c.baseApi.Mul(&Q.X, c.thirdRootOne), + Y: *c.baseApi.Select(selector4, negQY, &Q.Y), + } + tablePhiQ[0] = c.Neg(tablePhiQ[1]) + + // precompute P+Q, -P-Q, P-Q, -P+Q, Φ(P)+Φ(Q), -Φ(P)-Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) + var tableS, tablePhiS [4]*AffinePoint[B] + tableS[0] = c.Add(tableP[0], tableQ[0]) + tableS[1] = c.Neg(tableS[0]) + tableS[2] = c.Add(tableP[1], tableQ[0]) + tableS[3] = c.Neg(tableS[2]) + tablePhiS[0] = c.Add(tablePhiP[0], tablePhiQ[0]) + tablePhiS[1] = c.Neg(tablePhiS[0]) + tablePhiS[2] = c.Add(tablePhiP[1], tablePhiQ[0]) + tablePhiS[3] = c.Neg(tablePhiS[2]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = P + Q + Φ(P) + Φ(Q) + Acc := c.Add(tableS[1], tablePhiS[1]) + B1 := Acc + // then we add G (the base point) to Acc to avoid incomplete additions in + // the loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it + // might happen that Acc==Bi or Acc==-Bi. But now we force Acc to be + // different than the stored Bi. However we need at the end to subtract + // [2^nbits]G from the result. + // + // g0 = G + g := c.Generator() + Acc = c.Add(Acc, g) + + s1bits := c.scalarApi.ToBits(u1) + s2bits := c.scalarApi.ToBits(u2) + t1bits := c.scalarApi.ToBits(v1) + t2bits := c.scalarApi.ToBits(v2) + var st S + nbits := st.Modulus().BitLen()>>2 + 1 + + // At each iteration we look up the point Bi from: + // B1 = +P + Q + Φ(P) + Φ(Q) + // B2 = +P + Q + Φ(P) - Φ(Q) + B2 := c.Add(tableS[1], tablePhiS[2]) + // B3 = +P + Q - Φ(P) + Φ(Q) + B3 := c.Add(tableS[1], tablePhiS[3]) + // B4 = +P + Q - Φ(P) - Φ(Q) + B4 := c.Add(tableS[1], tablePhiS[0]) + // B5 = +P - Q + Φ(P) + Φ(Q) + B5 := c.Add(tableS[2], tablePhiS[1]) + // B6 = +P - Q + Φ(P) - Φ(Q) + B6 := c.Add(tableS[2], tablePhiS[2]) + // B7 = +P - Q - Φ(P) + Φ(Q) + B7 := c.Add(tableS[2], tablePhiS[3]) + // B8 = +P - Q - Φ(P) - Φ(Q) + B8 := c.Add(tableS[2], tablePhiS[0]) + // B9 = -P + Q + Φ(P) + Φ(Q) + B9 := c.Neg(B8) + // B10 = -P + Q + Φ(P) - Φ(Q) + B10 := c.Neg(B7) + // B11 = -P + Q - Φ(P) + Φ(Q) + B11 := c.Neg(B6) + // B12 = -P + Q - Φ(P) - Φ(Q) + B12 := c.Neg(B5) + // B13 = -P - Q + Φ(P) + Φ(Q) + B13 := c.Neg(B4) + // B14 = -P - Q + Φ(P) - Φ(Q) + B14 := c.Neg(B3) + // B15 = -P - Q - Φ(P) + Φ(Q) + B15 := c.Neg(B2) + // B16 = -P - Q - Φ(P) - Φ(Q) + B16 := c.Neg(B1) + // note that half the points are negatives of the other half, + // hence have the same X coordinates. + + var Bi *AffinePoint[B] + for i := nbits - 1; i >= nbits-3; i-- { + // selectorY takes values in [0,15] + selectorY := c.api.Add( + s1bits[i], + c.api.Mul(s2bits[i], 2), + c.api.Mul(t1bits[i], 4), + c.api.Mul(t2bits[i], 8), + ) + // selectorX takes values in [0,7] s.t.: + // - when selectorY < 8: selectorX = selectorY + // - when selectorY >= 8: selectorX = 15 - selectorY + selectorX := c.api.Add( + c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(t2bits[i], 2))), + c.api.Mul(t2bits[i], 15), + ) + // Bi.Y are distincts so we need a 16-to-1 multiplexer, + // but only half of the Bi.X are distinct so we need a 8-to-1. + Bi = &AffinePoint[B]{ + X: *c.baseApi.Mux(selectorX, + &B16.X, &B8.X, &B14.X, &B6.X, &B12.X, &B4.X, &B10.X, &B2.X, + ), + Y: *c.baseApi.Mux(selectorY, + &B16.Y, &B8.Y, &B14.Y, &B6.Y, &B12.Y, &B4.Y, &B10.Y, &B2.Y, + &B15.Y, &B7.Y, &B13.Y, &B5.Y, &B11.Y, &B3.Y, &B9.Y, &B1.Y, + ), + } + // Acc = [2]Acc + Bi + Acc = c.double(Acc) + Acc = c.Add(Acc, Bi) + } + + for i := nbits - 4; i > 0; i-- { + // selectorY takes values in [0,15] + selectorY := c.api.Add( + s1bits[i], + c.api.Mul(s2bits[i], 2), + c.api.Mul(t1bits[i], 4), + c.api.Mul(t2bits[i], 8), + ) + // selectorX takes values in [0,7] s.t.: + // - when selectorY < 8: selectorX = selectorY + // - when selectorY >= 8: selectorX = 15 - selectorY + selectorX := c.api.Add( + c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(t2bits[i], 2))), + c.api.Mul(t2bits[i], 15), + ) + // Bi.Y are distincts so we need a 16-to-1 multiplexer, + // but only half of the Bi.X are distinct so we need a 8-to-1. + Bi = &AffinePoint[B]{ + X: *c.baseApi.Mux(selectorX, + &B16.X, &B8.X, &B14.X, &B6.X, &B12.X, &B4.X, &B10.X, &B2.X, + ), + Y: *c.baseApi.Mux(selectorY, + &B16.Y, &B8.Y, &B14.Y, &B6.Y, &B12.Y, &B4.Y, &B10.Y, &B2.Y, + &B15.Y, &B7.Y, &B13.Y, &B5.Y, &B11.Y, &B3.Y, &B9.Y, &B1.Y, + ), + } + // Acc = [2]Acc + Bi + Acc = c.doubleAndAdd(Acc, Bi) + } + + // i = 0 + // subtract the P, Q, Φ(P), Φ(Q) if the first bits are 0 + tableP[0] = c.Add(tableP[0], Acc) + Acc = c.Select(s1bits[0], Acc, tableP[0]) + tablePhiP[0] = c.Add(tablePhiP[0], Acc) + Acc = c.Select(s2bits[0], Acc, tablePhiP[0]) + tableQ[0] = c.Add(tableQ[0], Acc) + Acc = c.Select(t1bits[0], Acc, tableQ[0]) + tablePhiQ[0] = c.Add(tablePhiQ[0], Acc) + Acc = c.Select(t2bits[0], Acc, tablePhiQ[0]) + + // Add should be now equal to [2^nbits]G + gm := c.GeneratorMultiples()[nbits-1] + c.AssertIsEqual(Acc, &gm) + + return &AffinePoint[B]{ + X: *point[0], + Y: *point[1], + } +} diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 051224bf1b..bd83dbeeb1 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -3,6 +3,7 @@ package sw_emulated import ( "crypto/elliptic" "crypto/rand" + "fmt" "math/big" "testing" @@ -20,6 +21,8 @@ import ( stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" fr_stark "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -1436,7 +1439,7 @@ func (c *ScalarMulTestBounded[T, S]) Define(api frontend.API) error { if err != nil { return err } - res := cr.scalarMulGeneric(&c.P, &c.S, algopts.WithNbScalarBits(c.bits)) + res := cr.scalarMulJoye(&c.P, &c.S, algopts.WithNbScalarBits(c.bits)) cr.AssertIsEqual(res, &c.Q) return nil } @@ -1951,7 +1954,7 @@ func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { if err != nil { return err } - res := cr.scalarMulGeneric(&c.P, &c.S) + res := cr.scalarMulJoye(&c.P, &c.S) cr.AssertIsEqual(res, &c.Q) return nil } @@ -2183,3 +2186,61 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases3(t *testing.T) { err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) } + +type ScalarMulGLVAndFakeGLVTest[T, S emulated.FieldParams] struct { + Q, R AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulGLVAndFakeGLVTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulGLVAndFakeGLV(&c.Q, &c.S) + cr.AssertIsEqual(res, &c.R) + return nil +} + +func TestScalarMulGLVAndFakeGLV(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + var r fr_secp.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S secp256k1.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulGLVAndFakeGLVTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + witness := ScalarMulGLVAndFakeGLVTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](s), + Q: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](S.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](S.Y), + }, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + +// bench +func BenchmarkScalarMulNew(b *testing.B) { + c := ScalarMulGLVAndFakeGLVTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("New: ", p.NbConstraints()) +} + +func BenchmarkScalarMulOld(b *testing.B) { + c := ScalarMulTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("Old: ", p.NbConstraints()) +} From d236bead1092308ae2dc9418e2da451cf971f43b Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 26 Sep 2024 16:01:17 -0400 Subject: [PATCH 24/60] fix: check hinted einsenstein scalars --- std/algebra/emulated/sw_emulated/hints.go | 42 +++++++++-- std/algebra/emulated/sw_emulated/point.go | 87 +++++++++++++++++++---- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 1fd5b73bc9..ea7e5d261c 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -318,8 +318,8 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 6 { - return fmt.Errorf("expecting six outputs") + if len(outputs) != 10 { + return fmt.Errorf("expecting ten outputs") } var r, s eisenstein.ComplexNumber glvBasis := new(ecc.Lattice) @@ -336,6 +336,10 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { outputs[3].SetUint64(0) outputs[4].SetUint64(0) outputs[5].SetUint64(0) + outputs[6].SetUint64(0) + outputs[7].SetUint64(0) + outputs[8].SetUint64(0) + outputs[9].SetUint64(0) res := eisenstein.HalfGCD(&r, &s) if res[0].A0.Sign() == -1 { outputs[0].SetUint64(1) @@ -355,6 +359,18 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if res[2].A1.Sign() == -1 { outputs[5].SetUint64(1) } + if r.A0.Sign() == -1 { + outputs[6].SetUint64(1) + } + if r.A1.Sign() == -1 { + outputs[7].SetUint64(1) + } + if s.A0.Sign() == -1 { + outputs[8].SetUint64(1) + } + if s.A1.Sign() == -1 { + outputs[9].SetUint64(1) + } return nil }) @@ -365,8 +381,8 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 6 { - return fmt.Errorf("expecting six outputs") + if len(outputs) != 10 { + return fmt.Errorf("expecting ten outputs") } var r, s eisenstein.ComplexNumber glvBasis := new(ecc.Lattice) @@ -384,6 +400,10 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro outputs[3].Set(&res[1].A1) outputs[4].Set(&res[2].A0) outputs[5].Set(&res[2].A1) + outputs[6].Set(&r.A0) + outputs[7].Set(&r.A1) + outputs[8].Set(&s.A0) + outputs[9].Set(&s.A1) if outputs[0].Sign() == -1 { outputs[0].Neg(outputs[0]) } @@ -402,8 +422,18 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if outputs[5].Sign() == -1 { outputs[5].Neg(outputs[5]) } - fmt.Println(outputs[0].BitLen(), outputs[1].BitLen(), outputs[2].BitLen(), outputs[3].BitLen(), outputs[4].BitLen(), outputs[5].BitLen()) - + if outputs[6].Sign() == -1 { + outputs[6].Neg(outputs[6]) + } + if outputs[7].Sign() == -1 { + outputs[7].Neg(outputs[7]) + } + if outputs[8].Sign() == -1 { + outputs[8].Neg(outputs[8]) + } + if outputs[9].Sign() == -1 { + outputs[9].Neg(outputs[9]) + } return nil }) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 796de3cc9b..882b037234 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1563,27 +1563,88 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // This can be done through a hinted half-GCD in the number field // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. - sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 6, s, c.eigenvalue) + sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 10, s, c.eigenvalue) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - u1, u2, v1, v2, _, _ := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5] + u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] // integers can be negative. So we return positive integers in the hint and negate the corresponsing points here. - signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 6, s, c.eigenvalue) + signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, s, c.eigenvalue) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } - selector1, selector2, selector3, selector4, _, _ := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5] - - // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0. - // (s1*v1 - s2*v2 + r1*w1 - r2*w2 - u1) + j(s1*v2 + s2*v1 + r1*w2 + r2*w1 - u2) = 0. - // TODO: implement this check - // c.scalarApi.AssertIsEqual( - // c.scalarApi.Add(s3, c.scalarApi.Mul(s4, c.eigenvalue)), - // s, - // ) + selector1, selector2, selector3, selector4, selector5, selector6, selector7, selector8, selector9, selector10 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5], signs[6], signs[7], signs[8], signs[9] + + // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 + // is equivalent to: + // s1v1+r1w1=s2v2+r2w2+u1 and + // s1v2+s2v1+r1w2+r2w1=s2v2+r2w2+u2 + s1v1 := c.scalarApi.Mul(s1, v1) + s2v2 := c.scalarApi.Mul(s2, v2) + r1w1 := c.scalarApi.Mul(r1, w1) + r2w2 := c.scalarApi.Mul(r2, w2) + zero := c.scalarApi.Zero() + + xor1 := c.api.Xor(selector9, selector3) + xor2 := c.api.Xor(selector7, selector5) + xor3 := c.api.Xor(selector10, selector4) + xor4 := c.api.Xor(selector8, selector6) + + lhs1 := c.scalarApi.Select(xor1, zero, s1v1) + lhs2 := c.scalarApi.Select(xor2, zero, r1w1) + lhs3 := c.scalarApi.Select(xor3, s2v2, zero) + lhs4 := c.scalarApi.Select(xor4, r2w2, zero) + lhs5 := c.scalarApi.Select(selector1, u1, zero) + lhs := c.scalarApi.Add(c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4)) + lhs = c.scalarApi.Add(lhs, lhs5) + + rhs1 := c.scalarApi.Select(xor1, s1v1, zero) + rhs2 := c.scalarApi.Select(xor2, r1w1, zero) + rhs3 := c.scalarApi.Select(xor3, zero, s2v2) + rhs4 := c.scalarApi.Select(xor4, zero, r2w2) + rhs5 := c.scalarApi.Select(selector1, zero, u1) + rhs := c.scalarApi.Add(c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4)) + rhs = c.scalarApi.Add(rhs, rhs5) + + c.scalarApi.AssertIsEqual(lhs, rhs) + + s1v2 := c.scalarApi.Mul(s1, v2) + s2v1 := c.scalarApi.Mul(s2, v1) + r1w2 := c.scalarApi.Mul(r1, w2) + r2w1 := c.scalarApi.Mul(r2, w1) + + xor1 = c.api.Xor(selector9, selector4) + xor2 = c.api.Xor(selector10, selector3) + xor5 := c.api.Xor(selector7, selector6) + xor6 := c.api.Xor(selector8, selector5) + + lhs1 = c.scalarApi.Select(xor1, zero, s1v2) + lhs2 = c.scalarApi.Select(xor2, zero, s2v1) + lhs3 = c.scalarApi.Select(xor5, zero, r1w2) + lhs4 = c.scalarApi.Select(xor6, zero, r2w1) + lhs5 = c.scalarApi.Select(xor3, s2v2, zero) + lhs6 := c.scalarApi.Select(xor4, r2w2, zero) + lhs7 := c.scalarApi.Select(selector2, u2, zero) + + lhs = c.scalarApi.Add(c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4)) + lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs5, lhs6)) + lhs = c.scalarApi.Add(lhs, lhs7) + + rhs1 = c.scalarApi.Select(xor1, s1v2, zero) + rhs2 = c.scalarApi.Select(xor2, s2v1, zero) + rhs3 = c.scalarApi.Select(xor5, r1w2, zero) + rhs4 = c.scalarApi.Select(xor6, r2w1, zero) + rhs5 = c.scalarApi.Select(xor3, zero, s2v2) + rhs6 := c.scalarApi.Select(xor4, zero, r2w2) + rhs7 := c.scalarApi.Select(selector2, zero, u2) + + rhs = c.scalarApi.Add(c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4)) + rhs = c.scalarApi.Add(rhs, c.scalarApi.Add(rhs5, rhs6)) + rhs = c.scalarApi.Add(rhs, rhs7) + + c.scalarApi.AssertIsEqual(lhs, rhs) // precompute -P, -Φ(P), Φ(P) var tableP, tablePhiP [2]*AffinePoint[B] @@ -1643,7 +1704,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem t1bits := c.scalarApi.ToBits(v1) t2bits := c.scalarApi.ToBits(v2) var st S - nbits := st.Modulus().BitLen()>>2 + 1 + nbits := st.Modulus().BitLen()>>2 + 5 // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) From d3d78d2e49c7772dcdd841bfecb5d89e17612333 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 26 Sep 2024 16:29:45 -0400 Subject: [PATCH 25/60] fix: increase the bound by 6 bits --- std/algebra/emulated/sw_emulated/point.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 882b037234..518e9e52b4 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1704,7 +1704,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem t1bits := c.scalarApi.ToBits(v1) t2bits := c.scalarApi.ToBits(v2) var st S - nbits := st.Modulus().BitLen()>>2 + 5 + nbits := st.Modulus().BitLen()>>2 + 6 // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) From d95bdcf9773ee737002d5ea67e94b5c292779b26 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 27 Sep 2024 21:07:10 -0400 Subject: [PATCH 26/60] fix: edge cases in fakeglv+glv --- std/algebra/emulated/sw_emulated/hints.go | 28 +++++ std/algebra/emulated/sw_emulated/point.go | 119 +++++++++++------- .../emulated/sw_emulated/point_test.go | 70 ++++++++--- 3 files changed, 158 insertions(+), 59 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index ea7e5d261c..536463b06d 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -6,6 +6,8 @@ import ( "math/big" "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" "github.com/consensys/gnark-crypto/ecc/bn254" bn_fp "github.com/consensys/gnark-crypto/ecc/bn254/fp" bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761" @@ -208,6 +210,32 @@ func scalarMulGLVG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error P.ScalarMultiplication(&P, S) P.X.BigInt(outputs[0]) P.Y.BigInt(outputs[1]) + } else if field.Cmp(bls12381_fp.Modulus()) == 0 { + var fp emparams.BLS12381Fp + var fr emparams.BLS12381Fr + PXLimbs := inputs[:fp.NbLimbs()] + PYLimbs := inputs[fp.NbLimbs() : 2*fp.NbLimbs()] + SLimbs := inputs[2*fp.NbLimbs():] + Px, Py, S := new(big.Int), new(big.Int), new(big.Int) + if err := limbs.Recompose(PXLimbs, fp.BitsPerLimb(), Px); err != nil { + return err + + } + if err := limbs.Recompose(PYLimbs, fp.BitsPerLimb(), Py); err != nil { + return err + + } + if err := limbs.Recompose(SLimbs, fr.BitsPerLimb(), S); err != nil { + return err + + } + // compute the resulting point [s]Q + var P bls12381.G1Affine + P.X.SetBigInt(Px) + P.Y.SetBigInt(Py) + P.ScalarMultiplication(&P, S) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) } else if field.Cmp(secp_fp.Modulus()) == 0 { var fp emparams.Secp256k1Fp var fr emparams.Secp256k1Fr diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 518e9e52b4..7b90051b28 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -508,10 +508,10 @@ func (c *Curve[B, S]) Mux(sel frontend.Variable, inputs ...*AffinePoint[B]) *Aff // ScalarMul computes [s]p and returns it. It doesn't modify p nor s. // This function doesn't check that the p is on the curve. See AssertIsOnCurve. // -// ScalarMul calls scalarMulFakeGLV or scalarMulGLV depending on whether an efficient endomorphism is available. +// ScalarMul calls scalarMulFakeGLV or scalarMulGLVAndFakeGLV depending on whether an efficient endomorphism is available. func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { if c.eigenvalue != nil && c.thirdRootOne != nil { - return c.scalarMulGLV(p, s, opts...) + return c.scalarMulGLVAndFakeGLV(p, s, opts...) } else { return c.scalarMulFakeGLV(p, s, opts...) @@ -1527,23 +1527,18 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } } -func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Element[S]) *AffinePoint[B] { - - // First we compute the hinted scalar mul Q = [s]P - // P coordinates are in Fp and the scalar s in Fr - // we decompose Q.X, Q.Y, s into limbs and recompose them in the hint. - var inps []frontend.Variable - inps = append(inps, P.X.Limbs...) - inps = append(inps, P.Y.Limbs...) - inps = append(inps, s.Limbs...) - point, err := c.baseApi.NewHintWithNativeInput(scalarMulGLVG1Hint, 2, inps...) +func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { + cfg, err := algopts.NewConfig(opts...) if err != nil { - panic(fmt.Sprintf("scalar mul hint: %v", err)) + panic(err) } - Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} - // We check that Q - [s]P = 0 or Q + [s](-P) = 0, so we negate P. - P = c.Neg(P) + var selector0 frontend.Variable + _s := s + if cfg.CompleteArithmetic { + selector0 = c.scalarApi.IsZero(s) + _s = c.scalarApi.Select(selector0, c.scalarApi.One(), s) + } // Checking Q + [s](-P) = 0 is equivalent to [v]Q + [v*s](-P) = 0 for some nonzero v. // @@ -1563,24 +1558,27 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // This can be done through a hinted half-GCD in the number field // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. - sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 10, s, c.eigenvalue) + sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 10, _s, c.eigenvalue) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] - // integers can be negative. So we return positive integers in the hint and negate the corresponsing points here. - signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, s, c.eigenvalue) + // Eisenstein integers real and imaginary parts can be negative. So we + // return the absolute value in the hint and negate the corresponsing points + // here when needed. + signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, _s, c.eigenvalue) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } selector1, selector2, selector3, selector4, selector5, selector6, selector7, selector8, selector9, selector10 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5], signs[6], signs[7], signs[8], signs[9] - // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 - // is equivalent to: - // s1v1+r1w1=s2v2+r2w2+u1 and - // s1v2+s2v1+r1w2+r2w1=s2v2+r2w2+u2 + // We need to check that: + // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 + // which is equivalent to checking: + // s1*v1 + r1*w1 = s2*v2 + r2*w2 + u1 and + // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 s1v1 := c.scalarApi.Mul(s1, v1) s2v2 := c.scalarApi.Mul(s2, v2) r1w1 := c.scalarApi.Mul(r1, w1) @@ -1646,6 +1644,33 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem c.scalarApi.AssertIsEqual(lhs, rhs) + // Next we compute the hinted scalar mul Q = [s]P + // P coordinates are in Fp and the scalar s in Fr + // we decompose Q.X, Q.Y, s into limbs and recompose them in the hint. + var inps []frontend.Variable + inps = append(inps, P.X.Limbs...) + inps = append(inps, P.Y.Limbs...) + inps = append(inps, s.Limbs...) + point, err := c.baseApi.NewHintWithNativeInput(scalarMulGLVG1Hint, 2, inps...) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} + + // We check that Q - [s]P = 0 or Q + [s](-P) = 0, so we negate P. + P = c.Neg(P) + + var _selector0 frontend.Variable + one := c.baseApi.One() + dummy := &AffinePoint[B]{X: *one, Y: *one} + addFn := c.Add + if cfg.CompleteArithmetic { + addFn = c.AddUnified + // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue + _selector0 = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) + Q = c.Select(_selector0, dummy, Q) + } + // precompute -P, -Φ(P), Φ(P) var tableP, tablePhiP [2]*AffinePoint[B] negPY := c.baseApi.Neg(&P.Y) @@ -1676,18 +1701,18 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // precompute P+Q, -P-Q, P-Q, -P+Q, Φ(P)+Φ(Q), -Φ(P)-Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) var tableS, tablePhiS [4]*AffinePoint[B] - tableS[0] = c.Add(tableP[0], tableQ[0]) + tableS[0] = addFn(tableP[0], tableQ[0]) tableS[1] = c.Neg(tableS[0]) - tableS[2] = c.Add(tableP[1], tableQ[0]) + tableS[2] = addFn(tableP[1], tableQ[0]) tableS[3] = c.Neg(tableS[2]) - tablePhiS[0] = c.Add(tablePhiP[0], tablePhiQ[0]) + tablePhiS[0] = addFn(tablePhiP[0], tablePhiQ[0]) tablePhiS[1] = c.Neg(tablePhiS[0]) - tablePhiS[2] = c.Add(tablePhiP[1], tablePhiQ[0]) + tablePhiS[2] = addFn(tablePhiP[1], tablePhiQ[0]) tablePhiS[3] = c.Neg(tablePhiS[2]) // we suppose that the first bits of the sub-scalars are 1 and set: // Acc = P + Q + Φ(P) + Φ(Q) - Acc := c.Add(tableS[1], tablePhiS[1]) + Acc := addFn(tableS[1], tablePhiS[1]) B1 := Acc // then we add G (the base point) to Acc to avoid incomplete additions in // the loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it @@ -1697,7 +1722,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // // g0 = G g := c.Generator() - Acc = c.Add(Acc, g) + Acc = addFn(Acc, g) s1bits := c.scalarApi.ToBits(u1) s2bits := c.scalarApi.ToBits(u2) @@ -1709,19 +1734,19 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) // B2 = +P + Q + Φ(P) - Φ(Q) - B2 := c.Add(tableS[1], tablePhiS[2]) + B2 := addFn(tableS[1], tablePhiS[2]) // B3 = +P + Q - Φ(P) + Φ(Q) - B3 := c.Add(tableS[1], tablePhiS[3]) + B3 := addFn(tableS[1], tablePhiS[3]) // B4 = +P + Q - Φ(P) - Φ(Q) - B4 := c.Add(tableS[1], tablePhiS[0]) + B4 := addFn(tableS[1], tablePhiS[0]) // B5 = +P - Q + Φ(P) + Φ(Q) - B5 := c.Add(tableS[2], tablePhiS[1]) + B5 := addFn(tableS[2], tablePhiS[1]) // B6 = +P - Q + Φ(P) - Φ(Q) - B6 := c.Add(tableS[2], tablePhiS[2]) + B6 := addFn(tableS[2], tablePhiS[2]) // B7 = +P - Q - Φ(P) + Φ(Q) - B7 := c.Add(tableS[2], tablePhiS[3]) + B7 := addFn(tableS[2], tablePhiS[3]) // B8 = +P - Q - Φ(P) - Φ(Q) - B8 := c.Add(tableS[2], tablePhiS[0]) + B8 := addFn(tableS[2], tablePhiS[0]) // B9 = -P + Q + Φ(P) + Φ(Q) B9 := c.Neg(B8) // B10 = -P + Q + Φ(P) - Φ(Q) @@ -1770,7 +1795,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem } // Acc = [2]Acc + Bi Acc = c.double(Acc) - Acc = c.Add(Acc, Bi) + Acc = addFn(Acc, Bi) } for i := nbits - 4; i > 0; i-- { @@ -1799,23 +1824,31 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem &B15.Y, &B7.Y, &B13.Y, &B5.Y, &B11.Y, &B3.Y, &B9.Y, &B1.Y, ), } - // Acc = [2]Acc + Bi - Acc = c.doubleAndAdd(Acc, Bi) + if cfg.CompleteArithmetic { + Acc = c.double(Acc) + Acc = c.AddUnified(Acc, Bi) + } else { + // Acc = [2]Acc + Bi + Acc = c.doubleAndAdd(Acc, Bi) + } } // i = 0 // subtract the P, Q, Φ(P), Φ(Q) if the first bits are 0 - tableP[0] = c.Add(tableP[0], Acc) + tableP[0] = addFn(tableP[0], Acc) Acc = c.Select(s1bits[0], Acc, tableP[0]) - tablePhiP[0] = c.Add(tablePhiP[0], Acc) + tablePhiP[0] = addFn(tablePhiP[0], Acc) Acc = c.Select(s2bits[0], Acc, tablePhiP[0]) - tableQ[0] = c.Add(tableQ[0], Acc) + tableQ[0] = addFn(tableQ[0], Acc) Acc = c.Select(t1bits[0], Acc, tableQ[0]) - tablePhiQ[0] = c.Add(tablePhiQ[0], Acc) + tablePhiQ[0] = addFn(tablePhiQ[0], Acc) Acc = c.Select(t2bits[0], Acc, tablePhiQ[0]) // Add should be now equal to [2^nbits]G gm := c.GeneratorMultiples()[nbits-1] + if cfg.CompleteArithmetic { + Acc = c.Select(c.api.Or(selector0, _selector0), &gm, Acc) + } c.AssertIsEqual(Acc, &gm) return &AffinePoint[B]{ diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index bd83dbeeb1..a03c66cac6 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -3,7 +3,6 @@ package sw_emulated import ( "crypto/elliptic" "crypto/rand" - "fmt" "math/big" "testing" @@ -21,8 +20,6 @@ import ( stark_curve "github.com/consensys/gnark-crypto/ecc/stark-curve" fr_stark "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/scs" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" @@ -2228,19 +2225,60 @@ func TestScalarMulGLVAndFakeGLV(t *testing.T) { assert.NoError(err) } -// bench -func BenchmarkScalarMulNew(b *testing.B) { - c := ScalarMulGLVAndFakeGLVTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("New: ", p.NbConstraints()) +type ScalarMulGLVAndFakeGLVEdgeCasesTest[T, S emulated.FieldParams] struct { + P, R AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulGLVAndFakeGLVEdgeCasesTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.scalarMulGLVAndFakeGLV(&c.P, &c.S, algopts.WithCompleteArithmetic()) + cr.AssertIsEqual(res, &c.R) + return nil } -func BenchmarkScalarMulOld(b *testing.B) { - c := ScalarMulTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("Old: ", p.NbConstraints()) +func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + var r fr_secp.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S secp256k1.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](s), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](0), + Y: emulated.ValueOf[emulated.Secp256k1Fp](0), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](0), + Y: emulated.ValueOf[emulated.Secp256k1Fp](0), + }, + } + err := test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // 0 * P == (0,0) + witness2 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](new(big.Int)), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](0), + Y: emulated.ValueOf[emulated.Secp256k1Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) } From f41ee7d2e32ba03429cdc55ad14e5c7b6da51752 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 30 Sep 2024 18:42:22 -0400 Subject: [PATCH 27/60] perf: save some constraints --- std/algebra/emulated/sw_emulated/point.go | 33 +---------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 7b90051b28..dc0f324e51 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1767,38 +1767,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // hence have the same X coordinates. var Bi *AffinePoint[B] - for i := nbits - 1; i >= nbits-3; i-- { - // selectorY takes values in [0,15] - selectorY := c.api.Add( - s1bits[i], - c.api.Mul(s2bits[i], 2), - c.api.Mul(t1bits[i], 4), - c.api.Mul(t2bits[i], 8), - ) - // selectorX takes values in [0,7] s.t.: - // - when selectorY < 8: selectorX = selectorY - // - when selectorY >= 8: selectorX = 15 - selectorY - selectorX := c.api.Add( - c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(t2bits[i], 2))), - c.api.Mul(t2bits[i], 15), - ) - // Bi.Y are distincts so we need a 16-to-1 multiplexer, - // but only half of the Bi.X are distinct so we need a 8-to-1. - Bi = &AffinePoint[B]{ - X: *c.baseApi.Mux(selectorX, - &B16.X, &B8.X, &B14.X, &B6.X, &B12.X, &B4.X, &B10.X, &B2.X, - ), - Y: *c.baseApi.Mux(selectorY, - &B16.Y, &B8.Y, &B14.Y, &B6.Y, &B12.Y, &B4.Y, &B10.Y, &B2.Y, - &B15.Y, &B7.Y, &B13.Y, &B5.Y, &B11.Y, &B3.Y, &B9.Y, &B1.Y, - ), - } - // Acc = [2]Acc + Bi - Acc = c.double(Acc) - Acc = addFn(Acc, Bi) - } - - for i := nbits - 4; i > 0; i-- { + for i := nbits - 1; i > 0; i-- { // selectorY takes values in [0,15] selectorY := c.api.Add( s1bits[i], From 424cf9484a7e6147c7dba4e5031996dd6f38a065 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 1 Oct 2024 14:25:19 -0400 Subject: [PATCH 28/60] fix(fake-glv): keep input point unchanged --- std/algebra/emulated/sw_emulated/hints.go | 14 ++++++++++---- std/algebra/emulated/sw_emulated/point.go | 3 --- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 536463b06d..5a0fdb890d 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -178,6 +178,8 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { }) } +// TODO @yelhousni: generalize for any supported curve. +// as it currently works only for BN254, BLS12-381, BW6-761 and Secp256k1 curves. func scalarMulGLVG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 2 { @@ -355,8 +357,10 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { r.A0.Set(&glvBasis.V1[0]) r.A1.Set(&glvBasis.V1[1]) sp := ecc.SplitScalar(inputs[0], glvBasis) - s.A0.Set(&sp[0]) - s.A1.Set(&sp[1]) + // in-circuit we check that Q - [s]P = 0 or equivalently Q + [-s]P = 0 + // so here we return -s instead of s. + s.A0.Neg(&sp[0]) + s.A1.Neg(&sp[1]) outputs[0].SetUint64(0) outputs[1].SetUint64(0) @@ -418,8 +422,10 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro r.A0.Set(&glvBasis.V1[0]) r.A1.Set(&glvBasis.V1[1]) sp := ecc.SplitScalar(inputs[0], glvBasis) - s.A0.Set(&sp[0]) - s.A1.Set(&sp[1]) + // in-circuit we check that Q - [s]P = 0 or equivalently Q + [-s]P = 0 + // so here we return -s instead of s. + s.A0.Neg(&sp[0]) + s.A1.Neg(&sp[1]) res := eisenstein.HalfGCD(&r, &s) outputs[0].Set(&res[0].A0) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index dc0f324e51..21e0ea3ad5 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1657,9 +1657,6 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem } Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} - // We check that Q - [s]P = 0 or Q + [s](-P) = 0, so we negate P. - P = c.Neg(P) - var _selector0 frontend.Variable one := c.baseApi.One() dummy := &AffinePoint[B]{X: *one, Y: *one} From d5779fb18db488ff400c9635875db8ec2cc92b9d Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Wed, 2 Oct 2024 14:07:00 -0400 Subject: [PATCH 29/60] fix: increase subscalars bounds --- std/algebra/emulated/sw_emulated/point.go | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 21e0ea3ad5..5c875a841c 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1540,14 +1540,14 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem _s = c.scalarApi.Select(selector0, c.scalarApi.One(), s) } - // Checking Q + [s](-P) = 0 is equivalent to [v]Q + [v*s](-P) = 0 for some nonzero v. + // Checking Q - [s]P = 0 is equivalent to [v]Q + [-s*v]P = 0 for some nonzero v. // // The GLV curves supported in gnark have j-invariant 0, which means the eigenvalue // of the GLV endomorphism is a primitive cube root of unity. If we write // v, s and r as Eisenstein integers we can express the check as: // - // [v1 + λ*v2]Q + [u1 + λ*u2](-P) = 0 - // [v1]Q + [v2]phi(Q) + [u1]phi(-P) + [u2]phi(-P) = 0 + // [v1 + λ*v2]Q + [u1 + λ*u2]P = 0 + // [v1]Q + [v2]phi(Q) + [u1]P + [u2]phi(P) = 0 // // where (v1 + λ*v2)*(s1 + λ*s2) = u1 + λu2 mod (r1 + λ*r2) and u1, u2, v1, v2 < r^{1/4}. // @@ -1721,12 +1721,12 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem g := c.Generator() Acc = addFn(Acc, g) - s1bits := c.scalarApi.ToBits(u1) - s2bits := c.scalarApi.ToBits(u2) - t1bits := c.scalarApi.ToBits(v1) - t2bits := c.scalarApi.ToBits(v2) + u1bits := c.scalarApi.ToBits(u1) + u2bits := c.scalarApi.ToBits(u2) + v1bits := c.scalarApi.ToBits(v1) + v2bits := c.scalarApi.ToBits(v2) var st S - nbits := st.Modulus().BitLen()>>2 + 6 + nbits := st.Modulus().BitLen()>>2 + 10 // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) @@ -1767,17 +1767,17 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem for i := nbits - 1; i > 0; i-- { // selectorY takes values in [0,15] selectorY := c.api.Add( - s1bits[i], - c.api.Mul(s2bits[i], 2), - c.api.Mul(t1bits[i], 4), - c.api.Mul(t2bits[i], 8), + u1bits[i], + c.api.Mul(u2bits[i], 2), + c.api.Mul(v1bits[i], 4), + c.api.Mul(v2bits[i], 8), ) // selectorX takes values in [0,7] s.t.: // - when selectorY < 8: selectorX = selectorY // - when selectorY >= 8: selectorX = 15 - selectorY selectorX := c.api.Add( - c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(t2bits[i], 2))), - c.api.Mul(t2bits[i], 15), + c.api.Mul(selectorY, c.api.Sub(1, c.api.Mul(v2bits[i], 2))), + c.api.Mul(v2bits[i], 15), ) // Bi.Y are distincts so we need a 16-to-1 multiplexer, // but only half of the Bi.X are distinct so we need a 8-to-1. @@ -1802,13 +1802,13 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // i = 0 // subtract the P, Q, Φ(P), Φ(Q) if the first bits are 0 tableP[0] = addFn(tableP[0], Acc) - Acc = c.Select(s1bits[0], Acc, tableP[0]) + Acc = c.Select(u1bits[0], Acc, tableP[0]) tablePhiP[0] = addFn(tablePhiP[0], Acc) - Acc = c.Select(s2bits[0], Acc, tablePhiP[0]) + Acc = c.Select(u2bits[0], Acc, tablePhiP[0]) tableQ[0] = addFn(tableQ[0], Acc) - Acc = c.Select(t1bits[0], Acc, tableQ[0]) + Acc = c.Select(v1bits[0], Acc, tableQ[0]) tablePhiQ[0] = addFn(tablePhiQ[0], Acc) - Acc = c.Select(t2bits[0], Acc, tablePhiQ[0]) + Acc = c.Select(v2bits[0], Acc, tablePhiQ[0]) // Add should be now equal to [2^nbits]G gm := c.GeneratorMultiples()[nbits-1] From 6de85e5f0b5a8630620917fbc022592991322e15 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 3 Oct 2024 17:22:29 -0400 Subject: [PATCH 30/60] perf: optimize fake-GLV scalars check --- std/algebra/emulated/sw_emulated/hints.go | 49 ++++++++------ std/algebra/emulated/sw_emulated/point.go | 80 ++++++++--------------- 2 files changed, 57 insertions(+), 72 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index 5a0fdb890d..f9dcb26501 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -351,16 +351,20 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if len(outputs) != 10 { return fmt.Errorf("expecting ten outputs") } - var r, s eisenstein.ComplexNumber glvBasis := new(ecc.Lattice) ecc.PrecomputeLattice(field, inputs[1], glvBasis) - r.A0.Set(&glvBasis.V1[0]) - r.A1.Set(&glvBasis.V1[1]) + r := eisenstein.ComplexNumber{ + A0: &glvBasis.V1[0], + A1: &glvBasis.V1[1], + } sp := ecc.SplitScalar(inputs[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.A0.Neg(&sp[0]) - s.A1.Neg(&sp[1]) + s := eisenstein.ComplexNumber{ + A0: &sp[0], + A1: &sp[1], + } + s.Neg(&s) outputs[0].SetUint64(0) outputs[1].SetUint64(0) @@ -416,28 +420,31 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if len(outputs) != 10 { return fmt.Errorf("expecting ten outputs") } - var r, s eisenstein.ComplexNumber glvBasis := new(ecc.Lattice) ecc.PrecomputeLattice(field, inputs[1], glvBasis) - r.A0.Set(&glvBasis.V1[0]) - r.A1.Set(&glvBasis.V1[1]) + r := eisenstein.ComplexNumber{ + A0: &glvBasis.V1[0], + A1: &glvBasis.V1[1], + } sp := ecc.SplitScalar(inputs[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.A0.Neg(&sp[0]) - s.A1.Neg(&sp[1]) - + s := eisenstein.ComplexNumber{ + A0: &sp[0], + A1: &sp[1], + } + s.Neg(&s) res := eisenstein.HalfGCD(&r, &s) - outputs[0].Set(&res[0].A0) - outputs[1].Set(&res[0].A1) - outputs[2].Set(&res[1].A0) - outputs[3].Set(&res[1].A1) - outputs[4].Set(&res[2].A0) - outputs[5].Set(&res[2].A1) - outputs[6].Set(&r.A0) - outputs[7].Set(&r.A1) - outputs[8].Set(&s.A0) - outputs[9].Set(&s.A1) + outputs[0].Set(res[0].A0) + outputs[1].Set(res[0].A1) + outputs[2].Set(res[1].A0) + outputs[3].Set(res[1].A1) + outputs[4].Set(res[2].A0) + outputs[5].Set(res[2].A1) + outputs[6].Set(r.A0) + outputs[7].Set(r.A1) + outputs[8].Set(s.A0) + outputs[9].Set(s.A1) if outputs[0].Sign() == -1 { outputs[0].Neg(outputs[0]) } diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 5c875a841c..a4f5af3165 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1579,68 +1579,46 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // which is equivalent to checking: // s1*v1 + r1*w1 = s2*v2 + r2*w2 + u1 and // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 + // or that: + // s1*v1 + r1*w1 + u2 = s1*v2 + s2*v1 + r1*w2 + r2*w1 + u1 s1v1 := c.scalarApi.Mul(s1, v1) - s2v2 := c.scalarApi.Mul(s2, v2) r1w1 := c.scalarApi.Mul(r1, w1) - r2w2 := c.scalarApi.Mul(r2, w2) + s1v2 := c.scalarApi.Mul(s1, v2) + s2v1 := c.scalarApi.Mul(s2, v1) + r1w2 := c.scalarApi.Mul(r1, w2) + r2w1 := c.scalarApi.Mul(r2, w1) zero := c.scalarApi.Zero() xor1 := c.api.Xor(selector9, selector3) xor2 := c.api.Xor(selector7, selector5) - xor3 := c.api.Xor(selector10, selector4) - xor4 := c.api.Xor(selector8, selector6) + xor3 := c.api.Xor(selector9, selector4) + xor4 := c.api.Xor(selector10, selector3) + xor5 := c.api.Xor(selector7, selector6) + xor6 := c.api.Xor(selector8, selector5) lhs1 := c.scalarApi.Select(xor1, zero, s1v1) lhs2 := c.scalarApi.Select(xor2, zero, r1w1) - lhs3 := c.scalarApi.Select(xor3, s2v2, zero) - lhs4 := c.scalarApi.Select(xor4, r2w2, zero) - lhs5 := c.scalarApi.Select(selector1, u1, zero) + lhs3 := c.scalarApi.Select(xor3, s1v2, zero) + lhs4 := c.scalarApi.Select(xor4, s2v1, zero) + lhs5 := c.scalarApi.Select(xor5, r1w2, zero) + lhs6 := c.scalarApi.Select(xor6, r2w1, zero) + lhs7 := c.scalarApi.Select(selector1, u1, zero) + lhs8 := c.scalarApi.Select(selector2, zero, u2) lhs := c.scalarApi.Add(c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4)) - lhs = c.scalarApi.Add(lhs, lhs5) + lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs5, lhs6)) + lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs7, lhs8)) rhs1 := c.scalarApi.Select(xor1, s1v1, zero) rhs2 := c.scalarApi.Select(xor2, r1w1, zero) - rhs3 := c.scalarApi.Select(xor3, zero, s2v2) - rhs4 := c.scalarApi.Select(xor4, zero, r2w2) - rhs5 := c.scalarApi.Select(selector1, zero, u1) + rhs3 := c.scalarApi.Select(xor3, zero, s1v2) + rhs4 := c.scalarApi.Select(xor4, zero, s2v1) + rhs5 := c.scalarApi.Select(xor5, zero, r1w2) + rhs6 := c.scalarApi.Select(xor6, zero, r2w1) + rhs7 := c.scalarApi.Select(selector1, zero, u1) + rhs8 := c.scalarApi.Select(selector2, u2, zero) rhs := c.scalarApi.Add(c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4)) - rhs = c.scalarApi.Add(rhs, rhs5) - - c.scalarApi.AssertIsEqual(lhs, rhs) - - s1v2 := c.scalarApi.Mul(s1, v2) - s2v1 := c.scalarApi.Mul(s2, v1) - r1w2 := c.scalarApi.Mul(r1, w2) - r2w1 := c.scalarApi.Mul(r2, w1) - - xor1 = c.api.Xor(selector9, selector4) - xor2 = c.api.Xor(selector10, selector3) - xor5 := c.api.Xor(selector7, selector6) - xor6 := c.api.Xor(selector8, selector5) - - lhs1 = c.scalarApi.Select(xor1, zero, s1v2) - lhs2 = c.scalarApi.Select(xor2, zero, s2v1) - lhs3 = c.scalarApi.Select(xor5, zero, r1w2) - lhs4 = c.scalarApi.Select(xor6, zero, r2w1) - lhs5 = c.scalarApi.Select(xor3, s2v2, zero) - lhs6 := c.scalarApi.Select(xor4, r2w2, zero) - lhs7 := c.scalarApi.Select(selector2, u2, zero) - - lhs = c.scalarApi.Add(c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4)) - lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs5, lhs6)) - lhs = c.scalarApi.Add(lhs, lhs7) - - rhs1 = c.scalarApi.Select(xor1, s1v2, zero) - rhs2 = c.scalarApi.Select(xor2, s2v1, zero) - rhs3 = c.scalarApi.Select(xor5, r1w2, zero) - rhs4 = c.scalarApi.Select(xor6, r2w1, zero) - rhs5 = c.scalarApi.Select(xor3, zero, s2v2) - rhs6 := c.scalarApi.Select(xor4, zero, r2w2) - rhs7 := c.scalarApi.Select(selector2, zero, u2) - - rhs = c.scalarApi.Add(c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4)) rhs = c.scalarApi.Add(rhs, c.scalarApi.Add(rhs5, rhs6)) - rhs = c.scalarApi.Add(rhs, rhs7) + rhs = c.scalarApi.Add(rhs, c.scalarApi.Add(rhs7, rhs8)) c.scalarApi.AssertIsEqual(lhs, rhs) @@ -1714,10 +1692,10 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // then we add G (the base point) to Acc to avoid incomplete additions in // the loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it // might happen that Acc==Bi or Acc==-Bi. But now we force Acc to be - // different than the stored Bi. However we need at the end to subtract - // [2^nbits]G from the result. + // different than the stored Bi. However, at the end, Acc will not be the + // point at infinity but [2^nbits]G. // - // g0 = G + // N.B.: Acc cannot be equal to G, otherwise this means G = -Φ²([s+1]P) g := c.Generator() Acc = addFn(Acc, g) @@ -1810,7 +1788,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem tablePhiQ[0] = addFn(tablePhiQ[0], Acc) Acc = c.Select(v2bits[0], Acc, tablePhiQ[0]) - // Add should be now equal to [2^nbits]G + // Acc should be now equal to [2^nbits]G gm := c.GeneratorMultiples()[nbits-1] if cfg.CompleteArithmetic { Acc = c.Select(c.api.Or(selector0, _selector0), &gm, Acc) From d90a599ca9808436963de8f156a8a15aa4d94be4 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 4 Oct 2024 12:55:22 -0400 Subject: [PATCH 31/60] docs: add comments --- std/algebra/emulated/sw_emulated/point.go | 65 ++++++++++++++++++----- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index a4f5af3165..e4df0e2e18 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1527,12 +1527,22 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } } +// scalarMulGLVAndFakeGLV computes [s]P and returns it. It doesn't modify P nor s. +// It implements the "GLV + fake GLV" explained in [ethresear.ch/fake-GLV]. +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [ethresear.ch/fake-GLV]: https://ethresear.ch/t/fake-glv-you-dont-need-an-efficient-endomorphism-to-implement-glv-like-scalar-multiplication-in-snark-circuits/20394 +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { cfg, err := algopts.NewConfig(opts...) if err != nil { panic(err) } + // handle zero-scalar var selector0 frontend.Variable _s := s if cfg.CompleteArithmetic { @@ -1540,6 +1550,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem _s = c.scalarApi.Select(selector0, c.scalarApi.One(), s) } + // Instead of computing [s]P=Q, we check that Q-[s]P == 0. // Checking Q - [s]P = 0 is equivalent to [v]Q + [-s*v]P = 0 for some nonzero v. // // The GLV curves supported in gnark have j-invariant 0, which means the eigenvalue @@ -1549,12 +1560,13 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // [v1 + λ*v2]Q + [u1 + λ*u2]P = 0 // [v1]Q + [v2]phi(Q) + [u1]P + [u2]phi(P) = 0 // - // where (v1 + λ*v2)*(s1 + λ*s2) = u1 + λu2 mod (r1 + λ*r2) and u1, u2, v1, v2 < r^{1/4}. + // where (v1 + λ*v2)*(s1 + λ*s2) = u1 + λu2 mod (r1 + λ*r2) + // and u1, u2, v1, v2 < r^{1/4} (up to a constant factor). // // This can be done as follows: - // 1. decompose s into s1 + λ*s2 mod r s.t. s1, s2 < sqrt(r) (hinted GLV decomposition). + // 1. decompose s into s1 + λ*s2 mod r s.t. s1, s2 < sqrt(r) (hinted classical GLV decomposition). // 2. decompose r into r1 + λ*r2 s.t. r1, r2 < sqrt(r) (hardcoded half-GCD of λ mod r). - // 3. find u1, u2, v1, v2 < r^{1/4} s.t. (v1 + λ*v2)*(s1 + λ*s2) = (u1 + λ*u2) mod (r1 + λ*r2). + // 3. find u1, u2, v1, v2 < c*r^{1/4} s.t. (v1 + λ*v2)*(s1 + λ*s2) = (u1 + λ*u2) mod (r1 + λ*r2). // This can be done through a hinted half-GCD in the number field // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. @@ -1566,8 +1578,8 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] // Eisenstein integers real and imaginary parts can be negative. So we - // return the absolute value in the hint and negate the corresponsing points - // here when needed. + // return the absolute value in the hint and negate the corresponsing + // points here when needed. signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, _s, c.eigenvalue) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) @@ -1581,6 +1593,9 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 // or that: // s1*v1 + r1*w1 + u2 = s1*v2 + s2*v1 + r1*w2 + r2*w1 + u1 + // + // Since all these values can be negative, we gather all positive values + // either in the lhs or rhs and check equality. s1v1 := c.scalarApi.Mul(s1, v1) r1w1 := c.scalarApi.Mul(r1, w1) s1v2 := c.scalarApi.Mul(s1, v2) @@ -1604,9 +1619,18 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem lhs6 := c.scalarApi.Select(xor6, r2w1, zero) lhs7 := c.scalarApi.Select(selector1, u1, zero) lhs8 := c.scalarApi.Select(selector2, zero, u2) - lhs := c.scalarApi.Add(c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4)) - lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs5, lhs6)) - lhs = c.scalarApi.Add(lhs, c.scalarApi.Add(lhs7, lhs8)) + lhs := c.scalarApi.Add( + c.scalarApi.Add(lhs1, lhs2), + c.scalarApi.Add(lhs3, lhs4), + ) + lhs = c.scalarApi.Add( + lhs, + c.scalarApi.Add(lhs5, lhs6), + ) + lhs = c.scalarApi.Add( + lhs, + c.scalarApi.Add(lhs7, lhs8), + ) rhs1 := c.scalarApi.Select(xor1, s1v1, zero) rhs2 := c.scalarApi.Select(xor2, r1w1, zero) @@ -1616,9 +1640,18 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem rhs6 := c.scalarApi.Select(xor6, zero, r2w1) rhs7 := c.scalarApi.Select(selector1, zero, u1) rhs8 := c.scalarApi.Select(selector2, u2, zero) - rhs := c.scalarApi.Add(c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4)) - rhs = c.scalarApi.Add(rhs, c.scalarApi.Add(rhs5, rhs6)) - rhs = c.scalarApi.Add(rhs, c.scalarApi.Add(rhs7, rhs8)) + rhs := c.scalarApi.Add( + c.scalarApi.Add(rhs1, rhs2), + c.scalarApi.Add(rhs3, rhs4), + ) + rhs = c.scalarApi.Add( + rhs, + c.scalarApi.Add(rhs5, rhs6), + ) + rhs = c.scalarApi.Add( + rhs, + c.scalarApi.Add(rhs7, rhs8), + ) c.scalarApi.AssertIsEqual(lhs, rhs) @@ -1635,6 +1668,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem } Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} + // handle (0,0)-point var _selector0 frontend.Variable one := c.baseApi.One() dummy := &AffinePoint[B]{X: *one, Y: *one} @@ -1674,7 +1708,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem } tablePhiQ[0] = c.Neg(tablePhiQ[1]) - // precompute P+Q, -P-Q, P-Q, -P+Q, Φ(P)+Φ(Q), -Φ(P)-Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) + // precompute -P-Q, P+Q, P-Q, -P+Q, -Φ(P)-Φ(Q), Φ(P)+Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) var tableS, tablePhiS [4]*AffinePoint[B] tableS[0] = addFn(tableP[0], tableQ[0]) tableS[1] = c.Neg(tableS[0]) @@ -1699,12 +1733,15 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem g := c.Generator() Acc = addFn(Acc, g) + // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). + // We prove that the factor is 760 * sqrt(2), + // so we need to add 10 bits to r^{1/4}.nbits(). + var st S + nbits := st.Modulus().BitLen()>>2 + 10 u1bits := c.scalarApi.ToBits(u1) u2bits := c.scalarApi.ToBits(u2) v1bits := c.scalarApi.ToBits(v1) v2bits := c.scalarApi.ToBits(v2) - var st S - nbits := st.Modulus().BitLen()>>2 + 10 // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) From c4949fc9f12825e6e72b6c7d612aa463c791c2c7 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 4 Oct 2024 13:37:19 -0400 Subject: [PATCH 32/60] perf: optimize zero-point edge case --- std/algebra/emulated/sw_emulated/point.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index e4df0e2e18..bdd27fc338 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1670,13 +1670,12 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // handle (0,0)-point var _selector0 frontend.Variable - one := c.baseApi.One() - dummy := &AffinePoint[B]{X: *one, Y: *one} addFn := c.Add if cfg.CompleteArithmetic { addFn = c.AddUnified // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue _selector0 = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) + dummy := &AffinePoint[B]{X: *c.baseApi.One(), Y: *c.baseApi.Zero()} Q = c.Select(_selector0, dummy, Q) } @@ -1805,13 +1804,8 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem &B15.Y, &B7.Y, &B13.Y, &B5.Y, &B11.Y, &B3.Y, &B9.Y, &B1.Y, ), } - if cfg.CompleteArithmetic { - Acc = c.double(Acc) - Acc = c.AddUnified(Acc, Bi) - } else { - // Acc = [2]Acc + Bi - Acc = c.doubleAndAdd(Acc, Bi) - } + // Acc = [2]Acc + Bi + Acc = c.doubleAndAdd(Acc, Bi) } // i = 0 From e18d308f4e8e32453397146b88ce912f988da413 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 4 Oct 2024 16:53:38 -0400 Subject: [PATCH 33/60] feat: 4-dim fake-GLV in native --- std/algebra/native/sw_bls12377/g1.go | 238 ++++++++++++++++++++++ std/algebra/native/sw_bls12377/g1_test.go | 37 ++++ std/algebra/native/sw_bls12377/hints.go | 155 ++++++++++++++ 3 files changed, 430 insertions(+) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 4961063ec3..efcd8d7838 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -17,6 +17,7 @@ limitations under the License. package sw_bls12377 import ( + "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -671,3 +672,240 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] return P } + +// fake-GLV +func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + cc := getInnerCurveConfig(api.Compiler().Field()) + + // handle zero-scalar + var selector0 frontend.Variable + _s := s + if cfg.CompleteArithmetic { + selector0 = api.IsZero(s) + _s = api.Select(selector0, 1, s) + } + + // Instead of computing [s]P=Q, we check that Q-[s]P == 0. + // Checking Q - [s]P = 0 is equivalent to [v]Q + [-s*v]P = 0 for some nonzero v. + // + // The GLV curves supported in gnark have j-invariant 0, which means the eigenvalue + // of the GLV endomorphism is a primitive cube root of unity. If we write + // v, s and r as Eisenstein integers we can express the check as: + // + // [v1 + λ*v2]Q + [u1 + λ*u2]P = 0 + // [v1]Q + [v2]phi(Q) + [u1]P + [u2]phi(P) = 0 + // + // where (v1 + λ*v2)*(s1 + λ*s2) = u1 + λu2 mod (r1 + λ*r2) + // and u1, u2, v1, v2 < r^{1/4} (up to a constant factor). + // + // This can be done as follows: + // 1. decompose s into s1 + λ*s2 mod r s.t. s1, s2 < sqrt(r) (hinted classical GLV decomposition). + // 2. decompose r into r1 + λ*r2 s.t. r1, r2 < sqrt(r) (hardcoded half-GCD of λ mod r). + // 3. find u1, u2, v1, v2 < c*r^{1/4} s.t. (v1 + λ*v2)*(s1 + λ*s2) = (u1 + λ*u2) mod (r1 + λ*r2). + // This can be done through a hinted half-GCD in the number field + // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of + // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. + sd, err := api.NewHint(halfGCDEisenstein, 10, _s, cc.lambda) + if err != nil { + panic(fmt.Sprintf("halfGCDEisenstein hint: %v", err)) + } + u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] + + // Eisenstein integers real and imaginary parts can be negative. So we + // return the absolute value in the hint and negate the corresponsing + // points here when needed. + signs, err := api.NewHint(halfGCDEisensteinSigns, 10, _s, cc.lambda) + if err != nil { + panic(fmt.Sprintf("halfGCDEisensteinSigns hint: %v", err)) + } + selector1, selector2, selector3, selector4, selector5, selector6, selector7, selector8, selector9, selector10 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5], signs[6], signs[7], signs[8], signs[9] + + // We need to check that: + // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 + // which is equivalent to checking: + // s1*v1 + r1*w1 = s2*v2 + r2*w2 + u1 and + // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 + // or that: + // s1*v1 + r1*w1 + u2 = s1*v2 + s2*v1 + r1*w2 + r2*w1 + u1 + // + // Since all these values can be negative, we gather all positive values + // either in the lhs or rhs and check equality. + s1v1 := api.Mul(s1, v1) + r1w1 := api.Mul(r1, w1) + s1v2 := api.Mul(s1, v2) + s2v1 := api.Mul(s2, v1) + r1w2 := api.Mul(r1, w2) + r2w1 := api.Mul(r2, w1) + + xor1 := api.Xor(selector9, selector3) + xor2 := api.Xor(selector7, selector5) + xor3 := api.Xor(selector9, selector4) + xor4 := api.Xor(selector10, selector3) + xor5 := api.Xor(selector7, selector6) + xor6 := api.Xor(selector8, selector5) + + lhs1 := api.Select(xor1, 0, s1v1) + lhs2 := api.Select(xor2, 0, r1w1) + lhs3 := api.Select(xor3, s1v2, 0) + lhs4 := api.Select(xor4, s2v1, 0) + lhs5 := api.Select(xor5, r1w2, 0) + lhs6 := api.Select(xor6, r2w1, 0) + lhs7 := api.Select(selector1, u1, 0) + lhs8 := api.Select(selector2, 0, u2) + lhs := api.Add( + api.Add(lhs1, lhs2), + api.Add(lhs3, lhs4), + ) + lhs = api.Add( + lhs, + api.Add(lhs5, lhs6), + ) + lhs = api.Add( + lhs, + api.Add(lhs7, lhs8), + ) + + rhs1 := api.Select(xor1, s1v1, 0) + rhs2 := api.Select(xor2, r1w1, 0) + rhs3 := api.Select(xor3, 0, s1v2) + rhs4 := api.Select(xor4, 0, s2v1) + rhs5 := api.Select(xor5, 0, r1w2) + rhs6 := api.Select(xor6, 0, r2w1) + rhs7 := api.Select(selector1, 0, u1) + rhs8 := api.Select(selector2, u2, 0) + rhs := api.Add( + api.Add(rhs1, rhs2), + api.Add(rhs3, rhs4), + ) + rhs = api.Add( + rhs, + api.Add(rhs5, rhs6), + ) + rhs = api.Add( + rhs, + api.Add(rhs7, rhs8), + ) + + api.AssertIsEqual(lhs, rhs) + + // Next we compute the hinted scalar mul Q = [s]P + point, err := api.NewHint(scalarMulGLVG1Hint, 2, P.X, P.Y, s) + if err != nil { + panic(fmt.Sprintf("scalar mul hint: %v", err)) + } + Q := G1Affine{X: point[0], Y: point[1]} + + // handle (0,0)-point + var _selector0 frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue + _selector0 = api.And(api.IsZero(&Q.X), api.IsZero(&Q.Y)) + dummy := G1Affine{X: 1, Y: 0} + Q.Select(api, _selector0, dummy, Q) + } + + // precompute -P, -Φ(P), Φ(P) + var tableP, tablePhiP [2]G1Affine + negPY := api.Neg(P.Y) + tableP[1] = G1Affine{ + X: P.X, + Y: api.Select(selector1, negPY, P.Y), + } + tableP[0].Neg(api, tableP[1]) + tablePhiP[1] = G1Affine{ + X: api.Mul(P.X, cc.thirdRootOne1), + Y: api.Select(selector2, negPY, P.Y), + } + tablePhiP[0].Neg(api, tablePhiP[1]) + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [2]G1Affine + negQY := api.Neg(Q.Y) + tableQ[1] = G1Affine{ + X: Q.X, + Y: api.Select(selector3, negQY, Q.Y), + } + tableQ[0].Neg(api, tableQ[1]) + tablePhiQ[1] = G1Affine{ + X: api.Mul(Q.X, cc.thirdRootOne1), + Y: api.Select(selector4, negQY, Q.Y), + } + tablePhiQ[0].Neg(api, tablePhiQ[1]) + + // precompute -P-Q, P+Q, P-Q, -P+Q, -Φ(P)-Φ(Q), Φ(P)+Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) + var tableS, tablePhiS [4]G1Affine + tableS[0] = tableP[0] + tableS[0].AddAssign(api, tableQ[0]) + tableS[1].Neg(api, tableS[0]) + tableS[2] = tableP[1] + tableS[2].AddAssign(api, tableQ[0]) + tableS[3].Neg(api, tableS[2]) + tablePhiS[0] = tablePhiP[0] + tablePhiS[0].AddAssign(api, tablePhiQ[0]) + tablePhiS[1].Neg(api, tablePhiS[0]) + tablePhiS[2] = tablePhiP[1] + tablePhiS[2].AddAssign(api, tablePhiQ[0]) + tablePhiS[3].Neg(api, tablePhiS[2]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = P + Q + Φ(P) + Φ(Q) + Acc := tableS[1] + Acc.AddAssign(api, tablePhiS[1]) + // When doing doubleAndAdd(Acc, B) as (Acc+B)+Acc it might happen that + // Acc==B or -B. So we add the point H=(0,1) on BLS12-377 of order 2 to it + // to avoid incomplete additions in the loop by forcing Acc to be different + // than the stored B. Normally, the point H should be "killed out" by the + // first doubling in the loop and the result will remain unchanged. + // However, we are using affine coordinates that do not encode the infinity + // point. Given the affine formulae, doubling (0,1) results in (0,-1). + // Since the loop size N=nbits-1 is odd the result at the end should be + // [2^N]H = H = (0,1). + H := G1Affine{X: 0, Y: 1} + Acc.AddAssign(api, H) + + // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). + // We prove that the factor is 760 * sqrt(2), + // so we need to add 10 bits to r^{1/4}.nbits(). + nbits := cc.lambda.BitLen()>>1 + 10 + u1bits := api.ToBinary(u1, nbits) + u2bits := api.ToBinary(u2, nbits) + v1bits := api.ToBinary(v1, nbits) + v2bits := api.ToBinary(v2, nbits) + + var B G1Affine + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(u1bits[i], v1bits[i]), tableS[2].X, tableS[0].X) + B.Y = api.Lookup2(u1bits[i], v1bits[i], tableS[0].Y, tableS[2].Y, tableS[3].Y, tableS[1].Y) + Acc.DoubleAndAdd(api, &Acc, &B) + B.X = api.Select(api.Xor(u2bits[i], v2bits[i]), tablePhiS[2].X, tablePhiS[0].X) + B.Y = api.Lookup2(u2bits[i], v2bits[i], tablePhiS[0].Y, tablePhiS[2].Y, tablePhiS[3].Y, tablePhiS[1].Y) + Acc.AddAssign(api, B) + } + + // i = 0 + // subtract the P, Q, Φ(P), Φ(Q) if the first bits are 0 + tableP[0].AddAssign(api, Acc) + Acc.Select(api, u1bits[0], Acc, tableP[0]) + tablePhiP[0].AddAssign(api, Acc) + Acc.Select(api, u2bits[0], Acc, tablePhiP[0]) + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, v1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddAssign(api, Acc) + Acc.Select(api, v2bits[0], Acc, tablePhiQ[0]) + + // Acc should be now equal to H=(0,1) + gm := G1Affine{X: 0, Y: 1} + if cfg.CompleteArithmetic { + Acc.Select(api, api.Or(selector0, _selector0), gm, Acc) + } + Acc.AssertIsEqual(api, gm) + + R.X = point[0] + R.Y = point[1] + + return R +} diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index f8b4287ed9..b8dce023f8 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -933,3 +933,40 @@ func TestMultiScalarMulFolded(t *testing.T) { }, &assignment, ecc.BW6_761.ScalarField()) assert.NoError(err) } + +// fake GLV +type scalarMulGLVAndFakeGLV struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *scalarMulGLVAndFakeGLV) Define(api frontend.API) error { + expected := G1Affine{} + expected.scalarMulGLVAndFakeGLV(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestScalarMulG1GLVAndFakeGLV(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c bls12377.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness scalarMulGLVAndFakeGLV + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index d59ef955ef..3b1cfe509b 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -5,6 +5,8 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" + bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" + "github.com/consensys/gnark-crypto/field/eisenstein" "github.com/consensys/gnark/constraint/solver" ) @@ -13,6 +15,9 @@ func GetHints() []solver.Hint { decomposeScalarG1, decomposeScalarG1Simple, decomposeScalarG2, + scalarMulGLVG1Hint, + halfGCDEisenstein, + halfGCDEisensteinSigns, } } @@ -88,3 +93,153 @@ func decomposeScalarG2(scalarField *big.Int, inputs []*big.Int, outputs []*big.I return nil } + +func scalarMulGLVG1Hint(scalarField *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 3 { + return fmt.Errorf("expecting three inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + + // compute the resulting point [s]Q + var P bls12377.G1Affine + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + P.ScalarMultiplication(&P, inputs[2]) + P.X.BigInt(outputs[0]) + P.Y.BigInt(outputs[1]) + return nil +} + +func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two input") + } + if len(outputs) != 10 { + return fmt.Errorf("expecting ten outputs") + } + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(mod, inputs[1], glvBasis) + r := eisenstein.ComplexNumber{ + A0: &glvBasis.V1[0], + A1: &glvBasis.V1[1], + } + sp := ecc.SplitScalar(inputs[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) + res := eisenstein.HalfGCD(&r, &s) + outputs[0].Set(res[0].A0) + outputs[1].Set(res[0].A1) + outputs[2].Set(res[1].A0) + outputs[3].Set(res[1].A1) + outputs[4].Set(res[2].A0) + outputs[5].Set(res[2].A1) + outputs[6].Set(r.A0) + outputs[7].Set(r.A1) + outputs[8].Set(s.A0) + outputs[9].Set(s.A1) + if outputs[0].Sign() == -1 { + outputs[0].Neg(outputs[0]) + } + if outputs[1].Sign() == -1 { + outputs[1].Neg(outputs[1]) + } + if outputs[2].Sign() == -1 { + outputs[2].Neg(outputs[2]) + } + if outputs[3].Sign() == -1 { + outputs[3].Neg(outputs[3]) + } + if outputs[4].Sign() == -1 { + outputs[4].Neg(outputs[4]) + } + if outputs[5].Sign() == -1 { + outputs[5].Neg(outputs[5]) + } + if outputs[6].Sign() == -1 { + outputs[6].Neg(outputs[6]) + } + if outputs[7].Sign() == -1 { + outputs[7].Neg(outputs[7]) + } + if outputs[8].Sign() == -1 { + outputs[8].Neg(outputs[8]) + } + if outputs[9].Sign() == -1 { + outputs[9].Neg(outputs[9]) + } + return nil +} + +func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two input") + } + if len(outputs) != 10 { + return fmt.Errorf("expecting ten outputs") + } + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(mod, inputs[1], glvBasis) + r := eisenstein.ComplexNumber{ + A0: &glvBasis.V1[0], + A1: &glvBasis.V1[1], + } + sp := ecc.SplitScalar(inputs[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) + + outputs[0].SetUint64(0) + outputs[1].SetUint64(0) + outputs[2].SetUint64(0) + outputs[3].SetUint64(0) + outputs[4].SetUint64(0) + outputs[5].SetUint64(0) + outputs[6].SetUint64(0) + outputs[7].SetUint64(0) + outputs[8].SetUint64(0) + outputs[9].SetUint64(0) + res := eisenstein.HalfGCD(&r, &s) + if res[0].A0.Sign() == -1 { + outputs[0].SetUint64(1) + } + if res[0].A1.Sign() == -1 { + outputs[1].SetUint64(1) + } + if res[1].A0.Sign() == -1 { + outputs[2].SetUint64(1) + } + if res[1].A1.Sign() == -1 { + outputs[3].SetUint64(1) + } + if res[2].A0.Sign() == -1 { + outputs[4].SetUint64(1) + } + if res[2].A1.Sign() == -1 { + outputs[5].SetUint64(1) + } + if r.A0.Sign() == -1 { + outputs[6].SetUint64(1) + } + if r.A1.Sign() == -1 { + outputs[7].SetUint64(1) + } + if s.A0.Sign() == -1 { + outputs[8].SetUint64(1) + } + if s.A1.Sign() == -1 { + outputs[9].SetUint64(1) + } + + return nil +} From 05a044256c79369854beb45191b4f1cf64ac3ac6 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 8 Oct 2024 16:45:56 -0400 Subject: [PATCH 34/60] perf: optimize joint scalar mul glv --- std/algebra/native/sw_bls12377/g1.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index efcd8d7838..6db8bb2ffb 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -482,24 +482,24 @@ func (P *G1Affine) jointScalarMul(api frontend.API, Q, R G1Affine, s, t frontend func (P *G1Affine) jointScalarMulUnsafe(api frontend.API, Q, R G1Affine, s, t frontend.Variable) *G1Affine { cc := getInnerCurveConfig(api.Compiler().Field()) - sd, err := api.Compiler().NewHint(decomposeScalarG1, 3, s) + sd, err := api.Compiler().NewHint(decomposeScalarG1Simple, 2, s) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } s1, s2 := sd[0], sd[1] - td, err := api.Compiler().NewHint(decomposeScalarG1, 3, t) + td, err := api.Compiler().NewHint(decomposeScalarG1Simple, 2, t) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } t1, t2 := td[0], td[1] - api.AssertIsEqual(api.Add(s1, api.Mul(s2, cc.lambda)), api.Add(s, api.Mul(cc.fr, sd[2]))) - api.AssertIsEqual(api.Add(t1, api.Mul(t2, cc.lambda)), api.Add(t, api.Mul(cc.fr, td[2]))) + api.AssertIsEqual(api.Add(s1, api.Mul(s2, cc.lambda)), s) + api.AssertIsEqual(api.Add(t1, api.Mul(t2, cc.lambda)), t) - nbits := cc.lambda.BitLen() + 1 + nbits := cc.lambda.BitLen() s1bits := api.ToBinary(s1, nbits) s2bits := api.ToBinary(s2, nbits) From 2cb22be893b1043dc1d25e8d7bbdf284ca5bf68b Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Wed, 9 Oct 2024 15:46:18 -0400 Subject: [PATCH 35/60] perf(native/bls377): scratch some scs in 4-dim fake GLV --- std/algebra/native/sw_bls12377/g1.go | 43 +++++++++++-------------- std/algebra/native/sw_bls12377/hints.go | 23 +++---------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 6db8bb2ffb..0b38709c63 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -713,16 +713,19 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte if err != nil { panic(fmt.Sprintf("halfGCDEisenstein hint: %v", err)) } - u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] + u1, u2, v1, v2, w1, w2, _, _, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] + // r is fixed and is equal to 91893752504881257701523279626832445440 - ω + var r1 big.Int + r1.SetString("91893752504881257701523279626832445440", 10) // Eisenstein integers real and imaginary parts can be negative. So we // return the absolute value in the hint and negate the corresponsing // points here when needed. - signs, err := api.NewHint(halfGCDEisensteinSigns, 10, _s, cc.lambda) + signs, err := api.NewHint(halfGCDEisensteinSigns, 6, _s, cc.lambda) if err != nil { panic(fmt.Sprintf("halfGCDEisensteinSigns hint: %v", err)) } - selector1, selector2, selector3, selector4, selector5, selector6, selector7, selector8, selector9, selector10 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5], signs[6], signs[7], signs[8], signs[9] + selector1, selector2, selector3, selector4, selector5, selector6 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5] // We need to check that: // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 @@ -739,21 +742,13 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte s1v2 := api.Mul(s1, v2) s2v1 := api.Mul(s2, v1) r1w2 := api.Mul(r1, w2) - r2w1 := api.Mul(r2, w1) - - xor1 := api.Xor(selector9, selector3) - xor2 := api.Xor(selector7, selector5) - xor3 := api.Xor(selector9, selector4) - xor4 := api.Xor(selector10, selector3) - xor5 := api.Xor(selector7, selector6) - xor6 := api.Xor(selector8, selector5) - - lhs1 := api.Select(xor1, 0, s1v1) - lhs2 := api.Select(xor2, 0, r1w1) - lhs3 := api.Select(xor3, s1v2, 0) - lhs4 := api.Select(xor4, s2v1, 0) - lhs5 := api.Select(xor5, r1w2, 0) - lhs6 := api.Select(xor6, r2w1, 0) + + lhs1 := api.Select(selector3, s1v1, 0) + lhs2 := api.Select(selector5, 0, r1w1) + lhs3 := api.Select(selector4, 0, s1v2) + lhs4 := api.Select(selector3, 0, s2v1) + lhs5 := api.Select(selector6, r1w2, 0) + lhs6 := api.Select(selector5, 0, w1) lhs7 := api.Select(selector1, u1, 0) lhs8 := api.Select(selector2, 0, u2) lhs := api.Add( @@ -769,12 +764,12 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte api.Add(lhs7, lhs8), ) - rhs1 := api.Select(xor1, s1v1, 0) - rhs2 := api.Select(xor2, r1w1, 0) - rhs3 := api.Select(xor3, 0, s1v2) - rhs4 := api.Select(xor4, 0, s2v1) - rhs5 := api.Select(xor5, 0, r1w2) - rhs6 := api.Select(xor6, 0, r2w1) + rhs1 := api.Select(selector3, 0, s1v1) + rhs2 := api.Select(selector5, r1w1, 0) + rhs3 := api.Select(selector4, s1v2, 0) + rhs4 := api.Select(selector3, s2v1, 0) + rhs5 := api.Select(selector6, 0, r1w2) + rhs6 := api.Select(selector5, w1, 0) rhs7 := api.Select(selector1, 0, u1) rhs8 := api.Select(selector2, u2, 0) rhs := api.Add( diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index 3b1cfe509b..da3f71a054 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -125,9 +125,11 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro A0: &glvBasis.V1[0], A1: &glvBasis.V1[1], } + // r = 91893752504881257701523279626832445440 - ω sp := ecc.SplitScalar(inputs[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.A0 and s.A1 are always positive. s := eisenstein.ComplexNumber{ A0: &sp[0], A1: &sp[1], @@ -181,11 +183,12 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 10 { - return fmt.Errorf("expecting ten outputs") + if len(outputs) != 6 { + return fmt.Errorf("expecting six outputs") } glvBasis := new(ecc.Lattice) ecc.PrecomputeLattice(mod, inputs[1], glvBasis) + // r = 91893752504881257701523279626832445440 - ω r := eisenstein.ComplexNumber{ A0: &glvBasis.V1[0], A1: &glvBasis.V1[1], @@ -205,10 +208,6 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { outputs[3].SetUint64(0) outputs[4].SetUint64(0) outputs[5].SetUint64(0) - outputs[6].SetUint64(0) - outputs[7].SetUint64(0) - outputs[8].SetUint64(0) - outputs[9].SetUint64(0) res := eisenstein.HalfGCD(&r, &s) if res[0].A0.Sign() == -1 { outputs[0].SetUint64(1) @@ -228,18 +227,6 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if res[2].A1.Sign() == -1 { outputs[5].SetUint64(1) } - if r.A0.Sign() == -1 { - outputs[6].SetUint64(1) - } - if r.A1.Sign() == -1 { - outputs[7].SetUint64(1) - } - if s.A0.Sign() == -1 { - outputs[8].SetUint64(1) - } - if s.A1.Sign() == -1 { - outputs[9].SetUint64(1) - } return nil } From 17113e9cbb6c69f34272d53602f0abd09844621e Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 10 Oct 2024 10:06:05 -0400 Subject: [PATCH 36/60] chore: up gnark-crypto --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index eb53a39b14..ae318b01bd 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.14.2 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.13 + github.com/consensys/bavard v0.1.22 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.14.1-0.20240909142611-e6b99e74cec1 + github.com/consensys/gnark-crypto v0.14.1-0.20241010074759-441b06ec4070 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 diff --git a/go.sum b/go.sum index ce9397ffbf..6b5a3a6ead 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.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/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.14.1-0.20240909142611-e6b99e74cec1 h1:xsKDyn8I+lnrLFsJL6bbDavs7xTrmKeQE/xe/htVt3I= -github.com/consensys/gnark-crypto v0.14.1-0.20240909142611-e6b99e74cec1/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= +github.com/consensys/gnark-crypto v0.14.1-0.20241010074759-441b06ec4070 h1:SYLmdvFCtpsuHzy8NOe5m1YWyv7MVnZ7jiwXvQobssI= +github.com/consensys/gnark-crypto v0.14.1-0.20241010074759-441b06ec4070/go.mod h1:F/hJyWBcTr1sWeifAKfEN3aVb3G4U5zheEC8IbWQun4= 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= From 73a67e05f76b246c67d257b31e596ed4bfdb9ddc Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 11 Oct 2024 16:00:36 -0400 Subject: [PATCH 37/60] test(bls12-377): test edge case of fake glv --- go.mod | 4 +-- go.sum | 8 +++--- std/algebra/native/sw_bls12377/g1_test.go | 34 +++++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 062037f1de..c75380e104 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.14.2 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.15 + github.com/consensys/bavard v0.1.22 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.14.1-0.20241002214024-485db50997ef + github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3 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 diff --git a/go.sum b/go.sum index a705f27256..1960733dee 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.15 h1:fxv2mg1afRMJvZgpwEgLmyr2MsQwaAYcyKf31UBHzw4= -github.com/consensys/bavard v0.1.15/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/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.14.1-0.20241002214024-485db50997ef h1:ZK7HNEFMkTslyLKLbWpDATuZYUWbOcjm8yl50rL9XdQ= -github.com/consensys/gnark-crypto v0.14.1-0.20241002214024-485db50997ef/go.mod h1:AL8vs/7MyZ0P93tcNDkUWVwf2rWLUGFUP/1iqiF7h4E= +github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3 h1:jVatckGR1s3OHs4QnGsppX+w2P3eedlWxi7ZFq56rjA= +github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3/go.mod h1:F/hJyWBcTr1sWeifAKfEN3aVb3G4U5zheEC8IbWQun4= 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= diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index b8dce023f8..6decaaa749 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -970,3 +970,37 @@ func TestScalarMulG1GLVAndFakeGLV(t *testing.T) { assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } + +type scalarMulGLVAndFakeGLVEdgeCases struct { + A G1Affine + R frontend.Variable +} + +func (circuit *scalarMulGLVAndFakeGLVEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + infinity := G1Affine{X: 0, Y: 0} + expected1.varScalarMul(api, circuit.A, 0, algopts.WithCompleteArithmetic()) + expected2.varScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, infinity) + expected2.AssertIsEqual(api, infinity) + return nil +} + +func TestScalarMulG1GLVAndFakeGLVEdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + var a bls12377.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness scalarMulGLVAndFakeGLVEdgeCases + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} From d8b1a228c1bf8c6e9b48cf59dfa3b164f625d1c8 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 14 Oct 2024 13:54:28 -0400 Subject: [PATCH 38/60] fix(bls12-377): fix edge case of fake glv --- std/algebra/native/sw_bls12377/g1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 0b38709c63..a2e1f45704 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -798,7 +798,7 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte var _selector0 frontend.Variable if cfg.CompleteArithmetic { // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue - _selector0 = api.And(api.IsZero(&Q.X), api.IsZero(&Q.Y)) + _selector0 = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) dummy := G1Affine{X: 1, Y: 0} Q.Select(api, _selector0, dummy, Q) } From 6940810a5649a70819740c6f041d724241ed1821 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 14 Oct 2024 17:34:08 -0400 Subject: [PATCH 39/60] ci: make linter happy --- std/algebra/emulated/sw_emulated/point.go | 2 +- std/algebra/native/sw_bls12377/g1.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index bdd27fc338..00888b1a20 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1578,7 +1578,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] // Eisenstein integers real and imaginary parts can be negative. So we - // return the absolute value in the hint and negate the corresponsing + // return the absolute value in the hint and negate the corresponding // points here when needed. signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, _s, c.eigenvalue) if err != nil { diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index a2e1f45704..714e9a713f 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -719,7 +719,7 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte r1.SetString("91893752504881257701523279626832445440", 10) // Eisenstein integers real and imaginary parts can be negative. So we - // return the absolute value in the hint and negate the corresponsing + // return the absolute value in the hint and negate the corresponding // points here when needed. signs, err := api.NewHint(halfGCDEisensteinSigns, 6, _s, cc.lambda) if err != nil { From 31c74ac10378a4e33fb0d0e79740b194f1a95b6a Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 14 Oct 2024 17:57:57 -0400 Subject: [PATCH 40/60] test: update stats --- internal/stats/latest_stats.csv | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index fbc129594e..5d3b8ba838 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -209,42 +209,42 @@ pairing_bw6761,bls24_315,plonk,0,0 pairing_bw6761,bls24_317,plonk,0,0 pairing_bw6761,bw6_761,plonk,0,0 pairing_bw6761,bw6_633,plonk,0,0 -scalar_mul_G1_bn254,bn254,groth16,99938,159576 +scalar_mul_G1_bn254,bn254,groth16,75511,118870 scalar_mul_G1_bn254,bls12_377,groth16,0,0 scalar_mul_G1_bn254,bls12_381,groth16,0,0 scalar_mul_G1_bn254,bls24_315,groth16,0,0 scalar_mul_G1_bn254,bls24_317,groth16,0,0 scalar_mul_G1_bn254,bw6_761,groth16,0,0 scalar_mul_G1_bn254,bw6_633,groth16,0,0 -scalar_mul_G1_bn254,bn254,plonk,381115,356144 +scalar_mul_G1_bn254,bn254,plonk,283182,265982 scalar_mul_G1_bn254,bls12_377,plonk,0,0 scalar_mul_G1_bn254,bls12_381,plonk,0,0 scalar_mul_G1_bn254,bls24_315,plonk,0,0 scalar_mul_G1_bn254,bls24_317,plonk,0,0 scalar_mul_G1_bn254,bw6_761,plonk,0,0 scalar_mul_G1_bn254,bw6_633,plonk,0,0 -scalar_mul_P256,bn254,groth16,186380,301997 +scalar_mul_P256,bn254,groth16,100828,161106 scalar_mul_P256,bls12_377,groth16,0,0 scalar_mul_P256,bls12_381,groth16,0,0 scalar_mul_P256,bls24_315,groth16,0,0 scalar_mul_P256,bls24_317,groth16,0,0 scalar_mul_P256,bw6_761,groth16,0,0 scalar_mul_P256,bw6_633,groth16,0,0 -scalar_mul_P256,bn254,plonk,737681,687661 +scalar_mul_P256,bn254,plonk,385060,359805 scalar_mul_P256,bls12_377,plonk,0,0 scalar_mul_P256,bls12_381,plonk,0,0 scalar_mul_P256,bls24_315,plonk,0,0 scalar_mul_P256,bls24_317,plonk,0,0 scalar_mul_P256,bw6_761,plonk,0,0 scalar_mul_P256,bw6_633,plonk,0,0 -scalar_mul_secp256k1,bn254,groth16,100948,161209 +scalar_mul_secp256k1,bn254,groth16,76305,120075 scalar_mul_secp256k1,bls12_377,groth16,0,0 scalar_mul_secp256k1,bls12_381,groth16,0,0 scalar_mul_secp256k1,bls24_315,groth16,0,0 scalar_mul_secp256k1,bls24_317,groth16,0,0 scalar_mul_secp256k1,bw6_761,groth16,0,0 scalar_mul_secp256k1,bw6_633,groth16,0,0 -scalar_mul_secp256k1,bn254,plonk,385109,359843 +scalar_mul_secp256k1,bn254,plonk,286142,268739 scalar_mul_secp256k1,bls12_377,plonk,0,0 scalar_mul_secp256k1,bls12_381,plonk,0,0 scalar_mul_secp256k1,bls24_315,plonk,0,0 From f69f2ab0f36a835ffcddcfa851c9d6c7cc91bce7 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 14 Oct 2024 23:08:05 -0400 Subject: [PATCH 41/60] perf: save some scs in edge cases --- std/algebra/emulated/sw_emulated/point.go | 10 ++--- .../emulated/sw_emulated/point_test.go | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 00888b1a20..fa7c67a054 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1730,7 +1730,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // // N.B.: Acc cannot be equal to G, otherwise this means G = -Φ²([s+1]P) g := c.Generator() - Acc = addFn(Acc, g) + Acc = c.Add(Acc, g) // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). // We prove that the factor is 760 * sqrt(2), @@ -1810,13 +1810,13 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // i = 0 // subtract the P, Q, Φ(P), Φ(Q) if the first bits are 0 - tableP[0] = addFn(tableP[0], Acc) + tableP[0] = c.Add(tableP[0], Acc) Acc = c.Select(u1bits[0], Acc, tableP[0]) - tablePhiP[0] = addFn(tablePhiP[0], Acc) + tablePhiP[0] = c.Add(tablePhiP[0], Acc) Acc = c.Select(u2bits[0], Acc, tablePhiP[0]) - tableQ[0] = addFn(tableQ[0], Acc) + tableQ[0] = c.Add(tableQ[0], Acc) Acc = c.Select(v1bits[0], Acc, tableQ[0]) - tablePhiQ[0] = addFn(tablePhiQ[0], Acc) + tablePhiQ[0] = c.Add(tablePhiQ[0], Acc) Acc = c.Select(v2bits[0], Acc, tablePhiQ[0]) // Acc should be now equal to [2^nbits]G diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index a03c66cac6..45279ca3d6 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -2282,3 +2282,46 @@ func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases(t *testing.T) { err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) } + +func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases2(t *testing.T) { + assert := test.NewAssert(t) + _, _, g, _ := bn254.Generators() + var r fr_bn.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S bn254.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](s), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + } + err := test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // 0 * P == (0,0) + witness2 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](new(big.Int)), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) +} From 46d07b7dc1b09308da1a69b06c8d58e658f9fab0 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 14 Oct 2024 23:11:57 -0400 Subject: [PATCH 42/60] fix: dummy point when Q=(0,0) --- std/algebra/emulated/sw_emulated/point.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index fa7c67a054..bcfb59ff6b 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1673,10 +1673,10 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem addFn := c.Add if cfg.CompleteArithmetic { addFn = c.AddUnified - // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue + // if Q=(0,0) we assign a dummy point to Q and R and continue _selector0 = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) - dummy := &AffinePoint[B]{X: *c.baseApi.One(), Y: *c.baseApi.Zero()} - Q = c.Select(_selector0, dummy, Q) + dummy := c.GeneratorMultiples()[3] + Q = c.Select(_selector0, &dummy, Q) } // precompute -P, -Φ(P), Φ(P) From f57ec664a397100d6418dc952e6ba04f65b766dc Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 15 Oct 2024 12:00:39 -0400 Subject: [PATCH 43/60] fix: fake 4-dim glv with complete arithmetic and no edge-cases --- std/algebra/emulated/sw_emulated/point.go | 46 +++++++++---------- .../emulated/sw_emulated/point_test.go | 6 ++- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index bcfb59ff6b..cdfc5510ad 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1670,26 +1670,26 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // handle (0,0)-point var _selector0 frontend.Variable - addFn := c.Add + _P := P if cfg.CompleteArithmetic { - addFn = c.AddUnified - // if Q=(0,0) we assign a dummy point to Q and R and continue - _selector0 = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y)) - dummy := c.GeneratorMultiples()[3] - Q = c.Select(_selector0, &dummy, Q) + // if Q=(0,0) we assign a dummy point to Q and continue + Q = c.Select(selector0, &c.GeneratorMultiples()[3], Q) + // if P=(0,0) we assign a dummy point to P and continue + _selector0 = c.api.And(c.baseApi.IsZero(&P.X), c.baseApi.IsZero(&P.Y)) + _P = c.Select(_selector0, &c.GeneratorMultiples()[4], P) } // precompute -P, -Φ(P), Φ(P) var tableP, tablePhiP [2]*AffinePoint[B] - negPY := c.baseApi.Neg(&P.Y) + negPY := c.baseApi.Neg(&_P.Y) tableP[1] = &AffinePoint[B]{ - X: P.X, - Y: *c.baseApi.Select(selector1, negPY, &P.Y), + X: _P.X, + Y: *c.baseApi.Select(selector1, negPY, &_P.Y), } tableP[0] = c.Neg(tableP[1]) tablePhiP[1] = &AffinePoint[B]{ - X: *c.baseApi.Mul(&P.X, c.thirdRootOne), - Y: *c.baseApi.Select(selector2, negPY, &P.Y), + X: *c.baseApi.Mul(&_P.X, c.thirdRootOne), + Y: *c.baseApi.Select(selector2, negPY, &_P.Y), } tablePhiP[0] = c.Neg(tablePhiP[1]) @@ -1709,18 +1709,18 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // precompute -P-Q, P+Q, P-Q, -P+Q, -Φ(P)-Φ(Q), Φ(P)+Φ(Q), Φ(P)-Φ(Q), -Φ(P)+Φ(Q) var tableS, tablePhiS [4]*AffinePoint[B] - tableS[0] = addFn(tableP[0], tableQ[0]) + tableS[0] = c.Add(tableP[0], tableQ[0]) tableS[1] = c.Neg(tableS[0]) - tableS[2] = addFn(tableP[1], tableQ[0]) + tableS[2] = c.Add(tableP[1], tableQ[0]) tableS[3] = c.Neg(tableS[2]) - tablePhiS[0] = addFn(tablePhiP[0], tablePhiQ[0]) + tablePhiS[0] = c.Add(tablePhiP[0], tablePhiQ[0]) tablePhiS[1] = c.Neg(tablePhiS[0]) - tablePhiS[2] = addFn(tablePhiP[1], tablePhiQ[0]) + tablePhiS[2] = c.Add(tablePhiP[1], tablePhiQ[0]) tablePhiS[3] = c.Neg(tablePhiS[2]) // we suppose that the first bits of the sub-scalars are 1 and set: // Acc = P + Q + Φ(P) + Φ(Q) - Acc := addFn(tableS[1], tablePhiS[1]) + Acc := c.Add(tableS[1], tablePhiS[1]) B1 := Acc // then we add G (the base point) to Acc to avoid incomplete additions in // the loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it @@ -1745,19 +1745,19 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // At each iteration we look up the point Bi from: // B1 = +P + Q + Φ(P) + Φ(Q) // B2 = +P + Q + Φ(P) - Φ(Q) - B2 := addFn(tableS[1], tablePhiS[2]) + B2 := c.Add(tableS[1], tablePhiS[2]) // B3 = +P + Q - Φ(P) + Φ(Q) - B3 := addFn(tableS[1], tablePhiS[3]) + B3 := c.Add(tableS[1], tablePhiS[3]) // B4 = +P + Q - Φ(P) - Φ(Q) - B4 := addFn(tableS[1], tablePhiS[0]) + B4 := c.Add(tableS[1], tablePhiS[0]) // B5 = +P - Q + Φ(P) + Φ(Q) - B5 := addFn(tableS[2], tablePhiS[1]) + B5 := c.Add(tableS[2], tablePhiS[1]) // B6 = +P - Q + Φ(P) - Φ(Q) - B6 := addFn(tableS[2], tablePhiS[2]) + B6 := c.Add(tableS[2], tablePhiS[2]) // B7 = +P - Q - Φ(P) + Φ(Q) - B7 := addFn(tableS[2], tablePhiS[3]) + B7 := c.Add(tableS[2], tablePhiS[3]) // B8 = +P - Q - Φ(P) - Φ(Q) - B8 := addFn(tableS[2], tablePhiS[0]) + B8 := c.Add(tableS[2], tablePhiS[0]) // B9 = -P + Q + Φ(P) + Φ(Q) B9 := c.Neg(B8) // B10 = -P + Q + Φ(P) - Φ(Q) diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 45279ca3d6..b844961006 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -2194,8 +2194,10 @@ func (c *ScalarMulGLVAndFakeGLVTest[T, S]) Define(api frontend.API) error { if err != nil { return err } - res := cr.scalarMulGLVAndFakeGLV(&c.Q, &c.S) - cr.AssertIsEqual(res, &c.R) + res1 := cr.scalarMulGLVAndFakeGLV(&c.Q, &c.S) + res2 := cr.scalarMulGLVAndFakeGLV(&c.Q, &c.S, algopts.WithCompleteArithmetic()) + cr.AssertIsEqual(res1, &c.R) + cr.AssertIsEqual(res2, &c.R) return nil } From 32ec487f8f9d7ac2d72c7150012bcdb82cdfdbbf Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 15 Oct 2024 12:26:07 -0400 Subject: [PATCH 44/60] test: edge case 0 * (0,0) == (0,0) --- .../emulated/sw_emulated/point_test.go | 90 +++++++++++++++++++ std/algebra/native/sw_bls12377/g1.go | 19 ++-- std/algebra/native/sw_bls12377/g1_test.go | 3 + 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index b844961006..531e99f17d 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -869,6 +869,21 @@ func TestScalarMulEdgeCasesEdgeCases(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](new(big.Int)), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](infinity.X), + Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } type IsOnCurveTest[T, S emulated.FieldParams] struct { @@ -2098,6 +2113,21 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulFakeGLVEdgeCasesTest[emulated.P256Fp, emulated.P256Fr]{ + S: emulated.ValueOf[emulated.P256Fr](new(big.Int)), + P: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](0), + Y: emulated.ValueOf[emulated.P256Fp](0), + }, + R: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](0), + Y: emulated.ValueOf[emulated.P256Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } func TestScalarMulFakeGLVEdgeCasesEdgeCases2(t *testing.T) { @@ -2139,6 +2169,21 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases2(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulFakeGLVEdgeCasesTest[emulated.P384Fp, emulated.P384Fr]{ + S: emulated.ValueOf[emulated.P384Fr](new(big.Int)), + P: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](0), + Y: emulated.ValueOf[emulated.P384Fp](0), + }, + R: AffinePoint[emulated.P384Fp]{ + X: emulated.ValueOf[emulated.P384Fp](0), + Y: emulated.ValueOf[emulated.P384Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } func TestScalarMulFakeGLVEdgeCasesEdgeCases3(t *testing.T) { @@ -2182,6 +2227,21 @@ func TestScalarMulFakeGLVEdgeCasesEdgeCases3(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulFakeGLVEdgeCasesTest[emulated.STARKCurveFp, emulated.STARKCurveFr]{ + S: emulated.ValueOf[emulated.STARKCurveFr](new(big.Int)), + P: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](0), + Y: emulated.ValueOf[emulated.STARKCurveFp](0), + }, + R: AffinePoint[emulated.STARKCurveFp]{ + X: emulated.ValueOf[emulated.STARKCurveFp](0), + Y: emulated.ValueOf[emulated.STARKCurveFp](0), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } type ScalarMulGLVAndFakeGLVTest[T, S emulated.FieldParams] struct { @@ -2283,6 +2343,21 @@ func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](new(big.Int)), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](0), + Y: emulated.ValueOf[emulated.Secp256k1Fp](0), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](0), + Y: emulated.ValueOf[emulated.Secp256k1Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases2(t *testing.T) { @@ -2326,4 +2401,19 @@ func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases2(t *testing.T) { } err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) + + // 0 * (0,0) == (0,0) + witness3 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](new(big.Int)), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](0), + Y: emulated.ValueOf[emulated.BN254Fp](0), + }, + } + err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) + assert.NoError(err) } diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 714e9a713f..4d9e416b8a 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -796,24 +796,27 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte // handle (0,0)-point var _selector0 frontend.Variable + _P := P if cfg.CompleteArithmetic { - // if Q=(0,0) we assign a dummy (1,1) to Q and R and continue + // if Q=(0,0) we assign a dummy point to Q and continue _selector0 = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) - dummy := G1Affine{X: 1, Y: 0} - Q.Select(api, _selector0, dummy, Q) + Q.Select(api, selector0, G1Affine{X: 1, Y: 0}, Q) + // if P=(0,0) we assign a dummy point to P and continue + _selector0 = api.And(api.IsZero(P.X), api.IsZero(P.Y)) + _P.Select(api, _selector0, G1Affine{X: 2, Y: 1}, P) } // precompute -P, -Φ(P), Φ(P) var tableP, tablePhiP [2]G1Affine - negPY := api.Neg(P.Y) + negPY := api.Neg(_P.Y) tableP[1] = G1Affine{ - X: P.X, - Y: api.Select(selector1, negPY, P.Y), + X: _P.X, + Y: api.Select(selector1, negPY, _P.Y), } tableP[0].Neg(api, tableP[1]) tablePhiP[1] = G1Affine{ - X: api.Mul(P.X, cc.thirdRootOne1), - Y: api.Select(selector2, negPY, P.Y), + X: api.Mul(_P.X, cc.thirdRootOne1), + Y: api.Select(selector2, negPY, _P.Y), } tablePhiP[0].Neg(api, tablePhiP[1]) diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 6decaaa749..4de3cdc21f 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -979,11 +979,14 @@ type scalarMulGLVAndFakeGLVEdgeCases struct { func (circuit *scalarMulGLVAndFakeGLVEdgeCases) Define(api frontend.API) error { expected1 := G1Affine{} expected2 := G1Affine{} + expected3 := G1Affine{} infinity := G1Affine{X: 0, Y: 0} expected1.varScalarMul(api, circuit.A, 0, algopts.WithCompleteArithmetic()) expected2.varScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) + expected3.varScalarMul(api, infinity, 0, algopts.WithCompleteArithmetic()) expected1.AssertIsEqual(api, infinity) expected2.AssertIsEqual(api, infinity) + expected3.AssertIsEqual(api, infinity) return nil } From 72ac56993596879c84b43046272a12b35ac32eaa Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 15 Oct 2024 12:30:48 -0400 Subject: [PATCH 45/60] ci(linter): ineffassign --- std/algebra/native/sw_bls12377/g1.go | 1 - 1 file changed, 1 deletion(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 4d9e416b8a..c16d0195c3 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -799,7 +799,6 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte _P := P if cfg.CompleteArithmetic { // if Q=(0,0) we assign a dummy point to Q and continue - _selector0 = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) Q.Select(api, selector0, G1Affine{X: 1, Y: 0}, Q) // if P=(0,0) we assign a dummy point to P and continue _selector0 = api.And(api.IsZero(P.X), api.IsZero(P.Y)) From 3707fe772df04ebda3c30c8fb658b2fabcae969c Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 11:50:08 -0400 Subject: [PATCH 46/60] test: Joye scalar mul --- .../emulated/sw_emulated/point_test.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 531e99f17d..524de47646 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1971,6 +1971,55 @@ func (c *ScalarMulJoyeTest[T, S]) Define(api frontend.API) error { return nil } +func TestScalarMulJoye(t *testing.T) { + assert := test.NewAssert(t) + p256 := elliptic.P256() + s, err := rand.Int(rand.Reader, p256.Params().N) + assert.NoError(err) + px, py := p256.ScalarBaseMult(s.Bytes()) + + circuit := ScalarMulJoyeTest[emulated.P256Fp, emulated.P256Fr]{} + witness := ScalarMulJoyeTest[emulated.P256Fp, emulated.P256Fr]{ + S: emulated.ValueOf[emulated.P256Fr](s), + P: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](p256.Params().Gx), + Y: emulated.ValueOf[emulated.P256Fp](p256.Params().Gy), + }, + Q: AffinePoint[emulated.P256Fp]{ + X: emulated.ValueOf[emulated.P256Fp](px), + Y: emulated.ValueOf[emulated.P256Fp](py), + }, + } + err = test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + +func TestScalarMulJoye2(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + var r fr_secp.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var S secp256k1.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulJoyeTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + witness := ScalarMulJoyeTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](s), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + Q: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](S.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](S.Y), + }, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + // fake GLV type ScalarMulFakeGLVTest[T, S emulated.FieldParams] struct { Q, R AffinePoint[T] From 92695a26ae2cca5e169231a47d25f8c6589fa288 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 12:13:38 -0400 Subject: [PATCH 47/60] refactor: apply review suggestions --- std/algebra/emulated/sw_emulated/point.go | 3 +-- .../native/twistededwards/curve_test.go | 19 ------------------- std/algebra/native/twistededwards/point.go | 2 +- std/math/emulated/emparams/emparams.go | 2 +- 4 files changed, 3 insertions(+), 23 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index cdfc5510ad..869bb88eb0 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -2,7 +2,6 @@ package sw_emulated import ( "fmt" - "math" "math/big" "slices" @@ -1322,7 +1321,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] } var st S - nbits := int(math.Ceil(float64(st.Modulus().BitLen()) / 2)) + nbits := (st.Modulus().BitLen() + 1) / 2 s1bits := c.scalarApi.ToBits(s1) s2bits := c.scalarApi.ToBits(s2) diff --git a/std/algebra/native/twistededwards/curve_test.go b/std/algebra/native/twistededwards/curve_test.go index f43e9efbaf..b443249f92 100644 --- a/std/algebra/native/twistededwards/curve_test.go +++ b/std/algebra/native/twistededwards/curve_test.go @@ -446,22 +446,3 @@ func (circuit *varScalarMul) Define(api frontend.API) error { return nil } - -// bench -func BenchmarkBandersnatch(b *testing.B) { - var c varScalarMul - c.curveID = twistededwards.BLS12_381_BANDERSNATCH - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &c) - p.Stop() - fmt.Println("Bandersnatch GLV: ", p.NbConstraints()) -} - -func BenchmarkJubjub(b *testing.B) { - var c varScalarMul - c.curveID = twistededwards.BLS12_381 - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &c) - p.Stop() - fmt.Println("Jubjub 2-bit double-and-add: ", p.NbConstraints()) -} diff --git a/std/algebra/native/twistededwards/point.go b/std/algebra/native/twistededwards/point.go index 6410b79367..ff036395a2 100644 --- a/std/algebra/native/twistededwards/point.go +++ b/std/algebra/native/twistededwards/point.go @@ -275,7 +275,7 @@ func (p *Point) scalarMulFakeGLV(api frontend.API, p1 *Point, scalar frontend.Va rhs := api.Select(bit, api.Add(_k, _s2), _k) api.AssertIsEqual(lhs, rhs) - n := int(math.Ceil(float64(curve.Order.BitLen()) / 2)) + n := (curve.Order.BitLen() + 1) / 2 b1 := api.ToBinary(s1, n) b2 := api.ToBinary(s2, n) diff --git a/std/math/emulated/emparams/emparams.go b/std/math/emulated/emparams/emparams.go index 9f86e73d39..736530148c 100644 --- a/std/math/emulated/emparams/emparams.go +++ b/std/math/emulated/emparams/emparams.go @@ -289,7 +289,7 @@ func (fr BLS24315Fr) Modulus() *big.Int { return ecc.BLS24_315.ScalarField() } // The prime modulus for type parametrisation is: // // 0x800000000000011000000000000000000000000000000000000000000000001 (base 16) -// 361850278866613121369732278309507010562310721533159669997309205613587202048196699973092056135872020481 (base 10) +// 3618502788666131213697322783095070105623107215331596699973092056135872020481 (base 10) // // This is the base field of the STARK curve. type STARKCurveFp struct{ fourLimbPrimeField } From daddeb0339cd84c8c7a5fafa5babf7be6bf82ed1 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 12:19:13 -0400 Subject: [PATCH 48/60] fix: remove unused imports --- std/algebra/native/twistededwards/curve_test.go | 4 ---- std/algebra/native/twistededwards/point.go | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/std/algebra/native/twistededwards/curve_test.go b/std/algebra/native/twistededwards/curve_test.go index b443249f92..1aee963405 100644 --- a/std/algebra/native/twistededwards/curve_test.go +++ b/std/algebra/native/twistededwards/curve_test.go @@ -18,11 +18,9 @@ package twistededwards import ( "crypto/rand" - "fmt" "math/big" "testing" - "github.com/consensys/gnark-crypto/ecc" tbls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/twistededwards" tbls12381_bandersnatch "github.com/consensys/gnark-crypto/ecc/bls12-381/bandersnatch" tbls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/twistededwards" @@ -34,9 +32,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/twistededwards" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/internal/utils" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/test" ) diff --git a/std/algebra/native/twistededwards/point.go b/std/algebra/native/twistededwards/point.go index ff036395a2..4b17d61a26 100644 --- a/std/algebra/native/twistededwards/point.go +++ b/std/algebra/native/twistededwards/point.go @@ -16,10 +16,7 @@ limitations under the License. package twistededwards -import ( - "github.com/consensys/gnark/frontend" - "math" -) +import "github.com/consensys/gnark/frontend" // neg computes the negative of a point in SNARK coordinates func (p *Point) neg(api frontend.API, p1 *Point) *Point { From 4f7abef9929cb17e711c823d7be12af8c0c9357c Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 16:19:55 -0400 Subject: [PATCH 49/60] perf(4D-fake-GLV/emulated): simpler scalar decomposition check --- std/algebra/emulated/sw_emulated/hints.go | 69 +++++------------ std/algebra/emulated/sw_emulated/point.go | 93 ++++++++--------------- 2 files changed, 51 insertions(+), 111 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index f9dcb26501..f7c1d7ae36 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -348,8 +348,8 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 10 { - return fmt.Errorf("expecting ten outputs") + if len(outputs) != 5 { + return fmt.Errorf("expecting five outputs") } glvBasis := new(ecc.Lattice) ecc.PrecomputeLattice(field, inputs[1], glvBasis) @@ -371,12 +371,15 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { outputs[2].SetUint64(0) outputs[3].SetUint64(0) outputs[4].SetUint64(0) - outputs[5].SetUint64(0) - outputs[6].SetUint64(0) - outputs[7].SetUint64(0) - outputs[8].SetUint64(0) - outputs[9].SetUint64(0) res := eisenstein.HalfGCD(&r, &s) + s.A1.Mul(res[1].A1, inputs[1]). + Add(s.A1, res[1].A0). + Mul(s.A1, inputs[0]). + Add(s.A1, res[0].A0) + s.A0.Mul(res[0].A1, inputs[1]) + s.A1.Add(s.A1, s.A0). + Div(s.A1, field) + if res[0].A0.Sign() == -1 { outputs[0].SetUint64(1) } @@ -389,25 +392,9 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if res[1].A1.Sign() == -1 { outputs[3].SetUint64(1) } - if res[2].A0.Sign() == -1 { - outputs[4].SetUint64(1) - } - if res[2].A1.Sign() == -1 { - outputs[5].SetUint64(1) - } - if r.A0.Sign() == -1 { - outputs[6].SetUint64(1) - } - if r.A1.Sign() == -1 { - outputs[7].SetUint64(1) - } - if s.A0.Sign() == -1 { - outputs[8].SetUint64(1) - } if s.A1.Sign() == -1 { - outputs[9].SetUint64(1) + outputs[4].SetUint64(1) } - return nil }) } @@ -417,8 +404,8 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 10 { - return fmt.Errorf("expecting ten outputs") + if len(outputs) != 5 { + return fmt.Errorf("expecting five outputs") } glvBasis := new(ecc.Lattice) ecc.PrecomputeLattice(field, inputs[1], glvBasis) @@ -439,12 +426,14 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro outputs[1].Set(res[0].A1) outputs[2].Set(res[1].A0) outputs[3].Set(res[1].A1) - outputs[4].Set(res[2].A0) - outputs[5].Set(res[2].A1) - outputs[6].Set(r.A0) - outputs[7].Set(r.A1) - outputs[8].Set(s.A0) - outputs[9].Set(s.A1) + outputs[4].Mul(res[1].A1, inputs[1]). + Add(outputs[4], res[1].A0). + Mul(outputs[4], inputs[0]). + Add(outputs[4], res[0].A0) + s.A0.Mul(res[0].A1, inputs[1]) + outputs[4].Add(outputs[4], s.A0). + Div(outputs[4], field) + if outputs[0].Sign() == -1 { outputs[0].Neg(outputs[0]) } @@ -460,22 +449,6 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if outputs[4].Sign() == -1 { outputs[4].Neg(outputs[4]) } - if outputs[5].Sign() == -1 { - outputs[5].Neg(outputs[5]) - } - if outputs[6].Sign() == -1 { - outputs[6].Neg(outputs[6]) - } - if outputs[7].Sign() == -1 { - outputs[7].Neg(outputs[7]) - } - if outputs[8].Sign() == -1 { - outputs[8].Neg(outputs[8]) - } - if outputs[9].Sign() == -1 { - outputs[9].Neg(outputs[9]) - } return nil - }) } diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 869bb88eb0..8c50aec294 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1569,88 +1569,56 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // This can be done through a hinted half-GCD in the number field // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. - sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 10, _s, c.eigenvalue) + // + // The hint returns u1, u2, v1, v2 and the quotient q. + // In-circuit we check that (v1 + λ*v2)*s = (u1 + λ*u2) + r*q + sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 5, _s, c.eigenvalue) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - u1, u2, v1, v2, w1, w2, r1, r2, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] + u1, u2, v1, v2, q := sd[0], sd[1], sd[2], sd[3], sd[4] // Eisenstein integers real and imaginary parts can be negative. So we // return the absolute value in the hint and negate the corresponding // points here when needed. - signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 10, _s, c.eigenvalue) + signs, err := c.scalarApi.NewHintWithNativeOutput(halfGCDEisensteinSigns, 5, _s, c.eigenvalue) if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } - selector1, selector2, selector3, selector4, selector5, selector6, selector7, selector8, selector9, selector10 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5], signs[6], signs[7], signs[8], signs[9] + isNegu1, isNegu2, isNegv1, isNegv2, isNegq := signs[0], signs[1], signs[2], signs[3], signs[4] // We need to check that: - // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 - // which is equivalent to checking: - // s1*v1 + r1*w1 = s2*v2 + r2*w2 + u1 and - // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 - // or that: - // s1*v1 + r1*w1 + u2 = s1*v2 + s2*v1 + r1*w2 + r2*w1 + u1 - // - // Since all these values can be negative, we gather all positive values - // either in the lhs or rhs and check equality. - s1v1 := c.scalarApi.Mul(s1, v1) - r1w1 := c.scalarApi.Mul(r1, w1) - s1v2 := c.scalarApi.Mul(s1, v2) - s2v1 := c.scalarApi.Mul(s2, v1) - r1w2 := c.scalarApi.Mul(r1, w2) - r2w1 := c.scalarApi.Mul(r2, w1) + // s*(v1 + λ*v2) + u1 + λ*u2 - r * q = 0 + var st S + r := emulated.ValueOf[S](st.Modulus()) + sv1 := c.scalarApi.Mul(_s, v1) + sλv2 := c.scalarApi.Mul(_s, c.scalarApi.Mul(c.eigenvalue, v2)) + λu2 := c.scalarApi.Mul(c.eigenvalue, u2) + rq := c.scalarApi.Mul(&r, q) zero := c.scalarApi.Zero() - xor1 := c.api.Xor(selector9, selector3) - xor2 := c.api.Xor(selector7, selector5) - xor3 := c.api.Xor(selector9, selector4) - xor4 := c.api.Xor(selector10, selector3) - xor5 := c.api.Xor(selector7, selector6) - xor6 := c.api.Xor(selector8, selector5) - - lhs1 := c.scalarApi.Select(xor1, zero, s1v1) - lhs2 := c.scalarApi.Select(xor2, zero, r1w1) - lhs3 := c.scalarApi.Select(xor3, s1v2, zero) - lhs4 := c.scalarApi.Select(xor4, s2v1, zero) - lhs5 := c.scalarApi.Select(xor5, r1w2, zero) - lhs6 := c.scalarApi.Select(xor6, r2w1, zero) - lhs7 := c.scalarApi.Select(selector1, u1, zero) - lhs8 := c.scalarApi.Select(selector2, zero, u2) + lhs1 := c.scalarApi.Select(isNegv1, zero, sv1) + lhs2 := c.scalarApi.Select(isNegv2, zero, sλv2) + lhs3 := c.scalarApi.Select(isNegu1, zero, u1) + lhs4 := c.scalarApi.Select(isNegu2, zero, λu2) + lhs5 := c.scalarApi.Select(isNegq, rq, zero) lhs := c.scalarApi.Add( c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4), ) - lhs = c.scalarApi.Add( - lhs, - c.scalarApi.Add(lhs5, lhs6), - ) - lhs = c.scalarApi.Add( - lhs, - c.scalarApi.Add(lhs7, lhs8), - ) + lhs = c.scalarApi.Add(lhs, lhs5) - rhs1 := c.scalarApi.Select(xor1, s1v1, zero) - rhs2 := c.scalarApi.Select(xor2, r1w1, zero) - rhs3 := c.scalarApi.Select(xor3, zero, s1v2) - rhs4 := c.scalarApi.Select(xor4, zero, s2v1) - rhs5 := c.scalarApi.Select(xor5, zero, r1w2) - rhs6 := c.scalarApi.Select(xor6, zero, r2w1) - rhs7 := c.scalarApi.Select(selector1, zero, u1) - rhs8 := c.scalarApi.Select(selector2, u2, zero) + rhs1 := c.scalarApi.Select(isNegv1, sv1, zero) + rhs2 := c.scalarApi.Select(isNegv2, sλv2, zero) + rhs3 := c.scalarApi.Select(isNegu1, u1, zero) + rhs4 := c.scalarApi.Select(isNegu2, λu2, zero) + rhs5 := c.scalarApi.Select(isNegq, zero, rq) rhs := c.scalarApi.Add( c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4), ) - rhs = c.scalarApi.Add( - rhs, - c.scalarApi.Add(rhs5, rhs6), - ) - rhs = c.scalarApi.Add( - rhs, - c.scalarApi.Add(rhs7, rhs8), - ) + rhs = c.scalarApi.Add(rhs, rhs5) c.scalarApi.AssertIsEqual(lhs, rhs) @@ -1683,12 +1651,12 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem negPY := c.baseApi.Neg(&_P.Y) tableP[1] = &AffinePoint[B]{ X: _P.X, - Y: *c.baseApi.Select(selector1, negPY, &_P.Y), + Y: *c.baseApi.Select(isNegu1, negPY, &_P.Y), } tableP[0] = c.Neg(tableP[1]) tablePhiP[1] = &AffinePoint[B]{ X: *c.baseApi.Mul(&_P.X, c.thirdRootOne), - Y: *c.baseApi.Select(selector2, negPY, &_P.Y), + Y: *c.baseApi.Select(isNegu2, negPY, &_P.Y), } tablePhiP[0] = c.Neg(tablePhiP[1]) @@ -1697,12 +1665,12 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem negQY := c.baseApi.Neg(&Q.Y) tableQ[1] = &AffinePoint[B]{ X: Q.X, - Y: *c.baseApi.Select(selector3, negQY, &Q.Y), + Y: *c.baseApi.Select(isNegv1, negQY, &Q.Y), } tableQ[0] = c.Neg(tableQ[1]) tablePhiQ[1] = &AffinePoint[B]{ X: *c.baseApi.Mul(&Q.X, c.thirdRootOne), - Y: *c.baseApi.Select(selector4, negQY, &Q.Y), + Y: *c.baseApi.Select(isNegv2, negQY, &Q.Y), } tablePhiQ[0] = c.Neg(tablePhiQ[1]) @@ -1734,7 +1702,6 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). // We prove that the factor is 760 * sqrt(2), // so we need to add 10 bits to r^{1/4}.nbits(). - var st S nbits := st.Modulus().BitLen()>>2 + 10 u1bits := c.scalarApi.ToBits(u1) u2bits := c.scalarApi.ToBits(u2) From 4bd0d489071eb087a620bb7e9696ec17a8fb336d Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 16:57:58 -0400 Subject: [PATCH 50/60] perf(4D-fake-GLV/native): simpler scalar decomposition check --- std/algebra/native/sw_bls12377/g1.go | 87 +++++++++---------------- std/algebra/native/sw_bls12377/hints.go | 66 ++++++++----------- 2 files changed, 57 insertions(+), 96 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index c16d0195c3..049a9819ec 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -709,81 +709,52 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte // This can be done through a hinted half-GCD in the number field // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. - sd, err := api.NewHint(halfGCDEisenstein, 10, _s, cc.lambda) + // + // The hint returns u1, u2, v1, v2 and the quotient q. + // In-circuit we check that (v1 + λ*v2)*s = (u1 + λ*u2) + r*q + sd, err := api.NewHint(halfGCDEisenstein, 5, _s, cc.lambda) if err != nil { panic(fmt.Sprintf("halfGCDEisenstein hint: %v", err)) } - u1, u2, v1, v2, w1, w2, _, _, s1, s2 := sd[0], sd[1], sd[2], sd[3], sd[4], sd[5], sd[6], sd[7], sd[8], sd[9] - // r is fixed and is equal to 91893752504881257701523279626832445440 - ω - var r1 big.Int - r1.SetString("91893752504881257701523279626832445440", 10) + u1, u2, v1, v2, q := sd[0], sd[1], sd[2], sd[3], sd[4] // Eisenstein integers real and imaginary parts can be negative. So we // return the absolute value in the hint and negate the corresponding // points here when needed. - signs, err := api.NewHint(halfGCDEisensteinSigns, 6, _s, cc.lambda) + signs, err := api.NewHint(halfGCDEisensteinSigns, 5, _s, cc.lambda) if err != nil { panic(fmt.Sprintf("halfGCDEisensteinSigns hint: %v", err)) } - selector1, selector2, selector3, selector4, selector5, selector6 := signs[0], signs[1], signs[2], signs[3], signs[4], signs[5] + isNegu1, isNegu2, isNegv1, isNegv2, isNegq := signs[0], signs[1], signs[2], signs[3], signs[4] // We need to check that: - // (s1 + j*s2)(v1 + j*v2) + (r1 + j*r2)(w1 + j*w2) - (u1 + j*u2) = 0 - // which is equivalent to checking: - // s1*v1 + r1*w1 = s2*v2 + r2*w2 + u1 and - // s1*v2 + s2*v1 + r1*w2 + r2*w1 = s2*v2 + r2*w2 + u2 - // or that: - // s1*v1 + r1*w1 + u2 = s1*v2 + s2*v1 + r1*w2 + r2*w1 + u1 - // - // Since all these values can be negative, we gather all positive values - // either in the lhs or rhs and check equality. - s1v1 := api.Mul(s1, v1) - r1w1 := api.Mul(r1, w1) - s1v2 := api.Mul(s1, v2) - s2v1 := api.Mul(s2, v1) - r1w2 := api.Mul(r1, w2) - - lhs1 := api.Select(selector3, s1v1, 0) - lhs2 := api.Select(selector5, 0, r1w1) - lhs3 := api.Select(selector4, 0, s1v2) - lhs4 := api.Select(selector3, 0, s2v1) - lhs5 := api.Select(selector6, r1w2, 0) - lhs6 := api.Select(selector5, 0, w1) - lhs7 := api.Select(selector1, u1, 0) - lhs8 := api.Select(selector2, 0, u2) + // s*(v1 + λ*v2) + u1 + λ*u2 - r * q = 0 + sv1 := api.Mul(_s, v1) + sλv2 := api.Mul(_s, api.Mul(cc.lambda, v2)) + λu2 := api.Mul(cc.lambda, u2) + rq := api.Mul(cc.fr, q) + + lhs1 := api.Select(isNegv1, 0, sv1) + lhs2 := api.Select(isNegv2, 0, sλv2) + lhs3 := api.Select(isNegu1, 0, u1) + lhs4 := api.Select(isNegu2, 0, λu2) + lhs5 := api.Select(isNegq, rq, 0) lhs := api.Add( api.Add(lhs1, lhs2), api.Add(lhs3, lhs4), ) - lhs = api.Add( - lhs, - api.Add(lhs5, lhs6), - ) - lhs = api.Add( - lhs, - api.Add(lhs7, lhs8), - ) + lhs = api.Add(lhs, lhs5) - rhs1 := api.Select(selector3, 0, s1v1) - rhs2 := api.Select(selector5, r1w1, 0) - rhs3 := api.Select(selector4, s1v2, 0) - rhs4 := api.Select(selector3, s2v1, 0) - rhs5 := api.Select(selector6, 0, r1w2) - rhs6 := api.Select(selector5, w1, 0) - rhs7 := api.Select(selector1, 0, u1) - rhs8 := api.Select(selector2, u2, 0) + rhs1 := api.Select(isNegv1, sv1, 0) + rhs2 := api.Select(isNegv2, sλv2, 0) + rhs3 := api.Select(isNegu1, u1, 0) + rhs4 := api.Select(isNegu2, λu2, 0) + rhs5 := api.Select(isNegq, 0, rq) rhs := api.Add( api.Add(rhs1, rhs2), api.Add(rhs3, rhs4), ) - rhs = api.Add( - rhs, - api.Add(rhs5, rhs6), - ) - rhs = api.Add( - rhs, - api.Add(rhs7, rhs8), - ) + rhs = api.Add(rhs, rhs5) api.AssertIsEqual(lhs, rhs) @@ -810,12 +781,12 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte negPY := api.Neg(_P.Y) tableP[1] = G1Affine{ X: _P.X, - Y: api.Select(selector1, negPY, _P.Y), + Y: api.Select(isNegu1, negPY, _P.Y), } tableP[0].Neg(api, tableP[1]) tablePhiP[1] = G1Affine{ X: api.Mul(_P.X, cc.thirdRootOne1), - Y: api.Select(selector2, negPY, _P.Y), + Y: api.Select(isNegu2, negPY, _P.Y), } tablePhiP[0].Neg(api, tablePhiP[1]) @@ -824,12 +795,12 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte negQY := api.Neg(Q.Y) tableQ[1] = G1Affine{ X: Q.X, - Y: api.Select(selector3, negQY, Q.Y), + Y: api.Select(isNegv1, negQY, Q.Y), } tableQ[0].Neg(api, tableQ[1]) tablePhiQ[1] = G1Affine{ X: api.Mul(Q.X, cc.thirdRootOne1), - Y: api.Select(selector4, negQY, Q.Y), + Y: api.Select(isNegv2, negQY, Q.Y), } tablePhiQ[0].Neg(api, tablePhiQ[1]) diff --git a/std/algebra/native/sw_bls12377/hints.go b/std/algebra/native/sw_bls12377/hints.go index da3f71a054..9c0c86333d 100644 --- a/std/algebra/native/sw_bls12377/hints.go +++ b/std/algebra/native/sw_bls12377/hints.go @@ -112,24 +112,23 @@ func scalarMulGLVG1Hint(scalarField *big.Int, inputs []*big.Int, outputs []*big. return nil } -func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { +func halfGCDEisenstein(scalarField *big.Int, inputs []*big.Int, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 10 { - return fmt.Errorf("expecting ten outputs") + if len(outputs) != 5 { + return fmt.Errorf("expecting five outputs") } + cc := getInnerCurveConfig(scalarField) glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(mod, inputs[1], glvBasis) + ecc.PrecomputeLattice(cc.fr, inputs[1], glvBasis) r := eisenstein.ComplexNumber{ A0: &glvBasis.V1[0], A1: &glvBasis.V1[1], } - // r = 91893752504881257701523279626832445440 - ω sp := ecc.SplitScalar(inputs[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.A0 and s.A1 are always positive. s := eisenstein.ComplexNumber{ A0: &sp[0], A1: &sp[1], @@ -140,12 +139,14 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro outputs[1].Set(res[0].A1) outputs[2].Set(res[1].A0) outputs[3].Set(res[1].A1) - outputs[4].Set(res[2].A0) - outputs[5].Set(res[2].A1) - outputs[6].Set(r.A0) - outputs[7].Set(r.A1) - outputs[8].Set(s.A0) - outputs[9].Set(s.A1) + outputs[4].Mul(res[1].A1, inputs[1]). + Add(outputs[4], res[1].A0). + Mul(outputs[4], inputs[0]). + Add(outputs[4], res[0].A0) + s.A0.Mul(res[0].A1, inputs[1]) + outputs[4].Add(outputs[4], s.A0). + Div(outputs[4], cc.fr) + if outputs[0].Sign() == -1 { outputs[0].Neg(outputs[0]) } @@ -161,34 +162,20 @@ func halfGCDEisenstein(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro if outputs[4].Sign() == -1 { outputs[4].Neg(outputs[4]) } - if outputs[5].Sign() == -1 { - outputs[5].Neg(outputs[5]) - } - if outputs[6].Sign() == -1 { - outputs[6].Neg(outputs[6]) - } - if outputs[7].Sign() == -1 { - outputs[7].Neg(outputs[7]) - } - if outputs[8].Sign() == -1 { - outputs[8].Neg(outputs[8]) - } - if outputs[9].Sign() == -1 { - outputs[9].Neg(outputs[9]) - } + return nil } -func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { +func halfGCDEisensteinSigns(scalarField *big.Int, inputs, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("expecting two input") } - if len(outputs) != 6 { - return fmt.Errorf("expecting six outputs") + if len(outputs) != 5 { + return fmt.Errorf("expecting five outputs") } + cc := getInnerCurveConfig(scalarField) glvBasis := new(ecc.Lattice) - ecc.PrecomputeLattice(mod, inputs[1], glvBasis) - // r = 91893752504881257701523279626832445440 - ω + ecc.PrecomputeLattice(cc.fr, inputs[1], glvBasis) r := eisenstein.ComplexNumber{ A0: &glvBasis.V1[0], A1: &glvBasis.V1[1], @@ -207,8 +194,15 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { outputs[2].SetUint64(0) outputs[3].SetUint64(0) outputs[4].SetUint64(0) - outputs[5].SetUint64(0) res := eisenstein.HalfGCD(&r, &s) + s.A1.Mul(res[1].A1, inputs[1]). + Add(s.A1, res[1].A0). + Mul(s.A1, inputs[0]). + Add(s.A1, res[0].A0) + s.A0.Mul(res[0].A1, inputs[1]) + s.A1.Add(s.A1, s.A0). + Div(s.A1, cc.fr) + if res[0].A0.Sign() == -1 { outputs[0].SetUint64(1) } @@ -221,12 +215,8 @@ func halfGCDEisensteinSigns(mod *big.Int, inputs, outputs []*big.Int) error { if res[1].A1.Sign() == -1 { outputs[3].SetUint64(1) } - if res[2].A0.Sign() == -1 { + if s.A1.Sign() == -1 { outputs[4].SetUint64(1) } - if res[2].A1.Sign() == -1 { - outputs[5].SetUint64(1) - } - return nil } From 2c8033bc3e1a61540c96876aed5715a684baedd4 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 17 Oct 2024 17:02:50 -0400 Subject: [PATCH 51/60] test: update stats --- internal/stats/latest_stats.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index 5d3b8ba838..a11ef9d669 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -209,14 +209,14 @@ pairing_bw6761,bls24_315,plonk,0,0 pairing_bw6761,bls24_317,plonk,0,0 pairing_bw6761,bw6_761,plonk,0,0 pairing_bw6761,bw6_633,plonk,0,0 -scalar_mul_G1_bn254,bn254,groth16,75511,118870 +scalar_mul_G1_bn254,bn254,groth16,75246,118454 scalar_mul_G1_bn254,bls12_377,groth16,0,0 scalar_mul_G1_bn254,bls12_381,groth16,0,0 scalar_mul_G1_bn254,bls24_315,groth16,0,0 scalar_mul_G1_bn254,bls24_317,groth16,0,0 scalar_mul_G1_bn254,bw6_761,groth16,0,0 scalar_mul_G1_bn254,bw6_633,groth16,0,0 -scalar_mul_G1_bn254,bn254,plonk,283182,265982 +scalar_mul_G1_bn254,bn254,plonk,282201,265060 scalar_mul_G1_bn254,bls12_377,plonk,0,0 scalar_mul_G1_bn254,bls12_381,plonk,0,0 scalar_mul_G1_bn254,bls24_315,plonk,0,0 @@ -237,14 +237,14 @@ scalar_mul_P256,bls24_315,plonk,0,0 scalar_mul_P256,bls24_317,plonk,0,0 scalar_mul_P256,bw6_761,plonk,0,0 scalar_mul_P256,bw6_633,plonk,0,0 -scalar_mul_secp256k1,bn254,groth16,76305,120075 +scalar_mul_secp256k1,bn254,groth16,76041,119660 scalar_mul_secp256k1,bls12_377,groth16,0,0 scalar_mul_secp256k1,bls12_381,groth16,0,0 scalar_mul_secp256k1,bls24_315,groth16,0,0 scalar_mul_secp256k1,bls24_317,groth16,0,0 scalar_mul_secp256k1,bw6_761,groth16,0,0 scalar_mul_secp256k1,bw6_633,groth16,0,0 -scalar_mul_secp256k1,bn254,plonk,286142,268739 +scalar_mul_secp256k1,bn254,plonk,285162,267818 scalar_mul_secp256k1,bls12_377,plonk,0,0 scalar_mul_secp256k1,bls12_381,plonk,0,0 scalar_mul_secp256k1,bls24_315,plonk,0,0 From 94c091c3f0da135b2d94bf7bd588510451dcfdc6 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 18 Oct 2024 14:19:00 -0400 Subject: [PATCH 52/60] fix(4D-fake-GLV): scalar=1 edge-case --- std/algebra/emulated/sw_emulated/point.go | 18 ++++-- .../emulated/sw_emulated/point_test.go | 60 +++++++++++++++++++ std/algebra/native/sw_bls12377/g1_test.go | 6 +- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 8c50aec294..0ca2d5fb45 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1541,12 +1541,17 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem panic(err) } - // handle zero-scalar + // handle 0-scalar and (-1)-scalar cases var selector0 frontend.Variable _s := s if cfg.CompleteArithmetic { - selector0 = c.scalarApi.IsZero(s) - _s = c.scalarApi.Select(selector0, c.scalarApi.One(), s) + one := c.scalarApi.One() + selector0 = c.api.Or( + c.scalarApi.IsZero(s), + c.scalarApi.IsZero( + c.scalarApi.Add(s, one)), + ) + _s = c.scalarApi.Select(selector0, one, s) } // Instead of computing [s]P=Q, we check that Q-[s]P == 0. @@ -1636,7 +1641,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem Q := &AffinePoint[B]{X: *point[0], Y: *point[1]} // handle (0,0)-point - var _selector0 frontend.Variable + var _selector0, _selector1 frontend.Variable _P := P if cfg.CompleteArithmetic { // if Q=(0,0) we assign a dummy point to Q and continue @@ -1644,6 +1649,9 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // if P=(0,0) we assign a dummy point to P and continue _selector0 = c.api.And(c.baseApi.IsZero(&P.X), c.baseApi.IsZero(&P.Y)) _P = c.Select(_selector0, &c.GeneratorMultiples()[4], P) + // if s=±1 we assign a dummy point to Q and continue + _selector1 = c.baseApi.IsZero(c.baseApi.Sub(&P.X, &Q.X)) + Q = c.Select(_selector1, &c.GeneratorMultiples()[3], Q) } // precompute -P, -Φ(P), Φ(P) @@ -1788,7 +1796,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // Acc should be now equal to [2^nbits]G gm := c.GeneratorMultiples()[nbits-1] if cfg.CompleteArithmetic { - Acc = c.Select(c.api.Or(selector0, _selector0), &gm, Acc) + Acc = c.Select(c.api.Or(c.api.Or(selector0, _selector0), _selector1), &gm, Acc) } c.AssertIsEqual(Acc, &gm) diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 524de47646..7e96cc3a9e 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -2407,6 +2407,36 @@ func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases(t *testing.T) { } err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) assert.NoError(err) + + // 1 * P == P + witness4 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](big.NewInt(1)), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + } + err = test.IsSolved(&circuit, &witness4, testCurve.ScalarField()) + assert.NoError(err) + + // -1 * P == -P + witness5 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: emulated.ValueOf[emulated.Secp256k1Fr](big.NewInt(-1)), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + R: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y.Neg(&g.Y)), + }, + } + err = test.IsSolved(&circuit, &witness5, testCurve.ScalarField()) + assert.NoError(err) } func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases2(t *testing.T) { @@ -2465,4 +2495,34 @@ func TestScalarMulGLVAndFakeGLVEdgeCasesEdgeCases2(t *testing.T) { } err = test.IsSolved(&circuit, &witness3, testCurve.ScalarField()) assert.NoError(err) + + // 1 * P == P + witness4 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](big.NewInt(1)), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y), + }, + } + err = test.IsSolved(&circuit, &witness4, testCurve.ScalarField()) + assert.NoError(err) + + // -1 * P == -P + witness5 := ScalarMulGLVAndFakeGLVEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](big.NewInt(-1)), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](g.X), + Y: emulated.ValueOf[emulated.BN254Fp](g.Y.Neg(&g.Y)), + }, + } + err = test.IsSolved(&circuit, &witness5, testCurve.ScalarField()) + assert.NoError(err) } diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 4de3cdc21f..02c2929c63 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -977,16 +977,16 @@ type scalarMulGLVAndFakeGLVEdgeCases struct { } func (circuit *scalarMulGLVAndFakeGLVEdgeCases) Define(api frontend.API) error { - expected1 := G1Affine{} - expected2 := G1Affine{} - expected3 := G1Affine{} + expected1, expected2, expected3, expected4 := G1Affine{}, G1Affine{}, G1Affine{}, G1Affine{} infinity := G1Affine{X: 0, Y: 0} expected1.varScalarMul(api, circuit.A, 0, algopts.WithCompleteArithmetic()) expected2.varScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) expected3.varScalarMul(api, infinity, 0, algopts.WithCompleteArithmetic()) + expected4.varScalarMul(api, circuit.A, 1, algopts.WithCompleteArithmetic()) expected1.AssertIsEqual(api, infinity) expected2.AssertIsEqual(api, infinity) expected3.AssertIsEqual(api, infinity) + expected4.AssertIsEqual(api, circuit.A) return nil } From eca44d4161895172f3b3c49b179b82daafdcfca0 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 21 Oct 2024 14:22:06 -0400 Subject: [PATCH 53/60] fix: edge cases as witnesses instead of constants --- std/algebra/native/sw_bls12377/g1_test.go | 77 +++++++++++++---------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index 02c2929c63..af34f7256c 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -278,18 +278,17 @@ func TestConstantScalarMulG1(t *testing.T) { } type g1constantScalarMulEdgeCases struct { - A G1Affine - R *big.Int + A, Inf G1Affine + R *big.Int } func (circuit *g1constantScalarMulEdgeCases) Define(api frontend.API) error { expected1 := G1Affine{} expected2 := G1Affine{} - infinity := G1Affine{X: 0, Y: 0} expected1.constScalarMul(api, circuit.A, big.NewInt(0)) - expected2.constScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) + expected2.constScalarMul(api, circuit.Inf, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) return nil } @@ -311,6 +310,9 @@ func TestConstantScalarMulG1EdgeCases(t *testing.T) { // br is a circuit parameter circuit.R = br + witness.Inf.X = 0 + witness.Inf.Y = 0 + assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) @@ -353,18 +355,17 @@ func TestVarScalarMulG1(t *testing.T) { } type g1varScalarMulEdgeCases struct { - A G1Affine - R frontend.Variable + A, Inf G1Affine + R, Zero frontend.Variable } func (circuit *g1varScalarMulEdgeCases) Define(api frontend.API) error { expected1 := G1Affine{} expected2 := G1Affine{} - infinity := G1Affine{X: 0, Y: 0} - expected1.varScalarMul(api, circuit.A, 0, algopts.WithCompleteArithmetic()) - expected2.varScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) + expected2.varScalarMul(api, circuit.Inf, circuit.R, algopts.WithCompleteArithmetic()) + expected1.varScalarMul(api, circuit.A, circuit.Zero, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) return nil } @@ -381,6 +382,9 @@ func TestVarScalarMulG1EdgeCases(t *testing.T) { witness.R = r.String() // assign the inputs witness.A.Assign(&a) + witness.Inf.X = 0 + witness.Inf.Y = 0 + witness.Zero = 0 assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) @@ -626,9 +630,9 @@ func TestMultiScalarMul(t *testing.T) { } type g1JointScalarMulEdgeCases struct { - A, B G1Affine - C G1Affine `gnark:",public"` - R, S frontend.Variable + A, B, Inf G1Affine + C G1Affine `gnark:",public"` + R, S, Zero frontend.Variable } func (circuit *g1JointScalarMulEdgeCases) Define(api frontend.API) error { @@ -636,15 +640,14 @@ func (circuit *g1JointScalarMulEdgeCases) Define(api frontend.API) error { expected2 := G1Affine{} expected3 := G1Affine{} expected4 := G1Affine{} - infinity := G1Affine{X: 0, Y: 0} - expected1.jointScalarMul(api, infinity, infinity, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) - expected2.jointScalarMul(api, circuit.A, circuit.B, big.NewInt(0), big.NewInt(0), algopts.WithCompleteArithmetic()) - expected3.jointScalarMul(api, circuit.A, infinity, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) - expected4.jointScalarMul(api, circuit.A, circuit.B, circuit.R, big.NewInt(0), algopts.WithCompleteArithmetic()) + expected1.jointScalarMul(api, circuit.Inf, circuit.Inf, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) + expected2.jointScalarMul(api, circuit.A, circuit.B, circuit.Zero, circuit.Zero, algopts.WithCompleteArithmetic()) + expected3.jointScalarMul(api, circuit.A, circuit.Inf, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) + expected4.jointScalarMul(api, circuit.A, circuit.B, circuit.R, circuit.Zero, algopts.WithCompleteArithmetic()) _expected := G1Affine{} _expected.ScalarMul(api, circuit.A, circuit.R, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) expected3.AssertIsEqual(api, _expected) expected4.AssertIsEqual(api, _expected) return nil @@ -676,6 +679,10 @@ func TestJointScalarMulG1EdgeCases(t *testing.T) { c.FromJacobian(&_a) witness.C.Assign(&c) + witness.Inf.X = 0 + witness.Inf.Y = 0 + witness.Zero = 0 + assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } @@ -972,20 +979,19 @@ func TestScalarMulG1GLVAndFakeGLV(t *testing.T) { } type scalarMulGLVAndFakeGLVEdgeCases struct { - A G1Affine - R frontend.Variable + A, Inf G1Affine + R, Zero, One frontend.Variable } func (circuit *scalarMulGLVAndFakeGLVEdgeCases) Define(api frontend.API) error { expected1, expected2, expected3, expected4 := G1Affine{}, G1Affine{}, G1Affine{}, G1Affine{} - infinity := G1Affine{X: 0, Y: 0} - expected1.varScalarMul(api, circuit.A, 0, algopts.WithCompleteArithmetic()) - expected2.varScalarMul(api, infinity, circuit.R, algopts.WithCompleteArithmetic()) - expected3.varScalarMul(api, infinity, 0, algopts.WithCompleteArithmetic()) - expected4.varScalarMul(api, circuit.A, 1, algopts.WithCompleteArithmetic()) - expected1.AssertIsEqual(api, infinity) - expected2.AssertIsEqual(api, infinity) - expected3.AssertIsEqual(api, infinity) + expected1.varScalarMul(api, circuit.A, circuit.Zero, algopts.WithCompleteArithmetic()) + expected2.varScalarMul(api, circuit.Inf, circuit.R, algopts.WithCompleteArithmetic()) + expected3.varScalarMul(api, circuit.Inf, circuit.Zero, algopts.WithCompleteArithmetic()) + expected4.varScalarMul(api, circuit.A, circuit.One, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) + expected3.AssertIsEqual(api, circuit.Inf) expected4.AssertIsEqual(api, circuit.A) return nil } @@ -1004,6 +1010,11 @@ func TestScalarMulG1GLVAndFakeGLVEdgeCases(t *testing.T) { // assign the inputs witness.A.Assign(&a) + witness.Inf.X = 0 + witness.Inf.Y = 0 + witness.Zero = 0 + witness.One = 1 + assert := test.NewAssert(t) assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } From e2d5b8e4b8016cef8525fc8c0cc01010613e18cf Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 22 Oct 2024 14:06:28 -0400 Subject: [PATCH 54/60] refactor: apply review suggestions --- std/algebra/emulated/sw_emulated/hints.go | 43 +++++++++-------------- std/algebra/emulated/sw_emulated/point.go | 27 +++++++------- std/algebra/native/sw_bls12377/g1.go | 4 +++ 3 files changed, 33 insertions(+), 41 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index f7c1d7ae36..f19bfeef0f 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -31,8 +31,7 @@ func GetHints() []solver.Hint { return []solver.Hint{ decomposeScalarG1Signs, decomposeScalarG1Subscalars, - scalarMulG1Hint, - scalarMulGLVG1Hint, + scalarMulHint, halfGCD, halfGCDSigns, halfGCDEisenstein, @@ -93,10 +92,13 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) }) } -// TODO @yelhousni: generalize for any supported curve. -// as it currently works only for P-256, P-384 and STARK curve. -func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { +// TODO @yelhousni: generalize for any supported curve as it currently supports only: +// BN254, BLS12-381, BW6-761 and Secp256k1, P256, P384 and STARK curve. +func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } if len(outputs) != 2 { return fmt.Errorf("expecting two outputs") } @@ -120,8 +122,10 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { } curve := elliptic.P256() - // compute the resulting point [s]Q - outputs[0], outputs[1] = curve.ScalarMult(Px, Py, S.Bytes()) + // compute the resulting point [s]P + Qx, Qy := curve.ScalarMult(Px, Py, S.Bytes()) + outputs[0].Set(Qx) + outputs[1].Set(Qy) } else if field.Cmp(elliptic.P384().Params().P) == 0 { var fp emparams.P384Fp var fr emparams.P384Fr @@ -142,8 +146,10 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { } curve := elliptic.P384() - // compute the resulting point [s]Q - outputs[0], outputs[1] = curve.ScalarMult(Px, Py, S.Bytes()) + // compute the resulting point [s]P + Qx, Qy := curve.ScalarMult(Px, Py, S.Bytes()) + outputs[0].Set(Qx) + outputs[1].Set(Qy) } else if field.Cmp(stark_fp.Modulus()) == 0 { var fp emparams.STARKCurveFp var fr emparams.STARKCurveFr @@ -170,23 +176,7 @@ func scalarMulG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { P.ScalarMultiplication(&P, S) P.X.BigInt(outputs[0]) P.Y.BigInt(outputs[1]) - } else { - return fmt.Errorf("unsupported curve") - } - - return nil - }) -} - -// TODO @yelhousni: generalize for any supported curve. -// as it currently works only for BN254, BLS12-381, BW6-761 and Secp256k1 curves. -func scalarMulGLVG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { - if len(outputs) != 2 { - return fmt.Errorf("expecting two outputs") - } - - if field.Cmp(bn_fp.Modulus()) == 0 { + } else if field.Cmp(bn_fp.Modulus()) == 0 { var fp emparams.BN254Fp var fr emparams.BN254Fr PXLimbs := inputs[:fp.NbLimbs()] @@ -290,6 +280,7 @@ func scalarMulGLVG1Hint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error P.ScalarMultiplication(&P, S) P.X.BigInt(outputs[0]) P.Y.BigInt(outputs[1]) + } else { return fmt.Errorf("unsupported curve") } diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 0ca2d5fb45..201bc7c74e 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1255,8 +1255,8 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ // (0,0) is not on the curve but we conventionally take it as the // neutral/infinity point as per the [EVM]. // -// TODO @yelhousni: generalize for any supported curve as it currently works -// only for P-256, P-384 and STARK curve because of the scalarMulG1Hint. +// TODO @yelhousni: generalize for any supported curve as it currently supports only: +// P256, P384 and STARK curve. // // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { @@ -1301,7 +1301,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] inps = append(inps, Q.X.Limbs...) inps = append(inps, Q.Y.Limbs...) inps = append(inps, s.Limbs...) - R, err := c.baseApi.NewHintWithNativeInput(scalarMulG1Hint, 2, inps...) + R, err := c.baseApi.NewHintWithNativeInput(scalarMulHint, 2, inps...) if err != nil { panic(fmt.Sprintf("scalar mul hint: %v", err)) } @@ -1533,6 +1533,9 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] // (0,0) is not on the curve but we conventionally take it as the // neutral/infinity point as per the [EVM]. // +// TODO @yelhousni: generalize for any supported curve as it currently supports only: +// BN254, BLS12-381, BW6-761 and Secp256k1. +// // [ethresear.ch/fake-GLV]: https://ethresear.ch/t/fake-glv-you-dont-need-an-efficient-endomorphism-to-implement-glv-like-scalar-multiplication-in-snark-circuits/20394 // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { @@ -1575,14 +1578,14 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem // K=Q[w]/f(w). This corresponds to K being the Eisenstein ring of // integers i.e. w is a primitive cube root of unity, f(w)=w^2+w+1=0. // - // The hint returns u1, u2, v1, v2 and the quotient q. - // In-circuit we check that (v1 + λ*v2)*s = (u1 + λ*u2) + r*q + // The hint returns u1, u2, v1, v2. + // In-circuit we check that (v1 + λ*v2)*s = (u1 + λ*u2) mod r sd, err := c.scalarApi.NewHint(halfGCDEisenstein, 5, _s, c.eigenvalue) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - u1, u2, v1, v2, q := sd[0], sd[1], sd[2], sd[3], sd[4] + u1, u2, v1, v2 := sd[0], sd[1], sd[2], sd[3] // Eisenstein integers real and imaginary parts can be negative. So we // return the absolute value in the hint and negate the corresponding @@ -1591,39 +1594,33 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem if err != nil { panic(fmt.Sprintf("halfGCDSigns hint: %v", err)) } - isNegu1, isNegu2, isNegv1, isNegv2, isNegq := signs[0], signs[1], signs[2], signs[3], signs[4] + isNegu1, isNegu2, isNegv1, isNegv2 := signs[0], signs[1], signs[2], signs[3] // We need to check that: - // s*(v1 + λ*v2) + u1 + λ*u2 - r * q = 0 + // s*(v1 + λ*v2) + u1 + λ*u2 = 0 var st S - r := emulated.ValueOf[S](st.Modulus()) sv1 := c.scalarApi.Mul(_s, v1) sλv2 := c.scalarApi.Mul(_s, c.scalarApi.Mul(c.eigenvalue, v2)) λu2 := c.scalarApi.Mul(c.eigenvalue, u2) - rq := c.scalarApi.Mul(&r, q) zero := c.scalarApi.Zero() lhs1 := c.scalarApi.Select(isNegv1, zero, sv1) lhs2 := c.scalarApi.Select(isNegv2, zero, sλv2) lhs3 := c.scalarApi.Select(isNegu1, zero, u1) lhs4 := c.scalarApi.Select(isNegu2, zero, λu2) - lhs5 := c.scalarApi.Select(isNegq, rq, zero) lhs := c.scalarApi.Add( c.scalarApi.Add(lhs1, lhs2), c.scalarApi.Add(lhs3, lhs4), ) - lhs = c.scalarApi.Add(lhs, lhs5) rhs1 := c.scalarApi.Select(isNegv1, sv1, zero) rhs2 := c.scalarApi.Select(isNegv2, sλv2, zero) rhs3 := c.scalarApi.Select(isNegu1, u1, zero) rhs4 := c.scalarApi.Select(isNegu2, λu2, zero) - rhs5 := c.scalarApi.Select(isNegq, zero, rq) rhs := c.scalarApi.Add( c.scalarApi.Add(rhs1, rhs2), c.scalarApi.Add(rhs3, rhs4), ) - rhs = c.scalarApi.Add(rhs, rhs5) c.scalarApi.AssertIsEqual(lhs, rhs) @@ -1634,7 +1631,7 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem inps = append(inps, P.X.Limbs...) inps = append(inps, P.Y.Limbs...) inps = append(inps, s.Limbs...) - point, err := c.baseApi.NewHintWithNativeInput(scalarMulGLVG1Hint, 2, inps...) + point, err := c.baseApi.NewHintWithNativeInput(scalarMulHint, 2, inps...) if err != nil { panic(fmt.Sprintf("scalar mul hint: %v", err)) } diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 049a9819ec..7537bc6805 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -674,6 +674,8 @@ func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits [] } // fake-GLV +// +// N.B.: this method is more expensive than classical GLV, but it is useful for testing purposes. func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { cfg, err := algopts.NewConfig(opts...) if err != nil { @@ -712,6 +714,8 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte // // The hint returns u1, u2, v1, v2 and the quotient q. // In-circuit we check that (v1 + λ*v2)*s = (u1 + λ*u2) + r*q + // + // N.B.: this check may overflow. But we don't use this method anywhere but for testing purposes. sd, err := api.NewHint(halfGCDEisenstein, 5, _s, cc.lambda) if err != nil { panic(fmt.Sprintf("halfGCDEisenstein hint: %v", err)) From 3d0d7177427ef5b66db5a911b54a05e3f1a167df Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 22 Oct 2024 14:20:04 -0400 Subject: [PATCH 55/60] test: update stats --- internal/stats/latest_stats.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index a11ef9d669..a24c7aee95 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -209,14 +209,14 @@ pairing_bw6761,bls24_315,plonk,0,0 pairing_bw6761,bls24_317,plonk,0,0 pairing_bw6761,bw6_761,plonk,0,0 pairing_bw6761,bw6_633,plonk,0,0 -scalar_mul_G1_bn254,bn254,groth16,75246,118454 +scalar_mul_G1_bn254,bn254,groth16,75154,118312 scalar_mul_G1_bn254,bls12_377,groth16,0,0 scalar_mul_G1_bn254,bls12_381,groth16,0,0 scalar_mul_G1_bn254,bls24_315,groth16,0,0 scalar_mul_G1_bn254,bls24_317,groth16,0,0 scalar_mul_G1_bn254,bw6_761,groth16,0,0 scalar_mul_G1_bn254,bw6_633,groth16,0,0 -scalar_mul_G1_bn254,bn254,plonk,282201,265060 +scalar_mul_G1_bn254,bn254,plonk,281862,264745 scalar_mul_G1_bn254,bls12_377,plonk,0,0 scalar_mul_G1_bn254,bls12_381,plonk,0,0 scalar_mul_G1_bn254,bls24_315,plonk,0,0 @@ -237,14 +237,14 @@ scalar_mul_P256,bls24_315,plonk,0,0 scalar_mul_P256,bls24_317,plonk,0,0 scalar_mul_P256,bw6_761,plonk,0,0 scalar_mul_P256,bw6_633,plonk,0,0 -scalar_mul_secp256k1,bn254,groth16,76041,119660 +scalar_mul_secp256k1,bn254,groth16,75948,119517 scalar_mul_secp256k1,bls12_377,groth16,0,0 scalar_mul_secp256k1,bls12_381,groth16,0,0 scalar_mul_secp256k1,bls24_315,groth16,0,0 scalar_mul_secp256k1,bls24_317,groth16,0,0 scalar_mul_secp256k1,bw6_761,groth16,0,0 scalar_mul_secp256k1,bw6_633,groth16,0,0 -scalar_mul_secp256k1,bn254,plonk,285162,267818 +scalar_mul_secp256k1,bn254,plonk,284823,267503 scalar_mul_secp256k1,bls12_377,plonk,0,0 scalar_mul_secp256k1,bls12_381,plonk,0,0 scalar_mul_secp256k1,bls24_315,plonk,0,0 From 7a42e29975bfd9accdfa15a20e67bd9069e1b27e Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 22 Oct 2024 15:01:41 -0400 Subject: [PATCH 56/60] docs: correct comment --- std/algebra/emulated/sw_emulated/point.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 201bc7c74e..0c9d3e6681 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -663,7 +663,7 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op // note that half the points are negatives of the other half, // hence have the same X coordinates. - // when nbits is odd, we need to handle the first iteration separately + // when nbits is even, we need to handle the first iteration separately if nbits%2 == 0 { // Acc = [2]Acc ± Q ± Φ(Q) T := &AffinePoint[B]{ @@ -675,7 +675,7 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op Acc = c.double(Acc) Acc = c.add(Acc, T) } else { - // when nbits is even we start the main loop at normally nbits - 1 + // when nbits is odd we start the main loop at normally nbits - 1 nbits++ } for i := nbits - 2; i > 0; i -= 2 { @@ -1420,7 +1420,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] // note that half of these points are negatives of the other half, // hence have the same X coordinates. - // When nbits is odd, we need to handle the first iteration separately + // When nbits is even, we need to handle the first iteration separately if nbits%2 == 0 { // Acc = [2]Acc ± Q ± R T := &AffinePoint[B]{ @@ -1432,7 +1432,7 @@ func (c *Curve[B, S]) scalarMulFakeGLV(Q *AffinePoint[B], s *emulated.Element[S] Acc = c.double(Acc) Acc = c.add(Acc, T) } else { - // when nbits is even we start the main loop at normally nbits - 1 + // when nbits is odd we start the main loop at normally nbits - 1 nbits++ } for i := nbits - 2; i > 2; i -= 2 { From 93edbb265d5b1aa649b0a8c846b962e5b97a68cd Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 24 Oct 2024 14:28:05 -0400 Subject: [PATCH 57/60] perf(4D-fake-GLV): revisit loop bound (-1 bit) --- std/algebra/emulated/sw_emulated/point.go | 6 +++--- std/algebra/native/sw_bls12377/g1.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 0c9d3e6681..ead16f1f75 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -1705,9 +1705,9 @@ func (c *Curve[B, S]) scalarMulGLVAndFakeGLV(P *AffinePoint[B], s *emulated.Elem Acc = c.Add(Acc, g) // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). - // We prove that the factor is 760 * sqrt(2), - // so we need to add 10 bits to r^{1/4}.nbits(). - nbits := st.Modulus().BitLen()>>2 + 10 + // We prove that the factor is log_(3/sqrt(3)))(r). + // so we need to add 9 bits to r^{1/4}.nbits(). + nbits := st.Modulus().BitLen()>>2 + 9 u1bits := c.scalarApi.ToBits(u1) u2bits := c.scalarApi.ToBits(u2) v1bits := c.scalarApi.ToBits(v1) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 7537bc6805..2823d13460 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -840,9 +840,9 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte Acc.AddAssign(api, H) // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). - // We prove that the factor is 760 * sqrt(2), - // so we need to add 10 bits to r^{1/4}.nbits(). - nbits := cc.lambda.BitLen()>>1 + 10 + // We prove that the factor is log_(3/sqrt(3)))(r). + // so we need to add 9 bits to r^{1/4}.nbits(). + nbits := cc.lambda.BitLen()>>1 + 9 u1bits := api.ToBinary(u1, nbits) u2bits := api.ToBinary(u2, nbits) v1bits := api.ToBinary(v1, nbits) From 1bdd40c9018207cd93abef2b43a60ebe19e8d6bb Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 24 Oct 2024 14:55:41 -0400 Subject: [PATCH 58/60] test: update stats --- internal/stats/latest_stats.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index a24c7aee95..eb7b4efb74 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -209,14 +209,14 @@ pairing_bw6761,bls24_315,plonk,0,0 pairing_bw6761,bls24_317,plonk,0,0 pairing_bw6761,bw6_761,plonk,0,0 pairing_bw6761,bw6_633,plonk,0,0 -scalar_mul_G1_bn254,bn254,groth16,75154,118312 +scalar_mul_G1_bn254,bn254,groth16,74345,117078 scalar_mul_G1_bn254,bls12_377,groth16,0,0 scalar_mul_G1_bn254,bls12_381,groth16,0,0 scalar_mul_G1_bn254,bls24_315,groth16,0,0 scalar_mul_G1_bn254,bls24_317,groth16,0,0 scalar_mul_G1_bn254,bw6_761,groth16,0,0 scalar_mul_G1_bn254,bw6_633,groth16,0,0 -scalar_mul_G1_bn254,bn254,plonk,281862,264745 +scalar_mul_G1_bn254,bn254,plonk,278909,261995 scalar_mul_G1_bn254,bls12_377,plonk,0,0 scalar_mul_G1_bn254,bls12_381,plonk,0,0 scalar_mul_G1_bn254,bls24_315,plonk,0,0 @@ -237,14 +237,14 @@ scalar_mul_P256,bls24_315,plonk,0,0 scalar_mul_P256,bls24_317,plonk,0,0 scalar_mul_P256,bw6_761,plonk,0,0 scalar_mul_P256,bw6_633,plonk,0,0 -scalar_mul_secp256k1,bn254,groth16,75948,119517 +scalar_mul_secp256k1,bn254,groth16,75154,118312 scalar_mul_secp256k1,bls12_377,groth16,0,0 scalar_mul_secp256k1,bls12_381,groth16,0,0 scalar_mul_secp256k1,bls24_315,groth16,0,0 scalar_mul_secp256k1,bls24_317,groth16,0,0 scalar_mul_secp256k1,bw6_761,groth16,0,0 scalar_mul_secp256k1,bw6_633,groth16,0,0 -scalar_mul_secp256k1,bn254,plonk,284823,267503 +scalar_mul_secp256k1,bn254,plonk,281870,264753 scalar_mul_secp256k1,bls12_377,plonk,0,0 scalar_mul_secp256k1,bls12_381,plonk,0,0 scalar_mul_secp256k1,bls24_315,plonk,0,0 From 48f5aa8825fd0b25f9911b7e987493ec3bdb500c Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 24 Oct 2024 17:08:11 -0400 Subject: [PATCH 59/60] fix(4D-fake-glv/native): Acc=(0,-1) with +9 bits --- std/algebra/native/sw_bls12377/g1.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/std/algebra/native/sw_bls12377/g1.go b/std/algebra/native/sw_bls12377/g1.go index 2823d13460..84f038607a 100644 --- a/std/algebra/native/sw_bls12377/g1.go +++ b/std/algebra/native/sw_bls12377/g1.go @@ -842,7 +842,7 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte // u1, u2, v1, v2 < r^{1/4} (up to a constant factor). // We prove that the factor is log_(3/sqrt(3)))(r). // so we need to add 9 bits to r^{1/4}.nbits(). - nbits := cc.lambda.BitLen()>>1 + 9 + nbits := cc.lambda.BitLen()>>1 + 9 // 72 u1bits := api.ToBinary(u1, nbits) u2bits := api.ToBinary(u2, nbits) v1bits := api.ToBinary(v1, nbits) @@ -869,12 +869,12 @@ func (R *G1Affine) scalarMulGLVAndFakeGLV(api frontend.API, P G1Affine, s fronte tablePhiQ[0].AddAssign(api, Acc) Acc.Select(api, v2bits[0], Acc, tablePhiQ[0]) - // Acc should be now equal to H=(0,1) - gm := G1Affine{X: 0, Y: 1} + // Acc should be now equal to H=(0,-1) + H = G1Affine{X: 0, Y: -1} if cfg.CompleteArithmetic { - Acc.Select(api, api.Or(selector0, _selector0), gm, Acc) + Acc.Select(api, api.Or(selector0, _selector0), H, Acc) } - Acc.AssertIsEqual(api, gm) + Acc.AssertIsEqual(api, H) R.X = point[0] R.Y = point[1] From 4c7fe583255e3552ea540df51aa9cac253433db3 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 25 Oct 2024 10:49:41 -0400 Subject: [PATCH 60/60] perf: small optim in jSM --- std/algebra/emulated/sw_emulated/point.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index ead16f1f75..433300ccda 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -966,13 +966,14 @@ func (c *Curve[B, S]) jointScalarMulGLVUnsafe(Q, R *AffinePoint[B], s, t *emulat tableS[3] = c.Neg(tableS[2]) f0 := c.baseApi.Mul(&tableS[0].X, c.thirdRootOne) f2 := c.baseApi.Mul(&tableS[2].X, c.thirdRootOne) + xor := c.api.Xor(selector2, selector4) tablePhiS[0] = &AffinePoint[B]{ - X: *c.baseApi.Select(c.api.Xor(selector2, selector4), f2, f0), + X: *c.baseApi.Select(xor, f2, f0), Y: *c.baseApi.Lookup2(selector2, selector4, &tableS[0].Y, &tableS[2].Y, &tableS[3].Y, &tableS[1].Y), } tablePhiS[1] = c.Neg(tablePhiS[0]) tablePhiS[2] = &AffinePoint[B]{ - X: *c.baseApi.Select(c.api.Xor(selector2, selector4), f0, f2), + X: *c.baseApi.Select(xor, f0, f2), Y: *c.baseApi.Lookup2(selector2, selector4, &tableS[2].Y, &tableS[0].Y, &tableS[1].Y, &tableS[3].Y), } tablePhiS[3] = c.Neg(tablePhiS[2])