From af1f7f96fbf1c525729c70a883bdfe7ec2ae4f57 Mon Sep 17 00:00:00 2001 From: Collin Chin <16715212+collinc97@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:01:33 -0700 Subject: [PATCH] Implement Leo Account (#2513) * implement leo account new --seed * implement leo account new --write * implement leo account import --write * gitignore --- .gitignore | 5 +- Cargo.lock | 2 + Cargo.toml | 7 ++ compiler/compiler/tests/utilities/mod.rs | 2 +- errors/src/errors/cli/cli_errors.rs | 7 ++ errors/src/errors/mod.rs | 4 +- leo/cli/cli.rs | 21 ++-- leo/cli/commands/account.rs | 117 +++++++++++++++++++++++ leo/cli/commands/mod.rs | 3 + leo/package/src/package.rs | 2 +- leo/package/src/root/env.rs | 13 ++- 11 files changed, 159 insertions(+), 24 deletions(-) create mode 100644 leo/cli/commands/account.rs diff --git a/.gitignore b/.gitignore index a1484c9626..53a9838170 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ sccache*/ .\#* # code coverage scripts -*.bat \ No newline at end of file +*.bat + +# environment +.env \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 38bea06435..41ce36e89d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,6 +1342,7 @@ dependencies = [ "colored", "console", "dirs 5.0.1", + "dotenvy", "indexmap 1.9.3", "lazy_static", "leo-ast", @@ -1351,6 +1352,7 @@ dependencies = [ "leo-parser", "leo-span", "rand", + "rand_chacha", "rand_core", "reqwest", "rusty-hook", diff --git a/Cargo.toml b/Cargo.toml index 45f405eeb9..a333ed9941 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,9 @@ version = "0.15.7" [dependencies.dirs] version = "5.0.0" +[dependencies.dotenvy] +version = "0.15.7" + [dependencies.indexmap] version = "1.9" features = [ "serde" ] @@ -101,6 +104,10 @@ version = "1.4.0" [dependencies.rand] version = "0.8" +[dependencies.rand_chacha] +version = "0.3.0" +default-features = false + [dependencies.rand_core] version = "0.6.4" diff --git a/compiler/compiler/tests/utilities/mod.rs b/compiler/compiler/tests/utilities/mod.rs index e402b6a53f..990033a20d 100644 --- a/compiler/compiler/tests/utilities/mod.rs +++ b/compiler/compiler/tests/utilities/mod.rs @@ -110,7 +110,7 @@ pub fn setup_build_directory(program_name: &str, bytecode: &String, handler: &Ha let _manifest_file = Manifest::create(&directory, &program_id).unwrap(); // Create the environment file. - Env::::new().write_to(&directory).unwrap(); + Env::::new().unwrap().write_to(&directory).unwrap(); if Env::::exists_at(&directory) { println!(".env file created at {:?}", &directory); } diff --git a/errors/src/errors/cli/cli_errors.rs b/errors/src/errors/cli/cli_errors.rs index decb0957e6..36d841951e 100644 --- a/errors/src/errors/cli/cli_errors.rs +++ b/errors/src/errors/cli/cli_errors.rs @@ -164,4 +164,11 @@ create_messages!( msg: format!("Failed to execute the `execute` command.\nSnarkVM Error: {error}"), help: None, } + + @backtraced + failed_to_parse_seed { + args: (error: impl Display), + msg: format!("Failed to parse the seed string for account.\nSnarkVM Error: {error}"), + help: None, + } ); diff --git a/errors/src/errors/mod.rs b/errors/src/errors/mod.rs index b92def55c9..9289fb44cc 100644 --- a/errors/src/errors/mod.rs +++ b/errors/src/errors/mod.rs @@ -21,11 +21,11 @@ use crate::LeoMessageCode; pub mod ast; pub use self::ast::*; -/// Contains the AST error definitions. +/// Contains the CLI error definitions. pub mod cli; pub use self::cli::*; -/// Contains the AST error definitions. +/// Contains the Compiler error definitions. pub mod compiler; pub use self::compiler::*; diff --git a/leo/cli/cli.rs b/leo/cli/cli.rs index 4a9a609eb6..f5a74ab90f 100644 --- a/leo/cli/cli.rs +++ b/leo/cli/cli.rs @@ -44,12 +44,11 @@ pub struct CLI { ///Leo compiler and package manager #[derive(Parser, Debug)] enum Commands { - // #[clap(about = "Create a new Leo package in an existing directory")] - // Init { - // #[clap(flatten)] - // command: Init, - // }, - // + #[clap(about = "Create a new Aleo account")] + Account { + #[clap(subcommand)] + command: Account, + }, #[clap(about = "Create a new Leo package in a new directory")] New { #[clap(flatten)] @@ -80,13 +79,6 @@ enum Commands { #[clap(flatten)] command: Update, }, - // #[clap(subcommand)] - // Node(Node), - // #[clap(about = "Deploy a program")] - // Deploy { - // #[clap(flatten)] - // command: Deploy, - // }, } pub fn handle_error(res: Result) -> T { @@ -114,6 +106,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> { let context = handle_error(Context::new(cli.path)); match cli.command { + Commands::Account { command } => command.try_execute(context), Commands::New { command } => command.try_execute(context), Commands::Build { command } => { // Enter tracing span @@ -135,7 +128,5 @@ pub fn run_with_args(cli: CLI) -> Result<()> { Commands::Run { command } => command.try_execute(context), Commands::Execute { command } => command.try_execute(context), Commands::Update { command } => command.try_execute(context), - // Commands::Node(command) => command.try_execute(context), - // Commands::Deploy { command } => command.try_execute(context), } } diff --git a/leo/cli/commands/account.rs b/leo/cli/commands/account.rs new file mode 100644 index 0000000000..eea0ed6883 --- /dev/null +++ b/leo/cli/commands/account.rs @@ -0,0 +1,117 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; +use leo_package::root::Env; +use snarkvm::prelude::{Address, PrivateKey, ViewKey}; + +use rand::SeedableRng; +use rand_chacha::ChaChaRng; + +/// Commands to manage Aleo accounts. +#[derive(Parser, Debug)] +pub enum Account { + /// Generates a new Aleo account + New { + /// Seed the RNG with a numeric value. + #[clap(short = 's', long)] + seed: Option, + /// Write the private key to the .env file. + #[clap(short = 'w', long)] + write: bool, + }, + /// Derive an Aleo account from a private key. + Import { + /// Private key plaintext + private_key: PrivateKey, + /// Write the private key to the .env file. + #[clap(short = 'w', long)] + write: bool, + }, +} + +impl Command for Account { + type Input = (); + type Output = (); + + fn prelude(&self, _: Context) -> Result + where + Self: Sized, + { + Ok(()) + } + + fn apply(self, ctx: Context, _: Self::Input) -> Result + where + Self: Sized, + { + match self { + Account::New { seed, write } => { + // Sample a new Aleo account. + let private_key = match seed { + // Recover the field element deterministically. + Some(seed) => PrivateKey::new(&mut ChaChaRng::seed_from_u64(seed)), + // Sample a random field element. + None => PrivateKey::new(&mut ChaChaRng::from_entropy()), + } + .map_err(CliError::failed_to_parse_seed)?; + + // Derive the view key and address and print to stdout. + print_keys(private_key)?; + + // Save key data to .env file. + if write { + write_to_env_file(private_key, &ctx)?; + } + } + Account::Import { private_key, write } => { + // Derive the view key and address and print to stdout. + print_keys(private_key)?; + + // Save key data to .env file. + if write { + write_to_env_file(private_key, &ctx)?; + } + } + } + Ok(()) + } +} + +// Helper functions + +// Print keys as a formatted string without log level. +fn print_keys(private_key: PrivateKey) -> Result<()> { + let view_key = ViewKey::try_from(&private_key)?; + let address = Address::::try_from(&view_key)?; + + println!( + "\n {:>12} {private_key}\n {:>12} {view_key}\n {:>12} {address}\n", + "Private Key".cyan().bold(), + "View Key".cyan().bold(), + "Address".cyan().bold(), + ); + Ok(()) +} + +// Write the network and private key to the .env file in project directory. +fn write_to_env_file(private_key: PrivateKey, ctx: &Context) -> Result<()> { + let data = format!("NETWORK=testnet3\nPRIVATE_KEY={private_key}\n"); + let program_dir = ctx.dir()?; + Env::::from(data).write_to(&program_dir)?; + tracing::info!("✅ Private Key written to {}", program_dir.join(".env").display()); + Ok(()) +} diff --git a/leo/cli/commands/mod.rs b/leo/cli/commands/mod.rs index 38fcbcc81a..af250311db 100644 --- a/leo/cli/commands/mod.rs +++ b/leo/cli/commands/mod.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . +pub mod account; +pub use account::Account; + pub mod build; pub use build::Build; diff --git a/leo/package/src/package.rs b/leo/package/src/package.rs index 898b2584c8..2d08101665 100644 --- a/leo/package/src/package.rs +++ b/leo/package/src/package.rs @@ -149,7 +149,7 @@ impl Package { // Verify that the .env file does not exist. if !Env::::exists_at(path) { // Create the .env file. - Env::::new().write_to(path)?; + Env::::new()?.write_to(path)?; } // Create the source directory. diff --git a/leo/package/src/root/env.rs b/leo/package/src/root/env.rs index 9929032e6a..ebeb8a5075 100644 --- a/leo/package/src/root/env.rs +++ b/leo/package/src/root/env.rs @@ -25,12 +25,17 @@ pub static ENV_FILENAME: &str = ".env"; #[derive(Deserialize, Default)] pub struct Env { + data: String, _phantom: PhantomData, } impl Env { - pub fn new() -> Self { - Self { _phantom: PhantomData } + pub fn new() -> Result { + Ok(Self { data: Self::template()?, _phantom: PhantomData }) + } + + pub fn from(data: String) -> Self { + Self { data, _phantom: PhantomData } } pub fn exists_at(path: &Path) -> bool { @@ -48,11 +53,11 @@ impl Env { } let mut file = File::create(&path).map_err(PackageError::io_error_env_file)?; - file.write_all(self.template()?.as_bytes()).map_err(PackageError::io_error_env_file)?; + file.write_all(self.data.as_bytes()).map_err(PackageError::io_error_env_file)?; Ok(()) } - fn template(&self) -> Result { + fn template() -> Result { // Initialize an RNG. let rng = &mut rand::thread_rng();