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}