From 4c8d3c81e734c1760443b42a6c2229b68cfe9b3e Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 28 Jan 2021 12:21:53 +0530 Subject: [PATCH] Feat/is middleware signer (#182) * feat: signature from_str can handle 0x-prefixed strings * feat: add is_signer method to the middleware trait * fix: eth_sign Signature deserialisation * chore: refactor for cleaner decoding of Signature --- ethers-core/src/types/signature.rs | 14 +++++++++ ethers-middleware/src/signer.rs | 5 ++++ ethers-providers/src/lib.rs | 7 +++++ ethers-providers/src/provider.rs | 46 +++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/types/signature.rs b/ethers-core/src/types/signature.rs index ef4bc80d..448c1184 100644 --- a/ethers-core/src/types/signature.rs +++ b/ethers-core/src/types/signature.rs @@ -173,6 +173,7 @@ impl FromStr for Signature { type Err = SignatureError; fn from_str(s: &str) -> Result { + let s = s.strip_prefix("0x").unwrap_or(s); let bytes = hex::decode(s)?; Signature::try_from(&bytes[..]) } @@ -260,4 +261,17 @@ mod tests { Address::from_str("2c7536E3605D9C16a7a3D7b1898e529396a65c23").unwrap() ); } + + #[test] + fn signature_from_str() { + let s1 = Signature::from_str( + "0xaa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500" + ).expect("could not parse 0x-prefixed signature"); + + let s2 = Signature::from_str( + "aa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500" + ).expect("could not parse non-prefixed signature"); + + assert_eq!(s1, s2); + } } diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs index 5918f059..1249f99c 100644 --- a/ethers-middleware/src/signer.rs +++ b/ethers-middleware/src/signer.rs @@ -235,6 +235,11 @@ where &self.inner } + /// `SignerMiddleware` is instantiated with a signer. + async fn is_signer(&self) -> bool { + true + } + /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that /// gas cost and nonce calculations take it into account. For simple transactions this can be /// left to `None`. diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index c75d477e..db141447 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -306,6 +306,13 @@ pub trait Middleware: Sync + Send + Debug { .map_err(FromErr::from) } + /// This returns true if either the middleware stack contains a `SignerMiddleware`, or the + /// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of + /// the above conditions are met, then the middleware stack is not capable of signing data. + async fn is_signer(&self) -> bool { + self.inner().is_signer().await + } + async fn sign + Send + Sync>( &self, data: T, diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 9d923c3e..1e070d35 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -79,6 +79,9 @@ pub enum ProviderError { #[error(transparent)] HexError(#[from] hex::FromHexError), + + #[error("custom error: {0}")] + CustomError(String), } /// Types of filters supported by the JSON-RPC. @@ -316,6 +319,15 @@ impl Middleware for Provider

{ Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) } + /// The JSON-RPC provider is at the bottom-most position in the middleware stack. Here we check + /// if it has the key for the sender address unlocked, as well as supports the `eth_sign` call. + async fn is_signer(&self) -> bool { + match self.3 { + Some(sender) => self.sign(vec![], &sender).await.is_ok(), + None => false, + } + } + /// Signs data using a specific account. This account needs to be unlocked. async fn sign + Send + Sync>( &self, @@ -324,7 +336,15 @@ impl Middleware for Provider

{ ) -> Result { let data = utils::serialize(&data.into()); let from = utils::serialize(from); - self.request("eth_sign", [from, data]).await + + // get the response from `eth_sign` call and trim the 0x-prefix if present. + let sig: String = self.request("eth_sign", [from, data]).await?; + let sig = sig.strip_prefix("0x").unwrap_or(&sig); + + // decode the signature. + let sig = hex::decode(sig)?; + Ok(Signature::try_from(sig.as_slice()) + .map_err(|e| ProviderError::CustomError(e.to_string()))?) } ////// Contract state @@ -847,6 +867,30 @@ mod tests { } } + #[tokio::test] + async fn test_is_signer() { + use ethers_core::utils::Ganache; + use std::str::FromStr; + + let ganache = Ganache::new().spawn(); + let provider = Provider::::try_from(ganache.endpoint()) + .unwrap() + .with_sender(ganache.addresses()[0]); + assert_eq!(provider.is_signer().await, true); + + let provider = Provider::::try_from(ganache.endpoint()).unwrap(); + assert_eq!(provider.is_signer().await, false); + + let sender = Address::from_str("635B4764D1939DfAcD3a8014726159abC277BecC") + .expect("should be able to parse hex address"); + let provider = Provider::::try_from( + "https://ropsten.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778", + ) + .unwrap() + .with_sender(sender); + assert_eq!(provider.is_signer().await, false); + } + // this must be run with geth or parity since ganache-core still does not support // eth_pendingTransactions, https://github.com/trufflesuite/ganache-core/issues/405 // example command: `geth --dev --rpc --dev.period 1`