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}