1use alloy_dyn_abi::TypedData;
4use alloy_eips::eip2718::Decodable2718;
5use alloy_primitives::{eip191_hash_message, Address, Signature, B256};
6use alloy_signer::SignerSync;
7use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner};
8use reth_rpc_convert::SignableTxRequest;
9use reth_rpc_eth_api::helpers::{signer::Result, EthSigner};
10use reth_rpc_eth_types::SignError;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
15pub struct DevSigner {
16 addresses: Vec<Address>,
17 accounts: HashMap<Address, PrivateKeySigner>,
18}
19
20impl DevSigner {
21 pub fn random_signers<T: Decodable2718, TxReq: SignableTxRequest<T>>(
24 num: u32,
25 ) -> Vec<Box<dyn EthSigner<T, TxReq> + 'static>> {
26 let mut signers = Vec::with_capacity(num as usize);
27 for _ in 0..num {
28 let sk = PrivateKeySigner::random();
29
30 let address = sk.address();
31 let addresses = vec![address];
32
33 let accounts = HashMap::from([(address, sk)]);
34 signers.push(Box::new(Self { addresses, accounts }) as Box<dyn EthSigner<T, TxReq>>);
35 }
36 signers
37 }
38
39 pub fn from_mnemonic<T: Decodable2718, TxReq: SignableTxRequest<T>>(
42 mnemonic: &str,
43 num: u32,
44 ) -> Vec<Box<dyn EthSigner<T, TxReq> + 'static>> {
45 let mut signers = Vec::with_capacity(num as usize);
46
47 for i in 0..num {
48 let sk = MnemonicBuilder::<English>::default()
49 .phrase(mnemonic)
50 .index(i)
51 .expect("invalid derivation path")
52 .build()
53 .expect("failed to build signer from mnemonic");
54
55 let address = sk.address();
56 let addresses = vec![address];
57 let accounts = HashMap::from([(address, sk)]);
58
59 signers.push(Box::new(Self { addresses, accounts }) as Box<dyn EthSigner<T, TxReq>>);
60 }
61
62 signers
63 }
64
65 fn get_key(&self, account: Address) -> Result<&PrivateKeySigner> {
66 self.accounts.get(&account).ok_or(SignError::NoAccount)
67 }
68
69 fn sign_hash(&self, hash: B256, account: Address) -> Result<Signature> {
70 let signature = self.get_key(account)?.sign_hash_sync(&hash);
71 signature.map_err(|_| SignError::CouldNotSign)
72 }
73}
74
75#[async_trait::async_trait]
76impl<T: Decodable2718, TxReq: SignableTxRequest<T>> EthSigner<T, TxReq> for DevSigner {
77 fn accounts(&self) -> Vec<Address> {
78 self.addresses.clone()
79 }
80
81 fn is_signer_for(&self, addr: &Address) -> bool {
82 self.accounts.contains_key(addr)
83 }
84
85 async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature> {
86 let hash = eip191_hash_message(message);
89 self.sign_hash(hash, address)
90 }
91
92 async fn sign_transaction(&self, request: TxReq, address: &Address) -> Result<T> {
93 let signer = self.accounts.get(address).ok_or(SignError::NoAccount)?.clone();
95
96 let tx = request
98 .try_build_and_sign(&signer)
99 .await
100 .map_err(|_| SignError::InvalidTransactionRequest)?;
101
102 Ok(tx)
103 }
104
105 fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result<Signature> {
106 let encoded = payload.eip712_signing_hash().map_err(|_| SignError::InvalidTypedData)?;
107 self.sign_hash(encoded, address)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use alloy_consensus::Transaction;
115 use alloy_primitives::{Bytes, U256};
116 use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
117 use reth_ethereum_primitives::TransactionSigned;
118 use revm_primitives::TxKind;
119
120 fn build_signer() -> DevSigner {
121 let signer: PrivateKeySigner =
122 "4646464646464646464646464646464646464646464646464646464646464646".parse().unwrap();
123 let address = signer.address();
124 let accounts = HashMap::from([(address, signer)]);
125 let addresses = vec![address];
126 DevSigner { addresses, accounts }
127 }
128
129 #[tokio::test]
130 async fn test_sign_type_data() {
131 let eip_712_example = r#"{
132 "types": {
133 "EIP712Domain": [
134 {
135 "name": "name",
136 "type": "string"
137 },
138 {
139 "name": "version",
140 "type": "string"
141 },
142 {
143 "name": "chainId",
144 "type": "uint256"
145 },
146 {
147 "name": "verifyingContract",
148 "type": "address"
149 }
150 ],
151 "Person": [
152 {
153 "name": "name",
154 "type": "string"
155 },
156 {
157 "name": "wallet",
158 "type": "address"
159 }
160 ],
161 "Mail": [
162 {
163 "name": "from",
164 "type": "Person"
165 },
166 {
167 "name": "to",
168 "type": "Person"
169 },
170 {
171 "name": "contents",
172 "type": "string"
173 }
174 ]
175 },
176 "primaryType": "Mail",
177 "domain": {
178 "name": "Ether Mail",
179 "version": "1",
180 "chainId": 1,
181 "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
182 },
183 "message": {
184 "from": {
185 "name": "Cow",
186 "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
187 },
188 "to": {
189 "name": "Bob",
190 "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
191 },
192 "contents": "Hello, Bob!"
193 }
194 }"#;
195 let data: TypedData = serde_json::from_str(eip_712_example).unwrap();
196 let signer = build_signer();
197 let from = *signer.addresses.first().unwrap();
198 let sig = EthSigner::<reth_ethereum_primitives::TransactionSigned>::sign_typed_data(
199 &signer, from, &data,
200 )
201 .unwrap();
202 let expected = Signature::new(
203 U256::from_str_radix(
204 "5318aee9942b84885761bb20e768372b76e7ee454fc4d39b59ce07338d15a06c",
205 16,
206 )
207 .unwrap(),
208 U256::from_str_radix(
209 "5e585a2f4882ec3228a9303244798b47a9102e4be72f48159d890c73e4511d79",
210 16,
211 )
212 .unwrap(),
213 false,
214 );
215 assert_eq!(sig, expected)
216 }
217
218 #[tokio::test]
219 async fn test_signer() {
220 let message = b"Test message";
221 let signer = build_signer();
222 let from = *signer.addresses.first().unwrap();
223 let sig =
224 EthSigner::<reth_ethereum_primitives::TransactionSigned>::sign(&signer, from, message)
225 .await
226 .unwrap();
227 let expected = Signature::new(
228 U256::from_str_radix(
229 "54313da7432e4058b8d22491b2e7dbb19c7186c35c24155bec0820a8a2bfe0c1",
230 16,
231 )
232 .unwrap(),
233 U256::from_str_radix(
234 "687250f11a3d4435004c04a4cb60e846bc27997271d67f21c6c8170f17a25e10",
235 16,
236 )
237 .unwrap(),
238 true,
239 );
240 assert_eq!(sig, expected)
241 }
242
243 #[tokio::test]
244 async fn test_sign_transaction() {
245 let message = b"Test message";
246 let signer = build_signer();
247 let from = *signer.addresses.first().unwrap();
248 let request = TransactionRequest {
249 chain_id: Some(1u64),
250 from: Some(from),
251 to: Some(TxKind::Create),
252 gas: Some(1000),
253 gas_price: Some(1000u128),
254 value: Some(U256::from(1000)),
255 input: TransactionInput {
256 data: Some(Bytes::from(message.to_vec())),
257 input: Some(Bytes::from(message.to_vec())),
258 },
259 nonce: Some(0u64),
260 ..Default::default()
261 };
262 let txn_signed: std::result::Result<TransactionSigned, SignError> =
263 signer.sign_transaction(request, &from).await;
264 assert!(txn_signed.is_ok());
265
266 assert_eq!(Bytes::from(message.to_vec()), txn_signed.unwrap().input().0);
267 }
268}