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}