bc_envelope/extension/sskr.rs
1use std::collections::HashMap;
2
3pub use bc_components::{SSKRGroupSpec, SSKRSecret, SSKRShare, SSKRSpec};
4use bc_components::{SymmetricKey, sskr_combine, sskr_generate_using};
5use bc_rand::RandomNumberGenerator;
6#[cfg(feature = "known_value")]
7use known_values;
8
9use crate::{Envelope, Error, Result};
10
11/// Support for splitting and combining envelopes using SSKR (Shamir's Secret
12/// Sharing).
13///
14/// This module extends Gordian Envelope with functions to support Sharded
15/// Secret Key Reconstruction (SSKR), which is an implementation of Shamir's
16/// Secret Sharing. SSKR allows splitting a secret (in this case, a symmetric
17/// encryption key) into multiple shares, with a threshold required for
18/// reconstruction.
19///
20/// SSKR provides social recovery for encrypted envelopes by allowing the owner
21/// to distribute shares to trusted individuals or storage locations, with a
22/// specified threshold required to reconstruct the original envelope.
23///
24/// # How SSKR Works with Envelopes
25///
26/// The overall process is as follows:
27///
28/// 1. A Gordian Envelope is encrypted with a symmetric key
29/// 2. The symmetric key is split into multiple SSKR shares using a group
30/// threshold and member thresholds
31/// 3. Each share is added as an assertion to a copy of the encrypted envelope
32/// 4. These envelope copies are distributed to trusted individuals or storage
33/// locations
34/// 5. Later, when enough shares are brought together, the original envelope can
35/// be reconstructed
36///
37/// Sharded Secret Key Reconstruction (SSKR) for Envelopes.
38///
39/// For a complete working example, see the `sskr_split()` and `sskr_join()`
40/// method documentation.
41impl Envelope {
42 /// Returns a new `Envelope` with a `sskrShare: SSKRShare` assertion added.
43 ///
44 /// This is an internal helper function used by the SSKR split methods.
45 fn add_sskr_share(&self, share: &SSKRShare) -> Self {
46 self.add_assertion(known_values::SSKR_SHARE, share.clone())
47 }
48
49 /// Splits the envelope into a set of SSKR shares.
50 ///
51 /// This method splits the symmetric key used to encrypt the envelope into
52 /// SSKR shares, and returns multiple copies of the original envelope,
53 /// each with a different SSKR share added as an assertion. The envelope
54 /// subject should already be encrypted with the provided `content_key`.
55 ///
56 /// The returned structure is a nested array that preserves the group
57 /// structure of the SSKR shares. Each outer array represents a group,
58 /// and each inner array contains the shares for that group.
59 ///
60 /// # Parameters
61 ///
62 /// * `spec` - The SSKR specification that defines the group structure and
63 /// thresholds
64 /// * `content_key` - The symmetric key that was used to encrypt the
65 /// envelope
66 ///
67 /// # Returns
68 ///
69 /// A nested array of envelopes organized by groups, each envelope
70 /// containing the original encrypted envelope with a unique SSKR share
71 /// added as an assertion
72 ///
73 /// # Errors
74 ///
75 /// Returns an error if the SSKR shares cannot be generated
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use bc_envelope::prelude::*;
81 /// use bc_components::{SymmetricKey, SSKRGroupSpec, SSKRSpec};
82 ///
83 /// // Create and encrypt an envelope
84 /// let envelope = Envelope::new("Secret data").encrypt(&SymmetricKey::new());
85 ///
86 /// // Define a 2-of-3 SSKR split
87 /// let group_spec = SSKRGroupSpec::new(2, 3).unwrap();
88 /// let spec = SSKRSpec::new(1, vec![group_spec]).unwrap();
89 ///
90 /// // Create a symmetric key for the encrypted content
91 /// let content_key = SymmetricKey::new();
92 ///
93 /// // Split the envelope into shares
94 /// let shares = envelope.sskr_split(&spec, &content_key).unwrap();
95 ///
96 /// // The outer array represents groups (in this case, a single group)
97 /// assert_eq!(shares.len(), 1);
98 ///
99 /// // The inner array contains the shares for the group (3 shares in this example)
100 /// assert_eq!(shares[0].len(), 3);
101 ///
102 /// // Each share is an envelope with an 'sskrShare' assertion
103 /// for share in &shares[0] {
104 /// assert!(share.assertions_with_predicate(known_values::SSKR_SHARE).len() > 0);
105 /// }
106 /// ```
107 pub fn sskr_split(
108 &self,
109 spec: &SSKRSpec,
110 content_key: &SymmetricKey,
111 ) -> Result<Vec<Vec<Envelope>>> {
112 let mut rng = bc_rand::SecureRandomNumberGenerator;
113 self.sskr_split_using(spec, content_key, &mut rng)
114 }
115
116 /// Splits the envelope into a flattened set of SSKR shares.
117 ///
118 /// This method works like `sskr_split()` but returns a flat array of all
119 /// shares rather than preserving the group structure. This is
120 /// convenient when the group structure is not needed for distribution.
121 ///
122 /// # Parameters
123 ///
124 /// * `spec` - The SSKR specification that defines the group structure and
125 /// thresholds
126 /// * `content_key` - The symmetric key that was used to encrypt the
127 /// envelope
128 ///
129 /// # Returns
130 ///
131 /// A flat array of all envelopes containing SSKR shares
132 ///
133 /// # Errors
134 ///
135 /// Returns an error if the SSKR shares cannot be generated
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use bc_components::{SSKRGroupSpec, SSKRSpec, SymmetricKey};
141 /// use bc_envelope::prelude::*;
142 ///
143 /// // Create and encrypt an envelope
144 /// let envelope = Envelope::new("Secret data").encrypt(&SymmetricKey::new());
145 ///
146 /// // Define a 2-of-3 SSKR split in a single group
147 /// let group_spec = SSKRGroupSpec::new(2, 3).unwrap();
148 /// let spec = SSKRSpec::new(1, vec![group_spec]).unwrap();
149 ///
150 /// // Create a symmetric key for the encrypted content
151 /// let content_key = SymmetricKey::new();
152 ///
153 /// // Split the envelope into a flat array of shares
154 /// let shares = envelope.sskr_split_flattened(&spec, &content_key).unwrap();
155 ///
156 /// // We get all 3 shares in a flat array
157 /// assert_eq!(shares.len(), 3);
158 ///
159 /// // Each share is an envelope with an 'sskrShare' assertion
160 /// for share in &shares {
161 /// assert!(
162 /// share
163 /// .assertions_with_predicate(known_values::SSKR_SHARE)
164 /// .len()
165 /// > 0
166 /// );
167 /// }
168 /// ```
169 pub fn sskr_split_flattened(
170 &self,
171 spec: &SSKRSpec,
172 content_key: &SymmetricKey,
173 ) -> Result<Vec<Envelope>> {
174 Ok(self
175 .sskr_split(spec, content_key)?
176 .into_iter()
177 .flatten()
178 .collect())
179 }
180
181 #[doc(hidden)]
182 /// Internal function that splits the envelope using a provided random
183 /// number generator.
184 ///
185 /// This method is primarily used for testing to ensure deterministic SSKR
186 /// shares.
187 pub fn sskr_split_using(
188 &self,
189 spec: &SSKRSpec,
190 content_key: &SymmetricKey,
191 test_rng: &mut impl RandomNumberGenerator,
192 ) -> Result<Vec<Vec<Envelope>>> {
193 let master_secret = SSKRSecret::new(content_key.data())?;
194 let shares = sskr_generate_using(spec, &master_secret, test_rng)?;
195 let mut result: Vec<Vec<Envelope>> = Vec::new();
196 for group in shares {
197 let mut group_result: Vec<Envelope> = Vec::new();
198 for share in group {
199 let share_result = self.add_sskr_share(&share);
200 group_result.push(share_result);
201 }
202 result.push(group_result);
203 }
204 Ok(result)
205 }
206
207 /// Internal helper function that extracts SSKR shares from a set of
208 /// envelopes.
209 ///
210 /// This function groups the shares by their identifier (which must match
211 /// for shares to be combined).
212 fn sskr_shares_in(
213 envelopes: &[&Envelope],
214 ) -> Result<HashMap<u16, Vec<SSKRShare>>> {
215 let mut result: HashMap<u16, Vec<SSKRShare>> = HashMap::new();
216 for envelope in envelopes {
217 for assertion in
218 envelope.assertions_with_predicate(known_values::SSKR_SHARE)
219 {
220 let share = assertion
221 .as_object()
222 .unwrap()
223 .extract_subject::<SSKRShare>()?;
224 let identifier = share.identifier();
225 result
226 .entry(identifier)
227 .and_modify(|shares| shares.push(share.clone()))
228 .or_insert(vec![share]);
229 }
230 }
231 Ok(result)
232 }
233
234 /// Reconstructs the original envelope from a set of SSKR shares.
235 ///
236 /// Given a set of envelopes with SSKR share assertions, this method
237 /// attempts to combine the shares to reconstruct the original symmetric
238 /// key. If successful, it uses the key to decrypt the envelope and
239 /// return the original envelope subject.
240 ///
241 /// The method will try all combinations of shares with matching identifiers
242 /// to find a valid reconstruction.
243 ///
244 /// # Parameters
245 ///
246 /// * `envelopes` - An array of envelope references containing SSKR shares
247 ///
248 /// # Returns
249 ///
250 /// The original envelope if reconstruction is successful
251 ///
252 /// # Errors
253 ///
254 /// * Returns `EnvelopeError::InvalidShares` if not enough valid shares are
255 /// provided
256 /// * Returns various errors if decryption fails
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use bc_envelope::prelude::*;
262 /// use bc_components::{SymmetricKey, SSKRGroupSpec, SSKRSpec};
263 ///
264 /// // Create the original envelope with an assertion
265 /// let original = Envelope::new("Secret message")
266 /// .add_assertion("metadata", "This is a test");
267 ///
268 /// // Create a content key
269 /// let content_key = SymmetricKey::new();
270 ///
271 /// // Wrap the envelope (so the whole envelope including its assertions
272 /// // become the subject)
273 /// let wrapped_original = original
274 /// .wrap();
275 ///
276 /// // Encrypt the wrapped envelope
277 /// let encrypted = wrapped_original
278 /// .encrypt_subject(&content_key).unwrap();
279 ///
280 /// // Create a 2-of-3 SSKR split specification
281 /// let group = SSKRGroupSpec::new(2, 3).unwrap();
282 /// let spec = SSKRSpec::new(1, vec![group]).unwrap();
283 ///
284 /// // Split the encrypted envelope into shares
285 /// let shares = encrypted.sskr_split(&spec, &content_key).unwrap();
286 /// assert_eq!(shares[0].len(), 3);
287 ///
288 /// // The shares would normally be distributed to different people/places
289 /// // For recovery, we need at least the threshold number of shares (2 in this case)
290 /// let share1 = &shares[0][0];
291 /// let share2 = &shares[0][1];
292 ///
293 /// // Combine the shares to recover the original decrypted envelope
294 /// let recovered_wrapped = Envelope::sskr_join(&[share1, share2]).unwrap();
295 ///
296 /// // Unwrap the envelope to get the original envelope
297 /// let recovered = recovered_wrapped.try_unwrap().unwrap();
298 ///
299 /// // Check that the recovered envelope matches the original
300 /// assert!(recovered.is_identical_to(&original));
301 /// ```
302 pub fn sskr_join(envelopes: &[&Envelope]) -> Result<Envelope> {
303 if envelopes.is_empty() {
304 return Err(Error::InvalidShares);
305 }
306
307 let grouped_shares: Vec<_> =
308 Self::sskr_shares_in(envelopes)?.values().cloned().collect();
309 for shares in grouped_shares {
310 if let Ok(secret) = sskr_combine(&shares)
311 && let Ok(content_key) = SymmetricKey::from_data_ref(&secret)
312 && let Ok(envelope) =
313 envelopes.first().unwrap().decrypt_subject(&content_key)
314 {
315 return Ok(envelope.subject());
316 }
317 }
318
319 Err(Error::InvalidShares)
320 }
321}
322
323#[cfg(all(test, feature = "sskr", feature = "types", feature = "known_value"))]
324mod tests {
325 use bc_components::{SSKRGroupSpec, SSKRSpec, SymmetricKey};
326
327 use crate::prelude::*;
328
329 #[test]
330 fn test_sskr_split_and_join() {
331 // Create the original envelope with an assertion
332 let original = Envelope::new("Secret message")
333 .add_assertion("metadata", "This is a test");
334
335 // Create a content key
336 let content_key = SymmetricKey::new();
337
338 // Wrap the envelope (so the whole envelope including its assertions
339 // become the subject)
340 let wrapped_original = original.wrap();
341
342 // Encrypt the wrapped envelope
343 let encrypted = wrapped_original.encrypt_subject(&content_key).unwrap();
344
345 // Create a 2-of-3 SSKR split specification
346 let group = SSKRGroupSpec::new(2, 3).unwrap();
347 let spec = SSKRSpec::new(1, vec![group]).unwrap();
348
349 // Split the encrypted envelope into shares
350 let shares = encrypted.sskr_split(&spec, &content_key).unwrap();
351 assert_eq!(shares[0].len(), 3);
352
353 // The shares would normally be distributed to different people/places
354 // For recovery, we need at least the threshold number of shares (2 in
355 // this case)
356 let share1 = &shares[0][0];
357 let share2 = &shares[0][1];
358
359 // Combine the shares to recover the original decrypted envelope
360 let recovered_wrapped = Envelope::sskr_join(&[share1, share2]).unwrap();
361
362 // Unwrap the envelope to get the original envelope
363 let recovered = recovered_wrapped.try_unwrap().unwrap();
364
365 // Check that the recovered envelope matches the original
366 assert!(recovered.is_identical_to(&original));
367 }
368}