group_test.go raw
1 package p256k1
2
3 import (
4 "fmt"
5 "testing"
6 )
7
8 func TestGroupElementAffine(t *testing.T) {
9 // Test infinity point
10 var inf GroupElementAffine
11 inf.setInfinity()
12 if !inf.isInfinity() {
13 t.Error("setInfinity should create infinity point")
14 }
15 if !inf.isValid() {
16 t.Error("infinity point should be valid")
17 }
18
19 // Test generator point
20 if Generator.isInfinity() {
21 t.Error("generator should not be infinity")
22 }
23 if !Generator.isValid() {
24 t.Error("generator should be valid")
25 }
26
27 // Test point negation
28 var neg GroupElementAffine
29 neg.negate(&Generator)
30 if neg.isInfinity() {
31 t.Error("negated generator should not be infinity")
32 }
33 if !neg.isValid() {
34 t.Error("negated generator should be valid")
35 }
36
37 // Test that G + (-G) = O (using Jacobian arithmetic)
38 var gJac, negJac, result GroupElementJacobian
39 gJac.setGE(&Generator)
40 negJac.setGE(&neg)
41 result.addVar(&gJac, &negJac)
42 if !result.isInfinity() {
43 t.Error("G + (-G) should equal infinity")
44 }
45 }
46
47 func TestGroupElementJacobian(t *testing.T) {
48 // Test conversion between affine and Jacobian
49 var jac GroupElementJacobian
50 var aff GroupElementAffine
51
52 // Convert generator to Jacobian and back
53 jac.setGE(&Generator)
54 aff.setGEJ(&jac)
55
56 if !aff.equal(&Generator) {
57 t.Error("conversion G -> Jacobian -> affine should preserve point")
58 }
59
60 // Test point doubling
61 var doubled GroupElementJacobian
62 doubled.double(&jac)
63 if doubled.isInfinity() {
64 t.Error("2*G should not be infinity")
65 }
66
67 // Convert back to affine to validate
68 var doubledAff GroupElementAffine
69 doubledAff.setGEJ(&doubled)
70 if !doubledAff.isValid() {
71 t.Error("2*G should be valid point")
72 }
73 }
74
75 func TestGroupElementStorage(t *testing.T) {
76 // Test storage conversion
77 var storage GroupElementStorage
78 var restored GroupElementAffine
79
80 // Store and restore generator
81 Generator.toStorage(&storage)
82 restored.fromStorage(&storage)
83
84 if !restored.equal(&Generator) {
85 t.Error("storage conversion should preserve point")
86 }
87
88 // Test infinity storage
89 var inf GroupElementAffine
90 inf.setInfinity()
91 inf.toStorage(&storage)
92 restored.fromStorage(&storage)
93
94 if !restored.isInfinity() {
95 t.Error("infinity should be preserved in storage")
96 }
97 }
98
99 func TestGroupElementBytes(t *testing.T) {
100 var buf [64]byte
101 var restored GroupElementAffine
102
103 // Test generator conversion
104 Generator.toBytes(buf[:])
105 restored.fromBytes(buf[:])
106
107 if !restored.equal(&Generator) {
108 t.Error("byte conversion should preserve point")
109 }
110
111 // Test infinity conversion
112 var inf GroupElementAffine
113 inf.setInfinity()
114 inf.toBytes(buf[:])
115 restored.fromBytes(buf[:])
116
117 if !restored.isInfinity() {
118 t.Error("infinity should be preserved in byte conversion")
119 }
120 }
121
122 func BenchmarkGroupDouble(b *testing.B) {
123 var jac GroupElementJacobian
124 jac.setGE(&Generator)
125
126 b.ResetTimer()
127 for i := 0; i < b.N; i++ {
128 jac.double(&jac)
129 }
130 }
131
132 func BenchmarkGroupAdd(b *testing.B) {
133 var jac1, jac2 GroupElementJacobian
134 jac1.setGE(&Generator)
135 jac2.setGE(&Generator)
136 jac2.double(&jac2) // Make it 2*G
137
138 b.ResetTimer()
139 for i := 0; i < b.N; i++ {
140 jac1.addVar(&jac1, &jac2)
141 }
142 }
143
144 // TestBatchNormalize tests that BatchNormalize produces the same results as individual conversions
145 func TestBatchNormalize(t *testing.T) {
146 // Create several Jacobian points: G, 2G, 3G, 4G, ...
147 n := 10
148 points := make([]GroupElementJacobian, n)
149 expected := make([]GroupElementAffine, n)
150
151 var current GroupElementJacobian
152 current.setGE(&Generator)
153
154 for i := 0; i < n; i++ {
155 points[i] = current
156 // Get expected result using individual conversion
157 expected[i].setGEJ(¤t)
158 // Move to next point
159 var next GroupElementJacobian
160 next.addVar(¤t, &points[0]) // Add G each time
161 current = next
162 }
163
164 // Now use BatchNormalize
165 result := BatchNormalize(nil, points)
166
167 // Compare results
168 for i := 0; i < n; i++ {
169 // Normalize both for comparison
170 expected[i].x.normalize()
171 expected[i].y.normalize()
172 result[i].x.normalize()
173 result[i].y.normalize()
174
175 if !expected[i].x.equal(&result[i].x) {
176 t.Errorf("Point %d: X mismatch", i)
177 }
178 if !expected[i].y.equal(&result[i].y) {
179 t.Errorf("Point %d: Y mismatch", i)
180 }
181 if expected[i].infinity != result[i].infinity {
182 t.Errorf("Point %d: infinity mismatch", i)
183 }
184 }
185 }
186
187 // TestBatchNormalizeWithInfinity tests that BatchNormalize handles infinity points correctly
188 func TestBatchNormalizeWithInfinity(t *testing.T) {
189 points := make([]GroupElementJacobian, 5)
190
191 // Set some points to generator, some to infinity
192 points[0].setGE(&Generator)
193 points[1].setInfinity()
194 points[2].setGE(&Generator)
195 points[2].double(&points[2]) // 2G
196 points[3].setInfinity()
197 points[4].setGE(&Generator)
198
199 result := BatchNormalize(nil, points)
200
201 // Check infinity points
202 if !result[1].isInfinity() {
203 t.Error("Point 1 should be infinity")
204 }
205 if !result[3].isInfinity() {
206 t.Error("Point 3 should be infinity")
207 }
208
209 // Check non-infinity points
210 if result[0].isInfinity() {
211 t.Error("Point 0 should not be infinity")
212 }
213 if result[2].isInfinity() {
214 t.Error("Point 2 should not be infinity")
215 }
216 if result[4].isInfinity() {
217 t.Error("Point 4 should not be infinity")
218 }
219
220 // Verify non-infinity points are on the curve
221 if !result[0].isValid() {
222 t.Error("Point 0 should be valid")
223 }
224 if !result[2].isValid() {
225 t.Error("Point 2 should be valid")
226 }
227 if !result[4].isValid() {
228 t.Error("Point 4 should be valid")
229 }
230 }
231
232 // TestBatchNormalizeInPlace tests in-place batch normalization
233 func TestBatchNormalizeInPlace(t *testing.T) {
234 n := 5
235 points := make([]GroupElementJacobian, n)
236 expected := make([]GroupElementAffine, n)
237
238 var current GroupElementJacobian
239 current.setGE(&Generator)
240
241 for i := 0; i < n; i++ {
242 points[i] = current
243 expected[i].setGEJ(¤t)
244 var next GroupElementJacobian
245 next.addVar(¤t, &points[0])
246 current = next
247 }
248
249 // Normalize in place
250 BatchNormalizeInPlace(points)
251
252 // After normalization, Z should be 1 for all non-infinity points
253 for i := 0; i < n; i++ {
254 if !points[i].isInfinity() {
255 var one FieldElement
256 one.setInt(1)
257 points[i].z.normalize()
258 if !points[i].z.equal(&one) {
259 t.Errorf("Point %d: Z should be 1 after normalization", i)
260 }
261 }
262
263 // Check X and Y match expected
264 points[i].x.normalize()
265 points[i].y.normalize()
266 expected[i].x.normalize()
267 expected[i].y.normalize()
268
269 if !points[i].x.equal(&expected[i].x) {
270 t.Errorf("Point %d: X mismatch after in-place normalization", i)
271 }
272 if !points[i].y.equal(&expected[i].y) {
273 t.Errorf("Point %d: Y mismatch after in-place normalization", i)
274 }
275 }
276 }
277
278 // BenchmarkBatchNormalize benchmarks batch normalization vs individual conversions
279 func BenchmarkBatchNormalize(b *testing.B) {
280 sizes := []int{1, 2, 4, 8, 16, 32, 64}
281
282 for _, size := range sizes {
283 n := size // capture for closure
284
285 // Create n Jacobian points
286 points := make([]GroupElementJacobian, n)
287 var current GroupElementJacobian
288 current.setGE(&Generator)
289 for i := 0; i < n; i++ {
290 points[i] = current
291 current.double(¤t)
292 }
293
294 b.Run(
295 fmt.Sprintf("Individual_%d", n),
296 func(b *testing.B) {
297 out := make([]GroupElementAffine, n)
298 b.ResetTimer()
299 for i := 0; i < b.N; i++ {
300 for j := 0; j < n; j++ {
301 out[j].setGEJ(&points[j])
302 }
303 }
304 },
305 )
306
307 b.Run(
308 fmt.Sprintf("Batch_%d", n),
309 func(b *testing.B) {
310 out := make([]GroupElementAffine, n)
311 b.ResetTimer()
312 for i := 0; i < b.N; i++ {
313 BatchNormalize(out, points)
314 }
315 },
316 )
317 }
318 }
319