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}