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}