1#![allow(dead_code)]
2use crate::scw_verifier::SmartContractSignatureVerifier;
3
4use super::{
5 AccountId, Action, AddAssociation, CreateInbox, IdentityUpdate, RevokeAssociation,
6 SignatureError,
7 unsigned_actions::{
8 SignatureTextCreator, UnsignedAction, UnsignedAddAssociation,
9 UnsignedChangeRecoveryAddress, UnsignedCreateInbox, UnsignedIdentityUpdate,
10 UnsignedRevokeAssociation,
11 },
12 verified_signature::VerifiedSignature,
13};
14use crate::associations::AssociationError;
15use futures::future::try_join_all;
16use xmtp_db::identity_update::StoredIdentityUpdate;
17use xmtp_proto::xmtp::message_contents::SignedPublicKey as LegacySignedPublicKeyProto;
18
19impl TryFrom<StoredIdentityUpdate> for UnverifiedIdentityUpdate {
20 type Error = AssociationError;
21
22 fn try_from(update: StoredIdentityUpdate) -> Result<Self, Self::Error> {
23 Ok(UnverifiedIdentityUpdate::try_from(update.payload)?)
24 }
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct UnverifiedIdentityUpdate {
29 pub inbox_id: String,
30 pub client_timestamp_ns: u64,
31 pub actions: Vec<UnverifiedAction>,
32}
33
34impl UnverifiedIdentityUpdate {
35 pub fn new(inbox_id: String, client_timestamp_ns: u64, actions: Vec<UnverifiedAction>) -> Self {
36 Self {
37 inbox_id,
38 client_timestamp_ns,
39 actions,
40 }
41 }
42
43 fn signature_text(&self) -> String {
44 let unsigned_actions = self
45 .actions
46 .iter()
47 .map(|action| action.unsigned_action())
48 .collect();
49 let unsigned_identity_update = UnsignedIdentityUpdate::new(
50 unsigned_actions,
51 self.inbox_id.clone(),
52 self.client_timestamp_ns,
53 );
54
55 unsigned_identity_update.signature_text()
56 }
57
58 fn signatures(&self) -> Vec<UnverifiedSignature> {
59 self.actions
60 .iter()
61 .flat_map(|action| action.signatures())
62 .collect()
63 }
64
65 pub async fn to_verified(
66 &self,
67 scw_verifier: impl SmartContractSignatureVerifier,
68 ) -> Result<IdentityUpdate, SignatureError> {
69 let signature_text = self.signature_text();
70
71 let actions: Vec<Action> = try_join_all(
72 self.actions
73 .iter()
74 .map(|action| action.to_verified(&signature_text, &scw_verifier)),
75 )
76 .await?;
77
78 Ok(IdentityUpdate::new(
79 actions,
80 self.inbox_id.clone(),
81 self.client_timestamp_ns,
82 ))
83 }
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub enum UnverifiedAction {
88 CreateInbox(UnverifiedCreateInbox),
89 AddAssociation(UnverifiedAddAssociation),
90 RevokeAssociation(UnverifiedRevokeAssociation),
91 ChangeRecoveryAddress(UnverifiedChangeRecoveryAddress),
92}
93
94impl UnverifiedAction {
95 fn unsigned_action(&self) -> UnsignedAction {
96 match self {
97 UnverifiedAction::CreateInbox(action) => {
98 UnsignedAction::CreateInbox(action.unsigned_action.clone())
99 }
100 UnverifiedAction::AddAssociation(action) => {
101 UnsignedAction::AddAssociation(action.unsigned_action.clone())
102 }
103 UnverifiedAction::RevokeAssociation(action) => {
104 UnsignedAction::RevokeAssociation(action.unsigned_action.clone())
105 }
106 UnverifiedAction::ChangeRecoveryAddress(action) => {
107 UnsignedAction::ChangeRecoveryAddress(action.unsigned_action.clone())
108 }
109 }
110 }
111
112 fn signatures(&self) -> Vec<UnverifiedSignature> {
113 match self {
114 UnverifiedAction::CreateInbox(action) => {
115 vec![action.initial_identifier_signature.clone()]
116 }
117 UnverifiedAction::AddAssociation(action) => vec![
118 action.existing_member_signature.clone(),
119 action.new_member_signature.clone(),
120 ],
121 UnverifiedAction::RevokeAssociation(action) => {
122 vec![action.recovery_identifier_signature.clone()]
123 }
124 UnverifiedAction::ChangeRecoveryAddress(action) => {
125 vec![action.recovery_identifier_signature.clone()]
126 }
127 }
128 }
129
130 pub async fn to_verified<Text: AsRef<str>>(
131 &self,
132 signature_text: Text,
133 scw_verifier: impl SmartContractSignatureVerifier,
134 ) -> Result<Action, SignatureError> {
135 let action = match self {
136 UnverifiedAction::CreateInbox(action) => Action::CreateInbox(CreateInbox {
137 nonce: action.unsigned_action.nonce,
138 account_identifier: action.unsigned_action.account_identifier.clone(),
139 initial_identifier_signature: action
140 .initial_identifier_signature
141 .to_verified(signature_text.as_ref(), &scw_verifier)
142 .await?,
143 }),
144 UnverifiedAction::AddAssociation(action) => Action::AddAssociation(AddAssociation {
145 new_member_signature: action
146 .new_member_signature
147 .to_verified(signature_text.as_ref(), &scw_verifier)
148 .await?,
149 new_member_identifier: action.unsigned_action.new_member_identifier.clone(),
150 existing_member_signature: action
151 .existing_member_signature
152 .to_verified(signature_text.as_ref(), &scw_verifier)
153 .await?,
154 }),
155 UnverifiedAction::RevokeAssociation(action) => {
156 Action::RevokeAssociation(RevokeAssociation {
157 recovery_identifier_signature: action
158 .recovery_identifier_signature
159 .to_verified(signature_text.as_ref(), &scw_verifier)
160 .await?,
161 revoked_member: action.unsigned_action.revoked_member.clone(),
162 })
163 }
164 UnverifiedAction::ChangeRecoveryAddress(action) => {
165 Action::ChangeRecoveryIdentity(super::ChangeRecoveryIdentity {
166 recovery_identifier_signature: action
167 .recovery_identifier_signature
168 .to_verified(signature_text.as_ref(), &scw_verifier)
169 .await?,
170 new_recovery_identifier: action.unsigned_action.new_recovery_identifier.clone(),
171 })
172 }
173 };
174
175 Ok(action)
176 }
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub struct UnverifiedCreateInbox {
181 pub(crate) unsigned_action: UnsignedCreateInbox,
182 pub(crate) initial_identifier_signature: UnverifiedSignature,
183}
184
185impl UnverifiedCreateInbox {
186 pub fn new(
187 unsigned_action: UnsignedCreateInbox,
188 initial_identifier_signature: UnverifiedSignature,
189 ) -> Self {
190 Self {
191 unsigned_action,
192 initial_identifier_signature,
193 }
194 }
195}
196
197#[derive(Debug, Clone, PartialEq)]
198pub struct UnverifiedAddAssociation {
199 pub(crate) unsigned_action: UnsignedAddAssociation,
200 pub(crate) new_member_signature: UnverifiedSignature,
201 pub(crate) existing_member_signature: UnverifiedSignature,
202}
203
204impl UnverifiedAddAssociation {
205 pub fn new(
206 unsigned_action: UnsignedAddAssociation,
207 new_member_signature: UnverifiedSignature,
208 existing_member_signature: UnverifiedSignature,
209 ) -> Self {
210 Self {
211 unsigned_action,
212 new_member_signature,
213 existing_member_signature,
214 }
215 }
216}
217#[derive(Debug, Clone, PartialEq)]
218pub struct UnverifiedRevokeAssociation {
219 pub(crate) recovery_identifier_signature: UnverifiedSignature,
220 pub(crate) unsigned_action: UnsignedRevokeAssociation,
221}
222
223impl UnverifiedRevokeAssociation {
224 pub fn new(
225 unsigned_action: UnsignedRevokeAssociation,
226 recovery_identifier_signature: UnverifiedSignature,
227 ) -> Self {
228 Self {
229 unsigned_action,
230 recovery_identifier_signature,
231 }
232 }
233}
234
235#[derive(Debug, Clone, PartialEq)]
236pub struct UnverifiedChangeRecoveryAddress {
237 pub(crate) recovery_identifier_signature: UnverifiedSignature,
238 pub(crate) unsigned_action: UnsignedChangeRecoveryAddress,
239}
240
241impl UnverifiedChangeRecoveryAddress {
242 pub fn new(
243 unsigned_action: UnsignedChangeRecoveryAddress,
244 recovery_identifier_signature: UnverifiedSignature,
245 ) -> Self {
246 Self {
247 unsigned_action,
248 recovery_identifier_signature,
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq)]
254pub enum UnverifiedSignature {
255 InstallationKey(UnverifiedInstallationKeySignature),
256 RecoverableEcdsa(UnverifiedRecoverableEcdsaSignature),
257 SmartContractWallet(UnverifiedSmartContractWalletSignature),
258 LegacyDelegated(UnverifiedLegacyDelegatedSignature),
259 Passkey(UnverifiedPasskeySignature),
260}
261
262impl UnverifiedSignature {
263 pub async fn to_verified<Text: AsRef<str>>(
264 &self,
265 signature_text: Text,
266 scw_verifier: impl SmartContractSignatureVerifier,
267 ) -> Result<VerifiedSignature, SignatureError> {
268 match self {
269 UnverifiedSignature::InstallationKey(sig) => VerifiedSignature::from_installation_key(
270 signature_text,
271 &sig.signature_bytes,
272 *sig.verifying_key(),
273 ),
274 UnverifiedSignature::RecoverableEcdsa(sig) => {
275 VerifiedSignature::from_recoverable_ecdsa(signature_text, &sig.signature_bytes)
276 }
277 UnverifiedSignature::SmartContractWallet(sig) => {
278 VerifiedSignature::from_smart_contract_wallet(
279 signature_text,
280 scw_verifier,
281 &sig.signature_bytes,
282 sig.account_id.clone(),
283 &mut Some(sig.block_number),
284 )
285 .await
286 }
287 UnverifiedSignature::LegacyDelegated(sig) => VerifiedSignature::from_legacy_delegated(
288 signature_text,
289 &sig.legacy_key_signature.signature_bytes,
290 sig.signed_public_key_proto.clone(),
291 ),
292 UnverifiedSignature::Passkey(sig) => VerifiedSignature::from_passkey(
293 signature_text,
294 &sig.public_key,
295 &sig.signature,
296 &sig.authenticator_data,
297 &sig.client_data_json,
298 ),
299 }
300 }
301
302 pub fn new_recoverable_ecdsa(signature: Vec<u8>) -> Self {
303 Self::RecoverableEcdsa(UnverifiedRecoverableEcdsaSignature::new(signature))
304 }
305
306 pub fn new_installation_key(
307 signature: Vec<u8>,
308 verifying_key: ed25519_dalek::VerifyingKey,
309 ) -> Self {
310 Self::InstallationKey(UnverifiedInstallationKeySignature::new(
311 signature,
312 verifying_key,
313 ))
314 }
315
316 pub fn new_passkey(
317 public_key: Vec<u8>,
318 signature: Vec<u8>,
319 authenticator_data: Vec<u8>,
320 client_data_json: Vec<u8>,
321 ) -> Self {
322 Self::Passkey(UnverifiedPasskeySignature {
323 client_data_json,
324 authenticator_data,
325 signature,
326 public_key,
327 })
328 }
329
330 pub fn new_smart_contract_wallet(
331 signature: Vec<u8>,
332 account_id: AccountId,
333 block_number: u64,
334 ) -> Self {
335 Self::SmartContractWallet(UnverifiedSmartContractWalletSignature::new(
336 signature,
337 account_id,
338 block_number,
339 ))
340 }
341
342 pub fn new_legacy_delegated(
343 signature: Vec<u8>,
344 signed_public_key_proto: LegacySignedPublicKeyProto,
345 ) -> Self {
346 Self::LegacyDelegated(UnverifiedLegacyDelegatedSignature::new(
347 UnverifiedRecoverableEcdsaSignature::new(signature),
348 signed_public_key_proto,
349 ))
350 }
351}
352
353#[derive(Debug, Clone, PartialEq)]
354pub struct UnverifiedInstallationKeySignature {
355 pub(crate) signature_bytes: Vec<u8>,
356 pub(crate) verifying_key: Box<ed25519_dalek::VerifyingKey>,
359}
360
361impl UnverifiedInstallationKeySignature {
362 pub fn new(signature_bytes: Vec<u8>, verifying_key: ed25519_dalek::VerifyingKey) -> Self {
363 Self {
364 signature_bytes,
365 verifying_key: Box::new(verifying_key),
366 }
367 }
368
369 pub fn verifying_key(&self) -> &ed25519_dalek::VerifyingKey {
370 self.verifying_key.as_ref()
371 }
372
373 pub fn verifying_key_bytes(&self) -> Vec<u8> {
374 self.verifying_key.as_ref().as_ref().to_vec()
375 }
376}
377
378#[derive(Debug, Clone, PartialEq)]
379pub struct UnverifiedPasskeySignature {
380 pub public_key: Vec<u8>,
382 pub signature: Vec<u8>,
383 pub authenticator_data: Vec<u8>,
384 pub client_data_json: Vec<u8>,
385}
386
387#[derive(Debug, Clone, PartialEq)]
388pub struct UnverifiedRecoverableEcdsaSignature {
389 pub(crate) signature_bytes: Vec<u8>,
390}
391
392impl UnverifiedRecoverableEcdsaSignature {
393 pub fn new(signature_bytes: Vec<u8>) -> Self {
394 Self { signature_bytes }
395 }
396 pub fn signature_bytes(&self) -> &[u8] {
397 &self.signature_bytes
398 }
399}
400
401#[derive(Debug, Clone, PartialEq)]
402pub struct UnverifiedSmartContractWalletSignature {
403 pub(crate) signature_bytes: Vec<u8>,
404 pub(crate) account_id: AccountId,
405 pub(crate) block_number: u64,
406}
407
408#[derive(Debug, Clone, PartialEq)]
409pub struct NewUnverifiedSmartContractWalletSignature {
410 pub(crate) signature_bytes: Vec<u8>,
411 pub(crate) account_id: AccountId,
412 pub(crate) block_number: Option<u64>,
413}
414
415impl NewUnverifiedSmartContractWalletSignature {
416 pub fn new(signature_bytes: Vec<u8>, account_id: AccountId, block_number: Option<u64>) -> Self {
417 Self {
418 account_id,
419 block_number,
420 signature_bytes,
421 }
422 }
423}
424
425impl UnverifiedSmartContractWalletSignature {
426 pub fn new(signature_bytes: Vec<u8>, account_id: AccountId, block_number: u64) -> Self {
427 Self {
428 signature_bytes,
429 account_id,
430 block_number,
431 }
432 }
433}
434
435#[derive(Debug, Clone, PartialEq)]
436pub struct UnverifiedLegacyDelegatedSignature {
437 pub(crate) legacy_key_signature: UnverifiedRecoverableEcdsaSignature,
438 pub(crate) signed_public_key_proto: LegacySignedPublicKeyProto,
439}
440
441impl UnverifiedLegacyDelegatedSignature {
442 pub fn new(
443 legacy_key_signature: UnverifiedRecoverableEcdsaSignature,
444 signed_public_key_proto: LegacySignedPublicKeyProto,
445 ) -> Self {
446 Self {
447 legacy_key_signature,
448 signed_public_key_proto,
449 }
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 #[cfg(target_arch = "wasm32")]
456 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker);
457
458 use crate::associations::{member::Identifier, unsigned_actions::UnsignedCreateInbox};
459
460 use super::{
461 UnverifiedAction, UnverifiedCreateInbox, UnverifiedIdentityUpdate,
462 UnverifiedRecoverableEcdsaSignature, UnverifiedSignature,
463 };
464
465 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
466 #[cfg_attr(not(target_arch = "wasm32"), test)]
467 fn create_identity_update() {
468 let account_identifier = Identifier::rand_ethereum();
469 let nonce = 1;
470 let update = UnverifiedIdentityUpdate {
471 inbox_id: account_identifier.inbox_id(nonce).unwrap(),
472 client_timestamp_ns: 10,
473 actions: vec![UnverifiedAction::CreateInbox(UnverifiedCreateInbox {
474 unsigned_action: UnsignedCreateInbox {
475 account_identifier: account_identifier.clone(),
476 nonce,
477 },
478 initial_identifier_signature: UnverifiedSignature::RecoverableEcdsa(
479 UnverifiedRecoverableEcdsaSignature {
480 signature_bytes: vec![1, 2, 3],
481 },
482 ),
483 })],
484 };
485 assert!(
486 update
487 .signature_text()
488 .contains(format!("(Owner: {})", account_identifier).as_str()),
489 "could not find account address in signature text: {}",
490 update.signature_text()
491 );
492 }
493}