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