commit_verify/
embed.rs

1// Client-side-validation foundation libraries.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10//                         Institute for Distributed and Cognitive Systems
11// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not
15// use this file except in compliance with the License. You may obtain a copy of
16// the License at
17//
18//        http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software
21// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
22// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
23// License for the specific language governing permissions and limitations under
24// the License.
25
26//! Embedded commitments (commit-embed-verify scheme).
27
28use crate::CommitmentProtocol;
29
30/// Error during commitment verification
31#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
32#[display(doc_comments)]
33pub enum EmbedVerifyError<E: std::error::Error> {
34    /// commitment doesn't match the message.
35    CommitmentMismatch,
36
37    /// the message is invalid since a valid commitment to it can't be created.
38    ///
39    /// Details: {0}
40    #[from]
41    InvalidMessage(E),
42
43    /// the proof is invalid and the commitment can't be verified since the
44    /// original container can't be restored from it.
45    InvalidProof,
46
47    /// the proof does not match to the proof generated for the same message
48    /// during the verification.
49    ProofMismatch,
50}
51
52/// Trait for equivalence verification. Implemented for all types implementing
53/// `Eq`. For non-`Eq` types this trait provides a way to implement custom
54/// equivalence verification used during the commitment verification procedure.
55pub trait VerifyEq {
56    /// Verifies commit-equivalence of two instances of the same type.
57    fn verify_eq(&self, other: &Self) -> bool;
58}
59
60impl<T> VerifyEq for T
61where T: Eq
62{
63    fn verify_eq(&self, other: &Self) -> bool { self == other }
64}
65
66/// Proofs produced by [`EmbedCommitVerify::embed_commit`] procedure.
67pub trait EmbedCommitProof<Msg, Container, Protocol>
68where
69    Self: Sized + VerifyEq,
70    Container: EmbedCommitVerify<Msg, Protocol>,
71    Protocol: CommitmentProtocol,
72{
73    /// Restores original container before the commitment from the proof data
74    /// and a container containing embedded commitment.
75    ///
76    /// # Error
77    ///
78    /// If the container can't be restored from the proof returns
79    /// [`EmbedVerifyError::InvalidProof`].
80    fn restore_original_container(
81        &self,
82        commit_container: &Container,
83    ) -> Result<Container, EmbedVerifyError<Container::CommitError>>;
84}
85
86/// Trait for *embed-commit-verify scheme*, where some data structure (named
87/// *container*) may commit to an existing *message* (producing *commitment*
88/// data structure and a *proof*) in such a way that the original message can't
89/// be restored from the commitment, however, the fact of the commitment may be
90/// deterministically *verified* when the message and the proof are *revealed*.
91///
92/// To use the *embed-commit-verify scheme*, one needs to implement this trait
93/// for a data structure acting as a container for a specific commitment under
94/// a certain protocol, specified as a generic parameter. The container type
95/// must specify as associated types proof and commitment types.
96///
97/// Operations with *embed-commit-verify scheme* may be represented in the form
98/// of `EmbedCommit: (Container, Message) -> (Container', Proof)` (see
99/// [`Self::embed_commit`]) and `Verify: (Container', Message, Proof) -> bool`
100/// (see [`Self::verify`]).
101///
102/// This trait is heavily used in **deterministic bitcoin commitments**.
103///
104/// # Protocol definition
105///
106/// Generic parameter `Protocol` provides context and configuration for the
107/// commitment scheme protocol used for this container type.
108///
109/// Introduction of this generic allows to:
110/// - implement trait for foreign data types;
111/// - add multiple implementations under different commitment protocols to the
112///   combination of the same message and container type (each of each will have
113///   its own `Proof` type defined as an associated generic).
114///
115/// Usually represents a non-instantiable type, but may be a structure
116/// containing commitment protocol configuration or context objects.
117///
118/// ```
119/// # use commit_verify::CommitmentProtocol;
120///
121/// // Uninstantiable type
122/// pub enum Lnpbp6 {}
123///
124/// impl CommitmentProtocol for Lnpbp6 {}
125///
126/// // Protocol definition
127/// pub enum Lnpbp1 {}
128/// // ...
129/// ```
130pub trait EmbedCommitVerify<Msg, Protocol>
131where
132    Self: Sized,
133    Protocol: CommitmentProtocol,
134{
135    /// The proof of the commitment produced as a result of
136    /// [`Self::embed_commit`] procedure. This proof is later used
137    /// for verification.
138    type Proof: EmbedCommitProof<Msg, Self, Protocol>;
139
140    /// Error type that may be reported during [`Self::embed_commit`] procedure.
141    /// It may also be returned from [`Self::verify`] (wrapped into
142    /// [`EmbedVerifyError`] in case the proof data are invalid and the
143    /// commitment can't be re-created).
144    type CommitError: std::error::Error;
145
146    /// Creates a commitment to a message and embeds it into the provided
147    /// container (`self`) by mutating it and returning commitment proof.
148    ///
149    /// Implementations must error with a dedicated error type enumerating
150    /// commitment procedure mistakes.
151    fn embed_commit(&mut self, msg: &Msg) -> Result<Self::Proof, Self::CommitError>;
152
153    /// Verifies commitment with commitment proof against the message.
154    ///
155    /// Default implementation reconstructs the original container with the
156    /// [`EmbedCommitProof::restore_original_container`] method and repeats
157    /// [`Self::embed_commit`] procedure checking that the resulting proof and
158    /// commitment matches the provided `self` and `proof`.
159    ///
160    /// # Errors
161    ///
162    /// Errors if the commitment doesn't pass the validation (see
163    /// [`EmbedVerifyError`] variants for the cases when this may happen).
164    fn verify(
165        &self,
166        msg: &Msg,
167        proof: &Self::Proof,
168    ) -> Result<(), EmbedVerifyError<Self::CommitError>>
169    where
170        Self: VerifyEq,
171        Self::Proof: VerifyEq,
172    {
173        let mut container_prime = proof.restore_original_container(self)?;
174        let proof_prime = container_prime.embed_commit(msg)?;
175        if !proof_prime.verify_eq(proof) {
176            return Err(EmbedVerifyError::InvalidProof);
177        }
178        if !self.verify_eq(&container_prime) {
179            return Err(EmbedVerifyError::CommitmentMismatch);
180        }
181        Ok(())
182    }
183
184    /// Phantom method used to add a `Protocol` generic parameter to the trait.
185    ///
186    /// # Panics
187    ///
188    /// Always panics when called.
189    #[doc(hidden)]
190    fn _phantom(_: Protocol) {
191        unimplemented!("EmbedCommitVerify::_phantom is a marker method that must not be used")
192    }
193}
194
195/// Helpers for writing test functions working with the embed-commit-verify
196/// scheme.
197#[cfg(test)]
198pub(crate) mod test_helpers {
199    #![allow(missing_docs)]
200    #![cfg_attr(coverage_nightly, coverage(off))]
201
202    use core::fmt::Debug;
203    use core::hash::Hash;
204    use std::collections::HashSet;
205
206    use super::*;
207    use crate::{ConvolveCommit, ConvolveCommitProof};
208
209    pub enum TestProtocol {}
210    impl CommitmentProtocol for TestProtocol {}
211
212    pub const SUPPLEMENT: [u8; 32] = [0xFFu8; 32];
213
214    /// Runs round-trip of commitment-embed-verify for a given set of messages
215    /// and provided container.
216    pub fn embed_commit_verify_suite<Msg, Container>(messages: Vec<Msg>, container: Container)
217    where
218        Msg: AsRef<[u8]> + Eq + Clone,
219        Container: EmbedCommitVerify<Msg, TestProtocol> + Eq + Hash + Debug + Clone,
220        Container::Proof: Clone,
221    {
222        messages.iter().fold(
223            HashSet::<Container>::with_capacity(messages.len()),
224            |mut acc, msg| {
225                let mut commitment = container.clone();
226                let proof = commitment.embed_commit(msg).unwrap();
227
228                // Commitments MUST be deterministic: the same message must
229                // always produce the same commitment
230                (1..10).for_each(|_| {
231                    let mut commitment_prime = container.clone();
232                    commitment_prime.embed_commit(msg).unwrap();
233                    assert_eq!(commitment_prime, commitment);
234                });
235
236                // Testing verification
237                assert!(commitment.clone().verify(msg, &proof).is_ok());
238
239                messages.iter().for_each(|m| {
240                    // Testing that commitment verification succeeds only
241                    // for the original message and fails for the rest
242                    assert_eq!(commitment.clone().verify(m, &proof).is_ok(), m == msg);
243                });
244
245                acc.iter().for_each(|cmt| {
246                    // Testing that verification against other commitments
247                    // returns `false`
248                    assert!(cmt.clone().verify(msg, &proof).is_err());
249                });
250
251                // Detecting collision: each message should produce a unique
252                // commitment even if the same container is used
253                assert!(acc.insert(commitment));
254
255                acc
256            },
257        );
258    }
259
260    /// Runs round-trip of commitment-embed-verify for a given set of messages
261    /// and provided container.
262    pub fn convolve_commit_verify_suite<Msg, Source>(messages: Vec<Msg>, container: Source)
263    where
264        Msg: AsRef<[u8]> + Eq + Clone,
265        Source: ConvolveCommit<Msg, [u8; 32], TestProtocol> + VerifyEq + Eq + Hash + Debug + Clone,
266        Source::Commitment: Clone + Debug + Hash + VerifyEq + Eq,
267        [u8; 32]: ConvolveCommitProof<Msg, Source, TestProtocol, Suppl = [u8; 32]>,
268    {
269        messages.iter().fold(
270            HashSet::<Source::Commitment>::with_capacity(messages.len()),
271            |mut acc, msg| {
272                let (commitment, _) = container.convolve_commit(&SUPPLEMENT, msg).unwrap();
273
274                // Commitments MUST be deterministic: the same message must
275                // always produce the same commitment
276                (1..10).for_each(|_| {
277                    let (commitment_prime, _) =
278                        container.convolve_commit(&SUPPLEMENT, msg).unwrap();
279                    assert_eq!(commitment_prime, commitment);
280                });
281
282                // Testing verification
283                assert!(SUPPLEMENT.verify(msg, &commitment).is_ok());
284
285                messages.iter().for_each(|m| {
286                    // Testing that commitment verification succeeds only
287                    // for the original message and fails for the rest
288                    assert_eq!(SUPPLEMENT.verify(m, &commitment).is_ok(), m == msg);
289                });
290
291                acc.iter().for_each(|commitment| {
292                    // Testing that verification against other commitments
293                    // returns `false`
294                    assert!(SUPPLEMENT.verify(msg, commitment).is_err());
295                });
296
297                // Detecting collision: each message should produce a unique
298                // commitment even if the same container is used
299                assert!(acc.insert(commitment));
300
301                acc
302            },
303        );
304    }
305}
306
307#[cfg(test)]
308mod test {
309    #![cfg_attr(coverage_nightly, coverage(off))]
310
311    use core::fmt::Debug;
312
313    use amplify::confinement::{SmallBlob, SmallVec, U32};
314    use sha2::Sha256;
315
316    use super::test_helpers::*;
317    use super::*;
318    use crate::digest::DigestExt;
319    use crate::test_helpers::gen_messages;
320    use crate::{ConvolveCommit, ConvolveCommitProof};
321
322    #[derive(Clone, PartialEq, Eq, Debug, Hash, Error, Display)]
323    #[display("error")]
324    struct Error;
325
326    #[derive(Clone, PartialEq, Eq, Debug, Hash)]
327    struct DummyVec(SmallBlob);
328
329    #[derive(Clone, PartialEq, Eq, Debug, Hash)]
330    struct DummyProof(SmallBlob);
331
332    impl<T> EmbedCommitProof<T, DummyVec, TestProtocol> for DummyProof
333    where T: AsRef<[u8]> + Clone
334    {
335        fn restore_original_container(
336            &self,
337            _: &DummyVec,
338        ) -> Result<DummyVec, EmbedVerifyError<Error>> {
339            Ok(DummyVec(self.0.clone()))
340        }
341    }
342
343    impl<T> EmbedCommitVerify<T, TestProtocol> for DummyVec
344    where T: AsRef<[u8]> + Clone
345    {
346        type Proof = DummyProof;
347        type CommitError = Error;
348
349        fn embed_commit(&mut self, msg: &T) -> Result<Self::Proof, Self::CommitError> {
350            let proof = self.0.clone();
351            let result = &mut self.0;
352            result.extend(msg.as_ref().iter().copied()).unwrap();
353            Ok(DummyProof(proof))
354        }
355    }
356
357    impl<T> ConvolveCommit<T, [u8; 32], TestProtocol> for DummyVec
358    where T: AsRef<[u8]> + Clone
359    {
360        type Commitment = [u8; 32];
361        type CommitError = Error;
362
363        fn convolve_commit(
364            &self,
365            supplement: &[u8; 32],
366            msg: &T,
367        ) -> Result<(Self::Commitment, [u8; 32]), Self::CommitError> {
368            let mut engine = Sha256::default();
369            engine.input_raw(supplement);
370            engine.input_with_len::<U32>(msg.as_ref());
371            Ok((engine.finish(), *supplement))
372        }
373    }
374
375    impl<T> ConvolveCommitProof<T, DummyVec, TestProtocol> for [u8; 32]
376    where T: AsRef<[u8]> + Clone
377    {
378        type Suppl = [u8; 32];
379
380        fn restore_original(&self, _: &[u8; 32]) -> DummyVec { DummyVec(default!()) }
381
382        fn extract_supplement(&self) -> &Self::Suppl { self }
383    }
384
385    #[test]
386    fn test_embed_commit() {
387        embed_commit_verify_suite::<SmallVec<u8>, DummyVec>(gen_messages(), DummyVec(default!()));
388    }
389
390    #[test]
391    fn test_convolve_commit() {
392        convolve_commit_verify_suite::<SmallVec<u8>, DummyVec>(
393            gen_messages(),
394            DummyVec(small_vec![0xC0; 15]),
395        );
396    }
397}