From ee194db04f33f67040e21075ad7cc1d9b80619b5 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Nov 2023 14:13:58 +0100 Subject: [PATCH 01/13] refactor: use dedicated MultiScalarMul in kzg --- std/commitments/kzg/verifier.go | 45 ++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index bd05205cc9..29522ce461 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -531,15 +531,13 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit } // fold the committed quotients compute ∑ᵢλᵢ[Hᵢ(α)]G₁ - var foldedQuotients *G1El - quotients := make([]G1El, len(proofs)) + quotients := make([]*G1El, len(proofs)) for i := 0; i < len(randomNumbers); i++ { - quotients[i] = proofs[i].Quotient + quotients[i] = &proofs[i].Quotient } - foldedQuotients = v.curve.ScalarMul("ients[0], randomNumbers[0]) - for i := 1; i < len(digests); i++ { - tmp := v.curve.ScalarMul("ients[i], randomNumbers[i]) - foldedQuotients = v.curve.Add(tmp, foldedQuotients) + foldedQuotients, err := v.curve.MultiScalarMul(quotients, randomNumbers) + if err != nil { + return fmt.Errorf("fold quotients: %w", err) } // fold digests and evals @@ -551,7 +549,10 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit evals[i] = proofs[i].ClaimedValue } - foldedDigests, foldedEvals := v.fold(digests, evals, randomNumbers) + foldedDigests, foldedEvals, err := v.fold(digests, evals, randomNumbers) + if err != nil { + return fmt.Errorf("fold: %w", err) + } // compute commitment to folded Eval [∑ᵢλᵢfᵢ(aᵢ)]G₁ foldedEvalsCommit := v.curve.ScalarMul(&vk.G1, foldedEvals) @@ -563,14 +564,12 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit // combien the points and the quotients using γᵢ // ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁) - var foldedPointsQuotients *G1El for i := 0; i < len(randomNumbers); i++ { randomNumbers[i] = v.scalarApi.Mul(randomNumbers[i], &points[i]) } - foldedPointsQuotients = v.curve.ScalarMul("ients[0], randomNumbers[0]) - for i := 1; i < len(digests); i++ { - tmp = v.curve.ScalarMul("ients[i], randomNumbers[i]) - foldedPointsQuotients = v.curve.Add(foldedPointsQuotients, tmp) + foldedPointsQuotients, err := v.curve.MultiScalarMul(quotients, randomNumbers) + if err != nil { + return fmt.Errorf("fold point quotients: %w", err) } // ∑ᵢλᵢ[f_i(α)]G₁ - [∑ᵢλᵢfᵢ(aᵢ)]G₁ + ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁) @@ -620,7 +619,10 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b for i := 2; i < nbDigests; i++ { gammai[i] = v.scalarApi.Mul(gammai[i-1], gamma) } - foldedDigests, foldedEvaluations := v.fold(digests, batchOpeningProof.ClaimedValues, gammai) + foldedDigests, foldedEvaluations, err := v.fold(digests, batchOpeningProof.ClaimedValues, gammai) + if err != nil { + return OpeningProof[FR, G1El]{}, Commitment[G1El]{}, fmt.Errorf("fold: %w", err) + } return OpeningProof[FR, G1El]{ Quotient: batchOpeningProof.Quotient, ClaimedValue: *foldedEvaluations, @@ -667,7 +669,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], return gammaS, nil } -func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai []emulated.Element[FR], ci []*emulated.Element[FR]) (Commitment[G1El], *emulated.Element[FR]) { +func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai []emulated.Element[FR], ci []*emulated.Element[FR]) (Commitment[G1El], *emulated.Element[FR], error) { // length inconsistency between digests and evaluations should have been done before calling this function nbDigests := len(digests) @@ -680,15 +682,18 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai [] } // fold the digests ∑ᵢ[cᵢ]([fᵢ(α)]G₁) - foldedDigest := v.curve.ScalarMul(&digests[0].G1El, ci[0]) - for i := 1; i < nbDigests; i++ { - tmp := v.curve.ScalarMul(&digests[i].G1El, ci[i]) - foldedDigest = v.curve.Add(tmp, foldedDigest) + digestPoints := make([]*G1El, len(digests)) + for i := range digestPoints { + digestPoints[i] = &digests[i].G1El + } + foldedDigest, err := v.curve.MultiScalarMul(digestPoints, ci) + if err != nil { + return Commitment[G1El]{}, nil, fmt.Errorf("fold digests: %w", err) } // folding done return Commitment[G1El]{ G1El: *foldedDigest, - }, foldedEvaluations + }, foldedEvaluations, nil } From 3f9c1e11d6d12a1ce9ac0876c99134b5b8b4a6de Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Nov 2023 16:08:09 +0100 Subject: [PATCH 02/13] feat: add algebra options to modify scalar muls --- std/algebra/algopts/algopts.go | 29 +++++++++++ std/algebra/emulated/sw_emulated/point.go | 16 ++++-- .../emulated/sw_emulated/point_test.go | 50 +++++++++++++++++++ std/algebra/interfaces.go | 7 +-- std/algebra/native/sw_bls12377/pairing2.go | 9 ++-- std/algebra/native/sw_bls24315/pairing2.go | 9 ++-- 6 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 std/algebra/algopts/algopts.go diff --git a/std/algebra/algopts/algopts.go b/std/algebra/algopts/algopts.go new file mode 100644 index 0000000000..c09376d919 --- /dev/null +++ b/std/algebra/algopts/algopts.go @@ -0,0 +1,29 @@ +package algopts + +import "fmt" + +type algebraCfg struct { + NbScalarBits int +} + +type AlgebraOption func(*algebraCfg) error + +func WithNbBits(bits int) AlgebraOption { + return func(ac *algebraCfg) error { + if ac.NbScalarBits != 0 { + return fmt.Errorf("WithNbBits already set") + } + ac.NbScalarBits = bits + return nil + } +} + +func NewConfig(opts ...AlgebraOption) (*algebraCfg, error) { + ret := new(algebraCfg) + for i := range opts { + if err := opts[i](ret); err != nil { + return nil, err + } + } + return ret, nil +} diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index c8467a479d..9dd89da799 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "golang.org/x/exp/slices" ) @@ -465,7 +466,11 @@ func (c *Curve[B, S]) Lookup2(b0, b1 frontend.Variable, i0, i1, i2, i3 *AffinePo // [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]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *AffinePoint[B] { +func (c *Curve[B, S]) ScalarMul(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)) + } // if p=(0,0) we assign a dummy (0,1) to p and continue selector := c.api.And(c.baseApi.IsZero(&p.X), c.baseApi.IsZero(&p.Y)) @@ -476,6 +481,9 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *Affi sr := c.scalarApi.Reduce(s) sBits := c.scalarApi.ToBits(sr) n := st.Modulus().BitLen() + if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { + n = cfg.NbScalarBits + } // i = 1 Rb := c.triple(p) @@ -518,7 +526,7 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *Affi // // [HMV04]: https://link.springer.com/book/10.1007/b97644 // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf -func (c *Curve[B, S]) ScalarMulBase(s *emulated.Element[S]) *AffinePoint[B] { +func (c *Curve[B, S]) ScalarMulBase(s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { g := c.Generator() gm := c.GeneratorMultiples() @@ -634,7 +642,7 @@ func (c *Curve[B, S]) JointScalarMulBase(p *AffinePoint[B], s2, s1 *emulated.Ele // // For the points and scalars the same considerations apply as for // [Curve.AddUnified] and [Curve.SalarMul]. -func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[S]) (*AffinePoint[B], error) { +func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[S], opts ...algopts.AlgebraOption) (*AffinePoint[B], error) { if len(p) != len(s) { return nil, fmt.Errorf("mismatching points and scalars slice lengths") } @@ -646,7 +654,7 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ } res := c.ScalarMul(p[0], s[0]) for i := 1; i < len(p); i++ { - q := c.ScalarMul(p[i], s[i]) + q := c.ScalarMul(p[i], s[i], opts...) res = c.AddUnified(res, q) } return res, nil diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 0a3cd8326f..b6e0771d83 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -18,6 +18,7 @@ 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/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/emulated/emparams" "github.com/consensys/gnark/test" @@ -987,3 +988,52 @@ func TestMultiScalarMul(t *testing.T) { }, &assignment, ecc.BN254.ScalarField()) assert.NoError(err) } + +type ScalarMulTestBounded[T, S emulated.FieldParams] struct { + P, Q AffinePoint[T] + S emulated.Element[S] + bits int +} + +func (c *ScalarMulTestBounded[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, algopts.WithNbBits(c.bits)) + cr.AssertIsEqual(res, &c.Q) + return nil +} + +func TestScalarMulBounded(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + var r fr_secp.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + nbBits := 13 + mask := big.NewInt(1) + mask.Lsh(mask, uint(nbBits)) + mask.Sub(mask, big.NewInt(1)) + s.And(s, mask) + var S secp256k1.G1Affine + S.ScalarMultiplication(&g, s) + + circuit := ScalarMulTestBounded[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + bits: nbBits, + } + witness := ScalarMulTestBounded[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) +} diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index ea9a6f29ac..d3ed6ac9df 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -2,6 +2,7 @@ package algebra import ( "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/math/emulated" ) @@ -25,16 +26,16 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface { // ScalarMul returns the scalar multiplication of the point by a scalar. It // does not modify the inputs. - ScalarMul(*G1El, *emulated.Element[FR]) *G1El + ScalarMul(*G1El, *emulated.Element[FR], ...algopts.AlgebraOption) *G1El // ScalarMulBase returns the scalar multiplication of the curve base point // by a scalar. It does not modify the scalar. - ScalarMulBase(*emulated.Element[FR]) *G1El + ScalarMulBase(*emulated.Element[FR], ...algopts.AlgebraOption) *G1El // MultiScalarMul computes the sum ∑ s_i P_i for the input // scalars s_i and points P_i. It returns an error if the input lengths // mismatch. - MultiScalarMul([]*G1El, []*emulated.Element[FR]) (*G1El, error) + MultiScalarMul([]*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) (*G1El, error) // MarshalG1 returns the binary decomposition G1.X || G1.Y. It matches the // output of gnark-crypto's Marshal method on G1 points. diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index a75740febd..2837445660 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -9,6 +9,7 @@ import ( fr_bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" fr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/algebra/native/fields_bls12377" "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/math/emulated" @@ -90,7 +91,7 @@ func (c *Curve) Neg(P *G1Affine) *G1Affine { // ScalarMul computes scalar*P and returns the result. It doesn't modify the // inputs. -func (c *Curve) ScalarMul(P *G1Affine, s *Scalar) *G1Affine { +func (c *Curve) ScalarMul(P *G1Affine, s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { res := &G1Affine{ X: P.X, Y: P.Y, @@ -102,7 +103,7 @@ func (c *Curve) ScalarMul(P *G1Affine, s *Scalar) *G1Affine { // ScalarMulBase computes scalar*G where G is the standard base point of the // curve. It doesn't modify the scalar. -func (c *Curve) ScalarMulBase(s *Scalar) *G1Affine { +func (c *Curve) ScalarMulBase(s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { res := new(G1Affine) varScalar := c.packScalarToVar(s) res.ScalarMulBase(c.api, varScalar) @@ -112,7 +113,7 @@ func (c *Curve) ScalarMulBase(s *Scalar) *G1Affine { // MultiScalarMul computes ∑scalars_i * P_i and returns it. It doesn't modify // the inputs. It returns an error if there is a mismatch in the lengths of the // inputs. -func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, error) { +func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) (*G1Affine, error) { if len(P) != len(scalars) { return nil, fmt.Errorf("mismatching points and scalars slice lengths") } @@ -124,7 +125,7 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, err } res := c.ScalarMul(P[0], scalars[0]) for i := 1; i < len(P); i++ { - q := c.ScalarMul(P[i], scalars[i]) + q := c.ScalarMul(P[i], scalars[i], opts...) // check for infinity isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index a84808ab07..b11655d74f 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -9,6 +9,7 @@ import ( fr_bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" fr_bw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/algebra/native/fields_bls24315" "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/math/emulated" @@ -90,7 +91,7 @@ func (c *Curve) Neg(P *G1Affine) *G1Affine { // ScalarMul computes scalar*P and returns the result. It doesn't modify the // inputs. -func (c *Curve) ScalarMul(P *G1Affine, s *Scalar) *G1Affine { +func (c *Curve) ScalarMul(P *G1Affine, s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { res := &G1Affine{ X: P.X, Y: P.Y, @@ -102,7 +103,7 @@ func (c *Curve) ScalarMul(P *G1Affine, s *Scalar) *G1Affine { // ScalarMulBase computes scalar*G where G is the standard base point of the // curve. It doesn't modify the scalar. -func (c *Curve) ScalarMulBase(s *Scalar) *G1Affine { +func (c *Curve) ScalarMulBase(s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { res := new(G1Affine) varScalar := c.packScalarToVar(s) res.ScalarMulBase(c.api, varScalar) @@ -112,7 +113,7 @@ func (c *Curve) ScalarMulBase(s *Scalar) *G1Affine { // MultiScalarMul computes ∑scalars_i * P_i and returns it. It doesn't modify // the inputs. It returns an error if there is a mismatch in the lengths of the // inputs. -func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, error) { +func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) (*G1Affine, error) { if len(P) != len(scalars) { return nil, fmt.Errorf("mismatching points and scalars slice lengths") } @@ -124,7 +125,7 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, err } res := c.ScalarMul(P[0], scalars[0]) for i := 1; i < len(P); i++ { - q := c.ScalarMul(P[i], scalars[i]) + q := c.ScalarMul(P[i], scalars[i], opts...) // check for infinity... isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) From b3cce73b2366609d60d577eb781b50769f34c3ff Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Nov 2023 16:09:21 +0100 Subject: [PATCH 03/13] feat: use only subset of bits for proof folding --- std/commitments/kzg/verifier.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index 29522ce461..4215f1d906 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -31,6 +31,7 @@ import ( kzg_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/kzg" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_bn254" "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" @@ -606,10 +607,18 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b // derive the challenge γ, binded to the point and the commitments gamma, err := v.deriveGamma(point, digests, batchOpeningProof.ClaimedValues, dataTranscript...) if err != nil { - return retP, retC, err + return retP, retC, fmt.Errorf("derive gamma: %w", err) } // fold the claimed values and digests + // compute ∑ᵢ γ^i C_i = C_0 + γ(C_1 + γ(C2 ...)), allowing to bound the scalar multiplication iterations + foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbBits(v.api.Compiler().FieldBitLen())) + for i := len(digests) - 2; i > 0; i-- { + foldedDigests = v.curve.Add(&digests[i].G1El, foldedDigests) + foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbBits(v.api.Compiler().FieldBitLen())) + } + foldedDigests = v.curve.Add(&digests[0].G1El, foldedDigests) + // gammai = [1,γ,γ²,..,γⁿ⁻¹] gammai := make([]*emulated.Element[FR], nbDigests) gammai[0] = v.scalarApi.One() @@ -619,14 +628,17 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b for i := 2; i < nbDigests; i++ { gammai[i] = v.scalarApi.Mul(gammai[i-1], gamma) } - foldedDigests, foldedEvaluations, err := v.fold(digests, batchOpeningProof.ClaimedValues, gammai) - if err != nil { - return OpeningProof[FR, G1El]{}, Commitment[G1El]{}, fmt.Errorf("fold: %w", err) + foldedEvaluations := v.scalarApi.Zero() + for i := 0; i < nbDigests; i++ { + tmp := v.scalarApi.Mul(&batchOpeningProof.ClaimedValues[i], gammai[i]) + foldedEvaluations = v.scalarApi.Add(foldedEvaluations, tmp) } return OpeningProof[FR, G1El]{ - Quotient: batchOpeningProof.Quotient, - ClaimedValue: *foldedEvaluations, - }, foldedDigests, nil + Quotient: batchOpeningProof.Quotient, + ClaimedValue: *foldedEvaluations, + }, Commitment[G1El]{ + G1El: *foldedDigests, + }, nil } @@ -669,7 +681,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], return gammaS, nil } -func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai []emulated.Element[FR], ci []*emulated.Element[FR]) (Commitment[G1El], *emulated.Element[FR], error) { +func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai []emulated.Element[FR], ci []*emulated.Element[FR], algopts ...algopts.AlgebraOption) (Commitment[G1El], *emulated.Element[FR], error) { // length inconsistency between digests and evaluations should have been done before calling this function nbDigests := len(digests) From 0dd22599847bcd05907eaa6fb388450c65a55be9 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 11:24:43 +0100 Subject: [PATCH 04/13] perf: use more precice number of bits for folding --- std/commitments/kzg/verifier.go | 6 ++++-- std/recursion/wrapped_hash.go | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index 4215f1d906..af766ae842 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -597,6 +597,8 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], dataTranscript ...emulated.Element[FR]) (OpeningProof[FR, G1El], Commitment[G1El], error) { var retP OpeningProof[FR, G1El] var retC Commitment[G1El] + // we assume the short hash output size is full byte fitting into the modulus length. + nbScalarBits := ((v.api.Compiler().FieldBitLen()+7)/8 - 1) * 8 nbDigests := len(digests) // check consistency between numbers of claims vs number of digests @@ -612,10 +614,10 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b // fold the claimed values and digests // compute ∑ᵢ γ^i C_i = C_0 + γ(C_1 + γ(C2 ...)), allowing to bound the scalar multiplication iterations - foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbBits(v.api.Compiler().FieldBitLen())) + foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbBits(nbScalarBits)) for i := len(digests) - 2; i > 0; i-- { foldedDigests = v.curve.Add(&digests[i].G1El, foldedDigests) - foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbBits(v.api.Compiler().FieldBitLen())) + foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbBits(nbScalarBits)) } foldedDigests = v.curve.Add(&digests[0].G1El, foldedDigests) diff --git a/std/recursion/wrapped_hash.go b/std/recursion/wrapped_hash.go index a4f8c0893e..37dae99259 100644 --- a/std/recursion/wrapped_hash.go +++ b/std/recursion/wrapped_hash.go @@ -207,6 +207,10 @@ func (h *shortCircuitHash) Sum() frontend.Variable { h.wrapped.Write(v) res := h.wrapped.Sum() resBts := bits.ToBinary(h.api, res) + // XXX(ivokub): when changing the number of bits we construct the sum from + // then consider downstream users of short-hash which may assume the number + // of non-zero bits in the output. Most notably, we have the assumption in + // the KZG FoldProof method to avoid doing full scalar mul. res = bits.FromBinary(h.api, resBts[:((h.outSize+7)/8-1)*8]) return res } From 920fb23c119acfd17cd6e4a733e4ef803bf68f92 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 11:40:59 +0100 Subject: [PATCH 05/13] docs: add algopts package documentation --- std/algebra/algopts/algopts.go | 13 ++++++++++++- std/algebra/emulated/sw_emulated/point_test.go | 2 +- std/commitments/kzg/verifier.go | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/std/algebra/algopts/algopts.go b/std/algebra/algopts/algopts.go index c09376d919..59b4a77105 100644 --- a/std/algebra/algopts/algopts.go +++ b/std/algebra/algopts/algopts.go @@ -1,3 +1,8 @@ +// Package algopts provides shareable options for modifying algebraic operations. +// +// This package is separate to avoid cyclic imports and sharing the structures +// between interface definition, implementation getters and actual +// implementations. package algopts import "fmt" @@ -6,9 +11,14 @@ type algebraCfg struct { NbScalarBits int } +// AlgebraOption allows modifying algebraic operation behaviour. type AlgebraOption func(*algebraCfg) error -func WithNbBits(bits int) AlgebraOption { +// WithNbScalarBits defines the number bits when doing scalar multiplication. +// May be used when it is known that only bits least significant bits are +// non-zero. Reduces the cost for scalar multiplication. If not set then full +// width of scalars used. +func WithNbScalarBits(bits int) AlgebraOption { return func(ac *algebraCfg) error { if ac.NbScalarBits != 0 { return fmt.Errorf("WithNbBits already set") @@ -18,6 +28,7 @@ func WithNbBits(bits int) AlgebraOption { } } +// NewConfig applies all given options and returns a configuration to be used. func NewConfig(opts ...AlgebraOption) (*algebraCfg, error) { ret := new(algebraCfg) for i := range opts { diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index b6e0771d83..e3943d79af 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1000,7 +1000,7 @@ func (c *ScalarMulTestBounded[T, S]) Define(api frontend.API) error { if err != nil { return err } - res := cr.ScalarMul(&c.P, &c.S, algopts.WithNbBits(c.bits)) + res := cr.ScalarMul(&c.P, &c.S, algopts.WithNbScalarBits(c.bits)) cr.AssertIsEqual(res, &c.Q) return nil } diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index af766ae842..33429b15e9 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -614,10 +614,10 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b // fold the claimed values and digests // compute ∑ᵢ γ^i C_i = C_0 + γ(C_1 + γ(C2 ...)), allowing to bound the scalar multiplication iterations - foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbBits(nbScalarBits)) + foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbScalarBits(nbScalarBits)) for i := len(digests) - 2; i > 0; i-- { foldedDigests = v.curve.Add(&digests[i].G1El, foldedDigests) - foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbBits(nbScalarBits)) + foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbScalarBits(nbScalarBits)) } foldedDigests = v.curve.Add(&digests[0].G1El, foldedDigests) From d4a75dea49b21943f04ec0058e77a4e1ee0a2056 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 15:44:40 +0100 Subject: [PATCH 06/13] fix: use given nbBits for base scalar mul --- std/algebra/emulated/sw_emulated/point.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 9dd89da799..131de96916 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -527,18 +527,26 @@ func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S], opts // [HMV04]: https://link.springer.com/book/10.1007/b97644 // [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf func (c *Curve[B, S]) ScalarMulBase(s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { - g := c.Generator() - gm := c.GeneratorMultiples() + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(fmt.Sprintf("parse opts: %v", err)) + } var st S sr := c.scalarApi.Reduce(s) sBits := c.scalarApi.ToBits(sr) + n := st.Modulus().BitLen() + if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { + n = cfg.NbScalarBits + } + g := c.Generator() + gm := c.GeneratorMultiples() // i = 1, 2 // gm[0] = 3g, gm[1] = 5g, gm[2] = 7g res := c.Lookup2(sBits[1], sBits[2], g, &gm[0], &gm[1], &gm[2]) - for i := 3; i < st.Modulus().BitLen(); i++ { + for i := 3; i < n; i++ { // gm[i] = [2^i]g tmp := c.add(res, &gm[i]) res = c.Select(sBits[i], tmp, res) From b77d28e3695eab2717a975b0c73e7fd44db43a22 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 15:47:33 +0100 Subject: [PATCH 07/13] feat: define wide scalar mul and wide MSM --- std/algebra/interfaces.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index d3ed6ac9df..e5f1afb086 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -28,6 +28,11 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface { // does not modify the inputs. ScalarMul(*G1El, *emulated.Element[FR], ...algopts.AlgebraOption) *G1El + // WideScalarMul returns the scalar multiplications of the point by scalars. + // This method shares accumulator doubling and is more efficient than + // calling [ScalarMul] multiple times with same point. + WideScalarMul(*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) []*G1El + // ScalarMulBase returns the scalar multiplication of the curve base point // by a scalar. It does not modify the scalar. ScalarMulBase(*emulated.Element[FR], ...algopts.AlgebraOption) *G1El @@ -37,6 +42,11 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface { // mismatch. MultiScalarMul([]*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) (*G1El, error) + // WideScalarMul computes the sums S_j = ∑ s_{i,j} P_i. It uses wide scalar + // multiplication to share the accumulator doubling. It returns an error if + // the input lengths mismatch. + WideMultiScalarMul([]*G1El, [][]*emulated.Element[FR], ...algopts.AlgebraOption) ([]*G1El, error) + // MarshalG1 returns the binary decomposition G1.X || G1.Y. It matches the // output of gnark-crypto's Marshal method on G1 points. MarshalG1(G1El) []frontend.Variable From b6c17d712415e476821316fb4c8efea4d8a97db0 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 15:48:10 +0100 Subject: [PATCH 08/13] feat: implement wide ops for emulated --- std/algebra/emulated/sw_emulated/point.go | 61 +++++++ .../emulated/sw_emulated/point_test.go | 159 ++++++++++++++++++ 2 files changed, 220 insertions(+) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 131de96916..a956828795 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -667,3 +667,64 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ } return res, nil } + +func (c *Curve[B, S]) WideScalarMul(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)) + } + sbits := make([][]frontend.Variable, len(s)) + Q := make([]*AffinePoint[B], len(s)) + for i := range s { + sr := c.scalarApi.Reduce(s[i]) + sbits[i] = c.scalarApi.ToBits(sr) + // assume first bit is zero for now. Subtract later if not the case + Q[i] = p + } + var st S + n := st.Modulus().BitLen() + if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { + n = cfg.NbScalarBits + } + PP := make([]*AffinePoint[B], n) + PP[0] = p + for i := 1; i < n; i++ { + PP[i] = c.double(PP[i-1]) + } + negP := c.Neg(p) + for j := range sbits { + for i := 1; i < n; i++ { + tmp := c.add(Q[j], PP[i]) + Q[j] = c.Select(sbits[j][i], tmp, Q[j]) + } + Q[j] = c.Select(sbits[j][0], Q[j], c.AddUnified(Q[j], negP)) + } + return Q +} + +func (c *Curve[B, S]) WideMultiScalarMul(p []*AffinePoint[B], s [][]*emulated.Element[S], opts ...algopts.AlgebraOption) ([]*AffinePoint[B], error) { + if len(p) == 0 { + return nil, fmt.Errorf("no inputs") + } + width := len(s) + for i := 0; i < width; i++ { + if len(p) != len(s[i]) { + return nil, fmt.Errorf("mismatching points and scalars slice lengths") + } + } + scs := make([]*emulated.Element[S], width) + for i := 0; i < width; i++ { + scs[i] = s[i][0] + } + res := c.WideScalarMul(p[0], scs) + for i := 1; i < len(p); i++ { + for j := 0; j < width; j++ { + scs[j] = s[j][i] + } + q := c.WideScalarMul(p[i], scs, opts...) + for j := range q { + res[j] = c.AddUnified(res[j], q[j]) + } + } + return res, nil +} diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index e3943d79af..86bfca3e8f 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1037,3 +1037,162 @@ func TestScalarMulBounded(t *testing.T) { err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) assert.NoError(err) } + +type WideScalarMulTestBounded[T, S emulated.FieldParams] struct { + P AffinePoint[T] + S []emulated.Element[S] + Res []AffinePoint[T] + bits int +} + +func (c *WideScalarMulTestBounded[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + scalars := make([]*emulated.Element[S], len(c.S)) + for i := range scalars { + scalars[i] = &c.S[i] + } + res := cr.WideScalarMul(&c.P, scalars, algopts.WithNbScalarBits(c.bits)) + for i := range c.Res { + cr.AssertIsEqual(res[i], &c.Res[i]) + } + return nil +} + +func TestWideScalarMulTestBounded(t *testing.T) { + assert := test.NewAssert(t) + _, g := secp256k1.Generators() + + nbBits := 13 + mask := big.NewInt(1) + mask.Lsh(mask, uint(nbBits)) + mask.Sub(mask, big.NewInt(1)) + + nbScs := 3 + wScs := make([]emulated.Element[emparams.Secp256k1Fr], nbScs) + wRes := make([]AffinePoint[emparams.Secp256k1Fp], nbScs) + for i := range wScs { + var r fr_secp.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + s.And(s, mask) + var S secp256k1.G1Affine + S.ScalarMultiplication(&g, s) + wScs[i] = emulated.ValueOf[emparams.Secp256k1Fr](s) + wRes[i] = AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](S.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](S.Y), + } + } + + circuit := WideScalarMulTestBounded[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + S: make([]emulated.Element[emparams.Secp256k1Fr], nbScs), + Res: make([]AffinePoint[emparams.Secp256k1Fp], nbScs), + bits: nbBits, + } + witness := WideScalarMulTestBounded[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ + P: AffinePoint[emulated.Secp256k1Fp]{ + X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), + Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), + }, + S: wScs, + Res: wRes, + } + err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) + assert.NoError(err) +} + +type WideMultiScalarMulTest[T, S emulated.FieldParams] struct { + Points []AffinePoint[T] + Scalars [][]emulated.Element[S] + Res []AffinePoint[T] +} + +func (c *WideMultiScalarMulTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + ps := make([]*AffinePoint[T], len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([][]*emulated.Element[S], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = make([]*emulated.Element[S], len(c.Scalars[i])) + for j := range c.Scalars[i] { + ss[i][j] = &c.Scalars[i][j] + } + } + res, err := cr.WideMultiScalarMul(ps, ss) + if err != nil { + return err + } + for i := range res { + cr.AssertIsEqual(res[i], &c.Res[i]) + } + return nil +} + +func TestWideMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 2 + nbWidth := 3 + P := make([]secp256k1.G1Affine, nbLen) + S := make([][]fr_secp.Element, nbWidth) + for i := 0; i < nbWidth; i++ { + S[i] = make([]fr_secp.Element, nbLen) + for j := 0; j < nbLen; j++ { + S[i][j].SetRandom() + } + } + P[0].ScalarMultiplicationBase(big.NewInt(1)) + for i := 1; i < len(P); i++ { + P[i].Add(&P[i-1], &P[i-1]) + } + R := make([]secp256k1.G1Affine, nbWidth) + for i := 0; i < nbWidth; i++ { + _, err := R[i].MultiExp(P, S[i], ecc.MultiExpConfig{}) + assert.NoError(err) + } + + cP := make([]AffinePoint[emulated.Secp256k1Fp], len(P)) + for i := range cP { + cP[i] = AffinePoint[emparams.Secp256k1Fp]{ + X: emulated.ValueOf[emparams.Secp256k1Fp](P[i].X), + Y: emulated.ValueOf[emparams.Secp256k1Fp](P[i].Y), + } + } + cS := make([][]emulated.Element[emparams.Secp256k1Fr], len(S)) + for i := range cS { + cS[i] = make([]emulated.Element[emparams.Secp256k1Fr], nbLen) + for j := range cS[i] { + cS[i][j] = emulated.ValueOf[emparams.Secp256k1Fr](S[i][j]) + } + } + cR := make([]AffinePoint[emparams.Secp256k1Fp], nbWidth) + for i := range R { + cR[i] = AffinePoint[emparams.Secp256k1Fp]{ + X: emulated.ValueOf[emparams.Secp256k1Fp](R[i].X), + Y: emulated.ValueOf[emparams.Secp256k1Fp](R[i].Y), + } + } + assignment := WideMultiScalarMulTest[emparams.Secp256k1Fp, emparams.Secp256k1Fr]{ + Points: cP, + Scalars: cS, + Res: cR, + } + circuit := &WideMultiScalarMulTest[emparams.Secp256k1Fp, emparams.Secp256k1Fr]{ + Points: make([]AffinePoint[emparams.Secp256k1Fp], nbLen), + Scalars: make([][]emulated.Element[emparams.Secp256k1Fr], nbWidth), + Res: make([]AffinePoint[emparams.Secp256k1Fp], nbWidth), + } + for i := range circuit.Scalars { + circuit.Scalars[i] = make([]emulated.Element[emparams.Secp256k1Fr], nbLen) + } + err := test.IsSolved(circuit, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} From a836cd9f112da9e2bf6451b39cd6779b8e42f3ae Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 15:48:26 +0100 Subject: [PATCH 09/13] feat: wrap scalarmul and MSM in 2chain --- std/algebra/native/sw_bls12377/pairing2.go | 23 ++++++++++++++++++++++ std/algebra/native/sw_bls24315/pairing2.go | 23 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 2837445660..e81637b602 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -136,6 +136,29 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts return res, nil } +func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { + res := make([]*G1Affine, len(scalars)) + for i := range res { + res[i] = c.ScalarMul(P, scalars[i], opts...) + } + return res +} + +func (c *Curve) WideMultiScalarMul(P []*G1Affine, scalars [][]*Scalar, opts ...algopts.AlgebraOption) ([]*G1Affine, error) { + if len(P) == 0 { + return nil, fmt.Errorf("no inputs") + } + var err error + res := make([]*G1Affine, len(scalars)) + for i := range res { + res[i], err = c.MultiScalarMul(P, scalars[i], opts...) + if err != nil { + return nil, err + } + } + return res, nil +} + // Pairing allows computing pairing-related operations in BLS12-377. type Pairing struct { api frontend.API diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index b11655d74f..1bc5551229 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -136,6 +136,29 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts return res, nil } +func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { + res := make([]*G1Affine, len(scalars)) + for i := range res { + res[i] = c.ScalarMul(P, scalars[i], opts...) + } + return res +} + +func (c *Curve) WideMultiScalarMul(P []*G1Affine, scalars [][]*Scalar, opts ...algopts.AlgebraOption) ([]*G1Affine, error) { + if len(P) == 0 { + return nil, fmt.Errorf("no inputs") + } + var err error + res := make([]*G1Affine, len(scalars)) + for i := range res { + res[i], err = c.MultiScalarMul(P, scalars[i], opts...) + if err != nil { + return nil, err + } + } + return res, nil +} + // Pairing allows computing pairing-related operations in BLS24-315. type Pairing struct { api frontend.API From 0f2d1d1a57d6f806fab8dd0b9b5ed3c06121ae6b Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 15:48:58 +0100 Subject: [PATCH 10/13] perf: used wide MSM in KZG batch verification --- std/commitments/kzg/verifier.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index 33429b15e9..725c0267e9 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -530,16 +530,22 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit // TODO use real random numbers, follow the solidity smart contract to know which variables are used as seed randomNumbers[i] = v.scalarApi.Mul(randomNumbers[1], randomNumbers[i-1]) } + randomPointNumbers := make([]*emulated.Element[FR], len(randomNumbers)) + for i := range randomPointNumbers { + randomPointNumbers[i] = v.scalarApi.Mul(randomNumbers[i], &points[i]) + } - // fold the committed quotients compute ∑ᵢλᵢ[Hᵢ(α)]G₁ + // fold the committed quotients compute ∑ᵢλᵢ[Hᵢ(α)]G₁ and + // ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁) quotients := make([]*G1El, len(proofs)) for i := 0; i < len(randomNumbers); i++ { quotients[i] = &proofs[i].Quotient } - foldedQuotients, err := v.curve.MultiScalarMul(quotients, randomNumbers) + foldedQs, err := v.curve.WideMultiScalarMul(quotients, [][]*emulated.Element[FR]{randomNumbers, randomPointNumbers}) if err != nil { return fmt.Errorf("fold quotients: %w", err) } + foldedQuotients, foldedPointsQuotients := foldedQs[0], foldedQs[1] // fold digests and evals evals := make([]emulated.Element[FR], len(digests)) @@ -563,16 +569,6 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit var foldedDigest *G1El foldedDigest = v.curve.Add(&foldedDigests.G1El, tmp) - // combien the points and the quotients using γᵢ - // ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁) - for i := 0; i < len(randomNumbers); i++ { - randomNumbers[i] = v.scalarApi.Mul(randomNumbers[i], &points[i]) - } - foldedPointsQuotients, err := v.curve.MultiScalarMul(quotients, randomNumbers) - if err != nil { - return fmt.Errorf("fold point quotients: %w", err) - } - // ∑ᵢλᵢ[f_i(α)]G₁ - [∑ᵢλᵢfᵢ(aᵢ)]G₁ + ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁) // = [∑ᵢλᵢf_i(α) - ∑ᵢλᵢfᵢ(aᵢ) + ∑ᵢλᵢpᵢHᵢ(α)]G₁ foldedDigest = v.curve.Add(foldedDigest, foldedPointsQuotients) From be6d2c73a8f6a2f16076ce74e70f7c7ecd6cc340 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 18:14:43 +0100 Subject: [PATCH 11/13] feat: add native folding for MSM --- std/algebra/algopts/algopts.go | 16 ++++++++ std/algebra/emulated/sw_emulated/point.go | 37 +++++++++++++---- std/algebra/native/sw_bls12377/pairing2.go | 46 +++++++++++++++------ std/algebra/native/sw_bls24315/pairing2.go | 47 ++++++++++++++++------ 4 files changed, 114 insertions(+), 32 deletions(-) diff --git a/std/algebra/algopts/algopts.go b/std/algebra/algopts/algopts.go index 59b4a77105..9b80803f97 100644 --- a/std/algebra/algopts/algopts.go +++ b/std/algebra/algopts/algopts.go @@ -9,6 +9,7 @@ import "fmt" type algebraCfg struct { NbScalarBits int + FoldMulti bool } // AlgebraOption allows modifying algebraic operation behaviour. @@ -28,6 +29,21 @@ func WithNbScalarBits(bits int) AlgebraOption { } } +// WithFoldingScalarMul can be used when calling MultiScalarMul. By using this +// option we assume that the scalars are `1, scalar, scalar^2, ...`. We use the +// first element as the scalar to be used as a folding coefficients. By using +// this option we avoid one scalar multiplication and do not need to compute the +// powers of the folding coefficient. +func WithFoldingScalarMul() AlgebraOption { + return func(ac *algebraCfg) error { + if ac.FoldMulti { + return fmt.Errorf("withFoldingScalarMul already set") + } + ac.FoldMulti = true + return nil + } +} + // NewConfig applies all given options and returns a configuration to be used. func NewConfig(opts ...AlgebraOption) (*algebraCfg, error) { ret := new(algebraCfg) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index a956828795..b2413c97fb 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -651,21 +651,42 @@ func (c *Curve[B, S]) JointScalarMulBase(p *AffinePoint[B], s2, s1 *emulated.Ele // For the points and scalars the same considerations apply as for // [Curve.AddUnified] and [Curve.SalarMul]. func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[S], opts ...algopts.AlgebraOption) (*AffinePoint[B], error) { - if len(p) != len(s) { - return nil, fmt.Errorf("mismatching points and scalars slice lengths") - } + if len(p) == 0 { return &AffinePoint[B]{ X: *c.baseApi.Zero(), Y: *c.baseApi.Zero(), }, nil } - res := c.ScalarMul(p[0], s[0]) - for i := 1; i < len(p); i++ { - q := c.ScalarMul(p[i], s[i], opts...) - res = c.AddUnified(res, q) + cfg, err := algopts.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + if !cfg.FoldMulti { + // the scalars are unique + if len(p) != len(s) { + return nil, fmt.Errorf("mismatching points and scalars slice lengths") + } + res := c.ScalarMul(p[0], s[0]) + for i := 1; i < len(p); i++ { + q := c.ScalarMul(p[i], s[i], opts...) + res = c.AddUnified(res, q) + } + return res, nil + } else { + // scalars are powers + if len(s) == 0 { + return nil, fmt.Errorf("need scalar for folding") + } + gamma := s[0] + res := c.ScalarMul(p[len(p)-1], gamma, opts...) + for i := len(p) - 2; i > 0; i-- { + res = c.Add(p[i], res) + res = c.ScalarMul(res, gamma, opts...) + } + res = c.Add(p[0], res) + return res, nil } - return res, nil } func (c *Curve[B, S]) WideScalarMul(p *AffinePoint[B], s []*emulated.Element[S], opts ...algopts.AlgebraOption) []*AffinePoint[B] { diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index e81637b602..d1f89ab733 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -114,26 +114,48 @@ func (c *Curve) ScalarMulBase(s *Scalar, opts ...algopts.AlgebraOption) *G1Affin // the inputs. It returns an error if there is a mismatch in the lengths of the // inputs. func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) (*G1Affine, error) { - if len(P) != len(scalars) { - return nil, fmt.Errorf("mismatching points and scalars slice lengths") - } if len(P) == 0 { return &G1Affine{ X: 0, Y: 0, }, nil } - res := c.ScalarMul(P[0], scalars[0]) - for i := 1; i < len(P); i++ { - q := c.ScalarMul(P[i], scalars[i], opts...) + cfg, err := algopts.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + if !cfg.FoldMulti { + if len(P) != len(scalars) { + return nil, fmt.Errorf("mismatching points and scalars slice lengths") + } + res := c.ScalarMul(P[0], scalars[0]) + for i := 1; i < len(P); i++ { + q := c.ScalarMul(P[i], scalars[i], opts...) - // check for infinity - isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) - tmp := c.Add(res, q) - res.X = c.api.Select(isInfinity, res.X, tmp.X) - res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + // check for infinity... + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(res, q) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + } + return res, nil + } else { + // scalars are powers + if len(scalars) == 0 { + return nil, fmt.Errorf("need scalar for folding") + } + gamma := scalars[0] + res := c.ScalarMul(P[len(P)-1], gamma, opts...) + for i := len(P) - 2; i > 0; i-- { + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(P[i], res) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + res = c.ScalarMul(res, gamma, opts...) + } + res = c.Add(P[0], res) + return res, nil } - return res, nil } func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index 1bc5551229..c21a05fbbb 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -114,26 +114,49 @@ func (c *Curve) ScalarMulBase(s *Scalar, opts ...algopts.AlgebraOption) *G1Affin // the inputs. It returns an error if there is a mismatch in the lengths of the // inputs. func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) (*G1Affine, error) { - if len(P) != len(scalars) { - return nil, fmt.Errorf("mismatching points and scalars slice lengths") - } + if len(P) == 0 { return &G1Affine{ X: 0, Y: 0, }, nil } - res := c.ScalarMul(P[0], scalars[0]) - for i := 1; i < len(P); i++ { - q := c.ScalarMul(P[i], scalars[i], opts...) + cfg, err := algopts.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + if !cfg.FoldMulti { + if len(P) != len(scalars) { + return nil, fmt.Errorf("mismatching points and scalars slice lengths") + } + res := c.ScalarMul(P[0], scalars[0]) + for i := 1; i < len(P); i++ { + q := c.ScalarMul(P[i], scalars[i], opts...) - // check for infinity... - isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) - tmp := c.Add(res, q) - res.X = c.api.Select(isInfinity, res.X, tmp.X) - res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + // check for infinity... + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(res, q) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + } + return res, nil + } else { + // scalars are powers + if len(scalars) == 0 { + return nil, fmt.Errorf("need scalar for folding") + } + gamma := scalars[0] + res := c.ScalarMul(P[len(P)-1], gamma, opts...) + for i := len(P) - 2; i > 0; i-- { + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(P[i], res) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) + res = c.ScalarMul(res, gamma, opts...) + } + res = c.Add(P[0], res) + return res, nil } - return res, nil } func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { From 5e0738be1ec7231c880983b43fbf3d118774081f Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 24 Nov 2023 18:14:59 +0100 Subject: [PATCH 12/13] feat: use native folding for MSM --- std/commitments/kzg/verifier.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index 725c0267e9..b4d86705c1 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -525,6 +525,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit seed := whSnark.Sum() binSeed := bits.ToBinary(v.api, seed, bits.WithNbDigits(fr.Modulus().BitLen())) randomNumbers[1] = v.scalarApi.FromBits(binSeed...) + nbScalarBits := ((v.api.Compiler().FieldBitLen()+7)/8 - 1) * 8 for i := 2; i < len(randomNumbers); i++ { // TODO use real random numbers, follow the solidity smart contract to know which variables are used as seed @@ -541,11 +542,14 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit for i := 0; i < len(randomNumbers); i++ { quotients[i] = &proofs[i].Quotient } - foldedQs, err := v.curve.WideMultiScalarMul(quotients, [][]*emulated.Element[FR]{randomNumbers, randomPointNumbers}) + foldedQuotients, err := v.curve.MultiScalarMul(quotients, []*emulated.Element[FR]{randomNumbers[1]}, algopts.WithFoldingScalarMul(), algopts.WithNbScalarBits(nbScalarBits)) if err != nil { return fmt.Errorf("fold quotients: %w", err) } - foldedQuotients, foldedPointsQuotients := foldedQs[0], foldedQs[1] + foldedPointsQuotients, err := v.curve.MultiScalarMul(quotients, randomPointNumbers) + if err != nil { + return fmt.Errorf("fold point quotients: %w", err) + } // fold digests and evals evals := make([]emulated.Element[FR], len(digests)) @@ -610,12 +614,14 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b // fold the claimed values and digests // compute ∑ᵢ γ^i C_i = C_0 + γ(C_1 + γ(C2 ...)), allowing to bound the scalar multiplication iterations - foldedDigests := v.curve.ScalarMul(&digests[len(digests)-1].G1El, gamma, algopts.WithNbScalarBits(nbScalarBits)) - for i := len(digests) - 2; i > 0; i-- { - foldedDigests = v.curve.Add(&digests[i].G1El, foldedDigests) - foldedDigests = v.curve.ScalarMul(foldedDigests, gamma, algopts.WithNbScalarBits(nbScalarBits)) + digestsP := make([]*G1El, len(digests)) + for i := range digestsP { + digestsP[i] = &digests[i].G1El + } + foldedDigests, err := v.curve.MultiScalarMul(digestsP, []*emulated.Element[FR]{gamma}, algopts.WithNbScalarBits(nbScalarBits), algopts.WithFoldingScalarMul()) + if err != nil { + return retP, retC, fmt.Errorf("multi scalar mul: %w", err) } - foldedDigests = v.curve.Add(&digests[0].G1El, foldedDigests) // gammai = [1,γ,γ²,..,γⁿ⁻¹] gammai := make([]*emulated.Element[FR], nbDigests) From 80deb093ca0e8454603d68c8bf264b1874bbc77e Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Nov 2023 15:05:25 +0100 Subject: [PATCH 13/13] chore: remove Wide methods --- std/algebra/emulated/sw_emulated/point.go | 61 ------- .../emulated/sw_emulated/point_test.go | 159 ------------------ std/algebra/interfaces.go | 10 -- std/algebra/native/sw_bls12377/pairing2.go | 23 --- std/algebra/native/sw_bls24315/pairing2.go | 23 --- 5 files changed, 276 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index b2413c97fb..4fd266e732 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -688,64 +688,3 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ return res, nil } } - -func (c *Curve[B, S]) WideScalarMul(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)) - } - sbits := make([][]frontend.Variable, len(s)) - Q := make([]*AffinePoint[B], len(s)) - for i := range s { - sr := c.scalarApi.Reduce(s[i]) - sbits[i] = c.scalarApi.ToBits(sr) - // assume first bit is zero for now. Subtract later if not the case - Q[i] = p - } - var st S - n := st.Modulus().BitLen() - if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { - n = cfg.NbScalarBits - } - PP := make([]*AffinePoint[B], n) - PP[0] = p - for i := 1; i < n; i++ { - PP[i] = c.double(PP[i-1]) - } - negP := c.Neg(p) - for j := range sbits { - for i := 1; i < n; i++ { - tmp := c.add(Q[j], PP[i]) - Q[j] = c.Select(sbits[j][i], tmp, Q[j]) - } - Q[j] = c.Select(sbits[j][0], Q[j], c.AddUnified(Q[j], negP)) - } - return Q -} - -func (c *Curve[B, S]) WideMultiScalarMul(p []*AffinePoint[B], s [][]*emulated.Element[S], opts ...algopts.AlgebraOption) ([]*AffinePoint[B], error) { - if len(p) == 0 { - return nil, fmt.Errorf("no inputs") - } - width := len(s) - for i := 0; i < width; i++ { - if len(p) != len(s[i]) { - return nil, fmt.Errorf("mismatching points and scalars slice lengths") - } - } - scs := make([]*emulated.Element[S], width) - for i := 0; i < width; i++ { - scs[i] = s[i][0] - } - res := c.WideScalarMul(p[0], scs) - for i := 1; i < len(p); i++ { - for j := 0; j < width; j++ { - scs[j] = s[j][i] - } - q := c.WideScalarMul(p[i], scs, opts...) - for j := range q { - res[j] = c.AddUnified(res[j], q[j]) - } - } - return res, nil -} diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 86bfca3e8f..e3943d79af 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -1037,162 +1037,3 @@ func TestScalarMulBounded(t *testing.T) { err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) assert.NoError(err) } - -type WideScalarMulTestBounded[T, S emulated.FieldParams] struct { - P AffinePoint[T] - S []emulated.Element[S] - Res []AffinePoint[T] - bits int -} - -func (c *WideScalarMulTestBounded[T, S]) Define(api frontend.API) error { - cr, err := New[T, S](api, GetCurveParams[T]()) - if err != nil { - return err - } - scalars := make([]*emulated.Element[S], len(c.S)) - for i := range scalars { - scalars[i] = &c.S[i] - } - res := cr.WideScalarMul(&c.P, scalars, algopts.WithNbScalarBits(c.bits)) - for i := range c.Res { - cr.AssertIsEqual(res[i], &c.Res[i]) - } - return nil -} - -func TestWideScalarMulTestBounded(t *testing.T) { - assert := test.NewAssert(t) - _, g := secp256k1.Generators() - - nbBits := 13 - mask := big.NewInt(1) - mask.Lsh(mask, uint(nbBits)) - mask.Sub(mask, big.NewInt(1)) - - nbScs := 3 - wScs := make([]emulated.Element[emparams.Secp256k1Fr], nbScs) - wRes := make([]AffinePoint[emparams.Secp256k1Fp], nbScs) - for i := range wScs { - var r fr_secp.Element - _, _ = r.SetRandom() - s := new(big.Int) - r.BigInt(s) - s.And(s, mask) - var S secp256k1.G1Affine - S.ScalarMultiplication(&g, s) - wScs[i] = emulated.ValueOf[emparams.Secp256k1Fr](s) - wRes[i] = AffinePoint[emulated.Secp256k1Fp]{ - X: emulated.ValueOf[emulated.Secp256k1Fp](S.X), - Y: emulated.ValueOf[emulated.Secp256k1Fp](S.Y), - } - } - - circuit := WideScalarMulTestBounded[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ - S: make([]emulated.Element[emparams.Secp256k1Fr], nbScs), - Res: make([]AffinePoint[emparams.Secp256k1Fp], nbScs), - bits: nbBits, - } - witness := WideScalarMulTestBounded[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{ - P: AffinePoint[emulated.Secp256k1Fp]{ - X: emulated.ValueOf[emulated.Secp256k1Fp](g.X), - Y: emulated.ValueOf[emulated.Secp256k1Fp](g.Y), - }, - S: wScs, - Res: wRes, - } - err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) - assert.NoError(err) -} - -type WideMultiScalarMulTest[T, S emulated.FieldParams] struct { - Points []AffinePoint[T] - Scalars [][]emulated.Element[S] - Res []AffinePoint[T] -} - -func (c *WideMultiScalarMulTest[T, S]) Define(api frontend.API) error { - cr, err := New[T, S](api, GetCurveParams[T]()) - if err != nil { - return err - } - ps := make([]*AffinePoint[T], len(c.Points)) - for i := range c.Points { - ps[i] = &c.Points[i] - } - ss := make([][]*emulated.Element[S], len(c.Scalars)) - for i := range c.Scalars { - ss[i] = make([]*emulated.Element[S], len(c.Scalars[i])) - for j := range c.Scalars[i] { - ss[i][j] = &c.Scalars[i][j] - } - } - res, err := cr.WideMultiScalarMul(ps, ss) - if err != nil { - return err - } - for i := range res { - cr.AssertIsEqual(res[i], &c.Res[i]) - } - return nil -} - -func TestWideMultiScalarMul(t *testing.T) { - assert := test.NewAssert(t) - nbLen := 2 - nbWidth := 3 - P := make([]secp256k1.G1Affine, nbLen) - S := make([][]fr_secp.Element, nbWidth) - for i := 0; i < nbWidth; i++ { - S[i] = make([]fr_secp.Element, nbLen) - for j := 0; j < nbLen; j++ { - S[i][j].SetRandom() - } - } - P[0].ScalarMultiplicationBase(big.NewInt(1)) - for i := 1; i < len(P); i++ { - P[i].Add(&P[i-1], &P[i-1]) - } - R := make([]secp256k1.G1Affine, nbWidth) - for i := 0; i < nbWidth; i++ { - _, err := R[i].MultiExp(P, S[i], ecc.MultiExpConfig{}) - assert.NoError(err) - } - - cP := make([]AffinePoint[emulated.Secp256k1Fp], len(P)) - for i := range cP { - cP[i] = AffinePoint[emparams.Secp256k1Fp]{ - X: emulated.ValueOf[emparams.Secp256k1Fp](P[i].X), - Y: emulated.ValueOf[emparams.Secp256k1Fp](P[i].Y), - } - } - cS := make([][]emulated.Element[emparams.Secp256k1Fr], len(S)) - for i := range cS { - cS[i] = make([]emulated.Element[emparams.Secp256k1Fr], nbLen) - for j := range cS[i] { - cS[i][j] = emulated.ValueOf[emparams.Secp256k1Fr](S[i][j]) - } - } - cR := make([]AffinePoint[emparams.Secp256k1Fp], nbWidth) - for i := range R { - cR[i] = AffinePoint[emparams.Secp256k1Fp]{ - X: emulated.ValueOf[emparams.Secp256k1Fp](R[i].X), - Y: emulated.ValueOf[emparams.Secp256k1Fp](R[i].Y), - } - } - assignment := WideMultiScalarMulTest[emparams.Secp256k1Fp, emparams.Secp256k1Fr]{ - Points: cP, - Scalars: cS, - Res: cR, - } - circuit := &WideMultiScalarMulTest[emparams.Secp256k1Fp, emparams.Secp256k1Fr]{ - Points: make([]AffinePoint[emparams.Secp256k1Fp], nbLen), - Scalars: make([][]emulated.Element[emparams.Secp256k1Fr], nbWidth), - Res: make([]AffinePoint[emparams.Secp256k1Fp], nbWidth), - } - for i := range circuit.Scalars { - circuit.Scalars[i] = make([]emulated.Element[emparams.Secp256k1Fr], nbLen) - } - err := test.IsSolved(circuit, &assignment, ecc.BN254.ScalarField()) - assert.NoError(err) -} diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index e5f1afb086..d3ed6ac9df 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -28,11 +28,6 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface { // does not modify the inputs. ScalarMul(*G1El, *emulated.Element[FR], ...algopts.AlgebraOption) *G1El - // WideScalarMul returns the scalar multiplications of the point by scalars. - // This method shares accumulator doubling and is more efficient than - // calling [ScalarMul] multiple times with same point. - WideScalarMul(*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) []*G1El - // ScalarMulBase returns the scalar multiplication of the curve base point // by a scalar. It does not modify the scalar. ScalarMulBase(*emulated.Element[FR], ...algopts.AlgebraOption) *G1El @@ -42,11 +37,6 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface { // mismatch. MultiScalarMul([]*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) (*G1El, error) - // WideScalarMul computes the sums S_j = ∑ s_{i,j} P_i. It uses wide scalar - // multiplication to share the accumulator doubling. It returns an error if - // the input lengths mismatch. - WideMultiScalarMul([]*G1El, [][]*emulated.Element[FR], ...algopts.AlgebraOption) ([]*G1El, error) - // MarshalG1 returns the binary decomposition G1.X || G1.Y. It matches the // output of gnark-crypto's Marshal method on G1 points. MarshalG1(G1El) []frontend.Variable diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index d1f89ab733..a8fb6f241f 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -158,29 +158,6 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts } } -func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { - res := make([]*G1Affine, len(scalars)) - for i := range res { - res[i] = c.ScalarMul(P, scalars[i], opts...) - } - return res -} - -func (c *Curve) WideMultiScalarMul(P []*G1Affine, scalars [][]*Scalar, opts ...algopts.AlgebraOption) ([]*G1Affine, error) { - if len(P) == 0 { - return nil, fmt.Errorf("no inputs") - } - var err error - res := make([]*G1Affine, len(scalars)) - for i := range res { - res[i], err = c.MultiScalarMul(P, scalars[i], opts...) - if err != nil { - return nil, err - } - } - return res, nil -} - // Pairing allows computing pairing-related operations in BLS12-377. type Pairing struct { api frontend.API diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index c21a05fbbb..7e892e3104 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -159,29 +159,6 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts } } -func (c *Curve) WideScalarMul(P *G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) []*G1Affine { - res := make([]*G1Affine, len(scalars)) - for i := range res { - res[i] = c.ScalarMul(P, scalars[i], opts...) - } - return res -} - -func (c *Curve) WideMultiScalarMul(P []*G1Affine, scalars [][]*Scalar, opts ...algopts.AlgebraOption) ([]*G1Affine, error) { - if len(P) == 0 { - return nil, fmt.Errorf("no inputs") - } - var err error - res := make([]*G1Affine, len(scalars)) - for i := range res { - res[i], err = c.MultiScalarMul(P, scalars[i], opts...) - if err != nil { - return nil, err - } - } - return res, nil -} - // Pairing allows computing pairing-related operations in BLS24-315. type Pairing struct { api frontend.API