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