snarkos_cli/commands/
account.rs

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