bc_envelope/base/
envelope.rs

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