Skip to main content

chio_settle/channel/
terminal_outcome.rs

1use chio_core::canonical::canonical_json_bytes;
2#[cfg(test)]
3use chio_core::crypto::Keypair;
4use chio_core::crypto::{sha256_hex, PublicKey, Signature};
5use chio_core::economic_continuity::EconomicTerminalResultV1;
6use chio_core::receipt::body::ChioReceipt;
7use serde::{Deserialize, Serialize};
8
9use super::validation::{validate_digest, validate_positive};
10use super::{ChannelError, VerifiedAdmittedChannelReservationV1};
11
12pub const CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA: &str =
13    "chio.channel.terminal-outcome-commitment.v1";
14
15const CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN: &[u8] =
16    b"CHIO-CHANNEL-TERMINAL-OUTCOME-COMMITMENT-V1\0";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase", deny_unknown_fields)]
20pub struct ChannelTerminalOutcomeCommitmentBodyV1 {
21    pub schema: String,
22    pub operation_id: String,
23    pub reservation_id: String,
24    pub reservation_digest: String,
25    pub receipt_id: String,
26    pub receipt_digest: String,
27    pub terminal_result: EconomicTerminalResultV1,
28    pub outcome_recorded_at_unix_ms: u64,
29    pub terminalized_at_unix_ms: u64,
30}
31
32impl ChannelTerminalOutcomeCommitmentBodyV1 {
33    pub fn validate(&self) -> Result<(), ChannelError> {
34        if self.schema != CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA {
35            return Err(ChannelError::InvalidField(
36                "channel_terminal_outcome_schema",
37            ));
38        }
39        validate_digest("channel_terminal_outcome_operation_id", &self.operation_id)?;
40        validate_digest(
41            "channel_terminal_outcome_reservation_id",
42            &self.reservation_id,
43        )?;
44        validate_digest(
45            "channel_terminal_outcome_reservation_digest",
46            &self.reservation_digest,
47        )?;
48        super::validation::validate_text("channel_terminal_outcome_receipt_id", &self.receipt_id)?;
49        validate_digest(
50            "channel_terminal_outcome_receipt_digest",
51            &self.receipt_digest,
52        )?;
53        self.terminal_result
54            .validate()
55            .map_err(|_| ChannelError::AuthorityVerification)?;
56        validate_positive(
57            "channel_terminal_outcome_recorded_at",
58            self.outcome_recorded_at_unix_ms,
59        )?;
60        validate_positive(
61            "channel_terminal_outcome_terminalized_at",
62            self.terminalized_at_unix_ms,
63        )?;
64        if self.outcome_recorded_at_unix_ms > self.terminalized_at_unix_ms {
65            return Err(ChannelError::AuthorityVerification);
66        }
67        Ok(())
68    }
69
70    #[doc(hidden)]
71    pub fn signing_bytes(&self) -> Result<Vec<u8>, ChannelError> {
72        self.validate()?;
73        let canonical = canonical_json_bytes(self)
74            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
75        let mut bytes =
76            Vec::with_capacity(CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN.len() + canonical.len());
77        bytes.extend_from_slice(CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN);
78        bytes.extend_from_slice(&canonical);
79        Ok(bytes)
80    }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase", deny_unknown_fields)]
85pub struct SignedChannelTerminalOutcomeCommitmentV1 {
86    pub body: ChannelTerminalOutcomeCommitmentBodyV1,
87    #[serde(deserialize_with = "super::signed::deserialize_canonical_public_key")]
88    pub kernel_key: PublicKey,
89    #[serde(deserialize_with = "super::signed::deserialize_canonical_signature")]
90    pub kernel_signature: Signature,
91}
92
93impl SignedChannelTerminalOutcomeCommitmentV1 {
94    #[cfg(test)]
95    pub(crate) fn sign_for_test(
96        reservation: &VerifiedAdmittedChannelReservationV1,
97        receipt: &ChioReceipt,
98        terminal_result: EconomicTerminalResultV1,
99        outcome_recorded_at_unix_ms: u64,
100        terminalized_at_unix_ms: u64,
101        kernel_keypair: &Keypair,
102    ) -> Result<Self, ChannelError> {
103        let artifact = reservation.artifact();
104        let body = ChannelTerminalOutcomeCommitmentBodyV1 {
105            schema: CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA.to_owned(),
106            operation_id: artifact.body.operation_id.clone(),
107            reservation_id: artifact.body.reservation_id.clone(),
108            reservation_digest: artifact.digest()?,
109            receipt_id: receipt.id.clone(),
110            receipt_digest: receipt_digest(receipt)?,
111            terminal_result,
112            outcome_recorded_at_unix_ms,
113            terminalized_at_unix_ms,
114        };
115        let kernel_signature = kernel_keypair.sign(&body.signing_bytes()?);
116        Ok(Self {
117            body,
118            kernel_key: kernel_keypair.public_key(),
119            kernel_signature,
120        })
121    }
122
123    pub fn verify_signature(&self) -> Result<(), ChannelError> {
124        if self
125            .kernel_key
126            .verify(&self.body.signing_bytes()?, &self.kernel_signature)
127        {
128            Ok(())
129        } else {
130            Err(ChannelError::AuthorityVerification)
131        }
132    }
133}
134
135#[derive(Debug, Clone)]
136pub struct VerifiedChannelTerminalOutcomeCommitmentV1 {
137    artifact: SignedChannelTerminalOutcomeCommitmentV1,
138}
139
140impl VerifiedChannelTerminalOutcomeCommitmentV1 {
141    #[must_use]
142    pub const fn artifact(&self) -> &SignedChannelTerminalOutcomeCommitmentV1 {
143        &self.artifact
144    }
145
146    #[must_use]
147    pub const fn terminal_result(&self) -> &EconomicTerminalResultV1 {
148        &self.artifact.body.terminal_result
149    }
150
151    #[must_use]
152    pub const fn outcome_recorded_at_unix_ms(&self) -> u64 {
153        self.artifact.body.outcome_recorded_at_unix_ms
154    }
155
156    #[must_use]
157    pub const fn terminalized_at_unix_ms(&self) -> u64 {
158        self.artifact.body.terminalized_at_unix_ms
159    }
160}
161
162pub fn verify_channel_terminal_outcome_commitment(
163    artifact: &SignedChannelTerminalOutcomeCommitmentV1,
164    trusted_kernel_key: &PublicKey,
165    reservation: &VerifiedAdmittedChannelReservationV1,
166    receipt: &ChioReceipt,
167) -> Result<VerifiedChannelTerminalOutcomeCommitmentV1, ChannelError> {
168    artifact.body.validate()?;
169    artifact.verify_signature()?;
170    let reservation = reservation.artifact();
171    if &artifact.kernel_key != trusted_kernel_key
172        || &receipt.kernel_key != trusted_kernel_key
173        || !receipt
174            .verify_signature()
175            .map_err(|_| ChannelError::AuthorityVerification)?
176        || artifact.body.operation_id != reservation.body.operation_id
177        || artifact.body.reservation_id != reservation.body.reservation_id
178        || artifact.body.reservation_digest != reservation.digest()?
179        || artifact.body.receipt_id != receipt.id
180        || artifact.body.receipt_digest != receipt_digest(receipt)?
181        || receipt.timestamp != artifact.body.terminalized_at_unix_ms / 1_000
182    {
183        return Err(ChannelError::AuthorityVerification);
184    }
185    Ok(VerifiedChannelTerminalOutcomeCommitmentV1 {
186        artifact: artifact.clone(),
187    })
188}
189
190fn receipt_digest(receipt: &ChioReceipt) -> Result<String, ChannelError> {
191    Ok(sha256_hex(&canonical_json_bytes(receipt).map_err(
192        |error| ChannelError::Canonicalization(error.to_string()),
193    )?))
194}