diff --git a/contracts/src/token/erc20/extensions/permit.rs b/contracts/src/token/erc20/extensions/permit.rs index 58dfbcd4d..029f680b6 100644 --- a/contracts/src/token/erc20/extensions/permit.rs +++ b/contracts/src/token/erc20/extensions/permit.rs @@ -21,11 +21,14 @@ 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::{ + primitives::ecrecover::{ + self, ECDSAInvalidSignature, ECDSAInvalidSignatureS, + }, + Precompiles, + }, }, }; @@ -104,11 +107,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) + } } } } @@ -184,9 +189,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`]. @@ -243,7 +248,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..80d479bba --- /dev/null +++ b/contracts/src/utils/precompiles.rs @@ -0,0 +1,111 @@ +//! `ArbOS` precompiles wrapper enabling easier invocation. +use alloy_primitives::{Address, B256}; +use primitives::ecrecover::Error; +use stylus_sdk::prelude::*; + +use crate::utils::cryptography::ecdsa::recover; + +/// Precompile primitives. +pub mod primitives { + /// 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. +/// +/// 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] +/// #[entrypoint] +/// struct MyContract { +/// // your fields... +/// } +/// +/// // The `Precompiles` trait is automatically implemented for all +/// // contracts annotated with `#[entrypoint]` or that implement +/// // `stylus_sdk::prelude::TopLevelStorage`. +/// +/// #[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 + /// [`primitives::ecrecover::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) + } +} 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) } }