Skip to content

Commit bfe7088

Browse files
authored
test: add comprehensive coverage for ComputeSVector edge cases (#1730)
Signed-off-by: Ankit Basu <ankitbasu14@gmail.com>
1 parent 23ae1d4 commit bfe7088

1 file changed

Lines changed: 283 additions & 0 deletions

File tree

  • token/core/zkatdlog/nogh/v1/crypto/rp/bulletproof

token/core/zkatdlog/nogh/v1/crypto/rp/bulletproof/ipa_test.go

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,286 @@ func TestParallelIPAProver(t *testing.T) {
165165
},
166166
)
167167
}
168+
169+
// TestComputeSVector_SingleElement verifies that n=1 (zero rounds, one challenge)
170+
// produces s = [1] and sInv = [1].
171+
// With a single element there are zero bits to select from, so the product
172+
// is empty — the identity element 1.
173+
func TestComputeSVector_SingleElement(t *testing.T) {
174+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
175+
176+
// n=1 means 2^0 = 1, so we need 0 challenges.
177+
challenges := []*math.Zr{}
178+
s, sInv := bulletproof.ComputeSVector(1, challenges, curve)
179+
180+
require.Len(t, s, 1, "s should have exactly one element")
181+
require.Len(t, sInv, 1, "sInv should have exactly one element")
182+
183+
one := math2.One(curve)
184+
assert.True(t, s[0].Equals(one), "s[0] should be 1 for n=1")
185+
assert.True(t, sInv[0].Equals(one), "sInv[0] should be 1 for n=1")
186+
187+
// Also verify the inverse property holds: sInv[0] * s[0] == 1
188+
product := curve.ModMul(s[0], sInv[0], curve.GroupOrder)
189+
assert.True(t, product.Equals(one), "s[0]*sInv[0] should be 1")
190+
}
191+
192+
// TestComputeSVector_InverseProperty checks that sInv[i] * s[i] == 1
193+
// for all i across various vector sizes.
194+
func TestComputeSVector_InverseProperty(t *testing.T) {
195+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
196+
rand, err := curve.Rand()
197+
require.NoError(t, err)
198+
199+
one := math2.One(curve)
200+
201+
for _, rounds := range []int{1, 2, 3, 4, 5, 6} {
202+
n := 1 << rounds
203+
t.Run(strconv.Itoa(n), func(t *testing.T) {
204+
challenges := make([]*math.Zr, rounds)
205+
for j := range challenges {
206+
challenges[j] = curve.NewRandomZr(rand)
207+
}
208+
209+
s, sInv := bulletproof.ComputeSVector(n, challenges, curve)
210+
require.Len(t, s, n)
211+
require.Len(t, sInv, n)
212+
213+
for i := range n {
214+
product := curve.ModMul(s[i], sInv[i], curve.GroupOrder)
215+
assert.True(t, product.Equals(one),
216+
"s[%d]*sInv[%d] should be 1 (got %s)", i, i, product)
217+
}
218+
})
219+
}
220+
}
221+
222+
// TestComputeSVector_PanicOnBadN verifies that ComputeSVector panics when
223+
// n != 2^(len(challenges)), which indicates a programming error.
224+
func TestComputeSVector_PanicOnBadN(t *testing.T) {
225+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
226+
rand, err := curve.Rand()
227+
require.NoError(t, err)
228+
229+
challenges := []*math.Zr{curve.NewRandomZr(rand), curve.NewRandomZr(rand)}
230+
231+
// 2 challenges ⇒ n must be 4, but we pass 3 and 5
232+
for _, badN := range []int{3, 5, 6, 7} {
233+
t.Run(strconv.Itoa(badN), func(t *testing.T) {
234+
assert.Panics(t, func() {
235+
bulletproof.ComputeSVector(badN, challenges, curve)
236+
}, "ComputeSVector should panic for n=%d with 2 challenges", badN)
237+
})
238+
}
239+
}
240+
241+
// TestComputeSVector_DefinitionConsistency verifies each entry of the s vector
242+
// against its mathematical definition:
243+
//
244+
// s[i] = ∏_{r=0}^{k-1} (bit(i, k-1-r) == 1 ? x_r : x_r^{-1})
245+
//
246+
// This is an independent, naive O(n·log n) computation used purely
247+
// as a test oracle.
248+
func TestComputeSVector_DefinitionConsistency(t *testing.T) {
249+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
250+
rand, err := curve.Rand()
251+
require.NoError(t, err)
252+
253+
for _, rounds := range []int{1, 2, 3, 4, 5} {
254+
n := 1 << rounds
255+
t.Run(strconv.Itoa(n), func(t *testing.T) {
256+
challenges := make([]*math.Zr, rounds)
257+
challengeInvs := make([]*math.Zr, rounds)
258+
for j := range challenges {
259+
challenges[j] = curve.NewRandomZr(rand)
260+
challengeInvs[j] = challenges[j].Copy()
261+
challengeInvs[j].InvModOrder()
262+
}
263+
264+
s, sInv := bulletproof.ComputeSVector(n, challenges, curve)
265+
266+
// Check each entry against the definition.
267+
for i := range n {
268+
expected := math2.One(curve)
269+
expectedInv := math2.One(curve)
270+
for r := range rounds {
271+
bitPos := rounds - 1 - r
272+
if (i>>bitPos)&1 == 1 {
273+
expected = curve.ModMul(expected, challenges[r], curve.GroupOrder)
274+
expectedInv = curve.ModMul(expectedInv, challengeInvs[r], curve.GroupOrder)
275+
} else {
276+
expected = curve.ModMul(expected, challengeInvs[r], curve.GroupOrder)
277+
expectedInv = curve.ModMul(expectedInv, challenges[r], curve.GroupOrder)
278+
}
279+
}
280+
assert.True(t, s[i].Equals(expected),
281+
"s[%d] mismatch for n=%d", i, n)
282+
assert.True(t, sInv[i].Equals(expectedInv),
283+
"sInv[%d] mismatch for n=%d", i, n)
284+
}
285+
})
286+
}
287+
}
288+
289+
// TestComputeSVector_ConsistencyWithFoldReduction verifies that the s-vector
290+
// based generator reduction yields the same result as the iterative fold-based
291+
// reduceGenerators approach.
292+
//
293+
// For a set of generators G = {G_0, …, G_{n-1}} and challenge list
294+
// x = {x_0, …, x_{k-1}}, the final reduced generator should be:
295+
//
296+
// G' = ∑ s[i] · G_i
297+
//
298+
// This must equal the result of iteratively halving the generators
299+
// using the fold recurrence:
300+
//
301+
// G_i' = G_i · x^{-1} + G_{i+n/2} · x (for each round)
302+
func TestComputeSVector_ConsistencyWithFoldReduction(t *testing.T) {
303+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
304+
rand, err := curve.Rand()
305+
require.NoError(t, err)
306+
307+
for _, rounds := range []int{1, 2, 3, 4} {
308+
n := 1 << rounds
309+
t.Run(strconv.Itoa(n), func(t *testing.T) {
310+
// Generate random generators and challenges.
311+
generators := make([]*math.G1, n)
312+
for i := range generators {
313+
generators[i] = curve.HashToG1([]byte(strconv.Itoa(i * 17)))
314+
}
315+
challenges := make([]*math.Zr, rounds)
316+
for j := range challenges {
317+
challenges[j] = curve.NewRandomZr(rand)
318+
}
319+
320+
// Method 1: MSM with s-vector
321+
s, _ := bulletproof.ComputeSVector(n, challenges, curve)
322+
resultMSM := curve.MultiScalarMul(generators, s)
323+
324+
// Method 2: iterative fold reduction
325+
gens := make([]*math.G1, n)
326+
for i := range gens {
327+
gens[i] = generators[i].Copy()
328+
}
329+
for r := range rounds {
330+
half := len(gens) / 2
331+
x := challenges[r]
332+
xInv := x.Copy()
333+
xInv.InvModOrder()
334+
folded := make([]*math.G1, half)
335+
for i := range half {
336+
// G_i' = G_i · xInv + G_{i+half} · x
337+
folded[i] = gens[i].Mul2(xInv, gens[i+half], x)
338+
}
339+
gens = folded
340+
}
341+
require.Len(t, gens, 1, "fold should reduce to a single generator")
342+
resultFold := gens[0]
343+
344+
assert.True(t, resultMSM.Equals(resultFold),
345+
"MSM(s, G) must equal the iteratively folded generator for n=%d", n)
346+
})
347+
}
348+
}
349+
350+
// TestCloneGenerators_EmptySlices verifies CloneGenerators works correctly
351+
// when given empty slices as input, returning empty (non-nil) slices.
352+
func TestCloneGenerators_EmptySlices(t *testing.T) {
353+
leftGen, rightGen := bulletproof.CloneGenerators([]*math.G1{}, []*math.G1{})
354+
355+
require.NotNil(t, leftGen, "left generators should not be nil")
356+
require.NotNil(t, rightGen, "right generators should not be nil")
357+
assert.Empty(t, leftGen, "left generators should be empty")
358+
assert.Empty(t, rightGen, "right generators should be empty")
359+
}
360+
361+
// TestCloneGenerators_Independence verifies that cloned generators are
362+
// independent copies: mutating a clone does not affect the original.
363+
func TestCloneGenerators_Independence(t *testing.T) {
364+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
365+
366+
original := []*math.G1{
367+
curve.HashToG1([]byte("g0")),
368+
curve.HashToG1([]byte("g1")),
369+
}
370+
originalCopy := []*math.G1{
371+
original[0].Copy(),
372+
original[1].Copy(),
373+
}
374+
375+
left, _ := bulletproof.CloneGenerators(original, []*math.G1{})
376+
377+
// Mutate the cloned generators
378+
rand, err := curve.Rand()
379+
require.NoError(t, err)
380+
scalar := curve.NewRandomZr(rand)
381+
left[0] = left[0].Mul(scalar)
382+
383+
// Originals should be unchanged
384+
assert.True(t, original[0].Equals(originalCopy[0]),
385+
"mutating cloned generator should not affect original")
386+
}
387+
388+
// TestCloneGenerators_NilSlices verifies CloneGenerators works with nil slices,
389+
// returning empty (non-nil) slices.
390+
func TestCloneGenerators_NilSlices(t *testing.T) {
391+
leftGen, rightGen := bulletproof.CloneGenerators(nil, nil)
392+
393+
require.NotNil(t, leftGen, "left generators should not be nil")
394+
require.NotNil(t, rightGen, "right generators should not be nil")
395+
assert.Empty(t, leftGen)
396+
assert.Empty(t, rightGen)
397+
}
398+
399+
// TestComputeSVector_KnownValues verifies ComputeSVector against a manually
400+
// computed example with a single challenge.
401+
//
402+
// For k=1, challenge = [x]:
403+
//
404+
// s[0] = x^{-1}, s[1] = x
405+
// sInv[0] = x, sInv[1] = x^{-1}
406+
func TestComputeSVector_KnownValues(t *testing.T) {
407+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
408+
rand, err := curve.Rand()
409+
require.NoError(t, err)
410+
411+
x := curve.NewRandomZr(rand)
412+
xInv := x.Copy()
413+
xInv.InvModOrder()
414+
415+
s, sInv := bulletproof.ComputeSVector(2, []*math.Zr{x}, curve)
416+
417+
require.Len(t, s, 2)
418+
require.Len(t, sInv, 2)
419+
420+
// s[0] = x^{-1} (bit 0 for the single challenge → inverse)
421+
assert.True(t, s[0].Equals(xInv), "s[0] should be x^{-1}")
422+
// s[1] = x (bit 1 for the single challenge → challenge)
423+
assert.True(t, s[1].Equals(x), "s[1] should be x")
424+
// sInv is entry-wise inverse of s
425+
assert.True(t, sInv[0].Equals(x), "sInv[0] should be x")
426+
assert.True(t, sInv[1].Equals(xInv), "sInv[1] should be x^{-1}")
427+
}
428+
429+
// TestComputeSVector_DeterministicOutput verifies that calling ComputeSVector
430+
// twice with the same inputs produces identical results.
431+
func TestComputeSVector_DeterministicOutput(t *testing.T) {
432+
curve := math.Curves[math.BLS12_381_BBS_GURVY]
433+
rand, err := curve.Rand()
434+
require.NoError(t, err)
435+
436+
rounds := 4
437+
n := 1 << rounds
438+
challenges := make([]*math.Zr, rounds)
439+
for j := range challenges {
440+
challenges[j] = curve.NewRandomZr(rand)
441+
}
442+
443+
s1, sInv1 := bulletproof.ComputeSVector(n, challenges, curve)
444+
s2, sInv2 := bulletproof.ComputeSVector(n, challenges, curve)
445+
446+
for i := range n {
447+
assert.True(t, s1[i].Equals(s2[i]), "s[%d] should be deterministic", i)
448+
assert.True(t, sInv1[i].Equals(sInv2[i]), "sInv[%d] should be deterministic", i)
449+
}
450+
}

0 commit comments

Comments
 (0)