dark_crystal_key_backup_rust/
lib.rs

1//! Provides encryption of secret shares to specific recipients using [`crypto_box`](https://docs.rs/crate/crypto_box)
2//!
3//! Internally uses [`dark-crystal-secret-sharing-rust`](https://docs.rs/dark-crystal-secret-sharing-rust),
4//! which uses [`sharks`](https://docs.rs/sharks/0.5.0/sharks/) for Shamirs secret sharing and [`xsalsa20poly1305`](https://docs.rs/xsalsa20poly1305/0.8.0/xsalsa20poly1305/)
5//! for authenticated encryption.
6//!
7//! This is part of a work-in-progress Rust implementation of the [Dark Crystal Key Backup Protocol](https://darkcrystal.pw/protocol-specification/).
8
9pub use crypto_box::aead::Error;
10use crypto_box::aead::{generic_array::GenericArray, Aead};
11pub use crypto_box::{Box, PublicKey, SecretKey};
12pub use dark_crystal_secret_sharing_rust::{
13    combine_authenticated, default_threshold, share_authenticated, thresold_sanity, RecoveryError,
14    ShareError,
15};
16use rand::Rng;
17use std::convert::TryInto;
18use std::fmt;
19use zeroize::Zeroize;
20
21const PUBLIC_KEY_LENGTH: usize = 32;
22const NONCE_LENGTH: usize = 24;
23
24/// A set of encrypted shares, together with the public key used for encryption
25/// and the encrypted secret
26#[derive(Debug)]
27pub struct EncryptedShareSet {
28    pub ciphertext: Vec<u8>,
29    pub encrypted_shares: Vec<Vec<u8>>,
30    pub eph_public_key: PublicKey,
31}
32
33/// Create a set of shares and encrypt them to a given set of public keys
34pub fn share_and_encrypt(
35    public_keys: Vec<[u8; PUBLIC_KEY_LENGTH]>,
36    secret: Vec<u8>,
37    threshold: u8,
38) -> Result<EncryptedShareSet, ShareAndEncryptError> {
39    // TODO make a ShareError so these errors can be handled
40    let num_shares = public_keys.len().try_into().unwrap();
41    let (shares, ciphertext) = share_authenticated(&secret, num_shares, threshold)?;
42    let mut encrypted_shares: Vec<Vec<u8>> = Vec::new();
43
44    let mut rng = crypto_box::rand_core::OsRng;
45    let eph_secret_key = SecretKey::generate(&mut rng);
46    let eph_public_key = eph_secret_key.public_key();
47    let mut eph_secret_key_bytes = eph_secret_key.as_bytes().clone();
48
49    for share_index in 0..public_keys.len() {
50        let share = &shares[share_index];
51        let pk = PublicKey::from(public_keys[share_index]);
52        let esk = SecretKey::from(eph_secret_key_bytes);
53        encrypted_shares.push(encrypt(esk, pk, share.to_vec())?);
54    }
55    eph_secret_key_bytes.zeroize();
56
57    Ok(EncryptedShareSet {
58        encrypted_shares,
59        ciphertext,
60        eph_public_key,
61    })
62}
63
64/// Create a set of shares and encrypt them to a given set of public keys
65/// but make the shares shorted by using the nonce from the ciphertext
66/// when encrypting the shares
67pub fn share_and_encrypt_detached_nonce(
68    public_keys: Vec<[u8; PUBLIC_KEY_LENGTH]>,
69    secret: Vec<u8>,
70    threshold: u8,
71) -> Result<EncryptedShareSet, ShareAndEncryptError> {
72    // TODO make a ShareError so these errors can be handled
73    let num_shares = public_keys.len().try_into().unwrap();
74    let (shares, ciphertext) = share_authenticated(&secret, num_shares, threshold)?;
75    let mut encrypted_shares: Vec<Vec<u8>> = Vec::new();
76
77    let mut rng = crypto_box::rand_core::OsRng;
78    let eph_secret_key = SecretKey::generate(&mut rng);
79    let eph_public_key = eph_secret_key.public_key();
80    let mut eph_secret_key_bytes = eph_secret_key.as_bytes().clone();
81
82    let nonce: [u8; NONCE_LENGTH] = ciphertext.clone()[..NONCE_LENGTH].try_into().unwrap();
83
84    for share_index in 0..public_keys.len() {
85        let share = &shares[share_index];
86        let pk = PublicKey::from(public_keys[share_index]);
87        let esk = SecretKey::from(eph_secret_key_bytes);
88        encrypted_shares.push(encrypt_with_given_nonce(esk, pk, share.to_vec(), nonce)?);
89    }
90    eph_secret_key_bytes.zeroize();
91
92    Ok(EncryptedShareSet {
93        encrypted_shares,
94        ciphertext,
95        eph_public_key,
96    })
97}
98
99/// Encrypt a given message using crypto_box
100pub fn encrypt(
101    secret_key: SecretKey,
102    public_key: PublicKey,
103    plaintext: Vec<u8>,
104) -> Result<Vec<u8>, Error> {
105    let alice_box = Box::new(&public_key, &secret_key);
106    let mut rng = crypto_box::rand_core::OsRng;
107    let nonce_bytes = rng.gen::<[u8; NONCE_LENGTH]>();
108    let nonce = GenericArray::from_slice(&nonce_bytes);
109
110    let mut ciphertext_with_nonce = nonce_bytes.to_vec();
111
112    let ciphertext = alice_box.encrypt(&nonce, &plaintext[..])?;
113    ciphertext_with_nonce.extend(ciphertext);
114    Ok(ciphertext_with_nonce)
115}
116
117/// Decrypt a given ciphertext using crypto_box
118pub fn decrypt(
119    secret_key: SecretKey,
120    public_key: &PublicKey,
121    ciphertext_with_nonce: &Vec<u8>,
122) -> Result<Vec<u8>, Error> {
123    let ciphertext = &ciphertext_with_nonce[NONCE_LENGTH..];
124    let nonce = GenericArray::from_slice(&ciphertext_with_nonce[..NONCE_LENGTH]);
125
126    let bob_box = Box::new(public_key, &secret_key);
127    bob_box.decrypt(&nonce, &ciphertext[..])
128}
129
130/// Encrypt a given message using crypto_box
131/// using a given nonce rather than generating one
132pub fn encrypt_with_given_nonce(
133    secret_key: SecretKey,
134    public_key: PublicKey,
135    plaintext: Vec<u8>,
136    nonce: [u8; NONCE_LENGTH],
137) -> Result<Vec<u8>, Error> {
138    let alice_box = Box::new(&public_key, &secret_key);
139    let nonce = GenericArray::from_slice(&nonce);
140    alice_box.encrypt(&nonce, &plaintext[..])
141}
142
143/// Decrypt a given ciphertext using crypto_box
144/// using a given nonce rather than attaching one to the ciphertext
145pub fn decrypt_with_given_nonce(
146    secret_key: SecretKey,
147    public_key: &PublicKey,
148    ciphertext: &Vec<u8>,
149    nonce: [u8; NONCE_LENGTH],
150) -> Result<Vec<u8>, Error> {
151    let nonce = GenericArray::from_slice(&nonce);
152    let bob_box = Box::new(public_key, &secret_key);
153    bob_box.decrypt(&nonce, &ciphertext[..])
154}
155
156/// Error created when the share function fails
157#[derive(Debug)]
158pub struct ShareAndEncryptError {
159    pub message: String,
160}
161
162impl fmt::Display for ShareAndEncryptError {
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        write!(f, "Error during recovery {}", self.message)
165    }
166}
167
168impl From<crypto_box::aead::Error> for ShareAndEncryptError {
169    fn from(error: crypto_box::aead::Error) -> Self {
170        ShareAndEncryptError {
171            message: error.to_string(),
172        }
173    }
174}
175
176impl From<ShareError> for ShareAndEncryptError {
177    fn from(error: ShareError) -> Self {
178        ShareAndEncryptError {
179            message: error.to_string(),
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use dark_crystal_secret_sharing_rust::combine_authenticated;
188
189    #[test]
190    fn encryption() {
191        let mut rng = crypto_box::rand_core::OsRng;
192        let alice_secret_key = SecretKey::generate(&mut rng);
193        let alice_public_key = alice_secret_key.public_key();
194
195        let bob_secret_key = SecretKey::generate(&mut rng);
196        let bob_public_key = bob_secret_key.public_key();
197
198        let plaintext = b"hello";
199
200        let ciphertext = encrypt(alice_secret_key, bob_public_key, plaintext.to_vec()).unwrap();
201        let decrypted_plaintext = decrypt(bob_secret_key, &alice_public_key, &ciphertext).unwrap();
202
203        assert_eq!(&plaintext[..], &decrypted_plaintext[..]);
204    }
205
206    #[test]
207    fn encryption_with_given_nonce() {
208        let mut rng = crypto_box::rand_core::OsRng;
209        let alice_secret_key = SecretKey::generate(&mut rng);
210        let alice_public_key = alice_secret_key.public_key();
211
212        let bob_secret_key = SecretKey::generate(&mut rng);
213        let bob_public_key = bob_secret_key.public_key();
214
215        let nonce = rng.gen::<[u8; NONCE_LENGTH]>();
216        let plaintext = b"hello";
217
218        let ciphertext =
219            encrypt_with_given_nonce(alice_secret_key, bob_public_key, plaintext.to_vec(), nonce)
220                .unwrap();
221        let decrypted_plaintext =
222            decrypt_with_given_nonce(bob_secret_key, &alice_public_key, &ciphertext, nonce)
223                .unwrap();
224
225        assert_eq!(&plaintext[..], &decrypted_plaintext[..]);
226    }
227
228    #[test]
229    fn test_share_and_encrypt() {
230        let mut rng = crypto_box::rand_core::OsRng;
231        let alice_secret_key = SecretKey::generate(&mut rng);
232        let alice_public_key = alice_secret_key.public_key();
233
234        let bob_secret_key = SecretKey::generate(&mut rng);
235        let bob_public_key = bob_secret_key.public_key();
236
237        let mut public_keys: Vec<[u8; PUBLIC_KEY_LENGTH]> = Vec::new();
238        public_keys.push(*bob_public_key.as_bytes());
239        public_keys.push(*alice_public_key.as_bytes());
240
241        let original_secret = b"hello";
242        let encrypted_share_set =
243            share_and_encrypt(public_keys, original_secret[..].to_vec(), 2).unwrap();
244
245        assert_eq!(encrypted_share_set.encrypted_shares.len(), 2);
246        assert_eq!(encrypted_share_set.encrypted_shares[0].len(), 73);
247
248        // Now decrypt the shares
249        let mut decrypted_shares: Vec<Vec<u8>> = Vec::new();
250        decrypted_shares.push(
251            decrypt(
252                alice_secret_key,
253                &encrypted_share_set.eph_public_key,
254                &encrypted_share_set.encrypted_shares[1],
255            )
256            .unwrap(),
257        );
258        decrypted_shares.push(
259            decrypt(
260                bob_secret_key,
261                &encrypted_share_set.eph_public_key,
262                &encrypted_share_set.encrypted_shares[0],
263            )
264            .unwrap(),
265        );
266
267        // Recover secret
268        let recovered_secret =
269            combine_authenticated(decrypted_shares, encrypted_share_set.ciphertext).unwrap();
270        assert_eq!(recovered_secret, b"hello");
271    }
272
273    #[test]
274    fn test_share_and_encrypt_detached_nonce() {
275        let mut rng = crypto_box::rand_core::OsRng;
276        let alice_secret_key = SecretKey::generate(&mut rng);
277        let alice_public_key = alice_secret_key.public_key();
278
279        let bob_secret_key = SecretKey::generate(&mut rng);
280        let bob_public_key = bob_secret_key.public_key();
281
282        let mut public_keys: Vec<[u8; PUBLIC_KEY_LENGTH]> = Vec::new();
283        public_keys.push(*bob_public_key.as_bytes());
284        public_keys.push(*alice_public_key.as_bytes());
285
286        let original_secret = b"hello";
287        let encrypted_share_set =
288            share_and_encrypt_detached_nonce(public_keys, original_secret[..].to_vec(), 2).unwrap();
289
290        assert_eq!(encrypted_share_set.encrypted_shares.len(), 2);
291        assert_eq!(encrypted_share_set.encrypted_shares[0].len(), 49);
292
293        // Now decrypt the shares
294        let nonce = encrypted_share_set.ciphertext.clone()[..NONCE_LENGTH]
295            .try_into()
296            .unwrap();
297
298        let mut decrypted_shares: Vec<Vec<u8>> = Vec::new();
299        decrypted_shares.push(
300            decrypt_with_given_nonce(
301                alice_secret_key,
302                &encrypted_share_set.eph_public_key,
303                &encrypted_share_set.encrypted_shares[1],
304                nonce,
305            )
306            .unwrap(),
307        );
308        decrypted_shares.push(
309            decrypt_with_given_nonce(
310                bob_secret_key,
311                &encrypted_share_set.eph_public_key,
312                &encrypted_share_set.encrypted_shares[0],
313                nonce,
314            )
315            .unwrap(),
316        );
317
318        // Recover secret
319        let recovered_secret =
320            combine_authenticated(decrypted_shares, encrypted_share_set.ciphertext).unwrap();
321        assert_eq!(recovered_secret, b"hello");
322    }
323}