mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
Add new modexp package
This commit is contained in:
parent
d72ec587a7
commit
e131482fe2
8 changed files with 507 additions and 0 deletions
45
crypto/modexp/bigint/bigint.go
Normal file
45
crypto/modexp/bigint/bigint.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bigint
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// ModExp performs modular exponentiation using Go's big.Int
|
||||
// result = base^exp mod mod
|
||||
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||
baseBig := new(big.Int).SetBytes(base)
|
||||
expBig := new(big.Int).SetBytes(exp)
|
||||
modBig := new(big.Int).SetBytes(mod)
|
||||
|
||||
// Handle special cases
|
||||
if modBig.BitLen() == 0 {
|
||||
// Modulo 0 is undefined, return empty bytes
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
if baseBig.BitLen() == 1 {
|
||||
// If base == 1, then we can just return base % mod
|
||||
result := baseBig.Mod(baseBig, modBig)
|
||||
return result.Bytes(), nil
|
||||
}
|
||||
|
||||
// Perform modular exponentiation
|
||||
result := new(big.Int).Exp(baseBig, expBig, modBig)
|
||||
return result.Bytes(), nil
|
||||
}
|
||||
86
crypto/modexp/bigint/bigint_test.go
Normal file
86
crypto/modexp/bigint/bigint_test.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bigint
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModExp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
base string
|
||||
exp string
|
||||
mod string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
base: "2",
|
||||
exp: "10",
|
||||
mod: "1000",
|
||||
want: "24",
|
||||
},
|
||||
{
|
||||
name: "zero_exponent",
|
||||
base: "12345",
|
||||
exp: "0",
|
||||
mod: "67890",
|
||||
want: "1",
|
||||
},
|
||||
{
|
||||
name: "base_one",
|
||||
base: "1",
|
||||
exp: "999999",
|
||||
mod: "1000",
|
||||
want: "1",
|
||||
},
|
||||
{
|
||||
name: "zero_modulus",
|
||||
base: "2",
|
||||
exp: "10",
|
||||
mod: "0",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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)
|
||||
wantBig := new(big.Int)
|
||||
if tt.want != "" {
|
||||
wantBig.SetString(tt.want, 10)
|
||||
}
|
||||
|
||||
result, err := ModExp(baseBig.Bytes(), expBig.Bytes(), modBig.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("ModExp error: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(result, wantBig.Bytes()) {
|
||||
t.Errorf("ModExp result mismatch: got %x, want %x", result, wantBig.Bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
46
crypto/modexp/gmp/README.md
Normal file
46
crypto/modexp/gmp/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# 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 using a direct C call to avoid multiple calls to cgo. The main usage is for the modexp precompile.
|
||||
|
||||
### Byte Array Interface
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests from the go-ethereum root directory:
|
||||
|
||||
```bash
|
||||
go test ./crypto/modexp/gmp/...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
TODO -- whatever go-ethereum does.
|
||||
96
crypto/modexp/gmp/gmp.go
Normal file
96
crypto/modexp/gmp/gmp.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gmp
|
||||
|
||||
// #cgo LDFLAGS: -lgmp
|
||||
// #include "modexp.h"
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ModExp performs modular exponentiation using GMP
|
||||
// This is thread safe.
|
||||
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||
// Handle empty modulus - return empty result (EVM behavior)
|
||||
if len(mod) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// Special case: zero modulus
|
||||
allZero := true
|
||||
for _, b := range mod {
|
||||
if b != 0 {
|
||||
allZero = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allZero {
|
||||
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
|
||||
// This avoids UB when the length is zero.
|
||||
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,
|
||||
)
|
||||
|
||||
// Keep the slices alive until after the C call completes
|
||||
runtime.KeepAlive(base)
|
||||
runtime.KeepAlive(exp)
|
||||
runtime.KeepAlive(mod)
|
||||
runtime.KeepAlive(result)
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
135
crypto/modexp/gmp/gmp_test.go
Normal file
135
crypto/modexp/gmp/gmp_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestWrapperVsBigInt compares the wrapper with Go's math/big
|
||||
func TestWrapperVsBigInt(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 math/big
|
||||
bigResult := new(big.Int)
|
||||
bigResult.Exp(baseBig, expBig, modBig)
|
||||
|
||||
expectedResult := bigResult.Bytes()
|
||||
|
||||
// Compare results
|
||||
if !bytes.Equal(wrapperResult, expectedResult) {
|
||||
t.Errorf("Results differ:\nWrapper: %x\nExpected: %x",
|
||||
wrapperResult, expectedResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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 BenchmarkModExpBigInt(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 := new(big.Int).SetBytes(baseBytes)
|
||||
exp := new(big.Int).SetBytes(expBytes)
|
||||
mod := new(big.Int).SetBytes(modBytes)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
result := new(big.Int)
|
||||
result.Exp(base, exp, mod)
|
||||
_ = result.Bytes()
|
||||
}
|
||||
}
|
||||
55
crypto/modexp/gmp/modexp.c
Normal file
55
crypto/modexp/gmp/modexp.c
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include <gmp.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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_inits(base_mpz, exp_mpz, mod_mpz, result_mpz, NULL);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Perform modular exponentiation
|
||||
mpz_powm(result_mpz, base_mpz, exp_mpz, mod_mpz);
|
||||
|
||||
// Get exact size needed for result
|
||||
size_t needed = 0;
|
||||
mpz_export(NULL, &needed, 1, 1, 0, 0, result_mpz);
|
||||
|
||||
// Check if result buffer is large enough
|
||||
if (*result_len < needed) {
|
||||
mpz_clears(base_mpz, exp_mpz, mod_mpz, result_mpz, NULL);
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Export result to big-endian byte array
|
||||
mpz_export(result, &needed, 1, 1, 0, 0, result_mpz);
|
||||
*result_len = needed;
|
||||
|
||||
// Clean up
|
||||
mpz_clears(base_mpz, exp_mpz, mod_mpz, result_mpz, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
15
crypto/modexp/gmp/modexp.h
Normal file
15
crypto/modexp/gmp/modexp.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef MODEXP_H
|
||||
#define MODEXP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Perform modular exponentiation: base^exp mod mod
|
||||
// Returns 0 on success, -1 on invalid input, -2 if result buffer too small
|
||||
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);
|
||||
|
||||
#endif // MODEXP_H
|
||||
29
crypto/modexp/modexp.go
Normal file
29
crypto/modexp/modexp.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package modexp
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/crypto/modexp/gmp"
|
||||
)
|
||||
|
||||
// ModExp performs modular exponentiation on byte arrays
|
||||
// result = base^exp mod mod
|
||||
// This uses the gmp implementation by default.
|
||||
// To use bigint implementation, import crypto/modexp/bigint directly.
|
||||
func ModExp(base, exp, mod []byte) ([]byte, error) {
|
||||
return gmp.ModExp(base, exp, mod)
|
||||
}
|
||||
Loading…
Reference in a new issue