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