1use chio_core::crypto::PublicKey;
2use serde::{Deserialize, Serialize};
3
4use super::validation::{
5 digest, validate_digest, validate_positive, validate_text, I_JSON_MAX_SAFE_INTEGER,
6};
7use super::{
8 ChannelError, ChannelSignatureV1, SignedChannelStateV1, VerifiedChannelCloseV1,
9 VerifiedChannelStateV1,
10};
11
12pub const CHANNEL_DISPUTE_SCHEMA: &str = "chio.channel.dispute.v1";
13
14const CHANNEL_STATE_CHAIN_DIGEST_DOMAIN: &[u8] = b"chio.channel.state-chain.digest.v1\0";
15const CHANNEL_DISPUTE_ID_DOMAIN: &[u8] = b"chio.channel.dispute.id.v1\0";
16const CHANNEL_DISPUTE_DIGEST_DOMAIN: &[u8] = b"chio.channel.dispute.digest.v1\0";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase", deny_unknown_fields)]
20pub struct ChannelStateChainProofV1 {
21 pub base_state_digest: String,
22 pub states: Vec<SignedChannelStateV1>,
23}
24
25impl ChannelStateChainProofV1 {
26 pub fn digest(&self) -> Result<String, ChannelError> {
27 validate_digest("chain_base_state_digest", &self.base_state_digest)?;
28 if self.states.is_empty() {
29 return Err(ChannelError::InvalidField("channel_state_chain"));
30 }
31 digest(CHANNEL_STATE_CHAIN_DIGEST_DOMAIN, self)
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct VerifiedChannelStateChainV1 {
37 proof: ChannelStateChainProofV1,
38 terminal_state: VerifiedChannelStateV1,
39}
40
41impl VerifiedChannelStateChainV1 {
42 #[must_use]
43 pub const fn proof(&self) -> &ChannelStateChainProofV1 {
44 &self.proof
45 }
46
47 #[must_use]
48 pub const fn terminal_state(&self) -> &VerifiedChannelStateV1 {
49 &self.terminal_state
50 }
51}
52
53pub fn build_channel_state_chain(
54 base: &VerifiedChannelStateV1,
55 descendants: &[VerifiedChannelStateV1],
56) -> Result<VerifiedChannelStateChainV1, ChannelError> {
57 if descendants.is_empty() {
58 return Err(ChannelError::InvalidField("channel_state_chain"));
59 }
60 let base_state_digest = base.digest()?;
61 let mut prior_state_digest = base_state_digest.clone();
62 let mut prior_channel_id = base.body().channel_id.clone();
63 let mut prior_sequence = base.body().seq;
64 let mut states = Vec::with_capacity(descendants.len());
65 for descendant in descendants {
66 let body = descendant.body();
67 let expected_sequence = prior_sequence
68 .checked_add(1)
69 .filter(|sequence| *sequence <= I_JSON_MAX_SAFE_INTEGER)
70 .ok_or(ChannelError::ArithmeticOverflow)?;
71 let signature = descendant
72 .payee_signature()
73 .ok_or(ChannelError::AuthorityVerification)?;
74 if body.channel_id != prior_channel_id
75 || body.seq != expected_sequence
76 || body.prev_state_digest.as_deref() != Some(&prior_state_digest)
77 {
78 return Err(ChannelError::AuthorityVerification);
79 }
80 states.push(SignedChannelStateV1 {
81 body: body.clone(),
82 payee_signature: signature.clone(),
83 });
84 prior_state_digest = descendant.digest()?;
85 prior_channel_id.clone_from(&body.channel_id);
86 prior_sequence = body.seq;
87 }
88 let proof = ChannelStateChainProofV1 {
89 base_state_digest,
90 states,
91 };
92 proof.digest()?;
93 let terminal_state = descendants
94 .last()
95 .cloned()
96 .ok_or(ChannelError::InvalidField("channel_state_chain"))?;
97 Ok(VerifiedChannelStateChainV1 {
98 proof,
99 terminal_state,
100 })
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase", deny_unknown_fields)]
105pub struct ChannelDisputeBodyV1 {
106 pub schema: String,
107 pub dispute_id: String,
108 pub channel_id: String,
109 pub close_digest: String,
110 pub close_state_digest: String,
111 pub close_state_sequence: u64,
112 pub competing_state_digest: String,
113 pub competing_state_sequence: u64,
114 pub state_chain_proof_digest: String,
115 pub reason: String,
116 pub submitted_at_unix_ms: u64,
117}
118
119impl ChannelDisputeBodyV1 {
120 pub fn validate(&self) -> Result<(), ChannelError> {
121 if self.schema != CHANNEL_DISPUTE_SCHEMA {
122 return Err(ChannelError::InvalidField("channel_dispute_schema"));
123 }
124 for (field, value) in [
125 ("channel_dispute_id", &self.dispute_id),
126 ("dispute_channel_id", &self.channel_id),
127 ("dispute_close_digest", &self.close_digest),
128 ("dispute_close_state_digest", &self.close_state_digest),
129 (
130 "dispute_competing_state_digest",
131 &self.competing_state_digest,
132 ),
133 (
134 "dispute_state_chain_proof_digest",
135 &self.state_chain_proof_digest,
136 ),
137 ] {
138 validate_digest(field, value)?;
139 }
140 validate_text("channel_dispute_reason", &self.reason)?;
141 validate_positive("channel_dispute_submitted_at", self.submitted_at_unix_ms)?;
142 if self.close_state_sequence > I_JSON_MAX_SAFE_INTEGER
143 || self.competing_state_sequence > I_JSON_MAX_SAFE_INTEGER
144 || self.competing_state_sequence <= self.close_state_sequence
145 {
146 return Err(ChannelError::InvalidField("channel_dispute_sequence"));
147 }
148 Ok(())
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase", deny_unknown_fields)]
154pub struct SignedChannelDisputeV1 {
155 pub body: ChannelDisputeBodyV1,
156 pub submitter_signature: ChannelSignatureV1,
157}
158
159impl SignedChannelDisputeV1 {
160 pub fn digest(&self) -> Result<String, ChannelError> {
161 self.body.validate()?;
162 digest(CHANNEL_DISPUTE_DIGEST_DOMAIN, self)
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct ChannelDisputeSubmitterV1 {
168 pub submitter_id: String,
169 pub submitter_key_epoch: u64,
170 pub submitter_key: PublicKey,
171 pub trusted_time_unix_ms: u64,
172}
173
174impl ChannelDisputeSubmitterV1 {
175 fn validate(&self) -> Result<(), ChannelError> {
176 validate_text("channel_dispute_submitter_id", &self.submitter_id)?;
177 validate_positive(
178 "channel_dispute_submitter_key_epoch",
179 self.submitter_key_epoch,
180 )?;
181 validate_positive("channel_dispute_trusted_time", self.trusted_time_unix_ms)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct VerifiedChannelDisputeV1 {
187 dispute: SignedChannelDisputeV1,
188 chain: VerifiedChannelStateChainV1,
189}
190
191impl VerifiedChannelDisputeV1 {
192 #[must_use]
193 pub const fn artifact(&self) -> &SignedChannelDisputeV1 {
194 &self.dispute
195 }
196
197 #[must_use]
198 pub const fn chain(&self) -> &VerifiedChannelStateChainV1 {
199 &self.chain
200 }
201}
202
203pub fn derive_channel_dispute_id(
204 close_digest: &str,
205 competing_state_digest: &str,
206) -> Result<String, ChannelError> {
207 validate_digest("dispute_close_digest", close_digest)?;
208 validate_digest("dispute_competing_state_digest", competing_state_digest)?;
209 digest(
210 CHANNEL_DISPUTE_ID_DOMAIN,
211 &(close_digest, competing_state_digest),
212 )
213}
214
215pub fn build_channel_dispute_body(
216 close: &VerifiedChannelCloseV1,
217 chain: &VerifiedChannelStateChainV1,
218 reason: String,
219 submitted_at_unix_ms: u64,
220) -> Result<ChannelDisputeBodyV1, ChannelError> {
221 let close_digest = close.artifact().digest()?;
222 let competing_state_digest = chain.terminal_state().digest()?;
223 let competing_state = chain.terminal_state().body();
224 let body = ChannelDisputeBodyV1 {
225 schema: CHANNEL_DISPUTE_SCHEMA.to_owned(),
226 dispute_id: derive_channel_dispute_id(&close_digest, &competing_state_digest)?,
227 channel_id: close.artifact().body.channel_id.clone(),
228 close_digest,
229 close_state_digest: close.artifact().body.final_state_digest.clone(),
230 close_state_sequence: close.artifact().body.final_state_sequence,
231 competing_state_digest,
232 competing_state_sequence: competing_state.seq,
233 state_chain_proof_digest: chain.proof().digest()?,
234 reason,
235 submitted_at_unix_ms,
236 };
237 if chain.proof().base_state_digest != body.close_state_digest {
238 return Err(ChannelError::AuthorityVerification);
239 }
240 body.validate()?;
241 Ok(body)
242}
243
244pub fn verify_channel_dispute(
245 dispute: &SignedChannelDisputeV1,
246 close: &VerifiedChannelCloseV1,
247 chain: &VerifiedChannelStateChainV1,
248 submitter: &ChannelDisputeSubmitterV1,
249) -> Result<VerifiedChannelDisputeV1, ChannelError> {
250 dispute.body.validate()?;
251 submitter.validate()?;
252 dispute.submitter_signature.verify(
253 &dispute.body,
254 &submitter.submitter_id,
255 submitter.submitter_key_epoch,
256 &submitter.submitter_key,
257 )?;
258 let expected = build_channel_dispute_body(
259 close,
260 chain,
261 dispute.body.reason.clone(),
262 dispute.body.submitted_at_unix_ms,
263 )?;
264 if dispute.body != expected
265 || dispute.body.submitted_at_unix_ms > submitter.trusted_time_unix_ms
266 || submitter.trusted_time_unix_ms >= close.artifact().body.dispute_deadline_unix_ms
267 {
268 return Err(ChannelError::AuthorityVerification);
269 }
270 Ok(VerifiedChannelDisputeV1 {
271 dispute: dispute.clone(),
272 chain: chain.clone(),
273 })
274}