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}