bc_envelope/base/
envelope.rs

1#[cfg(not(feature = "multithreaded"))]
2use std::rc::Rc as RefCounted;
3#[cfg(feature = "multithreaded")]
4use std::sync::Arc as RefCounted;
5
6#[cfg(feature = "compress")]
7use bc_components::Compressed;
8#[cfg(feature = "encrypt")]
9use bc_components::EncryptedMessage;
10use bc_components::{Digest, DigestProvider};
11use dcbor::prelude::*;
12
13#[cfg(feature = "known_value")]
14use crate::extension::KnownValue;
15use crate::{EnvelopeEncodable, Error, Result, base::Assertion};
16
17/// A flexible container for structured data with built-in integrity
18/// verification.
19///
20/// Gordian Envelope is the primary data structure of this crate. It provides a
21/// way to encapsulate and organize data with cryptographic integrity, privacy
22/// features, and selective disclosure capabilities.
23///
24/// Key characteristics of envelopes:
25///
26/// - **Immutability**: Envelopes are immutable. Operations that appear to
27///   "modify" an envelope actually create a new envelope. This immutability is
28///   fundamental to maintaining the integrity of the envelope's digest tree.
29///
30/// - **O(1) Cloning**: Envelopes use reference counting (`Rc` or `Arc` when the
31///   `multithreaded` feature is enabled) and structure sharing to enable
32///   efficient O(1) cloning of an `Envelope` or recursively, any `Envelope`s it
33///   contains. Cloning an `Envelope` simply increments the reference count,
34///   allowing multiple owners without duplicating the underlying data.
35///
36/// - **Semantic Structure**: Envelopes can represent various semantic
37///   relationships through subjects, predicates, and objects (similar to RDF
38///   triples).
39///
40/// - **Digest Tree**: Each envelope maintains a Merkle-like digest tree that
41///   ensures the integrity of its contents and enables verification of
42///   individual parts.
43///
44/// - **Privacy Features**: Envelopes support selective disclosure through
45///   elision, encryption, and compression of specific parts, while maintaining
46///   the overall integrity of the structure.
47///
48/// - **Deterministic Representation**: Envelopes use deterministic CBOR
49///   encoding to ensure consistent serialization across platforms.
50///
51/// The Gordian Envelope specification is defined in an IETF Internet Draft, and
52/// this implementation closely follows that specification.
53///
54/// # Example
55///
56/// ```
57/// use bc_envelope::prelude::*;
58///
59/// // Create an envelope representing a person
60/// let person = Envelope::new("person")
61///     .add_assertion("name", "Alice")
62///     .add_assertion("age", 30)
63///     .add_assertion("email", "alice@example.com");
64///
65/// // Create a partially redacted version by eliding the email
66/// let redacted = person.elide_removing_target(
67///     &person.assertion_with_predicate("email").unwrap(),
68/// );
69///
70/// // The digest of both envelopes remains the same
71/// assert_eq!(person.digest(), redacted.digest());
72/// ```
73#[derive(Debug, Clone, Eq)]
74pub struct Envelope(RefCounted<EnvelopeCase>);
75
76impl Envelope {
77    /// Returns a reference to the underlying envelope case.
78    ///
79    /// The `EnvelopeCase` enum represents the specific structural variant of
80    /// this envelope. This method provides access to that underlying
81    /// variant for operations that need to differentiate between the
82    /// different envelope types.
83    ///
84    /// # Returns
85    ///
86    /// A reference to the `EnvelopeCase` that defines this envelope's
87    /// structure.
88    pub fn case(&self) -> &EnvelopeCase { &self.0 }
89}
90
91/// Conversion from `EnvelopeCase` to `Envelope`.
92///
93/// This allows creating an envelope directly from an envelope case variant.
94impl From<EnvelopeCase> for Envelope {
95    fn from(case: EnvelopeCase) -> Self { Self(RefCounted::new(case)) }
96}
97
98/// Conversion from `&Envelope` to `Envelope`.
99///
100/// This creates a clone of the envelope. Since envelopes use reference
101/// counting, this is a relatively inexpensive operation.
102impl From<&Envelope> for Envelope {
103    fn from(envelope: &Envelope) -> Self { envelope.clone() }
104}
105
106/// The core structural variants of a Gordian Envelope.
107///
108/// Each variant of this enum represents a different structural form that an
109/// envelope can take, as defined in the Gordian Envelope IETF Internet Draft.
110/// The different cases provide different capabilities and serve different
111/// purposes in the envelope ecosystem.
112///
113/// The `EnvelopeCase` is the internal representation of an envelope's
114/// structure. While each case has unique properties, they all maintain a digest
115/// that ensures the integrity of the envelope.
116///
117/// It is advised to use the other Envelop APIs for most uses. Please see the
118/// queries module for more information on how to interact with envelopes.
119#[derive(Debug, PartialEq, Eq)]
120pub enum EnvelopeCase {
121    /// Represents an envelope with a subject and one or more assertions.
122    ///
123    /// A node is the fundamental structural component for building complex data
124    /// structures with Gordian Envelope. It consists of a subject and a set of
125    /// assertions about that subject.
126    ///
127    /// The digest of a node is derived from the digests of its subject and all
128    /// assertions, ensuring that any change to the node or its components would
129    /// result in a different digest.
130    Node {
131        /// The subject of the node
132        subject: Envelope,
133        /// The assertions attached to the subject
134        assertions: Vec<Envelope>,
135        /// The digest of the node
136        digest: Digest,
137    },
138
139    /// Represents an envelope containing a primitive CBOR value.
140    ///
141    /// A leaf is the simplest form of envelope, containing a single CBOR value
142    /// such as a string, number, or boolean. Leaves are the terminal nodes in
143    /// the envelope structure.
144    ///
145    /// The digest of a leaf is derived directly from its CBOR representation.
146    Leaf {
147        /// The CBOR value contained in the leaf
148        cbor: CBOR,
149        /// The digest of the leaf
150        digest: Digest,
151    },
152
153    /// Represents an envelope that wraps another envelope.
154    ///
155    /// Wrapping provides a way to encapsulate an entire envelope as the subject
156    /// of another envelope, enabling hierarchical structures and metadata
157    /// attachment.
158    ///
159    /// The digest of a wrapped envelope is derived from the digest of the
160    /// envelope it wraps.
161    Wrapped {
162        /// The envelope being wrapped
163        envelope: Envelope,
164        /// The digest of the wrapped envelope
165        digest: Digest,
166    },
167
168    /// Represents a predicate-object assertion.
169    ///
170    /// An assertion is a statement about a subject, consisting of a predicate
171    /// (what is being asserted) and an object (the value of the assertion).
172    /// Assertions are attached to envelope subjects to form semantic
173    /// statements.
174    ///
175    /// For example, in the statement "Alice hasEmail alice@example.com":
176    /// - The subject is "Alice"
177    /// - The predicate is "hasEmail"
178    /// - The object is "alice@example.com"
179    Assertion(Assertion),
180
181    /// Represents an envelope that has been elided, leaving only its digest.
182    ///
183    /// Elision is a key privacy feature of Gordian Envelope, allowing parts of
184    /// an envelope to be removed while maintaining the integrity of the digest
185    /// tree. This enables selective disclosure of information.
186    Elided(Digest),
187
188    /// Represents a value from a namespace of unsigned integers used for
189    /// ontological concepts.
190    ///
191    /// Known Values are 64-bit unsigned integers used to represent stand-alone
192    /// ontological concepts like relationships (`isA`, `containedIn`),
193    /// classes (`Seed`, `PrivateKey`), or enumerated values (`MainNet`,
194    /// `OK`). They provide a compact, deterministic alternative to URIs for
195    /// representing common predicates and values.
196    ///
197    /// Using Known Values instead of strings for common predicates offers
198    /// several advantages:
199    /// - More compact representation (integers vs. long strings/URIs)
200    /// - Standardized semantics across implementations
201    /// - Deterministic encoding for cryptographic operations
202    /// - Resistance to manipulation attacks that target string representations
203    ///
204    /// Known Values are displayed with single quotes, e.g., `'isA'` or by their
205    /// numeric value like `'1'` (when no name is assigned).
206    ///
207    /// This variant is only available when the `known_value` feature is
208    /// enabled.
209    #[cfg(feature = "known_value")]
210    KnownValue {
211        /// The Known Value instance containing the integer value and optional
212        /// name
213        value: KnownValue,
214        /// The digest of the known value
215        digest: Digest,
216    },
217
218    /// Represents an envelope that has been encrypted.
219    ///
220    /// Encryption is a privacy feature that allows parts of an envelope to be
221    /// encrypted while maintaining the integrity of the digest tree. The
222    /// encrypted content can only be accessed by those with the appropriate
223    /// key.
224    ///
225    /// This variant is only available when the `encrypt` feature is enabled.
226    #[cfg(feature = "encrypt")]
227    Encrypted(EncryptedMessage),
228
229    /// Represents an envelope that has been compressed.
230    ///
231    /// Compression reduces the size of an envelope while maintaining its full
232    /// content and digest integrity. Unlike elision or encryption, compression
233    /// doesn't restrict access to the content, but simply makes it more
234    /// compact.
235    ///
236    /// This variant is only available when the `compress` feature is enabled.
237    #[cfg(feature = "compress")]
238    Compressed(Compressed),
239}
240
241/// Support for basic envelope creation.
242impl Envelope {
243    /// Creates an envelope with a `subject`, which
244    /// can be any instance that implements ``EnvelopeEncodable``.
245    pub fn new(subject: impl EnvelopeEncodable) -> Self {
246        subject.into_envelope()
247    }
248
249    /// Creates an envelope with a `subject`, which
250    /// can be any instance that implements ``EnvelopeEncodable``.
251    ///
252    /// If `subject` is `None`, returns a null envelope.
253    pub fn new_or_null(subject: Option<impl EnvelopeEncodable>) -> Self {
254        subject.map_or_else(Self::null, Self::new)
255    }
256
257    /// Creates an envelope with a `subject`, which
258    /// can be any instance that implements ``EnvelopeEncodable``.
259    ///
260    /// If `subject` is `None`, returns `None`.
261    pub fn new_or_none(
262        subject: Option<impl EnvelopeEncodable>,
263    ) -> Option<Self> {
264        subject.map(Self::new)
265    }
266
267    /// Creates an assertion envelope with a `predicate` and `object`,
268    /// each of which can be any instance that implements ``EnvelopeEncodable``.
269    pub fn new_assertion(
270        predicate: impl EnvelopeEncodable,
271        object: impl EnvelopeEncodable,
272    ) -> Self {
273        Self::new_with_assertion(Assertion::new(predicate, object))
274    }
275}
276
277/// Internal constructors
278impl Envelope {
279    pub(crate) fn new_with_unchecked_assertions(
280        subject: Self,
281        unchecked_assertions: Vec<Self>,
282    ) -> Self {
283        assert!(!unchecked_assertions.is_empty());
284        let mut sorted_assertions = unchecked_assertions;
285        sorted_assertions.sort_by_key(|a| a.digest());
286        let mut digests = vec![subject.digest()];
287        digests.extend(sorted_assertions.iter().map(|a| a.digest()));
288        let digest = Digest::from_digests(&digests);
289        (EnvelopeCase::Node { subject, assertions: sorted_assertions, digest })
290            .into()
291    }
292
293    pub(crate) fn new_with_assertions(
294        subject: Self,
295        assertions: Vec<Self>,
296    ) -> Result<Self> {
297        if !assertions
298            .iter()
299            .all(|a| a.is_subject_assertion() || a.is_subject_obscured())
300        {
301            return Err(Error::InvalidFormat);
302        }
303        Ok(Self::new_with_unchecked_assertions(subject, assertions))
304    }
305
306    pub(crate) fn new_with_assertion(assertion: Assertion) -> Self {
307        EnvelopeCase::Assertion(assertion).into()
308    }
309
310    #[cfg(feature = "known_value")]
311    pub(crate) fn new_with_known_value(value: KnownValue) -> Self {
312        let digest = value.digest();
313        (EnvelopeCase::KnownValue { value, digest }).into()
314    }
315
316    #[cfg(feature = "encrypt")]
317    pub(crate) fn new_with_encrypted(
318        encrypted_message: EncryptedMessage,
319    ) -> Result<Self> {
320        if !encrypted_message.has_digest() {
321            return Err(Error::MissingDigest);
322        }
323        Ok(EnvelopeCase::Encrypted(encrypted_message).into())
324    }
325
326    #[cfg(feature = "compress")]
327    pub(crate) fn new_with_compressed(compressed: Compressed) -> Result<Self> {
328        if !compressed.has_digest() {
329            return Err(Error::MissingDigest);
330        }
331        Ok(EnvelopeCase::Compressed(compressed).into())
332    }
333
334    pub(crate) fn new_elided(digest: Digest) -> Self {
335        EnvelopeCase::Elided(digest).into()
336    }
337
338    pub(crate) fn new_leaf(value: impl Into<CBOR>) -> Self {
339        let cbor: CBOR = value.into();
340        let digest = Digest::from_image(cbor.to_cbor_data());
341        (EnvelopeCase::Leaf { cbor, digest }).into()
342    }
343
344    pub(crate) fn new_wrapped(envelope: Self) -> Self {
345        let digest = Digest::from_digests(&[envelope.digest()]);
346        (EnvelopeCase::Wrapped { envelope, digest }).into()
347    }
348}
349
350impl AsRef<Envelope> for Envelope {
351    fn as_ref(&self) -> &Envelope { self }
352}
353
354#[cfg(test)]
355mod tests {
356    #[cfg(feature = "compress")]
357    use bc_components::Compressed;
358    use bc_components::DigestProvider;
359
360    #[cfg(feature = "known_value")]
361    use crate::extension::KnownValue;
362    use crate::{Assertion, Envelope};
363
364    #[test]
365    fn test_any_envelope() {
366        let e1 = Envelope::new_leaf("Hello");
367        let e2 = Envelope::new("Hello");
368        assert_eq!(e1.format(), e2.format());
369        assert_eq!(e1.digest(), e2.digest());
370    }
371
372    #[cfg(feature = "known_value")]
373    #[test]
374    fn test_any_known_value() {
375        let known_value = KnownValue::new(100);
376        let e1 = Envelope::new_with_known_value(known_value.clone());
377        let e2 = Envelope::new(known_value);
378        assert_eq!(e1.format(), e2.format());
379        assert_eq!(e1.digest(), e2.digest());
380    }
381
382    #[test]
383    fn test_any_assertion() {
384        let assertion = Assertion::new("knows", "Bob");
385        let e1 = Envelope::new_with_assertion(assertion.clone());
386        let e2 = Envelope::new(assertion);
387        assert_eq!(e1.format(), e2.format());
388        assert_eq!(e1.digest(), e2.digest());
389    }
390
391    #[test]
392    fn test_any_encrypted() {
393        //todo!()
394    }
395
396    #[cfg(feature = "compress")]
397    #[test]
398    fn test_any_compressed() {
399        let data = "Hello".as_bytes();
400        let digest = data.digest();
401        let compressed = Compressed::from_decompressed_data(data, Some(digest));
402        let e1 = Envelope::new_with_compressed(compressed.clone()).unwrap();
403        let e2 = Envelope::try_from(compressed).unwrap();
404        assert_eq!(e1.format(), e2.format());
405        assert_eq!(e1.digest(), e2.digest());
406    }
407
408    #[test]
409    fn test_any_cbor_encodable() {
410        let e1 = Envelope::new_leaf(1);
411        let e2 = Envelope::new(1);
412        assert_eq!(e1.format(), e2.format());
413        assert_eq!(e1.digest(), e2.digest());
414    }
415}