use num_bigint::BigUint;
use super::{EthereumAddress, EthereumPrivateKey, EthereumSignature};
use crate::{
blockchain::chains::EthereumChainId,
util::{keccak256, trim_bytes, KECCAK256_BYTES},
};
use rlp::RlpStream;
use crate::error::VaultError;
use crate::ethereum::signature::{EthereumBasicSignature, EthereumEIP2930Signature, Signable};
#[derive(Clone, Debug)]
pub struct EthereumLegacyTransaction {
pub chain_id: EthereumChainId,
pub nonce: u64,
pub gas_price: BigUint,
pub gas_limit: u64,
pub to: Option<EthereumAddress>,
pub value: BigUint,
pub data: Vec<u8>,
}
pub struct TxAccess {
pub address: EthereumAddress,
pub storage_keys: Vec<[u8; 32]>,
}
pub struct EthereumEIP1559Transaction {
pub chain_id: EthereumChainId,
pub nonce: u64,
pub max_gas_price: BigUint,
pub priority_gas_price: BigUint,
pub gas_limit: u64,
pub to: Option<EthereumAddress>,
pub value: BigUint,
pub data: Vec<u8>,
pub access: Vec<TxAccess>,
}
pub trait EthereumTransaction {
fn sign(
&self,
pk: EthereumPrivateKey
) -> Result<Vec<u8>, VaultError>;
fn encode_into(&self, rlp: &mut RlpStream, empty_sig: bool);
fn get_chain(&self) -> EthereumChainId;
fn encode_unsigned(&self) -> Vec<u8> {
let mut rlp = RlpStream::new();
self.encode_into(&mut rlp, true);
rlp.finalize_unbounded_list();
rlp.out().to_vec()
}
fn encode_signed(&self, sig: &dyn EthereumSignature) -> Vec<u8> {
let mut rlp = RlpStream::new();
self.encode_into(&mut rlp, false);
sig.append_to_rlp(self.get_chain(), &mut rlp);
rlp.finalize_unbounded_list();
rlp.out().to_vec()
}
fn hash(&self) -> [u8; KECCAK256_BYTES] {
let mut rlp = RlpStream::new();
self.encode_into(&mut rlp, true);
rlp.finalize_unbounded_list();
let vec = rlp.out();
keccak256(&vec)
}
}
impl EthereumTransaction for EthereumLegacyTransaction {
fn sign(
&self,
pk: EthereumPrivateKey
) -> Result<Vec<u8>, VaultError> {
let sig = pk.sign::<EthereumBasicSignature>(self)?
.to_eip155(self.chain_id);
Ok(self.encode_signed(&sig))
}
fn encode_into(&self, rlp: &mut RlpStream, empty_sig: bool) {
rlp.begin_unbounded_list();
rlp.append(&self.nonce);
rlp.append(&trim_bytes(&self.gas_price.to_bytes_be()));
rlp.append(&self.gas_limit);
match self.to {
Some(addr) => rlp.append(&addr.0.as_slice()),
_ => rlp.append_empty_data(),
};
rlp.append(&trim_bytes(&self.value.to_bytes_be()));
if self.data.is_empty() {
rlp.append_empty_data();
} else {
rlp.append(&self.data);
}
if empty_sig {
rlp.append(&self.chain_id.as_chainid());
rlp.append_empty_data();
rlp.append_empty_data();
}
}
fn get_chain(&self) -> EthereumChainId {
self.chain_id
}
}
impl EthereumTransaction for EthereumEIP1559Transaction {
fn sign(&self, pk: EthereumPrivateKey) -> Result<Vec<u8>, VaultError> {
let sig = pk.sign::<EthereumEIP2930Signature>(self)?;
Ok(self.encode_signed(&sig))
}
fn encode_into(&self, rlp: &mut RlpStream, _empty_sig: bool) {
rlp.append_raw(&[2], 1);
rlp.begin_unbounded_list();
rlp.append(&self.chain_id.as_chainid());
rlp.append(&self.nonce);
rlp.append(&trim_bytes(&self.priority_gas_price.to_bytes_be()));
rlp.append(&trim_bytes(&self.max_gas_price.to_bytes_be()));
rlp.append(&self.gas_limit);
match self.to {
Some(addr) => rlp.append(&addr.0.as_slice()),
_ => rlp.append_empty_data(),
};
rlp.append(&trim_bytes(&self.value.to_bytes_be()));
if self.data.is_empty() {
rlp.append_empty_data();
} else {
rlp.append(&self.data);
}
rlp.begin_unbounded_list();
for access in &self.access {
rlp.begin_unbounded_list();
rlp.append(&access.address.0.as_slice());
rlp.begin_list(access.storage_keys.len());
for storage in &access.storage_keys {
rlp.append_raw(storage, 32);
}
rlp.finalize_unbounded_list();
}
rlp.finalize_unbounded_list();
}
fn get_chain(&self) -> EthereumChainId {
self.chain_id
}
}
impl Signable for EthereumEIP1559Transaction {
fn as_sign_message(&self) -> Vec<u8> {
let mut rlp = RlpStream::new();
self.encode_into(&mut rlp, true);
rlp.finalize_unbounded_list();
rlp.out().to_vec()
}
}
impl Signable for EthereumLegacyTransaction {
fn as_sign_message(&self) -> Vec<u8> {
let mut rlp = RlpStream::new();
self.encode_into(&mut rlp, true);
rlp.finalize_unbounded_list();
rlp.out().to_vec()
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use num::{Num, Zero};
use super::*;
use crate::{blockchain::ethereum::EthereumAddress, tests::*};
#[test]
fn encode_tx() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::Custom(0x25),
nonce: 1,
gas_price: BigUint::from_str_radix(
"00000000000000000000000000000000000000000000000000000004e3b29200", 16
).unwrap(),
gas_limit: 21000,
to: Some(
"0x3eaf0b987b49c4d782ee134fdc1243fd0ccdfdd3"
.parse::<EthereumAddress>()
.unwrap(),
),
value: BigUint::from_str_radix("00000000000000000000000000000000000000000000000000DE0B6B3A764000", 16).unwrap(),
data: Vec::new(),
};
let rlp = tx.encode_unsigned();
let hex = hex::encode(rlp);
assert_eq!(
hex,
"".to_owned() +
"eb" + "01" + "85" + "04e3b29200" + "82" + "5208" + "94" + "3eaf0b987b49c4d782ee134fdc1243fd0ccdfdd3" + "87" + "de0b6b3a764000" + "80" + "25" + "80" + "80" );
}
#[test]
fn encode_tx_with_small_gassprice() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::Custom(0x25),
nonce: 0,
gas_price: BigUint::from(1u32),
gas_limit: 21000,
to: Some(
"0x3eaf0b987b49c4d782ee134fdc1243fd0ccdfdd3"
.parse::<EthereumAddress>()
.unwrap(),
),
value: BigUint::zero(),
data: Vec::new(),
};
let rlp = tx.encode_unsigned();
let hex = hex::encode(rlp);
assert_eq!(
hex,
"".to_owned() +
"df" + "80" + "01" + "82" + "5208" + "94" + "3eaf0b987b49c4d782ee134fdc1243fd0ccdfdd3" + "80" + "80" + "25" + "80" + "80" );
}
#[test]
fn should_sign_transaction_for_mainnet() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::EthereumClassic,
nonce: 0,
gas_price:
BigUint::from_str_radix("04e3b29200", 16).unwrap(),
gas_limit: 21000,
to: Some("0x3f4E0668C20E100d7C2A27D4b177Ac65B2875D26"
.parse::<EthereumAddress>()
.unwrap()),
value:
BigUint::from_str_radix("0de0b6b3a7640000", 16).unwrap(),
data: Vec::new(),
};
let pk = EthereumPrivateKey(to_32bytes(
"00b413b37c71bfb92719d16e28d7329dea5befa0d0b8190742f89e55617991cf",
));
let hex = hex::encode(
tx.sign(pk)
.unwrap(),
);
assert_eq!(
hex,
"f86d\
808504e3b29200825208\
94\
3f4e0668c20e100d7c2a27d4b177ac65b2875d26\
88\
0de0b6b3a7640000\
80\
81\
9e\
a0\
4ca75f697cf61daf1980dcd4f4460450e9e07b3c1b16ad1224b1a46e7e5c53b2\
a0\
59648e92e975d9cdf5d12698d7267595c087e83e9598639e13525f6fe7c047f1"
);
}
#[test]
fn should_sign_transaction_for_sepolia_testnet() {
let tx = EthereumEIP1559Transaction {
chain_id: EthereumChainId::Sepolia,
nonce: 0x123,
max_gas_price: BigUint::from_str_radix("4A817C800", 16).unwrap(),
priority_gas_price: BigUint::from_str_radix("3B9ACA00", 16).unwrap(),
gas_limit: 0x249F0,
to: Some("0x3535353535353535353535353535353535353535"
.parse::<EthereumAddress>()
.unwrap()),
value: BigUint::from_str_radix("DE0B6B3A7640000", 16).unwrap(), data: Vec::new(),
access: vec![]
};
let pk = EthereumPrivateKey(to_32bytes(
"4646464646464646464646464646464646464646464646464646464646464646",
));
assert_eq!(
hex::encode(tx.sign(pk).unwrap()),
"02f87983aa36a7820123843b9aca008504a817c800830249f0943535353535353535353535353535353535353535880de0b6b3a764000080c001a0ea3705d1137256ba5078c2d97a8886ea78e3c9ea4d3ffaa4985220705fdf02e1a00f05771ac81ddb47283f592790db1205c6b321c3cdc5164b16d89898d0802647"
);
}
#[test]
fn should_sign_transaction_eip155() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::Ethereum,
nonce: 9,
gas_price:
BigUint::from_str_radix("00000000000000000000000000000\
000000000000000000000000004a817c800", 16).unwrap(),
gas_limit: 21000,
to: Some("0x3535353535353535353535353535353535353535"
.parse::<EthereumAddress>()
.unwrap()),
value: BigUint::from_str_radix("000000000000000000000000000000\
0000000000000000000de0b6b3a7640000", 16).unwrap(),
data: Vec::new(),
};
let pk = EthereumPrivateKey(to_32bytes(
"4646464646464646464646464646464646464646464646464646464646464646",
));
assert_eq!(
hex::encode(tx.sign(pk).unwrap()),
"f86c\
09\
85\
04a817c800\
82\
5208\
94\
3535353535353535353535353535353535353535\
88\
0de0b6b3a7640000\
8025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa\
636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"
);
}
#[test]
fn encode_tx_eip1559() {
let tx = EthereumEIP1559Transaction {
chain_id: EthereumChainId::Ethereum,
nonce: 150,
max_gas_price: BigUint::from_str("82684598939").unwrap(),
priority_gas_price: BigUint::from_str("4000000000").unwrap(),
gas_limit: 51101,
to: Some("0x7bebd226154e865954a87650faefa8f485d36081"
.parse::<EthereumAddress>()
.unwrap()),
value: BigUint::zero(),
data: hex::decode("095ea7b300000000000000000000000003f7724180aa6b939894b5ca4314783b0b36b329ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(),
access: vec![]
};
let sig = EthereumEIP2930Signature {
y_parity: 1,
r: to_32bytes("d978ed98e78dd480b2aec86d1521962a8fe4009e44fb19f45b70d8005e602182"),
s: to_32bytes("347c933f78131995c1abd07c1d0be67d8f04c2cf99cd79510657e97ead8c1a9f")
};
let encoded = tx.encode_signed(&sig);
assert_eq!(
hex::encode(encoded),
"02f8b101819684ee6b280085134062da9b82c79d947bebd226154e865954a87650faefa8f485d3608180b844095ea7b300000000000000000000000003f7724180aa6b939894b5ca4314783b0b36b329ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a0d978ed98e78dd480b2aec86d1521962a8fe4009e44fb19f45b70d8005e602182a0347c933f78131995c1abd07c1d0be67d8f04c2cf99cd79510657e97ead8c1a9f"
);
}
#[test]
fn rs_should_be_quantity_1() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::Ethereum,
nonce: 0,
gas_price:
BigUint::from_str_radix("0000000000000000000000000000000000000000000000000000D55698372431", 16).unwrap(),
gas_limit: 2000000,
to: Some("0xF0109fC8DF283027b6285cc889F5aA624EaC1F55"
.parse::<EthereumAddress>()
.unwrap()),
value:
BigUint::from_str_radix("000000000000000000000000000000000000000000000000000000003B9ACA00", 16).unwrap(),
data: Vec::new(),
};
let pk = EthereumPrivateKey(to_32bytes(
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318",
));
let hex = hex::encode(tx.sign(pk).unwrap());
assert_eq!(hex,
"f86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a00\
9ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9c\
a0\
440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428");
}
#[test]
fn rs_should_be_quantity_2() {
let tx = EthereumLegacyTransaction {
chain_id: EthereumChainId::Ethereum,
nonce: 0,
gas_price:
BigUint::from_str_radix("0000000000000000000000000000000000000000000000000000000000000000", 16).unwrap(),
gas_limit: 31853,
to: Some("0xF0109fC8DF283027b6285cc889F5aA624EaC1F55"
.parse::<EthereumAddress>()
.unwrap()),
value:
BigUint::from_str_radix("0000000000000000000000000000000000000000000000000000000000000000", 16).unwrap(),
data: Vec::new(),
};
let pk = EthereumPrivateKey(to_32bytes(
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318",
));
let hex = hex::encode(tx.sign(pk).unwrap());
assert_eq!(
hex,
"f85d8080827c6d94f0109fc8df283027b6285cc889f5aa624eac1f558080269f\
22f17b38af35286ffbb0c6376c86ec91c20ecbad93f84913a0cc15e7580cd9\
9f\
83d6e12e82e3544cb4439964d5087da78f74cefeec9a450b16ae179fd8fe20"
);
}
#[test]
fn create_tx_eip1559() {
let tx = EthereumEIP1559Transaction {
chain_id: EthereumChainId::Ethereum,
nonce: 1234,
max_gas_price: BigUint::from_str("20000000000").unwrap(), priority_gas_price: BigUint::from_str("1000000000").unwrap(), gas_limit: 150_000,
to: Some("0x3535353535353535353535353535353535353535"
.parse::<EthereumAddress>()
.unwrap()),
value: BigUint::from_str("1234500000000000000").unwrap(), data: Vec::new(),
access: vec![]
};
let hash = hex::encode(EthereumTransaction::hash(&tx));
assert_eq!(hash, "68fe011ba5be4a03369d51810e7943abab15fbaf757f9296711558aee8ab772b");
let pk = EthereumPrivateKey(to_32bytes(
"4646464646464646464646464646464646464646464646464646464646464646",
));
let hex = hex::encode(tx.sign(pk).unwrap());
assert_eq!(
hex,
"02f876018204d2843b9aca008504a817c800830249f0943535353535353535353535353535353535353535881121d3359738400080c001a0f0b3347ec48e78bf5ef6075b332334518ebc2f90d2bf0fea080623179936382ea05c58c5beeafb2398d5e79b40b320421112a9672167f27e7fc55e76d2d7d11062"
);
}
}