Skip to main content

chio_settle/channel/
replay.rs

1use chio_core::canonical::canonical_json_bytes;
2use chio_core::crypto::PublicKey;
3use chio_core::economic_continuity::{
4    verify_economic_state_batch_advance, verify_economic_state_view, EconomicRequestBindingV1,
5    EconomicResourceHeadV1, EconomicResourceKeyV1, EconomicStateAnchorError,
6    EconomicStateAnchorPins, EconomicStateAnchorViewV1, EconomicStateBatchV1,
7    EconomicStateTransitionV1, EconomicTransitionAuthorizationV1, EconomicTransitionProofVerifier,
8    VerifiedEconomicStateView,
9};
10use chio_core::receipt::body::ChioReceipt;
11use chio_credit::obligation::ObligationAtomV1;
12use serde::{Deserialize, Deserializer, Serialize};
13
14use super::validation::{digest, validate_digest, validate_positive};
15use super::{
16    compose_channel_cancellation_transition, compose_channel_dispatch_transition,
17    compose_channel_reservation_transition, compose_channel_terminal_transition,
18    verify_admitted_channel_open, verify_admitted_channel_reservation, verify_channel_open_consent,
19    verify_channel_open_intent, verify_channel_prepared_reservation,
20    verify_channel_receipt_binding, verify_channel_reservation_proposal,
21    verify_channel_state_transition, verify_channel_terminal_outcome_commitment,
22    verify_retained_signed_channel_state, ChannelDisputePolicyV1, ChannelError,
23    ChannelFundingAuthorityV1, ChannelLifecycleBatchVerifier, ChannelLifecycleProjectionV1,
24    ChannelOpenTrustV1, ChannelPreparedReservationV1, ChannelReservationAuthorityV1,
25    RetainedChannelStateV1, SignedChannelFundingAcknowledgementV1, SignedChannelFundingEvidenceV1,
26    SignedChannelReservationV1, SignedChannelStateV1, SignedChannelTerminalOutcomeCommitmentV1,
27    VerifiedAdmittedChannelReservationV1, VerifiedChannelOpenConsentV1,
28    VerifiedChannelPreparedReservationV1, VerifiedChannelReservationProposalV1,
29    VerifiedChannelStateV1, VerifiedChannelTerminalOutcomeCommitmentV1,
30};
31
32pub const CHANNEL_TRANSITION_REPLAY_FORMAT: &str = "chio.channel.transition-replay.v1";
33pub const MAX_CHANNEL_TRANSITION_REPLAY_BYTES: usize = 4 * 1024 * 1024;
34pub const MAX_CHANNEL_TRANSITION_REPLAY_AUTHORITY_PINS_BYTES: usize = 256 * 1024;
35
36const CHANNEL_TRANSITION_REPLAY_VERSION: u64 = 1;
37const CHANNEL_TRANSITION_REPLAY_AUTHORITY_PINS_DOMAIN: &[u8] =
38    b"chio.channel.transition-replay.authority-pins.digest.v1\0";
39const CHANNEL_TRANSITION_REPLAY_DESCRIPTOR_DOMAIN: &[u8] =
40    b"chio.channel.transition-replay.descriptor.digest.v1\0";
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase", deny_unknown_fields)]
44struct ChannelTransitionReplayAnchorPinsV1 {
45    anchor_id: String,
46    namespace: String,
47    signer_key_id: String,
48    signer_key_epoch: u64,
49    #[serde(deserialize_with = "super::signed::deserialize_canonical_public_key")]
50    signer_public_key: PublicKey,
51}
52
53impl ChannelTransitionReplayAnchorPinsV1 {
54    fn from_runtime(pins: &EconomicStateAnchorPins) -> Self {
55        Self {
56            anchor_id: pins.anchor_id.clone(),
57            namespace: pins.namespace.clone(),
58            signer_key_id: pins.signer_key_id.clone(),
59            signer_key_epoch: pins.signer_key_epoch,
60            signer_public_key: pins.signer_public_key.clone(),
61        }
62    }
63
64    fn runtime(&self) -> EconomicStateAnchorPins {
65        EconomicStateAnchorPins {
66            anchor_id: self.anchor_id.clone(),
67            namespace: self.namespace.clone(),
68            signer_key_id: self.signer_key_id.clone(),
69            signer_key_epoch: self.signer_key_epoch,
70            signer_public_key: self.signer_public_key.clone(),
71        }
72    }
73
74    fn validate(&self) -> Result<(), ChannelError> {
75        self.runtime()
76            .validate()
77            .map_err(|_| ChannelError::AuthorityVerification)
78    }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase", deny_unknown_fields)]
83pub struct ChannelTransitionReplayAuthorityPinsV1 {
84    open_trust: ChannelOpenTrustV1,
85    funding_authority: ChannelFundingAuthorityV1,
86    reservation_authority: ChannelReservationAuthorityV1,
87    #[serde(
88        default,
89        skip_serializing_if = "Option::is_none",
90        deserialize_with = "deserialize_optional_canonical_public_key"
91    )]
92    trusted_kernel_key: Option<PublicKey>,
93    anchor: ChannelTransitionReplayAnchorPinsV1,
94}
95
96impl ChannelTransitionReplayAuthorityPinsV1 {
97    pub fn new(
98        open_trust: ChannelOpenTrustV1,
99        funding_authority: ChannelFundingAuthorityV1,
100        reservation_authority: ChannelReservationAuthorityV1,
101        trusted_kernel_key: Option<PublicKey>,
102        anchor: &EconomicStateAnchorPins,
103    ) -> Result<Self, ChannelError> {
104        let pins = Self {
105            open_trust,
106            funding_authority,
107            reservation_authority,
108            trusted_kernel_key,
109            anchor: ChannelTransitionReplayAnchorPinsV1::from_runtime(anchor),
110        };
111        pins.validate()?;
112        Ok(pins)
113    }
114
115    fn validate(&self) -> Result<(), ChannelError> {
116        self.open_trust.validate()?;
117        self.funding_authority.validate()?;
118        self.reservation_authority.validate()?;
119        self.anchor.validate()
120    }
121
122    pub fn digest(&self) -> Result<String, ChannelError> {
123        self.validate()?;
124        digest(CHANNEL_TRANSITION_REPLAY_AUTHORITY_PINS_DOMAIN, self)
125    }
126
127    pub fn canonical_bytes(&self) -> Result<Vec<u8>, ChannelError> {
128        self.validate()?;
129        let bytes = canonical_json_bytes(self)
130            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
131        if bytes.len() > MAX_CHANNEL_TRANSITION_REPLAY_AUTHORITY_PINS_BYTES {
132            return Err(ChannelError::InvalidField(
133                "channel_transition_replay_authority_pins_size",
134            ));
135        }
136        Ok(bytes)
137    }
138
139    pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ChannelError> {
140        if bytes.is_empty() || bytes.len() > MAX_CHANNEL_TRANSITION_REPLAY_AUTHORITY_PINS_BYTES {
141            return Err(ChannelError::InvalidField(
142                "channel_transition_replay_authority_pins_size",
143            ));
144        }
145        let pins: Self = serde_json::from_slice(bytes)
146            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
147        if pins.canonical_bytes()?.as_slice() != bytes {
148            return Err(ChannelError::Canonicalization(
149                "channel transition replay authority pins are not canonical".to_owned(),
150            ));
151        }
152        Ok(pins)
153    }
154
155    #[must_use]
156    pub fn anchor_pins(&self) -> EconomicStateAnchorPins {
157        self.anchor.runtime()
158    }
159
160    fn anchor(&self) -> EconomicStateAnchorPins {
161        self.anchor_pins()
162    }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase", deny_unknown_fields)]
167pub struct ChannelTransitionReplayOpenArtifactsV1 {
168    pub funding_evidence: SignedChannelFundingEvidenceV1,
169    pub funding_acknowledgement: SignedChannelFundingAcknowledgementV1,
170    pub dispute_policy: ChannelDisputePolicyV1,
171}
172
173impl ChannelTransitionReplayOpenArtifactsV1 {
174    fn validate(&self) -> Result<(), ChannelError> {
175        self.funding_evidence.digest()?;
176        self.funding_acknowledgement.digest()?;
177        self.dispute_policy.validate()
178    }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase", deny_unknown_fields)]
183pub struct ChannelReservationReplayContextV1 {
184    prepared: ChannelPreparedReservationV1,
185    signed_reservation: SignedChannelReservationV1,
186    prepared_base_view: EconomicStateAnchorViewV1,
187}
188
189impl ChannelReservationReplayContextV1 {
190    pub fn from_pre_anchor(
191        prepared: &VerifiedChannelPreparedReservationV1,
192        proposal: &VerifiedChannelReservationProposalV1,
193    ) -> Result<Self, ChannelError> {
194        if prepared.prepared().reservation != proposal.artifact().body {
195            return Err(ChannelError::AuthorityVerification);
196        }
197        let context = Self {
198            prepared: prepared.prepared().clone(),
199            signed_reservation: proposal.artifact().clone(),
200            prepared_base_view: prepared.current().view().clone(),
201        };
202        context.validate()?;
203        Ok(context)
204    }
205
206    pub fn from_verified(
207        prepared: &VerifiedChannelPreparedReservationV1,
208        reservation: &super::VerifiedAdmittedChannelReservationV1,
209    ) -> Result<Self, ChannelError> {
210        Self::from_pre_anchor(prepared, reservation.proposal())
211    }
212
213    fn validate(&self) -> Result<(), ChannelError> {
214        self.prepared.digest()?;
215        self.signed_reservation.digest()?;
216        self.prepared_base_view
217            .validate()
218            .map_err(|_| ChannelError::AuthorityVerification)?;
219        if self.prepared.reservation != self.signed_reservation.body
220            || self.prepared.anchor_id != self.prepared_base_view.anchor_id
221            || self.prepared.namespace != self.prepared_base_view.namespace
222            || self.prepared.checkpoint_sequence != self.prepared_base_view.checkpoint_sequence
223            || self.prepared.checkpoint_digest != self.prepared_base_view.checkpoint_digest
224            || self.prepared.observed_at_unix_ms != self.prepared_base_view.observed_at
225        {
226            return Err(ChannelError::AuthorityVerification);
227        }
228        Ok(())
229    }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233#[serde(rename_all = "snake_case")]
234pub enum ChannelTransitionReplayKindV1 {
235    Reservation,
236    Dispatch,
237    Terminal,
238    Cancellation,
239}
240
241impl ChannelTransitionReplayKindV1 {
242    #[must_use]
243    pub const fn as_str(self) -> &'static str {
244        match self {
245            Self::Reservation => "reservation",
246            Self::Dispatch => "dispatch",
247            Self::Terminal => "terminal",
248            Self::Cancellation => "cancellation",
249        }
250    }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase", deny_unknown_fields)]
255struct ChannelTransitionReplaySourceBindingV1 {
256    resource_key: EconomicResourceKeyV1,
257    #[serde(
258        default,
259        skip_serializing_if = "Option::is_none",
260        deserialize_with = "super::validation::deserialize_present_option"
261    )]
262    expected_head_digest: Option<String>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
267enum ChannelTransitionReplayEvidenceV1 {
268    Reservation,
269    Dispatch,
270    Cancellation {
271        reservation_view: Box<EconomicStateAnchorViewV1>,
272    },
273    Terminal {
274        reservation_view: Box<EconomicStateAnchorViewV1>,
275        signed_receipt: Box<ChioReceipt>,
276        #[serde(
277            default,
278            skip_serializing_if = "Option::is_none",
279            deserialize_with = "super::validation::deserialize_present_option"
280        )]
281        obligation: Option<Box<ObligationAtomV1>>,
282        signed_next_state: Box<SignedChannelStateV1>,
283        terminal_outcome: Box<SignedChannelTerminalOutcomeCommitmentV1>,
284    },
285}
286
287impl ChannelTransitionReplayEvidenceV1 {
288    const fn kind(&self) -> ChannelTransitionReplayKindV1 {
289        match self {
290            Self::Reservation => ChannelTransitionReplayKindV1::Reservation,
291            Self::Dispatch => ChannelTransitionReplayKindV1::Dispatch,
292            Self::Cancellation { .. } => ChannelTransitionReplayKindV1::Cancellation,
293            Self::Terminal { .. } => ChannelTransitionReplayKindV1::Terminal,
294        }
295    }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299#[serde(rename_all = "camelCase", deny_unknown_fields)]
300struct ChannelTransitionReplayBodyV1 {
301    authority_pins: ChannelTransitionReplayAuthorityPinsV1,
302    authority_pins_digest: String,
303    open_artifacts: ChannelTransitionReplayOpenArtifactsV1,
304    reservation_context: ChannelReservationReplayContextV1,
305    current_view: EconomicStateAnchorViewV1,
306    base_checkpoint_sequence: u64,
307    base_checkpoint_digest: String,
308    descriptor_key: String,
309    operation_id: String,
310    request: EconomicRequestBindingV1,
311    source_bindings: Vec<ChannelTransitionReplaySourceBindingV1>,
312    issued_at: u64,
313    #[serde(
314        default,
315        skip_serializing_if = "Option::is_none",
316        deserialize_with = "super::validation::deserialize_present_option"
317    )]
318    not_after_unix_ms: Option<u64>,
319    expected_batch_digest: String,
320    evidence: ChannelTransitionReplayEvidenceV1,
321}
322
323#[derive(Serialize)]
324#[serde(rename_all = "camelCase")]
325struct ChannelTransitionReplayDescriptorCommitmentV1<'a> {
326    format: &'a str,
327    version: u64,
328    body: &'a ChannelTransitionReplayBodyV1,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase", deny_unknown_fields)]
333pub struct ChannelTransitionReplayDescriptorV1 {
334    format: String,
335    version: u64,
336    body: Box<ChannelTransitionReplayBodyV1>,
337    descriptor_digest: String,
338}
339
340impl ChannelTransitionReplayDescriptorV1 {
341    pub fn for_reservation(
342        context: &ChannelReservationReplayContextV1,
343        open_artifacts: &ChannelTransitionReplayOpenArtifactsV1,
344        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
345        expected_batch: &EconomicStateBatchV1,
346    ) -> Result<Self, ChannelError> {
347        let current = verify_replay_view(&context.prepared_base_view, authority_pins)?;
348        Self::new(
349            context,
350            open_artifacts,
351            authority_pins,
352            &current,
353            ChannelTransitionReplayEvidenceV1::Reservation,
354            expected_batch,
355        )
356    }
357
358    pub fn for_dispatch(
359        context: &ChannelReservationReplayContextV1,
360        open_artifacts: &ChannelTransitionReplayOpenArtifactsV1,
361        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
362        current: &VerifiedEconomicStateView,
363        expected_batch: &EconomicStateBatchV1,
364    ) -> Result<Self, ChannelError> {
365        Self::new(
366            context,
367            open_artifacts,
368            authority_pins,
369            current,
370            ChannelTransitionReplayEvidenceV1::Dispatch,
371            expected_batch,
372        )
373    }
374
375    pub fn for_cancellation(
376        context: &ChannelReservationReplayContextV1,
377        open_artifacts: &ChannelTransitionReplayOpenArtifactsV1,
378        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
379        reservation_view: &VerifiedEconomicStateView,
380        current: &VerifiedEconomicStateView,
381        expected_batch: &EconomicStateBatchV1,
382    ) -> Result<Self, ChannelError> {
383        Self::new(
384            context,
385            open_artifacts,
386            authority_pins,
387            current,
388            ChannelTransitionReplayEvidenceV1::Cancellation {
389                reservation_view: Box::new(reservation_view.view().clone()),
390            },
391            expected_batch,
392        )
393    }
394
395    #[allow(clippy::too_many_arguments)]
396    pub fn for_terminal(
397        context: &ChannelReservationReplayContextV1,
398        open_artifacts: &ChannelTransitionReplayOpenArtifactsV1,
399        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
400        reservation_view: &VerifiedEconomicStateView,
401        current: &VerifiedEconomicStateView,
402        signed_receipt: &ChioReceipt,
403        obligation: Option<&ObligationAtomV1>,
404        signed_next_state: &SignedChannelStateV1,
405        terminal_outcome: &VerifiedChannelTerminalOutcomeCommitmentV1,
406        expected_batch: &EconomicStateBatchV1,
407    ) -> Result<Self, ChannelError> {
408        Self::new(
409            context,
410            open_artifacts,
411            authority_pins,
412            current,
413            ChannelTransitionReplayEvidenceV1::Terminal {
414                reservation_view: Box::new(reservation_view.view().clone()),
415                signed_receipt: Box::new(signed_receipt.clone()),
416                obligation: obligation.cloned().map(Box::new),
417                signed_next_state: Box::new(signed_next_state.clone()),
418                terminal_outcome: Box::new(terminal_outcome.artifact().clone()),
419            },
420            expected_batch,
421        )
422    }
423
424    #[must_use]
425    pub fn kind(&self) -> ChannelTransitionReplayKindV1 {
426        self.body.evidence.kind()
427    }
428
429    #[must_use]
430    pub fn key(&self) -> &str {
431        &self.body.descriptor_key
432    }
433
434    #[must_use]
435    pub fn request(&self) -> &EconomicRequestBindingV1 {
436        &self.body.request
437    }
438
439    #[must_use]
440    pub fn digest(&self) -> &str {
441        &self.descriptor_digest
442    }
443
444    #[must_use]
445    pub fn expected_batch_digest(&self) -> &str {
446        &self.body.expected_batch_digest
447    }
448
449    #[must_use]
450    pub fn not_after_unix_ms(&self) -> Option<u64> {
451        self.body.not_after_unix_ms
452    }
453
454    pub fn canonical_bytes(&self) -> Result<Vec<u8>, ChannelError> {
455        self.reconstruct(&self.body.authority_pins)?;
456        let bytes = canonical_json_bytes(self)
457            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
458        if bytes.len() > MAX_CHANNEL_TRANSITION_REPLAY_BYTES {
459            return Err(ChannelError::InvalidField("channel_transition_replay_size"));
460        }
461        Ok(bytes)
462    }
463
464    fn new(
465        context: &ChannelReservationReplayContextV1,
466        open_artifacts: &ChannelTransitionReplayOpenArtifactsV1,
467        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
468        current: &VerifiedEconomicStateView,
469        evidence: ChannelTransitionReplayEvidenceV1,
470        expected_batch: &EconomicStateBatchV1,
471    ) -> Result<Self, ChannelError> {
472        authority_pins.validate()?;
473        current
474            .view()
475            .verify(&authority_pins.anchor())
476            .map_err(|_| ChannelError::AuthorityVerification)?;
477        let expires_at = context.signed_reservation.body.expires_at_unix_ms;
478        let not_after_unix_ms = match evidence.kind() {
479            ChannelTransitionReplayKindV1::Cancellation
480            | ChannelTransitionReplayKindV1::Terminal => None,
481            ChannelTransitionReplayKindV1::Reservation
482            | ChannelTransitionReplayKindV1::Dispatch => Some(expires_at),
483        };
484        let body = ChannelTransitionReplayBodyV1 {
485            authority_pins: authority_pins.clone(),
486            authority_pins_digest: authority_pins.digest()?,
487            open_artifacts: open_artifacts.clone(),
488            reservation_context: context.clone(),
489            current_view: current.view().clone(),
490            base_checkpoint_sequence: current.view().checkpoint_sequence,
491            base_checkpoint_digest: current.view().checkpoint_digest.clone(),
492            descriptor_key: descriptor_key(
493                evidence.kind(),
494                &context.signed_reservation.body.operation_id,
495            ),
496            operation_id: context.signed_reservation.body.operation_id.clone(),
497            request: context.prepared.service.request.clone(),
498            source_bindings: source_bindings(&expected_batch.transitions),
499            issued_at: expected_batch.issued_at,
500            not_after_unix_ms,
501            expected_batch_digest: expected_batch.checkpoint_digest.clone(),
502            evidence,
503        };
504        let mut descriptor = Self {
505            format: CHANNEL_TRANSITION_REPLAY_FORMAT.to_owned(),
506            version: CHANNEL_TRANSITION_REPLAY_VERSION,
507            body: Box::new(body),
508            descriptor_digest: String::new(),
509        };
510        descriptor.descriptor_digest = descriptor.recompute_digest()?;
511        let reconstructed = descriptor.reconstruct(authority_pins)?;
512        verify_economic_state_batch_advance(
513            &reconstructed.current,
514            expected_batch.clone(),
515            &authority_pins.anchor(),
516            &ChannelLifecycleBatchVerifier::new(reconstructed.projection),
517        )
518        .map_err(|_| ChannelError::AuthorityVerification)?;
519        Ok(descriptor)
520    }
521
522    fn recompute_digest(&self) -> Result<String, ChannelError> {
523        digest(
524            CHANNEL_TRANSITION_REPLAY_DESCRIPTOR_DOMAIN,
525            &ChannelTransitionReplayDescriptorCommitmentV1 {
526                format: &self.format,
527                version: self.version,
528                body: &self.body,
529            },
530        )
531    }
532
533    fn reconstruct(
534        &self,
535        expected_authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
536    ) -> Result<ReconstructedChannelTransitionReplayV1, ChannelError> {
537        self.validate_envelope(expected_authority_pins)?;
538        let context = &self.body.reservation_context;
539        let prepared_current =
540            verify_replay_view(&context.prepared_base_view, expected_authority_pins)?;
541        let current = verify_replay_view(&self.body.current_view, expected_authority_pins)?;
542        let verified_intent = verify_channel_open_intent(
543            &context.prepared.signed_open_intent,
544            &self.body.open_artifacts.funding_evidence,
545            &expected_authority_pins.funding_authority,
546            &self.body.open_artifacts.dispute_policy,
547            &expected_authority_pins.open_trust,
548        )?;
549        let open = verify_channel_open_consent(
550            &context.prepared.signed_open,
551            &verified_intent,
552            &self.body.open_artifacts.funding_acknowledgement,
553            &expected_authority_pins.funding_authority,
554            &expected_authority_pins.open_trust,
555        )?;
556        let admitted_open = verify_admitted_channel_open(&open, &prepared_current)?;
557        let prior = replay_prior_state(
558            &context.prepared.prior_state,
559            &open,
560            &expected_authority_pins.open_trust,
561        )?;
562        let proposal = verify_channel_reservation_proposal(
563            &context.signed_reservation,
564            &admitted_open,
565            &prior,
566            &expected_authority_pins.reservation_authority,
567            &expected_authority_pins.open_trust,
568        )?;
569        let prepared = verify_channel_prepared_reservation(
570            &context.prepared,
571            &admitted_open,
572            &prior,
573            &prepared_current,
574            &context.prepared.reservation,
575            &context.prepared.service,
576        )?;
577        let projection = self.recompose_projection(
578            &open,
579            &prior,
580            &proposal,
581            &prepared,
582            &current,
583            expected_authority_pins,
584        )?;
585        if projection.operation_id() != Some(self.body.operation_id.as_str())
586            || projection.not_after_unix_ms() != self.body.not_after_unix_ms
587            || source_bindings(projection.transitions()) != self.body.source_bindings
588        {
589            return Err(ChannelError::AuthorityVerification);
590        }
591        Ok(ReconstructedChannelTransitionReplayV1 {
592            current,
593            projection,
594            prepared,
595            proposal,
596        })
597    }
598
599    fn recompose_projection(
600        &self,
601        open: &VerifiedChannelOpenConsentV1,
602        prior: &VerifiedChannelStateV1,
603        proposal: &VerifiedChannelReservationProposalV1,
604        prepared: &VerifiedChannelPreparedReservationV1,
605        current: &VerifiedEconomicStateView,
606        authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
607    ) -> Result<ChannelLifecycleProjectionV1, ChannelError> {
608        match &self.body.evidence {
609            ChannelTransitionReplayEvidenceV1::Reservation => {
610                if current.view() != prepared.current().view() {
611                    return Err(ChannelError::AuthorityVerification);
612                }
613                compose_channel_reservation_transition(prepared, proposal, self.body.issued_at)
614            }
615            ChannelTransitionReplayEvidenceV1::Dispatch => {
616                let reservation = verify_admitted_channel_reservation(proposal, prepared, current)?;
617                compose_channel_dispatch_transition(&reservation, current, self.body.issued_at)
618            }
619            ChannelTransitionReplayEvidenceV1::Cancellation { reservation_view } => {
620                let reservation_view = verify_replay_view(reservation_view, authority_pins)?;
621                let reservation =
622                    verify_admitted_channel_reservation(proposal, prepared, &reservation_view)?;
623                compose_channel_cancellation_transition(&reservation, current, self.body.issued_at)
624            }
625            ChannelTransitionReplayEvidenceV1::Terminal {
626                reservation_view,
627                signed_receipt,
628                obligation,
629                signed_next_state,
630                terminal_outcome,
631            } => {
632                let reservation_view = verify_replay_view(reservation_view, authority_pins)?;
633                let reservation =
634                    verify_admitted_channel_reservation(proposal, prepared, &reservation_view)?;
635                let trusted_kernel_key = authority_pins
636                    .trusted_kernel_key
637                    .as_ref()
638                    .ok_or(ChannelError::AuthorityVerification)?;
639                let receipt = verify_channel_receipt_binding(
640                    signed_receipt,
641                    trusted_kernel_key,
642                    &reservation,
643                    open,
644                    obligation.as_deref(),
645                )?;
646                let outcome = verify_channel_terminal_outcome_commitment(
647                    terminal_outcome,
648                    trusted_kernel_key,
649                    &reservation,
650                    signed_receipt,
651                )?;
652                let next = verify_channel_state_transition(
653                    signed_next_state,
654                    prior,
655                    &reservation,
656                    &receipt,
657                    open,
658                    &authority_pins.open_trust,
659                )?;
660                compose_channel_terminal_transition(
661                    open,
662                    &reservation,
663                    &next,
664                    &receipt,
665                    &outcome,
666                    current,
667                    self.body.issued_at,
668                )
669            }
670        }
671    }
672
673    fn validate_envelope(
674        &self,
675        expected_authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
676    ) -> Result<(), ChannelError> {
677        if self.format != CHANNEL_TRANSITION_REPLAY_FORMAT
678            || self.version != CHANNEL_TRANSITION_REPLAY_VERSION
679        {
680            return Err(ChannelError::InvalidField(
681                "channel_transition_replay_format",
682            ));
683        }
684        expected_authority_pins.validate()?;
685        self.body.authority_pins.validate()?;
686        self.body.open_artifacts.validate()?;
687        self.body.reservation_context.validate()?;
688        validate_digest(
689            "channel_transition_replay_authority_pins_digest",
690            &self.body.authority_pins_digest,
691        )?;
692        validate_digest(
693            "channel_transition_replay_descriptor_digest",
694            &self.descriptor_digest,
695        )?;
696        validate_digest(
697            "channel_transition_replay_base_checkpoint_digest",
698            &self.body.base_checkpoint_digest,
699        )?;
700        validate_digest(
701            "channel_transition_replay_operation_id",
702            &self.body.operation_id,
703        )?;
704        validate_digest(
705            "channel_transition_replay_expected_batch_digest",
706            &self.body.expected_batch_digest,
707        )?;
708        validate_positive(
709            "channel_transition_replay_base_checkpoint_sequence",
710            self.body.base_checkpoint_sequence,
711        )?;
712        validate_positive("channel_transition_replay_issued_at", self.body.issued_at)?;
713        self.body
714            .request
715            .validate()
716            .map_err(|_| ChannelError::AuthorityVerification)?;
717        let expected_not_after = match self.body.evidence.kind() {
718            ChannelTransitionReplayKindV1::Cancellation
719            | ChannelTransitionReplayKindV1::Terminal => None,
720            ChannelTransitionReplayKindV1::Reservation
721            | ChannelTransitionReplayKindV1::Dispatch => Some(
722                self.body
723                    .reservation_context
724                    .signed_reservation
725                    .body
726                    .expires_at_unix_ms,
727            ),
728        };
729        if &self.body.authority_pins != expected_authority_pins
730            || self.body.authority_pins_digest != expected_authority_pins.digest()?
731            || self.descriptor_digest != self.recompute_digest()?
732            || self.body.current_view.checkpoint_sequence != self.body.base_checkpoint_sequence
733            || self.body.current_view.checkpoint_digest != self.body.base_checkpoint_digest
734            || self.body.descriptor_key
735                != descriptor_key(self.body.evidence.kind(), &self.body.operation_id)
736            || self.body.operation_id
737                != self
738                    .body
739                    .reservation_context
740                    .signed_reservation
741                    .body
742                    .operation_id
743            || self.body.request != self.body.reservation_context.prepared.service.request
744            || self.body.not_after_unix_ms != expected_not_after
745        {
746            return Err(ChannelError::AuthorityVerification);
747        }
748        if let Some(not_after) = self.body.not_after_unix_ms {
749            validate_positive("channel_transition_replay_not_after", not_after)?;
750            if self.body.issued_at >= not_after {
751                return Err(ChannelError::AuthorityVerification);
752            }
753        }
754        if let ChannelTransitionReplayEvidenceV1::Terminal {
755            terminal_outcome, ..
756        } = &self.body.evidence
757        {
758            if terminal_outcome.body.terminalized_at_unix_ms > self.body.issued_at {
759                return Err(ChannelError::AuthorityVerification);
760            }
761        }
762        validate_source_bindings(&self.body.current_view, &self.body.source_bindings)
763    }
764}
765
766fn descriptor_key(kind: ChannelTransitionReplayKindV1, operation_id: &str) -> String {
767    format!("{}:{operation_id}", kind.as_str())
768}
769
770struct ReconstructedChannelTransitionReplayV1 {
771    current: VerifiedEconomicStateView,
772    projection: ChannelLifecycleProjectionV1,
773    prepared: VerifiedChannelPreparedReservationV1,
774    proposal: VerifiedChannelReservationProposalV1,
775}
776
777pub struct ChannelTransitionReplayVerifierV1 {
778    descriptor: ChannelTransitionReplayDescriptorV1,
779    current: VerifiedEconomicStateView,
780    projection: ChannelLifecycleProjectionV1,
781    prepared: VerifiedChannelPreparedReservationV1,
782    proposal: VerifiedChannelReservationProposalV1,
783}
784
785impl ChannelTransitionReplayVerifierV1 {
786    pub fn from_canonical_bytes(
787        bytes: &[u8],
788        expected_authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
789    ) -> Result<Self, ChannelError> {
790        if bytes.is_empty() || bytes.len() > MAX_CHANNEL_TRANSITION_REPLAY_BYTES {
791            return Err(ChannelError::InvalidField("channel_transition_replay_size"));
792        }
793        let descriptor: ChannelTransitionReplayDescriptorV1 = serde_json::from_slice(bytes)
794            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
795        let canonical = canonical_json_bytes(&descriptor)
796            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
797        if canonical.as_slice() != bytes {
798            return Err(ChannelError::Canonicalization(
799                "channel transition replay descriptor is not canonical".to_owned(),
800            ));
801        }
802        let reconstructed = descriptor.reconstruct(expected_authority_pins)?;
803        Ok(Self {
804            descriptor,
805            current: reconstructed.current,
806            projection: reconstructed.projection,
807            prepared: reconstructed.prepared,
808            proposal: reconstructed.proposal,
809        })
810    }
811
812    #[must_use]
813    pub const fn descriptor(&self) -> &ChannelTransitionReplayDescriptorV1 {
814        &self.descriptor
815    }
816
817    #[must_use]
818    pub const fn verified_reservation_proposal(&self) -> &VerifiedChannelReservationProposalV1 {
819        &self.proposal
820    }
821
822    pub fn verify_committed_reservation(
823        &self,
824        committed: &VerifiedEconomicStateView,
825    ) -> Result<VerifiedAdmittedChannelReservationV1, ChannelError> {
826        let expected_sequence = self
827            .current
828            .view()
829            .checkpoint_sequence
830            .checked_add(1)
831            .ok_or(ChannelError::ArithmeticOverflow)?;
832        committed
833            .view()
834            .verify(&self.descriptor.body.authority_pins.anchor_pins())
835            .map_err(|_| ChannelError::AuthorityVerification)?;
836        if self.descriptor.kind() != ChannelTransitionReplayKindV1::Reservation
837            || committed.view().checkpoint_sequence != expected_sequence
838            || committed.view().checkpoint_digest != self.descriptor.body.expected_batch_digest
839            || committed.view().observed_at < self.descriptor.body.issued_at
840        {
841            return Err(ChannelError::AuthorityVerification);
842        }
843        verify_admitted_channel_reservation(&self.proposal, &self.prepared, committed)
844    }
845}
846
847impl EconomicTransitionProofVerifier for ChannelTransitionReplayVerifierV1 {
848    fn verify_transition(
849        &self,
850        _current: Option<&EconomicResourceHeadV1>,
851        transition: &EconomicStateTransitionV1,
852    ) -> Result<EconomicTransitionAuthorizationV1, EconomicStateAnchorError> {
853        Err(EconomicStateAnchorError::TransitionProofRejected(
854            transition.resource_key.clone(),
855        ))
856    }
857
858    fn verify_batch(
859        &self,
860        current: &VerifiedEconomicStateView,
861        batch: &EconomicStateBatchV1,
862    ) -> Result<Vec<EconomicTransitionAuthorizationV1>, EconomicStateAnchorError> {
863        let rejected_key = batch
864            .transitions
865            .first()
866            .ok_or(EconomicStateAnchorError::InvalidView(
867                "channel replay batch has no transition",
868            ))?
869            .resource_key
870            .clone();
871        let rejected = || EconomicStateAnchorError::TransitionProofRejected(rejected_key.clone());
872        let pins = self.descriptor.body.authority_pins.anchor();
873        current.view().verify(&pins)?;
874        batch.verify_signature(&pins.signer_public_key)?;
875        if current.view() != self.current.view()
876            || batch.checkpoint_digest != self.descriptor.body.expected_batch_digest
877            || batch.anchor_id != pins.anchor_id
878            || batch.namespace != pins.namespace
879            || batch.signer_key_id != pins.signer_key_id
880            || batch.signer_key_epoch != pins.signer_key_epoch
881        {
882            return Err(rejected());
883        }
884        ChannelLifecycleBatchVerifier::new(self.projection.clone()).verify_batch(current, batch)
885    }
886}
887
888fn verify_replay_view(
889    view: &EconomicStateAnchorViewV1,
890    authority_pins: &ChannelTransitionReplayAuthorityPinsV1,
891) -> Result<VerifiedEconomicStateView, ChannelError> {
892    verify_economic_state_view(view.clone(), &authority_pins.anchor())
893        .map_err(|_| ChannelError::AuthorityVerification)
894}
895
896fn replay_prior_state(
897    retained: &RetainedChannelStateV1,
898    open: &VerifiedChannelOpenConsentV1,
899    trust: &ChannelOpenTrustV1,
900) -> Result<VerifiedChannelStateV1, ChannelError> {
901    match retained {
902        RetainedChannelStateV1::Initial { body } => {
903            if body.as_ref() != open.initial_state().body() {
904                return Err(ChannelError::AuthorityVerification);
905            }
906            Ok(open.initial_state().clone())
907        }
908        RetainedChannelStateV1::Signed { state } => {
909            verify_retained_signed_channel_state(state, open, trust)
910        }
911    }
912}
913
914fn source_bindings(
915    transitions: &[EconomicStateTransitionV1],
916) -> Vec<ChannelTransitionReplaySourceBindingV1> {
917    transitions
918        .iter()
919        .map(|transition| ChannelTransitionReplaySourceBindingV1 {
920            resource_key: transition.resource_key.clone(),
921            expected_head_digest: transition.expected_head_digest.clone(),
922        })
923        .collect()
924}
925
926fn validate_source_bindings(
927    current: &EconomicStateAnchorViewV1,
928    bindings: &[ChannelTransitionReplaySourceBindingV1],
929) -> Result<(), ChannelError> {
930    if bindings.is_empty()
931        || bindings.len() > 3
932        || !bindings
933            .windows(2)
934            .all(|pair| pair[0].resource_key < pair[1].resource_key)
935    {
936        return Err(ChannelError::AuthorityVerification);
937    }
938    for binding in bindings {
939        binding
940            .resource_key
941            .validate()
942            .map_err(|_| ChannelError::AuthorityVerification)?;
943        match binding.expected_head_digest.as_deref() {
944            Some(expected) => {
945                validate_digest("channel_transition_replay_source_head", expected)?;
946                let head = current
947                    .head(&binding.resource_key)
948                    .ok_or(ChannelError::AuthorityVerification)?;
949                if head
950                    .digest()
951                    .map_err(|_| ChannelError::AuthorityVerification)?
952                    != expected
953                {
954                    return Err(ChannelError::AuthorityVerification);
955                }
956            }
957            None if current.proves_resource_absent(&binding.resource_key) => {}
958            None => return Err(ChannelError::AuthorityVerification),
959        }
960    }
961    Ok(())
962}
963
964fn deserialize_optional_canonical_public_key<'de, D>(
965    deserializer: D,
966) -> Result<Option<PublicKey>, D::Error>
967where
968    D: Deserializer<'de>,
969{
970    let encoded = String::deserialize(deserializer)?;
971    let key = PublicKey::from_hex(&encoded).map_err(serde::de::Error::custom)?;
972    if key.to_hex() != encoded {
973        return Err(serde::de::Error::custom("noncanonical public key"));
974    }
975    Ok(Some(key))
976}