@@ -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