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, ¤t, result);
287 for assertion in assertions {
288 assertion.reveal_sets(target, ¤t, result);
289 }
290 }
291 EnvelopeCase::Wrapped { envelope, .. } => {
292 envelope.reveal_sets(target, ¤t, result);
293 }
294 EnvelopeCase::Assertion(assertion) => {
295 assertion.predicate().reveal_sets(target, ¤t, result);
296 assertion.object().reveal_sets(target, ¤t, 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}