bc_envelope/extension/
proof.rs

1use std::{
2    collections::{HashSet, hash_map::RandomState},
3    iter,
4};
5
6use bc_components::{Digest, DigestProvider};
7
8use crate::{Envelope, base::envelope::EnvelopeCase};
9
10/// # Inclusion Proofs
11///
12/// Inclusion proofs allow a holder of an envelope to prove that specific
13/// elements exist within the envelope without revealing the entire contents.
14/// This is particularly useful for selective disclosure of information in
15/// privacy-preserving scenarios.
16///
17/// The inclusion proof mechanism leverages the Merkle-like digest tree
18/// structure of envelopes:
19/// - The holder creates a minimal structure containing only the digests
20///   necessary to validate the proof
21/// - A verifier with a trusted root digest can confirm that the specific
22///   elements exist in the original envelope
23/// - All other content can remain elided, preserving privacy
24///
25/// For enhanced privacy, elements can be salted to prevent correlation attacks.
26///
27/// ## Examples
28///
29/// ### Basic Inclusion Proof
30///
31/// ```
32/// use std::collections::HashSet;
33///
34/// use bc_envelope::prelude::*;
35///
36/// // Create an envelope with multiple assertions
37/// let alice_friends = Envelope::new("Alice")
38///     .add_assertion("knows", "Bob")
39///     .add_assertion("knows", "Carol")
40///     .add_assertion("knows", "Dan");
41///
42/// // Create a representation of just the root digest
43/// let alice_friends_root = alice_friends.elide_revealing_set(&HashSet::new());
44///
45/// // Create the target we want to prove exists
46/// let knows_bob_assertion = Envelope::new_assertion("knows", "Bob");
47///
48/// // Generate a proof that Alice knows Bob
49/// let alice_knows_bob_proof = alice_friends
50///     .proof_contains_target(&knows_bob_assertion)
51///     .unwrap();
52///
53/// // A third party can verify the proof against the trusted root
54/// assert!(
55///     alice_friends_root.confirm_contains_target(
56///         &knows_bob_assertion,
57///         &alice_knows_bob_proof
58///     )
59/// );
60/// ```
61///
62/// ### Enhanced Privacy with Salting
63///
64/// ```
65/// use std::collections::HashSet;
66///
67/// use bc_envelope::prelude::*;
68///
69/// // Create an envelope with salted assertions for enhanced privacy
70/// let alice_friends = Envelope::new("Alice")
71///     .add_assertion_salted("knows", "Bob", true)
72///     .add_assertion_salted("knows", "Carol", true)
73///     .add_assertion_salted("knows", "Dan", true);
74///
75/// // Create a representation of just the root digest
76/// let alice_friends_root = alice_friends.elide_revealing_set(&HashSet::new());
77///
78/// // Create the target we want to prove exists
79/// let knows_bob_assertion = Envelope::new_assertion("knows", "Bob");
80///
81/// // Generate a proof that Alice knows Bob
82/// let alice_knows_bob_proof = alice_friends
83///     .proof_contains_target(&knows_bob_assertion)
84///     .unwrap();
85///
86/// // A third party can verify the proof against the trusted root
87/// // Note: The salting prevents the third party from guessing other friends
88/// // by simple correlation attacks
89/// assert!(
90///     alice_friends_root.confirm_contains_target(
91///         &knows_bob_assertion,
92///         &alice_knows_bob_proof
93///     )
94/// );
95/// ```
96impl Envelope {
97    /// Creates a proof that this envelope includes every element in the target
98    /// set.
99    ///
100    /// An inclusion proof is a specially constructed envelope that:
101    /// - Has the same digest as the original envelope (or an elided version of
102    ///   it)
103    /// - Contains the minimal structure needed to prove the existence of target
104    ///   elements
105    /// - Keeps all other content elided to preserve privacy
106    ///
107    /// # Parameters
108    /// - `target`: The set of digests representing elements that the proof must
109    ///   include.
110    ///
111    /// # Returns
112    /// - `Some(Envelope)`: A proof envelope if all targets can be proven to
113    ///   exist
114    /// - `None`: If it cannot be proven that the envelope contains every
115    ///   element in the target set
116    ///
117    /// # Example
118    ///
119    /// ```
120    /// use std::collections::HashSet;
121    ///
122    /// use bc_envelope::prelude::*;
123    ///
124    /// // Create a document with multiple assertions
125    /// let document = Envelope::new("Document")
126    ///     .add_assertion("title", "Important Report")
127    ///     .add_assertion("author", "Alice")
128    ///     .add_assertion("confidential", true);
129    ///
130    /// // Create a set of elements we want to prove exist
131    /// let title_assertion = Envelope::new_assertion("title", "Important Report");
132    /// let author_assertion = Envelope::new_assertion("author", "Alice");
133    ///
134    /// let mut target_set = HashSet::new();
135    /// target_set.insert(title_assertion.digest());
136    /// target_set.insert(author_assertion.digest());
137    ///
138    /// // Generate a proof for multiple elements
139    /// let proof = document.proof_contains_set(&target_set).unwrap();
140    ///
141    /// // The proof can be verified against the document's root digest
142    /// let document_root = document.elide_revealing_set(&HashSet::new());
143    /// assert!(document_root.confirm_contains_set(&target_set, &proof));
144    /// ```
145    pub fn proof_contains_set(
146        &self,
147        target: &HashSet<Digest, RandomState>,
148    ) -> Option<Envelope> {
149        let reveal_set = self.reveal_set_of_set(target);
150        if !target.is_subset(&reveal_set) {
151            return None;
152        }
153        Some(
154            self.elide_revealing_set(&reveal_set)
155                .elide_removing_set(target),
156        )
157    }
158
159    /// Creates a proof that this envelope includes the single target element.
160    ///
161    /// This is a convenience method that wraps `proof_contains_set()` for the
162    /// common case of proving the existence of just one element.
163    ///
164    /// # Parameters
165    /// - `target`: The element that the proof must demonstrate exists in this
166    ///   envelope.
167    ///
168    /// # Returns
169    /// - `Some(Envelope)`: A proof envelope if the target can be proven to
170    ///   exist
171    /// - `None`: If it cannot be proven that the envelope contains the target
172    ///   element
173    ///
174    /// # Example
175    ///
176    /// ```
177    /// use std::collections::HashSet;
178    ///
179    /// use bc_envelope::prelude::*;
180    ///
181    /// // Create a credential with various attributes
182    /// let credential = Envelope::new("Credential")
183    ///     .add_assertion("firstName", "John")
184    ///     .add_assertion("lastName", "Smith")
185    ///     .add_assertion("birthDate", "1990-01-01")
186    ///     .add_assertion("address", "123 Main St")
187    ///     .add_assertion("documentNumber", "ABC123456");
188    ///
189    /// // Create a trusted root digest
190    /// let credential_root = credential.elide_revealing_set(&HashSet::new());
191    ///
192    /// // The holder wants to prove just their name without revealing other details
193    /// let first_name = Envelope::new_assertion("firstName", "John");
194    ///
195    /// // Generate a proof for the first name
196    /// let proof = credential.proof_contains_target(&first_name).unwrap();
197    ///
198    /// // A verifier can confirm the proof is valid
199    /// assert!(credential_root.confirm_contains_target(&first_name, &proof));
200    /// ```
201    pub fn proof_contains_target(
202        &self,
203        target: &dyn DigestProvider,
204    ) -> Option<Envelope> {
205        let set = HashSet::from_iter(iter::once(target.digest()));
206        self.proof_contains_set(&set)
207    }
208
209    /// Verifies whether this envelope contains all elements in the target set
210    /// using the given inclusion proof.
211    ///
212    /// This method is used by a verifier to check if a proof demonstrates the
213    /// existence of all target elements within this envelope. The
214    /// verification succeeds only if:
215    /// 1. The proof's digest matches this envelope's digest
216    /// 2. The proof contains all the target elements
217    ///
218    /// # Parameters
219    /// - `target`: The set of digests representing elements that need to be
220    ///   proven to exist.
221    /// - `proof`: The inclusion proof envelope to verify.
222    ///
223    /// # Returns
224    /// - `true`: If all target elements are proven to exist in this envelope by
225    ///   the proof
226    /// - `false`: Otherwise
227    ///
228    /// # Example
229    ///
230    /// ```
231    /// use std::collections::HashSet;
232    ///
233    /// use bc_envelope::prelude::*;
234    ///
235    /// // A verifier has a trusted root digest of a document
236    /// let document = Envelope::new("Document")
237    ///     .add_assertion("title", "Report")
238    ///     .add_assertion("author", "Alice");
239    /// let document_root = document.elide_revealing_set(&HashSet::new());
240    ///
241    /// // Create a set of elements we want to verify
242    /// let author_assertion = Envelope::new_assertion("author", "Alice");
243    /// let mut target_set = HashSet::new();
244    /// target_set.insert(author_assertion.digest());
245    ///
246    /// // The holder provides a proof
247    /// let proof = document.proof_contains_set(&target_set).unwrap();
248    ///
249    /// // The verifier confirms the proof is valid
250    /// assert!(document_root.confirm_contains_set(&target_set, &proof));
251    /// ```
252    pub fn confirm_contains_set(
253        &self,
254        target: &HashSet<Digest, RandomState>,
255        proof: &Envelope,
256    ) -> bool {
257        self.digest() == proof.digest() && proof.contains_all(target)
258    }
259
260    /// Verifies whether this envelope contains the single target element using
261    /// the given inclusion proof.
262    ///
263    /// This is a convenience method that wraps `confirm_contains_set()` for the
264    /// common case of verifying just one element.
265    ///
266    /// # Parameters
267    /// - `target`: The element that needs to be proven to exist in this
268    ///   envelope.
269    /// - `proof`: The inclusion proof envelope to verify.
270    ///
271    /// # Returns
272    /// - `true`: If the target element is proven to exist in this envelope by
273    ///   the proof
274    /// - `false`: Otherwise
275    ///
276    /// # Example
277    ///
278    /// ```
279    /// use std::collections::HashSet;
280    ///
281    /// use bc_envelope::prelude::*;
282    ///
283    /// // A verifier has a trusted root digest of a credential
284    /// let credential = Envelope::new("Credential")
285    ///     .add_assertion("isOver21", true)
286    ///     .add_assertion("licenseNumber", "DL12345678");
287    /// let credential_root = credential.elide_revealing_set(&HashSet::new());
288    ///
289    /// // The element to be verified
290    /// let is_over_21 = Envelope::new_assertion("isOver21", true);
291    ///
292    /// // The holder provides a proof
293    /// let proof = credential.proof_contains_target(&is_over_21).unwrap();
294    ///
295    /// // The verifier confirms the proof shows the person is over 21
296    /// // without revealing their license number
297    /// assert!(credential_root.confirm_contains_target(&is_over_21, &proof));
298    /// ```
299    pub fn confirm_contains_target(
300        &self,
301        target: &dyn DigestProvider,
302        proof: &Envelope,
303    ) -> bool {
304        let set = HashSet::from_iter(iter::once(target.digest()));
305        self.confirm_contains_set(&set, proof)
306    }
307}
308
309/// Internal implementation methods for inclusion proofs
310impl Envelope {
311    /// Builds a set of all digests needed to reveal the target set.
312    ///
313    /// This collects all digests in the path from the envelope's root to each
314    /// target element.
315    fn reveal_set_of_set(&self, target: &HashSet<Digest>) -> HashSet<Digest> {
316        let mut result = HashSet::new();
317        self.reveal_sets(target, &HashSet::new(), &mut result);
318        result
319    }
320
321    /// Checks if this envelope contains all elements in the target set.
322    ///
323    /// Used during proof verification to confirm all target elements exist in
324    /// the proof.
325    fn contains_all(&self, target: &HashSet<Digest>) -> bool {
326        let mut target = target.clone();
327        self.remove_all_found(&mut target);
328        target.is_empty()
329    }
330
331    /// Recursively traverses the envelope to collect all digests needed to
332    /// reveal the target set.
333    ///
334    /// Builds the set of digests forming the path from the root to each target
335    /// element.
336    fn reveal_sets(
337        &self,
338        target: &HashSet<Digest>,
339        current: &HashSet<Digest>,
340        result: &mut HashSet<Digest>,
341    ) {
342        let mut current = current.clone();
343        current.insert(self.digest());
344
345        if target.contains(&self.digest()) {
346            result.extend(current.iter().cloned());
347        }
348
349        match self.case() {
350            EnvelopeCase::Node { subject, assertions, .. } => {
351                subject.reveal_sets(target, &current, result);
352                for assertion in assertions {
353                    assertion.reveal_sets(target, &current, result);
354                }
355            }
356            EnvelopeCase::Wrapped { envelope, .. } => {
357                envelope.reveal_sets(target, &current, result);
358            }
359            EnvelopeCase::Assertion(assertion) => {
360                assertion.predicate().reveal_sets(target, &current, result);
361                assertion.object().reveal_sets(target, &current, result);
362            }
363            _ => {}
364        }
365    }
366
367    /// Recursively traverses the envelope and removes found target elements
368    /// from the set.
369    ///
370    /// Used during proof verification to confirm all target elements are
371    /// present.
372    fn remove_all_found(&self, target: &mut HashSet<Digest>) {
373        if target.contains(&self.digest()) {
374            target.remove(&self.digest());
375        }
376        if target.is_empty() {
377            return;
378        }
379
380        match self.case() {
381            EnvelopeCase::Node { subject, assertions, .. } => {
382                subject.remove_all_found(target);
383                for assertion in assertions {
384                    assertion.remove_all_found(target);
385                }
386            }
387            EnvelopeCase::Wrapped { envelope, .. } => {
388                envelope.remove_all_found(target);
389            }
390            EnvelopeCase::Assertion(assertion) => {
391                assertion.predicate().remove_all_found(target);
392                assertion.object().remove_all_found(target);
393            }
394            _ => {}
395        }
396    }
397}