mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
add modexp interface
This commit is contained in:
parent
e71487b033
commit
68181011ee
13 changed files with 1743 additions and 0 deletions
241
crypto/modexp/benchmark_comprehensive_test.go
Normal file
241
crypto/modexp/benchmark_comprehensive_test.go
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
package modexp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
gmppool "github.com/ethereum/go-ethereum/crypto/modexp/gmp/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateWorstCase generates a byte array with all bits set to 1
|
||||||
|
func generateWorstCase(size int) []byte {
|
||||||
|
result := make([]byte, size)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = 0xFF
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkComprehensive tests with specific bit sizes and worst-case scenarios
|
||||||
|
func BenchmarkComprehensive(b *testing.B) {
|
||||||
|
// Test cases with specific bit sizes
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
bits int
|
||||||
|
bytes int
|
||||||
|
}{
|
||||||
|
{"1bit", 1, 1}, // 1 bit
|
||||||
|
{"8bit", 8, 1}, // 1 byte
|
||||||
|
{"16bit", 16, 2}, // 2 bytes
|
||||||
|
{"32bit", 32, 4}, // 4 bytes
|
||||||
|
{"64bit", 64, 8}, // 8 bytes
|
||||||
|
{"128bit", 128, 16}, // 16 bytes
|
||||||
|
{"256bit", 256, 32}, // 32 bytes
|
||||||
|
{"512bit", 512, 64}, // 64 bytes
|
||||||
|
{"1024bit", 1024, 128}, // 128 bytes
|
||||||
|
{"2048bit", 2048, 256}, // 256 bytes
|
||||||
|
{"4096bit", 4096, 512}, // 512 bytes
|
||||||
|
{"8192bit", 8192, 1024}, // 1024 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
implementations := []struct {
|
||||||
|
name string
|
||||||
|
fn func([]byte, []byte, []byte) ([]byte, error)
|
||||||
|
}{
|
||||||
|
{"BigInt", bigint.ModExp},
|
||||||
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
|
{"GMPPooled", gmppool.ModExp},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
// Generate worst-case inputs (all bits set)
|
||||||
|
base := generateWorstCase(tc.bytes)
|
||||||
|
exp := generateWorstCase(tc.bytes)
|
||||||
|
mod := generateWorstCase(tc.bytes)
|
||||||
|
|
||||||
|
// For 1-bit case, use specific values
|
||||||
|
if tc.bits == 1 {
|
||||||
|
base = []byte{0x01}
|
||||||
|
exp = []byte{0x01}
|
||||||
|
mod = []byte{0x03} // Must be > 1 for valid modulo
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, impl := range implementations {
|
||||||
|
b.Run(tc.name+"/"+impl.name, func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = impl.fn(base, exp, mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWorstCaseOnly focuses on worst-case scenarios with maximum values
|
||||||
|
func BenchmarkWorstCaseOnly(b *testing.B) {
|
||||||
|
sizes := []struct {
|
||||||
|
name string
|
||||||
|
bytes int
|
||||||
|
}{
|
||||||
|
{"8bit", 1},
|
||||||
|
{"16bit", 2},
|
||||||
|
{"32bit", 4},
|
||||||
|
{"64bit", 8},
|
||||||
|
{"128bit", 16},
|
||||||
|
{"256bit", 32},
|
||||||
|
{"512bit", 64},
|
||||||
|
{"1024bit", 128},
|
||||||
|
{"2048bit", 256},
|
||||||
|
{"4096bit", 512},
|
||||||
|
{"8192bit", 1024},
|
||||||
|
}
|
||||||
|
|
||||||
|
implementations := []struct {
|
||||||
|
name string
|
||||||
|
fn func([]byte, []byte, []byte) ([]byte, error)
|
||||||
|
}{
|
||||||
|
{"BigInt", bigint.ModExp},
|
||||||
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
|
{"GMPPooled", gmppool.ModExp},
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run("AllFF", func(b *testing.B) {
|
||||||
|
for _, size := range sizes {
|
||||||
|
base := generateWorstCase(size.bytes)
|
||||||
|
exp := generateWorstCase(size.bytes)
|
||||||
|
mod := generateWorstCase(size.bytes)
|
||||||
|
|
||||||
|
for _, impl := range implementations {
|
||||||
|
b.Run(size.name+"/"+impl.name, func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = impl.fn(base, exp, mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkMemoryProfile shows memory usage for each size
|
||||||
|
func BenchmarkMemoryProfile(b *testing.B) {
|
||||||
|
sizes := []struct {
|
||||||
|
name string
|
||||||
|
bytes int
|
||||||
|
}{
|
||||||
|
{"1B", 1},
|
||||||
|
{"2B", 2},
|
||||||
|
{"4B", 4},
|
||||||
|
{"8B", 8},
|
||||||
|
{"16B", 16},
|
||||||
|
{"32B", 32},
|
||||||
|
{"64B", 64},
|
||||||
|
{"128B", 128},
|
||||||
|
{"256B", 256},
|
||||||
|
{"512B", 512},
|
||||||
|
{"1024B", 1024},
|
||||||
|
}
|
||||||
|
|
||||||
|
implementations := []struct {
|
||||||
|
name string
|
||||||
|
fn func([]byte, []byte, []byte) ([]byte, error)
|
||||||
|
}{
|
||||||
|
{"BigInt", bigint.ModExp},
|
||||||
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
|
{"GMPPooled", gmppool.ModExp},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, size := range sizes {
|
||||||
|
base := generateWorstCase(size.bytes)
|
||||||
|
exp := generateWorstCase(size.bytes)
|
||||||
|
mod := generateWorstCase(size.bytes)
|
||||||
|
|
||||||
|
for _, impl := range implementations {
|
||||||
|
b.Run(size.name+"/"+impl.name, func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result, _ := impl.fn(base, exp, mod)
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkComparison provides a direct comparison table
|
||||||
|
func BenchmarkComparison(b *testing.B) {
|
||||||
|
// Prepare test data
|
||||||
|
testData := make(map[string]struct{ base, exp, mod []byte })
|
||||||
|
|
||||||
|
sizes := []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}
|
||||||
|
for _, size := range sizes {
|
||||||
|
name := ""
|
||||||
|
switch {
|
||||||
|
case size == 1:
|
||||||
|
name = "8bit"
|
||||||
|
case size == 2:
|
||||||
|
name = "16bit"
|
||||||
|
case size == 4:
|
||||||
|
name = "32bit"
|
||||||
|
case size == 8:
|
||||||
|
name = "64bit"
|
||||||
|
case size == 16:
|
||||||
|
name = "128bit"
|
||||||
|
case size == 32:
|
||||||
|
name = "256bit"
|
||||||
|
case size == 64:
|
||||||
|
name = "512bit"
|
||||||
|
case size == 128:
|
||||||
|
name = "1024bit"
|
||||||
|
case size == 256:
|
||||||
|
name = "2048bit"
|
||||||
|
case size == 512:
|
||||||
|
name = "4096bit"
|
||||||
|
case size == 1024:
|
||||||
|
name = "8192bit"
|
||||||
|
}
|
||||||
|
|
||||||
|
testData[name] = struct{ base, exp, mod []byte }{
|
||||||
|
base: generateWorstCase(size),
|
||||||
|
exp: generateWorstCase(size),
|
||||||
|
mod: generateWorstCase(size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run benchmarks in a structured way
|
||||||
|
for _, sizeName := range []string{"8bit", "16bit", "32bit", "64bit", "128bit", "256bit", "512bit", "1024bit", "2048bit", "4096bit", "8192bit"} {
|
||||||
|
data := testData[sizeName]
|
||||||
|
|
||||||
|
b.Run(sizeName, func(b *testing.B) {
|
||||||
|
b.Run("BigInt", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = bigint.ModExp(data.base, data.exp, data.mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("GMPPooled", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = gmppool.ModExp(data.base, data.exp, data.mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
133
crypto/modexp/bigint/benchmark_test.go
Normal file
133
crypto/modexp/bigint/benchmark_test.go
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
package bigint_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/modexp/bigint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateBytes generates random bytes of given length
|
||||||
|
func generateBytes(b *testing.B, length int) []byte {
|
||||||
|
bytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// Ensure odd modulus for better performance
|
||||||
|
if length > 0 {
|
||||||
|
bytes[length-1] |= 0x01
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkModExp runs benchmarks for different input sizes
|
||||||
|
func BenchmarkModExp(b *testing.B) {
|
||||||
|
sizes := []struct {
|
||||||
|
name string
|
||||||
|
size int
|
||||||
|
}{
|
||||||
|
{"32", 32},
|
||||||
|
{"64", 64},
|
||||||
|
{"128", 128},
|
||||||
|
{"256", 256},
|
||||||
|
{"512", 512},
|
||||||
|
{"1024", 1024},
|
||||||
|
{"2048", 2048},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, size := range sizes {
|
||||||
|
b.Run(size.name, func(b *testing.B) {
|
||||||
|
base := generateBytes(b, size.size)
|
||||||
|
exp := generateBytes(b, size.size)
|
||||||
|
mod := generateBytes(b, size.size)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = bigint.ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkModExpParallel tests parallel performance
|
||||||
|
func BenchmarkModExpParallel(b *testing.B) {
|
||||||
|
sizes := []int{128, 256, 512}
|
||||||
|
|
||||||
|
for _, size := range sizes {
|
||||||
|
base := generateBytes(b, size)
|
||||||
|
exp := generateBytes(b, size)
|
||||||
|
mod := generateBytes(b, size)
|
||||||
|
|
||||||
|
b.Run(string(rune(size))+"bytes", func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, _ = bigint.ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkModExpEdgeCases tests special cases
|
||||||
|
func BenchmarkModExpEdgeCases(b *testing.B) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
base []byte
|
||||||
|
exp []byte
|
||||||
|
mod []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "base_is_one",
|
||||||
|
base: []byte{0x01},
|
||||||
|
exp: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
mod: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_exponent",
|
||||||
|
base: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
exp: []byte{0x00},
|
||||||
|
mod: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "small_exponent_rsa",
|
||||||
|
base: generateBytes(b, 256),
|
||||||
|
exp: []byte{0x01, 0x00, 0x01}, // 65537
|
||||||
|
mod: generateBytes(b, 256),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_base",
|
||||||
|
base: []byte{},
|
||||||
|
exp: []byte{0xFF},
|
||||||
|
mod: []byte{0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = bigint.ModExp(tc.base, tc.exp, tc.mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkModExpAllocs measures allocations
|
||||||
|
func BenchmarkModExpAllocs(b *testing.B) {
|
||||||
|
sizes := []int{32, 128, 256, 1024}
|
||||||
|
|
||||||
|
for _, size := range sizes {
|
||||||
|
base := generateBytes(b, size)
|
||||||
|
exp := generateBytes(b, size)
|
||||||
|
mod := generateBytes(b, size)
|
||||||
|
|
||||||
|
b.Run(string(rune(size))+"bytes", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = bigint.ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
31
crypto/modexp/bigint/bigint.go
Normal file
31
crypto/modexp/bigint/bigint.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package bigint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModExp performs modular exponentiation on byte arrays using math/big
|
||||||
|
// result = base^exp mod mod
|
||||||
|
// This function matches the behavior of the EVM modexp precompile
|
||||||
|
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||||
|
// Create big.Int values
|
||||||
|
baseBig := new(big.Int).SetBytes(base)
|
||||||
|
expBig := new(big.Int).SetBytes(exp)
|
||||||
|
modBig := new(big.Int).SetBytes(mod)
|
||||||
|
|
||||||
|
var v []byte
|
||||||
|
switch {
|
||||||
|
case modBig.BitLen() == 0:
|
||||||
|
// Modulo 0 is undefined, return zero (matching EVM behavior)
|
||||||
|
return []byte{}, nil
|
||||||
|
case baseBig.BitLen() == 1: // a bit length of 1 means it's 1 (or -1).
|
||||||
|
// If base == 1, then we can just return base % mod (if mod >= 1, which it is)
|
||||||
|
v = baseBig.Mod(baseBig, modBig).Bytes()
|
||||||
|
default:
|
||||||
|
v = baseBig.Exp(baseBig, expBig, modBig).Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the result bytes without padding
|
||||||
|
// The caller is responsible for padding if needed
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
135
crypto/modexp/bigint/bigint_test.go
Normal file
135
crypto/modexp/bigint/bigint_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package bigint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestModExp(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base []byte
|
||||||
|
exp []byte
|
||||||
|
mod []byte
|
||||||
|
want []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple_2^10_mod_1000",
|
||||||
|
base: []byte{0x02}, // 2
|
||||||
|
exp: []byte{0x0A}, // 10
|
||||||
|
mod: []byte{0x03, 0xE8}, // 1000
|
||||||
|
want: []byte{0x18}, // 24 (1024 mod 1000)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_base",
|
||||||
|
base: []byte{0x00},
|
||||||
|
exp: []byte{0x05},
|
||||||
|
mod: []byte{0x07},
|
||||||
|
want: []byte{0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_exponent",
|
||||||
|
base: []byte{0x05},
|
||||||
|
exp: []byte{0x00},
|
||||||
|
mod: []byte{0x07},
|
||||||
|
want: []byte{0x01}, // Any number to the power of 0 is 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "large_numbers",
|
||||||
|
base: []byte{0xFF, 0xFF},
|
||||||
|
exp: []byte{0x02},
|
||||||
|
mod: []byte{0x01, 0x00, 0x00},
|
||||||
|
want: []byte{0xFE, 0x01}, // (65535^2) mod 65536 = 65025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_modulus",
|
||||||
|
base: []byte{0x02},
|
||||||
|
exp: []byte{0x03},
|
||||||
|
mod: []byte{},
|
||||||
|
want: []byte{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_modulus",
|
||||||
|
base: []byte{0x02},
|
||||||
|
exp: []byte{0x03},
|
||||||
|
mod: []byte{0x00, 0x00},
|
||||||
|
want: []byte{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_base_and_exp",
|
||||||
|
base: []byte{},
|
||||||
|
exp: []byte{},
|
||||||
|
mod: []byte{0x07},
|
||||||
|
want: []byte{0x01}, // 0^0 = 1 in Go's big.Int
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "base_equals_one",
|
||||||
|
base: []byte{0x01},
|
||||||
|
exp: []byte{0xFF, 0xFF, 0xFF, 0xFF}, // large exponent
|
||||||
|
mod: []byte{0x07},
|
||||||
|
want: []byte{0x01}, // 1^anything mod 7 = 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := ModExp(tt.base, tt.exp, tt.mod)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ModExp() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tt.wantErr && !bytes.Equal(got, tt.want) {
|
||||||
|
t.Errorf("ModExp() = %x, want %x", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TestLargeModExp tests with very large numbers
|
||||||
|
func TestLargeModExp(t *testing.T) {
|
||||||
|
// Test with 2048-bit numbers
|
||||||
|
base := make([]byte, 256)
|
||||||
|
exp := make([]byte, 256)
|
||||||
|
mod := make([]byte, 256)
|
||||||
|
|
||||||
|
// Fill with test data
|
||||||
|
for i := range base {
|
||||||
|
base[i] = byte(i)
|
||||||
|
exp[i] = byte(255 - i)
|
||||||
|
mod[i] = 0xFF
|
||||||
|
}
|
||||||
|
mod[0] = 0x7F // Make sure modulus is not too large
|
||||||
|
|
||||||
|
result, err := ModExp(base, exp, mod)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ModExp() with large numbers failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
t.Error("Expected non-empty result for large numbers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkModExp benchmarks the byte-oriented interface
|
||||||
|
func BenchmarkModExp(b *testing.B) {
|
||||||
|
base := make([]byte, 32)
|
||||||
|
exp := make([]byte, 32)
|
||||||
|
mod := make([]byte, 32)
|
||||||
|
|
||||||
|
for i := range base {
|
||||||
|
base[i] = byte(i * 17)
|
||||||
|
exp[i] = byte(i * 31)
|
||||||
|
mod[i] = byte(255 - i)
|
||||||
|
}
|
||||||
|
mod[31] |= 0x01 // Ensure odd modulus
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
81
crypto/modexp/gmp/README.md
Normal file
81
crypto/modexp/gmp/README.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# GMP Wrapper for go-ethereum
|
||||||
|
|
||||||
|
A CGO wrapper for GMP's modular exponentiation functionality.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You need to have GMP development libraries installed on your system:
|
||||||
|
|
||||||
|
- **Ubuntu/Debian**: `sudo apt-get install libgmp-dev`
|
||||||
|
- **Fedora/RHEL**: `sudo dnf install gmp-devel`
|
||||||
|
- **macOS**: `brew install gmp`
|
||||||
|
- **Windows**: See [GMP Windows builds](https://gmplib.org/)
|
||||||
|
|
||||||
|
> TODO: There is an alternative branch, where we compile from source however this is slightly messier (though it allows us to pin to a specific commit like we do for libsecp256k1)
|
||||||
|
|
||||||
|
## 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 APIs. This is because its unclear if a direct API is better or using a syncPool with a more flexible API is better.
|
||||||
|
|
||||||
|
### Byte Array Interface (Recommended)
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/ethereum/go-ethereum/crypto/modexp/gmp"
|
||||||
|
|
||||||
|
base := []byte{0x02} // 2
|
||||||
|
exp := []byte{0x0A} // 10
|
||||||
|
mod := []byte{0x03, 0xE8} // 1000
|
||||||
|
|
||||||
|
result, err := gmp.ModExp(base, exp, mod)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pooled Interface (High Performance)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a pool once
|
||||||
|
pool := gmp.NewIntPool()
|
||||||
|
|
||||||
|
// Use it for many operations
|
||||||
|
for i := 0; i < 1000000; i++ {
|
||||||
|
result := gmp.ExpModPooled(pool, base, exp, mod)
|
||||||
|
// Process result...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run tests from the go-ethereum root directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./crypto/modexp/gmp/...
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
TODO -- whatever go-ethereum does.
|
||||||
147
crypto/modexp/gmp/cwrapper/gmp.go
Normal file
147
crypto/modexp/gmp/cwrapper/gmp.go
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
package gmp
|
||||||
|
|
||||||
|
// #cgo LDFLAGS: -lgmp
|
||||||
|
// #include <gmp.h>
|
||||||
|
// #include <stdlib.h>
|
||||||
|
// #include <string.h>
|
||||||
|
// #include <stdint.h>
|
||||||
|
//
|
||||||
|
// static int modexp_bytes(
|
||||||
|
// const uint8_t* base, size_t base_len,
|
||||||
|
// const uint8_t* exp, size_t exp_len,
|
||||||
|
// const uint8_t* mod, size_t mod_len,
|
||||||
|
// uint8_t* result, size_t* result_len)
|
||||||
|
// {
|
||||||
|
// mpz_t base_mpz, exp_mpz, mod_mpz, result_mpz;
|
||||||
|
//
|
||||||
|
// // Check for NULL pointers
|
||||||
|
// if (!base || !exp || !mod || !result || !result_len) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Initialize GMP integers
|
||||||
|
// mpz_init(base_mpz);
|
||||||
|
// mpz_init(exp_mpz);
|
||||||
|
// mpz_init(mod_mpz);
|
||||||
|
// mpz_init(result_mpz);
|
||||||
|
//
|
||||||
|
// // Import big-endian byte arrays into GMP integers
|
||||||
|
// // Handle empty arrays specially - GMP treats NULL with size 0 as 0
|
||||||
|
// if (base_len > 0) {
|
||||||
|
// mpz_import(base_mpz, base_len, 1, 1, 0, 0, base);
|
||||||
|
// }
|
||||||
|
// if (exp_len > 0) {
|
||||||
|
// mpz_import(exp_mpz, exp_len, 1, 1, 0, 0, exp);
|
||||||
|
// }
|
||||||
|
// if (mod_len > 0) {
|
||||||
|
// mpz_import(mod_mpz, mod_len, 1, 1, 0, 0, mod);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Special case: modulus is zero - return empty result (EVM behavior)
|
||||||
|
// if (mpz_cmp_ui(mod_mpz, 0) == 0) {
|
||||||
|
// *result_len = 0;
|
||||||
|
// mpz_clear(base_mpz);
|
||||||
|
// mpz_clear(exp_mpz);
|
||||||
|
// mpz_clear(mod_mpz);
|
||||||
|
// mpz_clear(result_mpz);
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Special case: base has bit length 1 (base == 1)
|
||||||
|
// // Just return base % mod
|
||||||
|
// if (mpz_sizeinbase(base_mpz, 2) == 1) {
|
||||||
|
// mpz_mod(result_mpz, base_mpz, mod_mpz);
|
||||||
|
// } else {
|
||||||
|
// // Normal case: perform modular exponentiation
|
||||||
|
// mpz_powm(result_mpz, base_mpz, exp_mpz, mod_mpz);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Get size needed for result
|
||||||
|
// size_t needed = (mpz_sizeinbase(result_mpz, 2) + 7) / 8;
|
||||||
|
// if (needed == 0) needed = 1; // For zero result
|
||||||
|
//
|
||||||
|
// // Check if result buffer is large enough
|
||||||
|
// if (*result_len < needed) {
|
||||||
|
// mpz_clear(base_mpz);
|
||||||
|
// mpz_clear(exp_mpz);
|
||||||
|
// mpz_clear(mod_mpz);
|
||||||
|
// mpz_clear(result_mpz);
|
||||||
|
// return -2;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Export result to big-endian byte array
|
||||||
|
// size_t count;
|
||||||
|
// mpz_export(result, &count, 1, 1, 0, 0, result_mpz);
|
||||||
|
// *result_len = count;
|
||||||
|
//
|
||||||
|
// // Handle zero result specially
|
||||||
|
// if (count == 0) {
|
||||||
|
// result[0] = 0;
|
||||||
|
// *result_len = 1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Clean up
|
||||||
|
// mpz_clear(base_mpz);
|
||||||
|
// mpz_clear(exp_mpz);
|
||||||
|
// mpz_clear(mod_mpz);
|
||||||
|
// mpz_clear(result_mpz);
|
||||||
|
//
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// ModExp performs modular exponentiation using the C implementation directly
|
||||||
|
// This is a lower-level function that bypasses the Go wrapper types
|
||||||
|
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||||
|
// Handle empty modulus - return empty result (EVM behavior)
|
||||||
|
if len(mod) == 0 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate result buffer (size of modulus is the max possible result)
|
||||||
|
result := make([]byte, len(mod))
|
||||||
|
resultLen := C.size_t(len(result))
|
||||||
|
|
||||||
|
// Handle empty slices - pass a dummy non-nil pointer with length 0
|
||||||
|
dummy := C.uint8_t(0)
|
||||||
|
var basePtr, expPtr, modPtr *C.uint8_t = &dummy, &dummy, &dummy
|
||||||
|
|
||||||
|
if len(base) > 0 {
|
||||||
|
basePtr = (*C.uint8_t)(unsafe.Pointer(&base[0]))
|
||||||
|
}
|
||||||
|
if len(exp) > 0 {
|
||||||
|
expPtr = (*C.uint8_t)(unsafe.Pointer(&exp[0]))
|
||||||
|
}
|
||||||
|
if len(mod) > 0 {
|
||||||
|
modPtr = (*C.uint8_t)(unsafe.Pointer(&mod[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call C function
|
||||||
|
ret := C.modexp_bytes(
|
||||||
|
basePtr, C.size_t(len(base)),
|
||||||
|
expPtr, C.size_t(len(exp)),
|
||||||
|
modPtr, C.size_t(len(mod)),
|
||||||
|
(*C.uint8_t)(unsafe.Pointer(&result[0])), &resultLen,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
switch ret {
|
||||||
|
case 0:
|
||||||
|
// Success - trim result to actual size
|
||||||
|
if resultLen == 0 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
return result[:resultLen], nil
|
||||||
|
case -1:
|
||||||
|
return nil, errors.New("invalid parameter")
|
||||||
|
case -2:
|
||||||
|
return nil, errors.New("result buffer too small")
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown error")
|
||||||
|
}
|
||||||
|
}
|
||||||
128
crypto/modexp/gmp/cwrapper/gmp_test.go
Normal file
128
crypto/modexp/gmp/cwrapper/gmp_test.go
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package gmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWrapperVsExisting compares the wrapper with existing GMP bindings
|
||||||
|
func TestWrapperVsExisting(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: "123456789",
|
||||||
|
exp: "987654321",
|
||||||
|
mod: "1000000007",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "large_numbers",
|
||||||
|
base: "123456789012345678901234567890",
|
||||||
|
exp: "987654321098765432109876543210",
|
||||||
|
mod: "111111111111111111111111111111",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_exponent",
|
||||||
|
base: "12345",
|
||||||
|
exp: "0",
|
||||||
|
mod: "67890",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Parse numbers
|
||||||
|
baseBig := new(big.Int)
|
||||||
|
baseBig.SetString(tt.base, 10)
|
||||||
|
expBig := new(big.Int)
|
||||||
|
expBig.SetString(tt.exp, 10)
|
||||||
|
modBig := new(big.Int)
|
||||||
|
modBig.SetString(tt.mod, 10)
|
||||||
|
|
||||||
|
// Test with wrapper
|
||||||
|
baseBytes := baseBig.Bytes()
|
||||||
|
expBytes := expBig.Bytes()
|
||||||
|
modBytes := modBig.Bytes()
|
||||||
|
|
||||||
|
wrapperResult, err := ModExp(baseBytes, expBytes, modBytes)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
|
||||||
|
existingResult := gmpResult.Bytes()
|
||||||
|
|
||||||
|
// Compare results
|
||||||
|
if !bytes.Equal(wrapperResult, existingResult) {
|
||||||
|
t.Errorf("Results differ:\nWrapper: %x\nExisting: %x",
|
||||||
|
wrapperResult, existingResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWrapperVsExisting compares performance
|
||||||
|
func BenchmarkModExp(b *testing.B) {
|
||||||
|
base := make([]byte, 60)
|
||||||
|
exp := make([]byte, 60)
|
||||||
|
mod := make([]byte, 60)
|
||||||
|
|
||||||
|
for i := range base {
|
||||||
|
base[i] = byte(i * 17)
|
||||||
|
exp[i] = byte(i * 31)
|
||||||
|
mod[i] = byte(255 - i)
|
||||||
|
}
|
||||||
|
mod[59] |= 0x01
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkModExpExisting(b *testing.B) {
|
||||||
|
baseBytes := make([]byte, 60)
|
||||||
|
expBytes := make([]byte, 60)
|
||||||
|
modBytes := make([]byte, 60)
|
||||||
|
|
||||||
|
for i := range baseBytes {
|
||||||
|
baseBytes[i] = byte(i * 17)
|
||||||
|
expBytes[i] = byte(i * 31)
|
||||||
|
modBytes[i] = byte(255 - i)
|
||||||
|
}
|
||||||
|
modBytes[59] |= 0x01
|
||||||
|
|
||||||
|
base := NewInt()
|
||||||
|
base.SetBytes(baseBytes)
|
||||||
|
exp := NewInt()
|
||||||
|
exp.SetBytes(expBytes)
|
||||||
|
mod := NewInt()
|
||||||
|
mod.SetBytes(modBytes)
|
||||||
|
result := NewInt()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result.ExpMod(base, exp, mod)
|
||||||
|
_ = result.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
170
crypto/modexp/gmp/generic/gmp.go
Normal file
170
crypto/modexp/gmp/generic/gmp.go
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the decimal representation of z
|
||||||
|
func (z *Int) String() string {
|
||||||
|
if z == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get string from GMP
|
||||||
|
cs := C.mpz_get_str(nil, 10, &z.mpz[0])
|
||||||
|
defer C.free(unsafe.Pointer(cs))
|
||||||
|
|
||||||
|
return C.GoString(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
257
crypto/modexp/gmp/generic/gmp_test.go
Normal file
257
crypto/modexp/gmp/generic/gmp_test.go
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
96
crypto/modexp/gmp/pool/gmp.go
Normal file
96
crypto/modexp/gmp/pool/gmp.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package gmp
|
||||||
|
|
||||||
|
// #include <gmp.h>
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
generic "github.com/ethereum/go-ethereum/crypto/modexp/gmp/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntPool provides a pool of reusable GMP Int objects to reduce allocations
|
||||||
|
type IntPool struct {
|
||||||
|
pool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIntPool creates a new pool for GMP Int objects
|
||||||
|
func NewIntPool() *IntPool {
|
||||||
|
return &IntPool{
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return generic.NewInt()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves an Int from the pool
|
||||||
|
func (p *IntPool) Get() *generic.Int {
|
||||||
|
return p.pool.Get().(*generic.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put returns an Int to the pool after clearing it
|
||||||
|
func (p *IntPool) Put(i *generic.Int) {
|
||||||
|
// Clear the Int to avoid keeping large numbers in memory
|
||||||
|
i.SetUint64(0)
|
||||||
|
p.pool.Put(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpModPooled performs modular exponentiation using pooled Int objects
|
||||||
|
// This is useful for high-throughput scenarios where you want to minimize allocations
|
||||||
|
// This function matches the behavior of the EVM modexp precompile
|
||||||
|
func ExpModPooled(pool *IntPool, base, exp, mod []byte) []byte {
|
||||||
|
// Handle empty modulus - return empty result (EVM behavior)
|
||||||
|
if len(mod) == 0 {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Ints from pool
|
||||||
|
baseInt := pool.Get()
|
||||||
|
expInt := pool.Get()
|
||||||
|
modInt := pool.Get()
|
||||||
|
resultInt := pool.Get()
|
||||||
|
|
||||||
|
// Ensure we return Ints to pool when done
|
||||||
|
defer func() {
|
||||||
|
pool.Put(baseInt)
|
||||||
|
pool.Put(expInt)
|
||||||
|
pool.Put(modInt)
|
||||||
|
pool.Put(resultInt)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set values
|
||||||
|
baseInt.SetBytes(base)
|
||||||
|
expInt.SetBytes(exp)
|
||||||
|
modInt.SetBytes(mod)
|
||||||
|
|
||||||
|
// Check for zero modulus
|
||||||
|
if modInt.BitLen() == 0 {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (this allocates, but much less than creating new Ints)
|
||||||
|
return resultInt.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModExp performs modular exponentiation using a new pool for each operation
|
||||||
|
// For better performance, create a pool once and reuse it with ExpModPooled
|
||||||
|
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
result := ExpModPooled(pool, base, exp, mod)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreallocatedExpMod performs modular exponentiation with pre-allocated Int objects
|
||||||
|
// This gives the caller full control over object lifecycle
|
||||||
|
func PreallocatedExpMod(result, base, exp, mod *generic.Int) {
|
||||||
|
result.ExpMod(base, exp, mod)
|
||||||
|
}
|
||||||
236
crypto/modexp/gmp/pool/gmp_test.go
Normal file
236
crypto/modexp/gmp/pool/gmp_test.go
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
package gmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestIntPool tests basic pool functionality
|
||||||
|
func TestIntPool(t *testing.T) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
|
||||||
|
// Get an Int from pool
|
||||||
|
i1 := pool.Get()
|
||||||
|
i1.SetString("12345", 10)
|
||||||
|
|
||||||
|
// Return it to pool
|
||||||
|
pool.Put(i1)
|
||||||
|
|
||||||
|
// Get another Int (should be the same one, cleared)
|
||||||
|
i2 := pool.Get()
|
||||||
|
if i2.String() != "0" {
|
||||||
|
t.Errorf("Expected cleared Int, got %s", i2.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it's the same object
|
||||||
|
i2.SetString("67890", 10)
|
||||||
|
pool.Put(i2)
|
||||||
|
|
||||||
|
i3 := pool.Get()
|
||||||
|
// Should be cleared again
|
||||||
|
if i3.String() != "0" {
|
||||||
|
t.Errorf("Expected cleared Int after second put, got %s", i3.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExpModPooled tests pooled modular exponentiation
|
||||||
|
func TestExpModPooled(t *testing.T) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
|
||||||
|
base := []byte{0x02} // 2
|
||||||
|
exp := []byte{0x0A} // 10
|
||||||
|
mod := []byte{0x03, 0xE8} // 1000
|
||||||
|
|
||||||
|
result := ExpModPooled(pool, base, exp, mod)
|
||||||
|
|
||||||
|
// 2^10 mod 1000 = 1024 mod 1000 = 24
|
||||||
|
expected := []byte{0x18} // 24
|
||||||
|
if !bytesEqual(result, expected) {
|
||||||
|
t.Errorf("ExpModPooled: expected %x, got %x", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestModExp tests the convenience function
|
||||||
|
func TestModExp(t *testing.T) {
|
||||||
|
base := []byte{0x02}
|
||||||
|
exp := []byte{0x0A}
|
||||||
|
mod := []byte{0x03, 0xE8}
|
||||||
|
|
||||||
|
result, err := ModExp(base, exp, mod)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ModExp failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []byte{0x18}
|
||||||
|
if !bytesEqual(result, expected) {
|
||||||
|
t.Errorf("ModExp: expected %x, got %x", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test empty modulus case
|
||||||
|
result, err = ModExp(base, exp, []byte{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error for empty modulus, got %v", err)
|
||||||
|
}
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("Expected empty result for empty modulus, got %x", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TestPreallocatedExpMod tests pre-allocated operations
|
||||||
|
func TestPreallocatedExpMod(t *testing.T) {
|
||||||
|
base := NewInt()
|
||||||
|
exp := NewInt()
|
||||||
|
mod := NewInt()
|
||||||
|
result := NewInt()
|
||||||
|
|
||||||
|
base.SetString("2", 10)
|
||||||
|
exp.SetString("10", 10)
|
||||||
|
mod.SetString("1000", 10)
|
||||||
|
|
||||||
|
PreallocatedExpMod(result, base, exp, mod)
|
||||||
|
|
||||||
|
if result.String() != "24" {
|
||||||
|
t.Errorf("PreallocatedExpMod: expected 24, got %s", result.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkExpModPooled compares pooled vs non-pooled performance
|
||||||
|
func BenchmarkExpModPooled(b *testing.B) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
base := []byte("123456789012345678901234567890")
|
||||||
|
exp := []byte("987654321098765432109876543210")
|
||||||
|
mod := []byte("111111111111111111111111111111")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = ExpModPooled(pool, base, exp, mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkExpModNonPooled benchmarks non-pooled operations for comparison
|
||||||
|
func BenchmarkExpModNonPooled(b *testing.B) {
|
||||||
|
base := []byte("123456789012345678901234567890")
|
||||||
|
exp := []byte("987654321098765432109876543210")
|
||||||
|
mod := []byte("111111111111111111111111111111")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
baseInt := NewInt()
|
||||||
|
expInt := NewInt()
|
||||||
|
modInt := NewInt()
|
||||||
|
resultInt := NewInt()
|
||||||
|
|
||||||
|
baseInt.SetBytes(base)
|
||||||
|
expInt.SetBytes(exp)
|
||||||
|
modInt.SetBytes(mod)
|
||||||
|
resultInt.ExpMod(baseInt, expInt, modInt)
|
||||||
|
_ = resultInt.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TestPoolConcurrency tests that the pool is safe for concurrent use
|
||||||
|
func TestPoolConcurrency(t *testing.T) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Run many concurrent operations
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(n int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Get and use an Int
|
||||||
|
num := pool.Get()
|
||||||
|
num.SetString("12345", 10)
|
||||||
|
|
||||||
|
// Simulate some work
|
||||||
|
result := NewInt()
|
||||||
|
result.ExpMod(num, num, num)
|
||||||
|
|
||||||
|
// Return to pool
|
||||||
|
pool.Put(num)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPoolWithBigNumbers tests pool with large numbers
|
||||||
|
func TestPoolWithBigNumbers(t *testing.T) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
|
||||||
|
// Test with 2048-bit numbers
|
||||||
|
base := make([]byte, 256)
|
||||||
|
exp := make([]byte, 256)
|
||||||
|
mod := make([]byte, 256)
|
||||||
|
|
||||||
|
// Fill with test data
|
||||||
|
for i := range base {
|
||||||
|
base[i] = byte(i)
|
||||||
|
exp[i] = byte(255 - i)
|
||||||
|
mod[i] = 0xFF
|
||||||
|
}
|
||||||
|
mod[0] = 0x7F // Make sure modulus is not too large
|
||||||
|
|
||||||
|
// This should not panic or leak memory
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
result := ExpModPooled(pool, base, exp, mod)
|
||||||
|
if len(result) == 0 {
|
||||||
|
t.Error("Expected non-empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPoolComparison verifies pooled operations match non-pooled
|
||||||
|
func TestPoolComparison(t *testing.T) {
|
||||||
|
pool := NewIntPool()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
base string
|
||||||
|
exp string
|
||||||
|
mod string
|
||||||
|
}{
|
||||||
|
{"2", "10", "1000"},
|
||||||
|
{"123456789", "987654321", "1000000007"},
|
||||||
|
{"999999999999", "888888888888", "777777777777"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
// Non-pooled
|
||||||
|
base1 := NewInt()
|
||||||
|
exp1 := NewInt()
|
||||||
|
mod1 := NewInt()
|
||||||
|
result1 := NewInt()
|
||||||
|
|
||||||
|
base1.SetString(tc.base, 10)
|
||||||
|
exp1.SetString(tc.exp, 10)
|
||||||
|
mod1.SetString(tc.mod, 10)
|
||||||
|
result1.ExpMod(base1, exp1, mod1)
|
||||||
|
|
||||||
|
// Pooled
|
||||||
|
result2 := ExpModPooled(pool, base1.Bytes(), exp1.Bytes(), mod1.Bytes())
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
if !bytesEqual(result1.Bytes(), result2) {
|
||||||
|
t.Errorf("Pooled vs non-pooled mismatch for %s^%s mod %s", tc.base, tc.exp, tc.mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also compare with math/big
|
||||||
|
bigBase := new(big.Int)
|
||||||
|
bigExp := new(big.Int)
|
||||||
|
bigMod := new(big.Int)
|
||||||
|
bigResult := new(big.Int)
|
||||||
|
|
||||||
|
bigBase.SetString(tc.base, 10)
|
||||||
|
bigExp.SetString(tc.exp, 10)
|
||||||
|
bigMod.SetString(tc.mod, 10)
|
||||||
|
bigResult.Exp(bigBase, bigExp, bigMod)
|
||||||
|
|
||||||
|
if !bytesEqual(bigResult.Bytes(), result2) {
|
||||||
|
t.Errorf("Pooled vs math/big mismatch for %s^%s mod %s", tc.base, tc.exp, tc.mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crypto/modexp/modexp.go
Normal file
13
crypto/modexp/modexp.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package modexp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/modexp/bigint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModExp performs modular exponentiation on byte arrays
|
||||||
|
// result = base^exp mod mod
|
||||||
|
// This uses the bigint implementation by default.
|
||||||
|
// To use GMP implementation, import crypto/modexp/gmp directly.
|
||||||
|
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||||
|
return bigint.ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
75
crypto/modexp/modexp_test.go
Normal file
75
crypto/modexp/modexp_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package modexp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestModExp(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base []byte
|
||||||
|
exp []byte
|
||||||
|
mod []byte
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
base: []byte{0x02}, // 2
|
||||||
|
exp: []byte{0x0A}, // 10
|
||||||
|
mod: []byte{0x03, 0xE8}, // 1000
|
||||||
|
want: []byte{0x18}, // 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero_modulus",
|
||||||
|
base: []byte{0x02},
|
||||||
|
exp: []byte{0x03},
|
||||||
|
mod: []byte{},
|
||||||
|
want: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "base_equals_one",
|
||||||
|
base: []byte{0x01},
|
||||||
|
exp: []byte{0xFF, 0xFF},
|
||||||
|
mod: []byte{0x07},
|
||||||
|
want: []byte{0x01},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "large_numbers",
|
||||||
|
base: []byte{0xFF, 0xFF},
|
||||||
|
exp: []byte{0x02},
|
||||||
|
mod: []byte{0x01, 0x00, 0x00},
|
||||||
|
want: []byte{0xFE, 0x01},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := ModExp(tt.base, tt.exp, tt.mod)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ModExp error: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, tt.want) {
|
||||||
|
t.Errorf("ModExp() = %x, want %x", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkModExp(b *testing.B) {
|
||||||
|
base := make([]byte, 32)
|
||||||
|
exp := make([]byte, 32)
|
||||||
|
mod := make([]byte, 32)
|
||||||
|
|
||||||
|
for i := range base {
|
||||||
|
base[i] = byte(i * 17)
|
||||||
|
exp[i] = byte(i * 31)
|
||||||
|
mod[i] = byte(255 - i)
|
||||||
|
}
|
||||||
|
mod[31] |= 0x01 // Ensure odd modulus
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = ModExp(base, exp, mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue