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