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