bc_envelope/extension/
salt.rs

1//! Extension for adding salt to envelopes to prevent correlation.
2//!
3//! This module provides functionality for decorrelating envelopes by adding
4//! random salt. Salt is added as an assertion with the known value predicate
5//! 'salt' and a random value. When an envelope is elided, this salt ensures
6//! that the digest of the elided envelope cannot be correlated with other
7//! elided envelopes containing the same information.
8//!
9//! Decorrelation is an important privacy feature that prevents third parties
10//! from determining whether two elided envelopes originally contained the same
11//! information by comparing their digests.
12//!
13//! # Examples
14//!
15//! ```
16//! use bc_envelope::prelude::*;
17//!
18//! // Create a simple envelope
19//! let envelope = Envelope::new("Hello");
20//!
21//! // Create a decorrelated version by adding salt
22//! let salted = envelope.add_salt();
23//!
24//! // The salted envelope has a different digest than the original
25//! assert_ne!(envelope.digest(), salted.digest());
26//!
27//! // Format shows that the salted envelope has a salt assertion
28//! assert!(salted.format_flat().contains("'salt': Salt"));
29//! ```
30
31use std::ops::RangeInclusive;
32
33use anyhow::Result;
34use bc_components::Salt;
35use bc_rand::{RandomNumberGenerator, SecureRandomNumberGenerator};
36use dcbor::prelude::*;
37#[cfg(feature = "known_value")]
38use known_values;
39
40use crate::Envelope;
41
42/// Support for decorrelation of envelopes using salt.
43impl Envelope {
44    /// Adds a proportionally-sized salt assertion to decorrelate the envelope.
45    ///
46    /// This method adds random salt bytes as an assertion to the envelope. The
47    /// size of the salt is proportional to the size of the envelope being
48    /// salted:
49    /// - For small envelopes: 8-16 bytes
50    /// - For larger envelopes: 5-25% of the envelope's size
51    ///
52    /// Salt is added as an assertion with the predicate 'salt' (a known value)
53    /// and an object containing random bytes. This changes the digest of
54    /// the envelope while preserving its semantic content, making it
55    /// impossible to correlate with other envelopes containing
56    /// the same information.
57    ///
58    /// # Returns
59    ///
60    /// A new envelope with the salt assertion added.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use bc_envelope::prelude::*;
66    ///
67    /// // Create an envelope with personally identifiable information
68    /// let alice = Envelope::new("Alice")
69    ///     .add_assertion("email", "alice@example.com")
70    ///     .add_assertion("ssn", "123-45-6789");
71    ///
72    /// // Create a second envelope with the same information
73    /// let alice2 = Envelope::new("Alice")
74    ///     .add_assertion("email", "alice@example.com")
75    ///     .add_assertion("ssn", "123-45-6789");
76    ///
77    /// // The envelopes have the same digest because they contain the same information
78    /// assert_eq!(alice.digest(), alice2.digest());
79    ///
80    /// // Add salt to both envelopes
81    /// let alice_salted = alice.add_salt();
82    /// let alice2_salted = alice2.add_salt();
83    ///
84    /// // Now the envelopes have different digests, preventing correlation
85    /// assert_ne!(alice_salted.digest(), alice2_salted.digest());
86    ///
87    /// // When elided, the salted envelopes still can't be correlated
88    /// assert_ne!(alice_salted.elide().digest(), alice2_salted.elide().digest());
89    /// ```
90    pub fn add_salt(&self) -> Self {
91        let mut rng = SecureRandomNumberGenerator;
92        self.add_salt_using(&mut rng)
93    }
94
95    /// Adds the given Salt as an assertion to the envelope.
96    ///
97    /// This method attaches a pre-existing Salt object as an assertion to the
98    /// envelope, using the known value 'salt' as the predicate. This is
99    /// useful when you need to control the specific salt content being
100    /// added.
101    ///
102    /// # Parameters
103    ///
104    /// * `salt` - A Salt object containing random bytes
105    ///
106    /// # Returns
107    ///
108    /// A new envelope with the salt assertion added.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use bc_components::Salt;
114    /// use bc_envelope::prelude::*;
115    ///
116    /// // Create a salt with specific bytes
117    /// let salt = Salt::from_data(vec![1, 2, 3, 4, 5, 6, 7, 8]);
118    ///
119    /// // Add this specific salt to an envelope
120    /// let envelope = Envelope::new("Hello");
121    /// let salted = envelope.add_salt_instance(salt);
122    ///
123    /// // The envelope now contains the salt assertion
124    /// assert!(salted.format().contains("'salt': Salt"));
125    /// ```
126    pub fn add_salt_instance(&self, salt: Salt) -> Self {
127        self.add_assertion(known_values::SALT, salt)
128    }
129
130    /// Adds salt of a specific byte length to the envelope.
131    ///
132    /// This method adds salt of a specified number of bytes to decorrelate the
133    /// envelope. It requires that the byte count be at least 8 bytes (64
134    /// bits) to ensure sufficient entropy for effective decorrelation.
135    ///
136    /// # Parameters
137    ///
138    /// * `count` - The exact number of salt bytes to add
139    ///
140    /// # Returns
141    ///
142    /// A Result containing the new envelope with salt added, or an error if the
143    /// byte count is less than the minimum (8 bytes).
144    ///
145    /// # Errors
146    ///
147    /// Returns an error if the specified byte count is less than 8.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use bc_envelope::prelude::*;
153    ///
154    /// let envelope = Envelope::new("Hello");
155    ///
156    /// // Add exactly 16 bytes of salt
157    /// let salted = envelope.add_salt_with_len(16).unwrap();
158    ///
159    /// // Trying to add less than 8 bytes will result in an error
160    /// assert!(envelope.add_salt_with_len(7).is_err());
161    /// ```
162    pub fn add_salt_with_len(&self, count: usize) -> Result<Self> {
163        let mut rng = SecureRandomNumberGenerator;
164        self.add_salt_with_len_using(count, &mut rng)
165    }
166
167    /// Adds salt of a specific byte length to the envelope using the provided
168    /// random number generator.
169    ///
170    /// This is an internal method that enables testing with deterministic
171    /// random number generators. It should not be used directly by most
172    /// code.
173    ///
174    /// # Parameters
175    ///
176    /// * `count` - The exact number of salt bytes to add
177    /// * `rng` - The random number generator to use
178    ///
179    /// # Returns
180    ///
181    /// A Result containing the new envelope with salt added, or an error if the
182    /// byte count is less than the minimum (8 bytes).
183    #[doc(hidden)]
184    pub fn add_salt_with_len_using(
185        &self,
186        count: usize,
187        rng: &mut impl RandomNumberGenerator,
188    ) -> Result<Self> {
189        let salt = Salt::new_with_len_using(count, rng)?;
190        Ok(self.add_salt_instance(salt))
191    }
192
193    /// Adds salt with a byte length randomly chosen from the given range.
194    ///
195    /// This method adds salt with a length randomly selected from the specified
196    /// range to decorrelate the envelope. This approach provides additional
197    /// decorrelation by varying the size of the salt itself.
198    ///
199    /// # Parameters
200    ///
201    /// * `range` - The inclusive range of byte lengths to choose from
202    ///
203    /// # Returns
204    ///
205    /// A Result containing the new envelope with salt added, or an error if the
206    /// minimum value of the range is less than 8 bytes.
207    ///
208    /// # Errors
209    ///
210    /// Returns an error if the minimum of the range is less than 8.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use std::ops::RangeInclusive;
216    ///
217    /// use bc_envelope::prelude::*;
218    ///
219    /// let envelope = Envelope::new("Hello");
220    ///
221    /// // Add salt with a length randomly chosen between 16 and 32 bytes
222    /// let salted = envelope.add_salt_in_range(16..=32).unwrap();
223    ///
224    /// // Trying to use a range with minimum less than 8 will result in an error
225    /// assert!(envelope.add_salt_in_range(4..=16).is_err());
226    /// ```
227    pub fn add_salt_in_range(
228        &self,
229        range: RangeInclusive<usize>,
230    ) -> Result<Self> {
231        let mut rng = SecureRandomNumberGenerator;
232        self.add_salt_in_range_using(&range, &mut rng)
233    }
234
235    /// Adds salt with a byte length randomly chosen from the given range using
236    /// the provided random number generator.
237    ///
238    /// This is an internal method that enables testing with deterministic
239    /// random number generators. It should not be used directly by most
240    /// code.
241    ///
242    /// # Parameters
243    ///
244    /// * `range` - The inclusive range of byte lengths to choose from
245    /// * `rng` - The random number generator to use
246    ///
247    /// # Returns
248    ///
249    /// A Result containing the new envelope with salt added, or an error if the
250    /// minimum value of the range is less than 8 bytes.
251    #[doc(hidden)]
252    pub fn add_salt_in_range_using(
253        &self,
254        range: &RangeInclusive<usize>,
255        rng: &mut impl RandomNumberGenerator,
256    ) -> Result<Self> {
257        Ok(self.add_salt_instance(Salt::new_in_range_using(range, rng)?))
258    }
259
260    /// Adds salt with a size proportional to the envelope's size using the
261    /// provided random number generator.
262    ///
263    /// This method is primarily for testing and internal use. It creates salt
264    /// with a size proportional to the serialized size of the envelope and
265    /// adds it as an assertion.
266    ///
267    /// # Parameters
268    ///
269    /// * `rng` - The random number generator to use
270    ///
271    /// # Returns
272    ///
273    /// A new envelope with the proportionally-sized salt assertion added.
274    #[doc(hidden)]
275    pub fn add_salt_using(&self, rng: &mut impl RandomNumberGenerator) -> Self {
276        let salt = Salt::new_for_size_using(
277            self.tagged_cbor().to_cbor_data().len(),
278            rng,
279        );
280        self.add_salt_instance(salt)
281    }
282}