bc_envelope/base/digest.rs
1use std::{cell::RefCell, collections::HashSet};
2
3use bc_components::{Digest, DigestProvider};
4
5use super::{envelope::EnvelopeCase, walk::EdgeType};
6use crate::Envelope;
7
8/// Support for calculating the digests associated with `Envelope`.
9///
10/// Envelopes implement the `DigestProvider` trait, which means they can provide
11/// cryptographic digests of their contents. This is a fundamental feature of
12/// Gordian Envelope that enables privacy-enhancing features like selective
13/// disclosure through elision while maintaining verifiable integrity.
14impl DigestProvider for Envelope {
15 /// Returns the envelope's digest.
16 ///
17 /// The digest of an envelope uniquely identifies its semantic content,
18 /// regardless of whether parts of it are elided, encrypted, or
19 /// compressed. This is a key property that enables privacy features
20 /// while maintaining integrity.
21 ///
22 /// Two envelopes with the same digest are considered semantically
23 /// equivalent - they represent the same information, even if parts of
24 /// one envelope are elided or obscured.
25 ///
26 /// # Returns
27 ///
28 /// A borrowed reference to the digest if it's already computed and stored
29 /// in the envelope, or a newly computed digest if needed.
30 ///
31 /// # Examples
32 ///
33 /// ```
34 /// # use bc_envelope::prelude::*;
35 /// # use bc_components::DigestProvider;
36 /// let envelope = Envelope::new("Hello.");
37 /// let elided = envelope.elide();
38 ///
39 /// // Even though the content is elided, the digests match
40 /// assert_eq!(envelope.digest(), elided.digest());
41 /// ```
42 fn digest(&self) -> Digest {
43 match self.case() {
44 EnvelopeCase::Node { digest, .. } => *digest,
45 EnvelopeCase::Leaf { digest, .. } => *digest,
46 EnvelopeCase::Wrapped { digest, .. } => *digest,
47 EnvelopeCase::Assertion(assertion) => assertion.digest(),
48 EnvelopeCase::Elided(digest) => *digest,
49 #[cfg(feature = "known_value")]
50 EnvelopeCase::KnownValue { digest, .. } => *digest,
51 #[cfg(feature = "encrypt")]
52 EnvelopeCase::Encrypted(encrypted_message) => {
53 encrypted_message.digest()
54 }
55 #[cfg(feature = "compress")]
56 EnvelopeCase::Compressed(compressed) => compressed.digest(),
57 }
58 }
59}
60
61/// Support for working with the digest tree of an `Envelope`.
62///
63/// Gordian Envelope's structure includes a Merkle-like digest tree where each
64/// node has a cryptographic digest representing its content. These methods help
65/// navigate and utilize this digest tree.
66impl Envelope {
67 /// Returns the set of digests contained in the envelope's elements, down to
68 /// the specified level.
69 ///
70 /// This method collects all digests from the envelope's structure up to a
71 /// certain depth. It's extremely useful for selective elision or
72 /// revelation, allowing you to gather digests of specific parts of the
73 /// structure to target them for operations.
74 ///
75 /// # Parameters
76 ///
77 /// * `level_limit` - Maximum level depth to include (0 means no digests,
78 /// `usize::MAX` means all digests)
79 ///
80 /// # Returns
81 ///
82 /// A `HashSet` containing all the digests in the envelope structure up to
83 /// the specified level. The digest of the envelope itself and its
84 /// subject are both included (if they differ).
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// # use bc_envelope::prelude::*;
90 /// let envelope = Envelope::new("Alice")
91 /// .add_assertion("name", "Alice Smith")
92 /// .add_assertion("age", 30);
93 ///
94 /// // Level 0 returns an empty set
95 /// assert_eq!(envelope.digests(0).len(), 0);
96 ///
97 /// // Level 1 returns just the envelope's digest
98 /// assert_eq!(envelope.digests(1).len(), 2); // Envelope and subject digests
99 ///
100 /// // A deeper level includes assertion digests
101 /// assert!(envelope.digests(3).len() > 4); // Includes assertions plus their predicates and objects
102 /// ```
103 pub fn digests(&self, level_limit: usize) -> HashSet<Digest> {
104 let result = RefCell::new(HashSet::new());
105 let visitor = |envelope: &Envelope,
106 level: usize,
107 _: EdgeType,
108 _: ()|
109 -> (_, bool) {
110 if level < level_limit {
111 let mut result = result.borrow_mut();
112 result.insert(envelope.digest());
113 result.insert(envelope.subject().digest());
114 }
115 ((), false) // Continue walking
116 };
117 self.walk(false, (), &visitor);
118 result.into_inner()
119 }
120
121 /// Returns the set of all digests in the envelope, at all levels.
122 ///
123 /// This is a convenience method that retrieves every digest in the envelope
124 /// structure, no matter how deeply nested. It's useful when you need to
125 /// work with the complete digest tree, such as when verifying integrity
126 /// or preparing for comprehensive elision.
127 ///
128 /// # Returns
129 ///
130 /// A `HashSet` containing all digests in the envelope structure.
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// # use bc_envelope::prelude::*;
136 /// let envelope = Envelope::new("Alice")
137 /// .add_assertion("name", "Alice Smith")
138 /// .add_assertion("age", 30)
139 /// .add_assertion(
140 /// "address",
141 /// Envelope::new("Address")
142 /// .add_assertion("city", "Boston")
143 /// .add_assertion("zip", "02108"),
144 /// );
145 ///
146 /// // Gets all digests from the entire structure
147 /// let digests = envelope.deep_digests();
148 ///
149 /// // This includes nested assertions digests
150 /// assert!(digests.len() > 10);
151 /// ```
152 pub fn deep_digests(&self) -> HashSet<Digest> { self.digests(usize::MAX) }
153
154 /// Returns the set of digests in the envelope, down to its second level
155 /// only.
156 ///
157 /// This is a convenience method that retrieves just the top-level digests
158 /// in the envelope, including the envelope itself, its subject, and its
159 /// immediate assertions, but not deeper nested content. It's useful
160 /// when you want to work with just the top-level structure without
161 /// getting into nested details.
162 ///
163 /// # Returns
164 ///
165 /// A `HashSet` containing digests from the envelope's first two levels.
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// # use bc_envelope::prelude::*;
171 /// let envelope = Envelope::new("Alice")
172 /// .add_assertion("name", "Alice Smith")
173 /// .add_assertion(
174 /// "address",
175 /// Envelope::new("Address")
176 /// .add_assertion("city", "Boston")
177 /// .add_assertion("zip", "02108"),
178 /// );
179 ///
180 /// // Gets just the shallow digests
181 /// let shallow = envelope.shallow_digests();
182 ///
183 /// // Gets all digests
184 /// let deep = envelope.deep_digests();
185 ///
186 /// // The shallow set is smaller than the deep set
187 /// assert!(shallow.len() < deep.len());
188 /// ```
189 pub fn shallow_digests(&self) -> HashSet<Digest> { self.digests(2) }
190
191 /// Returns a digest that captures the structural form of an envelope, not
192 /// just its semantic content.
193 ///
194 /// While the regular `digest()` method provides a value for comparing
195 /// semantic equivalence (whether two envelopes contain the same
196 /// information), this method produces a digest that additionally
197 /// captures the structural form of the envelope, including its
198 /// elision patterns, encryption, and compression.
199 ///
200 /// This allows distinguishing between envelopes that contain the same
201 /// information but differ in how that information is structured or
202 /// obscured.
203 ///
204 /// # Semantic vs. Structural Comparison
205 ///
206 /// - **Semantic equivalence** (using `digest()` or `is_equivalent_to()`) -
207 /// Two envelopes contain the same information in their unobscured form,
208 /// complexity O(1)
209 ///
210 /// - **Structural identity** (using `structural_digest()` or
211 /// `is_identical_to()`) - Two envelopes not only contain the same
212 /// information but also have the same structure, including elision
213 /// patterns, encryption, compression, etc., complexity O(m + n) where m
214 /// and n are the number of elements in each envelope
215 ///
216 /// # Returns
217 ///
218 /// A `Digest` that uniquely identifies the envelope's structure as well as
219 /// its content.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// # use bc_envelope::prelude::*;
225 /// # use bc_components::DigestProvider;
226 /// // Two envelopes with the same content but different structure
227 /// let envelope1 = Envelope::new("Alice").add_assertion("name", "Alice Smith");
228 ///
229 /// let envelope2 = Envelope::new("Alice")
230 /// .add_assertion("name", "Alice Smith")
231 /// .elide_removing_target(&Envelope::new("Alice"));
232 ///
233 /// // They are semantically equivalent
234 /// assert!(envelope1.is_equivalent_to(&envelope2));
235 /// assert_eq!(envelope1.digest(), envelope2.digest());
236 ///
237 /// // But they are structurally different
238 /// assert!(!envelope1.is_identical_to(&envelope2));
239 /// assert_ne!(envelope1.structural_digest(), envelope2.structural_digest());
240 /// ```
241 pub fn structural_digest(&self) -> Digest {
242 let image = RefCell::new(Vec::new());
243 let visitor =
244 |envelope: &Envelope, _: usize, _: EdgeType, _: ()| -> (_, bool) {
245 // Add a discriminator to the image for the obscured cases.
246 match envelope.case() {
247 EnvelopeCase::Elided(_) => image.borrow_mut().push(1),
248 #[cfg(feature = "encrypt")]
249 EnvelopeCase::Encrypted(_) => image.borrow_mut().push(0),
250 #[cfg(feature = "compress")]
251 EnvelopeCase::Compressed(_) => image.borrow_mut().push(2),
252 _ => {}
253 }
254 image
255 .borrow_mut()
256 .extend_from_slice(envelope.digest().data());
257 ((), false) // Continue walking
258 };
259 self.walk(false, (), &visitor);
260 Digest::from_image(image.into_inner())
261 }
262
263 /// Tests if this envelope is semantically equivalent to another envelope.
264 ///
265 /// Two envelopes are semantically equivalent if they contain the same
266 /// information in their unobscured form, even if they have different
267 /// structures (e.g., one is partially elided and the other is not).
268 ///
269 /// This comparison has a complexity of O(1) as it simply compares the
270 /// top-level digests.
271 ///
272 /// # Parameters
273 ///
274 /// * `other` - The other envelope to compare with
275 ///
276 /// # Returns
277 ///
278 /// `true` if the envelopes are semantically equivalent, `false` otherwise.
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// # use bc_envelope::prelude::*;
284 /// // Create two envelopes with the same semantic content but different structure
285 /// let original = Envelope::new("Hello world");
286 /// let elided = original.elide();
287 ///
288 /// // They are semantically equivalent (contain the same information)
289 /// assert!(original.is_equivalent_to(&elided));
290 ///
291 /// // But they have different structures
292 /// assert!(!original.is_identical_to(&elided));
293 /// ```
294 pub fn is_equivalent_to(&self, other: &Self) -> bool {
295 self.digest() == other.digest()
296 }
297
298 /// Tests if two envelopes are structurally identical.
299 ///
300 /// This function determines whether two envelopes are structurally
301 /// identical. The comparison follows these rules:
302 ///
303 /// 1. If the envelopes are *not* semantically equivalent (different
304 /// digests), returns `false` - different content means they cannot be
305 /// identical
306 ///
307 /// 2. If the envelopes are semantically equivalent (same digests), then it
308 /// compares their structural digests to check if they have the same
309 /// structure (elision patterns, encryption, etc.)
310 ///
311 /// The comparison has a complexity of O(1) if the envelopes are not
312 /// semantically equivalent, and O(m + n) if they are, where m and n are
313 /// the number of elements in each envelope.
314 ///
315 /// # Parameters
316 ///
317 /// * `other` - The other envelope to compare with
318 ///
319 /// # Returns
320 ///
321 /// `true` if and only if the envelopes have both the same content
322 /// (semantically equivalent) AND the same structure, `false` otherwise.
323 ///
324 /// # Examples
325 ///
326 /// ```
327 /// # use bc_envelope::prelude::*;
328 /// // Two envelopes with identical structure
329 /// let env1 = Envelope::new("Alice");
330 /// let env2 = Envelope::new("Alice");
331 /// let env3 = Envelope::new("Bob");
332 ///
333 /// // Semantically different envelopes are not identical
334 /// assert!(!env1.is_identical_to(&env3));
335 ///
336 /// // Two envelopes with same content and structure are identical
337 /// assert!(env1.is_identical_to(&env2));
338 ///
339 /// // Envelopes with same content but different structure (one elided) are not identical
340 /// let elided = env1.elide();
341 /// assert!(env1.is_equivalent_to(&elided)); // semantically the same
342 /// assert!(!env1.is_identical_to(&elided)); // but structurally different
343 /// ```
344 pub fn is_identical_to(&self, other: &Self) -> bool {
345 if !self.is_equivalent_to(other) {
346 return false; // Different content means not identical
347 }
348 self.structural_digest() == other.structural_digest()
349 }
350}
351
352/// Implementation of `PartialEq` for `Envelope` to allow for structural
353/// comparison.
354///
355/// This implements the `==` operator for envelopes using the
356/// `is_identical_to()` method, which checks for both semantic equivalence and
357/// structural identity. Following the corrected behavior of that method:
358///
359/// 1. Envelopes with different content (not semantically equivalent) are not
360/// equal (!=)
361/// 2. Envelopes with the same content but different structure are not equal
362/// (!=)
363/// 3. Only envelopes with both the same content and structure are equal (==)
364///
365/// Note that we deliberately do *not* also implement `Eq` as this comparison
366/// for structural identity is potentially expensive (O(m + n) in the worst
367/// case), and data structures like `HashMap` expect `Eq` to be a fast
368/// operation.
369///
370/// # Usage with Hash Maps and Sets
371///
372/// If you want to use envelopes as keys in hash-based collections like
373/// `HashMap` or `HashSet`, you should pre-compute the envelope's
374/// `structural_digest()` and use that as the key instead, as this will be more
375/// efficient.
376///
377/// # Examples
378///
379/// ```
380/// # use bc_envelope::prelude::*;
381/// let env1 = Envelope::new("Alice");
382/// let env2 = Envelope::new("Alice");
383/// let env3 = Envelope::new("Bob");
384///
385/// // Same content and structure are equal
386/// assert!(env1 == env2);
387///
388/// // Different content means not equal
389/// assert!(env1 != env3);
390///
391/// // Same content but different structure (one elided) are not equal
392/// assert!(env1 != env1.elide());
393/// ```
394impl PartialEq for Envelope {
395 /// Compares two envelopes for structural identity.
396 ///
397 /// This is equivalent to calling `self.is_identical_to(other)`.
398 fn eq(&self, other: &Self) -> bool { self.is_identical_to(other) }
399}