diff --git a/crypto/modexp/bigint/bigint.go b/crypto/modexp/bigint/bigint.go
new file mode 100644
index 0000000000..2d1a7a2eea
--- /dev/null
+++ b/crypto/modexp/bigint/bigint.go
@@ -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 .
+
+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
+}
diff --git a/crypto/modexp/bigint/bigint_test.go b/crypto/modexp/bigint/bigint_test.go
new file mode 100644
index 0000000000..23c52d81d3
--- /dev/null
+++ b/crypto/modexp/bigint/bigint_test.go
@@ -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 .
+
+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())
+ }
+ })
+ }
+}
diff --git a/crypto/modexp/gmp/README.md b/crypto/modexp/gmp/README.md
new file mode 100644
index 0000000000..581fa6e885
--- /dev/null
+++ b/crypto/modexp/gmp/README.md
@@ -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.
\ No newline at end of file
diff --git a/crypto/modexp/gmp/gmp.go b/crypto/modexp/gmp/gmp.go
new file mode 100644
index 0000000000..70e8e5504d
--- /dev/null
+++ b/crypto/modexp/gmp/gmp.go
@@ -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 .
+
+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")
+ }
+}
diff --git a/crypto/modexp/gmp/gmp_test.go b/crypto/modexp/gmp/gmp_test.go
new file mode 100644
index 0000000000..5ac253dfb1
--- /dev/null
+++ b/crypto/modexp/gmp/gmp_test.go
@@ -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 .
+
+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()
+ }
+}
diff --git a/crypto/modexp/gmp/modexp.c b/crypto/modexp/gmp/modexp.c
new file mode 100644
index 0000000000..dd5457aa67
--- /dev/null
+++ b/crypto/modexp/gmp/modexp.c
@@ -0,0 +1,55 @@
+#include
+#include
+#include
+#include
+
+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;
+}
\ No newline at end of file
diff --git a/crypto/modexp/gmp/modexp.h b/crypto/modexp/gmp/modexp.h
new file mode 100644
index 0000000000..7df8fcf614
--- /dev/null
+++ b/crypto/modexp/gmp/modexp.h
@@ -0,0 +1,15 @@
+#ifndef MODEXP_H
+#define MODEXP_H
+
+#include
+#include
+
+// 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
\ No newline at end of file
diff --git a/crypto/modexp/modexp.go b/crypto/modexp/modexp.go
new file mode 100644
index 0000000000..7aed2b30c8
--- /dev/null
+++ b/crypto/modexp/modexp.go
@@ -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 .
+
+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)
+}
\ No newline at end of file