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}