snarkos_cli/commands/
account.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 crate::helpers::args::{network_id_parser, parse_private_key};
17
18mod sign;
19use sign::Sign;
20
21use snarkvm::console::{
22    account::{Address, PrivateKey, Signature},
23    network::{CanaryV0, MainnetV0, Network, TestnetV0},
24    prelude::{Environment, Uniform},
25    program::{ToFields, Value},
26    types::Field,
27};
28
29use anyhow::{Result, anyhow, bail};
30use clap::Parser;
31use colored::Colorize;
32use core::str::FromStr;
33use crossterm::ExecutableCommand;
34use rand::SeedableRng;
35use rand_chacha::ChaChaRng;
36use rayon::prelude::*;
37use std::{
38    fs::File,
39    io::{Read, Write},
40};
41
42use zeroize::Zeroize;
43
44/// Commands to manage Aleo accounts.
45#[derive(Debug, Parser, Zeroize)]
46#[command(
47    // Use kebab-case for all arguments (e.g., use the `private-key` flag for the `private_key` field).
48    // This is already the default, but we specify it in case clap's default changes in the future.
49    rename_all = "kebab-case",
50)]
51pub enum Account {
52    /// Generates a new Aleo account
53    New {
54        /// Specify the network to create an execution for.
55        /// [options: 0 = mainnet, 1 = testnet, 2 = canary]
56        #[clap(long, default_value_t=MainnetV0::ID, long, value_parser = network_id_parser())]
57        network: u16,
58        /// Seed the RNG with a numeric value
59        #[clap(short = 's', long)]
60        seed: Option<String>,
61        /// Try until an address with the vanity string is found
62        #[clap(short = 'v', long)]
63        vanity: Option<String>,
64        /// Print sensitive information (such as the private key) discreetly in an alternate screen
65        #[clap(long)]
66        discreet: bool,
67        /// Specify the path to a file where to save the account in addition to printing it
68        #[clap(long)]
69        save_to_file: Option<String>,
70    },
71    Sign(Sign),
72    Verify {
73        /// Specify the network to create an execution for.
74        /// [options: 0 = mainnet, 1 = testnet, 2 = canary]
75        #[clap(long, default_value_t=MainnetV0::ID, long, value_parser = network_id_parser())]
76        network: u16,
77        /// Address to use for verification
78        #[clap(short = 'a', long)]
79        address: String,
80        /// Signature to verify
81        #[clap(short = 's', long)]
82        signature: String,
83        /// Message (Aleo value) to verify the signature against
84        #[clap(short = 'm', long)]
85        message: String,
86        /// When enabled, parses the message as bytes instead of Aleo literals
87        #[clap(short = 'r', long)]
88        raw: bool,
89    },
90}
91
92/// Parse a raw Aleo input into fields
93fn aleo_literal_to_fields<N: Network>(input: &str) -> Result<Vec<Field<N>>> {
94    Value::<N>::from_str(input)?.to_fields()
95}
96
97impl Account {
98    /// Run a account command
99    pub fn parse(self) -> Result<String> {
100        match self {
101            Self::New { network, seed, vanity, discreet, save_to_file } => {
102                // Ensure only the seed or the vanity string is specified.
103                if seed.is_some() && vanity.is_some() {
104                    bail!("Cannot specify both the '--seed' and '--vanity' flags");
105                }
106
107                if save_to_file.is_some() && vanity.is_some() {
108                    bail!("Cannot specify both the '--save-to-file' and '--vanity' flags");
109                }
110
111                if save_to_file.is_some() && discreet {
112                    bail!("Cannot specify both the '--save-to-file' and '--discreet' flags");
113                }
114
115                match vanity {
116                    // Generate a vanity account for the specified network.
117                    Some(vanity) => match network {
118                        MainnetV0::ID => Self::new_vanity::<MainnetV0>(vanity.as_str(), discreet),
119                        TestnetV0::ID => Self::new_vanity::<TestnetV0>(vanity.as_str(), discreet),
120                        CanaryV0::ID => Self::new_vanity::<CanaryV0>(vanity.as_str(), discreet),
121                        unknown_id => bail!("Unknown network ID ({unknown_id})"),
122                    },
123                    // Generate a seeded account for the specified network.
124                    None => match network {
125                        MainnetV0::ID => Self::new_seeded::<MainnetV0>(seed, discreet, save_to_file),
126                        TestnetV0::ID => Self::new_seeded::<TestnetV0>(seed, discreet, save_to_file),
127                        CanaryV0::ID => Self::new_seeded::<CanaryV0>(seed, discreet, save_to_file),
128                        unknown_id => bail!("Unknown network ID ({unknown_id})"),
129                    },
130                }
131            }
132            Self::Sign(sign) => sign.execute(),
133
134            Self::Verify { network, address, signature, message, raw } => {
135                // Verify the signature for the specified network.
136                match network {
137                    MainnetV0::ID => Self::verify::<MainnetV0>(address, signature, message, raw),
138                    TestnetV0::ID => Self::verify::<TestnetV0>(address, signature, message, raw),
139                    CanaryV0::ID => Self::verify::<CanaryV0>(address, signature, message, raw),
140                    unknown_id => bail!("Unknown network ID ({unknown_id})"),
141                }
142            }
143        }
144    }
145
146    /// Generates a new Aleo account with the given vanity string.
147    fn new_vanity<N: Network>(vanity: &str, discreet: bool) -> Result<String> {
148        // A closure to generate a new Aleo account.
149        let sample_account = || snarkos_account::Account::<N>::new(&mut rand::thread_rng());
150
151        const ITERATIONS: u128 = u16::MAX as u128;
152        const ITERATIONS_STR: &str = "65,535";
153
154        // Ensure the vanity string is valid.
155        if !crate::helpers::is_in_bech32m_charset(vanity) {
156            bail!(
157                "The vanity string '{vanity}' contains invalid bech32m characters. Try using characters from the bech32m character set: {}",
158                crate::helpers::BECH32M_CHARSET
159            );
160        }
161
162        // Output a message if the character set is more than 4 characters.
163        if vanity.len() > 4 {
164            let message =
165                format!(" The vanity string '{vanity}' contains 5 or more characters and will take a while to find.\n");
166            println!("{}", message.yellow());
167        }
168
169        loop {
170            // Initialize a timer.
171            let timer = std::time::Instant::now();
172
173            // Generates bech32m addresses in parallel until one is found that
174            // includes the desired vanity string at the start or end of the address.
175            let account = (0..ITERATIONS).into_par_iter().find_map_any(|_| {
176                // Initialize the result.
177                let mut account = None;
178                // Sample a random account.
179                if let Ok(candidate) = sample_account() {
180                    // Encode the address as a bech32m string.
181                    let address = candidate.address().to_string();
182                    // Set the candidate if the address includes the desired vanity string
183                    // at the start or end of the address.
184                    if crate::helpers::has_vanity_string(&address, vanity) {
185                        account = Some(candidate);
186                    }
187                }
188                // Return the result.
189                account
190            });
191
192            // Return the result if a candidate was found.
193            if let Some(account) = account {
194                println!(); // Add a newline for formatting.
195                if !discreet {
196                    return Ok(account.to_string());
197                }
198                display_string_discreetly(
199                    &format!("{:>12}  {}", "Private Key".cyan().bold(), account.private_key()),
200                    "### Do not share or lose this private key! Press any key to complete. ###",
201                )
202                .unwrap();
203                let account_info = format!(
204                    " {:>12}  {}\n {:>12}  {}",
205                    "View Key".cyan().bold(),
206                    account.view_key(),
207                    "Address".cyan().bold(),
208                    account.address()
209                );
210                return Ok(account_info);
211            } else {
212                let rate = ITERATIONS / timer.elapsed().as_millis();
213                let rate = format!("[{rate} a/ms]");
214                println!(" {} Sampled {ITERATIONS_STR} accounts, searching...", rate.dimmed());
215            }
216        }
217    }
218
219    /// Generates a new Aleo account with an optional seed.
220    fn new_seeded<N: Network>(seed: Option<String>, discreet: bool, save_to_file: Option<String>) -> Result<String> {
221        // Recover the seed.
222        let seed = match seed {
223            // Recover the field element deterministically.
224            Some(seed) => {
225                Field::new(<N as Environment>::Field::from_str(&seed).map_err(|e| anyhow!("Invalid seed - {e}"))?)
226            }
227            // Sample a random field element.
228            None => Field::rand(&mut ChaChaRng::from_entropy()),
229        };
230        // Recover the private key from the seed as a field element.
231        let private_key =
232            PrivateKey::try_from(seed).map_err(|_| anyhow!("Failed to convert the seed into a valid private key"))?;
233        // Construct the account.
234        let account = snarkos_account::Account::<N>::try_from(private_key)?;
235        // Save to file in addition to printing it back to the user
236        if let Some(path) = save_to_file {
237            crate::check_parent_permissions(&path)?;
238            let mut file = File::create_new(path)?;
239            file.write_all(account.private_key().to_string().as_bytes())?;
240            crate::set_user_read_only(&file)?;
241        }
242        // Print the new Aleo account.
243        if !discreet {
244            return Ok(account.to_string());
245        }
246        display_string_discreetly(
247            &format!("{:>12}  {}", "Private Key".cyan().bold(), account.private_key()),
248            "### Do not share or lose this private key! Press any key to complete. ###",
249        )
250        .unwrap();
251        let account_info = format!(
252            " {:>12}  {}\n {:>12}  {}",
253            "View Key".cyan().bold(),
254            account.view_key(),
255            "Address".cyan().bold(),
256            account.address()
257        );
258        Ok(account_info)
259    }
260
261    // Verify a signature with an Aleo address
262    fn verify<N: Network>(address: String, signature: String, message: String, raw: bool) -> Result<String> {
263        // Parse the address
264        let address = Address::<N>::from_str(&address).map_err(|_| anyhow!("Failed to parse a valid address"))?;
265        // Parse the signature
266        let signature =
267            Signature::<N>::from_str(&signature).map_err(|_| anyhow!("Failed to parse a valid signature"))?;
268        // Verify the signature
269        let verified = if raw {
270            signature.verify_bytes(&address, message.as_bytes())
271        } else {
272            let fields =
273                aleo_literal_to_fields(&message).map_err(|_| anyhow!("Failed to parse a valid Aleo literal"))?;
274            signature.verify(&address, &fields)
275        };
276
277        // Return the verification result
278        match verified {
279            true => Ok("✅ The signature is valid".to_string()),
280            false => bail!("❌ The signature is invalid"),
281        }
282    }
283}
284
285// Print the string to an alternate screen, so that the string won't been printed to the terminal.
286fn display_string_discreetly(discreet_string: &str, continue_message: &str) -> Result<()> {
287    use crossterm::{
288        style::Print,
289        terminal::{EnterAlternateScreen, LeaveAlternateScreen},
290    };
291    let mut stdout = std::io::stdout();
292    stdout.execute(EnterAlternateScreen)?;
293    // print msg on the alternate screen
294    stdout.execute(Print(format!("{discreet_string}\n{continue_message}")))?;
295    stdout.flush()?;
296    wait_for_keypress();
297    stdout.execute(LeaveAlternateScreen)?;
298    Ok(())
299}
300
301fn wait_for_keypress() {
302    let mut single_key = [0u8];
303    std::io::stdin().read_exact(&mut single_key).unwrap();
304}
305
306#[cfg(test)]
307mod tests {
308    use super::{Account, Sign};
309
310    use std::{fs, fs::Permissions, io::Write};
311    use tempfile::{NamedTempFile, TempDir};
312
313    use anyhow::{Context, Result};
314    use colored::Colorize;
315
316    #[test]
317    fn test_new() -> Result<()> {
318        for _ in 0..3 {
319            let account = Account::New { network: 0, seed: None, vanity: None, discreet: false, save_to_file: None };
320            account.parse().with_context(|| "Account creation failed")?;
321        }
322
323        Ok(())
324    }
325
326    #[test]
327    fn test_new_seeded() -> Result<()> {
328        let seed = Some(1231275789u64.to_string());
329
330        let mut expected = format!(
331            " {:>12}  {}\n",
332            "Private Key".cyan().bold(),
333            "APrivateKey1zkp2n22c19hNdGF8wuEoQcuiyuWbquY6up4CtG5DYKqPX2X"
334        );
335        expected += &format!(
336            " {:>12}  {}\n",
337            "View Key".cyan().bold(),
338            "AViewKey1pNxZHn79XVJ4D2WG5Vn2YWsAzf5wzAs3dAuQtUAmUFF7"
339        );
340        expected += &format!(
341            " {:>12}  {}",
342            "Address".cyan().bold(),
343            "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5"
344        );
345
346        let vanity = None;
347        let account = Account::New { network: 0, seed, vanity, discreet: false, save_to_file: None };
348        let actual = account.parse().with_context(|| "Command execution failed")?;
349        assert_eq!(expected, actual);
350
351        Ok(())
352    }
353
354    #[test]
355    fn test_new_seeded_with_256bits_input() -> anyhow::Result<()> {
356        let seed = Some("38868010450269069756484274649022187108349082664538872491798902858296683054657".to_string());
357
358        let mut expected = format!(
359            " {:>12}  {}\n",
360            "Private Key".cyan().bold(),
361            "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p"
362        );
363        expected += &format!(
364            " {:>12}  {}\n",
365            "View Key".cyan().bold(),
366            "AViewKey1eYEGtb78FVg38SSYyzAeXnBdnWCba5t5YxUxtkTtvNAE"
367        );
368        expected += &format!(
369            " {:>12}  {}",
370            "Address".cyan().bold(),
371            "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j"
372        );
373
374        let vanity = None;
375        let account = Account::New { network: 0, seed, vanity, discreet: false, save_to_file: None };
376
377        let actual = account.parse().with_context(|| "Command execution failed")?;
378        assert_eq!(expected, actual);
379
380        Ok(())
381    }
382
383    #[cfg(unix)]
384    #[test]
385    fn test_new_save_to_file() -> anyhow::Result<()> {
386        use std::os::unix::fs::PermissionsExt;
387
388        let dir = TempDir::new().expect("Failed to create temp folder");
389        let dir_path = dir.path();
390        fs::set_permissions(dir_path, Permissions::from_mode(0o700)).expect("Failed to set permissions");
391
392        let mut file = dir.path().to_owned();
393        file.push("my-private-key-file");
394        let file = file.display().to_string();
395
396        let seed = Some(1231275789u64.to_string());
397        let vanity = None;
398        let discreet = false;
399        let save_to_file = Some(file.clone());
400        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file };
401
402        let actual = account.parse().with_context(|| "Command execution failed")?;
403
404        let expected = "APrivateKey1zkp2n22c19hNdGF8wuEoQcuiyuWbquY6up4CtG5DYKqPX2X";
405        assert!(actual.contains(expected));
406
407        let content = fs::read_to_string(&file).expect("Failed to read private-key-file");
408        assert_eq!(expected, content);
409
410        // check the permissions - to read-only for the owner
411        let metadata = fs::metadata(file).unwrap();
412        let permissions = metadata.permissions();
413        assert_eq!(permissions.mode() & 0o777, 0o400, "File permissions are not 0o400");
414
415        Ok(())
416    }
417
418    #[cfg(unix)]
419    #[test]
420    fn test_new_prevent_save_to_file_in_non_protected_folder() {
421        use std::os::unix::fs::PermissionsExt;
422
423        let dir = TempDir::new().expect("Failed to create temp folder");
424        let dir_path = dir.path();
425        fs::set_permissions(dir_path, Permissions::from_mode(0o444)).expect("Failed to set permissions");
426
427        let mut file = dir.path().to_owned();
428        file.push("my-private-key-file");
429        let file = file.display().to_string();
430
431        let seed = None;
432        let vanity = None;
433        let discreet = false;
434        let save_to_file = Some(file);
435        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file };
436
437        let res = account.parse();
438        assert!(res.is_err());
439    }
440
441    #[test]
442    fn test_new_prevent_save_to_file_in_non_existing_folder() {
443        let dir = TempDir::new().expect("Failed to create temp folder");
444
445        let mut file = dir.path().to_owned();
446        file.push("missing-folder");
447        file.push("my-private-key-file");
448        let file = file.display().to_string();
449
450        let seed = None;
451        let vanity = None;
452        let discreet = false;
453        let save_to_file = Some(file);
454        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file };
455
456        let res = account.parse();
457        assert!(res.is_err());
458    }
459
460    #[test]
461    fn test_new_prevent_overwrite_existing_file() {
462        let mut file = NamedTempFile::new().expect("Failed to create temp file");
463        write!(file, "don't overwrite me").expect("Failed to write secret to file");
464
465        let seed = None;
466        let vanity = None;
467        let discreet = false;
468        let path = file.path().display().to_string();
469        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file: Some(path) };
470
471        let res = account.parse();
472        assert!(res.is_err());
473
474        let expected = "don't overwrite me";
475        let content = fs::read_to_string(file).expect("Failed to read private-key-file");
476        assert_eq!(expected, content);
477    }
478
479    #[test]
480    fn test_new_disallow_save_to_file_with_discreet() {
481        let seed = None;
482        let vanity = None;
483        let discreet = true;
484        let save_to_file = Some("/tmp/not-important".to_string());
485        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file };
486
487        let res = account.parse();
488        assert!(res.is_err());
489    }
490
491    #[test]
492    fn test_new_disallow_save_to_file_with_vanity() {
493        let seed = None;
494        let vanity = Some("foo".to_string());
495        let discreet = false;
496        let save_to_file = Some("/tmp/not-important".to_string());
497        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file };
498
499        let res = account.parse();
500        assert!(res.is_err());
501    }
502
503    #[test]
504    fn test_signature_raw() -> Result<()> {
505        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
506        let message = "Hello, world!".to_string();
507        let account = Account::Sign(Sign {
508            network: 0,
509            private_key: Some(key),
510            private_key_file: None,
511            message,
512            raw: true,
513            dev_key: None,
514        });
515
516        account.parse().with_context(|| "Command execution failed")?;
517        Ok(())
518    }
519
520    #[test]
521    fn test_signature_raw_using_private_key_file() -> Result<()> {
522        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
523        let message = "Hello, world!".to_string();
524
525        let mut file = NamedTempFile::new().expect("Failed to create temp file");
526        writeln!(file, "{key}").expect("Failed to write key to temp file");
527
528        let path = file.path().display().to_string();
529        let account = Account::Sign(Sign {
530            network: 0,
531            private_key: None,
532            private_key_file: Some(path),
533            message,
534            raw: true,
535            dev_key: None,
536        });
537
538        account.parse().with_context(|| "Command execution failed")?;
539        Ok(())
540    }
541
542    #[cfg(unix)]
543    #[test]
544    fn test_signature_raw_using_private_key_file_from_account_new() -> Result<()> {
545        use std::os::unix::fs::PermissionsExt;
546
547        let message = "Hello, world!".to_string();
548
549        let dir = TempDir::new().expect("Failed to create temp folder");
550        let dir_path = dir.path();
551        fs::set_permissions(dir_path, Permissions::from_mode(0o700)).expect("Failed to set permissions");
552
553        let mut file = dir.path().to_owned();
554        file.push("my-private-key-file");
555        let file = file.display().to_string();
556
557        let seed = None;
558        let vanity = None;
559        let discreet = false;
560        let account = Account::New { network: 0, seed, vanity, discreet, save_to_file: Some(file.clone()) };
561        account.parse().with_context(|| "Command execution failed")?;
562
563        let account = Account::Sign(Sign {
564            network: 0,
565            private_key: None,
566            private_key_file: Some(file),
567            message,
568            raw: true,
569            dev_key: None,
570        });
571
572        account.parse().with_context(|| "Command execution failed")?;
573        Ok(())
574    }
575
576    #[test]
577    fn test_signature() -> Result<()> {
578        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
579        let message = "5field".to_string();
580        let account = Account::Sign(Sign {
581            network: 0,
582            private_key: Some(key),
583            private_key_file: None,
584            message,
585            raw: false,
586            dev_key: None,
587        });
588
589        account.parse().with_context(|| "Command execution failed")?;
590        Ok(())
591    }
592
593    #[test]
594    fn test_signature_fail() {
595        let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
596        let message = "not a literal value".to_string();
597        let account = Account::Sign(Sign {
598            network: 0,
599            private_key: Some(key),
600            private_key_file: None,
601            message,
602            raw: false,
603            dev_key: None,
604        });
605        assert!(account.parse().is_err());
606    }
607
608    #[test]
609    fn test_verify_raw() -> Result<()> {
610        // test signature of "Hello, world!"
611        let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j";
612        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
613        let message = "Hello, world!".to_string();
614        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: true };
615
616        account.parse().with_context(|| "Command execution failed")?;
617
618        // test signature of "Hello, world!" against the message "Different Message"
619        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
620        let message = "Different Message".to_string();
621        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: true };
622        let actual = account.parse();
623        assert!(actual.is_err());
624
625        // test signature of "Hello, world!" against the wrong address
626        let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
627        let message = "Hello, world!".to_string();
628        let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".to_string();
629        let account = Account::Verify { network: 0, address: wrong_address, signature, message, raw: true };
630        let actual = account.parse();
631        assert!(actual.is_err());
632
633        // test a valid signature of "Different Message"
634        let signature = "sign1424ztyt9hcm77nq450gvdszrvtg9kvhc4qadg4nzy9y0ah7wdqq7t36cxal42p9jj8e8pjpmc06lfev9nvffcpqv0cxwyr0a2j2tjqlesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk3yrr50".to_string();
635        let message = "Different Message".to_string();
636        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: true };
637        account.parse().with_context(|| "Command execution failed")?;
638
639        Ok(())
640    }
641
642    #[test]
643    fn test_verify() -> Result<()> {
644        // test signature of 5u8
645        let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j";
646        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
647        let message = "5field".to_string();
648        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: false };
649        account.parse().with_context(|| "Command execution failed")?;
650
651        // test signature of 5u8 against the message 10u8
652        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
653        let message = "10field".to_string();
654        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: false };
655        let actual = account.parse();
656        assert!(actual.is_err());
657
658        // test signature of 5u8 against the wrong address
659        let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
660        let message = "5field".to_string();
661        let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".to_string();
662        let account = Account::Verify { network: 0, address: wrong_address, signature, message, raw: false };
663        let actual = account.parse();
664        assert!(actual.is_err());
665
666        // test a valid signature of 10u8
667        let signature = "sign1t9v2t5tljk8pr5t6vkcqgkus0a3v69vryxmfrtwrwg0xtj7yv5qj2nz59e5zcyl50w23lhntxvt6vzeqfyu6dt56698zvfj2l6lz6q0esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk8rh9kt".to_string();
668        let message = "10field".to_string();
669        let account = Account::Verify { network: 0, address: address.to_string(), signature, message, raw: false };
670
671        account.parse().with_context(|| "Command execution failed")?;
672
673        Ok(())
674    }
675}