bc_envelope/extension/
proof.rs

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