Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 56 additions & 10 deletions token/core/zkatdlog/nogh/v1/crypto/rp/csp/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,38 @@ import (
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
)

// collapseTrailingEqualPoints folds a maximal trailing run of identical points
// into a single entry whose scalar is the sum of the run's scalars. As MSM is
// linear, MSM(points, scalars) is unchanged.
//
// The CSP generators are power-of-two padded with a trailing run of GenG1, so
// this shrinks the gen_f MSM to its distinct generators. It returns reduced
// views of the inputs and overwrites scalars[start] with the run sum, so the
// caller must not reuse scalars afterwards. The slices are returned unchanged
// when the trailing run has length < 2.
func collapseTrailingEqualPoints(points []*mathlib.G1, scalars []*mathlib.Zr, curve *mathlib.Curve) ([]*mathlib.G1, []*mathlib.Zr) {
n := len(points)
if n < 2 {
return points, scalars
}

start := n - 1
for start > 0 && points[start-1].Equals(points[n-1]) {
start--
}
if start == n-1 {
return points, scalars // no run to fold
}

sum := scalars[start].Copy()
for i := start + 1; i < n; i++ {
sum = curve.ModAdd(sum, scalars[i], curve.GroupOrder)
}
scalars[start] = sum

return points[:start+1], scalars[:start+1]
}

// Proof is the non-interactive compressed sigma-protocol proof for a linear
// form evaluation over a Pedersen commitment.
//
Expand Down Expand Up @@ -295,25 +327,39 @@ func (v *verifier) Verify(proof *Proof) error {
comScalars = append(comScalars, mulScratch.Copy())
}

com := v.Curve.MultiScalarMul(comPoints, comScalars)

// Compute the coefficient vector s such that
// gen_f = sum_i s[i] · gen[i] and f_f = sum_i s[i] · f[i]
// then evaluate both via a single MSM and a single inner product.
n := 1 << v.NumberOfRounds
s := sVector(n, challenges, v.Curve)

// gen_f = MSM(Generators, s)
genF := v.Curve.MultiScalarMul(v.Generators, s)

// f_f = ⟨s, LinearForm⟩ (scalar-field MSM via ModAddMul)
// f_f = ⟨s, LinearForm⟩ (scalar-field MSM via ModAddMul).
// Computed before the gen_f collapse below, which may truncate s in place.
fF := math.InnerProduct(s, v.LinearForm, v.Curve)

// Final check: com_f^{f_f} == gen_f^{val_f}
lhs := com.Mul(fF)
rhs := genF.Mul(val)
// gen_f = MSM(Generators, s). The generators are power-of-two padded with a
// trailing run of GenG1; fold it away so the MSM runs over distinct points.
genPoints, genScalars := collapseTrailingEqualPoints(v.Generators, s, v.Curve)

// Final check: com^{f_f} == gen_f^{val}, where com = MSM(comPoints, comScalars)
// and gen_f = MSM(genPoints, genScalars). Since MSM is linear this is the same as
// MSM(comPoints ∪ genPoints, [comScalars·f_f, genScalars·(−val)]) == identity,
// so we pre-scale the two scalar vectors (field muls), concatenate, and run a
// single MSM instead of two MSMs plus two EC scalar multiplications.
negVal := v.Curve.ModNeg(val, v.Curve.GroupOrder)

allPoints := make([]*mathlib.G1, 0, len(comPoints)+len(genPoints))
allScalars := make([]*mathlib.Zr, 0, len(comPoints)+len(genPoints))
for i := range comPoints {
allPoints = append(allPoints, comPoints[i])
allScalars = append(allScalars, v.Curve.ModMul(comScalars[i], fF, v.Curve.GroupOrder))
}
for i := range genPoints {
allPoints = append(allPoints, genPoints[i])
allScalars = append(allScalars, v.Curve.ModMul(genScalars[i], negVal, v.Curve.GroupOrder))
}

