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