Files
p256k1/glv_test.go
mleku 14dc85cdc3 Add BMI2/AVX2 field assembly and SIMD comparison benchmarks
- Port field operations assembler from libsecp256k1 (field_amd64.s,
    field_amd64_bmi2.s) with MULX/ADCX/ADOX instructions
  - Add AVX2 scalar and affine point operations in avx/ package
  - Implement CPU feature detection (cpufeatures.go) for AVX2/BMI2
  - Add libsecp256k1.so via purego for native C library comparison
  - Create comprehensive SIMD benchmark suite comparing btcec, P256K1
    pure Go, P256K1 ASM, and libsecp256k1
  - Add BENCHMARK_SIMD.md documenting performance across implementations
  - Remove BtcecSigner, consolidate on P256K1Signer as primary impl
  - Add field operation tests and benchmarks (field_asm_test.go,
    field_bench_test.go)
  - Update GLV endomorphism with wNAF scalar multiplication
  - Add scalar assembly (scalar_amd64.s) for optimized operations
  - Clean up dependencies and update benchmark reports
2025-11-29 08:11:13 +00:00

1959 lines
55 KiB
Go

package p256k1
import (
"encoding/hex"
"testing"
)
// TestGLVScalarConstants verifies that the GLV scalar constants are correctly defined
func TestGLVScalarConstants(t *testing.T) {
// Test lambda constant
// Expected: 0x5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72
var lambdaBytes [32]byte
scalarLambda.getB32(lambdaBytes[:])
expectedLambda := "5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72"
if hex.EncodeToString(lambdaBytes[:]) != expectedLambda {
t.Errorf("scalarLambda mismatch:\n got: %s\n want: %s", hex.EncodeToString(lambdaBytes[:]), expectedLambda)
}
// Test g1 constant
// Expected: 0x3086D221A7D46BCDE86C90E49284EB153DAA8A1471E8CA7FE893209A45DBB031
var g1Bytes [32]byte
scalarG1.getB32(g1Bytes[:])
expectedG1 := "3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7fe893209a45dbb031"
if hex.EncodeToString(g1Bytes[:]) != expectedG1 {
t.Errorf("scalarG1 mismatch:\n got: %s\n want: %s", hex.EncodeToString(g1Bytes[:]), expectedG1)
}
// Test g2 constant
// Expected: 0xE4437ED6010E88286F547FA90ABFE4C4221208AC9DF506C61571B4AE8AC47F71
var g2Bytes [32]byte
scalarG2.getB32(g2Bytes[:])
expectedG2 := "e4437ed6010e88286f547fa90abfe4c4221208ac9df506c61571b4ae8ac47f71"
if hex.EncodeToString(g2Bytes[:]) != expectedG2 {
t.Errorf("scalarG2 mismatch:\n got: %s\n want: %s", hex.EncodeToString(g2Bytes[:]), expectedG2)
}
// Test -b1 constant
// Expected: 0x00000000000000000000000000000000E4437ED6010E88286F547FA90ABFE4C3
var minusB1Bytes [32]byte
scalarMinusB1.getB32(minusB1Bytes[:])
expectedMinusB1 := "00000000000000000000000000000000e4437ed6010e88286f547fa90abfe4c3"
if hex.EncodeToString(minusB1Bytes[:]) != expectedMinusB1 {
t.Errorf("scalarMinusB1 mismatch:\n got: %s\n want: %s", hex.EncodeToString(minusB1Bytes[:]), expectedMinusB1)
}
// Test -b2 constant
// Expected: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8A280AC50774346DD765CDA83DB1562C
var minusB2Bytes [32]byte
scalarMinusB2.getB32(minusB2Bytes[:])
expectedMinusB2 := "fffffffffffffffffffffffffffffffe8a280ac50774346dd765cda83db1562c"
if hex.EncodeToString(minusB2Bytes[:]) != expectedMinusB2 {
t.Errorf("scalarMinusB2 mismatch:\n got: %s\n want: %s", hex.EncodeToString(minusB2Bytes[:]), expectedMinusB2)
}
}
// TestGLVLambdaCubeRoot verifies that λ^3 ≡ 1 (mod n)
func TestGLVLambdaCubeRoot(t *testing.T) {
// Compute λ^2
var lambda2 Scalar
lambda2.mul(&scalarLambda, &scalarLambda)
// Compute λ^3 = λ^2 * λ
var lambda3 Scalar
lambda3.mul(&lambda2, &scalarLambda)
// Check if λ^3 ≡ 1 (mod n)
if !lambda3.equal(&ScalarOne) {
var bytes [32]byte
lambda3.getB32(bytes[:])
t.Errorf("λ^3 ≠ 1 (mod n), got: %s", hex.EncodeToString(bytes[:]))
}
}
// TestGLVLambdaProperty verifies that λ^2 + λ + 1 ≡ 0 (mod n)
func TestGLVLambdaProperty(t *testing.T) {
// Compute λ^2
var lambda2 Scalar
lambda2.mul(&scalarLambda, &scalarLambda)
// Compute λ^2 + λ
var sum Scalar
sum.add(&lambda2, &scalarLambda)
// Compute λ^2 + λ + 1
sum.add(&sum, &ScalarOne)
// Check if λ^2 + λ + 1 ≡ 0 (mod n)
if !sum.isZero() {
var bytes [32]byte
sum.getB32(bytes[:])
t.Errorf("λ^2 + λ + 1 ≠ 0 (mod n), got: %s", hex.EncodeToString(bytes[:]))
}
}
// TestGLVBetaConstant verifies that the Beta field constant is correctly defined
func TestGLVBetaConstant(t *testing.T) {
// Expected: 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee
var betaBytes [32]byte
fieldBeta.getB32(betaBytes[:])
expectedBeta := "7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"
if hex.EncodeToString(betaBytes[:]) != expectedBeta {
t.Errorf("fieldBeta mismatch:\n got: %s\n want: %s", hex.EncodeToString(betaBytes[:]), expectedBeta)
}
}
// TestGLVBetaCubeRoot verifies that β^3 ≡ 1 (mod p)
func TestGLVBetaCubeRoot(t *testing.T) {
// Compute β^2
var beta2 FieldElement
beta2.sqr(&fieldBeta)
beta2.normalize()
// Compute β^3 = β^2 * β
var beta3 FieldElement
beta3.mul(&beta2, &fieldBeta)
beta3.normalize()
// Check if β^3 ≡ 1 (mod p)
if !beta3.equal(&FieldElementOne) {
var bytes [32]byte
beta3.getB32(bytes[:])
t.Errorf("β^3 ≠ 1 (mod p), got: %s", hex.EncodeToString(bytes[:]))
}
}
// TestGLVBetaProperty verifies that β^2 + β + 1 ≡ 0 (mod p)
func TestGLVBetaProperty(t *testing.T) {
// Compute β^2
var beta2 FieldElement
beta2.sqr(&fieldBeta)
// Compute β^2 + β
var sum FieldElement
sum = beta2
sum.add(&fieldBeta)
// Compute β^2 + β + 1
sum.add(&FieldElementOne)
sum.normalize()
// Check if β^2 + β + 1 ≡ 0 (mod p)
if !sum.normalizesToZeroVar() {
var bytes [32]byte
sum.getB32(bytes[:])
t.Errorf("β^2 + β + 1 ≠ 0 (mod p), got: %s", hex.EncodeToString(bytes[:]))
}
}
// TestGLVEndomorphismOnGenerator verifies that λ·G = (β·Gx, Gy)
// This confirms that the endomorphism relationship holds
func TestGLVEndomorphismOnGenerator(t *testing.T) {
// Compute λ·G using scalar multiplication
var lambdaG GroupElementJacobian
EcmultConst(&lambdaG, &Generator, &scalarLambda)
// Convert to affine
var lambdaGAff GroupElementAffine
lambdaGAff.setGEJ(&lambdaG)
lambdaGAff.x.normalize()
lambdaGAff.y.normalize()
// Compute β·Gx
var betaGx FieldElement
betaGx.mul(&Generator.x, &fieldBeta)
betaGx.normalize()
// Check X coordinate: λ·G.x should equal β·G.x
if !lambdaGAff.x.equal(&betaGx) {
var got, want [32]byte
lambdaGAff.x.getB32(got[:])
betaGx.getB32(want[:])
t.Errorf("λ·G.x ≠ β·G.x:\n got: %s\n want: %s", hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
}
// Check Y coordinate: λ·G.y should equal G.y
genY := Generator.y
genY.normalize()
if !lambdaGAff.y.equal(&genY) {
var got, want [32]byte
lambdaGAff.y.getB32(got[:])
genY.getB32(want[:])
t.Errorf("λ·G.y ≠ G.y:\n got: %s\n want: %s", hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
}
}
// =============================================================================
// Phase 2 Tests: Scalar Splitting
// =============================================================================
// TestScalarSplitLambdaProperty verifies that k1 + k2*λ ≡ k (mod n)
func TestScalarSplitLambdaProperty(t *testing.T) {
testCases := []struct {
name string
kHex string
}{
{"one", "0000000000000000000000000000000000000000000000000000000000000001"},
{"small", "0000000000000000000000000000000000000000000000000000000000000100"},
{"medium", "0000000000000000000000000000000100000000000000000000000000000000"},
{"large", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f"}, // n-2
{"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
{"random2", "0000000000000000000000000000000014551231950b75fc4402da1732fc9bebe"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Parse k
kBytes, _ := hex.DecodeString(tc.kHex)
var k Scalar
k.setB32(kBytes)
// Split k into k1, k2
var k1, k2 Scalar
scalarSplitLambda(&k1, &k2, &k)
// Verify: k1 + k2*λ ≡ k (mod n)
var k2Lambda, sum Scalar
k2Lambda.mul(&k2, &scalarLambda)
sum.add(&k1, &k2Lambda)
if !sum.equal(&k) {
var sumBytes, kBytes [32]byte
sum.getB32(sumBytes[:])
k.getB32(kBytes[:])
t.Errorf("k1 + k2*λ ≠ k:\n sum: %s\n k: %s",
hex.EncodeToString(sumBytes[:]), hex.EncodeToString(kBytes[:]))
}
})
}
}
// TestScalarSplitLambdaBounds verifies that k1 and k2 are bounded (< 2^128)
func TestScalarSplitLambdaBounds(t *testing.T) {
// Test with various random-ish scalars
testScalars := []string{
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
"7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
"0000000000000000000000000000000100000000000000000000000000000000",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
}
// Bounds from libsecp256k1: k1 < 2^128 or -k1 < 2^128, same for k2
// Actually the bounds are tighter: (a1 + a2 + 1)/2 for k1, (-b1 + b2)/2 + 1 for k2
// But we'll just check they fit in ~128 bits (upper limbs are small)
for _, hexStr := range testScalars {
kBytes, _ := hex.DecodeString(hexStr)
var k Scalar
k.setB32(kBytes)
var k1, k2 Scalar
scalarSplitLambda(&k1, &k2, &k)
// Check that k1 is small (upper two limbs should be 0 or the scalar is negated)
// If k1 is "high", then -k1 should be small
k1Small := (k1.d[2] == 0 && k1.d[3] == 0)
var negK1 Scalar
negK1.negate(&k1)
negK1Small := (negK1.d[2] == 0 && negK1.d[3] == 0)
if !k1Small && !negK1Small {
t.Errorf("k1 not bounded for k=%s: k1.d[2]=%x, k1.d[3]=%x", hexStr, k1.d[2], k1.d[3])
}
// Same for k2
k2Small := (k2.d[2] == 0 && k2.d[3] == 0)
var negK2 Scalar
negK2.negate(&k2)
negK2Small := (negK2.d[2] == 0 && negK2.d[3] == 0)
if !k2Small && !negK2Small {
t.Errorf("k2 not bounded for k=%s: k2.d[2]=%x, k2.d[3]=%x", hexStr, k2.d[2], k2.d[3])
}
}
}
// TestScalarSplitLambdaRandom tests splitLambda with random scalars
func TestScalarSplitLambdaRandom(t *testing.T) {
// Use deterministic "random" values based on hashing
for i := 0; i < 100; i++ {
// Create a pseudo-random scalar
sha := NewSHA256()
sha.Write([]byte{byte(i), byte(i >> 8)})
var kBytes [32]byte
sha.Finalize(kBytes[:])
var k Scalar
k.setB32(kBytes[:])
// Split
var k1, k2 Scalar
scalarSplitLambda(&k1, &k2, &k)
// Verify property
var k2Lambda, sum Scalar
k2Lambda.mul(&k2, &scalarLambda)
sum.add(&k1, &k2Lambda)
if !sum.equal(&k) {
t.Errorf("Iteration %d: k1 + k2*λ ≠ k", i)
}
}
}
// TestScalarSplit128 tests the 128-bit split function
func TestScalarSplit128(t *testing.T) {
// Test scalar: 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
kBytes, _ := hex.DecodeString("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")
var k Scalar
k.setB32(kBytes)
var k1, k2 Scalar
scalarSplit128(&k1, &k2, &k)
// k1 should be low 128 bits
// k2 should be high 128 bits
// In limb order: k.d[0], k.d[1] are low, k.d[2], k.d[3] are high
if k1.d[0] != k.d[0] || k1.d[1] != k.d[1] || k1.d[2] != 0 || k1.d[3] != 0 {
t.Errorf("k1 incorrect: got d[0:4]=%x,%x,%x,%x", k1.d[0], k1.d[1], k1.d[2], k1.d[3])
}
if k2.d[0] != k.d[2] || k2.d[1] != k.d[3] || k2.d[2] != 0 || k2.d[3] != 0 {
t.Errorf("k2 incorrect: got d[0:4]=%x,%x,%x,%x", k2.d[0], k2.d[1], k2.d[2], k2.d[3])
}
}
// TestMulShiftVar tests the mul_shift_var function
func TestMulShiftVar(t *testing.T) {
// Test case: multiply two known values and shift by 384
// This is the exact operation used in splitLambda
// Simple test: 1 * g1 >> 384 should give a small result
var result Scalar
result.mulShiftVar(&ScalarOne, &scalarG1, 384)
// The result should be 0 since 1 * g1 < 2^384
// (g1 is a 256-bit value, so 1 * g1 = g1 which is < 2^256 < 2^384)
if result.d[0] != 0 || result.d[1] != 0 || result.d[2] != 0 || result.d[3] != 0 {
t.Errorf("1 * g1 >> 384 should be 0, got: %x %x %x %x",
result.d[0], result.d[1], result.d[2], result.d[3])
}
// Test with a larger value that will produce non-zero result
// Use a value close to n to get a meaningful result
nMinus1Bytes, _ := hex.DecodeString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
var nMinus1 Scalar
nMinus1.setB32(nMinus1Bytes)
result.mulShiftVar(&nMinus1, &scalarG1, 384)
// Result should be non-zero and fit in ~128 bits
// (since g1 is ~256 bits, (n-1)*g1 is ~512 bits, >> 384 gives ~128 bits)
if result.d[2] != 0 || result.d[3] != 0 {
t.Logf("Warning: result has high limbs set, may indicate issue")
}
t.Logf("(n-1) * g1 >> 384 = %x %x %x %x", result.d[3], result.d[2], result.d[1], result.d[0])
}
// TestIsHigh tests the isHigh function
func TestIsHigh(t *testing.T) {
testCases := []struct {
name string
hexValue string
expected bool
}{
{"zero", "0000000000000000000000000000000000000000000000000000000000000000", false},
{"one", "0000000000000000000000000000000000000000000000000000000000000001", false},
{"n/2", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", false},
{"n/2+1", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", true},
{"n-1", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", true},
{"n-2", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bytes, _ := hex.DecodeString(tc.hexValue)
var s Scalar
s.setB32(bytes)
result := s.isHigh()
if result != tc.expected {
t.Errorf("isHigh(%s) = %v, want %v", tc.name, result, tc.expected)
}
})
}
}
// BenchmarkScalarSplitLambda benchmarks the scalar splitting operation
func BenchmarkScalarSplitLambda(b *testing.B) {
// Use a representative scalar
kBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var k Scalar
k.setB32(kBytes)
var k1, k2 Scalar
b.ResetTimer()
for i := 0; i < b.N; i++ {
scalarSplitLambda(&k1, &k2, &k)
}
}
// BenchmarkMulShiftVar benchmarks the mulShiftVar operation
func BenchmarkMulShiftVar(b *testing.B) {
kBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var k Scalar
k.setB32(kBytes)
var result Scalar
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.mulShiftVar(&k, &scalarG1, 384)
}
}
// =============================================================================
// Phase 3 Tests: Point Operations with Endomorphism
// =============================================================================
// TestMulLambdaAffine tests the affine mulLambda operation
func TestMulLambdaAffine(t *testing.T) {
// Test that mulLambda(G) produces the same result as λ*G via scalar multiplication
var lambdaG GroupElementJacobian
EcmultConst(&lambdaG, &Generator, &scalarLambda)
var lambdaGAff GroupElementAffine
lambdaGAff.setGEJ(&lambdaG)
lambdaGAff.x.normalize()
lambdaGAff.y.normalize()
// Now test mulLambda
var mulLambdaG GroupElementAffine
mulLambdaG.mulLambda(&Generator)
mulLambdaG.x.normalize()
mulLambdaG.y.normalize()
// They should be equal
if !lambdaGAff.x.equal(&mulLambdaG.x) {
var got, want [32]byte
mulLambdaG.x.getB32(got[:])
lambdaGAff.x.getB32(want[:])
t.Errorf("mulLambda(G).x ≠ λ*G.x:\n got: %s\n want: %s",
hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
}
if !lambdaGAff.y.equal(&mulLambdaG.y) {
var got, want [32]byte
mulLambdaG.y.getB32(got[:])
lambdaGAff.y.getB32(want[:])
t.Errorf("mulLambda(G).y ≠ λ*G.y:\n got: %s\n want: %s",
hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
}
}
// TestMulLambdaJacobian tests the Jacobian mulLambda operation
func TestMulLambdaJacobian(t *testing.T) {
// Convert generator to Jacobian
var gJac GroupElementJacobian
gJac.setGE(&Generator)
// Apply mulLambda
var mulLambdaG GroupElementJacobian
mulLambdaG.mulLambda(&gJac)
// Convert back to affine for comparison
var mulLambdaGAff GroupElementAffine
mulLambdaGAff.setGEJ(&mulLambdaG)
mulLambdaGAff.x.normalize()
mulLambdaGAff.y.normalize()
// Compute expected via scalar multiplication
var lambdaG GroupElementJacobian
EcmultConst(&lambdaG, &Generator, &scalarLambda)
var lambdaGAff GroupElementAffine
lambdaGAff.setGEJ(&lambdaG)
lambdaGAff.x.normalize()
lambdaGAff.y.normalize()
// They should be equal
if !lambdaGAff.equal(&mulLambdaGAff) {
t.Errorf("Jacobian mulLambda(G) ≠ λ*G")
}
}
// TestMulLambdaInfinity tests that mulLambda handles infinity correctly
func TestMulLambdaInfinity(t *testing.T) {
var inf GroupElementAffine
inf.setInfinity()
var result GroupElementAffine
result.mulLambda(&inf)
if !result.isInfinity() {
t.Errorf("mulLambda(infinity) should be infinity")
}
// Jacobian version
var infJac GroupElementJacobian
infJac.setInfinity()
var resultJac GroupElementJacobian
resultJac.mulLambda(&infJac)
if !resultJac.isInfinity() {
t.Errorf("Jacobian mulLambda(infinity) should be infinity")
}
}
// TestMulLambdaCubed tests that λ^3 * P = P (applying mulLambda 3 times returns to original)
func TestMulLambdaCubed(t *testing.T) {
// Start with generator
p := Generator
// Apply mulLambda three times
var p1, p2, p3 GroupElementAffine
p1.mulLambda(&p)
p2.mulLambda(&p1)
p3.mulLambda(&p2)
// Normalize for comparison
p3.x.normalize()
p3.y.normalize()
genNorm := Generator
genNorm.x.normalize()
genNorm.y.normalize()
// p3 should equal the original generator
if !p3.equal(&genNorm) {
var got, want [32]byte
p3.x.getB32(got[:])
genNorm.x.getB32(want[:])
t.Errorf("λ^3 * G ≠ G:\n got x: %s\n want x: %s",
hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
}
}
// TestEcmultEndoSplit tests the combined endomorphism split operation
func TestEcmultEndoSplit(t *testing.T) {
testCases := []string{
"0000000000000000000000000000000000000000000000000000000000000001",
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
"7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
}
for _, hexStr := range testCases {
t.Run(hexStr[:16]+"...", func(t *testing.T) {
// Parse scalar
sBytes, _ := hex.DecodeString(hexStr)
var s Scalar
s.setB32(sBytes)
// Use generator as the point
p := Generator
// Split
var s1, s2 Scalar
var p1, p2 GroupElementAffine
ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
// Verify s1 and s2 are not high (after negation adjustment)
if s1.isHigh() {
t.Errorf("s1 should not be high after split")
}
if s2.isHigh() {
t.Errorf("s2 should not be high after split")
}
// Verify: s1*p1 + s2*p2 = s*p
var s1p1, s2p2, sum, expected GroupElementJacobian
EcmultConst(&s1p1, &p1, &s1)
EcmultConst(&s2p2, &p2, &s2)
sum.addVar(&s1p1, &s2p2)
EcmultConst(&expected, &p, &s)
// Convert to affine for comparison
var sumAff, expectedAff GroupElementAffine
sumAff.setGEJ(&sum)
expectedAff.setGEJ(&expected)
sumAff.x.normalize()
sumAff.y.normalize()
expectedAff.x.normalize()
expectedAff.y.normalize()
if !sumAff.equal(&expectedAff) {
t.Errorf("s1*p1 + s2*p2 ≠ s*p")
}
})
}
}
// TestEcmultEndoSplitRandom tests endomorphism split with random scalars
func TestEcmultEndoSplitRandom(t *testing.T) {
for i := 0; i < 20; i++ {
// Generate pseudo-random scalar
sha := NewSHA256()
sha.Write([]byte{byte(i), byte(i >> 8), 0xAB, 0xCD})
var sBytes [32]byte
sha.Finalize(sBytes[:])
var s Scalar
s.setB32(sBytes[:])
// Use generator
p := Generator
// Split
var s1, s2 Scalar
var p1, p2 GroupElementAffine
ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
// Verify: s1*p1 + s2*p2 = s*p
var s1p1, s2p2, sum, expected GroupElementJacobian
EcmultConst(&s1p1, &p1, &s1)
EcmultConst(&s2p2, &p2, &s2)
sum.addVar(&s1p1, &s2p2)
EcmultConst(&expected, &p, &s)
var sumAff, expectedAff GroupElementAffine
sumAff.setGEJ(&sum)
expectedAff.setGEJ(&expected)
sumAff.x.normalize()
sumAff.y.normalize()
expectedAff.x.normalize()
expectedAff.y.normalize()
if !sumAff.equal(&expectedAff) {
t.Errorf("Iteration %d: s1*p1 + s2*p2 ≠ s*p", i)
}
}
}
// BenchmarkMulLambdaAffine benchmarks the affine mulLambda operation
func BenchmarkMulLambdaAffine(b *testing.B) {
p := Generator
var result GroupElementAffine
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.mulLambda(&p)
}
}
// BenchmarkMulLambdaJacobian benchmarks the Jacobian mulLambda operation
func BenchmarkMulLambdaJacobian(b *testing.B) {
var p GroupElementJacobian
p.setGE(&Generator)
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.mulLambda(&p)
}
}
// BenchmarkEcmultEndoSplit benchmarks the combined endomorphism split
func BenchmarkEcmultEndoSplit(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
p := Generator
var s1, s2 Scalar
var p1, p2 GroupElementAffine
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
}
}
// =============================================================================
// Phase 4 Tests: Strauss-GLV Algorithm
// =============================================================================
// TestBuildOddMultiplesTableAffine tests the odd multiples table building
func TestBuildOddMultiplesTableAffine(t *testing.T) {
// Build table from generator
var gJac GroupElementJacobian
gJac.setGE(&Generator)
const tableSize = 16 // Window size 5
preA := make([]GroupElementAffine, tableSize)
preBetaX := make([]FieldElement, tableSize)
buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
// Verify: preA[i] should equal (2*i+1)*G
for i := 0; i < tableSize; i++ {
var scalar Scalar
scalar.setInt(uint(2*i + 1))
var expected GroupElementJacobian
EcmultConst(&expected, &Generator, &scalar)
var expectedAff GroupElementAffine
expectedAff.setGEJ(&expected)
expectedAff.x.normalize()
expectedAff.y.normalize()
preA[i].x.normalize()
preA[i].y.normalize()
if !preA[i].equal(&expectedAff) {
t.Errorf("preA[%d] ≠ %d*G", i, 2*i+1)
}
// Verify β*x is correct
var expectedBetaX FieldElement
expectedBetaX.mul(&expectedAff.x, &fieldBeta)
expectedBetaX.normalize()
preBetaX[i].normalize()
if !preBetaX[i].equal(&expectedBetaX) {
t.Errorf("preBetaX[%d] ≠ β * preA[%d].x", i, i)
}
}
}
// TestTableGetGE tests the table lookup function
func TestTableGetGE(t *testing.T) {
// Build table from generator
var gJac GroupElementJacobian
gJac.setGE(&Generator)
const tableSize = 16
preA := make([]GroupElementAffine, tableSize)
preBetaX := make([]FieldElement, tableSize)
buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
testCases := []struct {
name string
n int
expected int // expected multiple (negative means negated)
}{
{"n=1", 1, 1},
{"n=3", 3, 3},
{"n=5", 5, 5},
{"n=15", 15, 15},
{"n=-1", -1, -1},
{"n=-3", -3, -3},
{"n=-15", -15, -15},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var pt GroupElementAffine
tableGetGE(&pt, preA, tc.n)
// Compute expected point
absN := tc.expected
if absN < 0 {
absN = -absN
}
var scalar Scalar
scalar.setInt(uint(absN))
var expectedJac GroupElementJacobian
EcmultConst(&expectedJac, &Generator, &scalar)
var expectedAff GroupElementAffine
expectedAff.setGEJ(&expectedJac)
// Negate if needed
if tc.expected < 0 {
expectedAff.negate(&expectedAff)
}
expectedAff.x.normalize()
expectedAff.y.normalize()
pt.x.normalize()
pt.y.normalize()
if !pt.equal(&expectedAff) {
t.Errorf("tableGetGE(%d) returned wrong point", tc.n)
}
})
}
// Test n=0 returns infinity
var pt GroupElementAffine
tableGetGE(&pt, preA, 0)
if !pt.isInfinity() {
t.Error("tableGetGE(0) should return infinity")
}
}
// TestTableGetGELambda tests the lambda-transformed table lookup
func TestTableGetGELambda(t *testing.T) {
// Build table from generator
var gJac GroupElementJacobian
gJac.setGE(&Generator)
const tableSize = 16
preA := make([]GroupElementAffine, tableSize)
preBetaX := make([]FieldElement, tableSize)
buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
// Test that tableGetGELambda(n) returns λ * tableGetGE(n)
for n := 1; n <= 15; n += 2 {
var ptLambda GroupElementAffine
tableGetGELambda(&ptLambda, preA, preBetaX, n)
// Get regular point and apply lambda
var pt GroupElementAffine
tableGetGE(&pt, preA, n)
var expected GroupElementAffine
expected.mulLambda(&pt)
ptLambda.x.normalize()
ptLambda.y.normalize()
expected.x.normalize()
expected.y.normalize()
if !ptLambda.equal(&expected) {
t.Errorf("tableGetGELambda(%d) ≠ λ * tableGetGE(%d)", n, n)
}
// Test negative n
tableGetGELambda(&ptLambda, preA, preBetaX, -n)
tableGetGE(&pt, preA, -n)
expected.mulLambda(&pt)
ptLambda.x.normalize()
ptLambda.y.normalize()
expected.x.normalize()
expected.y.normalize()
if !ptLambda.equal(&expected) {
t.Errorf("tableGetGELambda(%d) ≠ λ * tableGetGE(%d)", -n, -n)
}
}
}
// TestEcmultStraussWNAFGLV tests the full Strauss-GLV algorithm
func TestEcmultStraussWNAFGLV(t *testing.T) {
testCases := []string{
"0000000000000000000000000000000000000000000000000000000000000001", // 1
"0000000000000000000000000000000000000000000000000000000000000002", // 2
"0000000000000000000000000000000000000000000000000000000000000010", // 16
"00000000000000000000000000000000000000000000000000000000000000ff", // 255
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", // random
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
"7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
}
for _, hexStr := range testCases {
t.Run(hexStr[:8]+"...", func(t *testing.T) {
// Parse scalar
sBytes, _ := hex.DecodeString(hexStr)
var s Scalar
s.setB32(sBytes)
// Compute using Strauss-GLV
var resultGLV GroupElementJacobian
ecmultStraussWNAFGLV(&resultGLV, &Generator, &s)
// Compute using reference implementation
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &Generator, &s)
// Convert to affine for comparison
var resultGLVAff, resultRefAff GroupElementAffine
resultGLVAff.setGEJ(&resultGLV)
resultRefAff.setGEJ(&resultRef)
resultGLVAff.x.normalize()
resultGLVAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultGLVAff.equal(&resultRefAff) {
var gotX, wantX [32]byte
resultGLVAff.x.getB32(gotX[:])
resultRefAff.x.getB32(wantX[:])
t.Errorf("GLV result mismatch:\n got x: %s\n want x: %s",
hex.EncodeToString(gotX[:]), hex.EncodeToString(wantX[:]))
}
})
}
}
// TestEcmultStraussWNAFGLVRandom tests the Strauss-GLV algorithm with random scalars
func TestEcmultStraussWNAFGLVRandom(t *testing.T) {
for i := 0; i < 50; i++ {
// Generate pseudo-random scalar
sha := NewSHA256()
sha.Write([]byte{byte(i), byte(i >> 8), 0xDE, 0xAD})
var sBytes [32]byte
sha.Finalize(sBytes[:])
var s Scalar
s.setB32(sBytes[:])
// Compute using Strauss-GLV
var resultGLV GroupElementJacobian
ecmultStraussWNAFGLV(&resultGLV, &Generator, &s)
// Compute using reference implementation
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &Generator, &s)
// Convert to affine for comparison
var resultGLVAff, resultRefAff GroupElementAffine
resultGLVAff.setGEJ(&resultGLV)
resultRefAff.setGEJ(&resultRef)
resultGLVAff.x.normalize()
resultGLVAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultGLVAff.equal(&resultRefAff) {
t.Errorf("Iteration %d: GLV result mismatch", i)
}
}
}
// TestEcmultStraussWNAFGLVNonGenerator tests with a non-generator point
func TestEcmultStraussWNAFGLVNonGenerator(t *testing.T) {
// Create a non-generator point: 2*G
var two Scalar
two.setInt(2)
var twoGJac GroupElementJacobian
EcmultConst(&twoGJac, &Generator, &two)
var twoG GroupElementAffine
twoG.setGEJ(&twoGJac)
testCases := []string{
"0000000000000000000000000000000000000000000000000000000000000001",
"0000000000000000000000000000000000000000000000000000000000000003",
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
}
for _, hexStr := range testCases {
t.Run(hexStr[:8]+"...", func(t *testing.T) {
sBytes, _ := hex.DecodeString(hexStr)
var s Scalar
s.setB32(sBytes)
// Compute using Strauss-GLV
var resultGLV GroupElementJacobian
ecmultStraussWNAFGLV(&resultGLV, &twoG, &s)
// Compute using reference implementation
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &twoG, &s)
// Convert to affine for comparison
var resultGLVAff, resultRefAff GroupElementAffine
resultGLVAff.setGEJ(&resultGLV)
resultRefAff.setGEJ(&resultRef)
resultGLVAff.x.normalize()
resultGLVAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultGLVAff.equal(&resultRefAff) {
t.Errorf("GLV result mismatch for 2*G")
}
})
}
}
// TestEcmultStraussWNAFGLVEdgeCases tests edge cases
func TestEcmultStraussWNAFGLVEdgeCases(t *testing.T) {
// Test with zero scalar
var zero Scalar
var result GroupElementJacobian
ecmultStraussWNAFGLV(&result, &Generator, &zero)
if !result.isInfinity() {
t.Error("0 * G should be infinity")
}
// Test with infinity point
var inf GroupElementAffine
inf.setInfinity()
var one Scalar
one.setInt(1)
ecmultStraussWNAFGLV(&result, &inf, &one)
if !result.isInfinity() {
t.Error("1 * infinity should be infinity")
}
}
// BenchmarkEcmultStraussWNAFGLV benchmarks the Strauss-GLV algorithm
func BenchmarkEcmultStraussWNAFGLV(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultStraussWNAFGLV(&result, &Generator, &s)
}
}
// BenchmarkEcmultConst benchmarks the reference constant-time implementation
func BenchmarkEcmultConst(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
EcmultConst(&result, &Generator, &s)
}
}
// BenchmarkEcmultWindowedVar benchmarks the windowed variable-time implementation
func BenchmarkEcmultWindowedVar(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultWindowedVar(&result, &Generator, &s)
}
}
// BenchmarkBuildOddMultiplesTableAffine benchmarks table building
func BenchmarkBuildOddMultiplesTableAffine(b *testing.B) {
var gJac GroupElementJacobian
gJac.setGE(&Generator)
const tableSize = 16
preA := make([]GroupElementAffine, tableSize)
preBetaX := make([]FieldElement, tableSize)
b.ResetTimer()
for i := 0; i < b.N; i++ {
buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
}
}
// =============================================================================
// Phase 5 Tests: Generator Precomputation
// =============================================================================
// TestGenTablesInitialization tests that the generator tables are properly initialized
func TestGenTablesInitialization(t *testing.T) {
// Force initialization
EnsureGenTablesInitialized()
// Verify preGenG[0] = 1*G = G
preGenG[0].x.normalize()
preGenG[0].y.normalize()
genNorm := Generator
genNorm.x.normalize()
genNorm.y.normalize()
if !preGenG[0].equal(&genNorm) {
t.Error("preGenG[0] should equal G")
}
// Verify preGenG[i] = (2*i+1)*G
for i := 0; i < 8; i++ { // Check first 8 entries
var scalar Scalar
scalar.setInt(uint(2*i + 1))
var expected GroupElementJacobian
EcmultConst(&expected, &Generator, &scalar)
var expectedAff GroupElementAffine
expectedAff.setGEJ(&expected)
expectedAff.x.normalize()
expectedAff.y.normalize()
preGenG[i].x.normalize()
preGenG[i].y.normalize()
if !preGenG[i].equal(&expectedAff) {
t.Errorf("preGenG[%d] ≠ %d*G", i, 2*i+1)
}
}
// Verify preGenLambdaG[0] = λ*G
var lambdaG GroupElementAffine
lambdaG.mulLambda(&Generator)
lambdaG.x.normalize()
lambdaG.y.normalize()
preGenLambdaG[0].x.normalize()
preGenLambdaG[0].y.normalize()
if !preGenLambdaG[0].equal(&lambdaG) {
t.Error("preGenLambdaG[0] should equal λ*G")
}
// Verify preGenLambdaG[i] = (2*i+1)*(λ*G)
for i := 0; i < 8; i++ {
var scalar Scalar
scalar.setInt(uint(2*i + 1))
var expected GroupElementJacobian
EcmultConst(&expected, &lambdaG, &scalar)
var expectedAff GroupElementAffine
expectedAff.setGEJ(&expected)
expectedAff.x.normalize()
expectedAff.y.normalize()
preGenLambdaG[i].x.normalize()
preGenLambdaG[i].y.normalize()
if !preGenLambdaG[i].equal(&expectedAff) {
t.Errorf("preGenLambdaG[%d] ≠ %d*(λ*G)", i, 2*i+1)
}
}
}
// TestEcmultGenGLV tests the GLV generator multiplication
func TestEcmultGenGLV(t *testing.T) {
testCases := []string{
"0000000000000000000000000000000000000000000000000000000000000001", // 1
"0000000000000000000000000000000000000000000000000000000000000002", // 2
"0000000000000000000000000000000000000000000000000000000000000010", // 16
"00000000000000000000000000000000000000000000000000000000000000ff", // 255
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", // random
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
"7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
}
for _, hexStr := range testCases {
t.Run(hexStr[:8]+"...", func(t *testing.T) {
// Parse scalar
sBytes, _ := hex.DecodeString(hexStr)
var s Scalar
s.setB32(sBytes)
// Compute using GLV generator multiplication
var resultGLV GroupElementJacobian
ecmultGenGLV(&resultGLV, &s)
// Compute using reference implementation
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &Generator, &s)
// Convert to affine for comparison
var resultGLVAff, resultRefAff GroupElementAffine
resultGLVAff.setGEJ(&resultGLV)
resultRefAff.setGEJ(&resultRef)
resultGLVAff.x.normalize()
resultGLVAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultGLVAff.equal(&resultRefAff) {
var gotX, wantX [32]byte
resultGLVAff.x.getB32(gotX[:])
resultRefAff.x.getB32(wantX[:])
t.Errorf("GLV gen result mismatch:\n got x: %s\n want x: %s",
hex.EncodeToString(gotX[:]), hex.EncodeToString(wantX[:]))
}
})
}
}
// TestEcmultGenGLVRandom tests GLV generator multiplication with random scalars
func TestEcmultGenGLVRandom(t *testing.T) {
for i := 0; i < 50; i++ {
// Generate pseudo-random scalar
sha := NewSHA256()
sha.Write([]byte{byte(i), byte(i >> 8), 0xBE, 0xEF})
var sBytes [32]byte
sha.Finalize(sBytes[:])
var s Scalar
s.setB32(sBytes[:])
// Compute using GLV
var resultGLV GroupElementJacobian
ecmultGenGLV(&resultGLV, &s)
// Compute using reference
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &Generator, &s)
// Compare
var resultGLVAff, resultRefAff GroupElementAffine
resultGLVAff.setGEJ(&resultGLV)
resultRefAff.setGEJ(&resultRef)
resultGLVAff.x.normalize()
resultGLVAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultGLVAff.equal(&resultRefAff) {
t.Errorf("Iteration %d: GLV gen result mismatch", i)
}
}
}
// TestEcmultGenSimple tests the simple generator multiplication
func TestEcmultGenSimple(t *testing.T) {
testCases := []string{
"0000000000000000000000000000000000000000000000000000000000000001",
"00000000000000000000000000000000000000000000000000000000000000ff",
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
}
for _, hexStr := range testCases {
t.Run(hexStr[:8]+"...", func(t *testing.T) {
sBytes, _ := hex.DecodeString(hexStr)
var s Scalar
s.setB32(sBytes)
// Compute using simple generator multiplication
var resultSimple GroupElementJacobian
ecmultGenSimple(&resultSimple, &s)
// Compute using reference
var resultRef GroupElementJacobian
EcmultConst(&resultRef, &Generator, &s)
// Compare
var resultSimpleAff, resultRefAff GroupElementAffine
resultSimpleAff.setGEJ(&resultSimple)
resultRefAff.setGEJ(&resultRef)
resultSimpleAff.x.normalize()
resultSimpleAff.y.normalize()
resultRefAff.x.normalize()
resultRefAff.y.normalize()
if !resultSimpleAff.equal(&resultRefAff) {
t.Errorf("Simple gen result mismatch")
}
})
}
}
// TestEcmultGenGLVEdgeCases tests edge cases
func TestEcmultGenGLVEdgeCases(t *testing.T) {
// Test with zero scalar
var zero Scalar
var result GroupElementJacobian
ecmultGenGLV(&result, &zero)
if !result.isInfinity() {
t.Error("0 * G should be infinity")
}
// Test with scalar 1
var one Scalar
one.setInt(1)
ecmultGenGLV(&result, &one)
var resultAff GroupElementAffine
resultAff.setGEJ(&result)
resultAff.x.normalize()
resultAff.y.normalize()
genNorm := Generator
genNorm.x.normalize()
genNorm.y.normalize()
if !resultAff.equal(&genNorm) {
t.Error("1 * G should equal G")
}
}
// BenchmarkEcmultGenGLV benchmarks the GLV generator multiplication
func BenchmarkEcmultGenGLV(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
// Ensure tables are initialized before benchmark
EnsureGenTablesInitialized()
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultGenGLV(&result, &s)
}
}
// BenchmarkEcmultGenSimple benchmarks the simple generator multiplication
func BenchmarkEcmultGenSimple(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
// Ensure tables are initialized before benchmark
EnsureGenTablesInitialized()
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultGenSimple(&result, &s)
}
}
// BenchmarkEcmultGenConstRef benchmarks the reference constant-time implementation for G
func BenchmarkEcmultGenConstRef(b *testing.B) {
sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
var s Scalar
s.setB32(sBytes)
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
EcmultConst(&result, &Generator, &s)
}
}
// =============================================================================
// Phase 6: Cross-Validation Tests
// =============================================================================
// TestCrossValidationAllImplementations verifies that all scalar multiplication
// implementations produce the same results
func TestCrossValidationAllImplementations(t *testing.T) {
testCases := []struct {
name string
scalar string
}{
{"one", "0000000000000000000000000000000000000000000000000000000000000001"},
{"two", "0000000000000000000000000000000000000000000000000000000000000002"},
{"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
{"random2", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef"},
{"high_bits", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"half_order", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"},
{"small", "0000000000000000000000000000000000000000000000000000000000000010"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sBytes, _ := hex.DecodeString(tc.scalar)
var s Scalar
s.setB32(sBytes)
if s.isZero() {
return // Skip zero scalar
}
// Reference: EcmultConst (constant-time binary method)
var refResult GroupElementJacobian
EcmultConst(&refResult, &Generator, &s)
var refAff GroupElementAffine
refAff.setGEJ(&refResult)
refAff.x.normalize()
refAff.y.normalize()
// Test 1: ecmultGenGLV (GLV generator multiplication)
var glvResult GroupElementJacobian
ecmultGenGLV(&glvResult, &s)
var glvAff GroupElementAffine
glvAff.setGEJ(&glvResult)
glvAff.x.normalize()
glvAff.y.normalize()
if !refAff.x.equal(&glvAff.x) || !refAff.y.equal(&glvAff.y) {
t.Errorf("ecmultGenGLV mismatch for %s", tc.name)
}
// Test 2: ecmultGenSimple (simple generator multiplication)
var simpleResult GroupElementJacobian
ecmultGenSimple(&simpleResult, &s)
var simpleAff GroupElementAffine
simpleAff.setGEJ(&simpleResult)
simpleAff.x.normalize()
simpleAff.y.normalize()
if !refAff.x.equal(&simpleAff.x) || !refAff.y.equal(&simpleAff.y) {
t.Errorf("ecmultGenSimple mismatch for %s", tc.name)
}
// Test 3: EcmultGen (public interface)
var publicResult GroupElementJacobian
EcmultGen(&publicResult, &s)
var publicAff GroupElementAffine
publicAff.setGEJ(&publicResult)
publicAff.x.normalize()
publicAff.y.normalize()
if !refAff.x.equal(&publicAff.x) || !refAff.y.equal(&publicAff.y) {
t.Errorf("EcmultGen mismatch for %s", tc.name)
}
// Test 4: ecmultStraussWNAFGLV with Generator
var straussResult GroupElementJacobian
ecmultStraussWNAFGLV(&straussResult, &Generator, &s)
var straussAff GroupElementAffine
straussAff.setGEJ(&straussResult)
straussAff.x.normalize()
straussAff.y.normalize()
if !refAff.x.equal(&straussAff.x) || !refAff.y.equal(&straussAff.y) {
t.Errorf("ecmultStraussWNAFGLV mismatch for %s", tc.name)
}
// Test 5: Ecmult (public interface with Jacobian input)
var genJac GroupElementJacobian
genJac.setGE(&Generator)
var ecmultResult GroupElementJacobian
Ecmult(&ecmultResult, &genJac, &s)
var ecmultAff GroupElementAffine
ecmultAff.setGEJ(&ecmultResult)
ecmultAff.x.normalize()
ecmultAff.y.normalize()
if !refAff.x.equal(&ecmultAff.x) || !refAff.y.equal(&ecmultAff.y) {
t.Errorf("Ecmult mismatch for %s", tc.name)
}
// Test 6: ecmultWindowedVar
var windowedResult GroupElementJacobian
ecmultWindowedVar(&windowedResult, &Generator, &s)
var windowedAff GroupElementAffine
windowedAff.setGEJ(&windowedResult)
windowedAff.x.normalize()
windowedAff.y.normalize()
if !refAff.x.equal(&windowedAff.x) || !refAff.y.equal(&windowedAff.y) {
t.Errorf("ecmultWindowedVar mismatch for %s", tc.name)
}
})
}
}
// TestCrossValidationArbitraryPoint tests all implementations with a non-generator point
func TestCrossValidationArbitraryPoint(t *testing.T) {
// Create an arbitrary point P = 7*G
var sevenScalar Scalar
sevenScalar.d[0] = 7
var pJac GroupElementJacobian
EcmultConst(&pJac, &Generator, &sevenScalar)
var p GroupElementAffine
p.setGEJ(&pJac)
p.x.normalize()
p.y.normalize()
testCases := []struct {
name string
scalar string
}{
{"one", "0000000000000000000000000000000000000000000000000000000000000001"},
{"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
{"random2", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sBytes, _ := hex.DecodeString(tc.scalar)
var s Scalar
s.setB32(sBytes)
if s.isZero() {
return
}
// Reference: EcmultConst
var refResult GroupElementJacobian
EcmultConst(&refResult, &p, &s)
var refAff GroupElementAffine
refAff.setGEJ(&refResult)
refAff.x.normalize()
refAff.y.normalize()
// Test 1: ecmultStraussWNAFGLV
var straussResult GroupElementJacobian
ecmultStraussWNAFGLV(&straussResult, &p, &s)
var straussAff GroupElementAffine
straussAff.setGEJ(&straussResult)
straussAff.x.normalize()
straussAff.y.normalize()
if !refAff.x.equal(&straussAff.x) || !refAff.y.equal(&straussAff.y) {
t.Errorf("ecmultStraussWNAFGLV mismatch for arbitrary point with %s", tc.name)
}
// Test 2: ecmultWindowedVar
var windowedResult GroupElementJacobian
ecmultWindowedVar(&windowedResult, &p, &s)
var windowedAff GroupElementAffine
windowedAff.setGEJ(&windowedResult)
windowedAff.x.normalize()
windowedAff.y.normalize()
if !refAff.x.equal(&windowedAff.x) || !refAff.y.equal(&windowedAff.y) {
t.Errorf("ecmultWindowedVar mismatch for arbitrary point with %s", tc.name)
}
// Test 3: Ecmult (public interface)
var pJac2 GroupElementJacobian
pJac2.setGE(&p)
var ecmultResult GroupElementJacobian
Ecmult(&ecmultResult, &pJac2, &s)
var ecmultAff GroupElementAffine
ecmultAff.setGEJ(&ecmultResult)
ecmultAff.x.normalize()
ecmultAff.y.normalize()
if !refAff.x.equal(&ecmultAff.x) || !refAff.y.equal(&ecmultAff.y) {
t.Errorf("Ecmult mismatch for arbitrary point with %s", tc.name)
}
})
}
}
// TestCrossValidationRandomScalars tests with random scalars
func TestCrossValidationRandomScalars(t *testing.T) {
seed := uint64(0xdeadbeef12345678)
for i := 0; i < 50; i++ {
// Generate pseudo-random scalar
var sBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
sBytes[j] = byte(seed >> 56)
}
var s Scalar
s.setB32(sBytes[:])
if s.isZero() {
continue
}
// Reference
var refResult GroupElementJacobian
EcmultConst(&refResult, &Generator, &s)
var refAff GroupElementAffine
refAff.setGEJ(&refResult)
refAff.x.normalize()
refAff.y.normalize()
// GLV
var glvResult GroupElementJacobian
ecmultGenGLV(&glvResult, &s)
var glvAff GroupElementAffine
glvAff.setGEJ(&glvResult)
glvAff.x.normalize()
glvAff.y.normalize()
if !refAff.x.equal(&glvAff.x) || !refAff.y.equal(&glvAff.y) {
var sHex [32]byte
s.getB32(sHex[:])
t.Errorf("Random test %d failed: scalar=%s", i, hex.EncodeToString(sHex[:]))
}
}
}
// =============================================================================
// Phase 6: Property-Based Tests
// =============================================================================
// TestPropertyScalarSplitReconstruction verifies that k1 + k2*λ ≡ k (mod n)
func TestPropertyScalarSplitReconstruction(t *testing.T) {
seed := uint64(0xcafebabe98765432)
for i := 0; i < 100; i++ {
// Generate pseudo-random scalar
var kBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
kBytes[j] = byte(seed >> 56)
}
var k Scalar
k.setB32(kBytes[:])
if k.isZero() {
continue
}
// Split k
var k1, k2 Scalar
scalarSplitLambda(&k1, &k2, &k)
// Compute k2 * λ
var k2Lambda Scalar
k2Lambda.mul(&k2, &scalarLambda)
// Compute k1 + k2*λ
var reconstructed Scalar
reconstructed.add(&k1, &k2Lambda)
// Verify k1 + k2*λ ≡ k (mod n)
if !reconstructed.equal(&k) {
var kHex, rHex [32]byte
k.getB32(kHex[:])
reconstructed.getB32(rHex[:])
t.Errorf("Scalar split reconstruction failed:\n k=%s\n reconstructed=%s",
hex.EncodeToString(kHex[:]), hex.EncodeToString(rHex[:]))
}
}
}
// TestPropertyLambdaEndomorphism verifies that λ·P = (β·x, y) for points on the curve
func TestPropertyLambdaEndomorphism(t *testing.T) {
seed := uint64(0x1234567890abcdef)
for i := 0; i < 50; i++ {
// Generate a random point by multiplying G by a random scalar
var sBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
sBytes[j] = byte(seed >> 56)
}
var s Scalar
s.setB32(sBytes[:])
if s.isZero() {
continue
}
// P = s * G
var pJac GroupElementJacobian
EcmultConst(&pJac, &Generator, &s)
var p GroupElementAffine
p.setGEJ(&pJac)
p.x.normalize()
p.y.normalize()
if p.isInfinity() {
continue
}
// Method 1: λ·P via scalar multiplication
var lambdaPJac GroupElementJacobian
EcmultConst(&lambdaPJac, &p, &scalarLambda)
var lambdaP1 GroupElementAffine
lambdaP1.setGEJ(&lambdaPJac)
lambdaP1.x.normalize()
lambdaP1.y.normalize()
// Method 2: λ·P via endomorphism (β·x, y)
var lambdaP2 GroupElementAffine
lambdaP2.mulLambda(&p)
lambdaP2.x.normalize()
lambdaP2.y.normalize()
// Verify they match
if !lambdaP1.x.equal(&lambdaP2.x) || !lambdaP1.y.equal(&lambdaP2.y) {
t.Errorf("Lambda endomorphism mismatch at iteration %d", i)
}
}
}
// TestPropertyBetaCubeRoot verifies that β^3 ≡ 1 (mod p)
func TestPropertyBetaCubeRoot(t *testing.T) {
// Compute β^2
var beta2 FieldElement
beta2.mul(&fieldBeta, &fieldBeta)
// Compute β^3 = β^2 * β
var beta3 FieldElement
beta3.mul(&beta2, &fieldBeta)
beta3.normalize()
// Check if β^3 ≡ 1 (mod p)
if !beta3.equal(&FieldElementOne) {
var bytes [32]byte
beta3.getB32(bytes[:])
t.Errorf("β^3 ≠ 1 (mod p), got: %s", hex.EncodeToString(bytes[:]))
}
}
// TestPropertyDoubleNegate verifies that -(-P) = P
func TestPropertyDoubleNegate(t *testing.T) {
seed := uint64(0xfedcba0987654321)
for i := 0; i < 50; i++ {
// Generate a random point
var sBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
sBytes[j] = byte(seed >> 56)
}
var s Scalar
s.setB32(sBytes[:])
if s.isZero() {
continue
}
var pJac GroupElementJacobian
ecmultGenGLV(&pJac, &s)
var p GroupElementAffine
p.setGEJ(&pJac)
p.x.normalize()
p.y.normalize()
if p.isInfinity() {
continue
}
// Negate twice
var negP, negNegP GroupElementAffine
negP.negate(&p)
negNegP.negate(&negP)
negNegP.x.normalize()
negNegP.y.normalize()
// Verify -(-P) = P
if !p.x.equal(&negNegP.x) || !p.y.equal(&negNegP.y) {
t.Errorf("Double negation property failed at iteration %d", i)
}
}
}
// TestPropertyScalarAddition verifies that (a+b)*G = a*G + b*G
func TestPropertyScalarAddition(t *testing.T) {
seed := uint64(0xabcdef1234567890)
for i := 0; i < 50; i++ {
// Generate two random scalars
var aBytes, bBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
aBytes[j] = byte(seed >> 56)
seed = seed*6364136223846793005 + 1442695040888963407
bBytes[j] = byte(seed >> 56)
}
var a, b Scalar
a.setB32(aBytes[:])
b.setB32(bBytes[:])
// Compute (a+b)*G
var sum Scalar
sum.add(&a, &b)
var sumG GroupElementJacobian
ecmultGenGLV(&sumG, &sum)
var sumGAff GroupElementAffine
sumGAff.setGEJ(&sumG)
sumGAff.x.normalize()
sumGAff.y.normalize()
// Compute a*G + b*G
var aG, bG GroupElementJacobian
ecmultGenGLV(&aG, &a)
ecmultGenGLV(&bG, &b)
var aGplusBG GroupElementJacobian
aGplusBG.addVar(&aG, &bG)
var aGplusBGAff GroupElementAffine
aGplusBGAff.setGEJ(&aGplusBG)
aGplusBGAff.x.normalize()
aGplusBGAff.y.normalize()
// Verify (a+b)*G = a*G + b*G
if sumGAff.isInfinity() != aGplusBGAff.isInfinity() {
t.Errorf("Scalar addition property failed at iteration %d (infinity mismatch)", i)
continue
}
if !sumGAff.isInfinity() {
if !sumGAff.x.equal(&aGplusBGAff.x) || !sumGAff.y.equal(&aGplusBGAff.y) {
t.Errorf("Scalar addition property failed at iteration %d", i)
}
}
}
}
// TestPropertyScalarMultiplication verifies that (a*b)*G = a*(b*G)
func TestPropertyScalarMultiplication(t *testing.T) {
seed := uint64(0x9876543210fedcba)
for i := 0; i < 50; i++ {
// Generate two random scalars
var aBytes, bBytes [32]byte
for j := 0; j < 32; j++ {
seed = seed*6364136223846793005 + 1442695040888963407
aBytes[j] = byte(seed >> 56)
seed = seed*6364136223846793005 + 1442695040888963407
bBytes[j] = byte(seed >> 56)
}
var a, b Scalar
a.setB32(aBytes[:])
b.setB32(bBytes[:])
// Compute (a*b)*G
var product Scalar
product.mul(&a, &b)
var productG GroupElementJacobian
ecmultGenGLV(&productG, &product)
var productGAff GroupElementAffine
productGAff.setGEJ(&productG)
productGAff.x.normalize()
productGAff.y.normalize()
// Compute a*(b*G)
var bG GroupElementJacobian
ecmultGenGLV(&bG, &b)
var bGAff GroupElementAffine
bGAff.setGEJ(&bG)
bGAff.x.normalize()
bGAff.y.normalize()
var aBG GroupElementJacobian
ecmultStraussWNAFGLV(&aBG, &bGAff, &a)
var aBGAff GroupElementAffine
aBGAff.setGEJ(&aBG)
aBGAff.x.normalize()
aBGAff.y.normalize()
// Verify (a*b)*G = a*(b*G)
if productGAff.isInfinity() != aBGAff.isInfinity() {
t.Errorf("Scalar multiplication property failed at iteration %d (infinity mismatch)", i)
continue
}
if !productGAff.isInfinity() {
if !productGAff.x.equal(&aBGAff.x) || !productGAff.y.equal(&aBGAff.y) {
t.Errorf("Scalar multiplication property failed at iteration %d", i)
}
}
}
}
// TestEcmultCombined tests the combined na*a + ng*G function
func TestEcmultCombined(t *testing.T) {
testCases := []struct {
name string
na string
ng string
}{
{"both_one", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
{"na_only", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ng_only", "0000000000000000000000000000000000000000000000000000000000000000", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
{"random_both", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
}
// Create an arbitrary point P = 7*G
var sevenScalar Scalar
sevenScalar.d[0] = 7
var pJac GroupElementJacobian
EcmultConst(&pJac, &Generator, &sevenScalar)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
naBytes, _ := hex.DecodeString(tc.na)
ngBytes, _ := hex.DecodeString(tc.ng)
var na, ng Scalar
na.setB32(naBytes)
ng.setB32(ngBytes)
// Compute using EcmultCombined
var combined GroupElementJacobian
EcmultCombined(&combined, &pJac, &na, &ng)
var combinedAff GroupElementAffine
combinedAff.setGEJ(&combined)
combinedAff.x.normalize()
combinedAff.y.normalize()
// Compute reference: na*P + ng*G separately
var naP, ngG, ref GroupElementJacobian
if !na.isZero() {
var pAff GroupElementAffine
pAff.setGEJ(&pJac)
EcmultConst(&naP, &pAff, &na)
} else {
naP.setInfinity()
}
if !ng.isZero() {
EcmultConst(&ngG, &Generator, &ng)
} else {
ngG.setInfinity()
}
ref.addVar(&naP, &ngG)
var refAff GroupElementAffine
refAff.setGEJ(&ref)
refAff.x.normalize()
refAff.y.normalize()
// Verify
if combinedAff.isInfinity() != refAff.isInfinity() {
t.Errorf("EcmultCombined infinity mismatch for %s", tc.name)
return
}
if !combinedAff.isInfinity() {
if !combinedAff.x.equal(&refAff.x) || !combinedAff.y.equal(&refAff.y) {
t.Errorf("EcmultCombined result mismatch for %s", tc.name)
}
}
})
}
}
// BenchmarkEcmultCombined benchmarks the combined multiplication
func BenchmarkEcmultCombined(b *testing.B) {
naBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
ngBytes, _ := hex.DecodeString("deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef")
var na, ng Scalar
na.setB32(naBytes)
ng.setB32(ngBytes)
// Create point P = 7*G
var sevenScalar Scalar
sevenScalar.d[0] = 7
var pJac GroupElementJacobian
EcmultConst(&pJac, &Generator, &sevenScalar)
EnsureGenTablesInitialized()
var result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
EcmultCombined(&result, &pJac, &na, &ng)
}
}
// BenchmarkEcmultSeparate benchmarks separate na*P and ng*G computations
func BenchmarkEcmultSeparate(b *testing.B) {
naBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
ngBytes, _ := hex.DecodeString("deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef")
var na, ng Scalar
na.setB32(naBytes)
ng.setB32(ngBytes)
// Create point P = 7*G
var sevenScalar Scalar
sevenScalar.d[0] = 7
var pJac GroupElementJacobian
EcmultConst(&pJac, &Generator, &sevenScalar)
var pAff GroupElementAffine
pAff.setGEJ(&pJac)
EnsureGenTablesInitialized()
var naP, ngG, result GroupElementJacobian
b.ResetTimer()
for i := 0; i < b.N; i++ {
ecmultStraussWNAFGLV(&naP, &pAff, &na)
ecmultGenGLV(&ngG, &ng)
result.addVar(&naP, &ngG)
}
}