1use std::collections::{BTreeMap, BTreeSet};
23use std::fmt::{self, Display, Formatter};
24use std::hash::Hash;
25
26use amplify::confinement::{Confined, TinyVec, U64 as U64MAX};
27use amplify::Bytes32;
28use sha2::Sha256;
29use strict_encoding::{Sizing, StreamWriter, StrictDumb, StrictEncode, StrictType};
30use strict_types::typesys::TypeFqn;
31
32use crate::{Conceal, DigestExt, MerkleHash, MerkleLeaves, LIB_NAME_COMMIT_VERIFY};
33
34const COMMIT_MAX_LEN: usize = U64MAX;
35
36#[derive(Clone, Eq, PartialEq, Hash, Debug)]
37pub enum CommitColType {
38 List,
39 Set,
40 Map { key: TypeFqn },
41}
42
43#[derive(Clone, Eq, PartialEq, Hash, Debug)]
44pub enum CommitStep {
45 Serialized(TypeFqn),
46 Collection(CommitColType, Sizing, TypeFqn),
47 Hashed(TypeFqn),
48 Merklized(TypeFqn),
49 Concealed(TypeFqn),
50}
51
52#[derive(Clone, Debug)]
53pub struct CommitEngine {
54 finished: bool,
55 hasher: Sha256,
56 layout: TinyVec<CommitStep>,
57}
58
59fn commitment_fqn<T: StrictType>() -> TypeFqn {
60 TypeFqn::with(
61 libname!(T::STRICT_LIB_NAME),
62 T::strict_name().expect("commit encoder can commit only to named types"),
63 )
64}
65
66impl CommitEngine {
67 pub fn new(tag: &'static str) -> Self {
68 Self {
69 finished: false,
70 hasher: Sha256::from_tag(tag),
71 layout: empty!(),
72 }
73 }
74
75 fn inner_commit_to<T: StrictEncode, const MAX_LEN: usize>(&mut self, value: &T) {
76 debug_assert!(!self.finished);
77 let writer = StreamWriter::new::<MAX_LEN>(&mut self.hasher);
78 let ok = value.strict_write(writer).is_ok();
79 debug_assert!(ok);
80 }
81
82 pub fn commit_to_serialized<T: StrictEncode>(&mut self, value: &T) {
83 let fqn = commitment_fqn::<T>();
84 debug_assert!(
85 Some(&fqn.name) != MerkleHash::strict_name().as_ref() ||
86 fqn.lib.as_str() != MerkleHash::STRICT_LIB_NAME,
87 "do not use commit_to_serialized for merklized collections, use commit_to_merkle \
88 instead"
89 );
90 debug_assert!(
91 Some(&fqn.name) != StrictHash::strict_name().as_ref() ||
92 fqn.lib.as_str() != StrictHash::STRICT_LIB_NAME,
93 "do not use commit_to_serialized for StrictHash types, use commit_to_hash instead"
94 );
95 self.layout
96 .push(CommitStep::Serialized(fqn))
97 .expect("too many fields for commitment");
98
99 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&value);
100 }
101
102 pub fn commit_to_option<T: StrictEncode + StrictDumb>(&mut self, value: &Option<T>) {
103 let fqn = commitment_fqn::<T>();
104 self.layout
105 .push(CommitStep::Serialized(fqn))
106 .expect("too many fields for commitment");
107
108 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&value);
109 }
110
111 pub fn commit_to_hash<T: CommitEncode<CommitmentId = StrictHash> + StrictType>(
112 &mut self,
113 value: T,
114 ) {
115 let fqn = commitment_fqn::<T>();
116 self.layout
117 .push(CommitStep::Hashed(fqn))
118 .expect("too many fields for commitment");
119
120 self.inner_commit_to::<_, 32>(&value.commit_id());
121 }
122
123 pub fn commit_to_merkle<T: MerkleLeaves>(&mut self, value: &T)
124 where T::Leaf: StrictType {
125 let fqn = commitment_fqn::<T::Leaf>();
126 self.layout
127 .push(CommitStep::Merklized(fqn))
128 .expect("too many fields for commitment");
129
130 let root = MerkleHash::merklize(value);
131 self.inner_commit_to::<_, 32>(&root);
132 }
133
134 pub fn commit_to_concealed<T>(&mut self, value: &T)
135 where
136 T: Conceal + StrictType,
137 T::Concealed: StrictEncode,
138 {
139 let fqn = commitment_fqn::<T>();
140 self.layout
141 .push(CommitStep::Concealed(fqn))
142 .expect("too many fields for commitment");
143
144 let concealed = value.conceal();
145 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&concealed);
146 }
147
148 pub fn commit_to_list<T, const MIN: usize, const MAX: usize>(
149 &mut self,
150 collection: &Confined<Vec<T>, MIN, MAX>,
151 ) where
152 T: StrictEncode + StrictDumb,
153 {
154 let fqn = commitment_fqn::<T>();
155 let step =
156 CommitStep::Collection(CommitColType::List, Sizing::new(MIN as u64, MAX as u64), fqn);
157 self.layout
158 .push(step)
159 .expect("too many fields for commitment");
160 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection);
161 }
162
163 pub fn commit_to_set<T, const MIN: usize, const MAX: usize>(
164 &mut self,
165 collection: &Confined<BTreeSet<T>, MIN, MAX>,
166 ) where
167 T: Ord + StrictEncode + StrictDumb,
168 {
169 let fqn = commitment_fqn::<T>();
170 let step =
171 CommitStep::Collection(CommitColType::Set, Sizing::new(MIN as u64, MAX as u64), fqn);
172 self.layout
173 .push(step)
174 .expect("too many fields for commitment");
175 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection);
176 }
177
178 pub fn commit_to_map<K, V, const MIN: usize, const MAX: usize>(
179 &mut self,
180 collection: &Confined<BTreeMap<K, V>, MIN, MAX>,
181 ) where
182 K: Ord + Hash + StrictEncode + StrictDumb,
183 V: StrictEncode + StrictDumb,
184 {
185 let key_fqn = commitment_fqn::<K>();
186 let val_fqn = commitment_fqn::<V>();
187 let step = CommitStep::Collection(
188 CommitColType::Map { key: key_fqn },
189 Sizing::new(MIN as u64, MAX as u64),
190 val_fqn,
191 );
192 self.layout
193 .push(step)
194 .expect("too many fields for commitment");
195 self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection);
196 }
197
198 pub fn as_layout(&mut self) -> &[CommitStep] {
199 self.finished = true;
200 self.layout.as_ref()
201 }
202
203 pub fn into_layout(self) -> TinyVec<CommitStep> { self.layout }
204
205 pub fn set_finished(&mut self) { self.finished = true; }
206
207 pub fn finish(self) -> Sha256 { self.hasher }
208
209 pub fn finish_layout(self) -> (Sha256, TinyVec<CommitStep>) { (self.hasher, self.layout) }
210}
211
212pub trait CommitEncode {
216 type CommitmentId: CommitmentId;
218
219 fn commit_encode(&self, e: &mut CommitEngine);
222}
223
224#[derive(Getters, Clone, Eq, PartialEq, Hash, Debug)]
225pub struct CommitLayout {
226 idty: TypeFqn,
227 #[getter(as_copy)]
228 tag: &'static str,
229 fields: TinyVec<CommitStep>,
230}
231
232impl Display for CommitLayout {
233 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
234 Display::fmt(&self.to_vesper().display(), f)
235 }
236}
237
238pub trait CommitmentId: Copy + Ord + From<Sha256> + StrictType {
239 const TAG: &'static str;
240}
241
242pub trait CommitmentLayout: CommitEncode {
243 fn commitment_layout() -> CommitLayout;
244}
245
246impl<T> CommitmentLayout for T
247where T: CommitEncode + StrictDumb
248{
249 fn commitment_layout() -> CommitLayout {
250 let dumb = Self::strict_dumb();
251 let fields = dumb.commit().into_layout();
252 CommitLayout {
253 idty: TypeFqn::with(
254 libname!(Self::CommitmentId::STRICT_LIB_NAME),
255 Self::CommitmentId::strict_name()
256 .expect("commitment types must have explicit type name"),
257 ),
258 tag: T::CommitmentId::TAG,
259 fields,
260 }
261 }
262}
263
264pub trait CommitId: CommitEncode {
274 #[doc(hidden)]
275 fn commit(&self) -> CommitEngine;
276
277 fn commit_id(&self) -> Self::CommitmentId;
279}
280
281impl<T: CommitEncode> CommitId for T {
282 fn commit(&self) -> CommitEngine {
283 let mut engine = CommitEngine::new(T::CommitmentId::TAG);
284 self.commit_encode(&mut engine);
285 engine.set_finished();
286 engine
287 }
288
289 fn commit_id(&self) -> Self::CommitmentId { self.commit().finish().into() }
290}
291
292#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
293#[wrapper(Deref, BorrowSlice, Display, FromStr, Hex, Index, RangeOps)]
294#[derive(StrictDumb, strict_encoding::StrictType, StrictEncode, StrictDecode)]
295#[strict_type(lib = LIB_NAME_COMMIT_VERIFY)]
296#[cfg_attr(
297 feature = "serde",
298 derive(Serialize, Deserialize),
299 serde(crate = "serde_crate", transparent)
300)]
301pub struct StrictHash(
302 #[from]
303 #[from([u8; 32])]
304 Bytes32,
305);
306
307impl CommitmentId for StrictHash {
308 const TAG: &'static str = "urn:ubideco:strict-types:value-hash#2024-02-10";
309}
310
311impl From<Sha256> for StrictHash {
312 fn from(hash: Sha256) -> Self { hash.finish().into() }
313}