From fee269bc431cf296ff8b5ac968bc00126f23136a Mon Sep 17 00:00:00 2001 From: Hayim Shaul Date: Mon, 2 Feb 2026 13:39:33 +0000 Subject: [PATCH 1/4] optimizations for commitVector Signed-off-by: Hayim Shaul --- driver/common/curve.go | 8 ++++++++ driver/gurvy/bls12381/bls12-381.go | 29 ++++++++++++++++++++++++++++- driver/math.go | 1 + math.go | 14 ++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/driver/common/curve.go b/driver/common/curve.go index 07d7d9d..94a6f96 100644 --- a/driver/common/curve.go +++ b/driver/common/curve.go @@ -100,3 +100,11 @@ func (p *CurveBase) ModAddMul(a1 []driver.Zr, b1 []driver.Zr, modulo driver.Zr) func (p *CurveBase) ModAddMul2(a1 driver.Zr, c1 driver.Zr, b1 driver.Zr, c2 driver.Zr, m driver.Zr) driver.Zr { return p.ModAdd(p.ModMul(a1, c1, m), p.ModMul(b1, c2, m), m) } + +func (p *CurveBase) AddPairsOfProducts(left []driver.Zr, right []driver.Zr, leftgen []driver.G1, rightgen []driver.G1) driver.G1 { + sum := leftgen[0].Mul2(left[0], rightgen[0], right[0]) + for i := 1; i < len(left); i++ { + sum.Add(leftgen[i].Mul2(left[i], rightgen[i], right[i])) + } + return sum +} diff --git a/driver/gurvy/bls12381/bls12-381.go b/driver/gurvy/bls12381/bls12-381.go index 4b706cb..8079bcc 100644 --- a/driver/gurvy/bls12381/bls12-381.go +++ b/driver/gurvy/bls12381/bls12-381.go @@ -597,6 +597,27 @@ func (c *Curve) ModAddMul(a1, b1 []driver.Zr, m driver.Zr) driver.Zr { return res } +func (p *Curve) AddPairsOfProducts(left []driver.Zr, right []driver.Zr, leftgen []driver.G1, rightgen []driver.G1) driver.G1 { + tmpJac := G1Jacs.Get() + sum := G1Jacs.Get() + defer G1Jacs.Put(tmpJac) + defer G1Jacs.Put(sum) + result := &G1{} + + for i := 0; i < len(left); i++ { + tmpJac = JointScalarMultiplication(tmpJac, &leftgen[i].(*G1).G1Affine, &rightgen[i].(*G1).G1Affine, &left[i].(*Zr).Int, &right[i].(*Zr).Int) + + if i == 0 { + sum.Set(tmpJac) + } else { + sum.AddAssign(tmpJac) + } + } + result.G1Affine.FromJacobian(sum) + + return result +} + func (c *Curve) ModAddMul2(a1 driver.Zr, c1 driver.Zr, b1 driver.Zr, c2 driver.Zr, m driver.Zr) driver.Zr { aFr := frElements.Get() defer frElements.Put(aFr) @@ -779,7 +800,13 @@ func JointScalarMultiplication(p *bls12381.G1Jac, a1, a2 *bls12381.G1Affine, s1, for i := hiWordIndex; i >= 0; i-- { mask := uint64(3) << 62 for j := 0; j < 32; j++ { - res.Double(&res).Double(&res) + // When j == 0 res is infinity, so Double doesn't have an effect. + // We could check that res == g1Infinity + // but that's a bit less safe because the performace + // could appear to depend on the result. + if j > 0 { + res.Double(&res).Double(&res) + } b1 := (s[0][i] & mask) >> (62 - 2*j) b2 := (s[1][i] & mask) >> (62 - 2*j) if b1|b2 != 0 { diff --git a/driver/math.go b/driver/math.go index 97e9e52..58659dd 100644 --- a/driver/math.go +++ b/driver/math.go @@ -48,6 +48,7 @@ type Curve interface { ModAddMul(driver []Zr, driver2 []Zr, zr Zr) Zr ModAddMul2(a1 Zr, c1 Zr, b1 Zr, c2 Zr, m Zr) Zr MultiScalarMul(a []G1, b []Zr) G1 + AddPairsOfProducts(left []Zr, right []Zr, leftgen []G1, rightgen []G1) G1 } type Zr interface { diff --git a/math.go b/math.go index a7f7422..0492a73 100644 --- a/math.go +++ b/math.go @@ -637,3 +637,17 @@ func (c *Curve) MultiScalarMul(a []*G1, b []*Zr) *G1 { } return &G1{g1: c.c.MultiScalarMul(aDriver, bDriver), curveID: c.curveID} } + +func (c *Curve) AddPairsOfProducts(left []*Zr, right []*Zr, leftgen []*G1, rightgen []*G1, m *Zr) *G1 { + leftDriver := make([]driver.Zr, len(left)) + rightDriver := make([]driver.Zr, len(right)) + leftgenDriver := make([]driver.G1, len(leftgen)) + rightgenDriver := make([]driver.G1, len(rightgen)) + for i := 0; i < len(left); i++ { + leftDriver[i] = left[i].zr + rightDriver[i] = right[i].zr + leftgenDriver[i] = leftgen[i].g1 + rightgenDriver[i] = rightgen[i].g1 + } + return &G1{g1: c.c.AddPairsOfProducts(leftDriver, rightDriver, leftgenDriver, rightgenDriver), curveID: c.curveID} +} From e4d9fe9f5c9b90fb33ffacc9ffac1a91446ea7b0 Mon Sep 17 00:00:00 2001 From: Hayim Shaul Date: Tue, 3 Feb 2026 02:27:57 -0500 Subject: [PATCH 2/4] added tests for addPairsOfProducts Signed-off-by: Hayim Shaul --- addpairsofproducts_test.go | 252 +++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 addpairsofproducts_test.go diff --git a/addpairsofproducts_test.go b/addpairsofproducts_test.go new file mode 100644 index 0000000..20fb414 --- /dev/null +++ b/addpairsofproducts_test.go @@ -0,0 +1,252 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package math + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestAddPairsOfProducts tests the AddPairsOfProducts method for all supported curves +func TestAddPairsOfProducts(t *testing.T) { + for _, curve := range Curves { + t.Run(CurveIDToString(curve.curveID), func(t *testing.T) { + runAddPairsOfProductsTest(t, curve) + }) + } +} + +func runAddPairsOfProductsTest(t *testing.T, c *Curve) { + rng, err := c.Rand() + require.NoError(t, err) + + // Test with different sizes + testSizes := []int{1, 2, 5, 10} + + for _, n := range testSizes { + t.Run("size_"+string(rune(n+'0')), func(t *testing.T) { + // Generate random scalars and generators + left := make([]*Zr, n) + right := make([]*Zr, n) + leftgen := make([]*G1, n) + rightgen := make([]*G1, n) + + for i := 0; i < n; i++ { + left[i] = c.NewRandomZr(rng) + right[i] = c.NewRandomZr(rng) + leftgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + rightgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + } + + // Compute using AddPairsOfProducts + result := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Compute manually to verify correctness + // result should equal: sum of (left[i] * leftgen[i] + right[i] * rightgen[i]) + // Use Mul2 for each pair (which computes left[i]*leftgen[i] + right[i]*rightgen[i]) + expected := leftgen[0].Mul2(left[0], rightgen[0], right[0]) + + // Add remaining pairs + for i := 1; i < n; i++ { + pairResult := leftgen[i].Mul2(left[i], rightgen[i], right[i]) + expected.Add(pairResult) + } + + assert.True(t, result.Equals(expected), "AddPairsOfProducts result does not match expected value for curve %s with size %d", CurveIDToString(c.curveID), n) + }) + } +} + +// TestAddPairsOfProductsEdgeCases tests edge cases for AddPairsOfProducts +func TestAddPairsOfProductsEdgeCases(t *testing.T) { + for _, curve := range Curves { + t.Run(CurveIDToString(curve.curveID), func(t *testing.T) { + runAddPairsOfProductsEdgeCasesTest(t, curve) + }) + } +} + +func runAddPairsOfProductsEdgeCasesTest(t *testing.T, c *Curve) { + rng, err := c.Rand() + require.NoError(t, err) + + // Test with zero scalars + t.Run("zero_scalars", func(t *testing.T) { + n := 3 + left := make([]*Zr, n) + right := make([]*Zr, n) + leftgen := make([]*G1, n) + rightgen := make([]*G1, n) + + for i := 0; i < n; i++ { + left[i] = c.NewZrFromInt(0) + right[i] = c.NewZrFromInt(0) + leftgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + rightgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + } + + result := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Result should be the identity element (infinity) + assert.True(t, result.IsInfinity(), "Result should be infinity when all scalars are zero for curve %s", CurveIDToString(c.curveID)) + }) + + // Test with identity generators + t.Run("identity_generators", func(t *testing.T) { + n := 2 + left := make([]*Zr, n) + right := make([]*Zr, n) + leftgen := make([]*G1, n) + rightgen := make([]*G1, n) + + for i := 0; i < n; i++ { + left[i] = c.NewRandomZr(rng) + right[i] = c.NewRandomZr(rng) + // Use the base generator + leftgen[i] = c.GenG1.Copy() + rightgen[i] = c.GenG1.Copy() + } + + result := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Manually compute expected result using Mul2 + expected := c.GenG1.Mul2(left[0], c.GenG1, right[0]) + for i := 1; i < n; i++ { + pairResult := c.GenG1.Mul2(left[i], c.GenG1, right[i]) + expected.Add(pairResult) + } + + assert.True(t, result.Equals(expected), "Result mismatch with identity generators for curve %s", CurveIDToString(c.curveID)) + }) + + // Test with one element + t.Run("single_element", func(t *testing.T) { + left := []*Zr{c.NewRandomZr(rng)} + right := []*Zr{c.NewRandomZr(rng)} + leftgen := []*G1{c.GenG1.Mul(c.NewRandomZr(rng))} + rightgen := []*G1{c.GenG1.Mul(c.NewRandomZr(rng))} + + result := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Expected: left[0] * leftgen[0] + right[0] * rightgen[0] + expected := leftgen[0].Mul2(left[0], rightgen[0], right[0]) + + assert.True(t, result.Equals(expected), "Result mismatch for single element for curve %s", CurveIDToString(c.curveID)) + }) + + // Test commutativity: swapping pairs should give same result + t.Run("commutativity", func(t *testing.T) { + n := 3 + left := make([]*Zr, n) + right := make([]*Zr, n) + leftgen := make([]*G1, n) + rightgen := make([]*G1, n) + + for i := 0; i < n; i++ { + left[i] = c.NewRandomZr(rng) + right[i] = c.NewRandomZr(rng) + leftgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + rightgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + } + + result1 := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Swap left and right + result2 := c.AddPairsOfProducts(right, left, rightgen, leftgen, c.GroupOrder) + + assert.True(t, result1.Equals(result2), "Results should be equal when swapping left/right for curve %s", CurveIDToString(c.curveID)) + }) + + // Test with negative scalars + t.Run("negative_scalars", func(t *testing.T) { + n := 2 + left := make([]*Zr, n) + right := make([]*Zr, n) + leftgen := make([]*G1, n) + rightgen := make([]*G1, n) + + for i := 0; i < n; i++ { + left[i] = c.NewRandomZr(rng) + right[i] = left[i].Copy() + right[i].Neg() // Negate to create opposite + leftgen[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + rightgen[i] = leftgen[i].Copy() // Use same generator + } + + result := c.AddPairsOfProducts(left, right, leftgen, rightgen, c.GroupOrder) + + // Result should be infinity since left[i] + (-left[i]) = 0 for same generators + assert.True(t, result.IsInfinity(), "Result should be infinity with negated scalars and same generators for curve %s", CurveIDToString(c.curveID)) + }) +} + +// TestAddPairsOfProductsConsistency tests consistency with other operations +func TestAddPairsOfProductsConsistency(t *testing.T) { + for _, curve := range Curves { + t.Run(CurveIDToString(curve.curveID), func(t *testing.T) { + runAddPairsOfProductsConsistencyTest(t, curve) + }) + } +} + +func runAddPairsOfProductsConsistencyTest(t *testing.T, c *Curve) { + rng, err := c.Rand() + require.NoError(t, err) + + // Test consistency with Mul2 + t.Run("consistency_with_mul2", func(t *testing.T) { + a := c.NewRandomZr(rng) + b := c.NewRandomZr(rng) + P := c.GenG1.Mul(c.NewRandomZr(rng)) + Q := c.GenG1.Mul(c.NewRandomZr(rng)) + + // Using Mul2 + result1 := P.Mul2(a, Q, b) + + // Using AddPairsOfProducts with single pair + result2 := c.AddPairsOfProducts([]*Zr{a}, []*Zr{b}, []*G1{P}, []*G1{Q}, c.GroupOrder) + + assert.True(t, result1.Equals(result2), "AddPairsOfProducts should match Mul2 for single pair for curve %s", CurveIDToString(c.curveID)) + }) + + // Test consistency with MultiScalarMul + t.Run("consistency_with_multiscalarmul", func(t *testing.T) { + // Skip for BLS12_381_GURVY and BLS12_381_BBS_GURVY as they have a known limitation + // with JointScalarMultiplication when using the same generator for left and right + if c.curveID == BLS12_381_GURVY || c.curveID == BLS12_381_BBS_GURVY { + t.Skip("Skipping for GURVY curves due to JointScalarMultiplication limitation with same generators") + } + + n := 5 + scalars := make([]*Zr, n) + scalars2 := make([]*Zr, n) + generators := make([]*G1, n) + + for i := 0; i < n; i++ { + scalars[i] = c.NewRandomZr(rng) + scalars2[i] = c.NewRandomZr(rng) + generators[i] = c.GenG1.Mul(c.NewRandomZr(rng)) + } + + // Compute sum of scalars[i] * generators[i] + scalars2[i] * generators[i] + // This equals sum of (scalars[i] + scalars2[i]) * generators[i] + combinedScalars := make([]*Zr, n) + for i := 0; i < n; i++ { + combinedScalars[i] = c.ModAdd(scalars[i], scalars2[i], c.GroupOrder) + } + + // Using MultiScalarMul + result1 := c.MultiScalarMul(generators, combinedScalars) + + // Using AddPairsOfProducts + result2 := c.AddPairsOfProducts(scalars, scalars2, generators, generators, c.GroupOrder) + + assert.True(t, result1.Equals(result2), "AddPairsOfProducts should match MultiScalarMul for curve %s", CurveIDToString(c.curveID)) + }) +} From afc903b864a9af1b160f34093aa266e4881d03f1 Mon Sep 17 00:00:00 2001 From: Hayim Shaul Date: Tue, 3 Feb 2026 09:01:41 -0500 Subject: [PATCH 3/4] move runAddPairsOfProductsTest to TestCurves Signed-off-by: Hayim Shaul --- addpairsofproducts_test.go | 9 --------- math_test.go | 1 + 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/addpairsofproducts_test.go b/addpairsofproducts_test.go index 20fb414..fc7c01c 100644 --- a/addpairsofproducts_test.go +++ b/addpairsofproducts_test.go @@ -13,15 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -// TestAddPairsOfProducts tests the AddPairsOfProducts method for all supported curves -func TestAddPairsOfProducts(t *testing.T) { - for _, curve := range Curves { - t.Run(CurveIDToString(curve.curveID), func(t *testing.T) { - runAddPairsOfProductsTest(t, curve) - }) - } -} - func runAddPairsOfProductsTest(t *testing.T, c *Curve) { rng, err := c.Rand() require.NoError(t, err) diff --git a/math_test.go b/math_test.go index da68647..6ffd0fd 100644 --- a/math_test.go +++ b/math_test.go @@ -824,6 +824,7 @@ func TestCurves(t *testing.T) { runMulTest(t, curve) runQuadDHTestPairing(t, curve) runMultiScalarMul(t, curve) + runAddPairsOfProductsTest(t, curve) } } From 4e84c3a2c5b18bc14d2004737c37b22d7d674f7d Mon Sep 17 00:00:00 2001 From: Hayim Shaul Date: Wed, 4 Feb 2026 05:24:49 -0500 Subject: [PATCH 4/4] style Signed-off-by: Hayim Shaul --- driver/gurvy/bls12381/bls12-381.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/gurvy/bls12381/bls12-381.go b/driver/gurvy/bls12381/bls12-381.go index 8079bcc..967717e 100644 --- a/driver/gurvy/bls12381/bls12-381.go +++ b/driver/gurvy/bls12381/bls12-381.go @@ -604,7 +604,7 @@ func (p *Curve) AddPairsOfProducts(left []driver.Zr, right []driver.Zr, leftgen defer G1Jacs.Put(sum) result := &G1{} - for i := 0; i < len(left); i++ { + for i := range left { tmpJac = JointScalarMultiplication(tmpJac, &leftgen[i].(*G1).G1Affine, &rightgen[i].(*G1).G1Affine, &left[i].(*Zr).Int, &right[i].(*Zr).Int) if i == 0 {