diff --git a/Cargo.toml b/Cargo.toml index 8c5ee3aa..41c24571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,9 @@ edition = "2018" arrayvec = "0.5.0" ethabi = "9.0.0" ethereum-types = "0.8.0" -ethereum-transaction = "0.2.0" -ethsign = "0.7.3" futures = "0.1.26" jsonrpc-core = "14.0.0" log = "0.4.6" -parity-crypto = "0.4.2" parking_lot = "0.10.0" rustc-hex = "2.0.1" serde = { version = "1.0.90", features = ["derive"] } diff --git a/src/api/accounts.rs b/src/api/accounts.rs deleted file mode 100644 index 581952bf..00000000 --- a/src/api/accounts.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Partial implementation of the `Accounts` namespace. - -use crate::api::{Namespace, Web3}; -use crate::error::Error; -use crate::helpers::CallFuture; -use crate::types::{ - Address, Bytes, Recovery, RecoveryMessage, SignedData, SignedTransaction, TransactionParameters, H256, U256, -}; -use crate::Transport; -use ethereum_transaction::{ - Bytes as EthtxBytes, SignTransaction, SignedTransaction as EthtxSignedTransaction, Transaction, -}; -use ethsign::{Error as EthsignError, SecretKey}; -use futures::future::{self, Either, FutureResult, Join3}; -use futures::{Async, Future, Poll}; -use parity_crypto::Keccak256; -use std::borrow::Cow; -use std::mem; - -/// `Accounts` namespace -#[derive(Debug, Clone)] -pub struct Accounts { - transport: T, -} - -impl Namespace for Accounts { - fn new(transport: T) -> Self - where - Self: Sized, - { - Accounts { transport } - } - - fn transport(&self) -> &T { - &self.transport - } -} - -impl Accounts { - /// Gets the parent `web3` namespace - fn web3(&self) -> Web3 { - Web3::new(self.transport.clone()) - } - - /// Signs an Ethereum transaction with a given private key. - pub fn sign_transaction(&self, tx: TransactionParameters, key: &SecretKey) -> SignTransactionFuture { - SignTransactionFuture::new(self, tx, key) - } - - /// Hash a message according to EIP-191. - /// - /// The data is a UTF-8 encoded string and will enveloped as follows: - /// `"\x19Ethereum Signed Message:\n" + message.length + message` and hashed - /// using keccak256. - pub fn hash_message(&self, message: S) -> H256 - where - S: AsRef<[u8]>, - { - let message = message.as_ref(); - - let mut eth_message = Vec::from(format!("\x19Ethereum Signed Message:\n{}", message.len()).as_bytes()); - eth_message.extend_from_slice(message); - - eth_message.keccak256().into() - } - - /// Sign arbitrary string data. - /// - /// The data is UTF-8 encoded and enveloped the same way as with - /// `hash_message`. The returned signed data's signature is in 'Electrum' - /// notation, that is the recovery value `v` is either `27` or `28` (as - /// opposed to the standard notation where `v` is either `0` or `1`). This - /// is important to consider when using this signature with other crates - /// such as `ethsign`. - pub fn sign(&self, message: S, key: &SecretKey) -> Result - where - S: AsRef<[u8]>, - { - let message = message.as_ref(); - let message_hash = self.hash_message(message); - - let signature = key.sign(&message_hash[..]).map_err(EthsignError::from)?; - // convert to 'Electrum' notation - let v = signature.v + 27; - - let signature_bytes = Bytes({ - let mut bytes = Vec::with_capacity(65); - bytes.extend_from_slice(&signature.r[..]); - bytes.extend_from_slice(&signature.s[..]); - bytes.push(v); - bytes - }); - - // We perform this allocation only after all previous fallible actions have completed successfully. - let message = message.to_owned(); - - Ok(SignedData { - message, - message_hash, - v, - r: signature.r.into(), - s: signature.s.into(), - signature: signature_bytes, - }) - } - - /// Recovers the Ethereum address which was used to sign the given data. - /// - /// Recovery signature data uses 'Electrum' notation, this means the `v` - /// value is expected to be either `27` or `28`. - pub fn recover(&self, recovery: R) -> Result - where - R: Into, - { - let recovery = recovery.into(); - let message_hash = match recovery.message { - RecoveryMessage::Data(ref message) => self.hash_message(message), - RecoveryMessage::Hash(hash) => hash, - }; - let signature = recovery.as_signature(); - - let public_key = signature.recover(&message_hash[..]).map_err(EthsignError::from)?; - - Ok(public_key.address().into()) - } -} - -type MaybeReady = Either, CallFuture::Out>>; - -type TxParams = Join3, MaybeReady, MaybeReady>; - -/// Future resolving when transaction signing is complete. -/// -/// Transaction signing can perform RPC requests in order to fill missing -/// parameters required for signing `nonce`, `gas_price` and `chain_id`. Note -/// that if all transaction parameters were provided, this future will resolve -/// immediately. -pub struct SignTransactionFuture { - tx: TransactionParameters, - key: SecretKey, - inner: TxParams, -} - -impl SignTransactionFuture { - /// Creates a new SignTransactionFuture with accounts and transaction data. - pub fn new(accounts: &Accounts, tx: TransactionParameters, key: &SecretKey) -> SignTransactionFuture { - macro_rules! maybe { - ($o: expr, $f: expr) => { - match $o.clone() { - Some(value) => Either::A(future::ok(value)), - None => Either::B($f), - } - }; - } - - let from = key.public().address().into(); - let inner = Future::join3( - maybe!(tx.nonce, accounts.web3().eth().transaction_count(from, None)), - maybe!(tx.gas_price, accounts.web3().eth().gas_price()), - // TODO(nlordell): avoid converting chain ID to and from string, - // this will require wrapping the `Net::version()` call to convert - // the result from a string to a u64 - maybe!(tx.chain_id.map(|id| id.to_string()), accounts.web3().net().version()), - ); - - SignTransactionFuture { - tx, - key: key.clone(), - inner, - } - } -} - -impl Future for SignTransactionFuture { - type Item = SignedTransaction; - type Error = Error; - - fn poll(&mut self) -> Poll { - let (nonce, gas_price, chain_id) = try_ready!(self.inner.poll()); - let chain_id = chain_id.parse::().map_err(|e| Error::Decoder(e.to_string()))?; - - let data = mem::replace(&mut self.tx.data, Bytes::default()); - let tx = Transaction { - from: Address::zero(), // not used for signing. - to: self.tx.to, - nonce, - gas: self.tx.gas, - gas_price, - value: self.tx.value, - data: EthtxBytes(data.0), - }; - let signed = sign_transaction(tx, &self.key, chain_id)?; - - Ok(Async::Ready(signed)) - } -} - -/// Sign and return a raw signed transaction. -fn sign_transaction(tx: Transaction, key: &SecretKey, chain_id: u64) -> Result { - let tx = SignTransaction { - transaction: Cow::Owned(tx), - chain_id, - }; - - let hash = tx.hash(); - let sig = key.sign(&hash[..])?; - - let signed_tx = EthtxSignedTransaction::new(tx.transaction, tx.chain_id, sig.v, sig.r, sig.s); - let transaction_hash = signed_tx.hash().into(); - let raw_transaction = Bytes(signed_tx.to_rlp()); - - Ok(SignedTransaction { - message_hash: hash.into(), - v: signed_tx.v, - r: sig.r.into(), - s: sig.s.into(), - raw_transaction, - transaction_hash, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::tests::TestTransport; - use crate::types::Bytes; - use ethsign::SecretKey; - use rustc_hex::FromHex; - use serde_json::json; - - #[test] - fn accounts_sign_transaction() { - // retrieved test vector from: - // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction - - let tx = TransactionParameters { - to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse().unwrap()), - value: 1_000_000_000.into(), - gas: 2_000_000.into(), - ..Default::default() - }; - let secret: H256 = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" - .parse() - .unwrap(); - let key = SecretKey::from_raw(&secret[..]).unwrap(); - let nonce = U256::zero(); - let gas_price = U256::from(21_000_000_000u128); - let chain_id = "1"; - let from: Address = key.public().address().into(); - - let mut transport = TestTransport::default(); - transport.add_response(json!(nonce)); - transport.add_response(json!(gas_price)); - transport.add_response(json!(chain_id)); - - let signed = { - let accounts = Accounts::new(&transport); - accounts.sign_transaction(tx, &key).wait() - }; - - transport.assert_request( - "eth_getTransactionCount", - &[json!(from).to_string(), json!("latest").to_string()], - ); - transport.assert_request("eth_gasPrice", &[]); - transport.assert_request("net_version", &[]); - transport.assert_no_more_requests(); - - let expected = SignedTransaction { - message_hash: "88cfbd7e51c7a40540b233cf68b62ad1df3e92462f1c6018d6d67eae0f3b08f5" - .parse() - .unwrap(), - v: 0x25, - r: "c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895" - .parse() - .unwrap(), - s: "727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68" - .parse() - .unwrap(), - raw_transaction: Bytes( - "f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68" - .from_hex() - .unwrap(), - ), - transaction_hash: "de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593" - .parse() - .unwrap(), - }; - - assert_eq!(signed, Ok(expected)); - } - - #[test] - fn accounts_sign_transaction_with_all_parameters() { - let secret: Vec = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - .from_hex() - .unwrap(); - let key = SecretKey::from_raw(&secret).unwrap(); - - let accounts = Accounts::new(TestTransport::default()); - accounts - .sign_transaction( - TransactionParameters { - nonce: Some(0.into()), - gas_price: Some(1.into()), - chain_id: Some(42), - ..Default::default() - }, - &key, - ) - .wait() - .unwrap(); - - // sign_transaction makes no requests when all parameters are specified - accounts.transport().assert_no_more_requests(); - } - - #[test] - fn accounts_hash_message() { - // test vector taken from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#hashmessage - - let accounts = Accounts::new(TestTransport::default()); - let hash = accounts.hash_message("Hello World"); - - assert_eq!( - hash, - "a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2" - .parse() - .unwrap() - ); - - // this method does not actually make any requests. - accounts.transport().assert_no_more_requests(); - } - - #[test] - fn accounts_sign() { - // test vector taken from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign - - let accounts = Accounts::new(TestTransport::default()); - - let secret: Vec = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" - .from_hex() - .unwrap(); - let key = SecretKey::from_raw(&secret).unwrap(); - let signed = accounts.sign("Some data", &key).unwrap(); - - assert_eq!( - signed.message_hash, - "1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655" - .parse() - .unwrap() - ); - assert_eq!( - signed.signature.0, - "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c" - .from_hex::>() - .unwrap() - ); - - // this method does not actually make any requests. - accounts.transport().assert_no_more_requests(); - } - - #[test] - fn accounts_recover() { - // test vector taken from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#recover - - let accounts = Accounts::new(TestTransport::default()); - - let v = 0x1cu64; - let r: H256 = "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd" - .parse() - .unwrap(); - let s: H256 = "6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029" - .parse() - .unwrap(); - - let recovery = Recovery::new("Some data", v, r, s); - assert_eq!( - accounts.recover(recovery).unwrap(), - "2c7536E3605D9C16a7a3D7b1898e529396a65c23".parse().unwrap() - ); - - // this method does not actually make any requests. - accounts.transport().assert_no_more_requests(); - } - - #[test] - fn accounts_recover_signed() { - let secret: Vec = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - .from_hex() - .unwrap(); - let key = SecretKey::from_raw(&secret).unwrap(); - let address: Address = key.public().address().into(); - - let accounts = Accounts::new(TestTransport::default()); - - let signed = accounts.sign("rust-web3 rocks!", &key).unwrap(); - let recovered = accounts.recover(&signed).unwrap(); - assert_eq!(recovered, address); - - let signed = accounts - .sign_transaction( - TransactionParameters { - nonce: Some(0.into()), - gas_price: Some(1.into()), - chain_id: Some(42), - ..Default::default() - }, - &key, - ) - .wait() - .unwrap(); - let recovered = accounts.recover(&signed).unwrap(); - assert_eq!(recovered, address); - - // these methods make no requests - accounts.transport().assert_no_more_requests(); - } - - #[test] - fn sign_ethtx_transaction() { - // retrieved test vector from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#eth-accounts-signtransaction - - let tx = Transaction { - from: Default::default(), // not used for signing - nonce: 0.into(), - gas: 2_000_000.into(), - gas_price: 234_567_897_654_321u64.into(), - to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse().unwrap()), - value: 1_000_000_000.into(), - data: EthtxBytes(Vec::new()), - }; - let key = { - let raw: H256 = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" - .parse() - .unwrap(); - SecretKey::from_raw(&raw[..]).expect("valid key") - }; - - let signed = sign_transaction(tx, &key, 1).unwrap(); - - let expected = SignedTransaction { - message_hash: "6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5" - .parse() - .unwrap(), - v: 0x25, - r: "09ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9c" - .parse() - .unwrap(), - s: "440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428" - .parse() - .unwrap(), - raw_transaction: Bytes( - "f86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428" - .from_hex() - .unwrap(), - ), - transaction_hash: "d8f64a42b57be0d565f385378db2f6bf324ce14a594afc05de90436e9ce01f60" - .parse() - .unwrap(), - }; - - assert_eq!(signed, expected); - } -} diff --git a/src/api/mod.rs b/src/api/mod.rs index c8ff477d..cb33cac3 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,5 @@ //! `Web3` implementation -mod accounts; mod eth; mod eth_filter; mod eth_subscribe; @@ -11,7 +10,6 @@ mod personal; mod traces; mod web3; -pub use self::accounts::{Accounts, SignTransactionFuture}; pub use self::eth::Eth; pub use self::eth_filter::{BaseFilter, CreateFilter, EthFilter, FilterStream}; pub use self::eth_subscribe::{EthSubscribe, SubscriptionId, SubscriptionResult, SubscriptionStream}; @@ -58,11 +56,6 @@ impl Web3 { A::new(self.transport.clone()) } - /// Access methods from `accounts` namespace - pub fn accounts(&self) -> accounts::Accounts { - self.api() - } - /// Access methods from `eth` namespace pub fn eth(&self) -> eth::Eth { self.api() diff --git a/src/error.rs b/src/error.rs index 2a9c5ffe..9edea04e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ //! Web3 Error use crate::rpc::error::Error as RPCError; use derive_more::{Display, From}; -use ethsign::Error as EthsignError; use serde_json::Error as SerdeError; use std::io::Error as IoError; @@ -28,10 +27,6 @@ pub enum Error { /// io error #[display(fmt = "IO error: {}", _0)] Io(IoError), - /// signing error - #[display(fmt = "Signing error: {}", _0)] - #[from(ignore)] - Signing(String), /// web3 internal error #[display(fmt = "Internal Web3 error")] Internal, @@ -41,7 +36,7 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { - Unreachable | Decoder(_) | InvalidResponse(_) | Transport(_) | Signing(_) | Internal => None, + Unreachable | Decoder(_) | InvalidResponse(_) | Transport(_) | Internal => None, Rpc(ref e) => Some(e), Io(ref e) => Some(e), } @@ -54,12 +49,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: EthsignError) -> Self { - Error::Signing(format!("{:?}", err)) - } -} - impl Clone for Error { fn clone(&self) -> Self { use self::Error::*; @@ -70,7 +59,6 @@ impl Clone for Error { Transport(s) => Transport(s.clone()), Rpc(e) => Rpc(e.clone()), Io(e) => Io(IoError::from(e.kind())), - Signing(e) => Signing(e.clone()), Internal => Internal, } } @@ -81,10 +69,9 @@ impl PartialEq for Error { use self::Error::*; match (self, other) { (Unreachable, Unreachable) | (Internal, Internal) => true, - (Decoder(a), Decoder(b)) - | (InvalidResponse(a), InvalidResponse(b)) - | (Transport(a), Transport(b)) - | (Signing(a), Signing(b)) => a == b, + (Decoder(a), Decoder(b)) | (InvalidResponse(a), InvalidResponse(b)) | (Transport(a), Transport(b)) => { + a == b + } (Rpc(a), Rpc(b)) => a == b, (Io(a), Io(b)) => a.kind() == b.kind(), _ => false, diff --git a/src/helpers.rs b/src/helpers.rs index a439bcb7..448c0639 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -145,7 +145,7 @@ pub mod tests { assert_eq!(p, params); } - pub fn assert_no_more_requests(&self) { + pub fn assert_no_more_requests(&mut self) { let requests = self.requests.borrow(); assert_eq!( self.asserted, diff --git a/src/types/mod.rs b/src/types/mod.rs index 5b4a9bd3..3df0e2fc 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,8 +4,6 @@ mod block; mod bytes; mod log; mod parity_peers; -mod recovery; -mod signed; mod sync_state; mod trace_filtering; mod traces; @@ -21,8 +19,6 @@ pub use self::log::{Filter, FilterBuilder, Log}; pub use self::parity_peers::{ EthProtocolInfo, ParityPeerInfo, ParityPeerType, PeerNetworkInfo, PeerProtocolsInfo, PipProtocolInfo, }; -pub use self::recovery::{Recovery, RecoveryMessage}; -pub use self::signed::{SignedData, SignedTransaction, TransactionParameters}; pub use self::sync_state::{SyncInfo, SyncState}; pub use self::trace_filtering::{ Action, ActionType, Call, CallResult, CallType, Create, CreateResult, Res, Reward, RewardType, Suicide, Trace, diff --git a/src/types/recovery.rs b/src/types/recovery.rs deleted file mode 100644 index 2f1e9757..00000000 --- a/src/types/recovery.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::types::{SignedData, SignedTransaction, H256}; -use ethereum_transaction::SignedTransaction as EthtxSignedTransaction; -use ethsign::Signature; -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FmtResult}; - -/// Data for recovering the public address of signed data. -/// -/// Note that the signature data is in 'Electrum' notation and may have chain -/// replay protection applied. That means that `v` is expected to be `27`, `28`, -/// or `35 + chain_id * 2` or `36 + chain_id * 2`. -#[derive(Clone, Debug, PartialEq)] -pub struct Recovery { - /// The message to recover - pub message: RecoveryMessage, - /// V value. - pub v: u64, - /// R value. - pub r: H256, - /// S value. - pub s: H256, -} - -impl Recovery { - /// Creates new recovery data from its parts. - pub fn new(message: M, v: u64, r: H256, s: H256) -> Recovery - where - M: Into, - { - Recovery { - message: message.into(), - v, - r, - s, - } - } - - /// Creates new recovery data from a raw signature. - /// - /// This parses a raw signature which is expected to be 65 bytes long where - /// the first 32 bytes is the `r` value, the second 32 bytes the `s` value - /// and the final byte is the `v` value in 'Electrum' notation. - pub fn from_raw_signature(message: M, raw_signature: B) -> Result - where - M: Into, - B: AsRef<[u8]>, - { - let bytes = raw_signature.as_ref(); - - if bytes.len() != 65 { - return Err(ParseSignatureError); - } - - let v = bytes[64]; - let r = H256::from_slice(&bytes[0..32]); - let s = H256::from_slice(&bytes[32..64]); - - Ok(Recovery::new(message, v as _, r, s)) - } - - /// Retrieves the recovery signature in standard notation. - /// - /// This method returns a `ethsign::Signature` with `v` in standard - /// notation. This means that the `27`, `28` or chain relay protection is - /// removed from the recovery's v value. - pub fn as_signature(&self) -> Signature { - Signature { - v: self.standard_v(), - r: self.r.into(), - s: self.s.into(), - } - } - - /// Retrieve recovery value `v` in standard notation. - pub fn standard_v(&self) -> u8 { - // this is a re-implementation of `ethereum-transaction` as there is no - // good way to call this without building a full `SignedTransaction` - // which isn't needed here - match self.v { - 27 => 0, - 28 => 1, - v if v >= 35 => ((v - 1) % 2) as _, - _ => 4, - } - } -} - -impl<'a> From<&'a SignedData> for Recovery { - fn from(signed: &'a SignedData) -> Self { - Recovery::new(signed.message_hash, signed.v as _, signed.r, signed.s) - } -} - -impl<'a> From<&'a SignedTransaction> for Recovery { - fn from(tx: &'a SignedTransaction) -> Self { - Recovery::new(tx.message_hash, tx.v, tx.r, tx.s) - } -} - -impl<'a> From<&'a EthtxSignedTransaction<'a>> for Recovery { - fn from(tx: &'a EthtxSignedTransaction<'a>) -> Self { - Recovery::new(tx.bare_hash(), tx.v, H256(tx.r.into()), H256(tx.s.into())) - } -} - -/// Recovery message data. -/// -/// The message data can either be a binary message that is first hashed -/// according to EIP-191 and then recovered based on the signature or a -/// precomputed hash. -#[derive(Clone, Debug, PartialEq)] -pub enum RecoveryMessage { - /// Message bytes - Data(Vec), - /// Message hash - Hash(H256), -} - -impl From<&[u8]> for RecoveryMessage { - fn from(s: &[u8]) -> Self { - s.to_owned().into() - } -} - -impl From> for RecoveryMessage { - fn from(s: Vec) -> Self { - RecoveryMessage::Data(s) - } -} - -impl From<&str> for RecoveryMessage { - fn from(s: &str) -> Self { - s.as_bytes().to_owned().into() - } -} - -impl From for RecoveryMessage { - fn from(s: String) -> Self { - RecoveryMessage::Data(s.into_bytes()) - } -} - -impl From<[u8; 32]> for RecoveryMessage { - fn from(hash: [u8; 32]) -> Self { - H256(hash).into() - } -} - -impl From for RecoveryMessage { - fn from(hash: H256) -> Self { - RecoveryMessage::Hash(hash) - } -} - -/// An error parsing a raw signature. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct ParseSignatureError; - -impl Display for ParseSignatureError { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "error parsing raw signature: wrong number of bytes, expected 65") - } -} - -impl Error for ParseSignatureError {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::Bytes; - use rustc_hex::FromHex; - - #[test] - fn recovery_as_signature() { - let message = "Some data"; - let v = 0x1cu8; - let r: H256 = "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd" - .parse() - .unwrap(); - let s: H256 = "6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029" - .parse() - .unwrap(); - - let signed = SignedData { - message: message.as_bytes().to_owned(), - message_hash: "1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655" - .parse() - .unwrap(), - v, - r, - s, - signature: Bytes( - "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c" - .from_hex() - .unwrap() - ), - }; - let expected_signature = Signature { - v: 0x01, - r: r.into(), - s: s.into(), - }; - - assert_eq!(Recovery::from(&signed).as_signature(), expected_signature); - assert_eq!(Recovery::new(message, v as _, r, s).as_signature(), expected_signature); - assert_eq!( - Recovery::from_raw_signature(message, &signed.signature.0) - .unwrap() - .as_signature(), - expected_signature - ); - } -} diff --git a/src/types/signed.rs b/src/types/signed.rs deleted file mode 100644 index 87b30e91..00000000 --- a/src/types/signed.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::types::{Address, Bytes, CallRequest, H256, U256}; -use serde::{Deserialize, Serialize}; - -/// Struct representing signed data returned from `Accounts::sign` method. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub struct SignedData { - /// The original message that was signed. - pub message: Vec, - /// The keccak256 hash of the signed data. - #[serde(rename = "messageHash")] - pub message_hash: H256, - /// V value. - pub v: u8, - /// R value. - pub r: H256, - /// S value. - pub s: H256, - /// The signature bytes. - pub signature: Bytes, -} - -/// Transaction data for signing. -/// -/// The `Accounts::sign_transaction` method will fill optional fields with sane -/// defaults when they are omitted. Specifically the signing account's current -/// transaction count will be used for the `nonce`, the estimated recommended -/// gas price will be used for `gas_price`, and the current network ID will be -/// used for the `chain_id`. -/// -/// It is worth noting that the chain ID is not equivalent to the network ID. -/// They happen to be the same much of the time but it is recommended to set -/// this for signing transactions. -/// -/// `TransactionParameters` implements `Default` and uses `100_000` as the -/// default `gas` to use for the transaction. This is more than enough for -/// simple transactions sending ETH between accounts but may not be enough when -/// interacting with complex contracts. It is recommended when interacting -/// with contracts to use `Eth::estimate_gas` to estimate the required gas for -/// the transaction. -#[derive(Clone, Debug, PartialEq)] -pub struct TransactionParameters { - /// Transaction nonce (None for account transaction count) - pub nonce: Option, - /// To address - pub to: Option
, - /// Supplied gas - pub gas: U256, - /// Gas price (None for estimated gas price) - pub gas_price: Option, - /// Transfered value - pub value: U256, - /// Data - pub data: Bytes, - /// The chain ID (None for network ID) - pub chain_id: Option, -} - -/// The default fas for transactions. -/// -/// Unfortunatly there is no way to construct `U256`s with const functions for -/// constants so we just build it from it's `u64` words. Note that there is a -/// unit test to verify that it is constructed correctly and holds the expected -/// value of 100_000. -const TRANSACTION_DEFAULT_GAS: U256 = U256([100_000, 0, 0, 0]); - -impl Default for TransactionParameters { - fn default() -> Self { - TransactionParameters { - nonce: None, - to: None, - gas: TRANSACTION_DEFAULT_GAS, - gas_price: None, - value: U256::zero(), - data: Bytes::default(), - chain_id: None, - } - } -} - -impl From for TransactionParameters { - fn from(call: CallRequest) -> Self { - let to = if call.to != Address::zero() { - Some(call.to) - } else { - None - }; - - TransactionParameters { - nonce: None, - to, - gas: call.gas.unwrap_or(TRANSACTION_DEFAULT_GAS), - gas_price: call.gas_price, - value: call.value.unwrap_or_default(), - data: call.data.unwrap_or_default(), - chain_id: None, - } - } -} - -impl Into for TransactionParameters { - fn into(self) -> CallRequest { - CallRequest { - from: None, - to: self.to.unwrap_or_default(), - gas: Some(self.gas), - gas_price: self.gas_price, - value: Some(self.value), - data: Some(self.data), - } - } -} - -/// Data for offline signed transaction -#[derive(Clone, Debug, PartialEq)] -pub struct SignedTransaction { - /// The given message hash - pub message_hash: H256, - /// V value with chain replay protection. - pub v: u64, - /// R value. - pub r: H256, - /// S value. - pub s: H256, - /// The raw signed transaction ready to be sent with `send_raw_transaction` - pub raw_transaction: Bytes, - /// The transaction hash for the RLP encoded transaction. - pub transaction_hash: H256, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn verify_transaction_default_gas() { - assert_eq!(TRANSACTION_DEFAULT_GAS, U256::from(100_000)); - } -}