Skip to main content

chio_settle/channel/
rail.rs

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}