alloy_signer/
signer.rs

1use crate::Result;
2use alloy_primitives::{eip191_hash_message, Address, ChainId, Signature, B256};
3use async_trait::async_trait;
4use auto_impl::auto_impl;
5pub use either::Either;
6
7#[cfg(feature = "eip712")]
8use alloy_dyn_abi::eip712::TypedData;
9#[cfg(feature = "eip712")]
10use alloy_sol_types::{Eip712Domain, SolStruct};
11
12/// Asynchronous Ethereum signer.
13///
14/// All provided implementations rely on [`sign_hash`](Signer::sign_hash). A signer may not always
15/// be able to implement this method, in which case it should return
16/// [`UnsupportedOperation`](crate::Error::UnsupportedOperation), and implement all the signing
17/// methods directly.
18///
19/// Synchronous signers should implement both this trait and [`SignerSync`].
20///
21/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
22#[cfg_attr(target_family = "wasm", async_trait(?Send))]
23#[cfg_attr(not(target_family = "wasm"), async_trait)]
24#[auto_impl(&mut, Box)]
25pub trait Signer<Sig = Signature> {
26    /// Signs the given hash.
27    async fn sign_hash(&self, hash: &B256) -> Result<Sig>;
28
29    /// Signs the hash of the provided message after prefixing it, as specified in [EIP-191].
30    ///
31    /// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
32    #[inline]
33    async fn sign_message(&self, message: &[u8]) -> Result<Sig> {
34        self.sign_hash(&eip191_hash_message(message)).await
35    }
36
37    /// Encodes and signs the typed data according to [EIP-712].
38    ///
39    /// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
40    #[cfg(feature = "eip712")]
41    #[inline]
42    #[auto_impl(keep_default_for(&mut, Box))]
43    async fn sign_typed_data<T: SolStruct + Send + Sync>(
44        &self,
45        payload: &T,
46        domain: &Eip712Domain,
47    ) -> Result<Sig>
48    where
49        Self: Sized,
50    {
51        self.sign_hash(&payload.eip712_signing_hash(domain)).await
52    }
53
54    /// Encodes and signs the typed data according to [EIP-712] for Signers that are not dynamically
55    /// sized.
56    #[cfg(feature = "eip712")]
57    #[inline]
58    async fn sign_dynamic_typed_data(&self, payload: &TypedData) -> Result<Sig> {
59        self.sign_hash(&payload.eip712_signing_hash()?).await
60    }
61
62    /// Returns the signer's Ethereum Address.
63    fn address(&self) -> Address;
64
65    /// Returns the signer's chain ID.
66    fn chain_id(&self) -> Option<ChainId>;
67
68    /// Sets the signer's chain ID.
69    fn set_chain_id(&mut self, chain_id: Option<ChainId>);
70
71    /// Sets the signer's chain ID and returns `self`.
72    #[inline]
73    #[must_use]
74    #[auto_impl(keep_default_for(&mut, Box))]
75    fn with_chain_id(mut self, chain_id: Option<ChainId>) -> Self
76    where
77        Self: Sized,
78    {
79        self.set_chain_id(chain_id);
80        self
81    }
82}
83
84/// Synchronous Ethereum signer.
85///
86/// All provided implementations rely on [`sign_hash_sync`](SignerSync::sign_hash_sync). A signer
87/// may not always be able to implement this method, in which case it should return
88/// [`UnsupportedOperation`](crate::Error::UnsupportedOperation), and implement all the signing
89/// methods directly.
90///
91/// Synchronous signers should also implement [`Signer`], as they are always able to by delegating
92/// the asynchronous methods to the synchronous ones.
93///
94/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
95#[auto_impl(&, &mut, Box, Rc, Arc)]
96pub trait SignerSync<Sig = Signature> {
97    /// Signs the given hash.
98    fn sign_hash_sync(&self, hash: &B256) -> Result<Sig>;
99
100    /// Signs the hash of the provided message after prefixing it, as specified in [EIP-191].
101    ///
102    /// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
103    #[inline]
104    fn sign_message_sync(&self, message: &[u8]) -> Result<Sig> {
105        self.sign_hash_sync(&eip191_hash_message(message))
106    }
107
108    /// Encodes and signs the typed data according to [EIP-712].
109    ///
110    /// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
111    #[cfg(feature = "eip712")]
112    #[inline]
113    #[auto_impl(keep_default_for(&, &mut, Box, Rc, Arc))]
114    fn sign_typed_data_sync<T: SolStruct>(&self, payload: &T, domain: &Eip712Domain) -> Result<Sig>
115    where
116        Self: Sized,
117    {
118        self.sign_hash_sync(&payload.eip712_signing_hash(domain))
119    }
120
121    /// Encodes and signs the typed data according to [EIP-712] for Signers that are not dynamically
122    /// sized.
123    ///
124    /// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
125    #[cfg(feature = "eip712")]
126    #[inline]
127    fn sign_dynamic_typed_data_sync(&self, payload: &TypedData) -> Result<Sig> {
128        let hash = payload.eip712_signing_hash()?;
129        self.sign_hash_sync(&hash)
130    }
131
132    /// Returns the signer's chain ID.
133    fn chain_id_sync(&self) -> Option<ChainId>;
134}
135
136#[cfg_attr(target_family = "wasm", async_trait(?Send))]
137#[cfg_attr(not(target_family = "wasm"), async_trait)]
138#[async_trait]
139impl<A, B, Sig> Signer<Sig> for Either<A, B>
140where
141    A: Signer<Sig> + Send + Sync,
142    B: Signer<Sig> + Send + Sync,
143    Sig: Send,
144{
145    async fn sign_hash(&self, hash: &B256) -> Result<Sig> {
146        match self {
147            Self::Left(signer) => signer.sign_hash(hash).await,
148            Self::Right(signer) => signer.sign_hash(hash).await,
149        }
150    }
151
152    fn address(&self) -> Address {
153        match self {
154            Self::Left(signer) => signer.address(),
155            Self::Right(signer) => signer.address(),
156        }
157    }
158
159    fn chain_id(&self) -> Option<ChainId> {
160        match self {
161            Self::Left(signer) => signer.chain_id(),
162            Self::Right(signer) => signer.chain_id(),
163        }
164    }
165
166    fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
167        match self {
168            Self::Left(signer) => signer.set_chain_id(chain_id),
169            Self::Right(signer) => signer.set_chain_id(chain_id),
170        }
171    }
172}
173
174impl<A, B, Sig> SignerSync<Sig> for Either<A, B>
175where
176    A: SignerSync<Sig>,
177    B: SignerSync<Sig>,
178{
179    fn sign_hash_sync(&self, hash: &B256) -> Result<Sig> {
180        match self {
181            Self::Left(signer) => signer.sign_hash_sync(hash),
182            Self::Right(signer) => signer.sign_hash_sync(hash),
183        }
184    }
185
186    fn chain_id_sync(&self) -> Option<ChainId> {
187        match self {
188            Self::Left(signer) => signer.chain_id_sync(),
189            Self::Right(signer) => signer.chain_id_sync(),
190        }
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::{Error, UnsupportedSignerOperation};
198    use assert_matches::assert_matches;
199    use std::sync::Arc;
200
201    struct _ObjectSafe(Box<dyn Signer>, Box<dyn SignerSync>);
202
203    #[tokio::test]
204    async fn unimplemented() {
205        #[cfg(feature = "eip712")]
206        alloy_sol_types::sol! {
207            #[derive(Default, serde::Serialize)]
208            struct Eip712Data {
209                uint64 a;
210            }
211        }
212
213        async fn test_unimplemented_signer<S: Signer + SignerSync + Send + Sync>(s: &S) {
214            test_unsized_unimplemented_signer(s).await;
215            test_unsized_unimplemented_signer_sync(s);
216
217            #[cfg(feature = "eip712")]
218            assert!(s
219                .sign_typed_data_sync(&Eip712Data::default(), &Eip712Domain::default())
220                .is_err());
221            #[cfg(feature = "eip712")]
222            assert!(s
223                .sign_typed_data(&Eip712Data::default(), &Eip712Domain::default())
224                .await
225                .is_err());
226        }
227
228        async fn test_unsized_unimplemented_signer<S: Signer + ?Sized + Send + Sync>(s: &S) {
229            assert_matches!(
230                s.sign_hash(&B256::ZERO).await,
231                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
232            );
233
234            assert_matches!(
235                s.sign_message(&[]).await,
236                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
237            );
238
239            #[cfg(feature = "eip712")]
240            assert_matches!(
241                s.sign_dynamic_typed_data(&TypedData::from_struct(&Eip712Data::default(), None))
242                    .await,
243                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
244            );
245
246            assert_eq!(s.chain_id(), None);
247        }
248
249        fn test_unsized_unimplemented_signer_sync<S: SignerSync + ?Sized>(s: &S) {
250            assert_matches!(
251                s.sign_hash_sync(&B256::ZERO),
252                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
253            );
254
255            assert_matches!(
256                s.sign_message_sync(&[]),
257                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
258            );
259
260            #[cfg(feature = "eip712")]
261            assert_matches!(
262                s.sign_dynamic_typed_data_sync(&TypedData::from_struct(
263                    &Eip712Data::default(),
264                    None
265                )),
266                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
267            );
268
269            assert_eq!(s.chain_id_sync(), None);
270        }
271
272        struct UnimplementedSigner;
273
274        #[cfg_attr(target_family = "wasm", async_trait(?Send))]
275        #[cfg_attr(not(target_family = "wasm"), async_trait)]
276        impl Signer for UnimplementedSigner {
277            async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
278                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
279            }
280
281            fn address(&self) -> Address {
282                Address::ZERO
283            }
284
285            fn chain_id(&self) -> Option<ChainId> {
286                None
287            }
288
289            fn set_chain_id(&mut self, _chain_id: Option<ChainId>) {}
290        }
291
292        impl SignerSync for UnimplementedSigner {
293            fn sign_hash_sync(&self, _hash: &B256) -> Result<Signature> {
294                Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash))
295            }
296
297            fn chain_id_sync(&self) -> Option<ChainId> {
298                None
299            }
300        }
301
302        test_unimplemented_signer(&UnimplementedSigner).await;
303        test_unsized_unimplemented_signer(&UnimplementedSigner as &(dyn Signer + Send + Sync))
304            .await;
305        test_unsized_unimplemented_signer_sync(
306            &UnimplementedSigner as &(dyn SignerSync + Send + Sync),
307        );
308
309        test_unsized_unimplemented_signer(
310            &(Box::new(UnimplementedSigner) as Box<dyn Signer + Send + Sync>),
311        )
312        .await;
313        test_unsized_unimplemented_signer_sync(
314            &(Box::new(UnimplementedSigner) as Box<dyn SignerSync + Send + Sync>),
315        );
316
317        test_unsized_unimplemented_signer_sync(
318            &(Arc::new(UnimplementedSigner) as Arc<dyn SignerSync + Send + Sync>),
319        );
320    }
321}