From b98c1f712b13678d69e9804ad4a4817b53356375 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 12 Aug 2024 15:50:43 +0300 Subject: [PATCH 1/5] pem wallet generation --- Cargo.lock | 1 + framework/meta/Cargo.toml | 4 ++ framework/meta/src/cli/cli_args_standalone.rs | 58 +++++++++++++++++++ framework/meta/src/cli/cli_standalone_main.rs | 4 ++ framework/meta/src/cmd.rs | 1 + framework/meta/src/cmd/wallet.rs | 53 +++++++++++++++++ 6 files changed, 121 insertions(+) create mode 100644 framework/meta/src/cmd/wallet.rs diff --git a/Cargo.lock b/Cargo.lock index abeb3c37e1..4ae509e714 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1996,6 +1996,7 @@ dependencies = [ "multiversx-sc", "multiversx-sc-meta-lib", "multiversx-sc-snippets", + "multiversx-sdk", "pathdiff", "reqwest", "ruplacer", diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index bd6775ed1a..0aebe72194 100644 --- a/framework/meta/Cargo.toml +++ b/framework/meta/Cargo.toml @@ -52,3 +52,7 @@ features = ["alloc", "num-bigint"] [dependencies.multiversx-sc-snippets] version = "=0.52.3" path = "../snippets" + +[dependencies.multiversx-sdk] +version = "=0.5.0" +path = "../../sdk/core" diff --git a/framework/meta/src/cli/cli_args_standalone.rs b/framework/meta/src/cli/cli_args_standalone.rs index 335afe41ea..38857c6f9e 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -75,6 +75,12 @@ pub enum StandaloneCliAction { about = "Generates a report on the local depedencies of contract crates. Will explore indirect depdencies too." )] LocalDeps(LocalDepsArgs), + + #[command( + name = "wallet", + about = "Generates a new wallet or performs actions on an existing wallet." + )] + Wallet(WalletArgs), } #[derive(Default, Clone, PartialEq, Eq, Debug, Args)] @@ -348,3 +354,55 @@ pub struct AccountArgs { #[arg(long = "address", verbatim_doc_comment)] pub address: String, } + +#[derive(Clone, PartialEq, Eq, Debug, Subcommand)] +pub enum WalletAction { + #[command(name = "new", about = "Creates a new wallet")] + New(WalletNewArgs), + + #[command(name = "convert", about = "Converts a wallet")] + Convert(WalletConvertArgs), +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, ValueEnum)] +pub enum WalletFormat { + #[default] + Pem, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, ValueEnum)] +pub enum WalletConvertFormat { + #[default] + Bech32, + + Hex, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Parser)] +#[command(propagate_version = true)] +pub struct WalletArgs { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct WalletNewArgs { + /// The type of wallet to create. + #[arg(long = "format", verbatim_doc_comment)] + pub wallet_format: Option, + + /// The name of the wallet to create. + #[arg(long = "outfile", verbatim_doc_comment)] + pub outfile: Option, + // #[arg(long = "address-hrp")] + // pub address_hrp: Option, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct WalletConvertArgs { + #[arg(long = "from", verbatim_doc_comment)] + pub from: Option, + + #[arg(long = "to", verbatim_doc_comment)] + pub to: Option, +} diff --git a/framework/meta/src/cli/cli_standalone_main.rs b/framework/meta/src/cli/cli_standalone_main.rs index 6c8c6a67b0..5100750abc 100644 --- a/framework/meta/src/cli/cli_standalone_main.rs +++ b/framework/meta/src/cli/cli_standalone_main.rs @@ -1,5 +1,6 @@ use crate::cli::{StandaloneCliAction, StandaloneCliArgs}; use crate::cmd::retrieve_address::retrieve_address; +use crate::cmd::wallet::wallet; use clap::Parser; use crate::cmd::all::call_all_meta; @@ -41,6 +42,9 @@ pub async fn cli_main_standalone() { Some(StandaloneCliAction::LocalDeps(args)) => { local_deps(args); }, + Some(StandaloneCliAction::Wallet(args)) => { + wallet(args); + }, None => {}, } } diff --git a/framework/meta/src/cmd.rs b/framework/meta/src/cmd.rs index a588f90162..8053a2113e 100644 --- a/framework/meta/src/cmd.rs +++ b/framework/meta/src/cmd.rs @@ -9,3 +9,4 @@ pub mod template; pub mod test; pub mod test_coverage; pub mod upgrade; +pub mod wallet; diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs new file mode 100644 index 0000000000..4ad0a3f7d9 --- /dev/null +++ b/framework/meta/src/cmd/wallet.rs @@ -0,0 +1,53 @@ +use std::{fs::File, io::Write}; + +use crate::cli::{WalletAction, WalletArgs, WalletConvertArgs, WalletFormat, WalletNewArgs}; +use multiversx_sdk::{data::address::Address, wallet::Wallet}; + +pub fn wallet(args: &WalletArgs) { + let command = args + .command + .as_ref() + .expect("command expected after `wallet`"); + match command { + WalletAction::New(new_args) => new(new_args), + WalletAction::Convert(convert_args) => convert(convert_args), + } +} + +fn convert(convert_args: &WalletConvertArgs) { + todo!() +} + +fn new(new_args: &WalletNewArgs) { + let format = new_args + .wallet_format + .as_ref() + .expect("wallet format is required"); + let outfile = new_args.outfile.as_ref().expect("output file is required"); + + match format { + WalletFormat::Pem => { + let mnemonic = Wallet::generate_mnemonic(); + let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); + let pk_str: &str = &private_key.to_string(); + let wallet = Wallet::from_private_key(pk_str).unwrap(); + let address = wallet.address(); + + println!("Wallet address: {}", address); + + generate_pem(&address, pk_str, outfile); + }, + } +} + +fn generate_pem(address: &Address, private_key: &str, outfile: &String) { + let pem_content = format!( + "-----BEGIN PRIVATE KEY for {}-----\n{}\n-----END PRIVATE KEY for {}-----", + address.to_bech32_string().unwrap(), + private_key, + address.to_bech32_string().unwrap() + ); + + let mut file = File::create(outfile).unwrap(); + file.write_all(pem_content.as_bytes()).unwrap() +} From b530c1f6a712538be16e01c47a541e1c6cafd74d Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 12 Aug 2024 18:52:33 +0300 Subject: [PATCH 2/5] [WIP] generated pem file --- Cargo.lock | 19 +++-- framework/meta/Cargo.toml | 1 + framework/meta/src/cli/cli_args_standalone.rs | 32 +++----- framework/meta/src/cmd/wallet.rs | 77 +++++++++++++++++-- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ae509e714..08f2acc6ae 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,6 +195,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -1988,6 +1994,7 @@ dependencies = [ name = "multiversx-sc-meta" version = "0.52.3" dependencies = [ + "base64 0.13.1", "clap", "colored", "common-path", @@ -2038,7 +2045,7 @@ dependencies = [ name = "multiversx-sc-scenario" version = "0.52.3" dependencies = [ - "base64", + "base64 0.22.1", "bech32", "colored", "hex", @@ -2062,7 +2069,7 @@ dependencies = [ name = "multiversx-sc-snippets" version = "0.52.3" dependencies = [ - "base64", + "base64 0.22.1", "env_logger", "futures", "hex", @@ -2086,7 +2093,7 @@ name = "multiversx-sdk" version = "0.5.0" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "bech32", "bip39", "hex", @@ -2424,7 +2431,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64", + "base64 0.22.1", "serde", ] @@ -2756,7 +2763,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -2926,7 +2933,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index 0aebe72194..c59184d411 100644 --- a/framework/meta/Cargo.toml +++ b/framework/meta/Cargo.toml @@ -39,6 +39,7 @@ zip = { version = "2.1", features = ["deflate"], default-features = false } copy_dir = "0.1.2" pathdiff = "0.2.1" common-path = "1.0.0" +base64 = "0.13" [dependencies.multiversx-sc-meta-lib] version = "=0.52.3" diff --git a/framework/meta/src/cli/cli_args_standalone.rs b/framework/meta/src/cli/cli_args_standalone.rs index 38857c6f9e..d670987338 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -364,20 +364,6 @@ pub enum WalletAction { Convert(WalletConvertArgs), } -#[derive(Default, Clone, PartialEq, Eq, Debug, ValueEnum)] -pub enum WalletFormat { - #[default] - Pem, -} - -#[derive(Default, Clone, PartialEq, Eq, Debug, ValueEnum)] -pub enum WalletConvertFormat { - #[default] - Bech32, - - Hex, -} - #[derive(Default, Clone, PartialEq, Eq, Debug, Parser)] #[command(propagate_version = true)] pub struct WalletArgs { @@ -389,20 +375,24 @@ pub struct WalletArgs { pub struct WalletNewArgs { /// The type of wallet to create. #[arg(long = "format", verbatim_doc_comment)] - pub wallet_format: Option, + pub wallet_format: Option, /// The name of the wallet to create. #[arg(long = "outfile", verbatim_doc_comment)] pub outfile: Option, - // #[arg(long = "address-hrp")] - // pub address_hrp: Option, } #[derive(Default, Clone, PartialEq, Eq, Debug, Args)] pub struct WalletConvertArgs { - #[arg(long = "from", verbatim_doc_comment)] - pub from: Option, + #[arg(long = "in-format", verbatim_doc_comment)] + pub from: Option, - #[arg(long = "to", verbatim_doc_comment)] - pub to: Option, + #[arg(long = "out-format", verbatim_doc_comment)] + pub to: Option, + + #[arg(long = "infile", verbatim_doc_comment)] + pub infile: Option, + + #[arg(long = "outfile", verbatim_doc_comment)] + pub outfile: Option, } diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 4ad0a3f7d9..b1fdddee9c 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,6 +1,12 @@ -use std::{fs::File, io::Write}; +use base64; +use core::str; +use std::{ + fs::{self, File}, + io::Write, +}; -use crate::cli::{WalletAction, WalletArgs, WalletConvertArgs, WalletFormat, WalletNewArgs}; +use crate::cli::{WalletAction, WalletArgs, WalletConvertArgs, WalletNewArgs}; +use multiversx_sc_snippets::{hex, imports::Bech32Address}; use multiversx_sdk::{data::address::Address, wallet::Wallet}; pub fn wallet(args: &WalletArgs) { @@ -15,7 +21,46 @@ pub fn wallet(args: &WalletArgs) { } fn convert(convert_args: &WalletConvertArgs) { - todo!() + let infile = convert_args + .infile + .as_ref() + .expect("input file is required"); + let outfile = convert_args.outfile.as_ref(); + let in_format = convert_args + .from + .as_ref() + .expect("input format is required"); + let out_format = convert_args.to.as_ref().expect("output format is required"); + + let in_address = fs::read_to_string(infile).unwrap(); + let mut out_addr: String = String::from(""); + + match (in_format.as_str(), out_format.as_str()) { + ("address-bech32", "address-hex") => { + out_addr = Bech32Address::from_bech32_string(in_address).to_hex(); + }, + ("address-hex", "address-bech32") => { + // out_addr = Bech32Address::from(in_address).to_bech32_string(); + let bytes_from_hex: [u8; 64] = hex::decode(in_address).unwrap().try_into().unwrap(); + out_addr = Bech32Address::from_bech32_string( + str::from_utf8(&bytes_from_hex).unwrap().to_string(), + ) + .to_bech32_string(); + }, + _ => { + println!("Unsupported conversion"); + }, + } + + match outfile { + Some(outfile) => { + let mut file = File::create(outfile).unwrap(); + file.write_all(out_addr.as_bytes()).unwrap(); + }, + None => { + println!("{}", out_addr); + }, + } } fn new(new_args: &WalletNewArgs) { @@ -25,8 +70,8 @@ fn new(new_args: &WalletNewArgs) { .expect("wallet format is required"); let outfile = new_args.outfile.as_ref().expect("output file is required"); - match format { - WalletFormat::Pem => { + match format.as_str() { + "pem" => { let mnemonic = Wallet::generate_mnemonic(); let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); let pk_str: &str = &private_key.to_string(); @@ -37,14 +82,32 @@ fn new(new_args: &WalletNewArgs) { generate_pem(&address, pk_str, outfile); }, + _ => { + println!("Unsupported wallet format"); + }, } } fn generate_pem(address: &Address, private_key: &str, outfile: &String) { + println!("{private_key}"); + let private_key_hex_encoded = hex::encode(private_key.as_bytes()); + println!("HEX {private_key_hex_encoded}"); + // let priv_key_bytes = private_key_hex_encoded.as_bytes(); + let private_key_base64 = base64::encode(&private_key_hex_encoded.as_bytes()); + println!("B64 {private_key_base64}"); + + // Split the base64 string into 64-character lines + let formatted_key = private_key_base64 + .as_bytes() + .chunks(64) + .map(|chunk| std::str::from_utf8(chunk).unwrap()) + .collect::>() + .join("\n"); + let pem_content = format!( - "-----BEGIN PRIVATE KEY for {}-----\n{}\n-----END PRIVATE KEY for {}-----", + "-----BEGIN PRIVATE KEY for {}-----\n{}\n-----END PRIVATE KEY for {}-----\n", address.to_bech32_string().unwrap(), - private_key, + formatted_key, address.to_bech32_string().unwrap() ); From d5be23d47cd28e9aae627899dc80d85302d2e5c6 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Tue, 13 Aug 2024 13:09:31 +0300 Subject: [PATCH 3/5] demo wallet creation & conversion --- framework/meta/src/cli/cli_args_standalone.rs | 4 +- framework/meta/src/cmd/wallet.rs | 105 ++++++++++-------- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/framework/meta/src/cli/cli_args_standalone.rs b/framework/meta/src/cli/cli_args_standalone.rs index d670987338..59cb0a4d86 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -385,10 +385,10 @@ pub struct WalletNewArgs { #[derive(Default, Clone, PartialEq, Eq, Debug, Args)] pub struct WalletConvertArgs { #[arg(long = "in-format", verbatim_doc_comment)] - pub from: Option, + pub from: String, #[arg(long = "out-format", verbatim_doc_comment)] - pub to: Option, + pub to: String, #[arg(long = "infile", verbatim_doc_comment)] pub infile: Option, diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index b1fdddee9c..bdddf81b60 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,13 +1,14 @@ use base64; use core::str; +use multiversx_sc::types; use std::{ fs::{self, File}, - io::Write, + io::{self, Read, Write}, }; use crate::cli::{WalletAction, WalletArgs, WalletConvertArgs, WalletNewArgs}; use multiversx_sc_snippets::{hex, imports::Bech32Address}; -use multiversx_sdk::{data::address::Address, wallet::Wallet}; +use multiversx_sdk::{crypto::public_key::PublicKey, data::address::Address, wallet::Wallet}; pub fn wallet(args: &WalletArgs) { let command = args @@ -21,31 +22,37 @@ pub fn wallet(args: &WalletArgs) { } fn convert(convert_args: &WalletConvertArgs) { - let infile = convert_args - .infile - .as_ref() - .expect("input file is required"); + let infile = convert_args.infile.as_ref(); let outfile = convert_args.outfile.as_ref(); - let in_format = convert_args - .from - .as_ref() - .expect("input format is required"); - let out_format = convert_args.to.as_ref().expect("output format is required"); + let in_format = &convert_args.from; + let out_format = &convert_args.to; + + let mut in_address = String::new(); + let mut out_address: String = String::new(); + + match infile { + Some(file) => in_address = fs::read_to_string(file).unwrap(), + None => { + println!("Insert text below. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done."); + _ = io::stdin().read_to_string(&mut in_address).unwrap() + }, + } - let in_address = fs::read_to_string(infile).unwrap(); - let mut out_addr: String = String::from(""); + in_address = in_address.replace('\n', ""); match (in_format.as_str(), out_format.as_str()) { ("address-bech32", "address-hex") => { - out_addr = Bech32Address::from_bech32_string(in_address).to_hex(); + out_address = Bech32Address::from_bech32_string(in_address).to_hex(); }, ("address-hex", "address-bech32") => { - // out_addr = Bech32Address::from(in_address).to_bech32_string(); - let bytes_from_hex: [u8; 64] = hex::decode(in_address).unwrap().try_into().unwrap(); - out_addr = Bech32Address::from_bech32_string( - str::from_utf8(&bytes_from_hex).unwrap().to_string(), - ) - .to_bech32_string(); + let bytes_from_hex = hex::decode(in_address).unwrap(); + let bytes_arr: [u8; 32] = bytes_from_hex.try_into().unwrap(); + + let addr = types::Address::from(&bytes_arr); + out_address = Bech32Address::from(addr).to_bech32_str().to_string(); + }, + ("", _) | (_, "") => { + println!("error: the following arguments are required: --in-format, --out-format"); }, _ => { println!("Unsupported conversion"); @@ -55,49 +62,51 @@ fn convert(convert_args: &WalletConvertArgs) { match outfile { Some(outfile) => { let mut file = File::create(outfile).unwrap(); - file.write_all(out_addr.as_bytes()).unwrap(); + out_address.push('\n'); + file.write_all(out_address.as_bytes()).unwrap(); }, None => { - println!("{}", out_addr); + println!("{}", out_address); }, } } fn new(new_args: &WalletNewArgs) { - let format = new_args - .wallet_format - .as_ref() - .expect("wallet format is required"); - let outfile = new_args.outfile.as_ref().expect("output file is required"); + let format = new_args.wallet_format.as_ref(); + let outfile = new_args.outfile.as_ref(); + let mnemonic = Wallet::generate_mnemonic(); + println!("Mnemonic: {}", mnemonic); - match format.as_str() { - "pem" => { - let mnemonic = Wallet::generate_mnemonic(); - let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); - let pk_str: &str = &private_key.to_string(); - let wallet = Wallet::from_private_key(pk_str).unwrap(); - let address = wallet.address(); + let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); + let public_key = PublicKey::from(&private_key); - println!("Wallet address: {}", address); + let public_key_str: &str = &public_key.to_string(); + let private_key_str: &str = &private_key.to_string(); - generate_pem(&address, pk_str, outfile); - }, - _ => { - println!("Unsupported wallet format"); - }, + let wallet = Wallet::from_private_key(private_key_str).unwrap(); + let address = wallet.address(); + + println!("Wallet address: {}", address); + + if let Some(f) = format { + match (f.as_str(), outfile) { + ("pem", Some(file)) => { + generate_pem(&address, private_key_str, public_key_str, file); + }, + ("pem", None) => { + println!("Output file is required for PEM format"); + }, + _ => {}, + } } } -fn generate_pem(address: &Address, private_key: &str, outfile: &String) { - println!("{private_key}"); - let private_key_hex_encoded = hex::encode(private_key.as_bytes()); - println!("HEX {private_key_hex_encoded}"); - // let priv_key_bytes = private_key_hex_encoded.as_bytes(); - let private_key_base64 = base64::encode(&private_key_hex_encoded.as_bytes()); - println!("B64 {private_key_base64}"); +fn generate_pem(address: &Address, private_key: &str, public_key: &str, outfile: &String) { + let concat_keys = format!("{}{}", private_key, public_key); + let concat_keys_b64 = base64::encode(concat_keys); // Split the base64 string into 64-character lines - let formatted_key = private_key_base64 + let formatted_key = concat_keys_b64 .as_bytes() .chunks(64) .map(|chunk| std::str::from_utf8(chunk).unwrap()) From 47ef8de7f75ed1c031b448d00579debefa2e0941 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Tue, 13 Aug 2024 17:03:47 +0300 Subject: [PATCH 4/5] added bech32 command and updated conversion --- framework/meta/Cargo.toml | 2 +- framework/meta/src/cli/cli_args_standalone.rs | 17 ++- framework/meta/src/cmd/wallet.rs | 132 +++++++++++------- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index c59184d411..63625c1746 100644 --- a/framework/meta/Cargo.toml +++ b/framework/meta/Cargo.toml @@ -39,7 +39,7 @@ zip = { version = "2.1", features = ["deflate"], default-features = false } copy_dir = "0.1.2" pathdiff = "0.2.1" common-path = "1.0.0" -base64 = "0.13" +bip39 = "2.0.0" [dependencies.multiversx-sc-meta-lib] version = "=0.52.3" diff --git a/framework/meta/src/cli/cli_args_standalone.rs b/framework/meta/src/cli/cli_args_standalone.rs index 59cb0a4d86..104feac18d 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -360,15 +360,20 @@ pub enum WalletAction { #[command(name = "new", about = "Creates a new wallet")] New(WalletNewArgs), + #[command( + name = "bech32", + about = "Encodes/decodes a bech32 address to/from hex" + )] + Bech32(WalletBech32Args), #[command(name = "convert", about = "Converts a wallet")] Convert(WalletConvertArgs), } -#[derive(Default, Clone, PartialEq, Eq, Debug, Parser)] +#[derive(Clone, PartialEq, Eq, Debug, Parser)] #[command(propagate_version = true)] pub struct WalletArgs { #[command(subcommand)] - pub command: Option, + pub command: WalletAction, } #[derive(Default, Clone, PartialEq, Eq, Debug, Args)] @@ -396,3 +401,11 @@ pub struct WalletConvertArgs { #[arg(long = "outfile", verbatim_doc_comment)] pub outfile: Option, } + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct WalletBech32Args { + #[arg(long = "encode", verbatim_doc_comment)] + pub hex_address: Option, + #[arg(long = "decode", verbatim_doc_comment)] + pub bech32_address: Option, +} diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index bdddf81b60..7ed5ea58bb 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,4 +1,3 @@ -use base64; use core::str; use multiversx_sc::types; use std::{ @@ -6,17 +5,17 @@ use std::{ io::{self, Read, Write}, }; -use crate::cli::{WalletAction, WalletArgs, WalletConvertArgs, WalletNewArgs}; +use crate::cli::{WalletAction, WalletArgs, WalletBech32Args, WalletConvertArgs, WalletNewArgs}; +use bip39::Mnemonic; use multiversx_sc_snippets::{hex, imports::Bech32Address}; -use multiversx_sdk::{crypto::public_key::PublicKey, data::address::Address, wallet::Wallet}; - +use multiversx_sdk::{ + crypto::public_key::PublicKey, data::address::Address, utils::base64_encode, wallet::Wallet, +}; pub fn wallet(args: &WalletArgs) { - let command = args - .command - .as_ref() - .expect("command expected after `wallet`"); + let command = &args.command; match command { WalletAction::New(new_args) => new(new_args), + WalletAction::Bech32(bech32_args) => bech32_conversion(bech32_args), WalletAction::Convert(convert_args) => convert(convert_args), } } @@ -27,71 +26,105 @@ fn convert(convert_args: &WalletConvertArgs) { let in_format = &convert_args.from; let out_format = &convert_args.to; - let mut in_address = String::new(); - let mut out_address: String = String::new(); - - match infile { - Some(file) => in_address = fs::read_to_string(file).unwrap(), - None => { - println!("Insert text below. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done."); - _ = io::stdin().read_to_string(&mut in_address).unwrap() - }, - } - - in_address = in_address.replace('\n', ""); + let mut mnemonic_str = String::new(); match (in_format.as_str(), out_format.as_str()) { - ("address-bech32", "address-hex") => { - out_address = Bech32Address::from_bech32_string(in_address).to_hex(); - }, - ("address-hex", "address-bech32") => { - let bytes_from_hex = hex::decode(in_address).unwrap(); - let bytes_arr: [u8; 32] = bytes_from_hex.try_into().unwrap(); - - let addr = types::Address::from(&bytes_arr); - out_address = Bech32Address::from(addr).to_bech32_str().to_string(); - }, - ("", _) | (_, "") => { - println!("error: the following arguments are required: --in-format, --out-format"); + ("mnemonic", "pem") => match infile { + Some(file) => { + mnemonic_str = fs::read_to_string(file).unwrap(); + mnemonic_str = mnemonic_str.replace('\n', ""); + }, + None => { + println!("Insert text below. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done."); + _ = io::stdin().read_to_string(&mut mnemonic_str).unwrap() + }, }, _ => { println!("Unsupported conversion"); }, } + let mnemonic = Mnemonic::parse(mnemonic_str).unwrap(); + + let (private_key_str, public_key_str) = get_wallet_keys(mnemonic); + let address = get_wallet_address(private_key_str.as_str()); + match outfile { Some(outfile) => { - let mut file = File::create(outfile).unwrap(); - out_address.push('\n'); - file.write_all(out_address.as_bytes()).unwrap(); + generate_pem( + &address, + private_key_str.as_str(), + public_key_str.as_str(), + outfile, + ); }, None => { - println!("{}", out_address); + let pem_content = + generate_pem_content(&address, private_key_str.as_str(), public_key_str.as_str()); + print!("{}", pem_content); }, } } -fn new(new_args: &WalletNewArgs) { - let format = new_args.wallet_format.as_ref(); - let outfile = new_args.outfile.as_ref(); - let mnemonic = Wallet::generate_mnemonic(); - println!("Mnemonic: {}", mnemonic); +fn bech32_conversion(bech32_args: &WalletBech32Args) { + let encode_address = bech32_args.hex_address.as_ref(); + let decode_address = bech32_args.bech32_address.as_ref(); + + match (encode_address, decode_address) { + (Some(hex), None) => { + let bytes_from_hex = hex::decode(hex).unwrap(); + let bytes_arr: [u8; 32] = bytes_from_hex.try_into().unwrap(); + let addr = types::Address::from(&bytes_arr); + let bech32_addr = Bech32Address::from(addr).to_bech32_str().to_string(); + println!("{}", bech32_addr); + }, + (None, Some(bech32)) => { + let hex_addr = Bech32Address::from_bech32_string(bech32.to_string()).to_hex(); + println!("{}", hex_addr); + }, + (Some(_), Some(_)) => { + println!("error: only one of --encode or --decode can be used in the same command"); + }, + _ => {}, + } +} + +fn get_wallet_keys(mnemonic: Mnemonic) -> (String, String) { let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); let public_key = PublicKey::from(&private_key); let public_key_str: &str = &public_key.to_string(); let private_key_str: &str = &private_key.to_string(); - let wallet = Wallet::from_private_key(private_key_str).unwrap(); - let address = wallet.address(); + (private_key_str.to_string(), public_key_str.to_string()) +} + +fn get_wallet_address(private_key: &str) -> Address { + let wallet = Wallet::from_private_key(private_key).unwrap(); + wallet.address() +} + +fn new(new_args: &WalletNewArgs) { + let format = new_args.wallet_format.as_ref(); + let outfile = new_args.outfile.as_ref(); + let mnemonic = Wallet::generate_mnemonic(); + println!("Mnemonic: {}", mnemonic); + + let (private_key_str, public_key_str) = get_wallet_keys(mnemonic); + let address = get_wallet_address(private_key_str.as_str()); println!("Wallet address: {}", address); if let Some(f) = format { match (f.as_str(), outfile) { ("pem", Some(file)) => { - generate_pem(&address, private_key_str, public_key_str, file); + generate_pem( + &address, + private_key_str.as_str(), + public_key_str.as_str(), + file, + ); }, ("pem", None) => { println!("Output file is required for PEM format"); @@ -102,8 +135,14 @@ fn new(new_args: &WalletNewArgs) { } fn generate_pem(address: &Address, private_key: &str, public_key: &str, outfile: &String) { + let pem_content = generate_pem_content(address, private_key, public_key); + let mut file = File::create(outfile).unwrap(); + file.write_all(pem_content.as_bytes()).unwrap() +} + +fn generate_pem_content(address: &Address, private_key: &str, public_key: &str) -> String { let concat_keys = format!("{}{}", private_key, public_key); - let concat_keys_b64 = base64::encode(concat_keys); + let concat_keys_b64 = base64_encode(concat_keys); // Split the base64 string into 64-character lines let formatted_key = concat_keys_b64 @@ -120,6 +159,5 @@ fn generate_pem(address: &Address, private_key: &str, public_key: &str, outfile: address.to_bech32_string().unwrap() ); - let mut file = File::create(outfile).unwrap(); - file.write_all(pem_content.as_bytes()).unwrap() + pem_content } From eba0141dee9f708136bf2560255decb736be56b4 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Tue, 13 Aug 2024 17:50:45 +0300 Subject: [PATCH 5/5] deleted unnecessary dependency --- framework/meta/Cargo.toml | 4 ---- framework/meta/src/cmd/wallet.rs | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index 63625c1746..fba796a3d0 100644 --- a/framework/meta/Cargo.toml +++ b/framework/meta/Cargo.toml @@ -53,7 +53,3 @@ features = ["alloc", "num-bigint"] [dependencies.multiversx-sc-snippets] version = "=0.52.3" path = "../snippets" - -[dependencies.multiversx-sdk] -version = "=0.5.0" -path = "../../sdk/core" diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 7ed5ea58bb..7fcf28899a 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -7,10 +7,10 @@ use std::{ use crate::cli::{WalletAction, WalletArgs, WalletBech32Args, WalletConvertArgs, WalletNewArgs}; use bip39::Mnemonic; -use multiversx_sc_snippets::{hex, imports::Bech32Address}; -use multiversx_sdk::{ +use multiversx_sc_snippets::sdk::{ crypto::public_key::PublicKey, data::address::Address, utils::base64_encode, wallet::Wallet, }; +use multiversx_sc_snippets::{hex, imports::Bech32Address}; pub fn wallet(args: &WalletArgs) { let command = &args.command; match command {