use super::CurrentNetwork;
use snarkvm::{
console::program::Ciphertext,
prelude::{Record, ViewKey},
};
use anyhow::{bail, Result};
use clap::Parser;
use std::str::FromStr;
#[derive(Debug, Parser)]
pub struct Decrypt {
#[clap(short, long)]
pub ciphertext: String,
#[clap(short, long)]
pub view_key: String,
}
impl Decrypt {
pub fn parse(self) -> Result<String> {
Self::decrypt_ciphertext(&self.ciphertext, &self.view_key)
}
fn decrypt_ciphertext(ciphertext: &str, view_key: &str) -> Result<String> {
let ciphertext_record = Record::<CurrentNetwork, Ciphertext<CurrentNetwork>>::from_str(ciphertext)?;
let view_key = ViewKey::<CurrentNetwork>::from_str(view_key)?;
match ciphertext_record.decrypt(&view_key) {
Ok(plaintext_record) => Ok(plaintext_record.to_string()),
Err(_) => bail!("Invalid view key for the provided record ciphertext"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use indexmap::IndexMap;
use snarkvm::prelude::{
Address,
Entry,
Field,
Identifier,
Literal,
Network,
Owner,
Plaintext,
PrivateKey,
Scalar,
TestRng,
Uniform,
ViewKey,
};
const ITERATIONS: usize = 1000;
fn construct_ciphertext<N: Network>(
view_key: ViewKey<N>,
owner: Owner<N, Plaintext<N>>,
rng: &mut TestRng,
) -> Result<Record<N, Ciphertext<N>>> {
let randomizer = Scalar::rand(rng);
let record = Record::<N, Plaintext<N>>::from_plaintext(
owner,
IndexMap::from_iter(
vec![
(Identifier::from_str("a")?, Entry::Private(Plaintext::from(Literal::Field(Field::rand(rng))))),
(Identifier::from_str("b")?, Entry::Private(Plaintext::from(Literal::Scalar(Scalar::rand(rng))))),
]
.into_iter(),
),
N::g_scalar_multiply(&randomizer),
)?;
let ciphertext = record.encrypt(randomizer)?;
assert_eq!(record, ciphertext.decrypt(&view_key)?);
Ok(ciphertext)
}
#[test]
fn test_decryption() {
let mut rng = TestRng::default();
for _ in 0..ITERATIONS {
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let view_key = ViewKey::try_from(private_key).unwrap();
let address = Address::try_from(private_key).unwrap();
let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
let ciphertext = construct_ciphertext(view_key, owner, &mut rng).unwrap();
let expected_plaintext = ciphertext.decrypt(&view_key).unwrap();
let decrypt = Decrypt { ciphertext: ciphertext.to_string(), view_key: view_key.to_string() };
let plaintext = decrypt.parse().unwrap();
assert_eq!(plaintext, expected_plaintext.to_string());
}
}
#[ignore]
#[test]
fn test_failed_decryption() {
let mut rng = TestRng::default();
let incorrect_private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let incorrect_view_key = ViewKey::try_from(incorrect_private_key).unwrap();
for _ in 0..ITERATIONS {
let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
let view_key = ViewKey::try_from(private_key).unwrap();
let address = Address::try_from(private_key).unwrap();
let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
let ciphertext = construct_ciphertext::<CurrentNetwork>(view_key, owner, &mut rng).unwrap();
let decrypt = Decrypt { ciphertext: ciphertext.to_string(), view_key: incorrect_view_key.to_string() };
assert!(decrypt.parse().is_err());
}
}
}