diff --git a/src/kernel_evm/CHANGES.md b/src/kernel_evm/CHANGES.md index ab9919405a95672970124bb57bccd2e0be59dc3f..d6012eb12fe9c6a37c85cdbe23658b3a5f3487cb 100644 --- a/src/kernel_evm/CHANGES.md +++ b/src/kernel_evm/CHANGES.md @@ -50,3 +50,5 @@ - Upgrade to Kernel SDK 0.2.1 (!9417, !9526) - Porting benchmark framework to the EVM Kernel. (!9529, !9524, !9545) - Reorganized dependencies at the workspace level. (!9522) +- Refactor `TransactionCommon` and transactions' signatures modules. (!9590) + diff --git a/src/kernel_evm/ethereum/src/lib.rs b/src/kernel_evm/ethereum/src/lib.rs index 07ea24a25a5ea18903f70104ff66c73ade52b5f2..84c473e02d1360c010bbf723faa1343105d20d38 100644 --- a/src/kernel_evm/ethereum/src/lib.rs +++ b/src/kernel_evm/ethereum/src/lib.rs @@ -5,6 +5,7 @@ pub mod block; pub mod eth_gen; pub mod rlp_helpers; -pub mod signatures; pub mod transaction; +pub mod tx_common; +pub mod tx_signature; pub mod wei; diff --git a/src/kernel_evm/ethereum/src/transaction.rs b/src/kernel_evm/ethereum/src/transaction.rs index cf7b990c06afac0682f780132c6cf1f9a525bb37..590b8331075b8e6f7580db52aaa608f9fab1c789 100644 --- a/src/kernel_evm/ethereum/src/transaction.rs +++ b/src/kernel_evm/ethereum/src/transaction.rs @@ -2,7 +2,10 @@ // // SPDX-License-Identifier: MIT -use crate::rlp_helpers::*; +use crate::{ + rlp_helpers::*, + tx_signature::{rlp_append_opt, rlp_decode_opt, TxSignature}, +}; use primitive_types::{H160, H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; @@ -191,7 +194,7 @@ impl TryFrom<&[u8]> for TransactionReceipt { } } -/// Transaction receipt, https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash +/// Transaction object, https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash /// There a lot of redundancy between a transaction object and a transaction /// receipt. In fact, transaction objects should not be stored in the kernel /// but rather in the EVM node. Duplicating the code instead of sharing fields @@ -221,12 +224,8 @@ pub struct TransactionObject { pub index: u32, /// Value transferred in Wei. pub value: U256, - /// ECDSA recovery id - pub v: U256, - /// ECDSA signature r - pub r: H256, - /// ECDSA signature s - pub s: H256, + /// ECDSA signature + pub signature: Option, } impl Decodable for TransactionObject { @@ -246,9 +245,7 @@ impl Decodable for TransactionObject { let to: Option = decode_option(&next(&mut it)?, "to")?; let index: u32 = decode_field(&next(&mut it)?, "index")?; let value: U256 = decode_field_u256_le(&next(&mut it)?, "value")?; - let v: U256 = decode_field_u256_le(&next(&mut it)?, "v")?; - let r: H256 = decode_field_h256(&next(&mut it)?, "r")?; - let s: H256 = decode_field_h256(&next(&mut it)?, "s")?; + let signature = rlp_decode_opt(&mut it)?; Ok(Self { block_hash, block_number, @@ -261,9 +258,7 @@ impl Decodable for TransactionObject { to, index, value, - v, - r, - s, + signature, }) } else { Err(DecoderError::RlpIncorrectListLen) @@ -291,9 +286,7 @@ impl Encodable for TransactionObject { }; stream.append(&self.index); append_u256_le(stream, self.value); - append_u256_le(stream, self.v); - stream.append(&self.r); - stream.append(&self.s); + rlp_append_opt(&self.signature, stream); } } @@ -372,13 +365,21 @@ mod test { to: Some(address_of_str("3635353535353535353535353535353535353536")), index: 15u32, value: U256::from(0), - v: U256::from(1337), - r: H256::from_low_u64_be(0), - s: H256::from_low_u64_be(1), + signature: Some( + TxSignature::new( + U256::from(1337), + H256::from_low_u64_be(1), + H256::from_low_u64_be(2), + ) + .unwrap(), + ), }; object_encoding_roundtrip(v.clone()); - let v1 = TransactionObject { to: None, ..v }; + let v1 = TransactionObject { + to: None, + ..v.clone() + }; object_encoding_roundtrip(v1); let v2 = TransactionObject { diff --git a/src/kernel_evm/ethereum/src/signatures.rs b/src/kernel_evm/ethereum/src/tx_common.rs similarity index 82% rename from src/kernel_evm/ethereum/src/signatures.rs rename to src/kernel_evm/ethereum/src/tx_common.rs index 3d639b661c381779a0c51a6aefa882c3e64922b1..e6dcc526b869299b63de3b613f29e65be6356a45 100644 --- a/src/kernel_evm/ethereum/src/signatures.rs +++ b/src/kernel_evm/ethereum/src/tx_common.rs @@ -11,52 +11,33 @@ use std::array::TryFromSliceError; -use hex::FromHexError; -use libsecp256k1::{ - curve::Scalar, recover, sign, verify, Message, PublicKey, RecoveryId, SecretKey, - Signature, -}; +use libsecp256k1::{recover, Message, RecoveryId, Signature}; use primitive_types::{H160, H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; use sha3::{Digest, Keccak256}; use thiserror::Error; -use crate::rlp_helpers::{ - append_h256, append_option, append_vec, decode_field, decode_field_h256, - decode_option, next, +use crate::{ + rlp_helpers::{ + append_h256, append_option, append_vec, decode_field, decode_field_h256, + decode_option, next, + }, + tx_signature::{TxSigError, TxSignature}, }; -#[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 SigError { - #[error("Error reading a hex string: {0}")] - HexError(#[from] FromHexError), - #[error("Error decoding RLP encoded byte array: {0}")] DecoderError(#[from] DecoderError), #[error("Error extracting a slice")] SlicingError, - #[error("Error manipulating ECDSA key: {0}")] - ECDSAError(libsecp256k1::Error), + #[error("Signature error: {0}")] + TxSigError(TxSigError), - #[error("Error recomputing parity of signature: {0}")] - Parity(ParityError), -} - -impl From for SigError { - fn from(e: libsecp256k1::Error) -> Self { - Self::ECDSAError(e) - } + #[error("Transaction doesn't have a signature")] + UnsignedTransactionError, } impl From for SigError { @@ -65,34 +46,18 @@ impl From for SigError { } } -/// produces address from a secret key -pub fn string_to_sk_and_address(s: String) -> Result<(SecretKey, H160), SigError> { - let mut data: [u8; 32] = [0u8; 32]; - 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 mut value: [u8; 20] = [0u8; 20]; - value.copy_from_slice(&kec[12..]); - Ok((sk, value.into())) -} - -/// the type of a transaction -#[derive(Debug, PartialEq, Eq, Clone)] - -pub enum EthereumTransactionType { - /// transfer - EthereumTransfer, - ///create - EthereumCreate, - /// call - EthereumCall, +impl From for SigError { + fn from(e: TxSigError) -> Self { + SigError::TxSigError(e) + } } -/// Data common to all Ethereum transaction types +/// Data common for all kind of Ethereum transactions +/// (transfers, contract creation and contract invocation). +/// All transaction versions (Legacy, EIP-2930 and EIP-1559) +/// are parsed to this common type. +/// This type is common for both signed and unsigned transactions as well. #[derive(Debug, PartialEq, Eq, Clone)] - pub struct EthereumTransactionCommon { /// the id of the chain /// see `` for values @@ -120,63 +85,33 @@ pub struct EthereumTransactionCommon { pub value: U256, /// the transaction data. In principle this can be large pub data: Vec, - /// Signature x-axis part of point on elliptic curve. See yellow paper, appendix F - pub r: H256, - /// Signature, See yellow paper appendix F - pub s: H256, - /// the parity (recovery id) of the signature See yellow paper appendix F - /// used to recompute chain_id is applicable + /// If transaction is unsigned then this field is None /// See encoding details in - pub v: U256, + pub signature: Option, } impl EthereumTransactionCommon { /// Extracts the Keccak encoding of a message from an EthereumTransactionCommon - pub fn message(&self) -> Message { + fn message(&self) -> Message { let to_sign = EthereumTransactionCommon { - v: self.chain_id, - r: H256::zero(), - s: H256::zero(), + signature: None, ..self.clone() }; - let bytes = to_sign.rlp_bytes(); + + let bytes = to_sign.to_bytes(); let hash: [u8; 32] = Keccak256::digest(bytes).into(); 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), SigError> { - // copy r to Scalar - let r: H256 = self.r; - let r1: [u8; 32] = r.into(); - let mut r = Scalar([0; 8]); - let _ = r.set_b32(&r1); - // copy s to Scalar - let s: H256 = self.s; - let s1: [u8; 32] = s.into(); - let mut s = Scalar([0; 8]); - let _ = s.set_b32(&s1); - if s.is_high() { - // if s > secp256k1n / 2 the signature is invalid - // cf EIP2 (part 2) https://eips.ethereum.org/EIPS/eip-2 - Err(SigError::ECDSAError(libsecp256k1::Error::InvalidSignature)) - } else { - // recompute parity from v and chain_id - let ri_val = self - .compute_parity() - .ok_or(SigError::Parity(ParityError::V(self.v)))?; - let ri = RecoveryId::parse(ri_val.byte(0))?; - Ok((Signature { r, s }, ri)) - } + let tx_signature = self + .signature + .as_ref() + .ok_or(SigError::UnsignedTransactionError)?; + tx_signature + .signature(self.chain_id) + .map_err(SigError::TxSigError) } /// Find the caller address from r and s of the common data @@ -186,7 +121,7 @@ impl EthereumTransactionCommon { pub fn caller(&self) -> Result { let mes = self.message(); let (sig, ri) = self.signature()?; - let pk = recover(&mes, &sig, &ri)?; + let pk = recover(&mes, &sig, &ri).map_err(TxSigError::ECDSAError)?; let serialised = &pk.serialize()[1..]; let kec = Keccak256::digest(serialised); let value: [u8; 20] = kec.as_slice()[12..].try_into()?; @@ -194,70 +129,49 @@ impl EthereumTransactionCommon { Ok(value.into()) } - /// 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 { - 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; - let (r, s) = (H256::from(r.b32()), H256::from(s.b32())); - - let parity: u8 = ri.into(); - let v = self - .compute_v(parity) - .ok_or(SigError::Parity(ParityError::ChainId(self.chain_id)))?; + let signature = TxSignature::sign_legacy(&mes, string_sk, self.chain_id)?; + Ok(EthereumTransactionCommon { - v, - r, - s, + signature: Some(signature), ..self.clone() }) } - /// verifies the signature - pub fn verify_signature(self) -> Result { - let mes = self.message(); - let (sig, ri) = self.signature()?; - let pk = recover(&mes, &sig, &ri)?; - Ok(verify(&mes, &sig, &pk)) - } - - /// Unserialize bytes as a RLP encoded legacy transaction. - pub fn from_rlp_bytes( - bytes: &[u8], - ) -> Result { + // Unserialize Ethereum tx of arbitrary version from raw bytes. + // This is a separate method of the tx type + // but not rlp::Decodable instance because after legacy + // version a tx encoding, strictly speaking, is not RLP list anymore, + // rather opaque sequence of bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { let decoder = Rlp::new(bytes); EthereumTransactionCommon::decode(&decoder) } /// Unserialize an hex string as a RLP encoded legacy transaction. - pub fn from_rlp(e: String) -> Result { + pub fn from_hex(e: String) -> Result { let tx = hex::decode(e).or(Err(DecoderError::Custom("Couldn't parse hex value")))?; - Self::from_rlp_bytes(&tx) + Self::from_bytes(&tx) + } + + // Serialize Ethereum tx of arbitrary version to raw bytes. + // This is a separate method of the tx type + // but not rlp::Encodable instance because after legacy + // version a tx encoding, strictly speaking, is not RLP list anymore, + // rather opaque sequence of bytes. + pub fn to_bytes(self) -> Vec { + self.rlp_bytes().into() } } impl From for EthereumTransactionCommon { /// Decode a transaction in hex format. Unsafe, to be used only in tests : panics when fails fn from(e: String) -> Self { - EthereumTransactionCommon::from_rlp(e).unwrap() + EthereumTransactionCommon::from_hex(e).unwrap() } } @@ -265,7 +179,7 @@ impl TryFrom<&[u8]> for EthereumTransactionCommon { type Error = DecoderError; fn try_from(bytes: &[u8]) -> Result { - Self::from_rlp_bytes(bytes) + Self::from_bytes(bytes) } } @@ -283,13 +197,34 @@ impl Decodable for EthereumTransactionCommon { let v: U256 = decode_field(&next(&mut it)?, "v")?; let r: H256 = decode_field_h256(&next(&mut it)?, "r")?; let s: H256 = decode_field_h256(&next(&mut it)?, "s")?; - // in a rlp encoded unsigned eip-155 transaction, v is used to store the chainid - // in a rlp encoded signed eip-155 transaction, v is {0,1} + CHAIN_ID * 2 + 35 - let chain_id: U256 = if v > U256::from(35) { - (v - U256::from(35)) / U256::from(2) + + let is_unsigned = r == H256::zero() && s == H256::zero(); + let chain_id = if is_unsigned { + // in a rlp encoded unsigned eip-155 transaction, v is used to store the chainid + Ok(v) + } else { + // It's a **signed eip-155 transaction** with 9 fields, + // it means v has to be {0,1} + CHAIN_ID * 2 + 35 according to + // https://eips.ethereum.org/EIPS/eip-155 + // v > 36 (not v > 35) because we support only chain_id which is strictly greater than 0 + if v > U256::from(36) { + Ok((v - 35) / 2) + } else { + Err(DecoderError::Custom( + "v has to be greater than 36 for a signed EIP-155 transaction", + )) + } + }?; + + let signature = if is_unsigned { + None } else { - v + Some( + TxSignature::new(v, r, s) + .map_err(|_| DecoderError::Custom("Invalid signature"))?, + ) }; + Ok(Self { chain_id, nonce, @@ -298,9 +233,7 @@ impl Decodable for EthereumTransactionCommon { to, value, data, - v, - r, - s, + signature, }) } else { Err(DecoderError::RlpIncorrectListLen) @@ -320,26 +253,55 @@ impl Encodable for EthereumTransactionCommon { append_option(stream, self.to); stream.append(&self.value); append_vec(stream, self.data.clone()); - stream.append(&self.v); - append_h256(stream, self.r); - append_h256(stream, self.s); + match self.signature { + None => { + stream.append(&self.chain_id); + append_h256(stream, H256::zero()); + append_h256(stream, H256::zero()); + } + Some(ref sig) => Encodable::rlp_append(sig, stream), + } } } #[allow(clippy::from_over_into)] impl Into> for EthereumTransactionCommon { fn into(self) -> Vec { - self.rlp_bytes().to_vec() + self.to_bytes() } } +// Produces address from a secret key +// Used in tests only +pub fn string_to_sk_and_address_unsafe( + s: String, +) -> (libsecp256k1::SecretKey, primitive_types::H160) { + use libsecp256k1::PublicKey; + use libsecp256k1::SecretKey; + + let mut data: [u8; 32] = [0u8; 32]; + hex::decode_to_slice(s, &mut data).unwrap(); + let sk = SecretKey::parse(&data).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + let serialised = &pk.serialize()[1..]; + let kec = Keccak256::digest(serialised); + let mut value: [u8; 20] = [0u8; 20]; + value.copy_from_slice(&kec[12..]); + (sk, value.into()) +} + // cargo test ethereum::signatures::test --features testing #[cfg(test)] mod test { + use std::ops::Neg; + use libsecp256k1::curve::Scalar; + use crate::rlp_helpers::decode_h256; + use crate::tx_signature::TxSignature; + use super::*; fn address_from_str(s: &str) -> Option { let data = &hex::decode(s).unwrap(); @@ -362,21 +324,21 @@ mod test { to: address_from_str("3535353535353535353535353535353535353535"), value: U256::from(1000000000000000000u64), data: vec![], - v: U256::from(37), - r: string_to_h256_unsafe( - "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", - ), - s: string_to_h256_unsafe( - "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", - ), + signature: Some(TxSignature::new_unsafe( + 37, + string_to_h256_unsafe( + "28EF61340BD939BC2195FE537567866003E1A15D3C71FF63E1590620AA636276", + ), + string_to_h256_unsafe( + "67CBE9D8997F761AECB703304B3800CCF555C9F3DC64214B297FB1966A3B6D83", + ), + )), } } fn basic_eip155_transaction_unsigned() -> EthereumTransactionCommon { EthereumTransactionCommon { - v: U256::one(), - r: H256::zero(), - s: H256::zero(), + signature: None, ..basic_eip155_transaction() } } @@ -437,11 +399,10 @@ mod test { #[test] fn test_caller_classic() { // setup - let (_sk, address_from_sk) = string_to_sk_and_address( + let (_sk, address_from_sk) = string_to_sk_and_address_unsafe( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), - ) - .unwrap(); + ); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); @@ -449,7 +410,7 @@ mod test { address_from_str("9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").unwrap(); // act - let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); + let transaction = EthereumTransactionCommon::from_hex(encoded).unwrap(); let address = transaction.caller().unwrap(); // assert @@ -465,8 +426,7 @@ mod test { // act let tx = hex::decode(signing_data).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); assert!(decoded.is_ok(), "testing the decoding went ok"); // assert @@ -481,8 +441,7 @@ mod test { // act let tx = hex::decode(signed_tx).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert!(decoded.is_ok(), "testing the decoding went ok"); @@ -495,10 +454,10 @@ mod test { let signing_data = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080"; // act - let encoded = expected_transaction.rlp_bytes(); + let encoded = expected_transaction.to_bytes(); // assert - assert_eq!(signing_data, hex::encode(&encoded)); + assert_eq!(signing_data, hex::encode(encoded)); } pub fn string_to_h256_unsafe(s: &str) -> H256 { @@ -519,7 +478,6 @@ mod test { let value = U256::from(1000000000u64); let data: Vec = hex::decode("ffff").unwrap(); let chain_id = U256::one(); - let v = U256::from(38); let r = string_to_h256_unsafe( "e9637495be4c216a833ef390b1f6798917c8a102ab165c5085cced7ca1f2eb3a", ); @@ -534,9 +492,7 @@ mod test { to, value, data, - v, - r, - s, + signature: Some(TxSignature::new_unsafe(38, r, s)), } } @@ -547,10 +503,10 @@ mod test { let expected_encoded = "f8572e8506c50218ba8304312280843b9aca0082ffff26a0e9637495be4c216a833ef390b1f6798917c8a102ab165c5085cced7ca1f2eb3aa057854e7044a8fee7bccb6a2c32c4229dd9cbacad74350789e0ce75bf40b6f713"; // act - let encoded = transaction.rlp_bytes(); + let encoded = transaction.to_bytes(); // assert - assert_eq!(expected_encoded, hex::encode(&encoded)); + assert_eq!(expected_encoded, hex::encode(encoded)); } #[test] @@ -561,8 +517,7 @@ mod test { // act let tx = hex::decode(signed_tx).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert!(decoded.is_ok()); @@ -576,10 +531,10 @@ mod test { let signed_tx = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; // act - let encoded = expected_transaction.rlp_bytes(); + let encoded = expected_transaction.to_bytes(); // assert - assert_eq!(signed_tx, hex::encode(&encoded)); + assert_eq!(signed_tx, hex::encode(encoded)); } #[test] @@ -592,7 +547,6 @@ mod test { let to = address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"); let value = U256::from(5000000000000000u64); let data: Vec = hex::decode("deace8f5000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000041bca408a6b4029b42883aeb2c25087cab76cb58000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000002357a49c7d75f600000000000000000000000000000000000000000000000000000000640b5549000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d5640000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let v = U256::from(37); let r = string_to_h256_unsafe( "25dd6c973368c45ddfc17f5148e3f468a2e3f2c51920cbe9556a64942b0ab2eb", ); @@ -607,16 +561,13 @@ mod test { to, value, data, - v, - r, - s, + signature: Some(TxSignature::new_unsafe(37, r, s)), }; let signed_data = "f90150808509502f900082520894423163e58aabec5daa3dd1130b759d24bef0f6ea8711c37937e08000b8e4deace8f5000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000041bca408a6b4029b42883aeb2c25087cab76cb58000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000002357a49c7d75f600000000000000000000000000000000000000000000000000000000640b5549000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000000025a025dd6c973368c45ddfc17f5148e3f468a2e3f2c51920cbe9556a64942b0ab2eba031da07ce40c24b0a01f46fb2abc028b5ccd70dbd1cb330725323edc49a2a9558"; // act let tx = hex::decode(signed_data).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert_eq!(Ok(expected_transaction), decoded) @@ -630,8 +581,7 @@ mod test { // act let tx = hex::decode(signed_data).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert!(decoded.is_ok(), "testing the decoding went ok"); @@ -655,7 +605,6 @@ mod test { let to = address_from_str("ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"); let value = U256::from(760460536160301065u64); // /!\ > 2^53 -1 let data: Vec = hex::decode("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let v = U256::from(37); let r = string_to_h256_unsafe( "c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45a", ); @@ -670,16 +619,13 @@ mod test { to, value, data, - v, - r, - s, + signature: Some(TxSignature::new_unsafe(37, r, s)), }; // act let signed_data = "f903732e8506c50218ba8304312294ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b880a8db2d41b89b009b903043593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000025a0c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45aa05721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06"; let tx = hex::decode(signed_data).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert_eq!(Ok(expected_transaction), decoded); @@ -701,7 +647,6 @@ mod test { let to = address_from_str("ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"); let value = U256::from(760460536160301065u64); // /!\ > 2^53 -1 let data: Vec = hex::decode("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let v = U256::from(37); let r = string_to_h256_unsafe( "c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45a", ); @@ -716,17 +661,15 @@ mod test { to, value, data, - v, - r, - s, + signature: Some(TxSignature::new_unsafe(37, r, s)), }; let signed_data = "f903732e8506c50218ba8304312294ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b880a8db2d41b89b009b903043593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000064023c1700000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a8db2d41b89b009000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000002ab0c205a56c1e000000000000000000000000000000000000000000000000000000a8db2d41b89b00900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009eb6299e4bb6669e42cb295a254c8492f67ae2c600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000025a0c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45aa05721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06"; // act - let encoded = expected_transaction.rlp_bytes(); + let encoded = expected_transaction.to_bytes(); // assert - assert_eq!(signed_data, hex::encode(&encoded)); + assert_eq!(signed_data, hex::encode(encoded)); } #[test] @@ -753,20 +696,21 @@ mod test { to: address_from_str("4e1b2c985d729ae6e05ef7974013eeb48f394449"), value: U256::from(1000000000u64), data: vec![], - v: U256::from(38), - r: string_to_h256_unsafe( - "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", - ), - s: string_to_h256_unsafe( - "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", - ), + signature: Some(TxSignature::new_unsafe( + 38, + string_to_h256_unsafe( + "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", + ), + string_to_h256_unsafe( + "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", + ), + )), }; let signed_data = "f869018506fc23ac0083100000944e1b2c985d729ae6e05ef7974013eeb48f394449843b9aca008026a0bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ada06053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f"; // act let tx = hex::decode(signed_data).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // assert assert_eq!(Ok(expected_transaction), decoded); @@ -795,13 +739,15 @@ mod test { to: address_from_str("4e1b2c985d729ae6e05ef7974013eeb48f394449"), value: U256::from(1000000000u64), data: vec![], - v: U256::from(38), - r: string_to_h256_unsafe( - "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", - ), - s: string_to_h256_unsafe( - "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", - ), + signature: Some(TxSignature::new_unsafe( + 38, + string_to_h256_unsafe( + "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", + ), + string_to_h256_unsafe( + "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", + ), + )), }; // assert @@ -836,21 +782,20 @@ mod test { to: address_from_str("4e1b2c985d729ae6e05ef7974013eeb48f394449"), value: U256::from(1000000000u64), data: vec![], - v: U256::one(), - r: H256::zero(), - s: H256::zero(), + signature: None, }; // act - let signed = transaction + let signature = transaction .sign_transaction( "cb9db6b5878db2fa20586e23b7f7b51c22a7c6ed0530daafc2615b116f170cd3" .to_string(), ) + .unwrap() + .signature .unwrap(); // assert - let v = U256::from(38); let r = string_to_h256_unsafe( "bb03310570362eef497a09dd6e4ef42f56374965cfb09cc4e055a22a2eeac7ad", ); @@ -858,16 +803,16 @@ mod test { "6053c1bd83abb30c109801844709202208736d598649afe2a53f024b61b3383f", ); - assert_eq!(v, signed.v, "checking v"); - assert_eq!(r, signed.r, "checking r"); - assert_eq!(s, signed.s, "checking s"); + assert_eq!(U256::from(38), signature.v(), "checking v"); + assert_eq!(&r, signature.r(), "checking r"); + assert_eq!(&s, signature.s(), "checking s"); } #[test] fn test_caller_classic_with_chain_id() { let sk = "9bfc9fbe6296c8fef8eb8d6ce2ed5f772a011898c6cabe32d35e7c3e419efb1b" .to_string(); - let (_sk, address) = string_to_sk_and_address(sk.clone()).unwrap(); + let (_sk, address) = string_to_sk_and_address_unsafe(sk.clone()); // Check that the derived address is the expected one. let expected_address = address_from_str("6471A723296395CF1Dcc568941AFFd7A390f94CE").unwrap(); @@ -875,7 +820,7 @@ 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 transaction = EthereumTransactionCommon::from_hex(encoded).unwrap(); let address = transaction.caller().unwrap(); assert_eq!(expected_address, address); @@ -901,25 +846,28 @@ mod test { // than secp256k1n/2 are now considered invalid let transaction = basic_eip155_transaction(); - + let signature = transaction.signature.unwrap(); // flip s - let s: H256 = transaction.s; - let s1: [u8; 32] = s.into(); + let s: &H256 = signature.s(); + let s1: [u8; 32] = (*s).into(); let mut scalar = Scalar([0; 8]); let _ = scalar.set_b32(&s1); let flipped_scalar = scalar.neg(); let flipped_s = H256::from_slice(&flipped_scalar.b32()); // flip v - let flipped_v = if transaction.v == U256::from(37) { - U256::from(38) + let flipped_v = if signature.v() == U256::from(37) { + 38 } else { - U256::from(37) + 37 }; let flipped_transaction = EthereumTransactionCommon { - s: flipped_s, - v: flipped_v, + signature: Some(TxSignature::new_unsafe( + flipped_v, + *signature.r(), + flipped_s, + )), ..transaction }; @@ -927,7 +875,9 @@ mod test { // and the caller should be the same, if EIP2 is not implemented // but with EIP2 s should be too big, and the transaction should be rejected assert_eq!( - Err(SigError::ECDSAError(libsecp256k1::Error::InvalidSignature)), + Err(SigError::TxSigError(TxSigError::ECDSAError( + libsecp256k1::Error::InvalidSignature + ))), flipped_transaction.caller() ) } @@ -947,13 +897,15 @@ mod test { to: address_from_str("ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"), value: U256::from(760460536160301065u64), data, - v: U256::from(37), - r: string_to_h256_unsafe( - "c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45a", - ), - s: string_to_h256_unsafe( - "5721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06", - ), + signature: Some(TxSignature::new_unsafe( + 37, + string_to_h256_unsafe( + "c78be9ab81c622c08f7098eefc250935365fb794dfd94aec0fea16c32adec45a", + ), + string_to_h256_unsafe( + "5721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06", + ), + )), }; // check @@ -979,9 +931,7 @@ mod test { to: address_from_str("ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"), value: U256::from(760460536160301065u64), data, - v: U256::one(), - r: H256::zero(), - s: H256::zero(), + signature: None, }; // check @@ -1012,17 +962,17 @@ mod test { to: address_from_str("ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"), value: U256::from(760460536160301065u64), data, - v: U256::zero(), - r: H256::zero(), - s: H256::zero(), + signature: None, }; // act - let signed = transaction + let signature = transaction .sign_transaction( "dcdff53b4f013dbcdc717f89fe3bf4d8b10512aae282b48e01d7530470382701" .to_string(), ) + .unwrap() + .signature .unwrap(); // assert @@ -1034,9 +984,9 @@ mod test { "5721614264d8490c6866f110c1594151bbcc4fac43758adae644db6bc3314d06", ); - assert_eq!(v, signed.v, "checking v"); - assert_eq!(r, signed.r, "checking r"); - assert_eq!(s, signed.s, "checking s"); + assert_eq!(v, signature.v(), "checking v"); + assert_eq!(&r, signature.r(), "checking r"); + assert_eq!(&s, signature.s(), "checking s"); } #[test] @@ -1062,16 +1012,22 @@ mod test { } #[test] - fn test_rlp_decode_encode() { - let strings = - ["f86c0a8502540be400825208944bbeeb066ed09b7aed07bf39eee0460dfa261520880de0b6b3a7640000801ca0f3ae52c1ef3300f44df0bcfd1341c232ed6134672b16e35699ae3f5fe2493379a023d23d2955a239dd6f61c4e8b2678d174356ff424eac53da53e17706c43ef871".to_string(), - "f86a8302ae2a7b82f618948e998a00253cb1747679ac25e69a8d870b52d8898802c68af0bb140000802da0cd2d976eb691dc16a397462c828975f0b836e1b448ecb8f00d9765cf5032cecca066247d13fc2b65fd70a2931b5897fff4b3079e9587e69ac8a0036c99eb5ea927".to_string()]; - - strings.iter().fold((), |_, str| { - let e = EthereumTransactionCommon::from_rlp(str.clone()).unwrap(); - let encoded = e.rlp_bytes(); - assert_eq!(hex::encode(&encoded), *str); - }); + fn test_rlp_decode_fails_without_chain_id() { + // This transaction is signed but its v doesn't equal to CHAIN_ID * 2 + 35 + {0, 1} + // but equal to 27/28 as in "old" (before https://eips.ethereum.org/EIPS/eip-155) + // six fields encoding + let malformed_tx = "f86c0a8502540be400825208944bbeeb066ed09b7aed07bf39eee0460dfa261520880de0b6b3a7640000801ca0f3ae52c1ef3300f44df0bcfd1341c232ed6134672b16e35699ae3f5fe2493379a023d23d2955a239dd6f61c4e8b2678d174356ff424eac53da53e17706c43ef871".to_string(); + let e = EthereumTransactionCommon::from_hex(malformed_tx); + assert!(e.is_err()); + } + + #[test] + fn test_rlp_decode_encode_with_valid_chain_id() { + let wellformed_tx = + "f86a8302ae2a7b82f618948e998a00253cb1747679ac25e69a8d870b52d8898802c68af0bb140000802da0cd2d976eb691dc16a397462c828975f0b836e1b448ecb8f00d9765cf5032cecca066247d13fc2b65fd70a2931b5897fff4b3079e9587e69ac8a0036c99eb5ea927".to_string(); + let e = EthereumTransactionCommon::from_hex(wellformed_tx.clone()).unwrap(); + let encoded = e.to_bytes(); + assert_eq!(hex::encode(encoded), wellformed_tx); } #[test] @@ -1091,74 +1047,51 @@ mod test { // setup let signed_tx = "f901cc8086010000000000830250008080b90178608060405234801561001057600080fd5b50602a600081905550610150806100286000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea26469706673582212204d6c1853cec27824f5dbf8bcd0994714258d22fc0e0dc8a2460d87c70e3e57a564736f6c634300081200331ca06d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58da0219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1"; - let r = string_to_h256_unsafe( - "6d851632958801b6919ba534b4b1feb1bdfaabd0d42890bce200a11ac735d58d", - ); - let s = string_to_h256_unsafe( - "219b058d7169d7a4839c5cdd555b0820b545797365287a81ba409419912de7b1", - ); - // act let tx = hex::decode(signed_tx).unwrap(); - let decoder = Rlp::new(&tx); - let decoded = EthereumTransactionCommon::decode(&decoder); + let decoded = EthereumTransactionCommon::from_bytes(&tx); // 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" + assert_eq!( + decoded.err(), + Some(DecoderError::Custom( + "v has to be greater than 36 for a signed EIP-155 transaction", + )) ); - 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 { - v: U256::one(), // parity is not consistent with a signed transaction - ..basic_eip155_transaction() - }; + let transaction = basic_eip155_transaction_unsigned(); // 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 { - v: U256::from(38), // parity is consistent with a signed transaction - r: H256::zero(), // signature value is wrong - ..basic_eip155_transaction() - }; - - // sanity check assert!( - transaction.signature().is_ok(), - "testing signature is well formed" + transaction.signature().is_err(), + "testing signature for unsigned fails" ); + } - // check caller fails gracefully - assert!( - transaction.caller().is_err(), - "testing caller fails, because signature is useless" - ); + #[test] + fn test_impossible_create_invalid_sig() { + let basic = basic_eip155_transaction(); + let signature = basic.signature.unwrap(); + assert!(TxSignature::new(U256::from(38), H256::zero(), *signature.s()).is_err()); + assert!(TxSignature::new(U256::from(38), *signature.r(), H256::zero()).is_err()); } #[test] fn test_signature_invalid_parity_fails_gracefully() { + let basic = basic_eip155_transaction(); + let signature = basic.signature.unwrap(); // 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 + signature: Some(TxSignature::new_unsafe( + 150, + signature.r().to_owned(), + signature.s().to_owned(), + )), chain_id: U256::one(), - ..basic_eip155_transaction() + ..basic }; // check signature fails gracefully diff --git a/src/kernel_evm/ethereum/src/tx_signature.rs b/src/kernel_evm/ethereum/src/tx_signature.rs new file mode 100644 index 0000000000000000000000000000000000000000..305320626875c79e1a2e5d91ac43b5ff601259c7 --- /dev/null +++ b/src/kernel_evm/ethereum/src/tx_signature.rs @@ -0,0 +1,197 @@ +// SPDX-FileCopyrightText: 2022-2023 TriliTech +// +// SPDX-License-Identifier: MIT + +use hex::FromHexError; +use libsecp256k1::{curve::Scalar, sign, Message, RecoveryId, SecretKey, Signature}; +use primitive_types::{H256, U256}; +use rlp::{DecoderError, Encodable, RlpIterator}; +use thiserror::Error; + +use crate::rlp_helpers::{decode_field, decode_field_h256, next}; + +/// Represents a **valid** Ethereum signature +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TxSignature { + v: U256, + r: H256, + s: H256, +} + +#[derive(Error, Debug, PartialEq, Clone)] +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, Clone)] +pub enum TxSigError { + #[error("Error reading a hex string: {0}")] + HexError(#[from] FromHexError), + + #[error("Error manipulating ECDSA key: {0}")] + ECDSAError(libsecp256k1::Error), + + #[error("Error recomputing parity of signature: {0}")] + Parity(ParityError), + + #[error("Invalid R and S: {0} {1}")] + InvalidRS(H256, H256), +} + +impl From for TxSigError { + fn from(e: libsecp256k1::Error) -> Self { + Self::ECDSAError(e) + } +} + +impl TxSignature { + pub fn v(&self) -> U256 { + self.v + } + + pub fn r(&self) -> &H256 { + &self.r + } + + pub fn s(&self) -> &H256 { + &self.s + } + + pub fn new(v: U256, r: H256, s: H256) -> Result { + if r != H256::zero() && s != H256::zero() { + Ok(TxSignature { v, r, s }) + } else { + Err(TxSigError::InvalidRS(r, s)) + } + } + + /// Computes the parity for a transaction in the Ethereum "legacy" format, + /// ie a transaction that encodes the `chain_id` in the value `v`, this + /// should not be used for EIP-1559 or EIP-2930 transaction for example. + /// The boolean correspond to parity `0` or `1`. + fn legacy_compute_parity(&self, chain_id: U256) -> Result { + let err = TxSigError::Parity(ParityError::V(self.v)); + let chain_id_encoding = chain_id + .checked_mul(U256::from(2)) + .ok_or_else(|| err.clone())? + .checked_add(U256::from(35)) + .ok_or_else(|| err.clone())?; + let parity = self.v.checked_sub(chain_id_encoding); + match parity { + Some(p) if p < U256::from(2) => Ok(p == U256::one()), + _ => Err(err), + } + } + + /// Validate that signatures conforms EIP-2 + /// and that is possible + pub fn signature( + &self, + chain_id: U256, + ) -> Result<(Signature, RecoveryId), TxSigError> { + // copy r to Scalar + let r: H256 = self.r.to_owned(); + let r1: [u8; 32] = r.into(); + let mut r = Scalar([0; 8]); + let _ = r.set_b32(&r1); + // copy s to Scalar + let s: H256 = self.s.to_owned(); + let s1: [u8; 32] = s.into(); + let mut s = Scalar([0; 8]); + let _ = s.set_b32(&s1); + if s.is_high() { + // if s > secp256k1n / 2 the signature is invalid + // cf EIP2 (part 2) https://eips.ethereum.org/EIPS/eip-2 + Err(TxSigError::ECDSAError( + libsecp256k1::Error::InvalidSignature, + )) + } else { + // recompute parity from v and chain_id + let parity = self.legacy_compute_parity(chain_id)?; + let ri = RecoveryId::parse(parity as u8)?; + Ok((Signature { r, s }, ri)) + } + } + + /// compute v from parity and chain_id + fn compute_v(chain_id: U256, parity: u8) -> Option { + if chain_id == U256::zero() { + // we don't support transactions with unpresented chain_id + None + } else { + let chain_id_encoding = chain_id + .checked_mul(U256::from(2))? + .checked_add(U256::from(35))?; + U256::from(parity).checked_add(chain_id_encoding) + } + } + + /// This function creates a signature for a legacy tx. + /// Legacy tx has a tricky approach for signature creation, where + /// `v` field of a signature carry information about `chain_id`. + /// For more information see https://eips.ethereum.org/EIPS/eip-155 + pub fn sign_legacy( + msg: &Message, + string_sk: String, + chain_id: U256, + ) -> Result { + let hex: &[u8] = &hex::decode(string_sk)?; + let sk = SecretKey::parse_slice(hex)?; + let (sig, recovery_id) = sign(msg, &sk); + let parity: u8 = recovery_id.into(); + let v = Self::compute_v(chain_id, parity) + .ok_or(TxSigError::Parity(ParityError::ChainId(chain_id)))?; + + let (r, s) = (H256::from(sig.r.b32()), H256::from(sig.s.b32())); + Ok(TxSignature { v, r, s }) + } + + #[cfg(test)] + pub fn new_unsafe(v: u64, r: H256, s: H256) -> TxSignature { + TxSignature::new(U256::from(v), r, s).expect("Signature data should be valid") + } +} + +impl Encodable for TxSignature { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.append(&self.v); + stream.append(&self.r); + stream.append(&self.s); + } +} + +// Encode None as (0, 0, 0) +// This encoding is used for transaction object, +// which expects to have a signature. +// However, for deposits there are no signature so we have to mock it. +pub fn rlp_append_opt(sig: &Option, stream: &mut rlp::RlpStream) { + match sig { + Some(sig) => Encodable::rlp_append(sig, stream), + None => { + stream.append(&U256::zero()); + stream.append(&H256::zero()); + stream.append(&H256::zero()); + } + } +} + +// Decode (0, 0, 0) as None +// See comment for rlp_append_opt above. +pub fn rlp_decode_opt( + it: &mut RlpIterator<'_, '_>, +) -> Result, DecoderError> { + let v: U256 = decode_field(&next(it)?, "v")?; + let r: H256 = decode_field_h256(&next(it)?, "r")?; + let s: H256 = decode_field_h256(&next(it)?, "s")?; + if r == H256::zero() && s == H256::zero() && v == U256::zero() { + Ok(None) + } else { + let sig = TxSignature::new(v, r, s) + .map_err(|_| DecoderError::Custom("Invalid signature"))?; + Ok(Some(sig)) + } +} diff --git a/src/kernel_evm/evm_execution/src/lib.rs b/src/kernel_evm/evm_execution/src/lib.rs index a8e74453639ee65e5f0c9065f8bbe639980d0277..99f53cb0cc4334c727ebb3c7874b895e190ad392 100644 --- a/src/kernel_evm/evm_execution/src/lib.rs +++ b/src/kernel_evm/evm_execution/src/lib.rs @@ -171,7 +171,7 @@ mod test { use host::runtime::Runtime; use primitive_types::{H160, H256}; use std::str::FromStr; - use tezos_ethereum::signatures::EthereumTransactionCommon; + use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_smart_rollup_mock::MockHost; // The compiled initialization code for the Ethereum demo contract given @@ -643,11 +643,10 @@ mod test { #[test] //this is based on https://eips.ethereum.org/EIPS/eip-155 fn test_signatures() { - let (sk, _address) = tezos_ethereum::signatures::string_to_sk_and_address( + let (sk, _address) = tezos_ethereum::tx_common::string_to_sk_and_address_unsafe( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), - ) - .unwrap(); + ); let m: [u8; 32] = hex::decode( "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53", ) @@ -685,8 +684,7 @@ mod test { ]; test_list.iter().fold((), |_, (s, ea)| { let (_, a) = - tezos_ethereum::signatures::string_to_sk_and_address(s.to_string()) - .unwrap(); + tezos_ethereum::tx_common::string_to_sk_and_address_unsafe(s.to_string()); let value: [u8; 20] = hex::decode(ea).unwrap().try_into().unwrap(); let ea = value.into(); assert_eq!(a, ea); @@ -696,14 +694,13 @@ mod test { #[test] fn test_caller_classic() { let (_sk, address_from_sk) = - tezos_ethereum::signatures::string_to_sk_and_address( + tezos_ethereum::tx_common::string_to_sk_and_address_unsafe( "4646464646464646464646464646464646464646464646464646464646464646" .to_string(), - ) - .unwrap(); + ); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); - let transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); + let transaction = EthereumTransactionCommon::from_hex(encoded).unwrap(); let address = transaction.caller().unwrap(); let expected_address_string: [u8; 20] = hex::decode("9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F") @@ -722,7 +719,7 @@ mod test { .to_string(); let encoded = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83".to_string(); - let expected_transaction = EthereumTransactionCommon::from_rlp(encoded).unwrap(); + let expected_transaction = EthereumTransactionCommon::from_hex(encoded).unwrap(); let transaction = expected_transaction.sign_transaction(string_sk).unwrap(); assert_eq!(expected_transaction, transaction) diff --git a/src/kernel_evm/kernel/src/apply.rs b/src/kernel_evm/kernel/src/apply.rs index af2d3d5dac060c8a0ec9407ad8c03ff3c8a2fd4f..485a87283cb89ca58f04daff1c244dbe2ed048f0 100644 --- a/src/kernel_evm/kernel/src/apply.rs +++ b/src/kernel_evm/kernel/src/apply.rs @@ -9,10 +9,11 @@ use evm_execution::account_storage::{account_path, EthereumAccountStorage}; use evm_execution::handler::ExecutionOutcome; use evm_execution::precompiles::PrecompileBTreeMap; use evm_execution::run_transaction; -use primitive_types::{H160, H256, U256}; +use primitive_types::{H160, U256}; use tezos_ethereum::block::BlockConstants; -use tezos_ethereum::signatures::EthereumTransactionCommon; use tezos_ethereum::transaction::TransactionHash; +use tezos_ethereum::tx_common::EthereumTransactionCommon; +use tezos_ethereum::tx_signature::TxSignature; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_host::runtime::Runtime; @@ -62,24 +63,10 @@ impl Transaction { } } - fn v(&self) -> U256 { + fn signature(&self) -> Option { match &self.content { - TransactionContent::Deposit(_) => U256::zero(), - TransactionContent::Ethereum(transaction) => transaction.v, - } - } - - fn r(&self) -> H256 { - match &self.content { - TransactionContent::Deposit(_t) => H256::zero(), - TransactionContent::Ethereum(transaction) => transaction.r, - } - } - - fn s(&self) -> H256 { - match &self.content { - TransactionContent::Deposit(_) => H256::zero(), - TransactionContent::Ethereum(transaction) => transaction.s, + TransactionContent::Deposit(_) => None, + TransactionContent::Ethereum(transaction) => transaction.signature.clone(), } } } @@ -102,9 +89,7 @@ pub struct TransactionObjectInfo { pub to: Option, pub index: u32, pub value: U256, - pub v: U256, - pub r: H256, - pub s: H256, + pub signature: Option, } #[inline(always)] @@ -141,9 +126,7 @@ fn make_object_info( to: transaction.to(), index, value: transaction.value(), - v: transaction.v(), - r: transaction.r(), - s: transaction.s(), + signature: transaction.signature(), } } diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 019e6e2e72d5f2e1950eadec72b2787a13576bb0..4cd4df0d01c7129ef32b70946c6ddb94058b4846 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -188,10 +188,11 @@ mod tests { use std::collections::VecDeque; use std::ops::Rem; use std::str::FromStr; - use tezos_ethereum::signatures::EthereumTransactionCommon; use tezos_ethereum::transaction::{ TransactionHash, TransactionStatus, TRANSACTION_HASH_SIZE, }; + use tezos_ethereum::tx_common::EthereumTransactionCommon; + use tezos_ethereum::tx_signature::TxSignature; use tezos_smart_rollup_mock::MockHost; fn blueprint(transactions: Vec) -> QueueElement { @@ -260,9 +261,7 @@ mod tests { to, value, data, - v, - r, - s, + signature: Some(TxSignature::new(v, r, s).unwrap()), } } @@ -313,9 +312,7 @@ mod tests { to: None, value, data, - v: U256::one(), - r: H256::zero(), - s: H256::zero(), + signature: None, }; tx.sign_transaction(private_key.to_string()).unwrap() @@ -1047,9 +1044,7 @@ mod tests { to, value, data: vec![], - v: U256::one(), - r: H256::zero(), - s: H256::zero(), + signature: None, }; // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 diff --git a/src/kernel_evm/kernel/src/block_in_progress.rs b/src/kernel_evm/kernel/src/block_in_progress.rs index edd1b7e03507d0a0ec04e4123ef6afbf4375cdcb..5f5ed803887f64440612c026eb9619b45a52d045 100644 --- a/src/kernel_evm/kernel/src/block_in_progress.rs +++ b/src/kernel_evm/kernel/src/block_in_progress.rs @@ -307,9 +307,7 @@ impl BlockInProgress { to: object_info.to, index: object_info.index, value: object_info.value, - v: object_info.v, - r: object_info.r, - s: object_info.s, + signature: object_info.signature, } } } @@ -322,9 +320,14 @@ mod tests { use primitive_types::{H160, H256, U256}; use rlp::{Decodable, Encodable, Rlp}; use tezos_ethereum::{ - signatures::EthereumTransactionCommon, transaction::TRANSACTION_HASH_SIZE, + transaction::TRANSACTION_HASH_SIZE, tx_common::EthereumTransactionCommon, + tx_signature::TxSignature, }; + fn new_sig_unsafe(v: u64, r: H256, s: H256) -> TxSignature { + TxSignature::new(U256::from(v), r, s).unwrap() + } + fn dummy_etc(i: u8) -> EthereumTransactionCommon { EthereumTransactionCommon { chain_id: U256::from(i), @@ -334,9 +337,11 @@ mod tests { to: None, value: U256::from(i), data: Vec::new(), - r: H256::from([i; 32]), - s: H256::from([i; 32]), - v: U256::from(i), + signature: Some(new_sig_unsafe( + (36 + i * 2).into(), // need to be consistent with chain_id + H256::from([i; 32]), + H256::from([i; 32]), + )), } } @@ -373,7 +378,7 @@ mod tests { }; let encoded = bip.rlp_bytes(); - let expected = "f9014d2af8e2f86fa00101010101010101010101010101010101010101010101010101010101010101f84c01f84901010180018001a00101010101010101010101010101010101010101010101010101010101010101a00101010101010101010101010101010101010101010101010101010101010101f86fa00808080808080808080808080808080808080808080808080808080808080808f84c01f84908080880088008a00808080808080808080808080808080808080808080808080808080808080808a00808080808080808080808080808080808080808080808080808080808080808f842a00202020202020202020202020202020202020202020202020202020202020202a00909090909090909090909090909090909090909090909090909090909090909030405a00606060606060606060606060606060606060606060606060606060606060606"; + let expected = "f9014d2af8e2f86fa00101010101010101010101010101010101010101010101010101010101010101f84c01f84901010180018026a00101010101010101010101010101010101010101010101010101010101010101a00101010101010101010101010101010101010101010101010101010101010101f86fa00808080808080808080808080808080808080808080808080808080808080808f84c01f84908080880088034a00808080808080808080808080808080808080808080808080808080808080808a00808080808080808080808080808080808080808080808080808080808080808f842a00202020202020202020202020202020202020202020202020202020202020202a00909090909090909090909090909090909090909090909090909090909090909030405a00606060606060606060606060606060606060606060606060606060606060606"; assert_eq!(hex::encode(encoded), expected); let bytes = hex::decode(expected).expect("Should be valid hex string"); @@ -433,7 +438,7 @@ mod tests { }; let encoded = bip.rlp_bytes(); - let expected = "f901192af8aef86fa00101010101010101010101010101010101010101010101010101010101010101f84c01f84901010180018001a00101010101010101010101010101010101010101010101010101010101010101a00101010101010101010101010101010101010101010101010101010101010101f83ba00808080808080808080808080808080808080808080808080808080808080808d902d70808940808080808080808080808080808080808080808f842a00202020202020202020202020202020202020202020202020202020202020202a00909090909090909090909090909090909090909090909090909090909090909030405a00606060606060606060606060606060606060606060606060606060606060606"; + let expected = "f901192af8aef86fa00101010101010101010101010101010101010101010101010101010101010101f84c01f84901010180018026a00101010101010101010101010101010101010101010101010101010101010101a00101010101010101010101010101010101010101010101010101010101010101f83ba00808080808080808080808080808080808080808080808080808080808080808d902d70808940808080808080808080808080808080808080808f842a00202020202020202020202020202020202020202020202020202020202020202a00909090909090909090909090909090909090909090909090909090909090909030405a00606060606060606060606060606060606060606060606060606060606060606"; assert_eq!(hex::encode(encoded), expected); let bytes = hex::decode(expected).expect("Should be valid hex string"); diff --git a/src/kernel_evm/kernel/src/blueprint.rs b/src/kernel_evm/kernel/src/blueprint.rs index ca4a9d3d0b1f77f44973c1f31651b51c8d6b9204..c9cf0487bfaeff04d5661e5b9fe0b936b12f2bae 100644 --- a/src/kernel_evm/kernel/src/blueprint.rs +++ b/src/kernel_evm/kernel/src/blueprint.rs @@ -77,9 +77,9 @@ pub fn fetch( mod tests { use super::*; use crate::inbox::TransactionContent::Ethereum; - use primitive_types::{H160, H256, U256}; + use primitive_types::{H160, U256}; use tezos_ethereum::{ - signatures::EthereumTransactionCommon, transaction::TRANSACTION_HASH_SIZE, + transaction::TRANSACTION_HASH_SIZE, tx_common::EthereumTransactionCommon, }; fn address_from_str(s: &str) -> Option { @@ -99,9 +99,7 @@ mod tests { to: address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), value: U256::from(500000000u64), data: vec![], - v: U256::from(0), - r: H256::from_low_u64_be(0), - s: H256::from_low_u64_be(0), + signature: None, }; let valid_transaction = Transaction { diff --git a/src/kernel_evm/kernel/src/error.rs b/src/kernel_evm/kernel/src/error.rs index 88888046eb0aec640767889fbc2083d11f214273..3424289d793451c042b277f6548c92d56f16f1b8 100644 --- a/src/kernel_evm/kernel/src/error.rs +++ b/src/kernel_evm/kernel/src/error.rs @@ -7,7 +7,7 @@ use core::str::Utf8Error; use evm_execution::{DurableStorageError, EthereumError}; use primitive_types::U256; use rlp::DecoderError; -use tezos_ethereum::signatures::SigError; +use tezos_ethereum::tx_common::SigError; use tezos_smart_rollup_host::path::PathError; use tezos_smart_rollup_host::runtime::RuntimeError; use thiserror::Error; diff --git a/src/kernel_evm/kernel/src/inbox.rs b/src/kernel_evm/kernel/src/inbox.rs index 13f53d538f18fefb54a215dddbc306244ee28f11..1ba8fb7392d00bde66daf1383b96fb5e50ccb374 100644 --- a/src/kernel_evm/kernel/src/inbox.rs +++ b/src/kernel_evm/kernel/src/inbox.rs @@ -21,8 +21,8 @@ use rlp::{Decodable, DecoderError, Encodable}; use sha3::{Digest, Keccak256}; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::rlp_helpers::{decode_field, decode_tx_hash, next}; -use tezos_ethereum::signatures::EthereumTransactionCommon; use tezos_ethereum::transaction::TransactionHash; +use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; use tezos_smart_rollup_host::runtime::Runtime; @@ -218,7 +218,7 @@ fn handle_transaction_chunk( // if `data` was the missing chunk. if let Some(data) = crate::storage::store_transaction_chunk(host, &tx_hash, i, data)? { - if let Ok(tx) = EthereumTransactionCommon::from_rlp_bytes(&data) { + if let Ok(tx) = EthereumTransactionCommon::from_bytes(&data) { let content = TransactionContent::Ethereum(tx); return Ok(Some(Transaction { tx_hash, content })); } @@ -437,7 +437,7 @@ mod tests { fn large_transaction() -> (Vec, EthereumTransactionCommon) { let data: Vec = hex::decode(["f917e180843b9aca0082520894b53dc01974176e5dff2298c5a94343c2585e3c548a021dfe1f5c5363780000b91770".to_string(), "a".repeat(12_000), "820a96a07fd9567a72223bbc8f70bd2b42011339b61044d16b5a2233534db8ca01f3e57aa03ea489c4bb2b2b52f3c1a18966881114767654c9ab61d46b1fbff78a498043c2".to_string()].join("")).unwrap(); - let tx = EthereumTransactionCommon::from_rlp_bytes(&data).unwrap(); + let tx = EthereumTransactionCommon::from_bytes(&data).unwrap(); (data, tx) } @@ -446,7 +446,7 @@ mod tests { let mut host = MockHost::default(); let tx = - EthereumTransactionCommon::from_rlp_bytes(&hex::decode("f86d80843b9aca00825208940b52d4d3be5d18a7ab5e4476a2f5382bbf2b38d888016345785d8a000080820a95a0d9ef1298c18c88604e3f08e14907a17dfa81b1dc6b37948abe189d8db5cb8a43a06fc7040a71d71d3cb74bd05ead7046b10668ad255da60391c017eea31555f156").unwrap()).unwrap(); + EthereumTransactionCommon::from_bytes(&hex::decode("f86d80843b9aca00825208940b52d4d3be5d18a7ab5e4476a2f5382bbf2b38d888016345785d8a000080820a95a0d9ef1298c18c88604e3f08e14907a17dfa81b1dc6b37948abe189d8db5cb8a43a06fc7040a71d71d3cb74bd05ead7046b10668ad255da60391c017eea31555f156").unwrap()).unwrap(); let input = Input::SimpleTransaction(Box::new(Transaction { tx_hash: ZERO_TX_HASH, content: Ethereum(tx.clone()), @@ -698,7 +698,7 @@ mod tests { let mut host = MockHost::with_address(&address); let tx = - EthereumTransactionCommon::from_rlp_bytes(&hex::decode("f86d80843b9aca00825208940b52d4d3be5d18a7ab5\ + EthereumTransactionCommon::from_bytes(&hex::decode("f86d80843b9aca00825208940b52d4d3be5d18a7ab5\ e4476a2f5382bbf2b38d888016345785d8a000080820a95a0d9ef1298c18c88604e3f08e14907a17dfa81b1dc6b37948abe189d8db5cb8a43a06\ fc7040a71d71d3cb74bd05ead7046b10668ad255da60391c017eea31555f156").unwrap()).unwrap(); diff --git a/src/kernel_evm/kernel/src/parsing.rs b/src/kernel_evm/kernel/src/parsing.rs index d9c5e4eae70c4f4adc689f94fa81fe46e68ece99..8818d8a5a7f550bb0bf197598dfd06ab483f36a9 100644 --- a/src/kernel_evm/kernel/src/parsing.rs +++ b/src/kernel_evm/kernel/src/parsing.rs @@ -9,7 +9,7 @@ use crate::inbox::{Deposit, KernelUpgrade, Transaction, TransactionContent}; use primitive_types::{H160, U256}; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::{ - signatures::EthereumTransactionCommon, transaction::TransactionHash, + transaction::TransactionHash, tx_common::EthereumTransactionCommon, wei::eth_from_mutez, }; use tezos_evm_logging::{log, Level::*}; diff --git a/src/kernel_evm/kernel/src/tick_model.rs b/src/kernel_evm/kernel/src/tick_model.rs index bfb371e6c4e37b9273947c467709b226cb64ee3d..300e5212c939e23a381877a4114805a661147546 100644 --- a/src/kernel_evm/kernel/src/tick_model.rs +++ b/src/kernel_evm/kernel/src/tick_model.rs @@ -4,7 +4,7 @@ use crate::{apply::TransactionReceiptInfo, inbox::Transaction}; use evm::Config; -use tezos_ethereum::signatures::EthereumTransactionCommon; +use tezos_ethereum::tx_common::EthereumTransactionCommon; /////////////////////////////////////////////////////////////////////////////// /// TICK MODEL CONSTANTS