1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(deny_unknown_fields)]
5pub struct DeviceRecoveryReadiness {
6 pub registration: StoreDeviceRegistrationRef,
7 pub initial_ack: StoreAckRef,
8 pub bootstrap_cut: StoreHistoryCut,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct OwnerRecoveryNodeBody {
14 pub store_root_hash: ObjectHash,
15 pub recovery_id: DeviceRecoveryId,
16 pub owner_pubkey: String,
17 pub owner_grant: MembershipGrantId,
18 pub sequence: u64,
19 pub membership: StoreMembershipStateRef,
20 pub predecessor: Option<OwnerRecoveryNodeRef>,
21 pub readiness: DeviceRecoveryReadiness,
22 pub next_slot: ObjectSlot,
23}
24
25impl SignedBody for OwnerRecoveryNodeBody {
26 const DOMAIN: &'static [u8] = OWNER_RECOVERY_NODE_DOMAIN;
27}
28
29pub type OwnerRecoveryNode = Signed<OwnerRecoveryNodeBody>;
30
31impl OwnerRecoveryNode {
32 #[allow(clippy::too_many_arguments)]
33 pub fn signed(
34 store_root_hash: ObjectHash,
35 recovery_id: DeviceRecoveryId,
36 owner_grant: MembershipGrantId,
37 sequence: u64,
38 membership: StoreMembershipStateRef,
39 predecessor: Option<OwnerRecoveryNodeRef>,
40 readiness: DeviceRecoveryReadiness,
41 next_slot: ObjectSlot,
42 owner_signer: &UserKeypair,
43 ) -> Result<Self, StoreProtocolError> {
44 let body = OwnerRecoveryNodeBody {
45 store_root_hash,
46 recovery_id,
47 owner_pubkey: keys::public_key_hex(owner_signer),
48 owner_grant,
49 sequence,
50 membership,
51 predecessor,
52 readiness,
53 next_slot,
54 };
55 body.validate_shape()?;
56 Ok(Signed::sign(body, owner_signer))
57 }
58
59 pub fn parse_at(
60 bytes: &[u8],
61 store_root: &StoreRootRef,
62 reference: &OwnerRecoveryNodeRef,
63 ) -> Result<Self, StoreProtocolError> {
64 let node: Self = crate::objects::decode_protocol_object(bytes)?;
65 node.body().validate_shape()?;
66 if node.store_root_hash != store_root.store_root_hash
67 || node.owner_pubkey != reference.owner_pubkey
68 || node.owner_grant != reference.owner_grant
69 || node.sequence != reference.sequence
70 || node.node_hash() != reference.node_hash
71 {
72 return Err(StoreProtocolError::OwnerRecoveryMismatch);
73 }
74 let owner_pubkey = node.owner_pubkey.clone();
75 node.verify_by(&owner_pubkey)?;
76 Ok(node)
77 }
78
79 pub fn node_hash(&self) -> ObjectHash {
80 self.hash()
81 }
82}
83
84impl OwnerRecoveryNodeBody {
85 fn validate_shape(&self) -> Result<(), StoreProtocolError> {
86 let predecessor_matches = match &self.predecessor {
87 None => self.sequence == 1,
88 Some(predecessor) => {
89 predecessor.owner_pubkey == self.owner_pubkey
90 && predecessor.owner_grant == self.owner_grant
91 && predecessor.sequence.checked_add(1) == Some(self.sequence)
92 }
93 };
94 if !predecessor_matches || self.readiness.initial_ack.sequence != 1 {
95 return Err(StoreProtocolError::OwnerRecoveryMismatch);
96 }
97 Ok(())
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case", deny_unknown_fields)]
103pub enum StoreDeviceRegistrationOrigin {
104 Founder {
105 creation_id: StoreCreationId,
106 },
107 Join {
108 attempt_id: DeviceJoinAttemptId,
109 },
110 Recovery {
111 recovery_id: DeviceRecoveryId,
112 recovery_slot: ObjectSlot,
113 owner_grant: MembershipGrantId,
114 },
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case", deny_unknown_fields)]
119pub enum StoreDeviceRegistrationActivation {
120 Founder {
121 root: StoreRootRef,
122 },
123 Join {
124 attempt_id: DeviceJoinAttemptId,
125 },
126 Recovery {
127 recovery_id: DeviceRecoveryId,
128 node: OwnerRecoveryNodeRef,
129 },
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(deny_unknown_fields)]
134pub struct ActivatedStoreDeviceRegistrationRef {
135 pub registration: StoreDeviceRegistrationRef,
136 pub authority: StoreDeviceRegistrationActivationRef,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "snake_case", deny_unknown_fields)]
141pub enum StoreDeviceRegistrationActivationRef {
142 Join {
143 attempt_id: DeviceJoinAttemptId,
144 },
145 Recovery {
146 recovery_id: DeviceRecoveryId,
147 node: OwnerRecoveryNodeRef,
148 },
149}
150
151impl StoreDeviceRegistrationOrigin {
152 pub(super) fn external_id(&self) -> ObjectHash {
153 match self {
154 Self::Founder { creation_id } => creation_id.object_hash(),
155 Self::Join { attempt_id, .. } => attempt_id.object_hash(),
156 Self::Recovery { recovery_id, .. } => recovery_id.object_hash(),
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case", deny_unknown_fields)]
163pub enum DeviceStreamAnchor {
164 StoreAnnouncements {
165 first_slot: ObjectSlot,
166 },
167 StoreAcknowledgements {
168 first_slot: ObjectSlot,
169 },
170 StoreSnapshots {
171 first_slot: ObjectSlot,
172 },
173 CircleAcknowledgements {
177 circle_id: CircleId,
178 first_slot: ObjectSlot,
179 },
180 CircleSnapshots {
184 circle_id: CircleId,
185 first_slot: ObjectSlot,
186 },
187}
188
189impl DeviceStreamAnchor {
190 pub fn first_slot(&self) -> &ObjectSlot {
191 match self {
192 Self::StoreAnnouncements { first_slot }
193 | Self::StoreAcknowledgements { first_slot }
194 | Self::StoreSnapshots { first_slot }
195 | Self::CircleAcknowledgements { first_slot, .. }
196 | Self::CircleSnapshots { first_slot, .. } => first_slot,
197 }
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
202#[serde(rename_all = "snake_case", deny_unknown_fields)]
203pub enum GrantStreamAnchor {
204 StoreMembership {
205 first_slot: ObjectSlot,
206 },
207 OwnerRecovery {
208 first_slot: ObjectSlot,
209 },
210 CircleControl {
211 circle_id: CircleId,
212 first_slot: ObjectSlot,
213 },
214 CircleRoster {
215 circle_id: CircleId,
216 first_slot: ObjectSlot,
217 },
218 CircleMetadata {
219 circle_id: CircleId,
220 first_slot: ObjectSlot,
221 },
222}
223
224impl GrantStreamAnchor {
225 pub fn first_slot(&self) -> &ObjectSlot {
226 match self {
227 Self::StoreMembership { first_slot }
228 | Self::OwnerRecovery { first_slot }
229 | Self::CircleControl { first_slot, .. }
230 | Self::CircleRoster { first_slot, .. }
231 | Self::CircleMetadata { first_slot, .. } => first_slot,
232 }
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
237#[serde(deny_unknown_fields)]
238pub struct StoreDeviceRegistrationBody {
239 pub store_root: StoreRootRef,
240 pub device_id: StoreDeviceId,
241 pub author_pubkey: String,
242 pub device_signing_pubkey: String,
243 pub origin: StoreDeviceRegistrationOrigin,
244 pub provider: ProviderDeviceBinding,
245 pub store_commits: DeviceStreamAnchor,
246 pub acknowledgements: DeviceStreamAnchor,
247 pub snapshots: DeviceStreamAnchor,
248}
249
250impl SignedBody for StoreDeviceRegistrationBody {
251 const DOMAIN: &'static [u8] = REGISTRATION_DOMAIN;
252}
253
254pub type StoreDeviceRegistration = Signed<StoreDeviceRegistrationBody>;
255
256#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
257pub struct ReferencedStoreDeviceRegistration {
258 reference: StoreDeviceRegistrationRef,
259 value: StoreDeviceRegistration,
260}
261
262impl ReferencedStoreDeviceRegistration {
263 pub fn verified(
264 reference: StoreDeviceRegistrationRef,
265 value: StoreDeviceRegistration,
266 ) -> Result<Self, StoreProtocolError> {
267 reference.verify_registration(&value)?;
268 Ok(Self { reference, value })
269 }
270
271 pub fn reference(&self) -> &StoreDeviceRegistrationRef {
272 &self.reference
273 }
274
275 pub fn value(&self) -> &StoreDeviceRegistration {
276 &self.value
277 }
278}
279
280impl<'de> Deserialize<'de> for ReferencedStoreDeviceRegistration {
281 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
282 where
283 D: serde::Deserializer<'de>,
284 {
285 #[derive(Deserialize)]
286 #[serde(deny_unknown_fields)]
287 struct EncodedRegistration {
288 reference: StoreDeviceRegistrationRef,
289 value: StoreDeviceRegistration,
290 }
291
292 let encoded = EncodedRegistration::deserialize(deserializer)?;
293 Self::verified(encoded.reference, encoded.value).map_err(serde::de::Error::custom)
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
298pub struct ActivatedStoreDeviceRegistration {
299 registration: ReferencedStoreDeviceRegistration,
300 activation: StoreDeviceRegistrationActivation,
301}
302
303impl ActivatedStoreDeviceRegistration {
304 pub fn verified(
305 registration: ReferencedStoreDeviceRegistration,
306 activation: StoreDeviceRegistrationActivation,
307 ) -> Result<Self, StoreProtocolError> {
308 let value = registration.value();
309 let matches = match (&value.origin, &activation) {
310 (
311 StoreDeviceRegistrationOrigin::Founder { .. },
312 StoreDeviceRegistrationActivation::Founder { root },
313 ) => &value.store_root == root,
314 (
315 StoreDeviceRegistrationOrigin::Join {
316 attempt_id: origin_attempt,
317 ..
318 },
319 StoreDeviceRegistrationActivation::Join { attempt_id },
320 ) => origin_attempt == attempt_id,
321 (
322 StoreDeviceRegistrationOrigin::Recovery {
323 recovery_id: origin_recovery,
324 recovery_slot,
325 ..
326 },
327 StoreDeviceRegistrationActivation::Recovery { recovery_id, node },
328 ) => origin_recovery == recovery_id && recovery_slot == node.slot(),
329 _ => false,
330 };
331 if !matches {
332 return Err(StoreProtocolError::DeviceStateMismatch);
333 }
334 Ok(Self {
335 registration,
336 activation,
337 })
338 }
339
340 pub fn verify_reference(
341 &self,
342 reference: &ActivatedStoreDeviceRegistrationRef,
343 ) -> Result<(), StoreProtocolError> {
344 if self.registration.reference() != &reference.registration {
345 return Err(StoreProtocolError::DeviceStateMismatch);
346 }
347 let matches = match (&reference.authority, &self.activation) {
348 (
349 StoreDeviceRegistrationActivationRef::Join { attempt_id },
350 StoreDeviceRegistrationActivation::Join {
351 attempt_id: activated_attempt,
352 },
353 ) => attempt_id == activated_attempt,
354 (
355 StoreDeviceRegistrationActivationRef::Recovery { recovery_id, node },
356 StoreDeviceRegistrationActivation::Recovery {
357 recovery_id: activated_recovery,
358 node: activated_node,
359 },
360 ) => recovery_id == activated_recovery && node == activated_node,
361 _ => false,
362 };
363 if !matches {
364 return Err(StoreProtocolError::DeviceStateMismatch);
365 }
366 Ok(())
367 }
368
369 pub fn activated_reference(
370 &self,
371 ) -> Result<ActivatedStoreDeviceRegistrationRef, StoreProtocolError> {
372 let authority = match &self.activation {
373 StoreDeviceRegistrationActivation::Founder { .. } => {
374 return Err(StoreProtocolError::DeviceStateMismatch)
375 }
376 StoreDeviceRegistrationActivation::Join { attempt_id } => {
377 StoreDeviceRegistrationActivationRef::Join {
378 attempt_id: *attempt_id,
379 }
380 }
381 StoreDeviceRegistrationActivation::Recovery { recovery_id, node } => {
382 StoreDeviceRegistrationActivationRef::Recovery {
383 recovery_id: *recovery_id,
384 node: node.clone(),
385 }
386 }
387 };
388 Ok(ActivatedStoreDeviceRegistrationRef {
389 registration: self.registration.reference().clone(),
390 authority,
391 })
392 }
393
394 pub fn registration(&self) -> &ReferencedStoreDeviceRegistration {
395 &self.registration
396 }
397
398 pub fn reference(&self) -> &StoreDeviceRegistrationRef {
399 self.registration.reference()
400 }
401
402 pub fn value(&self) -> &StoreDeviceRegistration {
403 self.registration.value()
404 }
405
406 pub fn activation(&self) -> &StoreDeviceRegistrationActivation {
407 &self.activation
408 }
409
410 pub(crate) fn recovery_cursor(
411 &self,
412 ) -> Result<Option<OwnerRecoveryCursor>, StoreProtocolError> {
413 match (&self.registration.value().origin, &self.activation) {
414 (
415 StoreDeviceRegistrationOrigin::Recovery {
416 recovery_id,
417 recovery_slot,
418 owner_grant,
419 },
420 StoreDeviceRegistrationActivation::Recovery {
421 recovery_id: activated_recovery_id,
422 node,
423 },
424 ) if recovery_id == activated_recovery_id
425 && recovery_slot == node.object.slot()
426 && owner_grant == &node.owner_grant =>
427 {
428 Ok(Some(OwnerRecoveryCursor {
429 owner_grant: owner_grant.clone(),
430 position: OwnerRecoveryPosition::At { node: node.clone() },
431 }))
432 }
433 (
434 StoreDeviceRegistrationOrigin::Join { attempt_id, .. },
435 StoreDeviceRegistrationActivation::Join {
436 attempt_id: activated_attempt_id,
437 },
438 ) if attempt_id == activated_attempt_id => Ok(None),
439 (
440 StoreDeviceRegistrationOrigin::Founder { .. },
441 StoreDeviceRegistrationActivation::Founder { .. },
442 ) => Ok(None),
443 _ => Err(StoreProtocolError::Malformed(
444 "registration origin differs from its exact activation authority".to_string(),
445 )),
446 }
447 }
448}
449
450impl<'de> Deserialize<'de> for ActivatedStoreDeviceRegistration {
451 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
452 where
453 D: serde::Deserializer<'de>,
454 {
455 #[derive(Deserialize)]
456 #[serde(deny_unknown_fields)]
457 struct EncodedActivation {
458 registration: ReferencedStoreDeviceRegistration,
459 activation: StoreDeviceRegistrationActivation,
460 }
461
462 let encoded = EncodedActivation::deserialize(deserializer)?;
463 Self::verified(encoded.registration, encoded.activation).map_err(serde::de::Error::custom)
464 }
465}
466
467impl StoreDeviceRegistration {
468 fn device_stream_activation(
469 &self,
470 reference: &StoreDeviceRegistrationRef,
471 anchor: &DeviceStreamAnchor,
472 ) -> Result<StreamActivation, StoreProtocolError> {
473 reference.verify_registration(self)?;
474 Ok(StreamActivation::device_authorized(
475 self.store_root.store_root_hash,
476 reference.clone(),
477 anchor.clone(),
478 ))
479 }
480
481 pub fn store_announcement_activation(
482 &self,
483 reference: &StoreDeviceRegistrationRef,
484 ) -> Result<StreamActivation, StoreProtocolError> {
485 self.device_stream_activation(reference, &self.store_commits)
486 }
487
488 pub fn store_acknowledgement_activation(
489 &self,
490 reference: &StoreDeviceRegistrationRef,
491 ) -> Result<StreamActivation, StoreProtocolError> {
492 self.device_stream_activation(reference, &self.acknowledgements)
493 }
494
495 pub fn store_snapshot_activation(
496 &self,
497 reference: &StoreDeviceRegistrationRef,
498 ) -> Result<StreamActivation, StoreProtocolError> {
499 self.device_stream_activation(reference, &self.snapshots)
500 }
501
502 pub fn signed(
503 store_root: StoreRootRef,
504 origin: StoreDeviceRegistrationOrigin,
505 provider: ProviderDeviceBinding,
506 store_commits: DeviceStreamAnchor,
507 acknowledgements: DeviceStreamAnchor,
508 snapshots: DeviceStreamAnchor,
509 identity_signer: &UserKeypair,
510 ) -> Result<Self, StoreProtocolError> {
511 validate_registration_anchors(&store_commits, &acknowledgements, &snapshots)?;
512 let author_pubkey = keys::public_key_hex(identity_signer);
513 let device_signer = derive_device_signer(identity_signer, &store_root, &origin);
514 let device_signing_pubkey = keys::public_key_hex(&device_signer);
515 let device_id = StoreDeviceId::derive(&store_root, &origin);
516 Ok(Signed::sign(
517 StoreDeviceRegistrationBody {
518 store_root,
519 device_id,
520 author_pubkey,
521 device_signing_pubkey,
522 origin,
523 provider,
524 store_commits,
525 acknowledgements,
526 snapshots,
527 },
528 identity_signer,
529 ))
530 }
531
532 pub fn device_signer(
533 &self,
534 identity_signer: &UserKeypair,
535 ) -> Result<UserKeypair, StoreProtocolError> {
536 if keys::public_key_hex(identity_signer) != self.author_pubkey {
537 return Err(StoreProtocolError::InvalidSignature);
538 }
539 let signer = derive_device_signer(identity_signer, &self.store_root, &self.origin);
540 if keys::public_key_hex(&signer) != self.device_signing_pubkey {
541 return Err(StoreProtocolError::InvalidSignature);
542 }
543 Ok(signer)
544 }
545
546 pub fn registration_hash(&self) -> ObjectHash {
547 self.hash()
548 }
549
550 pub fn parse_at(
551 bytes: &[u8],
552 expected_store_root: &StoreRootRef,
553 expected_device: StoreDeviceId,
554 ) -> Result<Self, StoreProtocolError> {
555 let registration: Self = crate::objects::decode_protocol_object(bytes)?;
556 registration.require_version()?;
557 if ®istration.store_root != expected_store_root {
558 return Err(StoreProtocolError::StoreRootMismatch {
559 expected: expected_store_root.store_root_hash,
560 actual: registration.store_root.store_root_hash,
561 });
562 }
563 if registration.device_id != expected_device {
564 return Err(StoreProtocolError::RelocatedSlot {
565 expected: registration_slot_prefix(&expected_device.to_string()),
566 actual: registration_slot_prefix(®istration.device_id.to_string()),
567 });
568 }
569 if registration.device_id
570 != StoreDeviceId::derive(®istration.store_root, ®istration.origin)
571 {
572 return Err(StoreProtocolError::Malformed(
573 "Store device id differs from its root and origin".to_string(),
574 ));
575 }
576 validate_registration_anchors(
577 ®istration.store_commits,
578 ®istration.acknowledgements,
579 ®istration.snapshots,
580 )?;
581 let author_pubkey = registration.author_pubkey.clone();
582 registration.verify_by(&author_pubkey)?;
583 Ok(registration)
584 }
585}
586
587fn derive_device_signer(
588 identity_signer: &UserKeypair,
589 store_root: &StoreRootRef,
590 origin: &StoreDeviceRegistrationOrigin,
591) -> UserKeypair {
592 const DOMAIN: &[u8] = b"coven.store-device-signing-key.v1\0";
593 let context = serde_json::to_vec(&(store_root, origin))
594 .expect("Store device signing context serialization cannot fail");
595 identity_signer.derive_signing_key(DOMAIN, &context)
596}
597
598fn validate_registration_anchors(
599 commits: &DeviceStreamAnchor,
600 acknowledgements: &DeviceStreamAnchor,
601 snapshots: &DeviceStreamAnchor,
602) -> Result<(), StoreProtocolError> {
603 if !matches!(
604 acknowledgements,
605 DeviceStreamAnchor::StoreAcknowledgements { .. }
606 ) || !matches!(snapshots, DeviceStreamAnchor::StoreSnapshots { .. })
607 || !matches!(commits, DeviceStreamAnchor::StoreAnnouncements { .. })
608 {
609 return Err(StoreProtocolError::Malformed(
610 "Store device registration contains mismatched permanent stream anchors".to_string(),
611 ));
612 }
613 Ok(())
614}