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 ¤t,
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 ¤t,
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}