diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 051eb3d4295bc..5850c523a368d 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -78,12 +78,13 @@ pub enum WalletSubcommands { /// Sign a message or typed data. #[clap(visible_alias = "s")] Sign { - /// The message or typed data to sign. + /// The message, typed data, or hash to sign. + /// + /// Messages starting with 0x are expected to be hex encoded, which get decoded before + /// being signed. /// - /// Messages starting with 0x are expected to be hex encoded, - /// which get decoded before being signed. /// The message will be prefixed with the Ethereum Signed Message header and hashed before - /// signing. + /// signing, unless `--no-hash` is provided. /// /// Typed data can be provided as a json string or a file name. /// Use --data flag to denote the message is a string of typed data. @@ -92,15 +93,18 @@ pub enum WalletSubcommands { /// The data should be formatted as JSON. message: String, - /// If provided, the message will be treated as typed data. + /// Treat the message as JSON typed data. #[clap(long)] data: bool, - /// If provided, the message will be treated as a file name containing typed data. Requires - /// --data. + /// Treat the message as a file containing JSON typed data. Requires `--data`. #[clap(long, requires = "data")] from_file: bool, + /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. + #[clap(long, conflicts_with = "data")] + no_hash: bool, + #[clap(flatten)] wallet: WalletOpts, }, @@ -247,7 +251,7 @@ impl WalletSubcommands { let addr = wallet.address(); println!("{}", addr.to_alloy().to_checksum(None)); } - WalletSubcommands::Sign { message, data, from_file, wallet } => { + WalletSubcommands::Sign { message, data, from_file, no_hash, wallet } => { let wallet = wallet.signer().await?; let sig = if data { let typed_data: TypedData = if from_file { @@ -258,6 +262,8 @@ impl WalletSubcommands { serde_json::from_str(&message)? }; wallet.sign_typed_data(&typed_data).await? + } else if no_hash { + wallet.sign_hash(&message.parse()?).await? } else { wallet.sign_message(Self::hex_str_to_bytes(&message)?).await? }; diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index bee0796fd622f..6588f5e22cc17 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -23,4 +23,6 @@ pub enum WalletSignerError { Io(#[from] std::io::Error), #[error(transparent)] InvalidHex(#[from] FromHexError), + #[error("{0} cannot sign raw hashes")] + CannotSignRawHash(&'static str), } diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index baafebeb5f561..ed7b99a0c5761 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -1,4 +1,5 @@ use crate::error::WalletSignerError; +use alloy_primitives::B256; use async_trait::async_trait; use ethers_core::types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712}, @@ -153,6 +154,19 @@ impl Signer for &WalletSigner { } } +impl WalletSigner { + pub async fn sign_hash(&self, hash: &B256) -> Result { + match self { + // TODO: AWS can sign hashes but utilities aren't exposed in ethers-signers. + // TODO: Implement with alloy-signer. + Self::Aws(_aws) => Err(WalletSignerError::CannotSignRawHash("AWS")), + Self::Ledger(_) => Err(WalletSignerError::CannotSignRawHash("Ledger")), + Self::Local(wallet) => wallet.sign_hash(hash.0.into()).map_err(Into::into), + Self::Trezor(_) => Err(WalletSignerError::CannotSignRawHash("Trezor")), + } + } +} + /// Signers that require user action to be obtained. #[derive(Debug, Clone)] pub enum PendingSigner {