Skip to content

Commit

Permalink
feat: Add support for --save-to-file for account new
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphexion committed Oct 30, 2024
1 parent a518f7b commit 9a88d43
Showing 1 changed file with 78 additions and 9 deletions.
87 changes: 78 additions & 9 deletions cli/src/commands/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use snarkvm::console::{
types::Field,
};

use anyhow::{Result, anyhow, bail};
use anyhow::{anyhow, bail, Result};
use clap::Parser;
use colored::Colorize;
use core::str::FromStr;
Expand All @@ -31,6 +31,7 @@ use rand_chacha::ChaChaRng;
use rayon::prelude::*;
use std::{
io::{Read, Write},
fs::File,
path::PathBuf,
};
use zeroize::Zeroize;
Expand All @@ -52,6 +53,9 @@ pub enum Account {
/// Print sensitive information (such as the private key) discreetly in an alternate screen
#[clap(long)]
discreet: bool,
/// Specify the path to a file where to save the account instead of printing it
#[clap(long = "save-to-file")]
save_to_file: Option<String>,
},
Sign {
/// Specify the network of the private key to sign with
Expand Down Expand Up @@ -97,12 +101,20 @@ fn aleo_literal_to_fields<N: Network>(input: &str) -> Result<Vec<Field<N>>> {
impl Account {
pub fn parse(self) -> Result<String> {
match self {
Self::New { network, seed, vanity, discreet } => {
Self::New { network, seed, vanity, discreet, save_to_file } => {
// Ensure only the seed or the vanity string is specified.
if seed.is_some() && vanity.is_some() {
bail!("Cannot specify both the '--seed' and '--vanity' flags");
}

if save_to_file.is_some() && vanity.is_some() {
bail!("Cannot specify both the '--save-to-file' and '--vanity' flags");
}

if save_to_file.is_some() && discreet {
bail!("Cannot specify both the '--save-to-file' and '--discreet' flags");
}

match vanity {
// Generate a vanity account for the specified network.
Some(vanity) => match network {
Expand All @@ -113,9 +125,9 @@ impl Account {
},
// Generate a seeded account for the specified network.
None => match network {
MainnetV0::ID => Self::new_seeded::<MainnetV0>(seed, discreet),
TestnetV0::ID => Self::new_seeded::<TestnetV0>(seed, discreet),
CanaryV0::ID => Self::new_seeded::<CanaryV0>(seed, discreet),
MainnetV0::ID => Self::new_seeded::<MainnetV0>(seed, discreet, save_to_file),
TestnetV0::ID => Self::new_seeded::<TestnetV0>(seed, discreet, save_to_file),
CanaryV0::ID => Self::new_seeded::<CanaryV0>(seed, discreet, save_to_file),
unknown_id => bail!("Unknown network ID ({unknown_id})"),
},
}
Expand Down Expand Up @@ -227,7 +239,7 @@ impl Account {
}

/// Generates a new Aleo account with an optional seed.
fn new_seeded<N: Network>(seed: Option<String>, discreet: bool) -> Result<String> {
fn new_seeded<N: Network>(seed: Option<String>, discreet: bool, save_to_file: Option<String>) -> Result<String> {
// Recover the seed.
let seed = match seed {
// Recover the field element deterministically.
Expand All @@ -242,6 +254,12 @@ impl Account {
PrivateKey::try_from(seed).map_err(|_| anyhow!("Failed to convert the seed into a valid private key"))?;
// Construct the account.
let account = snarkos_account::Account::<N>::try_from(private_key)?;
// Save to file instead of printing it back to the user
if let Some(path) = save_to_file {
let mut file = File::create(path)?;
file.write_all(account.private_key().to_string().as_bytes())?;
return Ok(String::from(""));
}
// Print the new Aleo account.
if !discreet {
return Ok(account.to_string());
Expand Down Expand Up @@ -331,13 +349,16 @@ fn wait_for_keypress() {
#[cfg(test)]
mod tests {
use crate::commands::Account;
use tempfile::NamedTempFile;
use std::fs;
use std::io::Write;

use colored::Colorize;

#[test]
fn test_new() {
for _ in 0..3 {
let account = Account::New { network: 0, seed: None, vanity: None, discreet: false };
let account = Account::New { network: 0, seed: None, vanity: None, discreet: false, save_to_file: None };
assert!(account.parse().is_ok());
}
}
Expand All @@ -363,7 +384,7 @@ mod tests {
);

let vanity = None;
let account = Account::New { network: 0, seed, vanity, discreet: false };
let account = Account::New { network: 0, seed, vanity, discreet: false, save_to_file: None };
let actual = account.parse().unwrap();
assert_eq!(expected, actual);
}
Expand All @@ -389,11 +410,28 @@ mod tests {
);

let vanity = None;
let account = Account::New { network: 0, seed, vanity, discreet: false };
let account = Account::New { network: 0, seed, vanity, discreet: false, save_to_file: None };
let actual = account.parse().unwrap();
assert_eq!(expected, actual);
}

#[test]
fn test_new_save_to_file() {
let file = NamedTempFile::new().expect("Failed to create temp file");
let path = file.path().to_string_lossy().to_string();

let seed = Some(1231275789u64.to_string());
let vanity = None;
let discreet = false;
let account = Account::New { network: 0, seed, vanity, discreet, save_to_file: Some(path) };
let actual = account.parse().unwrap();
assert_eq!("", actual);

let expected = "APrivateKey1zkp2n22c19hNdGF8wuEoQcuiyuWbquY6up4CtG5DYKqPX2X";
let content = fs::read_to_string(file).expect("Failed to read private-key-file");
assert_eq!(expected, content);
}

#[test]
fn test_signature_raw() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
Expand All @@ -402,6 +440,37 @@ mod tests {
assert!(account.parse().is_ok());
}

#[test]
fn test_signature_raw_using_private_key_file() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
let message = "Hello, world!".to_string();

let mut file = NamedTempFile::new().expect("Failed to create temp file");
writeln!(file, "{}", key).expect("Failed to write key to temp file");

let path = file.path().to_string_lossy().to_string();
let account = Account::Sign { network: 0, private_key: None, private_key_file: Some(path), message, raw: true };
assert!(account.parse().is_ok());
}

#[test]
fn test_signature_raw_using_private_key_file_from_account_new() {
let message = "Hello, world!".to_string();

let file = NamedTempFile::new().expect("Failed to create temp file");
let path = file.path().to_string_lossy().to_string();

let seed = None;
let vanity = None;
let discreet = false;
let account = Account::New { network: 0, seed, vanity, discreet, save_to_file: Some(path.clone()) };
let actual = account.parse().unwrap();
assert_eq!("", actual);

let account = Account::Sign { network: 0, private_key: None, private_key_file: Some(path), message, raw: true };
assert!(account.parse().is_ok());
}

#[test]
fn test_signature() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".to_string();
Expand Down

0 comments on commit 9a88d43

Please sign in to comment.