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