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