if !lhs.Equals(rhs) {
if !v.Curve.MultiScalarMul(allPoints, allScalars).IsInfinity() {
return errors.New("CSP proof verification failed")
}

Expand Down
133 changes: 133 additions & 0 deletions token/core/zkatdlog/nogh/v1/crypto/rp/csp/lagrange_arithmetic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package csp

import (
"testing"

math "github.com/IBM/mathlib"
bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/stretchr/testify/require"
)

// TestFieldArithmeticOptimization verifies that the polynomial expansion
// (c-1)*(c-2) = c² - 3c + 2 holds in field arithmetic.
func TestFieldArithmeticOptimization(t *testing.T) {
curve := math.Curves[math.BN254]
rand, err := curve.Rand()
require.NoError(t, err)

// Generate a random field element c
cZr := curve.NewRandomZr(rand)
var c bn254fr.Element
c.SetBigInt(cZr.BigInt())

// Compute (c-1)
var cMinus1 bn254fr.Element
var one bn254fr.Element
one.SetOne()
cMinus1.Sub(&c, &one)

// Compute (c-2)
var cMinus2 bn254fr.Element
var two bn254fr.Element
two.SetInt64(2)
cMinus2.Sub(&c, &two)

// Direct multiplication: (c-1)*(c-2)
var directProduct bn254fr.Element
directProduct.Mul(&cMinus1, &cMinus2)

// Optimized computation: c² - 3c + 2
var cSquared bn254fr.Element
cSquared.Mul(&c, &c)

var three bn254fr.Element
three.SetInt64(3)

var threeC bn254fr.Element
threeC.Mul(&three, &c)

var optimized bn254fr.Element
optimized.Sub(&cSquared, &threeC)
optimized.Add(&optimized, &two)

// Verify they are equal
require.Equal(t, directProduct.Bytes(), optimized.Bytes(),
"(c-1)*(c-2) should equal c² - 3c + 2 in field arithmetic")

t.Logf("✓ Verified: (c-1)*(c-2) = c² - 3c + 2")
t.Logf(" Direct product: %x", directProduct.Bytes())
t.Logf(" Optimized: %x", optimized.Bytes())
}

// TestFieldArithmeticOptimizationPattern verifies the general pattern:
// (c-i)*(c-j) = c² - c*(i+j) + i*j
func TestFieldArithmeticOptimizationPattern(t *testing.T) {
curve := math.Curves[math.BN254]
rand, err := curve.Rand()
require.NoError(t, err)

// Generate a random field element c
cZr := curve.NewRandomZr(rand)
var c bn254fr.Element
c.SetBigInt(cZr.BigInt())

// Test several (i, j) pairs
testCases := []struct {
i, j int64
}{
{0, 3}, // (c-0)*(c-3) = c² - 3c
{1, 2}, // (c-1)*(c-2) = c² - 3c + 2
{0, 7}, // (c-0)*(c-7) = c² - 7c
{2, 5}, // (c-2)*(c-5) = c² - 7c + 10
}

for _, tc := range testCases {
// Compute (c-i)
var cMinusI bn254fr.Element
var iElem bn254fr.Element
iElem.SetInt64(tc.i)
cMinusI.Sub(&c, &iElem)

// Compute (c-j)
var cMinusJ bn254fr.Element
var jElem bn254fr.Element
jElem.SetInt64(tc.j)
cMinusJ.Sub(&c, &jElem)

// Direct multiplication: (c-i)*(c-j)
var directProduct bn254fr.Element
directProduct.Mul(&cMinusI, &cMinusJ)

// Optimized computation: c² - c*(i+j) + i*j
var cSquared bn254fr.Element
cSquared.Mul(&c, &c)

var iPlusJ bn254fr.Element
iPlusJ.SetInt64(tc.i + tc.j)

var cTimesSumIJ bn254fr.Element
cTimesSumIJ.Mul(&c, &iPlusJ)

var iTimesJ bn254fr.Element
iTimesJ.SetInt64(tc.i * tc.j)

var optimized bn254fr.Element
optimized.Sub(&cSquared, &cTimesSumIJ)
optimized.Add(&optimized, &iTimesJ)

// Verify they are equal
require.Equal(t, directProduct.Bytes(), optimized.Bytes(),
"(c-%d)*(c-%d) should equal c² - c*%d + %d in field arithmetic",
tc.i, tc.j, tc.i+tc.j, tc.i*tc.j)

t.Logf("✓ Verified: (c-%d)*(c-%d) = c² - c*%d + %d", tc.i, tc.j, tc.i+tc.j, tc.i*tc.j)
}
}

// Made with Bob
Loading
Loading