use aes_gcm::aead::consts::U12;
use aes_gcm::aes::Aes256;
use aes_gcm::{
aead::{Aead, AeadCore, KeyInit},
Aes256Gcm, AesGcm,
};
use crypto_bigint::{Encoding, U256};
use hkdf::Hkdf;
use sha2::Sha256;
use crate::errors::ShareBackupRecoveryError;
use crate::payloads::EncryptedShareBackup;
use crate::share::{SigningShare, SubShare};
use crate::traits::IdentityProvider;
pub fn backup(
entropy_seed: &[u8],
signing_share: &SigningShare,
sub_share: &SubShare,
identity_provider: &impl IdentityProvider,
) -> Result<EncryptedShareBackup, ShareBackupRecoveryError> {
let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng());
let cipher = generate_encryption_cipher(entropy_seed, identity_provider);
let encrypted_signing_share = cipher.encrypt(&nonce, signing_share.to_be_bytes().as_ref())?;
let encrypted_sub_share = (
cipher.encrypt(&nonce, sub_share.x().to_be_bytes().as_ref())?,
cipher.encrypt(&nonce, sub_share.y().to_be_bytes().as_ref())?,
);
Ok(EncryptedShareBackup {
signing_share: encrypted_signing_share,
sub_share: encrypted_sub_share,
nonce: nonce.to_vec(),
})
}
pub fn recover(
entropy_seed: &[u8],
encrypted_share_backup: &EncryptedShareBackup,
identity_provider: &impl IdentityProvider,
) -> Result<(SigningShare, SubShare), ShareBackupRecoveryError> {
let nonce = aes_gcm::Nonce::from_slice(&encrypted_share_backup.nonce);
let cipher = generate_encryption_cipher(entropy_seed, identity_provider);
let signing_share_bytes =
cipher.decrypt(nonce, encrypted_share_backup.signing_share.as_ref())?;
let signing_share = SigningShare::try_from(signing_share_bytes.as_ref())
.map_err(|_| ShareBackupRecoveryError::InvalidSigningShare)?;
let sub_share = SubShare::new(
U256::from_be_bytes(
cipher
.decrypt(nonce, encrypted_share_backup.sub_share.0.as_ref())?
.try_into()
.map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?,
),
U256::from_be_bytes(
cipher
.decrypt(nonce, encrypted_share_backup.sub_share.1.as_ref())?
.try_into()
.map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?,
),
)
.map_err(|_| ShareBackupRecoveryError::InvalidSubShare)?;
Ok((signing_share, sub_share))
}
fn generate_encryption_cipher(
entropy_seed: &[u8],
identity_provider: &impl IdentityProvider,
) -> AesGcm<Aes256, U12> {
let key_bytes = generate_encryption_key(entropy_seed, identity_provider);
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&key_bytes);
Aes256Gcm::new(key)
}
fn generate_encryption_key(
entropy_seed: &[u8],
identity_provider: &impl IdentityProvider,
) -> [u8; 32] {
let entropy = identity_provider.sign(entropy_seed);
let mut output_key = [0u8; 32];
Hkdf::<Sha256>::new(None, &entropy.sig)
.expand(&[], &mut output_key)
.expect("32 is a valid length for Sha256 to output");
output_key
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::Random32Bytes;
use crate::share::SecretShare;
use crate::share_split_reconstruct;
use crate::test_utils::MockECDSAIdentityProvider;
#[test]
fn share_recovery_with_encrypted_backup_works() {
let identity_provider = MockECDSAIdentityProvider::generate();
let entropy_seed = b"Hello, world!";
let secret_share = SecretShare::from(Random32Bytes::generate_mod_q());
let (signing_share, sub_share) =
share_split_reconstruct::split(&secret_share, &identity_provider).unwrap();
let backup_result = backup(entropy_seed, &signing_share, &sub_share, &identity_provider);
assert!(backup_result.is_ok());
let encrypted_share_backup = backup_result.unwrap();
let recover_result = recover(entropy_seed, &encrypted_share_backup, &identity_provider);
assert!(recover_result.is_ok());
let (recovered_signing_share, recovered_sub_share) = recover_result.unwrap();
assert_eq!(
&recovered_signing_share.to_be_bytes(),
&signing_share.to_be_bytes()
);
assert_eq!(recovered_sub_share.as_tuple(), sub_share.as_tuple());
}
#[test]
fn generate_encryption_key_works() {
let identity_provider = MockECDSAIdentityProvider::generate();
let entropy_seed = b"Hello, world!";
let encryption_key = generate_encryption_key(entropy_seed, &identity_provider);
assert_eq!(
encryption_key,
generate_encryption_key(entropy_seed, &identity_provider)
);
assert_ne!(
encryption_key,
generate_encryption_key(entropy_seed, &MockECDSAIdentityProvider::generate())
);
assert_ne!(
encryption_key,
generate_encryption_key(b"Another phrase.", &identity_provider)
);
}
}