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}