1mod chain_rpc_verifier;
2mod remote_signature_verifier;
3use crate::associations::AccountId;
4use alloy::{
5 primitives::{BlockNumber, Bytes},
6 providers::DynProvider,
7};
8pub use chain_rpc_verifier::*;
9pub use remote_signature_verifier::*;
10use std::{collections::HashMap, fs, path::Path, sync::Arc};
11use thiserror::Error;
12use tracing::info;
13use url::Url;
14use xmtp_common::{MaybeSend, MaybeSync, RetryableError};
15
16static DEFAULT_CHAIN_URLS: &str = include_str!("chain_urls_default.json");
17
18#[derive(Debug, Error)]
19pub enum VerifierError {
20 #[error("unexpected result from ERC-6492 {0}")]
21 UnexpectedERC6492Result(String),
22 #[error(transparent)]
23 FromHex(#[from] hex::FromHexError),
24 #[error(transparent)]
25 Provider(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
26 #[error(transparent)]
27 Url(#[from] url::ParseError),
28 #[error(transparent)]
29 Io(#[from] std::io::Error),
30 #[error(transparent)]
31 Serde(#[from] serde_json::Error),
32 #[error("URLs must be preceded with eip144:")]
33 MalformedEipUrl,
34 #[error("verifier not present")]
35 NoVerifier,
36 #[error("hash was invalid length or otherwise malformed")]
37 InvalidHash(Vec<u8>),
38 #[error("{0}")]
39 Other(Box<dyn RetryableError>),
40}
41
42impl RetryableError for VerifierError {
43 fn is_retryable(&self) -> bool {
44 use VerifierError::*;
45 match self {
46 Io(_) => true,
47 NoVerifier => true,
48 Provider(_) => true,
49 Other(o) => o.is_retryable(),
50 _ => false,
51 }
52 }
53}
54
55#[xmtp_common::async_trait]
56pub trait SmartContractSignatureVerifier: MaybeSend + MaybeSync {
57 async fn is_valid_signature(
65 &self,
66 account_id: AccountId,
67 hash: [u8; 32],
68 signature: Bytes,
69 block_number: Option<BlockNumber>,
70 ) -> Result<ValidationResponse, VerifierError>;
71}
72
73#[xmtp_common::async_trait]
74impl<T> SmartContractSignatureVerifier for Arc<T>
75where
76 T: SmartContractSignatureVerifier,
77{
78 async fn is_valid_signature(
79 &self,
80 account_id: AccountId,
81 hash: [u8; 32],
82 signature: Bytes,
83 block_number: Option<BlockNumber>,
84 ) -> Result<ValidationResponse, VerifierError> {
85 (**self)
86 .is_valid_signature(account_id, hash, signature, block_number)
87 .await
88 }
89}
90
91#[xmtp_common::async_trait]
92impl<T> SmartContractSignatureVerifier for &T
93where
94 T: SmartContractSignatureVerifier,
95{
96 async fn is_valid_signature(
97 &self,
98 account_id: AccountId,
99 hash: [u8; 32],
100 signature: Bytes,
101 block_number: Option<BlockNumber>,
102 ) -> Result<ValidationResponse, VerifierError> {
103 (*self)
104 .is_valid_signature(account_id, hash, signature, block_number)
105 .await
106 }
107}
108
109#[xmtp_common::async_trait]
110impl<T> SmartContractSignatureVerifier for Box<T>
111where
112 T: SmartContractSignatureVerifier + ?Sized,
113{
114 async fn is_valid_signature(
115 &self,
116 account_id: AccountId,
117 hash: [u8; 32],
118 signature: Bytes,
119 block_number: Option<BlockNumber>,
120 ) -> Result<ValidationResponse, VerifierError> {
121 (**self)
122 .is_valid_signature(account_id, hash, signature, block_number)
123 .await
124 }
125}
126
127#[derive(Clone)]
128pub struct ValidationResponse {
129 pub is_valid: bool,
130 pub block_number: Option<u64>,
131 pub error: Option<String>,
132}
133
134pub struct MultiSmartContractSignatureVerifier {
135 verifiers: HashMap<String, Box<dyn SmartContractSignatureVerifier>>,
136}
137
138impl MultiSmartContractSignatureVerifier {
139 pub fn new(urls: HashMap<String, url::Url>) -> Result<Self, VerifierError> {
140 let verifiers = urls
141 .into_iter()
142 .map(|(chain_id, url)| {
143 Ok::<_, VerifierError>((
144 chain_id,
145 Box::new(RpcSmartContractWalletVerifier::new(url.to_string())?) as Box<_>,
146 ))
147 })
148 .collect::<Result<HashMap<_, _>, _>>()?;
149
150 Ok(Self { verifiers })
151 }
152
153 pub fn new_providers(providers: HashMap<String, DynProvider>) -> Result<Self, VerifierError> {
154 let verifiers = providers
155 .into_iter()
156 .map(|(chain_id, provider)| {
157 (
158 chain_id,
159 Box::new(RpcSmartContractWalletVerifier::new_from_provider(provider)) as Box<_>,
160 )
161 })
162 .collect();
163 Ok(Self { verifiers })
164 }
165
166 pub fn new_from_env() -> Result<Self, VerifierError> {
167 let urls: HashMap<String, Url> = serde_json::from_str(DEFAULT_CHAIN_URLS)?;
168 Self::new(urls)?.upgrade()
169 }
170
171 pub fn new_from_file(path: impl AsRef<Path>) -> Result<Self, VerifierError> {
172 let json = fs::read_to_string(path.as_ref())?;
173 let urls: HashMap<String, Url> = serde_json::from_str(&json)?;
174
175 Self::new(urls)
176 }
177
178 pub fn upgrade(mut self) -> Result<Self, VerifierError> {
180 for (id, verifier) in self.verifiers.iter_mut() {
181 let eip_id = id.split(":").nth(1).ok_or(VerifierError::MalformedEipUrl)?;
183 if let Ok(url) = std::env::var(format!("CHAIN_RPC_{eip_id}")) {
184 *verifier = Box::new(RpcSmartContractWalletVerifier::new(url)?);
185 } else {
186 info!("No upgraded chain url for chain {id}, using default.");
187 };
188 }
189
190 #[cfg(feature = "test-utils")]
191 if let Ok(url) = std::env::var("ANVIL_URL") {
192 info!("Adding anvil from env to the verifiers: {url}");
193 self.add_anvil(url)?;
194 } else {
195 use xmtp_configuration::DockerUrls;
196 info!("adding default anvil url @{}", DockerUrls::ANVIL);
197 self.add_anvil(DockerUrls::ANVIL.to_string())?;
198 }
199 Ok(self)
200 }
201
202 pub fn add_verifier(&mut self, id: String, url: String) -> Result<(), VerifierError> {
203 self.verifiers
204 .insert(id, Box::new(RpcSmartContractWalletVerifier::new(url)?));
205 Ok(())
206 }
207
208 pub fn add_anvil(&mut self, url: String) -> Result<(), VerifierError> {
209 self.verifiers.insert(
210 "eip155:31337".to_string(),
211 Box::new(RpcSmartContractWalletVerifier::new(url)?),
212 );
213 Ok(())
214 }
215}
216
217#[xmtp_common::async_trait]
218impl SmartContractSignatureVerifier for MultiSmartContractSignatureVerifier {
219 async fn is_valid_signature(
220 &self,
221 account_id: AccountId,
222 hash: [u8; 32],
223 signature: Bytes,
224 block_number: Option<BlockNumber>,
225 ) -> Result<ValidationResponse, VerifierError> {
226 if let Some(verifier) = self.verifiers.get(&account_id.chain_id) {
227 return verifier
228 .is_valid_signature(account_id, hash, signature, block_number)
229 .await;
230 }
231
232 Err(VerifierError::NoVerifier)
233 }
234}