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, ¤t, result);
352 for assertion in assertions {
353 assertion.reveal_sets(target, ¤t, result);
354 }
355 }
356 EnvelopeCase::Wrapped { envelope, .. } => {
357 envelope.reveal_sets(target, ¤t, result);
358 }
359 EnvelopeCase::Assertion(assertion) => {
360 assertion.predicate().reveal_sets(target, ¤t, result);
361 assertion.object().reveal_sets(target, ¤t, 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}