From a38577499f200615a2cbb87f6110acf53b62dfd1 Mon Sep 17 00:00:00 2001 From: pecornilleau Date: Thu, 13 Apr 2023 08:25:14 +0200 Subject: [PATCH 1/4] SORU: EVM: make signature verification safe --- src/kernel_evm/Cargo.lock | 1 + src/kernel_evm/ethereum/Cargo.toml | 1 + src/kernel_evm/ethereum/src/signatures.rs | 248 +++++++++++++++++----- src/kernel_evm/evm_execution/src/lib.rs | 20 +- src/kernel_evm/kernel/src/block.rs | 8 +- 5 files changed, 216 insertions(+), 62 deletions(-) diff --git a/src/kernel_evm/Cargo.lock b/src/kernel_evm/Cargo.lock index 87418707c319..5e3c3f90fe30 100644 --- a/src/kernel_evm/Cargo.lock +++ b/src/kernel_evm/Cargo.lock @@ -1525,6 +1525,7 @@ dependencies = [ "sha3", "tezos-smart-rollup-encoding", "tezos_crypto_rs", + "thiserror", ] [[package]] diff --git a/src/kernel_evm/ethereum/Cargo.toml b/src/kernel_evm/ethereum/Cargo.toml index 79a5b04321f1..b20233d210a2 100644 --- a/src/kernel_evm/ethereum/Cargo.toml +++ b/src/kernel_evm/ethereum/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] evm = { version = "0.35.0", default-features = false } +thiserror = "1.0" primitive-types = { version = "0.11.1", default-features = false } rlp = "0.5.2" hex = "0.4" diff --git a/src/kernel_evm/ethereum/src/signatures.rs b/src/kernel_evm/ethereum/src/signatures.rs index 0c7647dae96b..abc8b488cb4b 100644 --- a/src/kernel_evm/ethereum/src/signatures.rs +++ b/src/kernel_evm/ethereum/src/signatures.rs @@ -9,7 +9,10 @@ //! We need to sign and write Ethereum specific values such //! as addresses and values. +use std::array::TryFromSliceError; + use crate::address::EthereumAddress; +use hex::FromHexError; use libsecp256k1::{ curve::Scalar, recover, sign, verify, Message, PublicKey, RecoveryId, SecretKey, Signature, @@ -17,16 +20,50 @@ use libsecp256k1::{ use primitive_types::{H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpIterator, RlpStream}; use sha3::{Digest, Keccak256}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum TransactionError { + #[error("Error reading a hex string")] + HexError(#[from] FromHexError), + + #[error("Error decoding RLP encoded byte array")] + DecoderError(#[from] DecoderError), + + #[error("Error extracting a slice")] + SlicingError(), + + #[error("Error manipulating ECDSA key")] + ECDSAError(), + + #[error("Error recomputing parity of signature")] + Parity(), +} + +impl From for TransactionError { + fn from(_: libsecp256k1::Error) -> Self { + Self::ECDSAError() + } +} +impl From for TransactionError { + fn from(_: TryFromSliceError) -> Self { + Self::SlicingError() + } +} /// produces address from a secret key -pub fn string_to_sk_and_address(s: String) -> (SecretKey, EthereumAddress) { - let data: [u8; 32] = hex::decode(s).unwrap().try_into().unwrap(); - let sk = SecretKey::parse(&data).unwrap(); +pub fn string_to_sk_and_address( + s: String, +) -> Result<(SecretKey, EthereumAddress), TransactionError> { + let mut data: [u8; 32] = [0u8; 32]; + let () = hex::decode_to_slice(s, &mut data)?; + let sk = SecretKey::parse(&data)?; let pk = PublicKey::from_secret_key(&sk); let serialised = &pk.serialize()[1..]; let kec = Keccak256::digest(serialised); - let value: [u8; 20] = kec.as_slice()[12..].try_into().unwrap(); - (sk, EthereumAddress::from(value)) + let mut value: [u8; 20] = [0u8; 20]; + value.copy_from_slice(&kec[12..]); + Ok((sk, EthereumAddress::from(value))) } /// the type of a transaction @@ -96,7 +133,7 @@ impl EthereumTransactionCommon { } /// Extracts the signature from an EthereumTransactionCommon - pub fn signature(&self) -> (Signature, RecoveryId) { + pub fn signature(&self) -> Result<(Signature, RecoveryId), TransactionError> { // copy r to Scalar let r: H256 = self.r; let r1: [u8; 32] = r.into(); @@ -108,36 +145,31 @@ impl EthereumTransactionCommon { let mut s = Scalar([0; 8]); let _ = s.set_b32(&s1); // recompute parity from v and chain_id - let ri = self.v - (self.chain_id * U256::from(2) + U256::from(35)); - if let Ok(ri) = RecoveryId::parse(ri.byte(0)) { - (Signature { r, s }, ri) - } else { - panic!( - "could not recompute parity from v={}, chain_id={}", - self.v, self.chain_id - ) - } + let ri_val = + U256::checked_sub(self.v, self.chain_id * U256::from(2) + U256::from(35)) + .ok_or(TransactionError::Parity()); + let ri = RecoveryId::parse(ri_val?.byte(0))?; + Ok((Signature { r, s }, ri)) } /// Find the caller address from r and s of the common data /// for an Ethereum transaction, ie, what address is associated /// with the signature of the message. /// TODO - pub fn caller(&self) -> EthereumAddress { + pub fn caller(&self) -> Result { let mes = self.message(); - let (sig, ri) = self.signature(); - let pk = recover(&mes, &sig, &ri).expect("Recover public key"); + let (sig, ri) = self.signature()?; + let pk = recover(&mes, &sig, &ri)?; let serialised = &pk.serialize()[1..]; let kec = Keccak256::digest(serialised); - let value: [u8; 20] = kec.as_slice()[12..] - .try_into() - .expect("Hash is 32 bytes, so [12..] should be 20 bytes long"); - EthereumAddress::from(value) - } + let value: [u8; 20] = kec.as_slice()[12..].try_into()?; + Ok(EthereumAddress::from(value)) + } ///produce a signed EthereumTransactionCommon. If the initial one was signed /// you should get the same thing. - pub fn sign_transaction(&self, string_sk: String) -> Self { - let sk = SecretKey::parse_slice(&hex::decode(string_sk).unwrap()).unwrap(); + pub fn sign_transaction(&self, string_sk: String) -> Result { + let hex: &[u8] = &hex::decode(string_sk)?; + let sk = SecretKey::parse_slice(hex)?; let mes = self.message(); let (sig, ri) = sign(&mes, &sk); let Signature { r, s } = sig; @@ -149,20 +181,20 @@ impl EthereumTransactionCommon { } else { U256::from(parity) + U256::from(2) * self.chain_id + U256::from(35) }; - EthereumTransactionCommon { + Ok(EthereumTransactionCommon { v, r, s, ..self.clone() - } + }) } /// verifies the signature - pub fn verify_signature(self) -> bool { + pub fn verify_signature(self) -> Result { let mes = self.message(); - let (sig, ri) = self.signature(); - let pk = recover(&mes, &sig, &ri).unwrap(); - verify(&mes, &sig, &pk) + let (sig, ri) = self.signature()?; + let pk = recover(&mes, &sig, &ri)?; + Ok(verify(&mes, &sig, &pk)) } /// Unserialize bytes as a RLP encoded legacy transaction. @@ -376,11 +408,12 @@ mod test { let (_sk, address_from_sk) = string_to_sk_and_address( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), - ); + ) + .unwrap(); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); - let address = transaction.caller(); + let address = transaction.caller().unwrap(); let expected_address_string: [u8; 20] = hex::decode("9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F") .unwrap() @@ -875,7 +908,7 @@ mod test { // assert assert_eq!( EthereumAddress::from("d9e5c94a12f78a96640757ac97ba0c257e8aa262".to_string()), - transaction.caller(), + transaction.caller().unwrap(), "test field from" ) } @@ -909,10 +942,12 @@ mod test { s: H256::zero(), }; - let signed = transaction.sign_transaction( - "cb9db6b5878db2fa20586e23b7f7b51c22a7c6ed0530daafc2615b116f170cd3" - .to_string(), - ); + let signed = transaction + .sign_transaction( + "cb9db6b5878db2fa20586e23b7f7b51c22a7c6ed0530daafc2615b116f170cd3" + .to_string(), + ) + .unwrap(); let v = U256::from(38); let r = string_to_h256_unsafe( @@ -931,7 +966,7 @@ mod test { fn test_caller_classic_with_chain_id() { let sk = "9bfc9fbe6296c8fef8eb8d6ce2ed5f772a011898c6cabe32d35e7c3e419efb1b" .to_string(); - let (_sk, address) = string_to_sk_and_address(sk.clone()); + let (_sk, address) = string_to_sk_and_address(sk.clone()).unwrap(); // Check that the derived address is the expected one. let expected_address_string: [u8; 20] = hex::decode(b"6471A723296395CF1Dcc568941AFFd7A390f94CE") @@ -944,12 +979,12 @@ mod test { // Check that the derived sender address is the expected one. let encoded = "f86d80843b9aca00825208940b52d4d3be5d18a7ab5e4476a2f5382bbf2b38d888016345785d8a000080820a95a0d9ef1298c18c88604e3f08e14907a17dfa81b1dc6b37948abe189d8db5cb8a43a06fc7040a71d71d3cb74bd05ead7046b10668ad255da60391c017eea31555f156".to_string(); let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); - let address = transaction.caller(); + let address = transaction.caller().unwrap(); assert_eq!(expected_address, address); // Check that signing the signed transaction returns the same transaction. let signed_transaction = transaction.sign_transaction(sk); - assert_eq!(transaction, signed_transaction) + assert_eq!(transaction, signed_transaction.unwrap()) } #[test] @@ -981,7 +1016,7 @@ mod test { assert_eq!( EthereumAddress::from("9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f".to_string()), - transaction.caller() + transaction.caller().unwrap() ) } @@ -1011,7 +1046,7 @@ mod test { }; assert_eq!( EthereumAddress::from("af1276cbb260bb13deddb4209ae99ae6e497f446".to_string()), - transaction.caller(), + transaction.caller().unwrap(), "checking caller" ) } @@ -1072,10 +1107,12 @@ mod test { }; // act - let signed = transaction.sign_transaction( - "dcdff53b4f013dbcdc717f89fe3bf4d8b10512aae282b48e01d7530470382701" - .to_string(), - ); + let signed = transaction + .sign_transaction( + "dcdff53b4f013dbcdc717f89fe3bf4d8b10512aae282b48e01d7530470382701" + .to_string(), + ) + .unwrap(); let v = U256::from(37); let r = string_to_h256_unsafe( @@ -1120,10 +1157,12 @@ mod test { "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", ); - let signed = transaction.sign_transaction( - "4646464646464646464646464646464646464646464646464646464646464646" - .to_string(), - ); + let signed = transaction + .sign_transaction( + "4646464646464646464646464646464646464646464646464646464646464646" + .to_string(), + ) + .unwrap(); assert_eq!(v, signed.v, "checking v"); assert_eq!(r, signed.r, "checking r"); @@ -1142,4 +1181,111 @@ mod test { assert_eq!(hex::encode(&encoded), *str); }); } + + #[test] + fn test_decoding_not_eip_155_fails_gracefully() { + // decoding of a transaction that is not eip 155, ie v = 28 / 27 + // initial transaction: + // { + // "nonce": "0x0", + // "gasPrice": "0x10000000000", + // "gasLimit": "0x25000", + // "value": "0x0", + // "data": "0x608060405234801561001057600080fd5b50602a600081905550610150806100286000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea26469706673582212204d6c1853cec27824f5dbf8bcd0994714258d22fc0e0dc8a2460d87c70e3e57a564736f6c63430008120033", + // "chainId": 0 + // } + // private key: 0xe75f4c63daecfbb5be03f65940257f5b15e440e6cf26faa126ce68741d5d0f78 + // caller address: 0x3dbeca6e9a6f0677e3c7b5946fc8adbb1b071e0a + let signed_tx = "f901cc8086010000000000830250008080b90178608060405234801561001057600080fd5b50602a600081905550610150806100286000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea26469706673582212204d6c1853cec27824f5dbf8bcd0994714258d22fc0e0dc8a2460d87c70e3e57a564736f6c634300081200331ca06d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58da0219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1"; + let r = string_to_h256_unsafe( + "6d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58d", + ); + let s = string_to_h256_unsafe( + "219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1", + ); + + let tx = hex::decode(signed_tx).unwrap(); + let decoder = Rlp::new(&tx); + let decoded = EthereumTransactionCommon::decode(&decoder); + assert!(decoded.is_ok(), "testing the decoding went ok"); + let decoded_transaction = decoded.unwrap(); + assert_eq!(U256::from(28), decoded_transaction.v, "testing v"); + assert_eq!(r, decoded_transaction.r, "testing r"); + assert_eq!(s, decoded_transaction.s, "testing s"); + assert!( + decoded_transaction.signature().is_err(), + "testing signature" + ); + assert!(decoded_transaction.caller().is_err(), "testing caller"); + } + + #[test] + fn test_signature_unsigned_fails_gracefully() { + // most data is not relevant here, the point is to test failure mode of signature verification + let transaction = EthereumTransactionCommon { + chain_id: U256::one(), + nonce: U256::from(9), + gas_price: U256::from(20000000000u64), + gas_limit: U256::from(21000), + to: EthereumAddress::from( + "3535353535353535353535353535353535353535".to_string(), + ), + value: U256::from(1000000000000000000u64), + data: vec![], + v: U256::one(), // parity is not consistent with a signed transaction + r: H256::zero(), + s: H256::zero(), + }; + + assert!(transaction.signature().is_err(), "testing invalid parity"); + } + #[test] + fn test_signature_invalid_signature_fails_gracefully() { + // most data is not relevant here, the point is to test failure mode of signature verification + let transaction = EthereumTransactionCommon { + chain_id: U256::one(), + nonce: U256::from(9), + gas_price: U256::from(20000000000u64), + gas_limit: U256::from(21000), + to: EthereumAddress::from( + "3535353535353535353535353535353535353535".to_string(), + ), + value: U256::from(1000000000000000000u64), + data: vec![], + v: U256::from(38), // parity is consistent with a signed transaction + r: H256::zero(), // signature value is wrong + s: H256::zero(), + }; + assert!( + transaction.signature().is_ok(), + "testing signature is well formed" + ); + assert!( + transaction.caller().is_err(), + "testing caller fails, because signature is useless" + ); + } + + #[test] + fn test_signature_invalid_parity_fails_gracefully() { + // most data is not relevant here, the point is to test failure mode of signature verification + let transaction = EthereumTransactionCommon { + chain_id: U256::one(), + nonce: U256::from(9), + gas_price: U256::from(20000000000u64), + gas_limit: U256::from(21000), + to: EthereumAddress::from( + "3535353535353535353535353535353535353535".to_string(), + ), + value: U256::from(1000000000000000000u64), + data: vec![], + v: U256::from(150), // parity value is not consistent with chain_id + r: H256::zero(), + s: H256::zero(), + }; + assert!( + transaction.signature().is_err(), + "testing parity is invalid" + ); + } } diff --git a/src/kernel_evm/evm_execution/src/lib.rs b/src/kernel_evm/evm_execution/src/lib.rs index 1b34a5cd9990..1011a1656701 100644 --- a/src/kernel_evm/evm_execution/src/lib.rs +++ b/src/kernel_evm/evm_execution/src/lib.rs @@ -476,7 +476,8 @@ mod test { let (sk, _address) = tezos_ethereum::signatures::string_to_sk_and_address( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), - ); + ) + .unwrap(); let m: [u8; 32] = hex::decode( "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53", ) @@ -514,7 +515,8 @@ mod test { ]; test_list.iter().fold((), |_, (s, ea)| { let (_, a) = - tezos_ethereum::signatures::string_to_sk_and_address(s.to_string()); + tezos_ethereum::signatures::string_to_sk_and_address(s.to_string()) + .unwrap(); let value: [u8; 20] = hex::decode(ea).unwrap().try_into().unwrap(); let ea = EthereumAddress::from(value); assert_eq!(a, ea); @@ -523,14 +525,16 @@ mod test { #[test] fn test_caller_classic() { - let (_sk, address_from_sk) = tezos_ethereum::signatures::string_to_sk_and_address( - "4646464646464646464646464646464646464646464646464646464646464646" - .to_string(), - ); + let (_sk, address_from_sk) = + tezos_ethereum::signatures::string_to_sk_and_address( + "4646464646464646464646464646464646464646464646464646464646464646" + .to_string(), + ) + .unwrap(); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); - let address = transaction.caller(); + let address = transaction.caller().unwrap(); let expected_address_string: [u8; 20] = hex::decode("9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F") .unwrap() @@ -550,7 +554,7 @@ mod test { "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); let expected_transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); - let transaction = expected_transaction.sign_transaction(string_sk); + let transaction = expected_transaction.sign_transaction(string_sk).unwrap(); assert_eq!(expected_transaction, transaction) } diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 1c17262103bc..29b3ff72379d 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use crate::blueprint::Queue; -use crate::error::Error; +use crate::error::{Error, TransferError}; use crate::helpers::address_to_hash; use crate::inbox::Transaction; use crate::storage; @@ -90,7 +90,9 @@ impl L2Block { fn get_tx_sender( tx: &EthereumTransactionCommon, ) -> Result<(OwnedPath, EthereumAddress), Error> { - let address = tx.caller(); + let address = tx + .caller() + .or(Err(Error::Transfer(TransferError::InvalidSignature)))?; // We reencode in hexadecimal, since the accounts hash are encoded in // hexadecimal in the storage. let hash = address_to_hash(address); @@ -170,7 +172,7 @@ fn apply_transaction( if sender_balance < value || sender_nonce != transaction.tx.nonce - || !transaction.tx.clone().verify_signature() + || transaction.tx.clone().verify_signature().is_err() { Ok(make_receipt( block, -- GitLab From 9e301f2f4d63905cd41a195a79e47dbc17973ced Mon Sep 17 00:00:00 2001 From: pecornilleau Date: Wed, 26 Apr 2023 19:02:32 +0200 Subject: [PATCH 2/4] SORU: EVM: cleanup tests --- src/kernel_evm/ethereum/src/signatures.rs | 399 +++++++--------------- 1 file changed, 127 insertions(+), 272 deletions(-) diff --git a/src/kernel_evm/ethereum/src/signatures.rs b/src/kernel_evm/ethereum/src/signatures.rs index abc8b488cb4b..017ebb4bd8df 100644 --- a/src/kernel_evm/ethereum/src/signatures.rs +++ b/src/kernel_evm/ethereum/src/signatures.rs @@ -350,6 +350,43 @@ impl Into> for EthereumTransactionCommon { mod test { use super::*; + // utility function to just build a standard correct transaction + // extracted from example in EIP 155 standard + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md + // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 + // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 + // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f + // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 + fn basic_eip155_transaction() -> EthereumTransactionCommon { + EthereumTransactionCommon { + chain_id: U256::one(), + nonce: U256::from(9), + gas_price: U256::from(20000000000u64), + gas_limit: U256::from(21000), + to: EthereumAddress::from( + "3535353535353535353535353535353535353535".to_string(), + ), + value: U256::from(1000000000000000000u64), + data: vec![], + v: U256::from(37), + r: string_to_h256_unsafe( + "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", + ), + s: string_to_h256_unsafe( + "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", + ), + } + } + + fn basic_eip155_transaction_unsigned() -> EthereumTransactionCommon { + EthereumTransactionCommon { + v: U256::one(), + r: H256::zero(), + s: H256::zero(), + ..basic_eip155_transaction() + } + } + fn h256_to_string(e: H256) -> String { format!("{:x}", e) } @@ -405,6 +442,7 @@ mod test { #[test] fn test_caller_classic() { + // setup let (_sk, address_from_sk) = string_to_sk_and_address( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), @@ -412,127 +450,65 @@ mod test { .unwrap(); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); - let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); - let address = transaction.caller().unwrap(); + let expected_address_string: [u8; 20] = hex::decode("9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F") .unwrap() .try_into() .unwrap(); let expected_address = EthereumAddress::from(expected_address_string); + + // act + let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); + let address = transaction.caller().unwrap(); + + // assert assert_eq!(expected_address, address); assert_eq!(expected_address, address_from_sk) } #[test] fn test_decoding_eip_155_example_unsigned() { - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - // Consider a transaction with nonce = 9, gasprice = 20 * 10**9, startgas = 21000, to = 0x3535353535353535353535353535353535353535, value = 10**18, data='' (empty) - // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 - // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 - // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f - // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 - let nonce = U256::from(9); - let gas_price = U256::from(20000000000u64); - let gas_limit = U256::from(21000); - let to = - EthereumAddress::from("3535353535353535353535353535353535353535".to_string()); - assert_ne!( - to, - EthereumAddress::from_u64_be(0), - "making sure the expected address is correct" - ); - let value = U256::from(1000000000000000000u64); - let data: Vec = vec![]; - let chain_id = U256::one(); - let v = chain_id; - let expected_transaction = EthereumTransactionCommon { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - data, - v, - r: H256::zero(), - s: H256::zero(), - }; - // setup + let expected_transaction = basic_eip155_transaction_unsigned(); let signing_data = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080"; + + // act let tx = hex::decode(signing_data).unwrap(); let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); assert!(decoded.is_ok(), "testing the decoding went ok"); + // assert let decoded_transaction = decoded.unwrap(); - assert_eq!(nonce, decoded_transaction.nonce, "testing nonce"); - assert_eq!( - gas_price, decoded_transaction.gas_price, - "testing gas price" - ); - assert_eq!( - gas_limit, decoded_transaction.gas_limit, - "testing gas limit" - ); - assert_eq!(to, decoded_transaction.to, "testing addresse"); - assert_eq!(value, decoded_transaction.value, "testing value"); - assert_eq!(Vec::::new(), decoded_transaction.data, "testing data"); - assert_eq!(U256::one(), decoded_transaction.v, "testing v"); - assert_eq!(H256::zero(), decoded_transaction.r, "testing r"); - assert_eq!(H256::zero(), decoded_transaction.s, "testing s"); assert_eq!(expected_transaction, decoded_transaction) } + #[test] fn test_decoding_leading0_signature() { // decoding of a transaction where r or s had some leading 0, which where deleted let signed_tx = "f888018506fc23ac00831000009412f142944da31ab85458787aaecaf5e34128619d80a40b7d796e0000000000000000000000000000000000000000000000000000000000000000269f75b1bc94b868a5a047470eae6008602e414d1471c2bbd14b37ffe56b1a85c9a001d9d58bb23af2090742aab9824c916fdc021a91f3e8d36571a5fc55547bc596"; + // act let tx = hex::decode(signed_tx).unwrap(); let decoder = Rlp::new(&tx); let decoded = EthereumTransactionCommon::decode(&decoder); + + // assert assert!(decoded.is_ok(), "testing the decoding went ok"); } + #[test] fn test_encoding_eip155_unsigned() { - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - // Consider a transaction with nonce = 9, gasprice = 20 * 10**9, startgas = 21000, to = 0x3535353535353535353535353535353535353535, value = 10**18, data='' (empty) - // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 - // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 - // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f - // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 - let nonce = U256::from(9); - let gas_price = U256::from(20000000000u64); - let gas_limit = U256::from(21000); - let to = - EthereumAddress::from("3535353535353535353535353535353535353535".to_string()); - let value = U256::from(1000000000000000000u64); - let data: Vec = vec![]; - let chain_id = U256::one(); - let v = chain_id; - let expected_transaction = EthereumTransactionCommon { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - data, - v, - r: H256::zero(), - s: H256::zero(), - }; - // setup + let expected_transaction = basic_eip155_transaction_unsigned(); let signing_data = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080"; - let tx = hex::decode(signing_data).unwrap(); + + // act let encoded = expected_transaction.rlp_bytes(); + // assert assert_eq!(signing_data, hex::encode(&encoded)); - assert_eq!(tx, &encoded[..]); - assert_eq!(tx, encoded.to_vec()); } pub fn string_to_h256_unsafe(s: &str) -> H256 { @@ -543,6 +519,8 @@ mod test { #[test] fn test_encoding_create() { + // setup + // transaction "without to field" // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f @@ -574,56 +552,28 @@ mod test { s, }; - // setup let signed_tx = "f8572e8506c50218ba8304312280843b9aca0082ffff26a0e9637495be4c216a833ef390b1f6798917c8a102ab165c5085cced7ca1f2eb3aa057854e7044a8fee7bccb6a2c32c4229dd9cbacad74350789e0ce75bf40b6f713"; + // act let encoded = expected_transaction.rlp_bytes(); + // assert assert_eq!(signed_tx, hex::encode(&encoded)); } + #[test] fn test_encoding_eip155_signed() { - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - // Consider a transaction with nonce = 9, gasprice = 20 * 10**9, startgas = 21000, to = 0x3535353535353535353535353535353535353535, value = 10**18, data='' (empty) - // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 - // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 - // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f - // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 - let nonce = U256::from(9); - let gas_price = U256::from(20000000000u64); - let gas_limit = U256::from(21000); - let to = - EthereumAddress::from("3535353535353535353535353535353535353535".to_string()); - let value = U256::from(1000000000000000000u64); - let data: Vec = vec![]; - let chain_id = U256::one(); - let v = U256::from(37); - let r = string_to_h256_unsafe( - "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", - ); - let s = string_to_h256_unsafe( - "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", - ); - let expected_transaction = EthereumTransactionCommon { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - data, - v, - r, - s, - }; - // setup + let expected_transaction = basic_eip155_transaction(); let signed_tx = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; + // act let encoded = expected_transaction.rlp_bytes(); + // assert assert_eq!(signed_tx, hex::encode(&encoded)); } + #[test] fn test_decoding_arbitrary_signed() { // arbitrary transaction with data @@ -654,82 +604,31 @@ mod test { r, s, }; + let signed_data = "f90150808509502f900082520894423163e58aabec5daa3dd1130b759d24bef0f6ea8711c37937e08000b8e4deace8f5000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000041bca408a6b4029b42883aeb2c25087cab76cb58000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000002357a49c7d75f600000000000000000000000000000000000000000000000000000000640b5549000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000000025a025dd6c973368c45ddfc17f5148e3f468a2e3f2c51920cbe9556a64942b0ab2eba031da07ce40c24b0a01f46fb2abc028b5ccd70dbd1cb330725323edc49a2a9558"; // act - let signed_data = "f90150808509502f900082520894423163e58aabec5daa3dd1130b759d24bef0f6ea8711c37937e08000b8e4deace8f5000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000041bca408a6b4029b42883aeb2c25087cab76cb58000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000002357a49c7d75f600000000000000000000000000000000000000000000000000000000640b5549000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000000025a025dd6c973368c45ddfc17f5148e3f468a2e3f2c51920cbe9556a64942b0ab2eba031da07ce40c24b0a01f46fb2abc028b5ccd70dbd1cb330725323edc49a2a9558"; let tx = hex::decode(signed_data).unwrap(); let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); // assert assert_eq!(Ok(expected_transaction), decoded) } + #[test] fn test_decoding_eip_155_example_signed() { - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - // Consider a transaction with nonce = 9, gasprice = 20 * 10**9, startgas = 21000, to = 0x3535353535353535353535353535353535353535, value = 10**18, data='' (empty) - // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 - // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 - // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f - // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 - let nonce = U256::from(9); - let gas_price = U256::from(20000000000u64); - let gas_limit = U256::from(21000); - let to = - EthereumAddress::from("3535353535353535353535353535353535353535".to_string()); - assert_ne!( - to, - EthereumAddress::from_u64_be(0), - "making sure the expected address is correct" - ); - let value = U256::from(1000000000000000000u64); - let data: Vec = vec![]; - let v = U256::from(37); - let r = string_to_h256_unsafe( - "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", - ); - let s = string_to_h256_unsafe( - "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", - ); - let expected_transaction = EthereumTransactionCommon { - chain_id: U256::one(), - nonce, - gas_price, - gas_limit, - to, - value, - data, - v, - r, - s, - }; - // setup + let expected_transaction = basic_eip155_transaction(); let signed_data = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; + + // act let tx = hex::decode(signed_data).unwrap(); let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); - assert!(decoded.is_ok(), "testing the decoding went ok"); + // assert + assert!(decoded.is_ok(), "testing the decoding went ok"); let decoded_transaction = decoded.unwrap(); - assert_eq!(nonce, decoded_transaction.nonce, "testing nonce"); - assert_eq!( - gas_price, decoded_transaction.gas_price, - "testing gas price" - ); - assert_eq!( - gas_limit, decoded_transaction.gas_limit, - "testing gas limit" - ); - assert_eq!(to, decoded_transaction.to, "testing addresse"); - assert_eq!(value, decoded_transaction.value, "testing value"); - assert_eq!(Vec::::new(), decoded_transaction.data, "testing data"); - assert_eq!(U256::from(37), decoded_transaction.v, "testing v"); - assert_eq!(r, decoded_transaction.r, "testing r"); - assert_eq!(s, decoded_transaction.s, "testing s"); - assert_eq!(expected_transaction, decoded_transaction) } @@ -774,7 +673,6 @@ mod test { let signed_data = "f903732e8506c50218ba8304312294ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b880a8db2d41b89b009b903043593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000025a0c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45aa05721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06"; let tx = hex::decode(signed_data).unwrap(); let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); // assert @@ -817,12 +715,12 @@ mod test { r, s, }; - - // act let signed_data = "f903732e8506c50218ba8304312294ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b880a8db2d41b89b009b903043593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000025a0c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45aa05721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06"; + // act let encoded = expected_transaction.rlp_bytes(); + // assert assert_eq!(signed_data, hex::encode(&encoded)); } @@ -860,14 +758,14 @@ mod test { "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", ), }; + let signed_data = "f869018506fc23ac0083100000944e1b2c985d729ae6e05ef7974013eeb48f394449843b9aca008026a0bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ada06053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f"; // act - let signed_data = "f869018506fc23ac0083100000944e1b2c985d729ae6e05ef7974013eeb48f394449843b9aca008026a0bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ada06053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f"; let tx = hex::decode(signed_data).unwrap(); let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + // assert assert_eq!(Ok(expected_transaction), decoded); } @@ -907,11 +805,14 @@ mod test { // assert assert_eq!( - EthereumAddress::from("d9e5c94a12f78a96640757ac97ba0c257e8aa262".to_string()), - transaction.caller().unwrap(), + Ok(EthereumAddress::from( + "d9e5c94a12f78a96640757ac97ba0c257e8aa262".to_string() + )), + transaction.caller(), "test field from" ) } + #[test] fn test_signature_ethereum_js() { // private key 0xcb9db6b5878db2fa20586e23b7f7b51c22a7c6ed0530daafc2615b116f170cd3 @@ -927,6 +828,7 @@ mod test { // s: 6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f // tx: 0xf869018506fc23ac0083100000944e1b2c985d729ae6e05ef7974013eeb48f394449843b9aca008026a0bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ada06053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f + // setup let transaction = EthereumTransactionCommon { chain_id: U256::one(), nonce: U256::from(1), @@ -942,6 +844,7 @@ mod test { s: H256::zero(), }; + // act let signed = transaction .sign_transaction( "cb9db6b5878db2fa20586e23b7f7b51c22a7c6ed0530daafc2615b116f170cd3" @@ -949,6 +852,7 @@ mod test { ) .unwrap(); + // assert let v = U256::from(38); let r = string_to_h256_unsafe( "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", @@ -988,32 +892,8 @@ mod test { } #[test] - fn test_call_eip155_example() { - // example directly lifted from eip155 description - // signing data 0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080 - // private key : 0x4646464646464646464646464646464646464646464646464646464646464646 - // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f - // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 let nonce = U256::from(9); - - let transaction = EthereumTransactionCommon { - chain_id: U256::one(), - nonce: U256::from(9), - gas_price: U256::from(20000000000u64), - gas_limit: U256::from(21000), - to: EthereumAddress::from( - "3535353535353535353535353535353535353535".to_string(), - ), - value: U256::from(1000000000000000000u64), - data: vec![], - v: U256::from(37), - r: string_to_h256_unsafe( - "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", - ), - s: string_to_h256_unsafe( - "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", - ), - }; - + fn test_caller_eip155_example() { + let transaction = basic_eip155_transaction(); assert_eq!( EthereumAddress::from("9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f".to_string()), transaction.caller().unwrap() @@ -1024,6 +904,7 @@ mod test { fn test_caller_uniswap_inspired() { // inspired by 0xf598016f51e0544187088ddd50fd37818fd268a0363a17281576425f3ee334cb + // setup let data: Vec = hex::decode("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000").unwrap(); let transaction = EthereumTransactionCommon { @@ -1044,9 +925,13 @@ mod test { "5721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06", ), }; + + // check assert_eq!( - EthereumAddress::from("af1276cbb260bb13deddb4209ae99ae6e497f446".to_string()), - transaction.caller().unwrap(), + Ok(EthereumAddress::from( + "af1276cbb260bb13deddb4209ae99ae6e497f446".to_string() + )), + transaction.caller(), "checking caller" ) } @@ -1055,6 +940,7 @@ mod test { fn test_message_uniswap_inspired() { // inspired by 0xf598016f51e0544187088ddd50fd37818fd268a0363a17281576425f3ee334cb + // setup let data: Vec = hex::decode("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000").unwrap(); let transaction = EthereumTransactionCommon { @@ -1072,6 +958,7 @@ mod test { s: H256::zero(), }; + // check assert_eq!( Message::parse_slice( &hex::decode( @@ -1114,6 +1001,7 @@ mod test { ) .unwrap(); + // assert let v = U256::from(37); let r = string_to_h256_unsafe( "c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45a", @@ -1135,38 +1023,18 @@ mod test { // corresponding address 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f // signed tx : 0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83 let nonce = U256::from(9); - let transaction = EthereumTransactionCommon { - chain_id: U256::one(), - nonce: U256::from(9), - gas_price: U256::from(20000000000u64), - gas_limit: U256::from(21000), - to: EthereumAddress::from( - "3535353535353535353535353535353535353535".to_string(), - ), - value: U256::from(1000000000000000000u64), - data: vec![], - v: U256::one(), - r: H256::zero(), - s: H256::zero(), - }; - let v = U256::from(37); - let r = string_to_h256_unsafe( - "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", - ); - let s = string_to_h256_unsafe( - "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", - ); + // setup + let transaction = basic_eip155_transaction_unsigned(); + let expected_signed = basic_eip155_transaction(); - let signed = transaction - .sign_transaction( - "4646464646464646464646464646464646464646464646464646464646464646" - .to_string(), - ) - .unwrap(); + // act + let signed = transaction.sign_transaction( + "4646464646464646464646464646464646464646464646464646464646464646" + .to_string(), + ); - assert_eq!(v, signed.v, "checking v"); - assert_eq!(r, signed.r, "checking r"); - assert_eq!(s, signed.s, "checking s"); + // assert + assert_eq!(Ok(expected_signed), signed, "checking signed transaction") } #[test] @@ -1196,6 +1064,8 @@ mod test { // } // private key: 0xe75f4c63daecfbb5be03f65940257f5b15e440e6cf26faa126ce68741d5d0f78 // caller address: 0x3dbeca6e9a6f0677e3c7b5946fc8adbb1b071e0a + + // setup let signed_tx = "f901cc8086010000000000830250008080b90178608060405234801561001057600080fd5b50602a600081905550610150806100286000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea26469706673582212204d6c1853cec27824f5dbf8bcd0994714258d22fc0e0dc8a2460d87c70e3e57a564736f6c634300081200331ca06d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58da0219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1"; let r = string_to_h256_unsafe( "6d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58d", @@ -1204,14 +1074,19 @@ mod test { "219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1", ); + // act let tx = hex::decode(signed_tx).unwrap(); let decoder = Rlp::new(&tx); let decoded = EthereumTransactionCommon::decode(&decoder); + + // sanity check assert!(decoded.is_ok(), "testing the decoding went ok"); let decoded_transaction = decoded.unwrap(); assert_eq!(U256::from(28), decoded_transaction.v, "testing v"); assert_eq!(r, decoded_transaction.r, "testing r"); assert_eq!(s, decoded_transaction.s, "testing s"); + + // check signature fails gracefully assert!( decoded_transaction.signature().is_err(), "testing signature" @@ -1223,43 +1098,30 @@ mod test { fn test_signature_unsigned_fails_gracefully() { // most data is not relevant here, the point is to test failure mode of signature verification let transaction = EthereumTransactionCommon { - chain_id: U256::one(), - nonce: U256::from(9), - gas_price: U256::from(20000000000u64), - gas_limit: U256::from(21000), - to: EthereumAddress::from( - "3535353535353535353535353535353535353535".to_string(), - ), - value: U256::from(1000000000000000000u64), - data: vec![], v: U256::one(), // parity is not consistent with a signed transaction - r: H256::zero(), - s: H256::zero(), + ..basic_eip155_transaction() }; + // check signature fails gracefully assert!(transaction.signature().is_err(), "testing invalid parity"); } + #[test] fn test_signature_invalid_signature_fails_gracefully() { // most data is not relevant here, the point is to test failure mode of signature verification let transaction = EthereumTransactionCommon { - chain_id: U256::one(), - nonce: U256::from(9), - gas_price: U256::from(20000000000u64), - gas_limit: U256::from(21000), - to: EthereumAddress::from( - "3535353535353535353535353535353535353535".to_string(), - ), - value: U256::from(1000000000000000000u64), - data: vec![], v: U256::from(38), // parity is consistent with a signed transaction r: H256::zero(), // signature value is wrong - s: H256::zero(), + ..basic_eip155_transaction() }; + + // sanity check assert!( transaction.signature().is_ok(), "testing signature is well formed" ); + + // check caller fails gracefully assert!( transaction.caller().is_err(), "testing caller fails, because signature is useless" @@ -1270,19 +1132,12 @@ mod test { fn test_signature_invalid_parity_fails_gracefully() { // most data is not relevant here, the point is to test failure mode of signature verification let transaction = EthereumTransactionCommon { + v: U256::from(150), // parity is not consistent with chain_id chain_id: U256::one(), - nonce: U256::from(9), - gas_price: U256::from(20000000000u64), - gas_limit: U256::from(21000), - to: EthereumAddress::from( - "3535353535353535353535353535353535353535".to_string(), - ), - value: U256::from(1000000000000000000u64), - data: vec![], - v: U256::from(150), // parity value is not consistent with chain_id - r: H256::zero(), - s: H256::zero(), + ..basic_eip155_transaction() }; + + // check signature fails gracefully assert!( transaction.signature().is_err(), "testing parity is invalid" -- GitLab From 78fcc90c528697a4f8081e59f164047539676125 Mon Sep 17 00:00:00 2001 From: pecornilleau Date: Wed, 26 Apr 2023 21:22:43 +0200 Subject: [PATCH 3/4] SORU: EVM: add U256 overflow checks --- src/kernel_evm/ethereum/src/signatures.rs | 85 ++++++++++++++++++----- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/src/kernel_evm/ethereum/src/signatures.rs b/src/kernel_evm/ethereum/src/signatures.rs index 017ebb4bd8df..efbc3244892f 100644 --- a/src/kernel_evm/ethereum/src/signatures.rs +++ b/src/kernel_evm/ethereum/src/signatures.rs @@ -31,24 +31,24 @@ pub enum TransactionError { DecoderError(#[from] DecoderError), #[error("Error extracting a slice")] - SlicingError(), + SlicingError, #[error("Error manipulating ECDSA key")] - ECDSAError(), + ECDSAError, #[error("Error recomputing parity of signature")] - Parity(), + Parity, } impl From for TransactionError { fn from(_: libsecp256k1::Error) -> Self { - Self::ECDSAError() + Self::ECDSAError } } impl From for TransactionError { fn from(_: TryFromSliceError) -> Self { - Self::SlicingError() + Self::SlicingError } } /// produces address from a secret key @@ -56,7 +56,7 @@ pub fn string_to_sk_and_address( s: String, ) -> Result<(SecretKey, EthereumAddress), TransactionError> { let mut data: [u8; 32] = [0u8; 32]; - let () = hex::decode_to_slice(s, &mut data)?; + hex::decode_to_slice(s, &mut data)?; let sk = SecretKey::parse(&data)?; let pk = PublicKey::from_secret_key(&sk); let serialised = &pk.serialize()[1..]; @@ -132,6 +132,15 @@ impl EthereumTransactionCommon { Message::parse(&hash) } + /// recompute parity from v and chain_id + fn compute_parity(&self) -> Option { + let chain_id_encoding = self + .chain_id + .checked_mul(U256::from(2))? + .checked_add(U256::from(35))?; + self.v.checked_sub(chain_id_encoding) + } + /// Extracts the signature from an EthereumTransactionCommon pub fn signature(&self) -> Result<(Signature, RecoveryId), TransactionError> { // copy r to Scalar @@ -145,10 +154,8 @@ impl EthereumTransactionCommon { let mut s = Scalar([0; 8]); let _ = s.set_b32(&s1); // recompute parity from v and chain_id - let ri_val = - U256::checked_sub(self.v, self.chain_id * U256::from(2) + U256::from(35)) - .ok_or(TransactionError::Parity()); - let ri = RecoveryId::parse(ri_val?.byte(0))?; + let ri_val = self.compute_parity().ok_or(TransactionError::Parity)?; + let ri = RecoveryId::parse(ri_val.byte(0))?; Ok((Signature { r, s }, ri)) } /// Find the caller address from r and s of the common data @@ -165,6 +172,21 @@ impl EthereumTransactionCommon { Ok(EthereumAddress::from(value)) } + + /// compute v from parity and chain_id + fn compute_v(&self, parity: u8) -> Option { + if self.chain_id == U256::zero() { + // parity is 0 or 1 + Some((27 + parity).into()) + } else { + let chain_id_encoding = self + .chain_id + .checked_mul(U256::from(2))? + .checked_add(U256::from(35))?; + U256::from(parity).checked_add(chain_id_encoding) + } + } + ///produce a signed EthereumTransactionCommon. If the initial one was signed /// you should get the same thing. pub fn sign_transaction(&self, string_sk: String) -> Result { @@ -176,11 +198,7 @@ impl EthereumTransactionCommon { let (r, s) = (H256::from(r.b32()), H256::from(s.b32())); let parity: u8 = ri.into(); - let v = if self.chain_id == U256::zero() { - (27 + parity).into() - } else { - U256::from(parity) + U256::from(2) * self.chain_id + U256::from(35) - }; + let v = self.compute_v(parity).ok_or(TransactionError::Parity)?; Ok(EthereumTransactionCommon { v, r, @@ -1140,7 +1158,42 @@ mod test { // check signature fails gracefully assert!( transaction.signature().is_err(), - "testing parity is invalid" + "testing signature checking fails gracefully" + ); + } + + #[test] + fn test_signature_invalid_chain_id_fails_gracefully() { + // most data is not relevant here, the point is to test failure mode of signature verification + let transaction = EthereumTransactionCommon { + chain_id: U256::max_value(), // chain_id will overflow parity computation + ..basic_eip155_transaction() + }; + + // check signature fails gracefully + assert!( + transaction.signature().is_err(), + "testing signature checking fails gracefully" + ); + } + + #[test] + fn test_sign_invalid_chain_id_fails_gracefully() { + // most data is not relevant here, the point is to test failure mode of signature verification + let transaction = EthereumTransactionCommon { + chain_id: U256::max_value(), + ..basic_eip155_transaction_unsigned() + }; + + // check signature fails gracefully + assert!( + transaction + .sign_transaction( + "4646464646464646464646464646464646464646464646464646464646464646" + .to_string() + ) + .is_err(), + "testing signature fails gracefully" ); } } -- GitLab From 8e2cbcffe9682afda8d0ba40314ad8d54a325a9e Mon Sep 17 00:00:00 2001 From: pecornilleau Date: Tue, 2 May 2023 13:42:02 +0200 Subject: [PATCH 4/4] SORU: EVM: more info to error --- src/kernel_evm/ethereum/src/signatures.rs | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/kernel_evm/ethereum/src/signatures.rs b/src/kernel_evm/ethereum/src/signatures.rs index efbc3244892f..821297a7a296 100644 --- a/src/kernel_evm/ethereum/src/signatures.rs +++ b/src/kernel_evm/ethereum/src/signatures.rs @@ -22,27 +22,35 @@ use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpIterator, RlpStream}; use sha3::{Digest, Keccak256}; use thiserror::Error; +#[derive(Error, Debug, PartialEq)] +pub enum ParityError { + #[error("Couldn't reconstruct V from chain_id: {0}")] + ChainId(U256), + + #[error("Couldn't reconstruct parity from V: {0}")] + V(U256), +} #[derive(Error, Debug, PartialEq)] pub enum TransactionError { - #[error("Error reading a hex string")] + #[error("Error reading a hex string: {0}")] HexError(#[from] FromHexError), - #[error("Error decoding RLP encoded byte array")] + #[error("Error decoding RLP encoded byte array: {0}")] DecoderError(#[from] DecoderError), #[error("Error extracting a slice")] SlicingError, - #[error("Error manipulating ECDSA key")] - ECDSAError, + #[error("Error manipulating ECDSA key: {0}")] + ECDSAError(libsecp256k1::Error), - #[error("Error recomputing parity of signature")] - Parity, + #[error("Error recomputing parity of signature: {0}")] + Parity(ParityError), } impl From for TransactionError { - fn from(_: libsecp256k1::Error) -> Self { - Self::ECDSAError + fn from(e: libsecp256k1::Error) -> Self { + Self::ECDSAError(e) } } @@ -154,7 +162,9 @@ impl EthereumTransactionCommon { let mut s = Scalar([0; 8]); let _ = s.set_b32(&s1); // recompute parity from v and chain_id - let ri_val = self.compute_parity().ok_or(TransactionError::Parity)?; + let ri_val = self + .compute_parity() + .ok_or(TransactionError::Parity(ParityError::V(self.v)))?; let ri = RecoveryId::parse(ri_val.byte(0))?; Ok((Signature { r, s }, ri)) } @@ -198,7 +208,9 @@ impl EthereumTransactionCommon { let (r, s) = (H256::from(r.b32()), H256::from(s.b32())); let parity: u8 = ri.into(); - let v = self.compute_v(parity).ok_or(TransactionError::Parity)?; + let v = self.compute_v(parity).ok_or(TransactionError::Parity( + ParityError::ChainId(self.chain_id), + ))?; Ok(EthereumTransactionCommon { v, r, -- GitLab