thegraph_core/signed_message/
signing.rs

1use alloy::{
2    primitives::{Address, SignatureError},
3    signers::{
4        Error as SignerError, SignerSync, UnsupportedSignerOperation,
5        k256::ecdsa::Error as EcdsaError,
6    },
7    sol_types::{Eip712Domain, SolStruct},
8};
9
10use super::message::{SignedMessage, ToSolStruct};
11
12/// Errors that can occur when signing a message.
13#[derive(Debug, thiserror::Error)]
14pub enum SigningError {
15    /// The signer does not support the operation
16    #[error("operation `{0}` is not supported by the signer")]
17    UnsupportedOperation(UnsupportedSignerOperation),
18
19    /// The ECDSA signature failed
20    #[error(transparent)]
21    Ecdsa(#[from] EcdsaError),
22
23    /// Generic error
24    #[error(transparent)]
25    Other(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
26}
27
28/// Errors that can occur when recovering the signer's address of a message.
29#[derive(Debug, thiserror::Error)]
30#[error(transparent)]
31pub struct RecoverSignerError(#[from] SignatureError);
32
33/// Errors that can occur when verifying the signer's address of a message.
34#[derive(Debug, thiserror::Error)]
35pub enum VerificationError {
36    /// Errors in signature parsing or verification
37    #[error(transparent)]
38    SignatureError(#[from] SignatureError),
39
40    /// The signer's address does not match the expected address
41    #[error("expected signer `{expected}` but received `{received}`")]
42    InvalidSigner {
43        /// The expected signer's address
44        expected: Address,
45        /// The received signer's address
46        received: Address,
47    },
48}
49
50/// Signs a message using the [EIP-712] standard
51///
52/// Returns a [`SignedMessage`] containing the message and the ECDSA signature of the message
53///
54/// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712 "EIP-712"
55pub fn sign<S, M, MSol>(
56    signer: &S,
57    domain: &Eip712Domain,
58    message: M,
59) -> Result<SignedMessage<M>, SigningError>
60where
61    S: SignerSync,
62    M: ToSolStruct<MSol>,
63    MSol: SolStruct,
64{
65    let message_sol = message.to_sol_struct();
66    let signature = signer
67        .sign_typed_data_sync(&message_sol, domain)
68        .map_err(|err| match err {
69            SignerError::UnsupportedOperation(err) => SigningError::UnsupportedOperation(err),
70            SignerError::TransactionChainIdMismatch { .. } => {
71                unreachable!("sign_typed_data_sync should not return TransactionChainIdMismatch")
72            }
73            SignerError::DynAbiError(_) => {
74                unreachable!("sign_typed_data_sync should not return DynAbiError")
75            }
76            SignerError::Ecdsa(err) => SigningError::Ecdsa(err),
77            SignerError::HexError(_) => {
78                unreachable!("sign_typed_data_sync should not return HexError")
79            }
80            SignerError::SignatureError(_) => {
81                unreachable!("sign_typed_data_sync should not return SignatureError")
82            }
83            SignerError::Other(err) => SigningError::Other(err),
84        })?;
85    Ok(SignedMessage { message, signature })
86}
87
88/// Recover the signer's address  an [EIP-712] signed message
89///
90/// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712 "EIP-712"
91pub fn recover_signer_address<M, MSol>(
92    domain: &Eip712Domain,
93    signed_message: &SignedMessage<M>,
94) -> Result<Address, RecoverSignerError>
95where
96    M: ToSolStruct<MSol>,
97    MSol: SolStruct,
98{
99    let message_sol = signed_message.message.to_sol_struct();
100    let recovery_message_hash = message_sol.eip712_signing_hash(domain);
101    let recovered_address = signed_message
102        .signature
103        .recover_address_from_prehash(&recovery_message_hash)?;
104    Ok(recovered_address)
105}
106
107/// Verify the signer's address of an [EIP-712] signed message
108///
109/// Returns `Ok(())` if the  signer's address retrieved from the signature matches the expected
110/// address. Otherwise, returns a [`VerificationError`] with details about the mismatch.
111///
112/// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712 "EIP-712"
113pub fn verify<M, MSol>(
114    domain: &Eip712Domain,
115    signed_message: &SignedMessage<M>,
116    expected_address: &Address,
117) -> Result<(), VerificationError>
118where
119    M: ToSolStruct<MSol>,
120    MSol: SolStruct,
121{
122    let recovered_address =
123        recover_signer_address(domain, signed_message).map_err(|RecoverSignerError(err)| err)?;
124
125    if recovered_address != *expected_address {
126        Err(VerificationError::InvalidSigner {
127            expected: expected_address.to_owned(),
128            received: recovered_address,
129        })
130    } else {
131        Ok(())
132    }
133}