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