field_asm_test.go raw

   1  //go:build !js && !wasm && !tinygo && !wasm32
   2  
   3  package p256k1
   4  
   5  import (
   6  	"testing"
   7  )
   8  
   9  // fieldMulPureGo is the pure Go implementation for comparison
  10  func fieldMulPureGo(r, a, b *FieldElement) {
  11  	// Extract limbs for easier access
  12  	a0, a1, a2, a3, a4 := a.n[0], a.n[1], a.n[2], a.n[3], a.n[4]
  13  	b0, b1, b2, b3, b4 := b.n[0], b.n[1], b.n[2], b.n[3], b.n[4]
  14  
  15  	const M = uint64(0xFFFFFFFFFFFFF)            // 2^52 - 1
  16  	const R = uint64(fieldReductionConstantShifted) // 0x1000003D10
  17  
  18  	// Following the C implementation algorithm exactly
  19  	var c, d uint128
  20  	d = mulU64ToU128(a0, b3)
  21  	d = addMulU128(d, a1, b2)
  22  	d = addMulU128(d, a2, b1)
  23  	d = addMulU128(d, a3, b0)
  24  
  25  	c = mulU64ToU128(a4, b4)
  26  
  27  	d = addMulU128(d, R, c.lo())
  28  	c = c.rshift(64)
  29  
  30  	t3 := d.lo() & M
  31  	d = d.rshift(52)
  32  
  33  	d = addMulU128(d, a0, b4)
  34  	d = addMulU128(d, a1, b3)
  35  	d = addMulU128(d, a2, b2)
  36  	d = addMulU128(d, a3, b1)
  37  	d = addMulU128(d, a4, b0)
  38  
  39  	d = addMulU128(d, R<<12, c.lo())
  40  
  41  	t4 := d.lo() & M
  42  	d = d.rshift(52)
  43  	tx := t4 >> 48
  44  	t4 &= (M >> 4)
  45  
  46  	c = mulU64ToU128(a0, b0)
  47  
  48  	d = addMulU128(d, a1, b4)
  49  	d = addMulU128(d, a2, b3)
  50  	d = addMulU128(d, a3, b2)
  51  	d = addMulU128(d, a4, b1)
  52  
  53  	u0 := d.lo() & M
  54  	d = d.rshift(52)
  55  	u0 = (u0 << 4) | tx
  56  
  57  	c = addMulU128(c, u0, R>>4)
  58  
  59  	r.n[0] = c.lo() & M
  60  	c = c.rshift(52)
  61  
  62  	c = addMulU128(c, a0, b1)
  63  	c = addMulU128(c, a1, b0)
  64  
  65  	d = addMulU128(d, a2, b4)
  66  	d = addMulU128(d, a3, b3)
  67  	d = addMulU128(d, a4, b2)
  68  
  69  	c = addMulU128(c, R, d.lo()&M)
  70  	d = d.rshift(52)
  71  
  72  	r.n[1] = c.lo() & M
  73  	c = c.rshift(52)
  74  
  75  	c = addMulU128(c, a0, b2)
  76  	c = addMulU128(c, a1, b1)
  77  	c = addMulU128(c, a2, b0)
  78  
  79  	d = addMulU128(d, a3, b4)
  80  	d = addMulU128(d, a4, b3)
  81  
  82  	c = addMulU128(c, R, d.lo())
  83  	d = d.rshift(64)
  84  
  85  	r.n[2] = c.lo() & M
  86  	c = c.rshift(52)
  87  
  88  	c = addMulU128(c, R<<12, d.lo())
  89  	c = addU128(c, t3)
  90  
  91  	r.n[3] = c.lo() & M
  92  	c = c.rshift(52)
  93  
  94  	r.n[4] = c.lo() + t4
  95  
  96  	r.magnitude = 1
  97  	r.normalized = false
  98  }
  99  
 100  func TestFieldMulAsmVsPureGo(t *testing.T) {
 101  	// Test with simple values first
 102  	a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
 103  	b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}
 104  
 105  	var rAsm, rGo FieldElement
 106  
 107  	// Pure Go
 108  	fieldMulPureGo(&rGo, &a, &b)
 109  
 110  	// Assembly
 111  	if hasFieldAsm() {
 112  		fieldMulAsm(&rAsm, &a, &b)
 113  		rAsm.magnitude = 1
 114  		rAsm.normalized = false
 115  
 116  		t.Logf("a = %v", a.n)
 117  		t.Logf("b = %v", b.n)
 118  		t.Logf("Go result:  %v", rGo.n)
 119  		t.Logf("Asm result: %v", rAsm.n)
 120  
 121  		for i := 0; i < 5; i++ {
 122  			if rAsm.n[i] != rGo.n[i] {
 123  				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
 124  			}
 125  		}
 126  	} else {
 127  		t.Skip("Assembly not available")
 128  	}
 129  }
 130  
 131  func TestFieldMulAsmVsPureGoLarger(t *testing.T) {
 132  	// Test with larger values
 133  	a := FieldElement{
 134  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 135  		magnitude:  1,
 136  		normalized: true,
 137  	}
 138  	b := FieldElement{
 139  		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
 140  		magnitude:  1,
 141  		normalized: true,
 142  	}
 143  
 144  	var rAsm, rGo FieldElement
 145  
 146  	// Pure Go
 147  	fieldMulPureGo(&rGo, &a, &b)
 148  
 149  	// Assembly
 150  	if hasFieldAsm() {
 151  		fieldMulAsm(&rAsm, &a, &b)
 152  		rAsm.magnitude = 1
 153  		rAsm.normalized = false
 154  
 155  		t.Logf("a = %v", a.n)
 156  		t.Logf("b = %v", b.n)
 157  		t.Logf("Go result:  %v", rGo.n)
 158  		t.Logf("Asm result: %v", rAsm.n)
 159  
 160  		for i := 0; i < 5; i++ {
 161  			if rAsm.n[i] != rGo.n[i] {
 162  				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
 163  			}
 164  		}
 165  	} else {
 166  		t.Skip("Assembly not available")
 167  	}
 168  }
 169  
 170  func TestFieldSqrAsmVsPureGo(t *testing.T) {
 171  	a := FieldElement{
 172  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 173  		magnitude:  1,
 174  		normalized: true,
 175  	}
 176  
 177  	var rAsm, rGo FieldElement
 178  
 179  	// Pure Go (a * a)
 180  	fieldMulPureGo(&rGo, &a, &a)
 181  
 182  	// Assembly
 183  	if hasFieldAsm() {
 184  		fieldSqrAsm(&rAsm, &a)
 185  		rAsm.magnitude = 1
 186  		rAsm.normalized = false
 187  
 188  		t.Logf("a = %v", a.n)
 189  		t.Logf("Go result:  %v", rGo.n)
 190  		t.Logf("Asm result: %v", rAsm.n)
 191  
 192  		for i := 0; i < 5; i++ {
 193  			if rAsm.n[i] != rGo.n[i] {
 194  				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
 195  			}
 196  		}
 197  	} else {
 198  		t.Skip("Assembly not available")
 199  	}
 200  }
 201  
 202  // BMI2 tests
 203  
 204  func TestFieldMulAsmBMI2VsPureGo(t *testing.T) {
 205  	if !hasFieldAsmBMI2() {
 206  		t.Skip("BMI2+ADX assembly not available")
 207  	}
 208  
 209  	// Test with simple values first
 210  	a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
 211  	b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}
 212  
 213  	var rBMI2, rGo FieldElement
 214  
 215  	// Pure Go
 216  	fieldMulPureGo(&rGo, &a, &b)
 217  
 218  	// BMI2 Assembly
 219  	fieldMulAsmBMI2(&rBMI2, &a, &b)
 220  	rBMI2.magnitude = 1
 221  	rBMI2.normalized = false
 222  
 223  	t.Logf("a = %v", a.n)
 224  	t.Logf("b = %v", b.n)
 225  	t.Logf("Go result:   %v", rGo.n)
 226  	t.Logf("BMI2 result: %v", rBMI2.n)
 227  
 228  	for i := 0; i < 5; i++ {
 229  		if rBMI2.n[i] != rGo.n[i] {
 230  			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
 231  		}
 232  	}
 233  }
 234  
 235  func TestFieldMulAsmBMI2VsPureGoLarger(t *testing.T) {
 236  	if !hasFieldAsmBMI2() {
 237  		t.Skip("BMI2+ADX assembly not available")
 238  	}
 239  
 240  	// Test with larger values
 241  	a := FieldElement{
 242  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 243  		magnitude:  1,
 244  		normalized: true,
 245  	}
 246  	b := FieldElement{
 247  		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
 248  		magnitude:  1,
 249  		normalized: true,
 250  	}
 251  
 252  	var rBMI2, rGo FieldElement
 253  
 254  	// Pure Go
 255  	fieldMulPureGo(&rGo, &a, &b)
 256  
 257  	// BMI2 Assembly
 258  	fieldMulAsmBMI2(&rBMI2, &a, &b)
 259  	rBMI2.magnitude = 1
 260  	rBMI2.normalized = false
 261  
 262  	t.Logf("a = %v", a.n)
 263  	t.Logf("b = %v", b.n)
 264  	t.Logf("Go result:   %v", rGo.n)
 265  	t.Logf("BMI2 result: %v", rBMI2.n)
 266  
 267  	for i := 0; i < 5; i++ {
 268  		if rBMI2.n[i] != rGo.n[i] {
 269  			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
 270  		}
 271  	}
 272  }
 273  
 274  func TestFieldMulAsmBMI2VsRegularAsm(t *testing.T) {
 275  	if !hasFieldAsmBMI2() {
 276  		t.Skip("BMI2+ADX assembly not available")
 277  	}
 278  	if !hasFieldAsm() {
 279  		t.Skip("Regular assembly not available")
 280  	}
 281  
 282  	// Test with larger values
 283  	a := FieldElement{
 284  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 285  		magnitude:  1,
 286  		normalized: true,
 287  	}
 288  	b := FieldElement{
 289  		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
 290  		magnitude:  1,
 291  		normalized: true,
 292  	}
 293  
 294  	var rBMI2, rAsm FieldElement
 295  
 296  	// Regular Assembly
 297  	fieldMulAsm(&rAsm, &a, &b)
 298  	rAsm.magnitude = 1
 299  	rAsm.normalized = false
 300  
 301  	// BMI2 Assembly
 302  	fieldMulAsmBMI2(&rBMI2, &a, &b)
 303  	rBMI2.magnitude = 1
 304  	rBMI2.normalized = false
 305  
 306  	t.Logf("a = %v", a.n)
 307  	t.Logf("b = %v", b.n)
 308  	t.Logf("Asm result:  %v", rAsm.n)
 309  	t.Logf("BMI2 result: %v", rBMI2.n)
 310  
 311  	for i := 0; i < 5; i++ {
 312  		if rBMI2.n[i] != rAsm.n[i] {
 313  			t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
 314  		}
 315  	}
 316  }
 317  
 318  func TestFieldSqrAsmBMI2VsPureGo(t *testing.T) {
 319  	if !hasFieldAsmBMI2() {
 320  		t.Skip("BMI2+ADX assembly not available")
 321  	}
 322  
 323  	a := FieldElement{
 324  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 325  		magnitude:  1,
 326  		normalized: true,
 327  	}
 328  
 329  	var rBMI2, rGo FieldElement
 330  
 331  	// Pure Go (a * a)
 332  	fieldMulPureGo(&rGo, &a, &a)
 333  
 334  	// BMI2 Assembly
 335  	fieldSqrAsmBMI2(&rBMI2, &a)
 336  	rBMI2.magnitude = 1
 337  	rBMI2.normalized = false
 338  
 339  	t.Logf("a = %v", a.n)
 340  	t.Logf("Go result:   %v", rGo.n)
 341  	t.Logf("BMI2 result: %v", rBMI2.n)
 342  
 343  	for i := 0; i < 5; i++ {
 344  		if rBMI2.n[i] != rGo.n[i] {
 345  			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
 346  		}
 347  	}
 348  }
 349  
 350  func TestFieldSqrAsmBMI2VsRegularAsm(t *testing.T) {
 351  	if !hasFieldAsmBMI2() {
 352  		t.Skip("BMI2+ADX assembly not available")
 353  	}
 354  	if !hasFieldAsm() {
 355  		t.Skip("Regular assembly not available")
 356  	}
 357  
 358  	a := FieldElement{
 359  		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
 360  		magnitude:  1,
 361  		normalized: true,
 362  	}
 363  
 364  	var rBMI2, rAsm FieldElement
 365  
 366  	// Regular Assembly
 367  	fieldSqrAsm(&rAsm, &a)
 368  	rAsm.magnitude = 1
 369  	rAsm.normalized = false
 370  
 371  	// BMI2 Assembly
 372  	fieldSqrAsmBMI2(&rBMI2, &a)
 373  	rBMI2.magnitude = 1
 374  	rBMI2.normalized = false
 375  
 376  	t.Logf("a = %v", a.n)
 377  	t.Logf("Asm result:  %v", rAsm.n)
 378  	t.Logf("BMI2 result: %v", rBMI2.n)
 379  
 380  	for i := 0; i < 5; i++ {
 381  		if rBMI2.n[i] != rAsm.n[i] {
 382  			t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
 383  		}
 384  	}
 385  }
 386  
 387  // TestFieldMulAsmBMI2Random tests with many random values
 388  func TestFieldMulAsmBMI2Random(t *testing.T) {
 389  	if !hasFieldAsmBMI2() {
 390  		t.Skip("BMI2+ADX assembly not available")
 391  	}
 392  	if !hasFieldAsm() {
 393  		t.Skip("Regular assembly not available")
 394  	}
 395  
 396  	// Test with many random values
 397  	for iter := 0; iter < 10000; iter++ {
 398  		var a, b FieldElement
 399  		a.magnitude = 1
 400  		a.normalized = true
 401  		b.magnitude = 1
 402  		b.normalized = true
 403  
 404  		// Generate deterministic but varied test data
 405  		seed := uint64(iter * 12345678901234567)
 406  		for j := 0; j < 5; j++ {
 407  			seed = seed*6364136223846793005 + 1442695040888963407 // LCG
 408  			a.n[j] = seed & 0xFFFFFFFFFFFFF
 409  
 410  			seed = seed*6364136223846793005 + 1442695040888963407
 411  			b.n[j] = seed & 0xFFFFFFFFFFFFF
 412  		}
 413  		// Limb 4 is only 48 bits
 414  		a.n[4] &= 0x0FFFFFFFFFFFF
 415  		b.n[4] &= 0x0FFFFFFFFFFFF
 416  
 417  		var rAsm, rBMI2 FieldElement
 418  
 419  		// Regular Assembly
 420  		fieldMulAsm(&rAsm, &a, &b)
 421  		rAsm.magnitude = 1
 422  		rAsm.normalized = false
 423  
 424  		// BMI2 Assembly
 425  		fieldMulAsmBMI2(&rBMI2, &a, &b)
 426  		rBMI2.magnitude = 1
 427  		rBMI2.normalized = false
 428  
 429  		// Compare results
 430  		for j := 0; j < 5; j++ {
 431  			if rAsm.n[j] != rBMI2.n[j] {
 432  				t.Errorf("Iteration %d: limb %d mismatch", iter, j)
 433  				t.Errorf("  a = %v", a.n)
 434  				t.Errorf("  b = %v", b.n)
 435  				t.Errorf("  Asm:  %v", rAsm.n)
 436  				t.Errorf("  BMI2: %v", rBMI2.n)
 437  				return
 438  			}
 439  		}
 440  	}
 441  }
 442  
 443  // TestFieldSqrAsmBMI2Random tests squaring with many random values
 444  func TestFieldSqrAsmBMI2Random(t *testing.T) {
 445  	if !hasFieldAsmBMI2() {
 446  		t.Skip("BMI2+ADX assembly not available")
 447  	}
 448  	if !hasFieldAsm() {
 449  		t.Skip("Regular assembly not available")
 450  	}
 451  
 452  	// Test with many random values
 453  	for iter := 0; iter < 10000; iter++ {
 454  		var a FieldElement
 455  		a.magnitude = 1
 456  		a.normalized = true
 457  
 458  		// Generate deterministic but varied test data
 459  		seed := uint64(iter * 98765432109876543)
 460  		for j := 0; j < 5; j++ {
 461  			seed = seed*6364136223846793005 + 1442695040888963407 // LCG
 462  			a.n[j] = seed & 0xFFFFFFFFFFFFF
 463  		}
 464  		// Limb 4 is only 48 bits
 465  		a.n[4] &= 0x0FFFFFFFFFFFF
 466  
 467  		var rAsm, rBMI2 FieldElement
 468  
 469  		// Regular Assembly
 470  		fieldSqrAsm(&rAsm, &a)
 471  		rAsm.magnitude = 1
 472  		rAsm.normalized = false
 473  
 474  		// BMI2 Assembly
 475  		fieldSqrAsmBMI2(&rBMI2, &a)
 476  		rBMI2.magnitude = 1
 477  		rBMI2.normalized = false
 478  
 479  		// Compare results
 480  		for j := 0; j < 5; j++ {
 481  			if rAsm.n[j] != rBMI2.n[j] {
 482  				t.Errorf("Iteration %d: limb %d mismatch", iter, j)
 483  				t.Errorf("  a = %v", a.n)
 484  				t.Errorf("  Asm:  %v", rAsm.n)
 485  				t.Errorf("  BMI2: %v", rBMI2.n)
 486  				return
 487  			}
 488  		}
 489  	}
 490  }
 491