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