wamu_core/
share_recovery_backup.rs1use aes_gcm::aead::consts::U12;
10use aes_gcm::aes::Aes256;
11use aes_gcm::{
12 aead::{Aead, AeadCore, KeyInit},
13 Aes256Gcm, AesGcm,
14};
15use crypto_bigint::{Encoding, U256};
16use hkdf::Hkdf;
17use sha2::Sha256;
18
19use crate::errors::ShareBackupRecoveryError;
20use crate::payloads::EncryptedShareBackup;
21use crate::share::{SigningShare, SubShare};
22use crate::traits::IdentityProvider;
23
24pub fn backup(
30 entropy_seed: &[u8],
31 signing_share: &SigningShare,
32 sub_share: &SubShare,
33 identity_provider: &impl IdentityProvider,
34) -> Result<EncryptedShareBackup, ShareBackupRecoveryError> {
35 let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng());
37
38 let cipher = generate_encryption_cipher(entropy_seed, identity_provider);
40 let encrypted_signing_share = cipher.encrypt(&nonce, signing_share.to_be_bytes().as_ref())?;
41 let encrypted_sub_share = (
42 cipher.encrypt(&nonce, sub_share.x().to_be_bytes().as_ref())?,
43 cipher.encrypt(&nonce, sub_share.y().to_be_bytes().as_ref())?,
44 );
45
46 Ok(EncryptedShareBackup {
48 signing_share: encrypted_signing_share,
49 sub_share: encrypted_sub_share,
50 nonce: nonce.to_vec(),
51 })
52}
53
54pub fn recover(
60 entropy_seed: &[u8],
61 encrypted_share_backup: &EncryptedShareBackup,
62 identity_provider: &impl IdentityProvider,
63) -> Result<(SigningShare, SubShare), ShareBackupRecoveryError> {
64 let nonce = aes_gcm::Nonce::from_slice(&encrypted_share_backup.nonce);
66
67 let cipher = generate_encryption_cipher(entropy_seed, identity_provider);
69 let signing_share_bytes =
70 cipher.decrypt(nonce, encrypted_share_backup.signing_share.as_ref())?;
71 let signing_share = SigningShare::try_from(signing_share_bytes.as_ref())
72 .map_err(|_| ShareBackupRecoveryError::InvalidSigningShare)?;
73 let sub_share = SubShare::new(
74 U256::from_be_bytes(
75 cipher
76 .decrypt(nonce, encrypted_share_backup.sub_share.0.as_ref())?
77 .try_into()
78 .map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?,
79 ),
80 U256::from_be_bytes(
81 cipher
82 .decrypt(nonce, encrypted_share_backup.sub_share.1.as_ref())?
83 .try_into()
84 .map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?,
85 ),
86 )
87 .map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?;
88
89 Ok((signing_share, sub_share))
90}
91
92fn generate_encryption_cipher(
94 entropy_seed: &[u8],
95 identity_provider: &impl IdentityProvider,
96) -> AesGcm<Aes256, U12> {
97 let key_bytes = generate_encryption_key(entropy_seed, identity_provider);
99 let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&key_bytes);
100
101 Aes256Gcm::new(key)
103}
104
105fn generate_encryption_key(
107 entropy_seed: &[u8],
108 identity_provider: &impl IdentityProvider,
109) -> [u8; 32] {
110 let entropy = identity_provider.sign(entropy_seed);
112
113 let mut output_key = [0u8; 32];
115 Hkdf::<Sha256>::new(None, &entropy.sig)
116 .expand(&[], &mut output_key)
117 .expect("32 is a valid length for Sha256 to output");
118
119 output_key
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::crypto::Random32Bytes;
127 use crate::share::SecretShare;
128 use crate::share_split_reconstruct;
129 use crate::test_utils::MockECDSAIdentityProvider;
130
131 #[test]
132 fn share_recovery_with_encrypted_backup_works() {
133 let identity_provider = MockECDSAIdentityProvider::generate();
135
136 let entropy_seed = b"Hello, world!";
138
139 let secret_share = SecretShare::from(Random32Bytes::generate_mod_q());
141
142 let (signing_share, sub_share) =
144 share_split_reconstruct::split(&secret_share, &identity_provider).unwrap();
145
146 let backup_result = backup(entropy_seed, &signing_share, &sub_share, &identity_provider);
148
149 assert!(backup_result.is_ok());
151
152 let encrypted_share_backup = backup_result.unwrap();
154
155 let recover_result = recover(entropy_seed, &encrypted_share_backup, &identity_provider);
157
158 assert!(recover_result.is_ok());
160
161 let (recovered_signing_share, recovered_sub_share) = recover_result.unwrap();
163
164 assert_eq!(
166 &recovered_signing_share.to_be_bytes(),
167 &signing_share.to_be_bytes()
168 );
169 assert_eq!(recovered_sub_share.as_tuple(), sub_share.as_tuple());
170 }
171
172 #[test]
173 fn generate_encryption_key_works() {
174 let identity_provider = MockECDSAIdentityProvider::generate();
176
177 let entropy_seed = b"Hello, world!";
179
180 let encryption_key = generate_encryption_key(entropy_seed, &identity_provider);
182
183 assert_eq!(
185 encryption_key,
186 generate_encryption_key(entropy_seed, &identity_provider)
187 );
188
189 assert_ne!(
191 encryption_key,
192 generate_encryption_key(entropy_seed, &MockECDSAIdentityProvider::generate())
193 );
194 assert_ne!(
195 encryption_key,
196 generate_encryption_key(b"Another phrase.", &identity_provider)
197 );
198 }
199}