1use chio_core::capability::scope::MonetaryAmount;
2use chio_core::crypto::PublicKey;
3use chio_core::economic_continuity::EconomicFrostBindingV1;
4#[cfg(test)]
5use chio_core::economic_continuity::{
6 EconomicAdmissionHandoffStateV1, EconomicEffectSlotV1, EconomicEffectStateV1,
7};
8use chio_core::signed_artifact::CHIO_CHANNEL_RELEASE_AUTHORIZATION_V1_SCHEMA;
9use chio_federation::frost::VerifiedFrostAuthorization;
10use serde::{Deserialize, Serialize};
11
12use super::validation::{
13 digest, parse_base_units, validate_digest, validate_positive, validate_text,
14};
15use super::{
16 channel_close_frost_action, ChannelCloseKindV1, ChannelError, ChannelEscrowReferenceV1,
17 VerifiedEffectiveChannelCloseV1,
18};
19
20const CHANNEL_RELEASE_AUTHORIZATION_DIGEST_DOMAIN: &[u8] =
21 b"chio.channel.release-authorization.digest.v1\0";
22const SIGNED_CHANNEL_RELEASE_AUTHORIZATION_DIGEST_DOMAIN: &[u8] =
23 b"chio.channel.release-authorization.signed-digest.v1\0";
24
25pub const CHANNEL_RELEASE_ROOT_PUBLICATION_EFFECT_KIND: &str = "channel_release_root_publication";
26pub const CHANNEL_RELEASE_BROADCAST_EFFECT_KIND: &str = "channel_release_broadcast";
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase", deny_unknown_fields)]
30pub struct ChannelReleaseAuthorizationBindingV1 {
31 channel_id: String,
32 escrow_reference: ChannelEscrowReferenceV1,
33 open_digest: String,
34 close_digest: String,
35 close_body_digest: String,
36 effective_close_digest: String,
37 final_state_digest: String,
38 final_state_sequence: u64,
39 final_cumulative_owed: MonetaryAmount,
40 channel_state_version: u64,
41 escrow_reservation_version: u64,
42 lifecycle_fence: u64,
43 bound_token_base_units: String,
44 expected_release_token_base_units: String,
45 expected_refund_token_base_units: String,
46 asset_binding_digest: String,
47 original_web3_dispatch_digest: String,
48 original_operator: String,
49 original_operator_key_hash: String,
50 payee_beneficiary_address: String,
51 channel_expiry_unix_ms: u64,
52 dispute_deadline_unix_ms: u64,
53 close_submission_cutoff_unix_ms: u64,
54 escrow_deadline_unix_ms: u64,
55 publisher_fence: u64,
56 authorized_at_unix_ms: u64,
57 frost: EconomicFrostBindingV1,
58 frost_scope_id: String,
59 frost_resource_id: String,
60 frost_resource_version: u64,
61 frost_resource_fence: u64,
62 frost_roster_digest: String,
63 frost_key_epoch: u64,
64 frost_issued_at_unix_ms: u64,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase", deny_unknown_fields)]
69pub struct ChannelReleaseMutationBindingV1 {
70 pub operation_id: String,
71 pub effect_slot_id: String,
72 pub scope_id: String,
73 pub effect_kind: String,
74 pub idempotency_key: String,
75 pub call_digest: String,
76 pub resource_head_digest: String,
77}
78
79impl ChannelReleaseMutationBindingV1 {
80 fn validate(&self, expected_effect_kind: &'static str) -> Result<(), ChannelError> {
81 for (field, value) in [
82 ("release_operation_id", &self.operation_id),
83 ("release_effect_slot_id", &self.effect_slot_id),
84 ("release_idempotency_key", &self.idempotency_key),
85 ("release_call_digest", &self.call_digest),
86 ("release_resource_head_digest", &self.resource_head_digest),
87 ] {
88 validate_digest(field, value)?;
89 }
90 validate_text("release_effect_scope_id", &self.scope_id)?;
91 validate_text("release_effect_kind", &self.effect_kind)?;
92 if self.effect_kind != expected_effect_kind {
93 return Err(ChannelError::AuthorityVerification);
94 }
95 Ok(())
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase", deny_unknown_fields)]
101pub struct ChannelReleaseAuthorizationBodyV1 {
102 pub schema: String,
103 pub authority: ChannelReleaseAuthorizationBindingV1,
104 pub publication_root: String,
105 pub root_publication: ChannelReleaseMutationBindingV1,
106 pub release_broadcast: ChannelReleaseMutationBindingV1,
107}
108
109impl ChannelReleaseAuthorizationBodyV1 {
110 fn validate(&self) -> Result<(), ChannelError> {
111 if self.schema != CHIO_CHANNEL_RELEASE_AUTHORIZATION_V1_SCHEMA {
112 return Err(ChannelError::InvalidField("release_authorization_schema"));
113 }
114 validate_digest("release_publication_root", &self.publication_root)?;
115 self.root_publication
116 .validate(CHANNEL_RELEASE_ROOT_PUBLICATION_EFFECT_KIND)?;
117 self.release_broadcast
118 .validate(CHANNEL_RELEASE_BROADCAST_EFFECT_KIND)?;
119 if self.root_publication.operation_id == self.release_broadcast.operation_id
120 || self.root_publication.effect_slot_id == self.release_broadcast.effect_slot_id
121 || self.root_publication.idempotency_key == self.release_broadcast.idempotency_key
122 || self.root_publication.call_digest == self.release_broadcast.call_digest
123 || self.root_publication.scope_id != self.authority.frost_scope_id
124 || self.release_broadcast.scope_id != self.authority.frost_scope_id
125 {
126 return Err(ChannelError::AuthorityVerification);
127 }
128 Ok(())
129 }
130
131 #[must_use]
132 pub fn authority(&self) -> &ChannelReleaseAuthorizationBindingV1 {
133 &self.authority
134 }
135
136 #[must_use]
137 pub fn publication_root(&self) -> &str {
138 &self.publication_root
139 }
140
141 #[must_use]
142 pub fn root_publication(&self) -> &ChannelReleaseMutationBindingV1 {
143 &self.root_publication
144 }
145
146 #[must_use]
147 pub fn release_broadcast(&self) -> &ChannelReleaseMutationBindingV1 {
148 &self.release_broadcast
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase", deny_unknown_fields)]
154pub struct SignedChannelReleaseAuthorizationV1 {
155 pub body: ChannelReleaseAuthorizationBodyV1,
156 pub publisher_signature: super::ChannelSignatureV1,
157}
158
159impl SignedChannelReleaseAuthorizationV1 {
160 pub fn digest(&self) -> Result<String, ChannelError> {
161 self.body.validate()?;
162 digest(SIGNED_CHANNEL_RELEASE_AUTHORIZATION_DIGEST_DOMAIN, self)
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct ChannelReleasePublisherTrustV1 {
168 pub publisher_id: String,
169 pub publisher_key_epoch: u64,
170 pub publisher_key: PublicKey,
171}
172
173impl ChannelReleasePublisherTrustV1 {
174 fn validate(&self) -> Result<(), ChannelError> {
175 validate_text("release_publisher_id", &self.publisher_id)?;
176 validate_positive("release_publisher_key_epoch", self.publisher_key_epoch)
177 }
178}
179
180impl ChannelReleaseAuthorizationBindingV1 {
181 #[must_use]
182 pub fn channel_id(&self) -> &str {
183 &self.channel_id
184 }
185
186 #[must_use]
187 pub fn escrow_reference(&self) -> &ChannelEscrowReferenceV1 {
188 &self.escrow_reference
189 }
190
191 #[must_use]
192 pub fn open_digest(&self) -> &str {
193 &self.open_digest
194 }
195
196 #[must_use]
197 pub fn close_digest(&self) -> &str {
198 &self.close_digest
199 }
200
201 #[must_use]
202 pub fn close_body_digest(&self) -> &str {
203 &self.close_body_digest
204 }
205
206 #[must_use]
207 pub fn effective_close_digest(&self) -> &str {
208 &self.effective_close_digest
209 }
210
211 #[must_use]
212 pub fn final_state_digest(&self) -> &str {
213 &self.final_state_digest
214 }
215
216 #[must_use]
217 pub const fn final_state_sequence(&self) -> u64 {
218 self.final_state_sequence
219 }
220
221 #[must_use]
222 pub const fn final_cumulative_owed(&self) -> &MonetaryAmount {
223 &self.final_cumulative_owed
224 }
225
226 #[must_use]
227 pub const fn channel_state_version(&self) -> u64 {
228 self.channel_state_version
229 }
230
231 #[must_use]
232 pub const fn escrow_reservation_version(&self) -> u64 {
233 self.escrow_reservation_version
234 }
235
236 #[must_use]
237 pub const fn lifecycle_fence(&self) -> u64 {
238 self.lifecycle_fence
239 }
240
241 #[must_use]
242 pub fn bound_token_base_units(&self) -> &str {
243 &self.bound_token_base_units
244 }
245
246 #[must_use]
247 pub fn expected_release_token_base_units(&self) -> &str {
248 &self.expected_release_token_base_units
249 }
250
251 #[must_use]
252 pub fn expected_refund_token_base_units(&self) -> &str {
253 &self.expected_refund_token_base_units
254 }
255
256 #[must_use]
257 pub fn asset_binding_digest(&self) -> &str {
258 &self.asset_binding_digest
259 }
260
261 #[must_use]
262 pub fn original_web3_dispatch_digest(&self) -> &str {
263 &self.original_web3_dispatch_digest
264 }
265
266 #[must_use]
267 pub fn original_operator(&self) -> &str {
268 &self.original_operator
269 }
270
271 #[must_use]
272 pub fn original_operator_key_hash(&self) -> &str {
273 &self.original_operator_key_hash
274 }
275
276 #[must_use]
277 pub fn payee_beneficiary_address(&self) -> &str {
278 &self.payee_beneficiary_address
279 }
280
281 #[must_use]
282 pub const fn channel_expiry_unix_ms(&self) -> u64 {
283 self.channel_expiry_unix_ms
284 }
285
286 #[must_use]
287 pub const fn dispute_deadline_unix_ms(&self) -> u64 {
288 self.dispute_deadline_unix_ms
289 }
290
291 #[must_use]
292 pub const fn close_submission_cutoff_unix_ms(&self) -> u64 {
293 self.close_submission_cutoff_unix_ms
294 }
295
296 #[must_use]
297 pub const fn escrow_deadline_unix_ms(&self) -> u64 {
298 self.escrow_deadline_unix_ms
299 }
300
301 #[must_use]
302 pub const fn publisher_fence(&self) -> u64 {
303 self.publisher_fence
304 }
305
306 #[must_use]
307 pub const fn authorized_at_unix_ms(&self) -> u64 {
308 self.authorized_at_unix_ms
309 }
310
311 #[must_use]
312 pub const fn frost(&self) -> &EconomicFrostBindingV1 {
313 &self.frost
314 }
315
316 #[must_use]
317 pub fn frost_scope_id(&self) -> &str {
318 &self.frost_scope_id
319 }
320
321 #[must_use]
322 pub fn frost_resource_id(&self) -> &str {
323 &self.frost_resource_id
324 }
325
326 #[must_use]
327 pub const fn frost_resource_version(&self) -> u64 {
328 self.frost_resource_version
329 }
330
331 #[must_use]
332 pub const fn frost_resource_fence(&self) -> u64 {
333 self.frost_resource_fence
334 }
335
336 #[must_use]
337 pub fn frost_roster_digest(&self) -> &str {
338 &self.frost_roster_digest
339 }
340
341 #[must_use]
342 pub const fn frost_key_epoch(&self) -> u64 {
343 self.frost_key_epoch
344 }
345
346 #[must_use]
347 pub const fn frost_issued_at_unix_ms(&self) -> u64 {
348 self.frost_issued_at_unix_ms
349 }
350}
351
352#[derive(Debug, Clone)]
353pub struct VerifiedChannelReleaseAuthorizationV1 {
354 close: VerifiedEffectiveChannelCloseV1,
355 binding: ChannelReleaseAuthorizationBindingV1,
356 authorization_digest: String,
357}
358
359impl VerifiedChannelReleaseAuthorizationV1 {
360 #[must_use]
361 pub const fn close(&self) -> &VerifiedEffectiveChannelCloseV1 {
362 &self.close
363 }
364
365 #[must_use]
366 pub const fn binding(&self) -> &ChannelReleaseAuthorizationBindingV1 {
367 &self.binding
368 }
369
370 #[must_use]
371 pub fn authorization_digest(&self) -> &str {
372 &self.authorization_digest
373 }
374}
375
376#[derive(Debug, Clone)]
377pub struct VerifiedSignedChannelReleaseAuthorizationV1 {
378 artifact: SignedChannelReleaseAuthorizationV1,
379 authority: VerifiedChannelReleaseAuthorizationV1,
380 digest: String,
381}
382
383impl VerifiedSignedChannelReleaseAuthorizationV1 {
384 #[must_use]
385 pub const fn artifact(&self) -> &SignedChannelReleaseAuthorizationV1 {
386 &self.artifact
387 }
388
389 #[must_use]
390 pub const fn body(&self) -> &ChannelReleaseAuthorizationBodyV1 {
391 &self.artifact.body
392 }
393
394 #[must_use]
395 pub const fn authority(&self) -> &VerifiedChannelReleaseAuthorizationV1 {
396 &self.authority
397 }
398
399 #[must_use]
400 pub fn digest(&self) -> &str {
401 &self.digest
402 }
403}
404
405#[cfg(test)]
406pub(crate) fn verify_channel_release_dispatch_slot(
407 authorization: &VerifiedSignedChannelReleaseAuthorizationV1,
408 slot: &EconomicEffectSlotV1,
409) -> Result<(), ChannelError> {
410 let body = authorization.body();
411 let authority = authorization.authority().binding();
412 let release = body.release_broadcast();
413 if body.authority() != authority
414 || slot.slot_id != release.effect_slot_id
415 || slot.operation_id != release.operation_id
416 || slot.resource_key.resource_family != "channel_escrow_reservation"
417 || slot.resource_key.scope_id != release.scope_id
418 || slot.resource_key.scope_id != authority.frost_scope_id()
419 || slot.resource_key.resource_id != authority.channel_id()
420 || slot.resource_key.resource_id != authority.frost_resource_id()
421 || slot.effect_kind != release.effect_kind
422 || slot.effect_kind != CHANNEL_RELEASE_BROADCAST_EFFECT_KIND
423 || slot.idempotency_key != release.idempotency_key
424 || slot.parameters_digest != release.call_digest
425 || slot.resource_head_digest != release.resource_head_digest
426 || slot.action_digest != authority.frost().action_digest
427 || slot.frost.as_ref() != Some(authority.frost())
428 || slot.admission_handoff.state != EconomicAdmissionHandoffStateV1::MutationSubmitted
429 || slot.state != EconomicEffectStateV1::DispatchCommitted
430 {
431 return Err(ChannelError::AuthorityVerification);
432 }
433 Ok(())
434}
435
436pub fn build_channel_release_authorization_body(
437 authority: &VerifiedChannelReleaseAuthorizationV1,
438 publication_root: String,
439 root_publication: ChannelReleaseMutationBindingV1,
440 release_broadcast: ChannelReleaseMutationBindingV1,
441) -> Result<ChannelReleaseAuthorizationBodyV1, ChannelError> {
442 let body = ChannelReleaseAuthorizationBodyV1 {
443 schema: CHIO_CHANNEL_RELEASE_AUTHORIZATION_V1_SCHEMA.to_owned(),
444 authority: authority.binding().clone(),
445 publication_root,
446 root_publication,
447 release_broadcast,
448 };
449 body.validate()?;
450 Ok(body)
451}
452
453pub fn verify_signed_channel_release_authorization(
454 artifact: &SignedChannelReleaseAuthorizationV1,
455 authority: &VerifiedChannelReleaseAuthorizationV1,
456 trust: &ChannelReleasePublisherTrustV1,
457 trusted_time_unix_ms: u64,
458) -> Result<VerifiedSignedChannelReleaseAuthorizationV1, ChannelError> {
459 trust.validate()?;
460 artifact.body.validate()?;
461 validate_positive("release_trusted_time", trusted_time_unix_ms)?;
462 if artifact.body.authority != *authority.binding()
463 || trusted_time_unix_ms < authority.binding().authorized_at_unix_ms()
464 || trusted_time_unix_ms >= authority.binding().close_submission_cutoff_unix_ms()
465 || trusted_time_unix_ms >= authority.binding().escrow_deadline_unix_ms()
466 {
467 return Err(ChannelError::AuthorityVerification);
468 }
469 artifact.publisher_signature.verify(
470 &artifact.body,
471 &trust.publisher_id,
472 trust.publisher_key_epoch,
473 &trust.publisher_key,
474 )?;
475 let digest = artifact.digest()?;
476 Ok(VerifiedSignedChannelReleaseAuthorizationV1 {
477 artifact: artifact.clone(),
478 authority: authority.clone(),
479 digest,
480 })
481}
482
483#[derive(Debug, Clone)]
484pub(super) struct ChannelReleaseFrostFacts {
485 pub(super) authorization_slot_id: String,
486 pub(super) authorization_id: String,
487 pub(super) action_digest: String,
488 pub(super) signed_envelope_digest: String,
489 pub(super) scope_id: String,
490 pub(super) resource_id: String,
491 pub(super) resource_version: u64,
492 pub(super) resource_fence: u64,
493 pub(super) roster_digest: String,
494 pub(super) key_epoch: u64,
495 pub(super) issued_at_unix_ms: u64,
496 pub(super) current: bool,
497}
498
499#[derive(Debug, Clone)]
500pub(crate) struct ChannelReleasePreparationFacts {
501 pub(crate) dispatch_digest: String,
502 pub(crate) chain_id: String,
503 pub(crate) escrow_contract: String,
504 pub(crate) escrow_id: String,
505 pub(crate) token_address: String,
506 pub(crate) token_symbol: String,
507 pub(crate) beneficiary_address: String,
508 pub(crate) operator: String,
509 pub(crate) operator_key_hash: String,
510 pub(crate) protocol_minor_unit_decimals: u8,
511 pub(crate) token_decimals: u8,
512 pub(crate) escrow_bound: MonetaryAmount,
513 pub(crate) release_amount: MonetaryAmount,
514 pub(crate) release_token_base_units: String,
515}
516
517pub fn verify_channel_release_authorization(
518 close: &VerifiedEffectiveChannelCloseV1,
519 frost: &VerifiedFrostAuthorization,
520 publisher_fence: u64,
521 trusted_time_unix_ms: u64,
522) -> Result<VerifiedChannelReleaseAuthorizationV1, ChannelError> {
523 let action = channel_close_frost_action(close, publisher_fence)?;
524 if frost.verify_action_preimage(&action).is_err() {
525 return Err(ChannelError::AuthorityVerification);
526 }
527 verify_channel_release_authorization_parts(
528 close,
529 &ChannelReleaseFrostFacts {
530 authorization_slot_id: frost.authorization_slot_id().to_owned(),
531 authorization_id: frost.authorization_id().to_owned(),
532 action_digest: frost.action_digest().to_owned(),
533 signed_envelope_digest: frost.proof_digest().to_owned(),
534 scope_id: frost.scope_id().to_owned(),
535 resource_id: frost.resource_id().to_owned(),
536 resource_version: frost.resource_version(),
537 resource_fence: frost.resource_fence(),
538 roster_digest: frost.roster_digest().to_owned(),
539 key_epoch: frost.key_epoch(),
540 issued_at_unix_ms: frost.issued_at(),
541 current: frost.is_current_at(trusted_time_unix_ms),
542 },
543 publisher_fence,
544 trusted_time_unix_ms,
545 )
546}
547
548pub(super) fn verify_channel_release_authorization_parts(
549 close: &VerifiedEffectiveChannelCloseV1,
550 frost: &ChannelReleaseFrostFacts,
551 publisher_fence: u64,
552 trusted_time_unix_ms: u64,
553) -> Result<VerifiedChannelReleaseAuthorizationV1, ChannelError> {
554 validate_positive("release_publisher_fence", publisher_fence)?;
555 validate_positive("release_trusted_time", trusted_time_unix_ms)?;
556 for (field, value) in [
557 ("release_frost_slot_id", &frost.authorization_slot_id),
558 ("release_frost_authorization_id", &frost.authorization_id),
559 ("release_frost_action_digest", &frost.action_digest),
560 (
561 "release_frost_signed_envelope_digest",
562 &frost.signed_envelope_digest,
563 ),
564 ("release_frost_roster_digest", &frost.roster_digest),
565 ] {
566 validate_digest(field, value)?;
567 }
568 validate_text("release_frost_scope_id", &frost.scope_id)?;
569 validate_text("release_frost_resource_id", &frost.resource_id)?;
570 validate_positive("release_frost_resource_version", frost.resource_version)?;
571 validate_positive("release_frost_resource_fence", frost.resource_fence)?;
572 validate_positive("release_frost_key_epoch", frost.key_epoch)?;
573
574 let verified_close = close.close();
575 let close_body = &verified_close.artifact().body;
576 let open = verified_close.open();
577 let intent = &open.intent().body;
578 let state = close.effective_state().body();
579 let snapshot = close.snapshot();
580 let lifecycle = snapshot.lifecycle();
581 let escrow = snapshot.escrow();
582 let close_digest = verified_close.artifact().digest()?;
583 let close_body_digest = close_body.digest()?;
584 let final_state_digest = close.effective_state().digest()?;
585 let action = channel_close_frost_action(close, publisher_fence)?;
586 let expected_action_digest = action
587 .action_digest()
588 .map_err(|_| ChannelError::AuthorityVerification)?;
589 let channel_expiry_unix_ms = intent
590 .channel_expiry_unix_secs
591 .checked_mul(1_000)
592 .ok_or(ChannelError::ArithmeticOverflow)?;
593 let close_submission_cutoff_unix_ms = intent
594 .close_submission_cutoff_unix_secs
595 .checked_mul(1_000)
596 .ok_or(ChannelError::ArithmeticOverflow)?;
597 let escrow_deadline_unix_ms = intent
598 .fixed_finality_broadcast_margin_secs
599 .checked_add(intent.close_submission_cutoff_unix_secs)
600 .and_then(|deadline| deadline.checked_mul(1_000))
601 .ok_or(ChannelError::ArithmeticOverflow)?;
602 let release = parse_base_units(close.expected_release_token_base_units())?;
603 let refund = parse_base_units(close.expected_refund_after_release_token_base_units())?;
604 let bound = parse_base_units(&intent.bound_token_base_units)?;
605 let frost_binding = EconomicFrostBindingV1 {
606 authorization_slot_id: frost.authorization_slot_id.clone(),
607 authorization_id: frost.authorization_id.clone(),
608 action_digest: frost.action_digest.clone(),
609 signed_envelope_digest: frost.signed_envelope_digest.clone(),
610 };
611 frost_binding
612 .validate()
613 .map_err(|_| ChannelError::AuthorityVerification)?;
614
615 if !frost.current
616 || frost.scope_id != snapshot.settlement_authority_scope_id()
617 || frost.resource_id != close_body.channel_id
618 || frost.resource_version != lifecycle.state_version
619 || frost.resource_fence != lifecycle.lifecycle_fence
620 || frost.action_digest != expected_action_digest
621 || frost.issued_at_unix_ms < snapshot.observed_at_unix_ms()
622 || frost.issued_at_unix_ms > trusted_time_unix_ms
623 || snapshot.observed_at_unix_ms() > trusted_time_unix_ms
624 || trusted_time_unix_ms >= close_submission_cutoff_unix_ms
625 || trusted_time_unix_ms >= escrow_deadline_unix_ms
626 || close_body.close_kind == ChannelCloseKindV1::Contested
627 && trusted_time_unix_ms < close_body.dispute_deadline_unix_ms
628 || close_body.open_digest != open.artifact().digest()?
629 || close_body.channel_id != lifecycle.channel_id
630 || close_body.channel_state_version != lifecycle.state_version
631 || close_body.escrow_reservation_version != escrow.version
632 || close_body.lifecycle_fence != lifecycle.lifecycle_fence
633 || close.effective_close_digest().is_empty()
634 || state.channel_id != close_body.channel_id
635 || state.seq != lifecycle.latest_sequence
636 || final_state_digest != lifecycle.latest_state_digest
637 || state.cumulative_owed.currency != intent.currency
638 || state.cumulative_owed.units > intent.bound.units
639 || intent
640 .asset_binding
641 .token_base_units(&state.cumulative_owed)?
642 != close.expected_release_token_base_units()
643 || release.checked_add(refund) != Some(bound)
644 || close_submission_cutoff_unix_ms >= escrow_deadline_unix_ms
645 {
646 return Err(ChannelError::AuthorityVerification);
647 }
648
649 let binding = ChannelReleaseAuthorizationBindingV1 {
650 channel_id: close_body.channel_id.clone(),
651 escrow_reference: intent.escrow_reference.clone(),
652 open_digest: close_body.open_digest.clone(),
653 close_digest,
654 close_body_digest,
655 effective_close_digest: close.effective_close_digest().to_owned(),
656 final_state_digest,
657 final_state_sequence: state.seq,
658 final_cumulative_owed: state.cumulative_owed.clone(),
659 channel_state_version: lifecycle.state_version,
660 escrow_reservation_version: escrow.version,
661 lifecycle_fence: lifecycle.lifecycle_fence,
662 bound_token_base_units: intent.bound_token_base_units.clone(),
663 expected_release_token_base_units: close.expected_release_token_base_units().to_owned(),
664 expected_refund_token_base_units: close
665 .expected_refund_after_release_token_base_units()
666 .to_owned(),
667 asset_binding_digest: intent.asset_binding.digest()?,
668 original_web3_dispatch_digest: intent.original_web3_dispatch_digest.clone(),
669 original_operator: intent.original_operator.clone(),
670 original_operator_key_hash: intent.original_operator_key_hash.clone(),
671 payee_beneficiary_address: intent.payee_beneficiary_address.clone(),
672 channel_expiry_unix_ms,
673 dispute_deadline_unix_ms: close_body.dispute_deadline_unix_ms,
674 close_submission_cutoff_unix_ms,
675 escrow_deadline_unix_ms,
676 publisher_fence,
677 authorized_at_unix_ms: trusted_time_unix_ms,
678 frost: frost_binding,
679 frost_scope_id: frost.scope_id.clone(),
680 frost_resource_id: frost.resource_id.clone(),
681 frost_resource_version: frost.resource_version,
682 frost_resource_fence: frost.resource_fence,
683 frost_roster_digest: frost.roster_digest.clone(),
684 frost_key_epoch: frost.key_epoch,
685 frost_issued_at_unix_ms: frost.issued_at_unix_ms,
686 };
687 let authorization_digest = digest(CHANNEL_RELEASE_AUTHORIZATION_DIGEST_DOMAIN, &binding)?;
688 Ok(VerifiedChannelReleaseAuthorizationV1 {
689 close: close.clone(),
690 binding,
691 authorization_digest,
692 })
693}
694
695pub(crate) fn verify_channel_release_preparation_parts(
696 authorization: &VerifiedChannelReleaseAuthorizationV1,
697 facts: &ChannelReleasePreparationFacts,
698) -> Result<(), ChannelError> {
699 let intent = &authorization.close().close().open().intent().body;
700 let binding = authorization.binding();
701 intent.validate()?;
702 if facts.dispatch_digest != binding.original_web3_dispatch_digest
703 || facts.chain_id != intent.asset_binding.chain_id
704 || facts.escrow_contract != intent.escrow_reference.escrow_contract
705 || facts.escrow_id != intent.escrow_reference.escrow_id
706 || facts.token_address != intent.asset_binding.token_address
707 || facts.token_symbol != intent.asset_binding.token_symbol
708 || facts.beneficiary_address != intent.payee_beneficiary_address
709 || facts.operator != intent.original_operator
710 || facts.operator_key_hash != intent.original_operator_key_hash
711 || facts.protocol_minor_unit_decimals != intent.asset_binding.protocol_minor_unit_decimals
712 || facts.token_decimals != intent.asset_binding.token_decimals
713 || facts.escrow_bound != intent.bound
714 || facts.release_amount
715 != authorization
716 .close()
717 .effective_state()
718 .body()
719 .cumulative_owed
720 || facts.release_token_base_units != binding.expected_release_token_base_units
721 || intent
722 .asset_binding
723 .token_base_units(&facts.release_amount)?
724 != facts.release_token_base_units
725 {
726 return Err(ChannelError::AuthorityVerification);
727 }
728 Ok(())
729}