Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keystore <-> pem conversion + keystore wallet generation #1741

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

147 changes: 91 additions & 56 deletions framework/meta/src/cmd/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use core::str;
use multiversx_sc::types;
use std::{
fs::{self, File},
io::{self, Read, Write},
};

use crate::cli::{WalletAction, WalletArgs, WalletBech32Args, WalletConvertArgs, WalletNewArgs};
use bip39::Mnemonic;
use multiversx_sc::types::{self};
use multiversx_sc_snippets::sdk::{
crypto::public_key::PublicKey, data::address::Address, utils::base64_encode, wallet::Wallet,
};
use multiversx_sc_snippets::{hex, imports::Bech32Address};
use std::{
fs::{self, File},
io::{self, Read, Write},
};

pub fn wallet(args: &WalletArgs) {
let command = &args.command;
match command {
Expand All @@ -27,45 +27,92 @@ fn convert(convert_args: &WalletConvertArgs) {
let out_format = &convert_args.to;

let mut mnemonic_str = String::new();
let private_key_str: String;
let public_key_str: String;

match (in_format.as_str(), out_format.as_str()) {
("mnemonic", "pem") => match infile {
Some(file) => {
mnemonic_str = fs::read_to_string(file).unwrap();
mnemonic_str = mnemonic_str.replace('\n', "");
(private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic_str);
write_resulted_pem(&public_key_str, &private_key_str, outfile);
},
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()
_ = io::stdin().read_to_string(&mut mnemonic_str).unwrap();
(private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic_str);
write_resulted_pem(&public_key_str, &private_key_str, outfile);
},
},
("keystore-secret", "pem") => match infile {
Some(file) => {
let private_key = Wallet::get_private_key_from_keystore_secret(
file,
&Wallet::get_keystore_password(),
)
.unwrap();
private_key_str = private_key.to_string();
let public_key = PublicKey::from(&private_key);
public_key_str = public_key.to_string();
write_resulted_pem(&public_key_str, &private_key_str, outfile);
},
None => {
panic!("Input file is required for keystore-secret format");
},
},
("pem", "keystore-secret") => match infile {
Some(file) => {
let pem_decoded_keys = Wallet::get_pem_decoded_content(file);
(private_key_str, public_key_str) = Wallet::get_wallet_keys_pem(file);

let address = get_wallet_address(&private_key_str);
let hex_decoded_keys = hex::decode(pem_decoded_keys).unwrap();

let json_result = Wallet::encrypt_keystore(
hex_decoded_keys.as_slice(),
&address,
&public_key_str,
&Wallet::get_keystore_password(),
);
write_resulted_keystore(json_result, outfile);
},
None => {
panic!("Input file is required for pem format");
},
},
_ => {
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());

fn write_resulted_pem(public_key: &str, private_key: &str, outfile: Option<&String>) {
let address = get_wallet_address(private_key);
match outfile {
Some(outfile) => {
generate_pem(
&address,
private_key_str.as_str(),
public_key_str.as_str(),
outfile,
);
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();
},
None => {
let pem_content =
generate_pem_content(&address, private_key_str.as_str(), public_key_str.as_str());
let pem_content = generate_pem_content(&address, private_key, public_key);
print!("{}", pem_content);
},
}
}

fn write_resulted_keystore(json_result: String, outfile: Option<&String>) {
match outfile {
Some(outfile) => {
let mut file = File::create(outfile).unwrap();
file.write_all(json_result.as_bytes()).unwrap();
},
None => {
println!("{}", json_result);
},
}
}

fn bech32_conversion(bech32_args: &WalletBech32Args) {
let encode_address = bech32_args.hex_address.as_ref();
let decode_address = bech32_args.bech32_address.as_ref();
Expand All @@ -90,57 +137,45 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) {
}
}

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();

(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 format = new_args.wallet_format.as_deref();
let outfile = new_args.outfile.as_ref(); // Handle outfile as Option<&str> if it's an Option<String>
let mnemonic = Wallet::generate_mnemonic();
println!("Mnemonic: {}", mnemonic);

let (private_key_str, public_key_str) = get_wallet_keys(mnemonic);
let (private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic.to_string());
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.as_str(),
public_key_str.as_str(),
file,
);
},
("pem", None) => {
println!("Output file is required for PEM format");
},
_ => {},
}
match format {
Some("pem") => {
write_resulted_pem(public_key_str.as_str(), private_key_str.as_str(), outfile);
},
Some("keystore-secret") => {
let concatenated_keys = format!("{}{}", private_key_str, public_key_str);
let hex_decoded_keys = hex::decode(concatenated_keys).unwrap();
let json_result = Wallet::encrypt_keystore(
hex_decoded_keys.as_slice(),
&address,
&public_key_str,
&Wallet::get_keystore_password(),
);
write_resulted_keystore(json_result, outfile);
},
Some(_) => {
println!("Unsupported format");
},
None => {},
}
}

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 {
pub 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);

