chio_settle/channel/
terminal_outcome.rs1use 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}