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)
	}
}