Expand Down
91 changes: 91 additions & 0 deletions framework/meta/tests/wallet_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::fs::{self, File};
use std::io::Write;

use multiversx_sc_meta::cmd::wallet::generate_pem_content;
use multiversx_sc_snippets::sdk::{crypto::public_key::PublicKey, data::address::Address};
use multiversx_sc_snippets::{hex, imports::Wallet};

const ALICE_PEM_PATH: &str = "../snippets/src/test_wallets/alice.pem";
const ALICE_KEYSTORE_PATH_TEST: &str = "alice.json";
const ALICE_PEM_PATH_TEST: &str = "alice_test.pem";
const KEYSTORE_PASSWORD: &str = "abcd";

fn create_keystore_from_pem(file: &str) {
let pem_decoded_keys = Wallet::get_pem_decoded_content(file);
let (private_key_str, public_key_str) = Wallet::get_wallet_keys_pem(file);

let address = Wallet::from_private_key(&private_key_str)
.unwrap()
.address();
let hex_decoded_keys = hex::decode(pem_decoded_keys).unwrap();

let json_result = Wallet::encrypt_keystore(
hex_decoded_keys.as_slice(),
&address,
&public_key_str,
KEYSTORE_PASSWORD,
);

write_to_file(&json_result, ALICE_KEYSTORE_PATH_TEST);
}

fn write_to_file(content: &str, file: &str) {
let mut file = File::create(file).unwrap();
file.write_all(content.as_bytes()).unwrap();
}

fn create_keystore_file_from_scratch() -> Address {
let mnemonic = Wallet::generate_mnemonic();
let (private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic.to_string());
let wallet = Wallet::from_private_key(&private_key_str).unwrap();
let address = wallet.address();

let concatenated_keys = format!("{}{}", private_key_str, public_key_str);
let hex_decoded_keys = hex::decode(concatenated_keys).unwrap();
let json_result = Wallet::encrypt_keystore(
hex_decoded_keys.as_slice(),
&address,
&public_key_str,
KEYSTORE_PASSWORD,
);
write_to_file(&json_result, ALICE_KEYSTORE_PATH_TEST);
address
}

#[test]
fn test_wallet_convert_pem_to_keystore() {
create_keystore_from_pem(ALICE_PEM_PATH);
let (private_key_pem, _public_key_pem) = Wallet::get_wallet_keys_pem(ALICE_PEM_PATH);
assert_eq!(
Wallet::get_private_key_from_keystore_secret(ALICE_KEYSTORE_PATH_TEST, KEYSTORE_PASSWORD)
.unwrap()
.to_string(),
private_key_pem
);
fs::remove_file(ALICE_KEYSTORE_PATH_TEST).unwrap();
}

#[test]
fn test_wallet_convert_keystore_to_pem() {
let address = create_keystore_file_from_scratch();

let private_key =
Wallet::get_private_key_from_keystore_secret(ALICE_KEYSTORE_PATH_TEST, KEYSTORE_PASSWORD)
.unwrap();
let private_key_str = private_key.to_string();
let public_key = PublicKey::from(&private_key);
let public_key_str = public_key.to_string();

let pem_content = generate_pem_content(&address, &private_key_str, &public_key_str);
write_to_file(&pem_content, ALICE_PEM_PATH_TEST);

assert_eq!(
Wallet::get_private_key_from_keystore_secret(ALICE_KEYSTORE_PATH_TEST, KEYSTORE_PASSWORD)
.unwrap()
.to_string(),
Wallet::get_wallet_keys_pem(ALICE_PEM_PATH_TEST).0
);

fs::remove_file(ALICE_PEM_PATH_TEST).unwrap();
fs::remove_file(ALICE_KEYSTORE_PATH_TEST).unwrap();
}
3 changes: 2 additions & 1 deletion sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ pem = "3.0.2"
log = "0.4.17"
scrypt = "0.11"
aes = "0.8"
ctr = "0.9.2"
ctr = "0.9.2"
uuid = {version = "1.10.0", features = ["v4"]}
8 changes: 7 additions & 1 deletion sdk/core/src/data/keystore.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use serde::{Deserialize, Serialize};

pub const KDF_N: u32 = 4096;
pub const KDF_R: u32 = 8;
pub const KDF_P: u32 = 1;
pub const KDF_DKLEN: usize = 32;
pub const KEYSTORE_VERSION: u32 = 4;

#[derive(Debug)]
pub enum WalletError {
InvalidPassword,
Expand Down Expand Up @@ -44,5 +50,5 @@ pub struct Keystore {
pub struct DecryptionParams {
pub derived_key_first_half: Vec<u8>,
pub iv: Vec<u8>,
pub ciphertext: Vec<u8>,
pub data: Vec<u8>,
}
Loading
Loading