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::{
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    use snarkvm::prelude::{
73        Address,
74        Entry,
75        Field,
76        Identifier,
77        Literal,
78        Network,
79        One,
80        Owner,
81        Plaintext,
82        PrivateKey,
83        Scalar,
84        TestRng,
85        U8,
86        Uniform,
87        ViewKey,
88        Zero,
89    };
90
91    use indexmap::IndexMap;
92    use rand::Rng;
93
94    type CurrentNetwork = MainnetV0;
95
96    const ITERATIONS: usize = 1000;
97
98    fn construct_ciphertext<N: Network>(
99        view_key: ViewKey<N>,
100        owner: Owner<N, Plaintext<N>>,
101        rng: &mut TestRng,
102    ) -> Result<Record<N, Ciphertext<N>>> {
103        // Prepare the record.
104        let randomizer = Scalar::rand(rng);
105        let version = match rng.r#gen() {
106            true => U8::<N>::one(),
107            false => U8::<N>::zero(),
108        };
109        let record = Record::<N, Plaintext<N>>::from_plaintext(
110            owner,
111            IndexMap::from_iter(
112                vec![
113                    (Identifier::from_str("a")?, Entry::Private(Plaintext::from(Literal::Field(Field::rand(rng))))),
114                    (Identifier::from_str("b")?, Entry::Private(Plaintext::from(Literal::Scalar(Scalar::rand(rng))))),
115                ]
116                .into_iter(),
117            ),
118            N::g_scalar_multiply(&randomizer),
119            version,
120        )?;
121        // Encrypt the record.
122        let ciphertext = record.encrypt(randomizer)?;
123        // Decrypt the record.
124        assert_eq!(record, ciphertext.decrypt(&view_key)?);
125
126        Ok(ciphertext)
127    }
128
129    #[test]
130    fn test_decryption() {
131        let mut rng = TestRng::default();
132
133        for _ in 0..ITERATIONS {
134            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
135            let view_key = ViewKey::try_from(private_key).unwrap();
136            let address = Address::try_from(private_key).unwrap();
137
138            // Construct the ciphertext.
139            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
140            let ciphertext = construct_ciphertext(view_key, owner, &mut rng).unwrap();
141
142            // Decrypt the ciphertext.
143            let expected_plaintext = ciphertext.decrypt(&view_key).unwrap();
144
145            let decrypt = Decrypt { network: 0, ciphertext: ciphertext.to_string(), view_key: view_key.to_string() };
146            let plaintext = decrypt.parse().unwrap();
147
148            // Check that the decryption is correct.
149            assert_eq!(plaintext, expected_plaintext.to_string());
150        }
151    }
152
153    #[test]
154    fn test_failed_decryption() {
155        let mut rng = TestRng::default();
156
157        // Generate a view key that is unaffiliated with the ciphertext.
158        let incorrect_private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
159        let incorrect_view_key = ViewKey::try_from(incorrect_private_key).unwrap();
160
161        for _ in 0..ITERATIONS {
162            let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
163            let view_key = ViewKey::try_from(private_key).unwrap();
164            let address = Address::try_from(private_key).unwrap();
165
166            // Construct the ciphertext.
167            let owner = Owner::Private(Plaintext::from(Literal::Address(address)));
168            let ciphertext = construct_ciphertext::<CurrentNetwork>(view_key, owner, &mut rng).unwrap();
169
170            // Enforce that the decryption fails.
171            let decrypt =
172                Decrypt { network: 0, ciphertext: ciphertext.to_string(), view_key: incorrect_view_key.to_string() };
173            assert!(decrypt.parse().is_err());
174        }
175    }
176}