Skip to content

Commit 9d2d81a

Browse files
authored
Improve CSP (#1838)
Signed-off-by: Hayim Shaul <hayim.shaul@gmail.com> Signed-off-by: Eyal Kushnir <Eyal.Kushnir@ibm.com>
1 parent 8e138c2 commit 9d2d81a

7 files changed

Lines changed: 1120 additions & 69 deletions

File tree

token/core/zkatdlog/nogh/v1/crypto/rp/csp/csp.go

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,38 @@ import (
1212
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
1313
)
1414

15+
// collapseTrailingEqualPoints folds a maximal trailing run of identical points
16+
// into a single entry whose scalar is the sum of the run's scalars. As MSM is
17+
// linear, MSM(points, scalars) is unchanged.
18+
//
19+
// The CSP generators are power-of-two padded with a trailing run of GenG1, so
20+
// this shrinks the gen_f MSM to its distinct generators. It returns reduced
21+
// views of the inputs and overwrites scalars[start] with the run sum, so the
22+
// caller must not reuse scalars afterwards. The slices are returned unchanged
23+
// when the trailing run has length < 2.
24+
func collapseTrailingEqualPoints(points []*mathlib.G1, scalars []*mathlib.Zr, curve *mathlib.Curve) ([]*mathlib.G1, []*mathlib.Zr) {
25+
n := len(points)
26+
if n < 2 {
27+
return points, scalars
28+
}
29+
30+
start := n - 1
31+
for start > 0 && points[start-1].Equals(points[n-1]) {
32+
start--
33+
}
34+
if start == n-1 {
35+
return points, scalars // no run to fold
36+
}
37+
38+
sum := scalars[start].Copy()
39+
for i := start + 1; i < n; i++ {
40+
sum = curve.ModAdd(sum, scalars[i], curve.GroupOrder)
41+
}
42+
scalars[start] = sum
43+
44+
return points[:start+1], scalars[:start+1]
45+
}
46+
1547
// Proof is the non-interactive compressed sigma-protocol proof for a linear
1648
// form evaluation over a Pedersen commitment.
1749
//
@@ -295,25 +327,39 @@ func (v *verifier) Verify(proof *Proof) error {
295327
comScalars = append(comScalars, mulScratch.Copy())
296328
}
297329

298-
com := v.Curve.MultiScalarMul(comPoints, comScalars)
299-
300330
// Compute the coefficient vector s such that
301331
// gen_f = sum_i s[i] · gen[i] and f_f = sum_i s[i] · f[i]
302332
// then evaluate both via a single MSM and a single inner product.
303333
n := 1 << v.NumberOfRounds
304334
s := sVector(n, challenges, v.Curve)
305335

306-
// gen_f = MSM(Generators, s)
307-
genF := v.Curve.MultiScalarMul(v.Generators, s)
308-
309-
// f_f = ⟨s, LinearForm⟩ (scalar-field MSM via ModAddMul)
336+
// f_f = ⟨s, LinearForm⟩ (scalar-field MSM via ModAddMul).
337+
// Computed before the gen_f collapse below, which may truncate s in place.
310338
fF := math.InnerProduct(s, v.LinearForm, v.Curve)
311339

312-
// Final check: com_f^{f_f} == gen_f^{val_f}
313-
lhs := com.Mul(fF)
314-
rhs := genF.Mul(val)
340+
// gen_f = MSM(Generators, s). The generators are power-of-two padded with a
341+
// trailing run of GenG1; fold it away so the MSM runs over distinct points.
342+
genPoints, genScalars := collapseTrailingEqualPoints(v.Generators, s, v.Curve)
343+
344+
// Final check: com^{f_f} == gen_f^{val}, where com = MSM(comPoints, comScalars)
345+
// and gen_f = MSM(genPoints, genScalars). Since MSM is linear this is the same as
346+
// MSM(comPoints ∪ genPoints, [comScalars·f_f, genScalars·(−val)]) == identity,
347+
// so we pre-scale the two scalar vectors (field muls), concatenate, and run a
348+
// single MSM instead of two MSMs plus two EC scalar multiplications.
349+
negVal := v.Curve.ModNeg(val, v.Curve.GroupOrder)
350+
351+
allPoints := make([]*mathlib.G1, 0, len(comPoints)+len(genPoints))
352+
allScalars := make([]*mathlib.Zr, 0, len(comPoints)+len(genPoints))
353+
for i := range comPoints {
354+
allPoints = append(allPoints, comPoints[i])
355+
allScalars = append(allScalars, v.Curve.ModMul(comScalars[i], fF, v.Curve.GroupOrder))
356+
}
357+
for i := range genPoints {
358+
allPoints = append(allPoints, genPoints[i])
359+
allScalars = append(allScalars, v.Curve.ModMul(genScalars[i], negVal, v.Curve.GroupOrder))
360+
}
315361

316-
if !lhs.Equals(rhs) {
362+
if !v.Curve.MultiScalarMul(allPoints, allScalars).IsInfinity() {
317363
return errors.New("CSP proof verification failed")
318364
}
319365

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package csp
8+
9+
import (
10+
"testing"
11+
12+
math "github.com/IBM/mathlib"
13+
bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
// TestFieldArithmeticOptimization verifies that the polynomial expansion
18+
// (c-1)*(c-2) = c² - 3c + 2 holds in field arithmetic.
19+
func TestFieldArithmeticOptimization(t *testing.T) {
20+
curve := math.Curves[math.BN254]
21+
rand, err := curve.Rand()
22+
require.NoError(t, err)
23+
24+
// Generate a random field element c
25+
cZr := curve.NewRandomZr(rand)
26+
var c bn254fr.Element
27+
c.SetBigInt(cZr.BigInt())
28+
29+
// Compute (c-1)
30+
var cMinus1 bn254fr.Element
31+
var one bn254fr.Element
32+
one.SetOne()
33+
cMinus1.Sub(&c, &one)
34+
35+
// Compute (c-2)
36+
var cMinus2 bn254fr.Element
37+
var two bn254fr.Element
38+
two.SetInt64(2)
39+
cMinus2.Sub(&c, &two)
40+
41+
// Direct multiplication: (c-1)*(c-2)
42+
var directProduct bn254fr.Element
43+
directProduct.Mul(&cMinus1, &cMinus2)
44+
45+
// Optimized computation: c² - 3c + 2
46+
var cSquared bn254fr.Element
47+
cSquared.Mul(&c, &c)
48+
49+
var three bn254fr.Element
50+
three.SetInt64(3)
51+
52+
var threeC bn254fr.Element
53+
threeC.Mul(&three, &c)
54+
55+
var optimized bn254fr.Element
56+
optimized.Sub(&cSquared, &threeC)
57+
optimized.Add(&optimized, &two)
58+
59+
// Verify they are equal
60+
require.Equal(t, directProduct.Bytes(), optimized.Bytes(),
61+
"(c-1)*(c-2) should equal c² - 3c + 2 in field arithmetic")
62+
63+
t.Logf("✓ Verified: (c-1)*(c-2) = c² - 3c + 2")
64+
t.Logf(" Direct product: %x", directProduct.Bytes())
65+
t.Logf(" Optimized: %x", optimized.Bytes())
66+
}
67+
68+
// TestFieldArithmeticOptimizationPattern verifies the general pattern:
69+
// (c-i)*(c-j) = c² - c*(i+j) + i*j
70+
func TestFieldArithmeticOptimizationPattern(t *testing.T) {
71+
curve := math.Curves[math.BN254]
72+
rand, err := curve.Rand()
73+
require.NoError(t, err)
74+
75+
// Generate a random field element c
76+
cZr := curve.NewRandomZr(rand)
77+
var c bn254fr.Element
78+
c.SetBigInt(cZr.BigInt())
79+
80+
// Test several (i, j) pairs
81+
testCases := []struct {
82+
i, j int64
83+
}{
84+
{0, 3}, // (c-0)*(c-3) = c² - 3c
85+
{1, 2}, // (c-1)*(c-2) = c² - 3c + 2
86+
{0, 7}, // (c-0)*(c-7) = c² - 7c
87+
{2, 5}, // (c-2)*(c-5) = c² - 7c + 10
88+
}
89+
90+
for _, tc := range testCases {
91+
// Compute (c-i)
92+
var cMinusI bn254fr.Element
93+
var iElem bn254fr.Element
94+
iElem.SetInt64(tc.i)
95+
cMinusI.Sub(&c, &iElem)
96+
97+
// Compute (c-j)
98+
var cMinusJ bn254fr.Element
99+
var jElem bn254fr.Element
100+
jElem.SetInt64(tc.j)
101+
cMinusJ.Sub(&c, &jElem)
102+
103+
// Direct multiplication: (c-i)*(c-j)
104+
var directProduct bn254fr.Element
105+
directProduct.Mul(&cMinusI, &cMinusJ)
106+
107+
// Optimized computation: c² - c*(i+j) + i*j
108+
var cSquared bn254fr.Element
109+
cSquared.Mul(&c, &c)
110+
111+
var iPlusJ bn254fr.Element
112+
iPlusJ.SetInt64(tc.i + tc.j)
113+
114+
var cTimesSumIJ bn254fr.Element
115+
cTimesSumIJ.Mul(&c, &iPlusJ)
116+
117+
var iTimesJ bn254fr.Element
118+
iTimesJ.SetInt64(tc.i * tc.j)
119+
120+
var optimized bn254fr.Element
121+
optimized.Sub(&cSquared, &cTimesSumIJ)
122+
optimized.Add(&optimized, &iTimesJ)
123+
124+
// Verify they are equal
125+
require.Equal(t, directProduct.Bytes(), optimized.Bytes(),
126+
"(c-%d)*(c-%d) should equal c² - c*%d + %d in field arithmetic",
127+
tc.i, tc.j, tc.i+tc.j, tc.i*tc.j)
128+
129+
t.Logf("✓ Verified: (c-%d)*(c-%d) = c² - c*%d + %d", tc.i, tc.j, tc.i+tc.j, tc.i*tc.j)
130+
}
131+
}
132+
133+
// Made with Bob

0 commit comments

Comments
 (0)