use super::*;
pub struct Encryptor<N: Network> {
_phantom: std::marker::PhantomData<N>,
}
impl<N: Network> Encryptor<N> {
pub fn encrypt_private_key_with_secret(private_key: &PrivateKey<N>, secret: &str) -> Result<Ciphertext<N>> {
Self::encrypt_field(&private_key.seed(), secret, "private_key")
}
pub fn decrypt_private_key_with_secret(ciphertext: &Ciphertext<N>, secret: &str) -> Result<PrivateKey<N>> {
let seed = Self::decrypt_field(ciphertext, secret, "private_key")?;
PrivateKey::try_from(seed)
}
fn encrypt_field(field: &Field<N>, secret: &str, domain: &str) -> Result<Ciphertext<N>> {
let domain = Field::<N>::new_domain_separator(domain);
let secret = Field::<N>::new_domain_separator(secret);
let mut rng = rand::thread_rng();
let nonce = Uniform::rand(&mut rng);
let blinding = N::hash_psd2(&[domain, nonce, secret])?;
let key = blinding * field;
let plaintext = Plaintext::<N>::Struct(
indexmap::IndexMap::from_iter(vec![
(Identifier::from_str("key")?, Plaintext::<N>::from(Literal::Field(key))),
(Identifier::from_str("nonce")?, Plaintext::<N>::from(Literal::Field(nonce))),
]),
OnceCell::new(),
);
plaintext.encrypt_symmetric(secret)
}
fn decrypt_field(ciphertext: &Ciphertext<N>, secret: &str, domain: &str) -> Result<Field<N>> {
let domain = Field::<N>::new_domain_separator(domain);
let secret = Field::<N>::new_domain_separator(secret);
let decrypted = ciphertext.decrypt_symmetric(secret)?;
let recovered_key = Self::extract_value(&decrypted, "key")?;
let recovered_nonce = Self::extract_value(&decrypted, "nonce")?;
let recovered_blinding = N::hash_psd2(&[domain, recovered_nonce, secret])?;
Ok(recovered_key / recovered_blinding)
}
fn extract_value(plaintext: &Plaintext<N>, identifier: &str) -> Result<Field<N>> {
let identity = Identifier::from_str(identifier)?;
let value = plaintext.find(&[identity])?;
match value {
Plaintext::<N>::Literal(literal, ..) => match literal {
Literal::Field(recovered_value) => Ok(recovered_value),
_ => Err(anyhow!("Wrong literal type")),
},
_ => Err(anyhow!("Expected literal")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use snarkvm_console::{network::Testnet3 as CurrentNetwork, prelude::TestRng};
#[test]
fn test_encryptor_encrypt_and_decrypt() {
let mut rng = TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let enc = Encryptor::<CurrentNetwork>::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
let recovered_private_key =
Encryptor::<CurrentNetwork>::decrypt_private_key_with_secret(&enc, "mypassword").unwrap();
assert_eq!(private_key, recovered_private_key);
}
#[test]
fn test_encryptor_wrong_private_key_doesnt_decrypt() {
let mut rng = TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let enc = Encryptor::<CurrentNetwork>::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
let recovered_private_key =
Encryptor::<CurrentNetwork>::decrypt_private_key_with_secret(&enc, "wrong_password");
assert!(recovered_private_key.is_err())
}
#[test]
fn test_encryptor_same_secret_doesnt_produce_same_ciphertext_on_different_runs() {
let mut rng = TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let enc = Encryptor::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
let enc2 = Encryptor::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
assert_ne!(enc, enc2);
let recovered_key_1 = Encryptor::decrypt_private_key_with_secret(&enc, "mypassword").unwrap();
let recovered_key_2 = Encryptor::decrypt_private_key_with_secret(&enc, "mypassword").unwrap();
assert_eq!(recovered_key_1, recovered_key_2);
}
#[test]
fn test_encryptor_private_keys_encrypted_with_different_passwords_match() {
let mut rng = TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let enc = Encryptor::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
let enc2 = Encryptor::encrypt_private_key_with_secret(&private_key, "mypassword2").unwrap();
assert_ne!(enc, enc2);
let recovered_key_1 = Encryptor::decrypt_private_key_with_secret(&enc, "mypassword").unwrap();
let recovered_key_2 = Encryptor::decrypt_private_key_with_secret(&enc2, "mypassword2").unwrap();
assert_eq!(recovered_key_1, recovered_key_2);
}
#[test]
fn test_encryptor_different_private_keys_encrypted_with_same_password_dont_match() {
let mut rng = TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let private_key2 = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let enc = Encryptor::encrypt_private_key_with_secret(&private_key, "mypassword").unwrap();
let enc2 = Encryptor::encrypt_private_key_with_secret(&private_key2, "mypassword").unwrap();
assert_ne!(enc, enc2);
let recovered_key_1 = Encryptor::decrypt_private_key_with_secret(&enc, "mypassword").unwrap();
let recovered_key_2 = Encryptor::decrypt_private_key_with_secret(&enc2, "mypassword").unwrap();
assert_ne!(recovered_key_1, recovered_key_2);
}
}