From 1ae38be18ece8fcdd1ab326ee70894e256222918 Mon Sep 17 00:00:00 2001 From: georgehao Date: Thu, 16 Oct 2025 14:09:15 +0800 Subject: [PATCH 1/4] EIP:7823 Set upper bounds for MODEXP && EIP-7883: ModExp Gas Cost && EIP-7951: Precompile for secp256r1 Curve Support --- src/precompile/mod.rs | 10 +++ src/precompile/modexp.rs | 147 +++++++++++++++++++++++++++++++++++++++ src/spec.rs | 7 +- 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 1028d9b..091bb84 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -31,6 +31,7 @@ impl ScrollPrecompileProvider { ScrollSpecId::BERNOULLI | ScrollSpecId::CURIE | ScrollSpecId::DARWIN => bernoulli(), ScrollSpecId::EUCLID => euclid(), ScrollSpecId::FEYNMAN => feynman(), + ScrollSpecId::Galileo => galileo(), }; Self { precompile_provider: EthPrecompiles { precompiles, spec: SpecId::default() }, spec } } @@ -102,6 +103,15 @@ pub(crate) fn feynman() -> &'static Precompiles { }) } +pub(crate) fn galileo() -> &'static Precompiles { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = feynman().clone(); + precompiles.extend([modexp::GALILEO]); + Box::new(precompiles) + }) +} + impl PrecompileProvider for ScrollPrecompileProvider where CTX: ContextTr>, diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index 4954343..301e640 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -1,5 +1,6 @@ use revm::{ precompile::{ + modexp, modexp::{berlin_gas_calc, run_inner}, u64_to_address, utilities::right_pad_with_offset, @@ -17,6 +18,9 @@ pub const BERNOULLI_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); /// The MODEXP precompile with BERNOULLI length limit rule. pub const BERNOULLI: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, bernoulli_run); +/// The Galileo MODEXP precompile. +pub const GALILEO: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, galileo_run); + /// The bernoulli MODEXP precompile implementation. /// /// # Errors @@ -45,3 +49,146 @@ pub fn bernoulli_run(input: &[u8], gas_limit: u64) -> PrecompileResult { const OSAKA: bool = false; run_inner::<_, OSAKA>(input, gas_limit, 200, berlin_gas_calc) } + +/// The MODEXP precompile with Galileo (OSAKA) implementation. +/// +/// This version removes the 32-byte length limit that was present in BERNOULLI, +/// allowing modexp operations with larger inputs according to the OSAKA specification. +pub fn galileo_run(input: &[u8], gas_limit: u64) -> PrecompileResult { + modexp::osaka_run(input, gas_limit) +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::primitives::hex; + + #[test] + fn test_galileo_modexp_backward_compatibility() { + // Test case: verify that Galileo modexp doesn't affect behavior before FEYNMAN + // Using a standard modexp test case: base=3, exp=5, mod=7 + // Expected result: 3^5 mod 7 = 243 mod 7 = 5 + + // Input format: [base_len(32 bytes)][exp_len(32 bytes)][mod_len(32 bytes)][base][exp][mod] + // base_len = 1, exp_len = 1, mod_len = 1 + // base = 3, exp = 5, mod = 7 + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 03\ + 05\ + 07", + ) + .unwrap(); + + let gas_limit = 100000u64; + + // Test BERNOULLI behavior + let bernoulli_result = bernoulli_run(&input, gas_limit); + assert!(bernoulli_result.is_ok(), "BERNOULLI modexp should succeed"); + let bernoulli_output = bernoulli_result.unwrap(); + + // Test Galileo behavior + let galileo_result = galileo_run(&input, gas_limit); + assert!(galileo_result.is_ok(), "Galileo modexp should succeed"); + let galileo_output = galileo_result.unwrap(); + + // Verify both produce the same result + assert_eq!( + bernoulli_output.bytes, galileo_output.bytes, + "Galileo modexp should produce the same result as BERNOULLI for valid inputs" + ); + + // Verify that Galileo uses more gas (OSAKA gas rules vs Berlin gas rules) + assert!( + galileo_output.gas_used >= bernoulli_output.gas_used, + "Galileo should use at least as much gas as BERNOULLI (OSAKA gas rules)" + ); + + // Expected result: 3^5 mod 7 = 5 + let expected = vec![5u8]; + assert_eq!(bernoulli_output.bytes.as_ref(), &expected); + assert_eq!(galileo_output.bytes.as_ref(), &expected); + } + + #[test] + fn test_galileo_modexp_with_32_byte_limit() { + // Test that Galileo handles the 32-byte limit differently than BERNOULLI + // Input with base_len = 33 (exceeds BERNOULLI limit) + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000021\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 030303030303030303030303030303030303030303030303030303030303030303\ + 05\ + 07", + ) + .unwrap(); + + let gas_limit = 100000u64; + + // BERNOULLI should reject this (base length > 32) + let bernoulli_result = bernoulli_run(&input, gas_limit); + assert!(bernoulli_result.is_err(), "BERNOULLI should reject base_len > 32"); + assert!( + matches!(bernoulli_result.unwrap_err(), PrecompileError::Other(msg) if msg.contains("ModexpBaseOverflow")), + "BERNOULLI should return ModexpBaseOverflow error" + ); + + // Galileo should accept this (no 32-byte limit) + let galileo_result = galileo_run(&input, gas_limit); + assert!(galileo_result.is_ok(), "Galileo should accept base_len > 32"); + } + + #[test] + fn test_galileo_modexp_preserves_feynman_behavior() { + // Test with inputs that should work in all versions + // This ensures Galileo doesn't break existing functionality + let test_cases = vec![ + // Small values + ( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 02\ + 03\ + 05", + vec![3u8], // 2^3 mod 5 = 8 mod 5 = 3 + ), + // Zero exponent (should return 1) + ( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 05\ + 00\ + 0d", + vec![1u8], // 5^0 mod 13 = 1 + ), + ]; + + for (input_hex, expected) in test_cases { + let input = hex::decode(input_hex).unwrap(); + let gas_limit = 100000u64; + + let bernoulli_result = bernoulli_run(&input, gas_limit).unwrap(); + let galileo_result = galileo_run(&input, gas_limit).unwrap(); + + assert_eq!( + bernoulli_result.bytes.as_ref(), + &expected, + "BERNOULLI should produce expected result" + ); + assert_eq!( + galileo_result.bytes.as_ref(), + &expected, + "Galileo should produce expected result" + ); + assert_eq!( + bernoulli_result.bytes, galileo_result.bytes, + "Galileo and BERNOULLI should produce identical results for valid inputs" + ); + } + } +} diff --git a/src/spec.rs b/src/spec.rs index 47e341b..122b352 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -12,6 +12,7 @@ pub enum ScrollSpecId { #[default] EUCLID = 5, FEYNMAN = 6, + Galileo = 7, } impl ScrollSpecId { @@ -41,7 +42,8 @@ impl ScrollSpecId { Self::CURIE | Self::DARWIN | Self::EUCLID | - Self::FEYNMAN => SpecId::SHANGHAI, + Self::FEYNMAN | + Self::Galileo => SpecId::SHANGHAI, } } } @@ -62,6 +64,7 @@ pub mod name { pub const DARWIN: &str = "darwin"; pub const EUCLID: &str = "euclid"; pub const FEYNMAN: &str = "feynman"; + pub const GALILEO: &str = "galileo"; } impl From<&str> for ScrollSpecId { @@ -73,6 +76,7 @@ impl From<&str> for ScrollSpecId { name::DARWIN => Self::DARWIN, name::EUCLID => Self::EUCLID, name::FEYNMAN => Self::FEYNMAN, + name::GALILEO => Self::Galileo, _ => Self::default(), } } @@ -87,6 +91,7 @@ impl From for &'static str { ScrollSpecId::DARWIN => name::DARWIN, ScrollSpecId::EUCLID => name::EUCLID, ScrollSpecId::FEYNMAN => name::FEYNMAN, + ScrollSpecId::Galileo => name::GALILEO, } } } From 0757d46735bc2bc33ac60d05c39ec61ca1e04d69 Mon Sep 17 00:00:00 2001 From: georgehao Date: Thu, 16 Oct 2025 14:45:48 +0800 Subject: [PATCH 2/4] fix lint --- src/precompile/modexp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index 301e640..48bf8d5 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -62,6 +62,7 @@ pub fn galileo_run(input: &[u8], gas_limit: u64) -> PrecompileResult { mod tests { use super::*; use revm::primitives::hex; + use std::vec; #[test] fn test_galileo_modexp_backward_compatibility() { From ff3f4c40287cc4829343d7a6104d7ce1c24ade10 Mon Sep 17 00:00:00 2001 From: georgehao Date: Fri, 17 Oct 2025 11:11:40 +0800 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: greg <82421016+greged93@users.noreply.github.com> --- src/precompile/mod.rs | 2 +- src/precompile/modexp.rs | 2 +- src/spec.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 091bb84..6f6170b 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -31,7 +31,7 @@ impl ScrollPrecompileProvider { ScrollSpecId::BERNOULLI | ScrollSpecId::CURIE | ScrollSpecId::DARWIN => bernoulli(), ScrollSpecId::EUCLID => euclid(), ScrollSpecId::FEYNMAN => feynman(), - ScrollSpecId::Galileo => galileo(), + ScrollSpecId::GALILEO => galileo(), }; Self { precompile_provider: EthPrecompiles { precompiles, spec: SpecId::default() }, spec } } diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index 48bf8d5..3165476 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -92,7 +92,7 @@ mod tests { // Test Galileo behavior let galileo_result = galileo_run(&input, gas_limit); - assert!(galileo_result.is_ok(), "Galileo modexp should succeed"); + assert!(galileo_result.is_ok(), "GALILEO modexp should succeed"); let galileo_output = galileo_result.unwrap(); // Verify both produce the same result diff --git a/src/spec.rs b/src/spec.rs index 122b352..6881bcf 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -12,7 +12,7 @@ pub enum ScrollSpecId { #[default] EUCLID = 5, FEYNMAN = 6, - Galileo = 7, + GALILEO = 7, } impl ScrollSpecId { From 8f5ba7d06b92745385c68db3fb2baf94efdfbe7a Mon Sep 17 00:00:00 2001 From: georgehao Date: Fri, 17 Oct 2025 11:39:21 +0800 Subject: [PATCH 4/4] address comment --- src/precompile/mod.rs | 2 +- src/precompile/modexp.rs | 26 ++++++++++++++------------ src/spec.rs | 6 +++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 6f6170b..8aa7129 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -107,7 +107,7 @@ pub(crate) fn galileo() -> &'static Precompiles { static INSTANCE: OnceBox = OnceBox::new(); INSTANCE.get_or_init(|| { let mut precompiles = feynman().clone(); - precompiles.extend([modexp::GALILEO]); + precompiles.extend([modexp::GALILEO, secp256r1::P256VERIFY_OSAKA]); Box::new(precompiles) }) } diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index 3165476..bd5454a 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -19,7 +19,7 @@ pub const BERNOULLI_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); pub const BERNOULLI: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, bernoulli_run); /// The Galileo MODEXP precompile. -pub const GALILEO: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, galileo_run); +pub const GALILEO: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, modexp::osaka_run); /// The bernoulli MODEXP precompile implementation. /// @@ -50,14 +50,6 @@ pub fn bernoulli_run(input: &[u8], gas_limit: u64) -> PrecompileResult { run_inner::<_, OSAKA>(input, gas_limit, 200, berlin_gas_calc) } -/// The MODEXP precompile with Galileo (OSAKA) implementation. -/// -/// This version removes the 32-byte length limit that was present in BERNOULLI, -/// allowing modexp operations with larger inputs according to the OSAKA specification. -pub fn galileo_run(input: &[u8], gas_limit: u64) -> PrecompileResult { - modexp::osaka_run(input, gas_limit) -} - #[cfg(test)] mod tests { use super::*; @@ -91,7 +83,7 @@ mod tests { let bernoulli_output = bernoulli_result.unwrap(); // Test Galileo behavior - let galileo_result = galileo_run(&input, gas_limit); + let galileo_result = modexp::osaka_run(&input, gas_limit); assert!(galileo_result.is_ok(), "GALILEO modexp should succeed"); let galileo_output = galileo_result.unwrap(); @@ -138,8 +130,18 @@ mod tests { ); // Galileo should accept this (no 32-byte limit) - let galileo_result = galileo_run(&input, gas_limit); + let galileo_result = modexp::osaka_run(&input, gas_limit); assert!(galileo_result.is_ok(), "Galileo should accept base_len > 32"); + + // Verify the computed result is correct + // (0x030303...0303)^5 mod 7 = 0 + let galileo_output = galileo_result.unwrap(); + let expected = vec![0u8]; + assert_eq!( + galileo_output.bytes.as_ref(), + &expected, + "Galileo should compute correct modexp result" + ); } #[test] @@ -174,7 +176,7 @@ mod tests { let gas_limit = 100000u64; let bernoulli_result = bernoulli_run(&input, gas_limit).unwrap(); - let galileo_result = galileo_run(&input, gas_limit).unwrap(); + let galileo_result = modexp::osaka_run(&input, gas_limit).unwrap(); assert_eq!( bernoulli_result.bytes.as_ref(), diff --git a/src/spec.rs b/src/spec.rs index 6881bcf..680ba11 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -43,7 +43,7 @@ impl ScrollSpecId { Self::DARWIN | Self::EUCLID | Self::FEYNMAN | - Self::Galileo => SpecId::SHANGHAI, + Self::GALILEO => SpecId::SHANGHAI, } } } @@ -76,7 +76,7 @@ impl From<&str> for ScrollSpecId { name::DARWIN => Self::DARWIN, name::EUCLID => Self::EUCLID, name::FEYNMAN => Self::FEYNMAN, - name::GALILEO => Self::Galileo, + name::GALILEO => Self::GALILEO, _ => Self::default(), } } @@ -91,7 +91,7 @@ impl From for &'static str { ScrollSpecId::DARWIN => name::DARWIN, ScrollSpecId::EUCLID => name::EUCLID, ScrollSpecId::FEYNMAN => name::FEYNMAN, - ScrollSpecId::Galileo => name::GALILEO, + ScrollSpecId::GALILEO => name::GALILEO, } } }