commit_verify/
id.rs

1// Client-side-validation foundation libraries.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr. Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use 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
212/// Prepares the data to the *consensus commit* procedure by first running
213/// necessary conceal and merklization procedures, and them performing strict
214/// encoding for the resulted data.
215pub trait CommitEncode {
216    /// Type of the resulting commitment.
217    type CommitmentId: CommitmentId;
218
219    /// Encodes the data for the commitment by writing them directly into a
220    /// [`std::io::Write`] writer instance
221    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
264/// High-level API used in client-side validation for producing a single
265/// commitment to the data, which includes running all necessary procedures like
266/// concealment with [`crate::Conceal`], merklization, strict encoding,
267/// wrapped into [`CommitEncode`], followed by the actual commitment to its
268/// output.
269///
270/// The trait is separate from the `CommitEncode` to prevent custom
271/// implementation of its methods, since `CommitId` can't be manually
272/// implemented for any type since it has a generic blanket implementation.
273pub trait CommitId: CommitEncode {
274    #[doc(hidden)]
275    fn commit(&self) -> CommitEngine;
276
277    /// Performs commitment to client-side-validated data
278    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}