bc_envelope/extension/
sskr.rs

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