Skip to main content

leo_lang/cli/commands/
account.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use leo_ast::NetworkName;
19
20#[cfg(not(feature = "only_testnet"))]
21use snarkvm::prelude::{CanaryV0, MainnetV0};
22use snarkvm::{
23    console::program::{Signature, ToFields, Value},
24    prelude::{Address, Network, PrivateKey, TestnetV0, ViewKey},
25};
26
27use crossterm::ExecutableCommand;
28use itertools::Itertools;
29use rand::SeedableRng;
30use rand_chacha::ChaChaRng;
31use std::{
32    io::{self, Read, Write},
33    path::PathBuf,
34    str::FromStr,
35};
36
37/// Commands to manage Aleo accounts.
38#[derive(Parser, Debug)]
39pub enum Account {
40    /// Generates a new Aleo account
41    New {
42        /// Seed the RNG with a numeric value.
43        #[clap(short = 's', long)]
44        seed: Option<u64>,
45        /// Write the private key to the .env file in the current directory.
46        #[clap(short = 'w', long)]
47        write: bool,
48        /// Print sensitive information (such as private key) discreetly to an alternate screen
49        #[clap(long)]
50        discreet: bool,
51        #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")]
52        network: NetworkName,
53        #[clap(
54            short = 'e',
55            long,
56            help = "Endpoint to record in the generated `.env` file. Only used with `--write`.",
57            default_value = "https://api.explorer.provable.com/v1"
58        )]
59        endpoint: String,
60    },
61    /// Derive an Aleo account from a private key.
62    Import {
63        /// Private key plaintext
64        private_key: Option<String>,
65        /// Write the private key to the .env file in the current directory.
66        #[clap(short = 'w', long)]
67        write: bool,
68        /// Print sensitive information (such as private key) discreetly to an alternate screen
69        #[clap(long)]
70        discreet: bool,
71        #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")]
72        network: NetworkName,
73        #[clap(
74            short = 'e',
75            long,
76            help = "Endpoint to record in the generated `.env` file. Only used with `--write`.",
77            default_value = "https://api.explorer.provable.com/v1"
78        )]
79        endpoint: String,
80    },
81    /// Sign a message using your Aleo private key.
82    Sign {
83        /// Specify the account private key
84        #[clap(long = "private-key")]
85        private_key: Option<String>,
86        /// Specify the path to a file containing the account private key
87        #[clap(long = "private-key-file")]
88        private_key_file: Option<String>,
89        /// Message (Aleo value) to sign
90        #[clap(short = 'm', long)]
91        message: String,
92        /// When enabled, parses the message as bytes instead of Aleo literals
93        #[clap(short = 'r', long)]
94        raw: bool,
95        #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")]
96        network: NetworkName,
97    },
98    /// Verify a message from an Aleo address.
99    Verify {
100        /// Address to use for verification
101        #[clap(short = 'a', long)]
102        address: String,
103        /// Signature to verify
104        #[clap(short = 's', long)]
105        signature: String,
106        /// Message (Aleo value) to verify the signature against
107        #[clap(short = 'm', long)]
108        message: String,
109        /// When enabled, parses the message as bytes instead of Aleo literals
110        #[clap(short = 'r', long)]
111        raw: bool,
112        #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")]
113        network: NetworkName,
114    },
115    /// Decrupt record ciphertext using your Aleo private key or view key.
116    Decrypt {
117        /// Specify the account key
118        #[clap(short = 'k', help = "Private key or view key to use for decryption")]
119        key: Option<String>,
120        /// Specify the path to a file containing the account private key
121        #[clap(short = 'f', help = "Path to a file containing the private key or view key")]
122        key_file: Option<String>,
123        /// The ciphertext to decrypt
124        #[clap(short = 'c', long)]
125        ciphertext: String,
126        #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")]
127        network: NetworkName,
128    },
129}
130
131impl Command for Account {
132    type Input = ();
133    type Output = ();
134
135    fn prelude(&self, _: Context) -> Result<Self::Input>
136    where
137        Self: Sized,
138    {
139        Ok(())
140    }
141
142    fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output>
143    where
144        Self: Sized,
145    {
146        match self {
147            Account::New { seed, write, discreet, network, endpoint } => match network {
148                NetworkName::TestnetV0 => {
149                    generate_new_account::<TestnetV0>(network, seed, write, discreet, &ctx, endpoint)
150                }
151                NetworkName::MainnetV0 => {
152                    #[cfg(feature = "only_testnet")]
153                    panic!("Mainnet chosen with only_testnet feature");
154                    #[cfg(not(feature = "only_testnet"))]
155                    generate_new_account::<MainnetV0>(network, seed, write, discreet, &ctx, endpoint)
156                }
157                NetworkName::CanaryV0 => {
158                    #[cfg(feature = "only_testnet")]
159                    panic!("Canary chosen with only_testnet feature");
160                    #[cfg(not(feature = "only_testnet"))]
161                    generate_new_account::<CanaryV0>(network, seed, write, discreet, &ctx, endpoint)
162                }
163            }?,
164            Account::Import { private_key, write, discreet, network, endpoint } => match network {
165                NetworkName::TestnetV0 => {
166                    import_account::<TestnetV0>(network, private_key, write, discreet, &ctx, endpoint)
167                }
168                NetworkName::MainnetV0 => {
169                    #[cfg(feature = "only_testnet")]
170                    panic!("Mainnet chosen with only_testnet feature");
171                    #[cfg(not(feature = "only_testnet"))]
172                    import_account::<MainnetV0>(network, private_key, write, discreet, &ctx, endpoint)
173                }
174                NetworkName::CanaryV0 => {
175                    #[cfg(feature = "only_testnet")]
176                    panic!("Canary chosen with only_testnet feature");
177                    #[cfg(not(feature = "only_testnet"))]
178                    import_account::<CanaryV0>(network, private_key, write, discreet, &ctx, endpoint)
179                }
180            }?,
181            Self::Sign { message, raw, private_key, private_key_file, network } => {
182                let result = match network {
183                    NetworkName::TestnetV0 => sign_message::<TestnetV0>(message, raw, private_key, private_key_file),
184                    NetworkName::MainnetV0 => {
185                        #[cfg(feature = "only_testnet")]
186                        panic!("Mainnet chosen with only_testnet feature");
187                        #[cfg(not(feature = "only_testnet"))]
188                        sign_message::<MainnetV0>(message, raw, private_key, private_key_file)
189                    }
190                    NetworkName::CanaryV0 => {
191                        #[cfg(feature = "only_testnet")]
192                        panic!("Canary chosen with only_testnet feature");
193                        #[cfg(not(feature = "only_testnet"))]
194                        sign_message::<CanaryV0>(message, raw, private_key, private_key_file)
195                    }
196                }?;
197                println!("{result}")
198            }
199            Self::Verify { address, signature, message, raw, network } => {
200                let result = match network {
201                    NetworkName::TestnetV0 => verify_message::<TestnetV0>(address, signature, message, raw),
202                    NetworkName::MainnetV0 => {
203                        #[cfg(feature = "only_testnet")]
204                        panic!("Mainnet chosen with only_testnet feature");
205                        #[cfg(not(feature = "only_testnet"))]
206                        verify_message::<MainnetV0>(address, signature, message, raw)
207                    }
208                    NetworkName::CanaryV0 => {
209                        #[cfg(feature = "only_testnet")]
210                        panic!("Canary chosen with only_testnet feature");
211                        #[cfg(not(feature = "only_testnet"))]
212                        verify_message::<CanaryV0>(address, signature, message, raw)
213                    }
214                }?;
215                println!("{result}")
216            }
217            Self::Decrypt { key, key_file, ciphertext, network } => {
218                let result = match network {
219                    NetworkName::TestnetV0 => decrypt_ciphertext::<TestnetV0>(key, key_file, &ciphertext),
220                    NetworkName::MainnetV0 => {
221                        #[cfg(feature = "only_testnet")]
222                        panic!("Mainnet chosen with only_testnet feature");
223                        #[cfg(not(feature = "only_testnet"))]
224                        decrypt_ciphertext::<MainnetV0>(key, key_file, &ciphertext)
225                    }
226                    NetworkName::CanaryV0 => {
227                        #[cfg(feature = "only_testnet")]
228                        panic!("Canary chosen with only_testnet feature");
229                        #[cfg(not(feature = "only_testnet"))]
230                        decrypt_ciphertext::<CanaryV0>(key, key_file, &ciphertext)
231                    }
232                }?;
233                println!("{result}")
234            }
235        }
236        Ok(())
237    }
238}
239
240// Helper functions
241
242// Generate a new account.
243fn generate_new_account<N: Network>(
244    network: NetworkName,
245    seed: Option<u64>,
246    write: bool,
247    discreet: bool,
248    ctx: &Context,
249    endpoint: String,
250) -> Result<()> {
251    // Sample a new Aleo account.
252    let private_key = match seed {
253        // Recover the field element deterministically.
254        Some(seed) => PrivateKey::<N>::new(&mut ChaChaRng::seed_from_u64(seed)),
255        // Sample a random field element.
256        None => PrivateKey::new(&mut ChaChaRng::from_rng(&mut rand::rng())),
257    }
258    .map_err(crate::errors::failed_to_parse_seed)?;
259
260    // Derive the view key and address and print to stdout.
261    print_keys(private_key, discreet)?;
262
263    // Save key data to .env file.
264    if write {
265        write_to_env_file(network, private_key, ctx, endpoint)?;
266    }
267    Ok(())
268}
269
270// Import an account.
271fn import_account<N: Network>(
272    network: NetworkName,
273    private_key: Option<String>,
274    write: bool,
275    discreet: bool,
276    ctx: &Context,
277    endpoint: String,
278) -> Result<()> {
279    let priv_key = match discreet {
280        true => {
281            let private_key_input = rpassword::prompt_password("Please enter your private key: ").unwrap();
282            FromStr::from_str(&private_key_input).map_err(crate::errors::failed_to_parse_private_key)?
283        }
284        false => match private_key {
285            Some(private_key) => FromStr::from_str(&private_key).map_err(crate::errors::failed_to_parse_private_key)?,
286            None => {
287                return Err(crate::errors::failed_to_execute_account(
288                    "Missing private key argument. Provide a private key or use the '--discreet' flag to enter it securely.",
289                )
290                .into());
291            }
292        },
293    };
294
295    // Derive the view key and address and print to stdout.
296    print_keys::<N>(priv_key, discreet)?;
297
298    // Save key data to .env file.
299    if write {
300        write_to_env_file::<N>(network, priv_key, ctx, endpoint)?;
301    }
302
303    Ok(())
304}
305
306// Print keys as a formatted string without log level.
307fn print_keys<N: Network>(private_key: PrivateKey<N>, discreet: bool) -> Result<()> {
308    let view_key = ViewKey::try_from(&private_key)?;
309    let address = Address::<N>::try_from(&view_key)?;
310
311    if !discreet {
312        println!(
313            "\n {:>12}  {private_key}\n {:>12}  {view_key}\n {:>12}  {address}\n",
314            "Private Key".cyan().bold(),
315            "View Key".cyan().bold(),
316            "Address".cyan().bold(),
317        );
318        return Ok(());
319    }
320    display_string_discreetly(
321        &private_key.to_string(),
322        "### Do not share or lose this private key! Press any key to complete. ###",
323    )?;
324    println!("\n {:>12}  {view_key}\n {:>12}  {address}\n", "View Key".cyan().bold(), "Address".cyan().bold(),);
325
326    Ok(())
327}
328
329// Sign a message with an Aleo private key
330pub(crate) fn sign_message<N: Network>(
331    message: String,
332    raw: bool,
333    private_key: Option<String>,
334    private_key_file: Option<String>,
335) -> Result<String> {
336    // Get the private key string.
337    let private_key_string = get_key_string(private_key, private_key_file, &["PRIVATE_KEY"])?;
338
339    // Parse the private key.
340    let private_key_string = private_key_string.trim();
341    let private_key = PrivateKey::<N>::from_str(private_key_string)
342        .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid private key"))?;
343
344    // Sample a random field element.
345    let mut rng = ChaChaRng::from_rng(&mut rand::rng());
346
347    // Sign the message
348    let signature = if raw {
349        private_key.sign_bytes(message.as_bytes(), &mut rng)
350    } else {
351        let fields = Value::<N>::from_str(&message)?
352            .to_fields()
353            .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid Aleo value"))?;
354        private_key.sign(&fields, &mut rng)
355    }
356    .map_err(|_| crate::errors::cli_runtime_error("Failed to sign the message"))?
357    .to_string();
358    // Return the signature as a string
359    Ok(signature)
360}
361
362// Verify a signature with an Aleo address
363pub(crate) fn verify_message<N: Network>(
364    address: String,
365    signature: String,
366    message: String,
367    raw: bool,
368) -> Result<String> {
369    // Parse the address.
370    let address = Address::<N>::from_str(&address)?;
371
372    let signature = Signature::<N>::from_str(&signature)
373        .map_err(|e| crate::errors::cli_invalid_input(format!("Failed to parse a valid signature: {e}")))?;
374
375    // Verify the signature
376    let verified = if raw {
377        signature.verify_bytes(&address, message.as_bytes())
378    } else {
379        let fields = Value::<N>::from_str(&message)?
380            .to_fields()
381            .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid Aleo value"))?;
382        signature.verify(&address, &fields)
383    };
384
385    // Return the verification result
386    match verified {
387        true => Ok("✅ The signature is valid".to_string()),
388        false => Err(crate::errors::cli_runtime_error("❌ The signature is invalid"))?,
389    }
390}
391
392// Decrypt a record ciphertext using a private key or view key.
393pub(crate) fn decrypt_ciphertext<N: Network>(
394    key: Option<String>,
395    key_file: Option<String>,
396    ciphertext: &str,
397) -> Result<String> {
398    // Get the key string.
399    let key_string = get_key_string(key, key_file, &["PRIVATE_KEY", "VIEW_KEY"])?;
400
401    // Parse the key.
402    let key_string = key_string.trim();
403    let view_key = if key_string.starts_with("APrivateKey1") {
404        // If the key starts with "APrivateKey1", treat it as a private key.
405        let private_key = PrivateKey::<N>::from_str(key_string)
406            .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid private key"))?;
407        // Convert the private key to a view key.
408        ViewKey::<N>::try_from(&private_key)
409            .map_err(|_| crate::errors::cli_invalid_input("Failed to convert private key to view key"))?
410    } else if key_string.starts_with("AViewKey1") {
411        // If the key starts with "AViewKey1", treat it as a view key.
412        ViewKey::<N>::from_str(key_string)
413            .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid view key"))?
414    } else {
415        // If the key is neither, return an error.
416        Err(crate::errors::cli_invalid_input("Invalid key format. Expected a private or view key."))?
417    };
418
419    // Parse the ciphertext as record ciphertext.
420    let record_ciphertext = Record::<N, Ciphertext<N>>::from_str(ciphertext)
421        .map_err(|_| crate::errors::cli_invalid_input("Failed to parse a valid record ciphertext"))?;
422
423    // Decrypt the record.
424    let decrypted_value = record_ciphertext
425        .decrypt(&view_key)
426        .map_err(|_| crate::errors::cli_runtime_error("Failed to decrypt the record ciphertext"))?;
427
428    // Return the decrypted value as a string.
429    Ok(decrypted_value.to_string())
430}
431
432// A helper function to get the key string from the environment or file.
433fn get_key_string(key: Option<String>, key_file: Option<String>, env_vars: &[&'static str]) -> Result<String> {
434    match (key, key_file) {
435        (Some(key), None) => Ok(key),
436        (None, Some(key_file)) => {
437            let path = key_file
438                .parse::<PathBuf>()
439                .map_err(|e| crate::errors::cli_invalid_input(format!("Invalid path - {e}")))?;
440            std::fs::read_to_string(path).map_err(|e| crate::errors::failed_to_read_file(e).into())
441        }
442        (None, None) => {
443            // Attempt to pull any of the environment variables
444            env_vars.iter().find_map(|&var| std::env::var(var).ok()).ok_or_else(|| {
445                crate::errors::cli_invalid_input(format!(
446                    "Missing the '--key', '--key-file', or the following environment variables: '{}'",
447                    env_vars.iter().format(",")
448                ))
449                .into()
450            })
451        }
452        (Some(_), Some(_)) => {
453            Err(crate::errors::cli_invalid_input("Cannot specify both the '--key' and '--key-file' flags").into())
454        }
455    }
456}
457
458// Write the network and private key to an .env file in project directory.
459fn write_to_env_file<N: Network>(
460    network: NetworkName,
461    private_key: PrivateKey<N>,
462    ctx: &Context,
463    endpoint: String,
464) -> Result<()> {
465    let program_dir = ctx.dir()?;
466    let env_path = program_dir.join(".env");
467    std::fs::write(env_path, format!("NETWORK={network}\nPRIVATE_KEY={private_key}\nENDPOINT={endpoint}\n"))
468        .map_err(crate::errors::io_error_env_file)?;
469    tracing::info!("✅ Private Key written to {}", program_dir.join(".env").display());
470    Ok(())
471}
472
473/// Print the string to an alternate screen, so that the string won't been printed to the terminal.
474fn display_string_discreetly(discreet_string: &str, continue_message: &str) -> Result<()> {
475    use crossterm::{
476        style::Print,
477        terminal::{EnterAlternateScreen, LeaveAlternateScreen},
478    };
479    let mut stdout = io::stdout();
480    stdout.execute(EnterAlternateScreen).unwrap();
481    // print msg on the alternate screen
482    stdout.execute(Print(format!("{discreet_string}\n{continue_message}"))).unwrap();
483    stdout.flush().unwrap();
484    wait_for_keypress();
485    stdout.execute(LeaveAlternateScreen).unwrap();
486    Ok(())
487}
488
489fn wait_for_keypress() {
490    let mut single_key = [0u8];
491    std::io::stdin().read_exact(&mut single_key).unwrap();
492}
493
494#[cfg(test)]
495mod tests {
496    use super::{decrypt_ciphertext, sign_message, verify_message};
497    use snarkvm::{
498        prelude::{
499            Address,
500            Identifier,
501            Network,
502            Plaintext,
503            PrivateKey,
504            Process,
505            ProgramID,
506            Record,
507            Scalar,
508            TestRng,
509            U8,
510            Uniform,
511            ViewKey,
512        },
513        synthesizer::program::StackTrait,
514    };
515    use std::str::FromStr;
516
517    type CurrentNetwork = snarkvm::prelude::MainnetV0;
518
519    #[test]
520    fn test_signature_raw() {
521        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
522        let message = "Hello, world!".to_string();
523        assert!(sign_message::<CurrentNetwork>(message, true, Some(key), None).is_ok());
524    }
525
526    #[test]
527    fn test_signature() {
528        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
529        let message = "5field".to_string();
530        assert!(sign_message::<CurrentNetwork>(message, false, Some(key), None).is_ok());
531    }
532
533    #[test]
534    fn test_signature_fail() {
535        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
536        let message = "not a literal value".to_string();
537        assert!(sign_message::<CurrentNetwork>(message, false, Some(key), None).is_err());
538    }
539
540    #[test]
541    fn test_verify_raw() {
542        // test signature of "Hello, world!"
543        let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j".to_string();
544        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
545        let message = "Hello, world!".to_string();
546        assert!(verify_message::<CurrentNetwork>(address.clone(), signature, message, true).is_ok());
547
548        // test signature of "Hello, world!" against the message "Different Message"
549        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
550        let message = "Different Message".to_string();
551        assert!(verify_message::<CurrentNetwork>(address.clone(), signature, message, true).is_err());
552
553        // test signature of "Hello, world!" against the wrong address
554        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
555        let message = "Hello, world!".to_string();
556        let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".to_string();
557        assert!(verify_message::<CurrentNetwork>(wrong_address, signature, message, true).is_err());
558
559        // test a valid signature of "Different Message"
560        let signature = "sign1424ztyt9hcm77nq450gvdszrvtg9kvhc4qadg4nzy9y0ah7wdqq7t36cxal42p9jj8e8pjpmc06lfev9nvffcpqv0cxwyr0a2j2tjqlesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk3yrr50".to_string();
561        let message = "Different Message".to_string();
562        assert!(verify_message::<CurrentNetwork>(address, signature, message, true).is_ok());
563    }
564
565    #[test]
566    fn test_verify() {
567        // test signature of 5u8
568        let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j".to_string();
569        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
570        let message = "5field".to_string();
571        assert!(verify_message::<CurrentNetwork>(address.clone(), signature, message, false).is_ok());
572
573        // test signature of 5u8 against the message 10u8
574        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
575        let message = "10field".to_string();
576        assert!(verify_message::<CurrentNetwork>(address.clone(), signature, message, false).is_err());
577
578        // test signature of 5u8 against the wrong address
579        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
580        let message = "5field".to_string();
581        let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".to_string();
582        assert!(verify_message::<CurrentNetwork>(wrong_address, signature, message, false).is_err());
583
584        // test a valid signature of 10u8
585        let signature = "sign1t9v2t5tljk8pr5t6vkcqgkus0a3v69vryxmfrtwrwg0xtj7yv5qj2nz59e5zcyl50w23lhntxvt6vzeqfyu6dt56698zvfj2l6lz6q0esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk8rh9kt".to_string();
586        let message = "10field".to_string();
587        assert!(verify_message::<CurrentNetwork>(address, signature, message, false).is_ok());
588    }
589
590    #[test]
591    fn test_decrypt() -> anyhow::Result<()> {
592        // Initialize an RNG.
593        let mut rng = &mut TestRng::default();
594
595        // Test decryption with a private key
596        let private_key =
597            PrivateKey::<CurrentNetwork>::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH")?;
598        let private_key_string = private_key.to_string();
599        let view_key = ViewKey::<CurrentNetwork>::try_from(&private_key)?;
600        let view_key_string = view_key.to_string();
601        let address = Address::<CurrentNetwork>::try_from(&view_key)?;
602
603        // Create a random record.
604        let process = Process::<CurrentNetwork>::load()?;
605        let stack = process.get_stack(ProgramID::from_str("credits.aleo")?)?;
606        let randomizer = Scalar::<CurrentNetwork>::rand(rng);
607        let nonce = CurrentNetwork::g_scalar_multiply(&randomizer);
608        let record = stack.sample_record(&address, &Identifier::from_str("credits").unwrap(), nonce, &mut rng)?;
609        let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_plaintext(
610            record.owner().clone(),
611            record.data().clone(),
612            nonce,
613            U8::new(u8::rand(rng) % 2),
614        )?;
615        let record_string = record.to_string();
616        let ciphertext = record.encrypt(randomizer)?;
617        let ciphertext_string = ciphertext.to_string();
618
619        // Test decryption with the private key
620        let candidate = decrypt_ciphertext::<CurrentNetwork>(Some(private_key_string), None, &ciphertext_string)?;
621        assert_eq!(candidate, record_string);
622
623        // Test decryption with a view key
624        let candidate = decrypt_ciphertext::<CurrentNetwork>(Some(view_key_string), None, &ciphertext_string)?;
625        assert_eq!(candidate, record_string);
626
627        Ok(())
628    }
629}