diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 7338bff86c..e750a77aaa 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -363,19 +363,18 @@ func (c *Curve[B, S]) Lookup2(b0, b1 frontend.Variable, i0, i1, i2, i3 *AffinePo // (0,0) is not on the curve but we conventionally take it as the // neutral/infinity point as per the [EVM]. // -// It computes the standard little-endian variable-base double-and-add algorithm -// [HMV04] (Algorithm 3.26). +// It computes the right-to-left variable-base add-only algorithm ([Joye07], Alg.2). // // Since we use incomplete formulas for the addition law, we need to start with -// a non-zero accumulator point (res). To do this, we skip the LSB (bit at +// a non-zero accumulator point (R0). To do this, we skip the LSB (bit at // position 0) and proceed assuming it was 1. At the end, we conditionally // subtract the initial value (p) if LSB is 1. We also handle the bits at -// positions 1, n-2 and n-1 outside of the loop to optimize the number of +// positions 1 and n-1 outside of the loop to optimize the number of // constraints using [ELM03] (Section 3.1) // // [ELM03]: https://arxiv.org/pdf/math/0208038.pdf -// [HMV04]: https://link.springer.com/book/10.1007/b97644 // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +// [Joye07]: https://www.iacr.org/archive/ches2007/47270135/47270135.pdf func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *AffinePoint[B] { // if p=(0,0) we assign a dummy (0,1) to p and continue @@ -389,35 +388,34 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *Affi n := st.Modulus().BitLen() // i = 1 - tmp := c.triple(p) - res := c.Select(sBits[1], tmp, p) - acc := c.add(tmp, p) - - for i := 2; i <= n-3; i++ { - tmp := c.add(res, acc) - res = c.Select(sBits[i], tmp, res) - acc = c.double(acc) + R := c.triple(p) + R0 := c.Select(sBits[1], R, p) + R1 := c.Select(sBits[1], p, R) + R2 := c.add(R0, R1) + + for i := 2; i < n-1; i++ { + R = c.Select(sBits[i], R0, R1) + R = c.add(R, R2) + R0 = c.Select(sBits[i], R, R0) + R1 = c.Select(sBits[i], R1, R) + R2 = c.add(R0, R1) } - // i = n-2 - tmp = c.add(res, acc) - res = c.Select(sBits[n-2], tmp, res) - // i = n-1 - tmp = c.doubleAndAdd(acc, res) - res = c.Select(sBits[n-1], tmp, res) + R = c.Select(sBits[n-1], R0, R1) + R = c.add(R, R2) + R0 = c.Select(sBits[n-1], R, R0) // i = 0 - // we use AddUnified here instead of Add so that when s=0, res=(0,0) + // we use AddUnified here instead of add so that when s=0, res=(0,0) // because AddUnified(p, -p) = (0,0) - tmp = c.AddUnified(res, c.Neg(p)) - res = c.Select(sBits[0], res, tmp) + R0 = c.Select(sBits[0], R0, c.AddUnified(R0, c.Neg(p))) // if p=(0,0), return (0,0) zero := c.baseApi.Zero() - res = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, res) + R0 = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, R0) - return res + return R0 } // ScalarMulBase computes s * g and returns it, where g is the fixed generator. @@ -428,12 +426,9 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *Affi // neutral/infinity point as per the [EVM]. // // It computes the standard little-endian fixed-base double-and-add algorithm -// [HMV04] (Algorithm 3.26). -// -// The method proceeds similarly to ScalarMul but with the points [2^i]g -// precomputed. The bits at positions 1 and 2 are handled outside of the loop -// to optimize the number of constraints using a Lookup2 with pre-computed -// [3]g, [5]g and [7]g points. +// [HMV04] (Algorithm 3.26), with the points [2^i]g precomputed. The bits at +// positions 1 and 2 are handled outside of the loop to optimize the number of +// constraints using a Lookup2 with pre-computed [3]g, [5]g and [7]g points. // // [HMV04]: https://link.springer.com/book/10.1007/b97644 // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf @@ -468,6 +463,8 @@ func (c *Curve[B, S]) ScalarMulBase(s *emulated.Element[S]) *AffinePoint[B] { // ⚠️ p must NOT be (0,0). // ⚠️ s1 and s2 must NOT be 0. // +// It uses the logic from ScalarMul() for s1 * g and the logic from ScalarMulBase() for s2 * g. +// // JointScalarMulBase is used to verify an ECDSA signature (r,s) on the // secp256k1 curve. In this case, p is a public key, s2=r/s and s1=hash/s. // - hash cannot be 0, because of pre-image resistance. @@ -492,42 +489,57 @@ func (c *Curve[B, S]) JointScalarMulBase(p *AffinePoint[B], s2, s1 *emulated.Ele s2Bits := c.scalarApi.ToBits(s2r) n := st.Modulus().BitLen() + // fixed-base // i = 1, 2 // gm[0] = 3g, gm[1] = 5g, gm[2] = 7g res1 := c.Lookup2(s1Bits[1], s1Bits[2], g, &gm[0], &gm[1], &gm[2]) - tmp2 := c.triple(p) - res2 := c.Select(s2Bits[1], tmp2, p) - acc := c.add(tmp2, p) - tmp2 = c.add(res2, acc) - res2 = c.Select(s2Bits[2], tmp2, res2) - acc = c.double(acc) + // var-base + // i = 1 + R := c.triple(p) + R0 := c.Select(s2Bits[1], R, p) + R1 := c.Select(s2Bits[1], p, R) + R2 := c.add(R0, R1) + // i = 2 + R = c.Select(s2Bits[2], R0, R1) + R = c.add(R, R2) + R0 = c.Select(s2Bits[2], R, R0) + R1 = c.Select(s2Bits[2], R1, R) + R2 = c.add(R0, R1) for i := 3; i <= n-3; i++ { + // fixed-base // gm[i] = [2^i]g tmp1 := c.add(res1, &gm[i]) res1 = c.Select(s1Bits[i], tmp1, res1) - tmp2 = c.add(res2, acc) - res2 = c.Select(s2Bits[i], tmp2, res2) - acc = c.double(acc) - } + // var-base + R = c.Select(s2Bits[i], R0, R1) + R = c.add(R, R2) + R0 = c.Select(s2Bits[i], R, R0) + R1 = c.Select(s2Bits[i], R1, R) + R2 = c.add(R0, R1) - // i = 0 - tmp1 := c.add(res1, c.Neg(g)) - res1 = c.Select(s1Bits[0], res1, tmp1) - tmp2 = c.add(res2, c.Neg(p)) - res2 = c.Select(s2Bits[0], res2, tmp2) + } // i = n-2 - tmp1 = c.add(res1, &gm[n-2]) + tmp1 := c.add(res1, &gm[n-2]) res1 = c.Select(s1Bits[n-2], tmp1, res1) - tmp2 = c.add(res2, acc) - res2 = c.Select(s2Bits[n-2], tmp2, res2) + R = c.Select(s2Bits[n-2], R0, R1) + R = c.add(R, R2) + R0 = c.Select(s2Bits[n-2], R, R0) + R1 = c.Select(s2Bits[n-2], R1, R) + R2 = c.add(R0, R1) // i = n-1 tmp1 = c.add(res1, &gm[n-1]) res1 = c.Select(s1Bits[n-1], tmp1, res1) - tmp2 = c.doubleAndAdd(acc, res2) - res2 = c.Select(s2Bits[n-1], tmp2, res2) + R = c.Select(s2Bits[n-1], R0, R1) + R = c.add(R, R2) + R0 = c.Select(s2Bits[n-1], R, R0) + + // i = 0 + tmp1 = c.add(res1, c.Neg(g)) + res1 = c.Select(s1Bits[0], res1, tmp1) + R0 = c.Select(s2Bits[0], R0, c.AddUnified(R0, c.Neg(p))) - return c.add(res1, res2) + return c.add(res1, R0) } diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index e006a8d7f2..0f16765816 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -219,11 +219,11 @@ func TestDoubleAndAdd(t *testing.T) { assert.NoError(err) } -type AddUnifiedEdgeCases[T, S emulated.FieldParams] struct { +type AddUnifiedEdgeCasesTest[T, S emulated.FieldParams] struct { P, Q, R AffinePoint[T] } -func (c *AddUnifiedEdgeCases[T, S]) Define(api frontend.API) error { +func (c *AddUnifiedEdgeCasesTest[T, S]) Define(api frontend.API) error { cr, err := New[T, S](api, GetCurveParams[T]()) if err != nil { return err @@ -245,10 +245,10 @@ func TestAddUnifiedEdgeCases(t *testing.T) { S.ScalarMultiplication(&g, s) Sn.Neg(&S) - circuit := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{} + circuit := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{} // (0,0) + (0,0) == (0,0) - witness1 := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ + witness1 := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ P: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](infinity.X), Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), @@ -266,7 +266,7 @@ func TestAddUnifiedEdgeCases(t *testing.T) { assert.NoError(err) // S + (0,0) == S - witness2 := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ + witness2 := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ P: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](S.X), Y: emulated.ValueOf[emulated.BN254Fp](S.Y), @@ -284,7 +284,7 @@ func TestAddUnifiedEdgeCases(t *testing.T) { assert.NoError(err) // (0,0) + S == S - witness3 := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ + witness3 := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ P: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](infinity.X), Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), @@ -302,7 +302,7 @@ func TestAddUnifiedEdgeCases(t *testing.T) { assert.NoError(err) // S + (-S) == (0,0) - witness4 := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ + witness4 := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ P: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](S.X), Y: emulated.ValueOf[emulated.BN254Fp](S.Y), @@ -320,7 +320,7 @@ func TestAddUnifiedEdgeCases(t *testing.T) { assert.NoError(err) // (-S) + S == (0,0) - witness5 := AddUnifiedEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ + witness5 := AddUnifiedEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ P: AffinePoint[emulated.BN254Fp]{ X: emulated.ValueOf[emulated.BN254Fp](Sn.X), Y: emulated.ValueOf[emulated.BN254Fp](Sn.Y), @@ -486,65 +486,6 @@ func TestScalarMul2(t *testing.T) { assert.NoError(err) } -type ScalarMulEdgeCases[T, S emulated.FieldParams] struct { - P, R AffinePoint[T] - S emulated.Element[S] -} - -func (c *ScalarMulEdgeCases[T, S]) Define(api frontend.API) error { - cr, err := New[T, S](api, GetCurveParams[T]()) - if err != nil { - return err - } - res := cr.ScalarMul(&c.P, &c.S) - cr.AssertIsEqual(res, &c.R) - return nil -} - -func TestScalarMulEdgeCasesEdgeCases(t *testing.T) { - assert := test.NewAssert(t) - var infinity bn254.G1Affine - _, _, 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 := ScalarMulEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{} - - // s * (0,0) == (0,0) - witness1 := ScalarMulEdgeCases[emulated.BN254Fp, emulated.BN254Fr]{ - S: emulated.ValueOf[emulated.BN254Fr](s), - P: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](infinity.X), - Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), - }, - R: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](infinity.X), - Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), - }, - } - err := test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) - assert.NoError(err) - - // 0 * S == (0,0) - witness2 := ScalarMulEdgeCases[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), - }, - R: AffinePoint[emulated.BN254Fp]{ - X: emulated.ValueOf[emulated.BN254Fp](infinity.X), - Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), - }, - } - err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) - assert.NoError(err) -} - func TestScalarMul3(t *testing.T) { assert := test.NewAssert(t) var r fr_bls381.Element @@ -617,6 +558,65 @@ func TestScalarMul5(t *testing.T) { assert.NoError(err) } +type ScalarMulEdgeCasesTest[T, S emulated.FieldParams] struct { + P, R AffinePoint[T] + S emulated.Element[S] +} + +func (c *ScalarMulEdgeCasesTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.ScalarMul(&c.P, &c.S) + cr.AssertIsEqual(res, &c.R) + return nil +} + +func TestScalarMulEdgeCasesEdgeCases(t *testing.T) { + assert := test.NewAssert(t) + var infinity bn254.G1Affine + _, _, 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 := ScalarMulEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{} + + // s * (0,0) == (0,0) + witness1 := ScalarMulEdgeCasesTest[emulated.BN254Fp, emulated.BN254Fr]{ + S: emulated.ValueOf[emulated.BN254Fr](s), + P: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](infinity.X), + Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](infinity.X), + Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), + }, + } + err := test.IsSolved(&circuit, &witness1, testCurve.ScalarField()) + assert.NoError(err) + + // 0 * S == (0,0) + 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), + }, + R: AffinePoint[emulated.BN254Fp]{ + X: emulated.ValueOf[emulated.BN254Fp](infinity.X), + Y: emulated.ValueOf[emulated.BN254Fp](infinity.Y), + }, + } + err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) + assert.NoError(err) +} + type IsOnCurveTest[T, S emulated.FieldParams] struct { Q AffinePoint[T] } @@ -725,3 +725,52 @@ func TestIsOnCurve3(t *testing.T) { err = test.IsSolved(&circuit, &witness2, testCurve.ScalarField()) assert.NoError(err) } + +type JointScalarMulBaseTest[T, S emulated.FieldParams] struct { + P, Q AffinePoint[T] + S1, S2 emulated.Element[S] +} + +func (c *JointScalarMulBaseTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + res := cr.JointScalarMulBase(&c.P, &c.S2, &c.S1) + cr.AssertIsEqual(res, &c.Q) + return nil +} + +func TestJointScalarMulBase(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + var p secp256k1.G1Affine + p.Double(&g) + var r1, r2 fr_secp.Element + _, _ = r1.SetRandom() + _, _ = r2.SetRandom() + s1 := new(big.Int) + r1.BigInt(s1) + s2 := new(big.Int) + r2.BigInt(s2) + var Sj secp256k1.G1Jac + Sj.JointScalarMultiplicationBase(&p, s1, s2) + var S secp256k1.G1Affine + S.FromJacobian(&Sj) + + circuit := JointScalarMulBaseTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{} + witness := JointScalarMulBaseTest[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S1: emulated.ValueOf[emulated.Secp256k1Fr](s1), + S2: emulated.ValueOf[emulated.Secp256k1Fr](s2), + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](p.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](p.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) +}