snarkos_cli/commands/developer/
decrypt.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use snarkvm::{
17    console::{
18        network::{CanaryV0, MainnetV0, Network, TestnetV0},
19        program::Ciphertext,
20    },
21    prelude::{Record, ViewKey},
22};
23
24use anyhow::{Result, bail};
25use clap::Parser;
26use std::str::FromStr;
27use zeroize::Zeroize;
28
29/// Decrypts a record ciphertext.
30#[derive(Debug, Parser, Zeroize)]
31pub struct Decrypt {
32    /// Specify the network of the ciphertext to decrypt.
33    #[clap(default_value = "0", long = "network")]
34    pub network: u16,
35    /// The record ciphertext to decrypt.
36    #[clap(short, long)]
37    pub ciphertext: String,
38    /// The view key used to decrypt the record ciphertext.
39    #[clap(short, long)]
40    pub view_key: String,
41}
42
43impl Decrypt {
44    pub fn parse(self) -> Result<String> {
45        // Decrypt the ciphertext for the given network.
46        match self.network {
47            MainnetV0::ID => Self::decrypt_ciphertext::<MainnetV0>(&self.ciphertext, &self.view_key),
48            TestnetV0::ID => Self::decrypt_ciphertext::<TestnetV0>(&self.ciphertext, &self.view_key),
49            CanaryV0::ID => Self::decrypt_ciphertext::<CanaryV0>(&self.ciphertext, &self.view_key),
50            unknown_id => bail!("Unknown network ID ({unknown_id})"),
51        }
52    }
53
54    /// Decrypts the ciphertext record with provided the view key.
55    fn decrypt_ciphertext<N: Network>(ciphertext: &str, view_key: &str) -> Result<String> {
56        // Parse the ciphertext record.
57        let ciphertext_record = Record::<N, Ciphertext<N>>::from_str(ciphertext)?;
58
59        // Parse the account view key.
60        let view_key = ViewKey::<N>::from_str(view_key)?;
61
62        match ciphertext_record.decrypt(&view_key) {
63            Ok(plaintext_record) => Ok(plaintext_record.to_string()),
64            Err(_) => bail!("Invalid view key for the provided record ciphertext"),
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    use indexmap::IndexMap;
74    use snarkvm::prelude::{
75        Address,
76        Entry,
77        Field,
78        Identifier,
79        Literal,
80        Network,
81        Owner,
82        Plaintext,
83        PrivateKey,
84        Scalar,
85        TestRng,
86        Uniform,
87        ViewKey,
88    };
89
90    type CurrentNetwork = MainnetV0;
91
92    const ITERATIONS: usize = 1000;
93
94    fn construct_ciphertext<N: Network>(
95        view_key: ViewKey<N>,
96        owner: Owner<N, Plaintext<N>>,
97        rng: &mut TestRng,
98    ) -> Result<Record<N, Ciphertext<N>>> {
99        // Prepare the record.
100        let randomizer = Scalar::rand(rng);
101        let record = Record::<N, Plaintext<N>>::from_plaintext(
102            owner,
103            IndexMap::from_iter(
104                vec![
105                    (Identifier::from_str("a")?, Entry::Private(Plaintext::from(Literal::Field(Field::rand(rng))))),
106                    (Identifier::from_str("b")?, Entry::Private(Plaintext::from(Literal::Scalar(Scalar::rand(rng))))),
107                ]
108                .into_iter(),
109            ),
110            N::g_scalar_multiply(&randomizer),
111        )?;
112        // Encrypt the record.
113        let ciphertext = record.encrypt(randomizer)?;
114        // Decrypt the record.
115        assert_eq!(record, ciphertext.decrypt(&view_key)?);
116
117        Ok(ciphertext)
118    }
119
120    #[test]
121    fn test_decryption() {
122        let mut rng = TestRng::default();
123
124        for _ in 0..ITERATIONS {
125            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
126            let view_key = ViewKey::try_from(private_key).unwrap();
127            let address = Address::try_from(private_key).unwrap();
128
129            // Construct the ciphertext.
130            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
131            let ciphertext = construct_ciphertext(view_key, owner, &mut rng).unwrap();
132
133            // Decrypt the ciphertext.
134            let expected_plaintext = ciphertext.decrypt(&view_key).unwrap();
135
136            let decrypt = Decrypt { network: 0, ciphertext: ciphertext.to_string(), view_key: view_key.to_string() };
137            let plaintext = decrypt.parse().unwrap();
138
139            // Check that the decryption is correct.
140            assert_eq!(plaintext, expected_plaintext.to_string());
141        }
142    }
143
144    #[test]
145    fn test_failed_decryption() {
146        let mut rng = TestRng::default();
147
148        // Generate a view key that is unaffiliated with the ciphertext.
149        let incorrect_private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
150        let incorrect_view_key = ViewKey::try_from(incorrect_private_key).unwrap();
151
152        for _ in 0..ITERATIONS {
153            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
154            let view_key = ViewKey::try_from(private_key).unwrap();
155            let address = Address::try_from(private_key).unwrap();
156
157            // Construct the ciphertext.
158            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
159            let ciphertext = construct_ciphertext::<CurrentNetwork>(view_key, owner, &mut rng).unwrap();
160
161            // Enforce that the decryption fails.
162            let decrypt =
163                Decrypt { network: 0, ciphertext: ciphertext.to_string(), view_key: incorrect_view_key.to_string() };
164            assert!(decrypt.parse().is_err());
165        }
166    }
167}