Skip to main content

xmtp_id/associations/
unverified.rs

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