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(&current)
 158  		// Move to next point
 159  		var next GroupElementJacobian
 160  		next.addVar(&current, &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(&current)
 244  		var next GroupElementJacobian
 245  		next.addVar(&current, &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(&current)
 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