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 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 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}