Skip to main content

snarkos_cli/commands/developer/
decrypt.rs

1// Copyright (c) 2019-2025 Provable Inc.
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::{network::Network, program::Ciphertext},
18    prelude::{Record, ViewKey},
19};
20
21use anyhow::{Context, Result, bail};
22use clap::{Parser, builder::NonEmptyStringValueParser};
23use std::str::FromStr;
24use zeroize::Zeroize;
25
26/// Decrypts a record ciphertext.
27#[derive(Debug, Parser, Zeroize)]
28pub struct Decrypt {
29    /// The record ciphertext to decrypt.
30    #[clap(short, long)]
31    ciphertext: String,
32    /// The view key used to decrypt the record ciphertext.
33    #[clap(short, long, value_parser=NonEmptyStringValueParser::default())]
34    view_key: String,
35    /// Sets verbosity of log output. By default, no logs are shown.
36    #[clap(long)]
37    verbosity: Option<u8>,
38}
39
40impl Decrypt {
41    /// Decrypt the record specified by the user.
42    pub fn parse<N: Network>(self) -> Result<String> {
43        // Parse the ciphertext record.
44        let ciphertext_record = Record::<N, Ciphertext<N>>::from_str(&self.ciphertext)
45            .with_context(|| "Failed to parse ciphertext record")?;
46
47        // Parse the account view key.
48        let view_key = ViewKey::<N>::from_str(&self.view_key).with_context(|| "Failed to parse view key")?;
49
50        match ciphertext_record.decrypt(&view_key) {
51            Ok(plaintext_record) => Ok(plaintext_record.to_string()),
52            Err(_) => bail!("Invalid view key for the provided record ciphertext"),
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use snarkvm::prelude::{
61        Address,
62        Entry,
63        Field,
64        Identifier,
65        Literal,
66        MainnetV0,
67        Network,
68        One,
69        Owner,
70        Plaintext,
71        PrivateKey,
72        Scalar,
73        TestRng,
74        U8,
75        Uniform,
76        ViewKey,
77        Zero,
78    };
79
80    use indexmap::IndexMap;
81    use rand::Rng;
82
83    type CurrentNetwork = MainnetV0;
84
85    const ITERATIONS: usize = 1000;
86
87    fn construct_ciphertext<N: Network>(
88        view_key: ViewKey<N>,
89        owner: Owner<N, Plaintext<N>>,
90        rng: &mut TestRng,
91    ) -> Result<Record<N, Ciphertext<N>>> {
92        // Prepare the record.
93        let randomizer = Scalar::rand(rng);
94        let version = match rng.r#gen() {
95            true => U8::<N>::one(),
96            false => U8::<N>::zero(),
97        };
98        let record = Record::<N, Plaintext<N>>::from_plaintext(
99            owner,
100            IndexMap::from_iter(
101                vec![
102                    (Identifier::from_str("a")?, Entry::Private(Plaintext::from(Literal::Field(Field::rand(rng))))),
103                    (Identifier::from_str("b")?, Entry::Private(Plaintext::from(Literal::Scalar(Scalar::rand(rng))))),
104                ]
105                .into_iter(),
106            ),
107            N::g_scalar_multiply(&randomizer),
108            version,
109        )?;
110        // Encrypt the record.
111        let ciphertext = record.encrypt(randomizer)?;
112        // Decrypt the record.
113        assert_eq!(record, ciphertext.decrypt(&view_key)?);
114
115        Ok(ciphertext)
116    }
117
118    #[test]
119    fn test_decryption() {
120        let mut rng = TestRng::default();
121
122        for _ in 0..ITERATIONS {
123            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
124            let view_key = ViewKey::try_from(private_key).unwrap();
125            let address = Address::try_from(private_key).unwrap();
126
127            // Construct the ciphertext.
128            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
129            let ciphertext = construct_ciphertext(view_key, owner, &mut rng).unwrap();
130
131            // Decrypt the ciphertext.
132            let expected_plaintext = ciphertext.decrypt(&view_key).unwrap();
133
134            let decrypt =
135                Decrypt { ciphertext: ciphertext.to_string(), view_key: view_key.to_string(), verbosity: None };
136            let plaintext = decrypt.parse::<CurrentNetwork>().unwrap();
137
138            // Check that the decryption is correct.
139            assert_eq!(plaintext, expected_plaintext.to_string());
140        }
141    }
142
143    #[test]
144    fn test_failed_decryption() {
145        let mut rng = TestRng::default();
146
147        // Generate a view key that is unaffiliated with the ciphertext.
148        let incorrect_private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
149        let incorrect_view_key = ViewKey::try_from(incorrect_private_key).unwrap();
150
151        for _ in 0..ITERATIONS {
152            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
153            let view_key = ViewKey::try_from(private_key).unwrap();
154            let address = Address::try_from(private_key).unwrap();
155
156            // Construct the ciphertext.
157            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
158            let ciphertext = construct_ciphertext::<CurrentNetwork>(view_key, owner, &mut rng).unwrap();
159
160            // Enforce that the decryption fails.
161            let decrypt = Decrypt {
162                ciphertext: ciphertext.to_string(),
163                view_key: incorrect_view_key.to_string(),
164                verbosity: None,
165            };
166            assert!(decrypt.parse::<CurrentNetwork>().is_err());
167        }
168    }
169}