diff --git a/crypto/modexp/benchmark_comprehensive_test.go b/crypto/modexp/benchmark_comprehensive_test.go index e9cd106a6a..a303df679e 100644 --- a/crypto/modexp/benchmark_comprehensive_test.go +++ b/crypto/modexp/benchmark_comprehensive_test.go @@ -5,7 +5,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/modexp/bigint" gmpcwrapper "github.com/ethereum/go-ethereum/crypto/modexp/gmp/cwrapper" - gmpgeneric "github.com/ethereum/go-ethereum/crypto/modexp/gmp/generic" ) // generateWorstCase generates a byte array with all bits set to 1 @@ -44,7 +43,6 @@ func BenchmarkComprehensive(b *testing.B) { fn func([]byte, []byte, []byte) ([]byte, error) }{ {"BigInt", bigint.ModExp}, - {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, } @@ -96,7 +94,6 @@ func BenchmarkWorstCaseOnly(b *testing.B) { fn func([]byte, []byte, []byte) ([]byte, error) }{ {"BigInt", bigint.ModExp}, - {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, } @@ -142,7 +139,6 @@ func BenchmarkMemoryProfile(b *testing.B) { fn func([]byte, []byte, []byte) ([]byte, error) }{ {"BigInt", bigint.ModExp}, - {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, } @@ -215,12 +211,6 @@ func BenchmarkComparison(b *testing.B) { } }) - b.Run("GMPGeneric", func(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _ = gmpgeneric.ModExp(data.base, data.exp, data.mod) - } - }) - b.Run("GMPCWrapper", func(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = gmpcwrapper.ModExp(data.base, data.exp, data.mod) diff --git a/crypto/modexp/gmp/README.md b/crypto/modexp/gmp/README.md index e2264ffff7..34ae8f1cba 100644 --- a/crypto/modexp/gmp/README.md +++ b/crypto/modexp/gmp/README.md @@ -15,9 +15,7 @@ You need to have GMP development libraries installed on your system: ## Usage -This package provides a GMP-backed implementation for modular exponentiation. The API can be expanded, however right now, the main usage is for the modexp precompile. - -There are currently two implementations: generic (using Go wrapper types) and cwrapper (direct C calls). +This package provides a GMP-backed implementation for modular exponentiation using direct C calls for maximum performance. The API can be expanded, however right now, the main usage is for the modexp precompile. ### Byte Array Interface (Recommended) @@ -35,25 +33,6 @@ if err != nil { // result = 24 (2^10 mod 1000) ``` -### Direct GMP Interface - -```go -// Create numbers -base := gmp.NewInt() -base.SetString("123456789", 10) - -exp := gmp.NewInt() -exp.SetString("987654321", 10) - -mod := gmp.NewInt() -mod.SetString("1000000007", 10) - -// Compute base^exp mod mod -result := gmp.NewInt() -result.ExpMod(base, exp, mod) - -fmt.Printf("Result: %s\n", result) -``` ## Testing diff --git a/crypto/modexp/gmp/cwrapper/gmp_test.go b/crypto/modexp/gmp/cwrapper/gmp_test.go index 9aa3191a4f..c63ed6a76a 100644 --- a/crypto/modexp/gmp/cwrapper/gmp_test.go +++ b/crypto/modexp/gmp/cwrapper/gmp_test.go @@ -6,8 +6,8 @@ import ( "testing" ) -// TestWrapperVsExisting compares the wrapper with existing GMP bindings -func TestWrapperVsExisting(t *testing.T) { +// TestWrapperVsBigInt compares the wrapper with Go's math/big +func TestWrapperVsBigInt(t *testing.T) { tests := []struct { name string base string @@ -60,22 +60,16 @@ func TestWrapperVsExisting(t *testing.T) { t.Fatalf("Wrapper error: %v", err) } - // Test with existing GMP bindings - gmpBase := NewInt() - gmpBase.SetString(tt.base, 10) - gmpExp := NewInt() - gmpExp.SetString(tt.exp, 10) - gmpMod := NewInt() - gmpMod.SetString(tt.mod, 10) - gmpResult := NewInt() - gmpResult.ExpMod(gmpBase, gmpExp, gmpMod) + // Test with math/big + bigResult := new(big.Int) + bigResult.Exp(baseBig, expBig, modBig) - existingResult := gmpResult.Bytes() + expectedResult := bigResult.Bytes() // Compare results - if !bytes.Equal(wrapperResult, existingResult) { - t.Errorf("Results differ:\nWrapper: %x\nExisting: %x", - wrapperResult, existingResult) + if !bytes.Equal(wrapperResult, expectedResult) { + t.Errorf("Results differ:\nWrapper: %x\nExpected: %x", + wrapperResult, expectedResult) } }) } @@ -100,7 +94,7 @@ func BenchmarkModExp(b *testing.B) { } } -func BenchmarkModExpExisting(b *testing.B) { +func BenchmarkModExpBigInt(b *testing.B) { baseBytes := make([]byte, 60) expBytes := make([]byte, 60) modBytes := make([]byte, 60) @@ -112,17 +106,17 @@ func BenchmarkModExpExisting(b *testing.B) { } modBytes[59] |= 0x01 - base := NewInt() + base := new(big.Int) base.SetBytes(baseBytes) - exp := NewInt() + exp := new(big.Int) exp.SetBytes(expBytes) - mod := NewInt() + mod := new(big.Int) mod.SetBytes(modBytes) - result := NewInt() + result := new(big.Int) b.ResetTimer() for i := 0; i < b.N; i++ { - result.ExpMod(base, exp, mod) + result.Exp(base, exp, mod) _ = result.Bytes() } } \ No newline at end of file diff --git a/crypto/modexp/gmp/generic/gmp.go b/crypto/modexp/gmp/generic/gmp.go deleted file mode 100644 index 43a47df52e..0000000000 --- a/crypto/modexp/gmp/generic/gmp.go +++ /dev/null @@ -1,158 +0,0 @@ -package gmp - -// Uses system-installed GMP library - -// #cgo LDFLAGS: -lgmp -// #include -// #include -// -// static inline int mpz_sgn_wrapper(const mpz_t op) { -// return mpz_sgn(op); -// } -import "C" -import ( - "runtime" - "unsafe" -) - -// Int represents a GMP integer -type Int struct { - mpz C.mpz_t -} - -// NewInt creates a new GMP integer -func NewInt() *Int { - z := &Int{} - C.mpz_init(&z.mpz[0]) - runtime.SetFinalizer(z, (*Int).destroy) - return z -} - -// destroy cleans up the GMP integer -func (z *Int) destroy() { - C.mpz_clear(&z.mpz[0]) -} - -// SetString sets the integer from a string in the given base -func (z *Int) SetString(s string, base int) (*Int, bool) { - cs := C.CString(s) - defer C.free(unsafe.Pointer(cs)) - - if C.mpz_set_str(&z.mpz[0], cs, C.int(base)) != 0 { - return nil, false - } - return z, true -} - -// ExpMod computes z = base^exp mod mod (modular exponentiation) -func (z *Int) ExpMod(base, exp, mod *Int) *Int { - C.mpz_powm(&z.mpz[0], &base.mpz[0], &exp.mpz[0], &mod.mpz[0]) - return z -} - -// SetBytes sets z to the value of buf interpreted as a big-endian unsigned integer -func (z *Int) SetBytes(buf []byte) *Int { - if len(buf) == 0 { - C.mpz_set_ui(&z.mpz[0], 0) - return z - } - - // Use GMP's import function for efficiency - C.mpz_import(&z.mpz[0], C.size_t(len(buf)), 1, 1, 0, 0, unsafe.Pointer(&buf[0])) - return z -} - -// Bytes returns the absolute value of z as a big-endian byte slice -func (z *Int) Bytes() []byte { - if z == nil { - return nil - } - - // Special case: zero returns empty slice (matching big.Int) - if C.mpz_sgn_wrapper(&z.mpz[0]) == 0 { - return []byte{} - } - - // Get the number of bytes needed - size := (C.mpz_sizeinbase(&z.mpz[0], 2) + 7) / 8 - - // Allocate buffer - buf := make([]byte, size) - - // Export to bytes - var count C.size_t - C.mpz_export(unsafe.Pointer(&buf[0]), &count, 1, 1, 0, 0, &z.mpz[0]) - - // Trim if needed (shouldn't happen but just in case) - if int(count) < len(buf) { - buf = buf[:count] - } - - return buf -} - - -// BitLen returns the number of bits required to represent z -func (z *Int) BitLen() int { - if z == nil { - return 0 - } - return int(C.mpz_sizeinbase(&z.mpz[0], 2)) -} - -// Mod sets z to x mod y and returns z -func (z *Int) Mod(x, y *Int) *Int { - C.mpz_mod(&z.mpz[0], &x.mpz[0], &y.mpz[0]) - return z -} - -// SetUint64 sets z to the value of x -func (z *Int) SetUint64(x uint64) *Int { - C.mpz_set_ui(&z.mpz[0], C.ulong(x)) - return z -} - -// ModExp performs modular exponentiation on byte arrays using GMP -// result = base^exp mod mod -// This function matches the behavior of the EVM modexp precompile -func ModExp(base, exp, mod []byte) ([]byte, error) { - // Handle empty modulus - return empty result (EVM behavior) - if len(mod) == 0 { - return []byte{}, nil - } - - // Check for zero modulus - allZero := true - for _, b := range mod { - if b != 0 { - allZero = false - break - } - } - if allZero { - return []byte{}, nil - } - - // Create GMP integers - baseInt := NewInt() - expInt := NewInt() - modInt := NewInt() - resultInt := NewInt() - - // Set values - baseInt.SetBytes(base) - expInt.SetBytes(exp) - modInt.SetBytes(mod) - - // Special case: base has bit length 1 (base == 1) - if baseInt.BitLen() == 1 { - // Just return base % mod - resultInt.Mod(baseInt, modInt) - } else { - // Normal case: perform modular exponentiation - resultInt.ExpMod(baseInt, expInt, modInt) - } - - // Get result bytes - return resultInt.Bytes(), nil -} \ No newline at end of file diff --git a/crypto/modexp/gmp/generic/gmp_test.go b/crypto/modexp/gmp/generic/gmp_test.go deleted file mode 100644 index 93e427af9c..0000000000 --- a/crypto/modexp/gmp/generic/gmp_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package gmp - -import ( - "bytes" - "math/big" - "testing" -) - -// TestModExpAgainstBigInt tests our GMP implementation against Go's math/big -func TestModExpAgainstBigInt(t *testing.T) { - tests := []struct { - name string - base string - exp string - mod string - }{ - { - name: "small_numbers", - base: "2", - exp: "10", - mod: "1000", - }, - { - name: "medium_numbers", - base: "123", - exp: "456", - mod: "789", - }, - { - name: "large_base", - base: "123456789012345678901234567890", - exp: "2", - mod: "1000000007", - }, - { - name: "large_exponent", - base: "2", - exp: "123456789012345678901234567890", - mod: "1000000007", - }, - { - name: "all_large", - base: "123456789012345678901234567890", - exp: "987654321098765432109876543210", - mod: "111111111111111111111111111111", - }, - { - name: "prime_modulus", - base: "12345", - exp: "67890", - mod: "2147483647", // 2^31 - 1 (Mersenne prime) - }, - { - name: "fermat_little_theorem", - base: "3", - exp: "16", - mod: "17", - }, - { - name: "carmichael_number", - base: "2", - exp: "560", - mod: "561", // First Carmichael number - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // GMP calculation - gmpBase := NewInt() - gmpBase.SetString(tt.base, 10) - - gmpExp := NewInt() - gmpExp.SetString(tt.exp, 10) - - gmpMod := NewInt() - gmpMod.SetString(tt.mod, 10) - - gmpResult := NewInt() - gmpResult.ExpMod(gmpBase, gmpExp, gmpMod) - - // math/big calculation - bigBase := new(big.Int) - bigBase.SetString(tt.base, 10) - - bigExp := new(big.Int) - bigExp.SetString(tt.exp, 10) - - bigMod := new(big.Int) - bigMod.SetString(tt.mod, 10) - - bigResult := new(big.Int) - bigResult.Exp(bigBase, bigExp, bigMod) - - // Compare results - if gmpResult.String() != bigResult.String() { - t.Errorf("ModExp mismatch:\n GMP: %s\n big: %s", - gmpResult.String(), bigResult.String()) - } - }) - } -} - -// TestSetBytesAgainstBigInt tests byte conversion against math/big -func TestSetBytesAgainstBigInt(t *testing.T) { - tests := []struct { - name string - bytes []byte - }{ - {"empty", []byte{}}, - {"single_byte", []byte{0x42}}, - {"two_bytes", []byte{0x12, 0x34}}, - {"four_bytes", []byte{0xDE, 0xAD, 0xBE, 0xEF}}, - {"eight_bytes", []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}}, - {"all_zeros", []byte{0x00, 0x00, 0x00, 0x00}}, - {"leading_zeros", []byte{0x00, 0x00, 0x12, 0x34}}, - {"all_ones", []byte{0xFF, 0xFF, 0xFF, 0xFF}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // GMP SetBytes - gmpInt := NewInt() - gmpInt.SetBytes(tt.bytes) - - // math/big SetBytes - bigInt := new(big.Int) - bigInt.SetBytes(tt.bytes) - - // Compare string representations - if gmpInt.String() != bigInt.String() { - t.Errorf("SetBytes mismatch for %x:\n GMP: %s\n big: %s", - tt.bytes, gmpInt.String(), bigInt.String()) - } - - // Test round trip - gmpBytes := gmpInt.Bytes() - bigBytes := bigInt.Bytes() - - // Handle empty/zero cases - // Note: both empty input and all-zeros should produce empty output - if len(bigBytes) == 0 { - if len(gmpBytes) != 0 { - t.Errorf("Expected empty bytes, got %x", gmpBytes) - } - return - } - - // Compare bytes (handling leading zeros) - if !bytesEqual(gmpBytes, bigBytes) { - t.Errorf("Bytes() mismatch:\n GMP: %x\n big: %x", - gmpBytes, bigBytes) - } - }) - } -} - -// TestModExpByteArrays tests modular exponentiation with byte arrays -func TestModExpByteArrays(t *testing.T) { - // Test case: RSA-like encryption with byte arrays - msgBytes := []byte("Hello!") - - // Convert to numbers - gmpMsg := NewInt() - gmpMsg.SetBytes(msgBytes) - - bigMsg := new(big.Int) - bigMsg.SetBytes(msgBytes) - - // Public exponent (common RSA value) - e := "65537" - - // Small modulus for testing - n := "12345678901234567890" - - // GMP calculation - gmpE := NewInt() - gmpE.SetString(e, 10) - - gmpN := NewInt() - gmpN.SetString(n, 10) - - gmpResult := NewInt() - gmpResult.ExpMod(gmpMsg, gmpE, gmpN) - - // math/big calculation - bigE := new(big.Int) - bigE.SetString(e, 10) - - bigN := new(big.Int) - bigN.SetString(n, 10) - - bigResult := new(big.Int) - bigResult.Exp(bigMsg, bigE, bigN) - - // Compare - if gmpResult.String() != bigResult.String() { - t.Errorf("Byte array ModExp mismatch:\n GMP: %s\n big: %s", - gmpResult.String(), bigResult.String()) - } - - // Verify we can convert back to bytes - resultBytes := gmpResult.Bytes() - if len(resultBytes) == 0 { - t.Error("Result bytes should not be empty") - } -} - -// Benchmark against math/big -func BenchmarkModExpGMP(b *testing.B) { - base := NewInt() - base.SetString("123456789012345678901234567890123456789012345678901234567890", 10) - - exp := NewInt() - exp.SetString("987654321098765432109876543210987654321098765432109876543210", 10) - - mod := NewInt() - mod.SetString("111111111111111111111111111111111111111111111111111111111111", 10) - - result := NewInt() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - result.ExpMod(base, exp, mod) - } -} - -func BenchmarkModExpBigInt(b *testing.B) { - base := new(big.Int) - base.SetString("123456789012345678901234567890123456789012345678901234567890", 10) - - exp := new(big.Int) - exp.SetString("987654321098765432109876543210987654321098765432109876543210", 10) - - mod := new(big.Int) - mod.SetString("111111111111111111111111111111111111111111111111111111111111", 10) - - result := new(big.Int) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - result.Exp(base, exp, mod) - } -} - -// Helper function -func bytesEqual(a, b []byte) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if a[i] != b[i] { - return false - } - } - return true -} \ No newline at end of file