tx_from_scratch/
lib.rs

1use rlp::{Encodable, RlpStream};
2use secp256k1::{Message, Secp256k1, SecretKey};
3use sha3::{Digest, Keccak256};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct Transaction {
7    /// Nonce of your next transaction 
8    pub nonce: u128,
9
10    /// Gas price
11    pub gas_price: u128,
12
13    /// Gas or Gas_limit. So amount of gas you are willing to spend
14    pub gas: u128,
15
16    /// Address you want to transact with. If you want to deploy a contract, `to` should be None.
17    ///
18    /// To convert your address from string to [u8; 20] you will have to use ethereum_types crate.
19    /// ```no_run
20    /// use ethereum_types::H160;
21    /// use std::str::FromStr;
22    ///
23    /// let address: [u8; 20] = H160::from_str(&"/* your address */").unwrap().to_fixed_bytes();
24    /// ```
25    pub to: Option<[u8; 20]>,
26
27    /// Amount of ether you want to send
28    pub value: u128,
29
30    /// If you want to interact or deploy smart contract add the bytecode here
31    pub data: Vec<u8>,
32
33    /// Chain id for the target chain. Mainnet = 1
34    pub chain_id: u64,
35}
36
37impl Transaction {
38    /// To use sign method you have to input your private key so it can sign the transaction.
39    /// It takes `&[u8]` as parameter. If you want to convert your private_key from string here is
40    /// how you can do that
41    /// ```no_run
42    /// use ethereum_types::H256;
43    /// use std::str::FromStr;
44    ///
45    /// let private_key = H256::from_str("/*your private key*/").unwrap();
46    ///
47    /// let tx = Transaction::default();
48    ///
49    /// let signed_tx = tx.sign(private_key.as_bytes());
50    /// ```
51    /// This will convert your private key to &[u8] from string
52    pub fn sign(&self, private_key: &[u8]) -> Vec<u8> {
53        let hashed_tx = self.hash();
54
55        let sign_only = Secp256k1::signing_only();
56        let message = Message::from_slice(&hashed_tx).unwrap();
57        let secret_key = SecretKey::from_slice(&private_key).expect("Wrong Private Key");
58        let (v, signature) = sign_only
59            .sign_ecdsa_recoverable(&message, &secret_key)
60            .serialize_compact();
61
62        let v = v.to_i32() as u64 + (self.chain_id * 2 + 35);
63        let r = signature[0..32].to_vec();
64        let s = signature[32..64].to_vec();
65
66        let mut stream = RlpStream::new();
67        self.rlp_append(&mut stream);
68        stream.append(&v);
69        stream.append(&r);
70        stream.append(&s);
71        stream.finalize_unbounded_list();
72
73        stream.out().to_vec()
74    }
75
76    pub fn hash(&self) -> Vec<u8> {
77        // Rlp encode transaction
78        let mut stream = RlpStream::new();
79        self.rlp_append(&mut stream);
80
81        // Add params for legacy transaction
82        stream.append(&self.chain_id);
83        stream.append_raw(&[0x80], 1);
84        stream.append_raw(&[0x80], 1);
85        stream.finalize_unbounded_list();
86        let rlp_bytes = stream.out().to_vec();
87
88        // Hash rlp_bytes
89        let mut hasher = Keccak256::new();
90        hasher.update(rlp_bytes);
91        // Return hashed transaction to be signed
92        hasher.finalize().to_vec()
93    }
94}
95
96impl Encodable for Transaction {
97    /// Implement Encodable trait for simpler Rlp Encoding
98    fn rlp_append(&self, s: &mut RlpStream) {
99        s.begin_unbounded_list();
100        s.append(&self.nonce);
101        s.append(&self.gas_price);
102        s.append(&self.gas);
103        if let None = self.to {
104            s.append(&Vec::new());
105        } else {
106            s.append(&self.to.unwrap().to_vec());
107        }
108        s.append(&self.value);
109        s.append(&self.data);
110    }
111}
112
113impl Default for Transaction {
114    /// Implement Default trait so users can specify what what they need and rest will be added
115    /// automatically.
116    /// ```no_run
117    /// use ethereum_types::H160;
118    /// use std::str::FromStr;
119    ///
120    /// let address: [u8; 20] = H160::from_str(&"/* your address */").unwrap().to_fixed_bytes();
121    ///
122    /// let tx = tx_from_scratch::Transaction {
123    ///     nonce: 10,
124    ///     to,
125    ///     value: 10,
126    ///     ..Default::default(),
127    /// }
128    /// ```
129    /// If you don't specify `to` the default is None so it will try to deploy a contract
130    ///
131    /// Default is: 
132    /// ```no_run
133    /// Transaction {
134    ///     nonce: 0,
135    ///     gas_price: 250,
136    ///     gas: 21000,
137    ///     to: None,
138    ///     value: 0,
139    ///     data: Vec::new(),
140    ///     chain_id: 1,
141    /// }
142    /// ```
143    fn default() -> Self {
144        Self {
145            nonce: 0,
146            gas_price: 250,
147            gas: 21000,
148            to: None,
149            value: 0,
150            data: Vec::new(),
151            chain_id: 1,
152        }
153    }
154}