1use std::{collections::HashSet, fmt};
4
5use rangeset::iter::{FromRangeIterator, IntoRangeIterator};
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 hash::HashAlgId,
10 transcript::{
11 hash::{PlaintextHash, PlaintextHashSecret},
12 Direction, RangeSet, Transcript,
13 },
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[non_exhaustive]
19pub enum TranscriptCommitmentKind {
20 Hash {
22 alg: HashAlgId,
24 },
25}
26
27impl fmt::Display for TranscriptCommitmentKind {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::Hash { alg } => write!(f, "hash ({alg})"),
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37#[non_exhaustive]
38pub enum TranscriptCommitment {
39 Hash(PlaintextHash),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45#[non_exhaustive]
46pub enum TranscriptSecret {
47 Hash(PlaintextHashSecret),
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TranscriptCommitConfig {
54 commits: Vec<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
55}
56
57impl TranscriptCommitConfig {
58 pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder<'_> {
60 TranscriptCommitConfigBuilder::new(transcript)
61 }
62
63 pub fn has_hash(&self) -> bool {
65 self.commits
66 .iter()
67 .any(|(_, kind)| matches!(kind, TranscriptCommitmentKind::Hash { .. }))
68 }
69
70 pub fn iter_hash(&self) -> impl Iterator<Item = (&(Direction, RangeSet<usize>), &HashAlgId)> {
72 self.commits.iter().map(|(idx, kind)| match kind {
73 TranscriptCommitmentKind::Hash { alg } => (idx, alg),
74 })
75 }
76
77 pub fn to_request(&self) -> TranscriptCommitRequest {
79 TranscriptCommitRequest {
80 hash: self
81 .iter_hash()
82 .map(|((dir, idx), alg)| (*dir, idx.clone(), *alg))
83 .collect(),
84 }
85 }
86}
87
88#[derive(Debug)]
90pub struct TranscriptCommitConfigBuilder<'a> {
91 transcript: &'a Transcript,
92 default_kind: TranscriptCommitmentKind,
93 commits: HashSet<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
94}
95
96impl<'a> TranscriptCommitConfigBuilder<'a> {
97 pub fn new(transcript: &'a Transcript) -> Self {
99 Self {
100 transcript,
101 default_kind: TranscriptCommitmentKind::Hash {
102 alg: HashAlgId::BLAKE3,
103 },
104 commits: HashSet::default(),
105 }
106 }
107
108 pub fn default_kind(&mut self, default_kind: TranscriptCommitmentKind) -> &mut Self {
110 self.default_kind = default_kind;
111 self
112 }
113
114 pub fn commit_with_kind(
122 &mut self,
123 ranges: impl IntoRangeIterator<usize>,
124 direction: Direction,
125 kind: TranscriptCommitmentKind,
126 ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
127 self.commit_with_kind_inner(RangeSet::from_range_iter(ranges), direction, kind)
128 }
129
130 fn commit_with_kind_inner(
131 &mut self,
132 idx: RangeSet<usize>,
133 direction: Direction,
134 kind: TranscriptCommitmentKind,
135 ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
136 if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
137 return Err(TranscriptCommitConfigBuilderError::new(
138 ErrorKind::Index,
139 format!(
140 "range is out of bounds of the transcript ({}): {} > {}",
141 direction,
142 idx.end().unwrap_or(0),
143 self.transcript.len_of_direction(direction)
144 ),
145 ));
146 }
147
148 self.commits.insert(((direction, idx), kind));
149
150 Ok(self)
151 }
152
153 pub fn commit(
160 &mut self,
161 ranges: impl IntoRangeIterator<usize>,
162 direction: Direction,
163 ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
164 self.commit_with_kind_inner(
165 RangeSet::from_range_iter(ranges),
166 direction,
167 self.default_kind,
168 )
169 }
170
171 pub fn commit_sent(
177 &mut self,
178 ranges: impl IntoRangeIterator<usize>,
179 ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
180 self.commit_with_kind_inner(
181 RangeSet::from_range_iter(ranges),
182 Direction::Sent,
183 self.default_kind,
184 )
185 }
186
187 pub fn commit_recv(
193 &mut self,
194 ranges: impl IntoRangeIterator<usize>,
195 ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
196 self.commit_with_kind_inner(
197 RangeSet::from_range_iter(ranges),
198 Direction::Received,
199 self.default_kind,
200 )
201 }
202
203 pub fn build(self) -> Result<TranscriptCommitConfig, TranscriptCommitConfigBuilderError> {
205 Ok(TranscriptCommitConfig {
206 commits: Vec::from_iter(self.commits),
207 })
208 }
209}
210
211#[derive(Debug, thiserror::Error)]
213pub struct TranscriptCommitConfigBuilderError {
214 kind: ErrorKind,
215 source: Option<Box<dyn std::error::Error + Send + Sync>>,
216}
217
218impl TranscriptCommitConfigBuilderError {
219 fn new<E>(kind: ErrorKind, source: E) -> Self
220 where
221 E: Into<Box<dyn std::error::Error + Send + Sync>>,
222 {
223 Self {
224 kind,
225 source: Some(source.into()),
226 }
227 }
228}
229
230#[derive(Debug)]
231enum ErrorKind {
232 Index,
233}
234
235impl fmt::Display for TranscriptCommitConfigBuilderError {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 match self.kind {
238 ErrorKind::Index => f.write_str("index error")?,
239 }
240
241 if let Some(source) = &self.source {
242 write!(f, " caused by: {source}")?;
243 }
244
245 Ok(())
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct TranscriptCommitRequest {
252 hash: Vec<(Direction, RangeSet<usize>, HashAlgId)>,
253}
254
255impl TranscriptCommitRequest {
256 pub fn has_hash(&self) -> bool {
258 !self.hash.is_empty()
259 }
260
261 pub fn iter_hash(&self) -> impl Iterator<Item = &(Direction, RangeSet<usize>, HashAlgId)> {
263 self.hash.iter()
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn test_range_out_of_bounds() {
273 let transcript = Transcript::new(
274 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
275 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
276 );
277 let mut builder = TranscriptCommitConfigBuilder::new(&transcript);
278
279 assert!(builder.commit_sent(&(10..15)).is_err());
280 assert!(builder.commit_recv(&(10..15)).is_err());
281 }
282}