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 StoreAcknowledgements {
165 first_slot: ObjectSlot,
166 },
167 CircleAcknowledgements {
171 circle_id: CircleId,
172 first_slot: ObjectSlot,
173 },
174 CircleSnapshots {
178 circle_id: CircleId,
179 first_slot: ObjectSlot,
180 },
181}
182
183impl DeviceStreamAnchor {
184 pub fn first_slot(&self) -> &ObjectSlot {
185 match self {
186 Self::StoreAcknowledgements { first_slot }
187 | Self::CircleAcknowledgements { first_slot, .. }
188 | Self::CircleSnapshots { first_slot, .. } => first_slot,
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
194#[serde(rename_all = "snake_case", deny_unknown_fields)]
195pub enum GrantStreamAnchor {
196 StoreMembership {
197 first_slot: ObjectSlot,
198 },
199 OwnerRecovery {
200 first_slot: ObjectSlot,
201 },
202 CircleControl {
203 circle_id: CircleId,
204 first_slot: ObjectSlot,
205 },
206 CircleRoster {
207 circle_id: CircleId,
208 first_slot: ObjectSlot,
209 },
210 CircleMetadata {
211 circle_id: CircleId,
212 first_slot: ObjectSlot,
213 },
214}
215
216impl GrantStreamAnchor {
217 pub fn first_slot(&self) -> &ObjectSlot {
218 match self {
219 Self::StoreMembership { first_slot }
220 | Self::OwnerRecovery { first_slot }
221 | Self::CircleControl { first_slot, .. }
222 | Self::CircleRoster { first_slot, .. }
223 | Self::CircleMetadata { first_slot, .. } => first_slot,
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(deny_unknown_fields)]
230pub struct StoreDeviceRegistrationBody {
231 pub store_root: StoreRootRef,
232 pub device_id: StoreDeviceId,
233 pub author_pubkey: String,
234 pub device_signing_pubkey: String,
235 pub origin: StoreDeviceRegistrationOrigin,
236 pub provider: ProviderDeviceBinding,
237 pub acknowledgements: DeviceStreamAnchor,
238}
239
240impl SignedBody for StoreDeviceRegistrationBody {
241 const DOMAIN: &'static [u8] = REGISTRATION_DOMAIN;
242}
243
244pub type StoreDeviceRegistration = Signed<StoreDeviceRegistrationBody>;
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
247pub struct ReferencedStoreDeviceRegistration {
248 reference: StoreDeviceRegistrationRef,
249 value: StoreDeviceRegistration,
250}
251
252impl ReferencedStoreDeviceRegistration {
253 pub fn verified(
254 reference: StoreDeviceRegistrationRef,
255 value: StoreDeviceRegistration,
256 ) -> Result<Self, StoreProtocolError> {
257 reference.verify_registration(&value)?;
258 Ok(Self { reference, value })
259 }
260
261 pub fn reference(&self) -> &StoreDeviceRegistrationRef {
262 &self.reference
263 }
264
265 pub fn value(&self) -> &StoreDeviceRegistration {
266 &self.value
267 }
268}
269
270impl<'de> Deserialize<'de> for ReferencedStoreDeviceRegistration {
271 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
272 where
273 D: serde::Deserializer<'de>,
274 {
275 #[derive(Deserialize)]
276 #[serde(deny_unknown_fields)]
277 struct EncodedRegistration {
278 reference: StoreDeviceRegistrationRef,
279 value: StoreDeviceRegistration,
280 }
281
282 let encoded = EncodedRegistration::deserialize(deserializer)?;
283 Self::verified(encoded.reference, encoded.value).map_err(serde::de::Error::custom)
284 }
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
288pub struct ActivatedStoreDeviceRegistration {
289 registration: ReferencedStoreDeviceRegistration,
290 activation: StoreDeviceRegistrationActivation,
291}
292
293impl ActivatedStoreDeviceRegistration {
294 pub fn verified(
295 registration: ReferencedStoreDeviceRegistration,
296 activation: StoreDeviceRegistrationActivation,
297 ) -> Result<Self, StoreProtocolError> {
298 let value = registration.value();
299 let matches = match (&value.origin, &activation) {
300 (
301 StoreDeviceRegistrationOrigin::Founder { .. },
302 StoreDeviceRegistrationActivation::Founder { root },
303 ) => &value.store_root == root,
304 (
305 StoreDeviceRegistrationOrigin::Join {
306 attempt_id: origin_attempt,
307 ..
308 },
309 StoreDeviceRegistrationActivation::Join { attempt_id },
310 ) => origin_attempt == attempt_id,
311 (
312 StoreDeviceRegistrationOrigin::Recovery {
313 recovery_id: origin_recovery,
314 recovery_slot,
315 ..
316 },
317 StoreDeviceRegistrationActivation::Recovery { recovery_id, node },
318 ) => origin_recovery == recovery_id && recovery_slot == node.slot(),
319 _ => false,
320 };
321 if !matches {
322 return Err(StoreProtocolError::DeviceStateMismatch);
323 }
324 Ok(Self {
325 registration,
326 activation,
327 })
328 }
329
330 pub fn verify_reference(
331 &self,
332 reference: &ActivatedStoreDeviceRegistrationRef,
333 ) -> Result<(), StoreProtocolError> {
334 if self.registration.reference() != &reference.registration {
335 return Err(StoreProtocolError::DeviceStateMismatch);
336 }
337 let matches = match (&reference.authority, &self.activation) {
338 (
339 StoreDeviceRegistrationActivationRef::Join { attempt_id },
340 StoreDeviceRegistrationActivation::Join {
341 attempt_id: activated_attempt,
342 },
343 ) => attempt_id == activated_attempt,
344 (
345 StoreDeviceRegistrationActivationRef::Recovery { recovery_id, node },
346 StoreDeviceRegistrationActivation::Recovery {
347 recovery_id: activated_recovery,
348 node: activated_node,
349 },
350 ) => recovery_id == activated_recovery && node == activated_node,
351 _ => false,
352 };
353 if !matches {
354 return Err(StoreProtocolError::DeviceStateMismatch);
355 }
356 Ok(())
357 }
358
359 pub fn activated_reference(
360 &self,
361 ) -> Result<ActivatedStoreDeviceRegistrationRef, StoreProtocolError> {
362 let authority = match &self.activation {
363 StoreDeviceRegistrationActivation::Founder { .. } => {
364 return Err(StoreProtocolError::DeviceStateMismatch)
365 }
366 StoreDeviceRegistrationActivation::Join { attempt_id } => {
367 StoreDeviceRegistrationActivationRef::Join {
368 attempt_id: *attempt_id,
369 }
370 }
371 StoreDeviceRegistrationActivation::Recovery { recovery_id, node } => {
372 StoreDeviceRegistrationActivationRef::Recovery {
373 recovery_id: *recovery_id,
374 node: node.clone(),
375 }
376 }
377 };
378 Ok(ActivatedStoreDeviceRegistrationRef {
379 registration: self.registration.reference().clone(),
380 authority,
381 })
382 }
383
384 pub fn registration(&self) -> &ReferencedStoreDeviceRegistration {
385 &self.registration
386 }
387
388 pub fn reference(&self) -> &StoreDeviceRegistrationRef {
389 self.registration.reference()
390 }
391
392 pub fn value(&self) -> &StoreDeviceRegistration {
393 self.registration.value()
394 }
395
396 pub fn activation(&self) -> &StoreDeviceRegistrationActivation {
397 &self.activation
398 }
399
400 pub(crate) fn recovery_cursor(
401 &self,
402 ) -> Result<Option<OwnerRecoveryCursor>, StoreProtocolError> {
403 match (&self.registration.value().origin, &self.activation) {
404 (
405 StoreDeviceRegistrationOrigin::Recovery {
406 recovery_id,
407 recovery_slot,
408 owner_grant,
409 },
410 StoreDeviceRegistrationActivation::Recovery {
411 recovery_id: activated_recovery_id,
412 node,
413 },
414 ) if recovery_id == activated_recovery_id
415 && recovery_slot == node.object.slot()
416 && owner_grant == &node.owner_grant =>
417 {
418 Ok(Some(OwnerRecoveryCursor {
419 owner_grant: owner_grant.clone(),
420 position: OwnerRecoveryPosition::At { node: node.clone() },
421 }))
422 }
423 (
424 StoreDeviceRegistrationOrigin::Join { attempt_id, .. },
425 StoreDeviceRegistrationActivation::Join {
426 attempt_id: activated_attempt_id,
427 },
428 ) if attempt_id == activated_attempt_id => Ok(None),
429 (
430 StoreDeviceRegistrationOrigin::Founder { .. },
431 StoreDeviceRegistrationActivation::Founder { .. },
432 ) => Ok(None),
433 _ => Err(StoreProtocolError::Malformed(
434 "registration origin differs from its exact activation authority".to_string(),
435 )),
436 }
437 }
438}
439
440impl<'de> Deserialize<'de> for ActivatedStoreDeviceRegistration {
441 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442 where
443 D: serde::Deserializer<'de>,
444 {
445 #[derive(Deserialize)]
446 #[serde(deny_unknown_fields)]
447 struct EncodedActivation {
448 registration: ReferencedStoreDeviceRegistration,
449 activation: StoreDeviceRegistrationActivation,
450 }
451
452 let encoded = EncodedActivation::deserialize(deserializer)?;
453 Self::verified(encoded.registration, encoded.activation).map_err(serde::de::Error::custom)
454 }
455}
456
457impl StoreDeviceRegistration {
458 pub fn store_acknowledgement_activation(
459 &self,
460 reference: &StoreDeviceRegistrationRef,
461 ) -> Result<StreamActivation, StoreProtocolError> {
462 reference.verify_registration(self)?;
463 Ok(StreamActivation::device_authorized(
464 self.store_root.store_root_hash,
465 reference.clone(),
466 self.acknowledgements.clone(),
467 ))
468 }
469
470 pub fn signed(
471 store_root: StoreRootRef,
472 origin: StoreDeviceRegistrationOrigin,
473 provider: ProviderDeviceBinding,
474 acknowledgements: DeviceStreamAnchor,
475 identity_signer: &UserKeypair,
476 ) -> Result<Self, StoreProtocolError> {
477 validate_acknowledgement_anchor(&acknowledgements)?;
478 let author_pubkey = keys::public_key_hex(identity_signer);
479 let device_signer = derive_device_signer(identity_signer, &store_root, &origin);
480 let device_signing_pubkey = keys::public_key_hex(&device_signer);
481 let device_id = StoreDeviceId::derive(&store_root, &origin);
482 Ok(Signed::sign(
483 StoreDeviceRegistrationBody {
484 store_root,
485 device_id,
486 author_pubkey,
487 device_signing_pubkey,
488 origin,
489 provider,
490 acknowledgements,
491 },
492 identity_signer,
493 ))
494 }
495
496 pub fn device_signer(
497 &self,
498 identity_signer: &UserKeypair,
499 ) -> Result<UserKeypair, StoreProtocolError> {
500 if keys::public_key_hex(identity_signer) != self.author_pubkey {
501 return Err(StoreProtocolError::InvalidSignature);
502 }
503 let signer = derive_device_signer(identity_signer, &self.store_root, &self.origin);
504 if keys::public_key_hex(&signer) != self.device_signing_pubkey {
505 return Err(StoreProtocolError::InvalidSignature);
506 }
507 Ok(signer)
508 }
509
510 pub fn registration_hash(&self) -> ObjectHash {
511 self.hash()
512 }
513
514 pub fn parse_at(
515 bytes: &[u8],
516 expected_store_root: &StoreRootRef,
517 expected_device: StoreDeviceId,
518 ) -> Result<Self, StoreProtocolError> {
519 let registration: Self = crate::objects::decode_protocol_object(bytes)?;
520 registration.require_version()?;
521 if ®istration.store_root != expected_store_root {
522 return Err(StoreProtocolError::StoreRootMismatch {
523 expected: expected_store_root.store_root_hash,
524 actual: registration.store_root.store_root_hash,
525 });
526 }
527 if registration.device_id != expected_device {
528 return Err(StoreProtocolError::RelocatedSlot {
529 expected: registration_slot_prefix(&expected_device.to_string()),
530 actual: registration_slot_prefix(®istration.device_id.to_string()),
531 });
532 }
533 if registration.device_id
534 != StoreDeviceId::derive(®istration.store_root, ®istration.origin)
535 {
536 return Err(StoreProtocolError::Malformed(
537 "Store device id differs from its root and origin".to_string(),
538 ));
539 }
540 validate_acknowledgement_anchor(®istration.acknowledgements)?;
541 let author_pubkey = registration.author_pubkey.clone();
542 registration.verify_by(&author_pubkey)?;
543 Ok(registration)
544 }
545}
546
547fn derive_device_signer(
548 identity_signer: &UserKeypair,
549 store_root: &StoreRootRef,
550 origin: &StoreDeviceRegistrationOrigin,
551) -> UserKeypair {
552 const DOMAIN: &[u8] = b"coven.store-device-signing-key.v1\0";
553 let context = serde_json::to_vec(&(store_root, origin))
554 .expect("Store device signing context serialization cannot fail");
555 identity_signer.derive_signing_key(DOMAIN, &context)
556}
557
558fn validate_acknowledgement_anchor(
559 acknowledgements: &DeviceStreamAnchor,
560) -> Result<(), StoreProtocolError> {
561 if !matches!(
562 acknowledgements,
563 DeviceStreamAnchor::StoreAcknowledgements { .. }
564 ) {
565 return Err(StoreProtocolError::Malformed(
566 "Store device registration contains a mismatched acknowledgement anchor".to_string(),
567 ));
568 }
569 Ok(())
570}