Implement Leo Account (#2513)

* implement leo account new --seed

* implement leo account new --write

* implement leo account import --write

* gitignore
This commit is contained in:
Collin Chin 2023-08-07 16:01:33 -07:00 committed by GitHub
parent b22232c02c
commit af1f7f96fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 24 deletions

3
.gitignore vendored
View File

@ -21,3 +21,6 @@ sccache*/
# code coverage scripts
*.bat
# environment
.env

2
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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::<Network>::new().write_to(&directory).unwrap();
Env::<Network>::new().unwrap().write_to(&directory).unwrap();
if Env::<Network>::exists_at(&directory) {
println!(".env file created at {:?}", &directory);
}

View File

@ -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,
}
);

View File

@ -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::*;

View File

@ -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<T>(res: Result<T>) -> 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),
}
}

117
leo/cli/commands/account.rs Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
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<u64>,
/// 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<CurrentNetwork>,
/// 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<Self::Input>
where
Self: Sized,
{
Ok(())
}
fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output>
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<CurrentNetwork>) -> Result<()> {
let view_key = ViewKey::try_from(&private_key)?;
let address = Address::<CurrentNetwork>::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<CurrentNetwork>, ctx: &Context) -> Result<()> {
let data = format!("NETWORK=testnet3\nPRIVATE_KEY={private_key}\n");
let program_dir = ctx.dir()?;
Env::<CurrentNetwork>::from(data).write_to(&program_dir)?;
tracing::info!("✅ Private Key written to {}", program_dir.join(".env").display());
Ok(())
}

View File

@ -14,6 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
pub mod account;
pub use account::Account;
pub mod build;
pub use build::Build;

View File

@ -149,7 +149,7 @@ impl<N: Network> Package<N> {
// Verify that the .env file does not exist.
if !Env::<N>::exists_at(path) {
// Create the .env file.
Env::<N>::new().write_to(path)?;
Env::<N>::new()?.write_to(path)?;
}
// Create the source directory.

View File

@ -25,12 +25,17 @@ pub static ENV_FILENAME: &str = ".env";
#[derive(Deserialize, Default)]
pub struct Env<N: Network> {
data: String,
_phantom: PhantomData<N>,
}
impl<N: Network> Env<N> {
pub fn new() -> Self {
Self { _phantom: PhantomData }
pub fn new() -> Result<Self> {
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<N: Network> Env<N> {
}
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<String> {
fn template() -> Result<String> {
// Initialize an RNG.
let rng = &mut rand::thread_rng();