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}