mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 14:16:44 +00:00
remove gmp_generic variant too -- reduce number of cgo calls
This commit is contained in:
parent
7fc1617ac1
commit
e33fcb3811
5 changed files with 16 additions and 468 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
package gmp
|
||||
|
||||
// Uses system-installed GMP library
|
||||
|
||||
// #cgo LDFLAGS: -lgmp
|
||||
// #include <gmp.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue