From 94b35347364cf453283c71513a44fb5b8848378f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 09:43:52 +0200 Subject: [PATCH 1/8] feat: add Precompiles trait --- .../src/token/erc20/extensions/permit.rs | 21 +++-- contracts/src/utils/cryptography/ecdsa.rs | 4 +- contracts/src/utils/mod.rs | 1 + contracts/src/utils/precompiles.rs | 90 +++++++++++++++++++ 4 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 contracts/src/utils/precompiles.rs diff --git a/contracts/src/token/erc20/extensions/permit.rs b/contracts/src/token/erc20/extensions/permit.rs index 58dfbcd4d..e6714593a 100644 --- a/contracts/src/token/erc20/extensions/permit.rs +++ b/contracts/src/token/erc20/extensions/permit.rs @@ -21,11 +21,12 @@ use stylus_sdk::{block, call::MethodError, function_selector, prelude::*}; use crate::{ token::erc20::{self, Erc20}, utils::{ - cryptography::{ - ecdsa::{self, ECDSAInvalidSignature, ECDSAInvalidSignatureS}, - eip712::IEip712, - }, + cryptography::eip712::IEip712, nonces::{INonces, Nonces}, + precompiles::{ + ecrecover::{self, ECDSAInvalidSignature, ECDSAInvalidSignatureS}, + Precompiles, + }, }, }; @@ -104,11 +105,13 @@ impl From for Error { } } -impl From for Error { - fn from(value: ecdsa::Error) -> Self { +impl From for Error { + fn from(value: ecrecover::Error) -> Self { match value { - ecdsa::Error::InvalidSignature(e) => Error::InvalidSignature(e), - ecdsa::Error::InvalidSignatureS(e) => Error::InvalidSignatureS(e), + ecrecover::Error::InvalidSignature(e) => Error::InvalidSignature(e), + ecrecover::Error::InvalidSignatureS(e) => { + Error::InvalidSignatureS(e) + } } } } @@ -243,7 +246,7 @@ impl Erc20Permit { let hash: B256 = self.eip712.hash_typed_data_v4(struct_hash); - let signer: Address = ecdsa::recover(self, hash, v, r, s)?; + let signer: Address = self.ecrecover(hash, v, r, s)?; if signer != owner { return Err(ERC2612InvalidSigner { signer, owner }.into()); diff --git a/contracts/src/utils/cryptography/ecdsa.rs b/contracts/src/utils/cryptography/ecdsa.rs index 941e8ce00..8c09677bd 100644 --- a/contracts/src/utils/cryptography/ecdsa.rs +++ b/contracts/src/utils/cryptography/ecdsa.rs @@ -11,8 +11,6 @@ use stylus_sdk::{ prelude::*, }; -use crate::utils::cryptography::ecdsa; - /// Address of the `ecrecover` EVM precompile. pub const ECRECOVER_ADDR: Address = address!("0000000000000000000000000000000000000001"); @@ -66,7 +64,7 @@ pub enum Error { InvalidSignatureS(ECDSAInvalidSignatureS), } -impl MethodError for ecdsa::Error { +impl MethodError for Error { fn encode(self) -> alloc::vec::Vec { self.into() } diff --git a/contracts/src/utils/mod.rs b/contracts/src/utils/mod.rs index 94a18192b..12941b7ac 100644 --- a/contracts/src/utils/mod.rs +++ b/contracts/src/utils/mod.rs @@ -5,6 +5,7 @@ pub mod math; pub mod metadata; pub mod nonces; pub mod pausable; +pub mod precompiles; pub mod structs; pub use metadata::Metadata; diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs new file mode 100644 index 000000000..e4db58697 --- /dev/null +++ b/contracts/src/utils/precompiles.rs @@ -0,0 +1,90 @@ +//! ArbOS precompile wrapper enabling easier precompile invocation. +use alloy_primitives::{Address, B256}; +use stylus_sdk::prelude::*; + +pub use crate::utils::cryptography::ecdsa as ecrecover; +use crate::utils::cryptography::ecdsa::{recover, Error}; + +/// Trait providing access to Arbitrum precompiles for Stylus contracts. +/// +/// This trait wraps complex precompile invocations to provide a clean, +/// ergonomic interface for calling Arbitrum's built-in cryptographic functions +/// and utilities from within Stylus smart contracts. +/// +/// Precompiles are pre-deployed contracts at fixed addresses that implement +/// commonly used cryptographic operations and other utilities. They execute +/// natively in the Arbitrum runtime for better performance compared to +/// implementing these operations in contract code. +/// +/// See: +/// +/// # Usage +/// +/// Implement this trait for your contract storage type to gain access to +/// precompile functionality: +/// +/// ```rust,ignore +/// use openzeppelin_stylus::utils::cryptography::Precompiles; +/// +/// #[storage] +/// struct MyContract { +/// // your fields... +/// } +/// +/// // The `Precompiles` trait is automatically implemented for all contracts. +/// +/// #[public] +/// impl MyContract { +/// fn verify_signature(&mut self, msg_hash: B256, sig: (u8, B256, B256)) -> Result { +/// let (v, r, s) = sig; +/// self.ecrecover(msg_hash, v, r, s) +/// } +/// } +/// ``` +/// +/// # Error Handling +/// +/// Precompile methods return `Result` types to handle both invalid inputs and +/// precompile execution failures. Always handle these errors appropriately +/// in your contract logic. +pub trait Precompiles: TopLevelStorage { + /// Returns the address that signed a hashed message (`hash`). + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. given address. + /// * `hash` - Hash of the message. + /// * `v` - `v` value from the signature. + /// * `r` - `r` value from the signature. + /// * `s` - `s` value from the signature. + /// + /// # Errors + /// + /// * [`Error::InvalidSignatureS`] - If the `s` value is grater than + /// [`SIGNATURE_S_UPPER_BOUND`]. + /// * [`Error::InvalidSignature`] - If the recovered address is + /// [`Address::ZERO`]. + /// + /// # Panics + /// + /// * If the `ecrecover` precompile fails to execute. + fn ecrecover( + &mut self, + hash: B256, + v: u8, + r: B256, + s: B256, + ) -> Result; +} + +impl Precompiles for T { + fn ecrecover( + &mut self, + hash: B256, + v: u8, + r: B256, + s: B256, + ) -> Result { + recover(self, hash, v, r, s) + } +} From 44e42be6fa64993ee1b58e40c17c7056bc3fa39d Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 09:48:36 +0200 Subject: [PATCH 2/8] docs: simplify comment --- contracts/src/utils/precompiles.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index e4db58697..442b7d6e3 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -27,11 +27,14 @@ use crate::utils::cryptography::ecdsa::{recover, Error}; /// use openzeppelin_stylus::utils::cryptography::Precompiles; /// /// #[storage] +/// #[entrypoint] /// struct MyContract { /// // your fields... /// } /// -/// // The `Precompiles` trait is automatically implemented for all contracts. +/// // The `Precompiles` trait is automatically implemented for all +/// // contracts annotated with `#[entrypoint]` or that implement +/// // `stylus_sdk::prelude::TopLevelStorage`. /// /// #[public] /// impl MyContract { From e6e1a8f13377c86ed2f4ef149a625ded969b16b5 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 09:52:45 +0200 Subject: [PATCH 3/8] docs: fix --- contracts/src/token/erc20/extensions/permit.rs | 6 +++--- contracts/src/utils/precompiles.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc20/extensions/permit.rs b/contracts/src/token/erc20/extensions/permit.rs index e6714593a..254c9b79d 100644 --- a/contracts/src/token/erc20/extensions/permit.rs +++ b/contracts/src/token/erc20/extensions/permit.rs @@ -187,9 +187,9 @@ pub trait IErc20Permit: INonces { /// * [`ERC2612ExpiredSignature`] - If the `deadline` param is from the /// past. /// * [`ERC2612InvalidSigner`] - If signer is not an `owner`. - /// * [`ecdsa::Error::InvalidSignatureS`] - If the `s` value is grater than - /// [`ecdsa::SIGNATURE_S_UPPER_BOUND`]. - /// * [`ecdsa::Error::InvalidSignature`] - If the recovered address is + /// * [`ecrecover::Error::InvalidSignatureS`] - If the `s` value is grater + /// than [`ecrecover::SIGNATURE_S_UPPER_BOUND`]. + /// * [`ecrecover::Error::InvalidSignature`] - If the recovered address is /// [`Address::ZERO`]. /// * [`erc20::Error::InvalidSpender`] - If the `spender` address is /// [`Address::ZERO`]. diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index 442b7d6e3..e8e1a0a39 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -1,4 +1,4 @@ -//! ArbOS precompile wrapper enabling easier precompile invocation. +//! `ArbOS` precompile wrapper enabling easier precompile invocation. use alloy_primitives::{Address, B256}; use stylus_sdk::prelude::*; @@ -64,7 +64,7 @@ pub trait Precompiles: TopLevelStorage { /// # Errors /// /// * [`Error::InvalidSignatureS`] - If the `s` value is grater than - /// [`SIGNATURE_S_UPPER_BOUND`]. + /// [`ecrecover::SIGNATURE_S_UPPER_BOUND`]. /// * [`Error::InvalidSignature`] - If the recovered address is /// [`Address::ZERO`]. /// From 969e44371aa2b48aca50f38af9381cb873b72b85 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 10:11:31 +0200 Subject: [PATCH 4/8] feat: add primitives module containing types --- contracts/src/token/erc20/extensions/permit.rs | 4 +++- contracts/src/utils/precompiles.rs | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/extensions/permit.rs b/contracts/src/token/erc20/extensions/permit.rs index 254c9b79d..029f680b6 100644 --- a/contracts/src/token/erc20/extensions/permit.rs +++ b/contracts/src/token/erc20/extensions/permit.rs @@ -24,7 +24,9 @@ use crate::{ cryptography::eip712::IEip712, nonces::{INonces, Nonces}, precompiles::{ - ecrecover::{self, ECDSAInvalidSignature, ECDSAInvalidSignatureS}, + primitives::ecrecover::{ + self, ECDSAInvalidSignature, ECDSAInvalidSignatureS, + }, Precompiles, }, }, diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index e8e1a0a39..5b0ad39be 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -1,9 +1,19 @@ //! `ArbOS` precompile wrapper enabling easier precompile invocation. use alloy_primitives::{Address, B256}; +use primitives::ecrecover::{recover, Error}; use stylus_sdk::prelude::*; -pub use crate::utils::cryptography::ecdsa as ecrecover; -use crate::utils::cryptography::ecdsa::{recover, Error}; +/// Ethereum ecrecover precompile primitives. +/// +/// This module provides the cryptographic primitives needed for Ethereum's +/// `ecrecover` precompile, which recovers the Ethereum address from an +/// ECDSA signature and message hash. +/// +/// Re-exports selected ECDSA types and constants specifically relevant +/// to the ecrecover operation. +pub mod primitives { + pub use crate::utils::cryptography::ecdsa as ecrecover; +} /// Trait providing access to Arbitrum precompiles for Stylus contracts. /// From a79d78324dee2ffc3513d51f699100756b6cfb02 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 10:14:26 +0200 Subject: [PATCH 5/8] docs: fix --- contracts/src/utils/precompiles.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index 5b0ad39be..85f64c94c 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -1,18 +1,26 @@ //! `ArbOS` precompile wrapper enabling easier precompile invocation. use alloy_primitives::{Address, B256}; -use primitives::ecrecover::{recover, Error}; +use primitives::ecrecover::Error; use stylus_sdk::prelude::*; -/// Ethereum ecrecover precompile primitives. -/// -/// This module provides the cryptographic primitives needed for Ethereum's -/// `ecrecover` precompile, which recovers the Ethereum address from an -/// ECDSA signature and message hash. -/// -/// Re-exports selected ECDSA types and constants specifically relevant -/// to the ecrecover operation. +use crate::utils::cryptography::ecdsa::recover; + +/// Precompile primitives. pub mod primitives { - pub use crate::utils::cryptography::ecdsa as ecrecover; + /// The ecrecover precompile primitives. + /// + /// This module provides the cryptographic primitives needed for the + /// `ecrecover` precompile, which recovers the signer address from an + /// ECDSA signature and message hash. + /// + /// Re-exports selected ECDSA types and constants specifically relevant + /// to the ecrecover operation. + pub mod ecrecover { + pub use crate::utils::cryptography::ecdsa::{ + ECDSAInvalidSignature, ECDSAInvalidSignatureS, EcRecoverData, + Error, ECRECOVER_ADDR, SIGNATURE_S_UPPER_BOUND, + }; + } } /// Trait providing access to Arbitrum precompiles for Stylus contracts. From a04420a4fee713d9a3b659c7687e0f4bf1f2757f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Jun 2025 10:25:20 +0200 Subject: [PATCH 6/8] docs: fix --- contracts/src/utils/precompiles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index 85f64c94c..4147da6cb 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -82,7 +82,7 @@ pub trait Precompiles: TopLevelStorage { /// # Errors /// /// * [`Error::InvalidSignatureS`] - If the `s` value is grater than - /// [`ecrecover::SIGNATURE_S_UPPER_BOUND`]. + /// [`primitives::ecrecover::SIGNATURE_S_UPPER_BOUND`]. /// * [`Error::InvalidSignature`] - If the recovered address is /// [`Address::ZERO`]. /// From cb2542e0f5144a5d34503571c138bdc4cf57625a Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 24 Jun 2025 13:50:34 +0200 Subject: [PATCH 7/8] docs: wording Co-authored-by: Daniel Bigos --- contracts/src/utils/precompiles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/utils/precompiles.rs b/contracts/src/utils/precompiles.rs index 4147da6cb..80d479bba 100644 --- a/contracts/src/utils/precompiles.rs +++ b/contracts/src/utils/precompiles.rs @@ -1,4 +1,4 @@ -//! `ArbOS` precompile wrapper enabling easier precompile invocation. +//! `ArbOS` precompiles wrapper enabling easier invocation. use alloy_primitives::{Address, B256}; use primitives::ecrecover::Error; use stylus_sdk::prelude::*; From 9da43e0e508796c54145bc809fa925d702a8743d Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 24 Jun 2025 14:04:43 +0200 Subject: [PATCH 8/8] test: update ecdsa --- examples/ecdsa/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/ecdsa/src/lib.rs b/examples/ecdsa/src/lib.rs index 37403a490..e76bb6911 100644 --- a/examples/ecdsa/src/lib.rs +++ b/examples/ecdsa/src/lib.rs @@ -4,7 +4,9 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::{Address, B256}; -use openzeppelin_stylus::utils::cryptography::ecdsa; +use openzeppelin_stylus::utils::{ + cryptography::ecdsa, precompiles::Precompiles, +}; use stylus_sdk::prelude::*; #[entrypoint] @@ -20,6 +22,6 @@ impl ECDSAExample { r: B256, s: B256, ) -> Result { - ecdsa::recover(self, hash, v, r, s) + self.ecrecover(hash, v, r, s) } }