Skip to main content

reth_rpc/eth/helpers/
signer.rs

1//! An abstraction over ethereum signers.
2
3use 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/// Holds developer keys
13#[derive(Debug, Clone)]
14pub struct DevSigner {
15    addresses: Vec<Address>,
16    accounts: AddressMap<PrivateKeySigner>,
17}
18
19impl DevSigner {
20    /// Generates provided number of random dev signers
21    /// which satisfy [`EthSigner`] trait
22    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    /// Generates dev signers deterministically from a fixed mnemonic.
39    /// Uses the Ethereum derivation path: `m/44'/60'/0'/0/{index}`
40    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        // Hash message according to EIP 191:
86        // https://ethereum.org/es/developers/docs/apis/json-rpc/#eth_sign
87        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        // create local signer wallet from signing key
93        let signer = self.accounts.get(address).ok_or(SignError::NoAccount)?.clone();
94
95        // build and sign transaction with signer
96        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}