Skip to main content

coven_protocol/store_commit/device_state/
retained.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct VerifiedStoreDeviceOperations {
5    proposals: Vec<(
6        RetainedStoreDeviceExclusionProposal,
7        StoreDeviceExclusionProposal,
8    )>,
9    outcomes: Vec<VerifiedStoreDeviceExclusionOutcome>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13enum VerifiedStoreDeviceExclusionOutcome {
14    Excluded {
15        source: RetainedStoreDeviceExclusionOutcome,
16        accepted_cut: StoreHistoryCut,
17    },
18    Cancelled(RetainedStoreDeviceExclusionOutcome),
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct RetainedStoreDeviceRegistrationActivations {
24    registrations: Vec<RetainedStoreDeviceRegistrationActivation>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(deny_unknown_fields)]
29struct RetainedStoreDeviceRegistrationActivation {
30    canonical_registration: Vec<u8>,
31    authority: StoreDeviceRegistrationActivation,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct RetainedStoreDeviceOperations {
37    proposals: Vec<RetainedStoreDeviceExclusionProposal>,
38    outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct RetainedStoreDeviceExclusionProposal {
44    reference: StoreDeviceExclusionProposalRef,
45    canonical_proposal: Vec<u8>,
46    canonical_target_registration: Vec<u8>,
47    canonical_owner_registration: Vec<u8>,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case", deny_unknown_fields)]
52pub enum RetainedStoreDeviceExclusionOutcome {
53    Excluded {
54        reference: StoreDeviceExclusionRef,
55        canonical_outcome: Vec<u8>,
56        proposal: RetainedStoreDeviceExclusionProposal,
57        canonical_owner_registration: Vec<u8>,
58    },
59    Cancelled {
60        reference: StoreDeviceExclusionCancellationRef,
61        canonical_outcome: Vec<u8>,
62        proposal: RetainedStoreDeviceExclusionProposal,
63        canonical_owner_registration: Vec<u8>,
64    },
65}
66
67impl VerifiedStoreDeviceOperations {
68    pub fn proposals(
69        &self,
70    ) -> impl ExactSizeIterator<
71        Item = (
72            &StoreDeviceExclusionProposalRef,
73            &StoreDeviceExclusionProposal,
74        ),
75    > {
76        self.proposals
77            .iter()
78            .map(|(source, proposal)| (&source.reference, proposal))
79    }
80
81    pub fn exclusions(&self) -> impl Iterator<Item = (&StoreDeviceExclusionRef, &StoreHistoryCut)> {
82        self.outcomes.iter().filter_map(|outcome| match outcome {
83            VerifiedStoreDeviceExclusionOutcome::Excluded {
84                source,
85                accepted_cut,
86            } => Some((source.exclusion_reference(), accepted_cut)),
87            VerifiedStoreDeviceExclusionOutcome::Cancelled(_) => None,
88        })
89    }
90
91    pub(crate) fn from_retained_sources(
92        root: &StoreRootRef,
93        commit: &StoreBatchCommit,
94        proposals: Vec<RetainedStoreDeviceExclusionProposal>,
95        outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
96    ) -> Result<Self, StoreProtocolError> {
97        let proposal_refs = proposals
98            .iter()
99            .map(|source| source.reference.clone())
100            .collect::<Vec<_>>();
101        let outcome_refs = outcomes
102            .iter()
103            .map(RetainedStoreDeviceExclusionOutcome::wire_reference)
104            .collect::<Vec<_>>();
105        if proposal_refs.as_slice() != commit.device_exclusion_proposals()
106            || outcome_refs.as_slice() != commit.device_exclusion_outcomes()
107        {
108            return Err(StoreProtocolError::DeviceStateMismatch);
109        }
110        let retained = RetainedStoreDeviceOperations {
111            proposals: proposals.clone(),
112            outcomes: outcomes.clone(),
113        };
114        let proposals = proposals
115            .into_iter()
116            .map(|source| {
117                let proposal = source.verify(root)?;
118                if proposal.frozen_device_state != commit.device_state {
119                    return Err(StoreProtocolError::DeviceStateMismatch);
120                }
121                Ok((source, proposal))
122            })
123            .collect::<Result<Vec<_>, StoreProtocolError>>()?;
124        let outcomes = outcomes
125            .into_iter()
126            .map(|source| source.verify(root))
127            .collect::<Result<Vec<_>, StoreProtocolError>>()?;
128        let verified = Self {
129            proposals,
130            outcomes,
131        };
132        if verified.to_retained() != retained {
133            return Err(StoreProtocolError::DeviceStateMismatch);
134        }
135        Ok(verified)
136    }
137
138    pub fn without_exclusions(commit: &StoreBatchCommit) -> Result<Self, StoreProtocolError> {
139        if !commit.device_exclusion_proposals().is_empty()
140            || !commit.device_exclusion_outcomes().is_empty()
141        {
142            return Err(StoreProtocolError::DeviceStateMismatch);
143        }
144        Ok(Self {
145            proposals: Vec::new(),
146            outcomes: Vec::new(),
147        })
148    }
149
150    pub fn to_retained(&self) -> RetainedStoreDeviceOperations {
151        RetainedStoreDeviceOperations {
152            proposals: self
153                .proposals
154                .iter()
155                .map(|(source, _)| source.clone())
156                .collect(),
157            outcomes: self
158                .outcomes
159                .iter()
160                .map(VerifiedStoreDeviceExclusionOutcome::source)
161                .cloned()
162                .collect(),
163        }
164    }
165
166    pub fn apply_to(
167        &self,
168        predecessor: ResolvedStoreDeviceState,
169        predecessor_ref: &StoreDeviceStateRef,
170    ) -> Result<ResolvedStoreDeviceState, StoreProtocolError> {
171        let mut state = predecessor;
172        for (source, proposal) in &self.proposals {
173            state = state.propose_exclusion(source.reference.clone(), proposal, predecessor_ref)?;
174        }
175        for outcome in &self.outcomes {
176            state = match outcome {
177                VerifiedStoreDeviceExclusionOutcome::Excluded {
178                    source,
179                    accepted_cut,
180                } => state.exclude(source.exclusion_reference().clone(), accepted_cut.clone())?,
181                VerifiedStoreDeviceExclusionOutcome::Cancelled(source) => {
182                    state.cancel_exclusion(source.cancellation_reference().clone())?
183                }
184            };
185        }
186        Ok(state)
187    }
188}
189
190impl RetainedStoreDeviceRegistrationActivations {
191    pub fn from_verified(
192        root: &StoreRootRef,
193        commit: &StoreBatchCommit,
194        registrations: &[ActivatedStoreDeviceRegistration],
195    ) -> Result<Self, StoreProtocolError> {
196        if registrations.len() != commit.device_registrations().len() {
197            return Err(StoreProtocolError::DeviceStateMismatch);
198        }
199        let retained = Self {
200            registrations: registrations
201                .iter()
202                .map(|registration| RetainedStoreDeviceRegistrationActivation {
203                    canonical_registration: registration.value().to_bytes(),
204                    authority: registration.activation().clone(),
205                })
206                .collect(),
207        };
208        retained.verify_for(root, commit)?;
209        Ok(retained)
210    }
211
212    pub fn verify_for(
213        &self,
214        root: &StoreRootRef,
215        commit: &StoreBatchCommit,
216    ) -> Result<Vec<ActivatedStoreDeviceRegistration>, StoreProtocolError> {
217        if self.registrations.len() != commit.device_registrations().len() {
218            return Err(StoreProtocolError::DeviceStateMismatch);
219        }
220        commit
221            .device_registrations()
222            .iter()
223            .zip(&self.registrations)
224            .map(|(activated, retained)| retained.verify(root, activated))
225            .collect()
226    }
227}
228
229impl RetainedStoreDeviceRegistrationActivation {
230    fn verify(
231        &self,
232        root: &StoreRootRef,
233        activated: &ActivatedStoreDeviceRegistrationRef,
234    ) -> Result<ActivatedStoreDeviceRegistration, StoreProtocolError> {
235        let registration = verify_retained_registration(
236            root,
237            &activated.registration,
238            &self.canonical_registration,
239        )?;
240        let registration = ReferencedStoreDeviceRegistration::verified(
241            activated.registration.clone(),
242            registration,
243        )?;
244        let registration =
245            ActivatedStoreDeviceRegistration::verified(registration, self.authority.clone())?;
246        registration.verify_reference(activated)?;
247        Ok(registration)
248    }
249}
250
251impl RetainedStoreDeviceOperations {
252    pub fn from_sources(
253        proposals: Vec<RetainedStoreDeviceExclusionProposal>,
254        outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
255    ) -> Self {
256        Self {
257            proposals,
258            outcomes,
259        }
260    }
261
262    pub fn verify_for(
263        &self,
264        root: &StoreRootRef,
265        commit: &StoreBatchCommit,
266    ) -> Result<VerifiedStoreDeviceOperations, StoreProtocolError> {
267        VerifiedStoreDeviceOperations::from_retained_sources(
268            root,
269            commit,
270            self.proposals.clone(),
271            self.outcomes.clone(),
272        )
273    }
274}
275
276impl RetainedStoreDeviceExclusionProposal {
277    pub fn from_exact(
278        reference: StoreDeviceExclusionProposalRef,
279        proposal: &StoreDeviceExclusionProposal,
280        target: &StoreDeviceRegistration,
281        owner: &StoreDeviceRegistration,
282    ) -> Result<Self, StoreProtocolError> {
283        let retained = Self {
284            reference,
285            canonical_proposal: proposal.to_bytes(),
286            canonical_target_registration: target.to_bytes(),
287            canonical_owner_registration: owner.to_bytes(),
288        };
289        let opened = retained.verify_with_registrations(&target.store_root)?;
290        if opened.object.value != *proposal || opened.target != *target || opened.owner != *owner {
291            return Err(StoreProtocolError::DeviceStateMismatch);
292        }
293        Ok(retained)
294    }
295
296    pub fn from_verified(proposal: &VerifiedDeviceExclusionProposal) -> Self {
297        Self {
298            reference: proposal.reference.clone(),
299            canonical_proposal: proposal.object.bytes.clone(),
300            canonical_target_registration: proposal.target.to_bytes(),
301            canonical_owner_registration: proposal.owner.to_bytes(),
302        }
303    }
304
305    pub fn reference(&self) -> &StoreDeviceExclusionProposalRef {
306        &self.reference
307    }
308
309    fn verify(
310        &self,
311        root: &StoreRootRef,
312    ) -> Result<StoreDeviceExclusionProposal, StoreProtocolError> {
313        self.verify_with_registrations(root)
314            .map(|proposal| proposal.object.value)
315    }
316
317    fn verify_with_registrations(
318        &self,
319        root: &StoreRootRef,
320    ) -> Result<VerifiedDeviceExclusionProposal, StoreProtocolError> {
321        self.reference.object.verify(&self.canonical_proposal)?;
322        let unverified: StoreDeviceExclusionProposal =
323            serde_json::from_slice(&self.canonical_proposal)?;
324        if unverified.to_bytes() != self.canonical_proposal {
325            return Err(StoreProtocolError::Malformed(
326                "retained Store device exclusion proposal is not canonically encoded".to_string(),
327            ));
328        }
329        let target = verify_retained_registration(
330            root,
331            &unverified.target,
332            &self.canonical_target_registration,
333        )?;
334        let owner = verify_retained_registration(
335            root,
336            &unverified.owner_registration,
337            &self.canonical_owner_registration,
338        )?;
339        let proposal = StoreDeviceExclusionProposal::parse_at(
340            &self.canonical_proposal,
341            &self.reference,
342            &target,
343            &owner,
344        )?;
345        Ok(VerifiedDeviceExclusionProposal {
346            reference: self.reference.clone(),
347            object: crate::objects::VerifiedObject {
348                value: proposal,
349                bytes: self.canonical_proposal.clone(),
350                semantic_hash: self.reference.proposal_hash,
351                object: self.reference.object.clone(),
352            },
353            target,
354            owner,
355        })
356    }
357}
358
359impl RetainedStoreDeviceExclusionOutcome {
360    pub fn from_exact(
361        reference: &StoreDeviceExclusionOutcomeRef,
362        proposal: RetainedStoreDeviceExclusionProposal,
363        outcome: &StoreDeviceExclusionOutcome,
364        owner: &StoreDeviceRegistration,
365    ) -> Result<Self, StoreProtocolError> {
366        if reference.proposal() != outcome.proposal()
367            || reference.outcome_hash() != outcome.outcome_hash()
368        {
369            return Err(StoreProtocolError::DeviceStateMismatch);
370        }
371        let canonical_outcome = outcome.to_bytes();
372        reference.object().verify(&canonical_outcome)?;
373        Ok(match (reference, outcome) {
374            (
375                StoreDeviceExclusionOutcomeRef::Excluded(reference),
376                StoreDeviceExclusionOutcome::Excluded(_),
377            ) => Self::Excluded {
378                reference: reference.clone(),
379                canonical_outcome,
380                proposal,
381                canonical_owner_registration: owner.to_bytes(),
382            },
383            (
384                StoreDeviceExclusionOutcomeRef::Cancelled(reference),
385                StoreDeviceExclusionOutcome::Cancelled(_),
386            ) => Self::Cancelled {
387                reference: reference.clone(),
388                canonical_outcome,
389                proposal,
390                canonical_owner_registration: owner.to_bytes(),
391            },
392            _ => return Err(StoreProtocolError::DeviceStateMismatch),
393        })
394    }
395
396    pub fn from_verified(
397        reference: &StoreDeviceExclusionOutcomeRef,
398        proposal: RetainedStoreDeviceExclusionProposal,
399        outcome: &VerifiedDeviceExclusionOutcome,
400    ) -> Result<Self, StoreProtocolError> {
401        match (reference, &outcome.object.value) {
402            (
403                StoreDeviceExclusionOutcomeRef::Excluded(reference),
404                StoreDeviceExclusionOutcome::Excluded(_),
405            ) => Ok(Self::Excluded {
406                reference: reference.clone(),
407                canonical_outcome: outcome.object.bytes.clone(),
408                proposal,
409                canonical_owner_registration: outcome.owner.to_bytes(),
410            }),
411            (
412                StoreDeviceExclusionOutcomeRef::Cancelled(reference),
413                StoreDeviceExclusionOutcome::Cancelled(_),
414            ) => Ok(Self::Cancelled {
415                reference: reference.clone(),
416                canonical_outcome: outcome.object.bytes.clone(),
417                proposal,
418                canonical_owner_registration: outcome.owner.to_bytes(),
419            }),
420            _ => Err(StoreProtocolError::DeviceStateMismatch),
421        }
422    }
423
424    pub fn wire_reference(&self) -> StoreDeviceExclusionOutcomeRef {
425        match self {
426            Self::Excluded { reference, .. } => {
427                StoreDeviceExclusionOutcomeRef::Excluded(reference.clone())
428            }
429            Self::Cancelled { reference, .. } => {
430                StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone())
431            }
432        }
433    }
434
435    fn exclusion_reference(&self) -> &StoreDeviceExclusionRef {
436        match self {
437            Self::Excluded { reference, .. } => reference,
438            Self::Cancelled { .. } => unreachable!("verified exclusion changed variant"),
439        }
440    }
441
442    fn cancellation_reference(&self) -> &StoreDeviceExclusionCancellationRef {
443        match self {
444            Self::Cancelled { reference, .. } => reference,
445            Self::Excluded { .. } => unreachable!("verified cancellation changed variant"),
446        }
447    }
448
449    fn verify(
450        self,
451        root: &StoreRootRef,
452    ) -> Result<VerifiedStoreDeviceExclusionOutcome, StoreProtocolError> {
453        let (reference, canonical_outcome, proposal_source, canonical_owner_registration) =
454            match &self {
455                Self::Excluded {
456                    reference,
457                    canonical_outcome,
458                    proposal,
459                    canonical_owner_registration,
460                } => (
461                    StoreDeviceExclusionOutcomeRef::Excluded(reference.clone()),
462                    canonical_outcome,
463                    proposal,
464                    canonical_owner_registration,
465                ),
466                Self::Cancelled {
467                    reference,
468                    canonical_outcome,
469                    proposal,
470                    canonical_owner_registration,
471                } => (
472                    StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone()),
473                    canonical_outcome,
474                    proposal,
475                    canonical_owner_registration,
476                ),
477            };
478        reference.object().verify(canonical_outcome)?;
479        let proposal = proposal_source.verify_with_registrations(root)?;
480        let unverified: StoreDeviceExclusionOutcome = serde_json::from_slice(canonical_outcome)?;
481        if unverified.to_bytes() != *canonical_outcome {
482            return Err(StoreProtocolError::Malformed(
483                "retained Store device exclusion outcome is not canonically encoded".to_string(),
484            ));
485        }
486        let owner_reference = match &unverified {
487            StoreDeviceExclusionOutcome::Excluded(exclusion) => &exclusion.owner_registration,
488            StoreDeviceExclusionOutcome::Cancelled(cancellation) => {
489                &cancellation.owner_registration
490            }
491        };
492        let owner =
493            verify_retained_registration(root, owner_reference, canonical_owner_registration)?;
494        let outcome = StoreDeviceExclusionOutcome::parse_at(
495            canonical_outcome,
496            &reference,
497            &proposal.object.value,
498            &proposal.target,
499            &owner,
500        )?;
501        match (&self, outcome) {
502            (Self::Excluded { .. }, StoreDeviceExclusionOutcome::Excluded(exclusion)) => {
503                if exclusion.proof.frozen_device_state != proposal.object.value.frozen_device_state
504                {
505                    return Err(StoreProtocolError::DeviceStateMismatch);
506                }
507                Ok(VerifiedStoreDeviceExclusionOutcome::Excluded {
508                    source: self,
509                    accepted_cut: exclusion.proof.cutoff.clone(),
510                })
511            }
512            (Self::Cancelled { .. }, StoreDeviceExclusionOutcome::Cancelled(_)) => {
513                Ok(VerifiedStoreDeviceExclusionOutcome::Cancelled(self))
514            }
515            _ => Err(StoreProtocolError::DeviceStateMismatch),
516        }
517    }
518}
519
520fn verify_retained_registration(
521    root: &StoreRootRef,
522    reference: &StoreDeviceRegistrationRef,
523    canonical_registration: &[u8],
524) -> Result<StoreDeviceRegistration, StoreProtocolError> {
525    reference.object.verify(canonical_registration)?;
526    let registration =
527        StoreDeviceRegistration::parse_at(canonical_registration, root, reference.device_id)?;
528    if registration.to_bytes() != canonical_registration {
529        return Err(StoreProtocolError::Malformed(
530            "retained Store device registration is not canonically encoded".to_string(),
531        ));
532    }
533    reference.verify_registration(&registration)?;
534    Ok(registration)
535}
536
537impl VerifiedStoreDeviceExclusionOutcome {
538    fn source(&self) -> &RetainedStoreDeviceExclusionOutcome {
539        match self {
540            Self::Excluded { source, .. } | Self::Cancelled(source) => source,
541        }
542    }
543}