reth_rpc/eth/helpers/
signer.rs

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