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