1use crate::{api::Namespace, signing, types::H256, Transport};
4
5#[derive(Debug, Clone)]
7pub struct Accounts<T> {
8 transport: T,
9}
10
11impl<T: Transport> Namespace<T> for Accounts<T> {
12 fn new(transport: T) -> Self
13 where
14 Self: Sized,
15 {
16 Accounts { transport }
17 }
18
19 fn transport(&self) -> &T {
20 &self.transport
21 }
22}
23
24impl<T: Transport> Accounts<T> {
25 pub fn hash_message<S>(&self, message: S) -> H256
31 where
32 S: AsRef<[u8]>,
33 {
34 let message = message.as_ref();
35
36 let mut eth_message = format!("\x19Ethereum Signed Message:\n{}", message.len()).into_bytes();
37 eth_message.extend_from_slice(message);
38
39 signing::keccak256(ð_message).into()
40 }
41}
42
43#[cfg(feature = "signing")]
44mod accounts_signing {
45 use super::*;
46 use crate::{
47 api::Web3,
48 error,
49 signing::Signature,
50 types::{
51 Address, Bytes, Recovery, RecoveryMessage, SignedData, SignedTransaction, TransactionParameters, U256,
52 },
53 };
54 use rlp::RlpStream;
55 use std::convert::TryInto;
56
57 impl<T: Transport> Accounts<T> {
58 fn web3(&self) -> Web3<T> {
60 Web3::new(self.transport.clone())
61 }
62
63 pub async fn sign_transaction<K: signing::Key>(
70 &self,
71 tx: TransactionParameters,
72 key: K,
73 ) -> error::Result<SignedTransaction> {
74 macro_rules! maybe {
75 ($o: expr, $f: expr) => {
76 async {
77 match $o {
78 Some(value) => Ok(value),
79 None => $f.await,
80 }
81 }
82 };
83 }
84 let from = key.address();
85 let (nonce, gas_price, chain_id) = futures::future::try_join3(
86 maybe!(tx.nonce, self.web3().eth().transaction_count(from, None)),
87 maybe!(tx.gas_price, self.web3().eth().gas_price()),
88 maybe!(tx.chain_id.map(U256::from), self.web3().eth().chain_id()),
89 )
90 .await?;
91 let chain_id = chain_id.as_u64();
92 let tx = Transaction {
93 to: tx.to,
94 nonce,
95 gas: tx.gas,
96 gas_price,
97 value: tx.value,
98 data: tx.data.0,
99 };
100 let signed = tx.sign(key, chain_id);
101 Ok(signed)
102 }
103
104 pub fn sign<S>(&self, message: S, key: impl signing::Key) -> SignedData
112 where
113 S: AsRef<[u8]>,
114 {
115 let message = message.as_ref();
116 let message_hash = self.hash_message(message);
117
118 let signature = key
119 .sign(&message_hash.as_bytes(), None)
120 .expect("hash is non-zero 32-bytes; qed");
121 let v = signature
122 .v
123 .try_into()
124 .expect("signature recovery in electrum notation always fits in a u8");
125
126 let signature_bytes = Bytes({
127 let mut bytes = Vec::with_capacity(65);
128 bytes.extend_from_slice(signature.r.as_bytes());
129 bytes.extend_from_slice(signature.s.as_bytes());
130 bytes.push(v);
131 bytes
132 });
133
134 let message = message.to_owned();
136
137 SignedData {
138 message,
139 message_hash,
140 v,
141 r: signature.r,
142 s: signature.s,
143 signature: signature_bytes,
144 }
145 }
146
147 pub fn recover<R>(&self, recovery: R) -> error::Result<Address>
152 where
153 R: Into<Recovery>,
154 {
155 let recovery = recovery.into();
156 let message_hash = match recovery.message {
157 RecoveryMessage::Data(ref message) => self.hash_message(message),
158 RecoveryMessage::Hash(hash) => hash,
159 };
160 let (signature, recovery_id) = recovery
161 .as_signature()
162 .ok_or_else(|| error::Error::Recovery(signing::RecoveryError::InvalidSignature))?;
163 let address = signing::recover(message_hash.as_bytes(), &signature, recovery_id)?;
164 Ok(address)
165 }
166 }
167 pub struct Transaction {
169 pub to: Option<Address>,
170 pub nonce: U256,
171 pub gas: U256,
172 pub gas_price: U256,
173 pub value: U256,
174 pub data: Vec<u8>,
175 }
176
177 impl Transaction {
178 fn rlp_append_unsigned(&self, rlp: &mut RlpStream, chain_id: u64) {
180 rlp.begin_list(9);
181 rlp.append(&self.nonce);
182 rlp.append(&self.gas_price);
183 rlp.append(&self.gas);
184 if let Some(to) = self.to {
185 rlp.append(&to);
186 } else {
187 rlp.append(&"");
188 }
189 rlp.append(&self.value);
190 rlp.append(&self.data);
191 rlp.append(&chain_id);
192 rlp.append(&0u8);
193 rlp.append(&0u8);
194 }
195
196 fn rlp_append_signed(&self, rlp: &mut RlpStream, signature: &Signature) {
198 rlp.begin_list(9);
199 rlp.append(&self.nonce);
200 rlp.append(&self.gas_price);
201 rlp.append(&self.gas);
202 if let Some(to) = self.to {
203 rlp.append(&to);
204 } else {
205 rlp.append(&"");
206 }
207 rlp.append(&self.value);
208 rlp.append(&self.data);
209 rlp.append(&signature.v);
210 rlp.append(&U256::from_big_endian(signature.r.as_bytes()));
211 rlp.append(&U256::from_big_endian(signature.s.as_bytes()));
212 }
213
214 pub fn sign(self, sign: impl signing::Key, chain_id: u64) -> SignedTransaction {
216 let mut rlp = RlpStream::new();
217 self.rlp_append_unsigned(&mut rlp, chain_id);
218
219 let hash = signing::keccak256(rlp.as_raw());
220 let signature = sign
221 .sign(&hash, Some(chain_id))
222 .expect("hash is non-zero 32-bytes; qed");
223
224 rlp.clear();
225 self.rlp_append_signed(&mut rlp, &signature);
226
227 let transaction_hash = signing::keccak256(rlp.as_raw()).into();
228 let raw_transaction = rlp.out().to_vec().into();
229
230 SignedTransaction {
231 message_hash: hash.into(),
232 v: signature.v,
233 r: signature.r,
234 s: signature.s,
235 raw_transaction,
236 transaction_hash,
237 }
238 }
239 }
240}
241
242#[cfg(all(test, not(target_arch = "wasm32")))]
243mod tests {
244 use super::*;
245 use crate::{
246 signing::{SecretKey, SecretKeyRef},
247 transports::test::TestTransport,
248 types::{Address, Recovery, SignedTransaction, TransactionParameters, U256},
249 };
250 use accounts_signing::*;
251 use hex_literal::hex;
252 use serde_json::json;
253
254 #[test]
255 fn accounts_sign_transaction() {
256 let tx = TransactionParameters {
260 to: Some(hex!("F0109fC8DF283027b6285cc889F5aA624EaC1F55").into()),
261 value: 1_000_000_000.into(),
262 gas: 2_000_000.into(),
263 ..Default::default()
264 };
265 let key = SecretKey::from_slice(&hex!(
266 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
267 ))
268 .unwrap();
269 let nonce = U256::zero();
270 let gas_price = U256::from(21_000_000_000u128);
271 let chain_id = "0x1";
272 let from: Address = signing::secret_key_address(&key);
273
274 let mut transport = TestTransport::default();
275 transport.add_response(json!(nonce));
276 transport.add_response(json!(gas_price));
277 transport.add_response(json!(chain_id));
278
279 let signed = {
280 let accounts = Accounts::new(&transport);
281 futures::executor::block_on(accounts.sign_transaction(tx, &key))
282 };
283
284 transport.assert_request(
285 "eth_getTransactionCount",
286 &[json!(from).to_string(), json!("latest").to_string()],
287 );
288 transport.assert_request("eth_gasPrice", &[]);
289 transport.assert_request("eth_chainId", &[]);
290 transport.assert_no_more_requests();
291
292 let expected = SignedTransaction {
293 message_hash: hex!("88cfbd7e51c7a40540b233cf68b62ad1df3e92462f1c6018d6d67eae0f3b08f5").into(),
294 v: 0x25,
295 r: hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895").into(),
296 s: hex!("727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").into(),
297 raw_transaction: hex!("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").into(),
298 transaction_hash: hex!("de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593").into(),
299 };
300
301 assert_eq!(signed, Ok(expected));
302 }
303
304 #[test]
305 fn accounts_sign_transaction_with_all_parameters() {
306 let key = SecretKey::from_slice(&hex!(
307 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
308 ))
309 .unwrap();
310
311 let accounts = Accounts::new(TestTransport::default());
312 futures::executor::block_on(accounts.sign_transaction(
313 TransactionParameters {
314 nonce: Some(0.into()),
315 gas_price: Some(1.into()),
316 chain_id: Some(42),
317 ..Default::default()
318 },
319 &key,
320 ))
321 .unwrap();
322
323 accounts.transport().assert_no_more_requests();
325 }
326
327 #[test]
328 fn accounts_hash_message() {
329 let accounts = Accounts::new(TestTransport::default());
333 let hash = accounts.hash_message("Hello World");
334
335 assert_eq!(
336 hash,
337 hex!("a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2").into()
338 );
339
340 accounts.transport().assert_no_more_requests();
342 }
343
344 #[test]
345 fn accounts_sign() {
346 let accounts = Accounts::new(TestTransport::default());
350
351 let key = SecretKey::from_slice(&hex!(
352 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
353 ))
354 .unwrap();
355 let signed = accounts.sign("Some data", SecretKeyRef::new(&key));
356
357 assert_eq!(
358 signed.message_hash,
359 hex!("1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655").into()
360 );
361 assert_eq!(
362 signed.signature.0,
363 hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c")
364 );
365
366 accounts.transport().assert_no_more_requests();
368 }
369
370 #[test]
371 fn accounts_recover() {
372 let accounts = Accounts::new(TestTransport::default());
376
377 let v = 0x1cu64;
378 let r = hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd").into();
379 let s = hex!("6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029").into();
380
381 let recovery = Recovery::new("Some data", v, r, s);
382 assert_eq!(
383 accounts.recover(recovery).unwrap(),
384 hex!("2c7536E3605D9C16a7a3D7b1898e529396a65c23").into()
385 );
386
387 accounts.transport().assert_no_more_requests();
389 }
390
391 #[test]
392 fn accounts_recover_signed() {
393 let key = SecretKey::from_slice(&hex!(
394 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
395 ))
396 .unwrap();
397 let address: Address = signing::secret_key_address(&key);
398
399 let accounts = Accounts::new(TestTransport::default());
400
401 let signed = accounts.sign("rust-web3 rocks!", &key);
402 let recovered = accounts.recover(&signed).unwrap();
403 assert_eq!(recovered, address);
404
405 let signed = futures::executor::block_on(accounts.sign_transaction(
406 TransactionParameters {
407 nonce: Some(0.into()),
408 gas_price: Some(1.into()),
409 chain_id: Some(42),
410 ..Default::default()
411 },
412 &key,
413 ))
414 .unwrap();
415 let recovered = accounts.recover(&signed).unwrap();
416 assert_eq!(recovered, address);
417
418 accounts.transport().assert_no_more_requests();
420 }
421
422 #[test]
423 fn sign_transaction_data() {
424 let tx = Transaction {
428 nonce: 0.into(),
429 gas: 2_000_000.into(),
430 gas_price: 234_567_897_654_321u64.into(),
431 to: Some(hex!("F0109fC8DF283027b6285cc889F5aA624EaC1F55").into()),
432 value: 1_000_000_000.into(),
433 data: Vec::new(),
434 };
435 let skey = SecretKey::from_slice(&hex!(
436 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
437 ))
438 .unwrap();
439 let key = SecretKeyRef::new(&skey);
440
441 let signed = tx.sign(key, 1);
442
443 let expected = SignedTransaction {
444 message_hash: hex!("6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5").into(),
445 v: 0x25,
446 r: hex!("09ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9c").into(),
447 s: hex!("440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428").into(),
448 raw_transaction: hex!("f86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428").into(),
449 transaction_hash: hex!("d8f64a42b57be0d565f385378db2f6bf324ce14a594afc05de90436e9ce01f60").into(),
450 };
451
452 assert_eq!(signed, expected);
453 }
454}