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