From 29d88c37ead910b914b08b47b2212b723d5afe29 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 14 Aug 2024 20:19:29 +0300 Subject: [PATCH 01/15] draft keystore json password check --- Cargo.lock | 81 +++++++++++++++++++++++++++++++++++------- sdk/core/Cargo.toml | 1 + sdk/core/src/wallet.rs | 75 +++++++++++++++++++++++++++++++++++--- 3 files changed, 139 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08f2acc6ae..8a39cc6185 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,15 +197,15 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64" -version = "0.22.1" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-features" @@ -435,6 +435,16 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.11" @@ -1527,6 +1537,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "interact" version = "0.0.0" @@ -1994,7 +2013,10 @@ dependencies = [ name = "multiversx-sc-meta" version = "0.52.3" dependencies = [ - "base64 0.13.1", +<<<<<<< Updated upstream +======= + "bip39", +>>>>>>> Stashed changes "clap", "colored", "common-path", @@ -2003,7 +2025,6 @@ dependencies = [ "multiversx-sc", "multiversx-sc-meta-lib", "multiversx-sc-snippets", - "multiversx-sdk", "pathdiff", "reqwest", "ruplacer", @@ -2045,7 +2066,7 @@ dependencies = [ name = "multiversx-sc-scenario" version = "0.52.3" dependencies = [ - "base64 0.22.1", + "base64", "bech32", "colored", "hex", @@ -2069,7 +2090,7 @@ dependencies = [ name = "multiversx-sc-snippets" version = "0.52.3" dependencies = [ - "base64 0.22.1", + "base64", "env_logger", "futures", "hex", @@ -2093,7 +2114,7 @@ name = "multiversx-sdk" version = "0.5.0" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bech32", "bip39", "hex", @@ -2104,6 +2125,7 @@ dependencies = [ "pem", "rand 0.8.5", "reqwest", + "scrypt", "serde", "serde_json", "serde_repr", @@ -2390,6 +2412,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -2423,6 +2456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "hmac", ] [[package]] @@ -2431,7 +2465,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.1", + "base64", "serde", ] @@ -2763,7 +2797,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -2933,7 +2967,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -2960,6 +2994,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3000,6 +3043,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.8", +] + [[package]] name = "second-contract" version = "0.0.0" diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index 463b4ce130..c198548239 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -35,3 +35,4 @@ bech32 = "0.9" itertools = "0.13.0" pem = "3.0.2" log = "0.4.17" +scrypt = "0.11" \ No newline at end of file diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index ab1b0a2f37..4177d42967 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,11 +1,18 @@ extern crate rand; +use core::str; +use std::{ + fs::{self, File}, + io::{self, Read}, +}; + use anyhow::Result; use bip39::{Language, Mnemonic}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; -use serde_json::json; -use sha2::{Digest, Sha512}; +use scrypt::{scrypt, Params}; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; use zeroize::Zeroize; @@ -20,7 +27,8 @@ use crate::{ const EGLD_COIN_TYPE: u32 = 508; const HARDENED: u32 = 0x80000000; -type HmacSha521 = Hmac; +type HmacSha512 = Hmac; +type HmacSha256 = Hmac; #[derive(Copy, Clone, Debug)] pub struct Wallet { @@ -63,7 +71,7 @@ impl Wallet { let hardened_child_padding: u8 = 0; let mut digest = - HmacSha521::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); digest.update(&seed); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); let mut key = intermediary[..serialized_key_len].to_vec(); @@ -83,7 +91,7 @@ impl Wallet { buff.push(child_idx as u8); digest = - HmacSha521::new_from_slice(&chain_code).expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size"); digest.update(&buff); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); key = intermediary[..serialized_key_len].to_vec(); @@ -131,4 +139,61 @@ impl Wallet { self.priv_key.sign(tx_bytes) } + + pub fn validate_keystore_password(path: &str) { + println!( + "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." + ); + let mut password = String::new(); + _ = io::stdin().read_to_string(&mut password).unwrap(); + password = password.replace('\n', ""); + print!("{:?}", password); + + let keystore_file = File::open(path); + match keystore_file { + Ok(_) => { + let json_body = fs::read_to_string(path).unwrap(); + let keystore: Value = serde_json::from_str(&json_body).unwrap(); + let ciphertext = keystore["crypto"]["ciphertext"] + .as_str() + .unwrap() + .as_bytes(); + let _iv = keystore["crypto"]["cipherparams"]["iv"] + .as_str() + .unwrap() + .as_bytes(); + let salt = keystore["crypto"]["kdfparams"]["salt"] + .as_str() + .unwrap() + .as_bytes(); + + let json_mac: &[u8] = keystore["crypto"]["mac"].as_str().unwrap().as_bytes(); + let kdfparams = &keystore["crypto"]["kdfparams"]; + let n = kdfparams["n"].as_f64().unwrap(); + let r = kdfparams["r"].as_u64().unwrap(); + let p = kdfparams["p"].as_u64().unwrap(); + let dklen = kdfparams["dklen"].as_u64().unwrap() as usize; + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); + + let mut derived_key = vec![0u8; 32]; + + _ = scrypt(password.as_bytes(), salt, ¶ms, &mut derived_key).unwrap(); + + let _derived_key_first_half = &derived_key[0..16]; + let derived_key_second_half = &derived_key[16..32]; + + let mut input_mac = HmacSha256::new_from_slice(derived_key_second_half).unwrap(); + input_mac.update(ciphertext); + let computed_mac = input_mac.finalize(); + let mac_bytes = computed_mac.into_bytes(); + let hex_mac = hex::encode(mac_bytes); + + println!("{:?}", hex_mac.as_bytes()); + println!("{:?}", json_mac); + }, + Err(e) => { + println!("Error: {}", e); + }, + } + } } From 75d327321dde0676b89c413970b95bedd7ec6737 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Thu, 15 Aug 2024 16:23:21 +0300 Subject: [PATCH 02/15] password verfication fixed --- sdk/core/src/wallet.rs | 46 +++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 4177d42967..11a2ff8a3a 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -139,7 +139,7 @@ impl Wallet { self.priv_key.sign(tx_bytes) } - + // fd56d1c4d149ac397af292796100f347f0f47a8f52b9683741d8a90db872450e pub fn validate_keystore_password(path: &str) { println!( "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." @@ -147,49 +147,45 @@ impl Wallet { let mut password = String::new(); _ = io::stdin().read_to_string(&mut password).unwrap(); password = password.replace('\n', ""); - print!("{:?}", password); let keystore_file = File::open(path); match keystore_file { Ok(_) => { let json_body = fs::read_to_string(path).unwrap(); let keystore: Value = serde_json::from_str(&json_body).unwrap(); - let ciphertext = keystore["crypto"]["ciphertext"] - .as_str() - .unwrap() - .as_bytes(); - let _iv = keystore["crypto"]["cipherparams"]["iv"] - .as_str() - .unwrap() - .as_bytes(); - let salt = keystore["crypto"]["kdfparams"]["salt"] - .as_str() - .unwrap() - .as_bytes(); - - let json_mac: &[u8] = keystore["crypto"]["mac"].as_str().unwrap().as_bytes(); + + let ciphertext = + hex::decode(keystore["crypto"]["ciphertext"].as_str().unwrap()).unwrap(); + let _iv = hex::decode(keystore["crypto"]["cipherparams"]["iv"].as_str().unwrap()) + .unwrap(); + let salt = + hex::decode(keystore["crypto"]["kdfparams"]["salt"].as_str().unwrap()).unwrap(); + + let json_mac = hex::decode(keystore["crypto"]["mac"].as_str().unwrap()).unwrap(); + let kdfparams = &keystore["crypto"]["kdfparams"]; let n = kdfparams["n"].as_f64().unwrap(); let r = kdfparams["r"].as_u64().unwrap(); let p = kdfparams["p"].as_u64().unwrap(); let dklen = kdfparams["dklen"].as_u64().unwrap() as usize; + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); let mut derived_key = vec![0u8; 32]; - - _ = scrypt(password.as_bytes(), salt, ¶ms, &mut derived_key).unwrap(); + _ = scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); let _derived_key_first_half = &derived_key[0..16]; let derived_key_second_half = &derived_key[16..32]; let mut input_mac = HmacSha256::new_from_slice(derived_key_second_half).unwrap(); - input_mac.update(ciphertext); - let computed_mac = input_mac.finalize(); - let mac_bytes = computed_mac.into_bytes(); - let hex_mac = hex::encode(mac_bytes); - - println!("{:?}", hex_mac.as_bytes()); - println!("{:?}", json_mac); + input_mac.update(&ciphertext); + let computed_mac = input_mac.finalize().into_bytes(); + + if computed_mac.as_slice() == json_mac.as_slice() { + println!("Password is correct"); + } else { + println!("Password is incorrect"); + } }, Err(e) => { println!("Error: {}", e); From d85ffa57558cd227da00eb5a5837c6083b873003 Mon Sep 17 00:00:00 2001 From: JustEatAnApple Date: Mon, 19 Aug 2024 09:44:16 +0300 Subject: [PATCH 03/15] [WIP] Sign with keystore --- Cargo.lock | 22 +++++++ sdk/core/Cargo.toml | 4 +- sdk/core/src/data/keystore.rs | 35 ++++++++++ sdk/core/src/data/mod.rs | 1 + sdk/core/src/wallet.rs | 121 ++++++++++++++++++++-------------- 5 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 sdk/core/src/data/keystore.rs diff --git a/Cargo.lock b/Cargo.lock index 8a39cc6185..ab52414004 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -680,6 +691,15 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -2113,10 +2133,12 @@ dependencies = [ name = "multiversx-sdk" version = "0.5.0" dependencies = [ + "aes", "anyhow", "base64", "bech32", "bip39", + "ctr", "hex", "hmac", "itertools", diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index c198548239..32e170618f 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -35,4 +35,6 @@ bech32 = "0.9" itertools = "0.13.0" pem = "3.0.2" log = "0.4.17" -scrypt = "0.11" \ No newline at end of file +scrypt = "0.11" +aes = "0.8" +ctr = "0.9.2" \ No newline at end of file diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs new file mode 100644 index 0000000000..fd3a618ce7 --- /dev/null +++ b/sdk/core/src/data/keystore.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CryptoParams { + pub iv: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KdfParams { + pub dklen: u32, + pub salt: String, + pub n: u32, + pub r: u32, + pub p: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Crypto { + pub ciphertext: String, + pub cipherparams: CryptoParams, + pub cipher: String, + pub kdf: String, + pub kdfparams: KdfParams, + pub mac: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Keystore { + pub version: u32, + pub kind: String, + pub id: String, + pub address: String, + pub bech32: String, + pub crypto: Crypto, +} \ No newline at end of file diff --git a/sdk/core/src/data/mod.rs b/sdk/core/src/data/mod.rs index 797ec6e1ce..9a270211df 100644 --- a/sdk/core/src/data/mod.rs +++ b/sdk/core/src/data/mod.rs @@ -3,6 +3,7 @@ pub mod account_storage; pub mod address; pub mod esdt; pub mod hyperblock; +pub mod keystore; pub mod network_config; pub mod network_economics; pub mod network_status; diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 11a2ff8a3a..4b664fe4fd 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,17 +1,18 @@ extern crate rand; -use core::str; use std::{ fs::{self, File}, io::{self, Read}, }; +use aes::{cipher::KeyIvInit, Aes128}; use anyhow::Result; use bip39::{Language, Mnemonic}; +use ctr::{cipher::StreamCipher, Ctr128BE}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; use scrypt::{scrypt, Params}; -use serde_json::{json, Value}; +use serde_json::json; use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; use zeroize::Zeroize; @@ -21,7 +22,7 @@ use crate::{ private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, public_key::PublicKey, }, - data::{address::Address, transaction::Transaction}, + data::{address::Address, keystore::Keystore, transaction::Transaction}, }; const EGLD_COIN_TYPE: u32 = 508; @@ -119,6 +120,13 @@ impl Wallet { Ok(Self { priv_key: pri_key }) } + // HeadCanon + pub fn from_keystore_secret(file_path: &str) -> Result { + let (x, y, z) = Self::validate_keystore_password(file_path).unwrap(); + let priv_key = PrivateKey::from_hex_str(Self::decrypt_secret_key(&x, &y,& z).as_str())?; + Ok(Self {priv_key: priv_key}) + } + pub fn address(&self) -> Address { let public_key = PublicKey::from(&self.priv_key); Address::from(&public_key) @@ -139,57 +147,70 @@ impl Wallet { self.priv_key.sign(tx_bytes) } - // fd56d1c4d149ac397af292796100f347f0f47a8f52b9683741d8a90db872450e - pub fn validate_keystore_password(path: &str) { + + pub fn validate_keystore_password(path: &str) -> Result<(Vec, Vec, Vec), ()> { println!( "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." ); let mut password = String::new(); - _ = io::stdin().read_to_string(&mut password).unwrap(); - password = password.replace('\n', ""); - - let keystore_file = File::open(path); - match keystore_file { - Ok(_) => { - let json_body = fs::read_to_string(path).unwrap(); - let keystore: Value = serde_json::from_str(&json_body).unwrap(); - - let ciphertext = - hex::decode(keystore["crypto"]["ciphertext"].as_str().unwrap()).unwrap(); - let _iv = hex::decode(keystore["crypto"]["cipherparams"]["iv"].as_str().unwrap()) - .unwrap(); - let salt = - hex::decode(keystore["crypto"]["kdfparams"]["salt"].as_str().unwrap()).unwrap(); - - let json_mac = hex::decode(keystore["crypto"]["mac"].as_str().unwrap()).unwrap(); - - let kdfparams = &keystore["crypto"]["kdfparams"]; - let n = kdfparams["n"].as_f64().unwrap(); - let r = kdfparams["r"].as_u64().unwrap(); - let p = kdfparams["p"].as_u64().unwrap(); - let dklen = kdfparams["dklen"].as_u64().unwrap() as usize; - - let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); - - let mut derived_key = vec![0u8; 32]; - _ = scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); - - let _derived_key_first_half = &derived_key[0..16]; - let derived_key_second_half = &derived_key[16..32]; - - let mut input_mac = HmacSha256::new_from_slice(derived_key_second_half).unwrap(); - input_mac.update(&ciphertext); - let computed_mac = input_mac.finalize().into_bytes(); - - if computed_mac.as_slice() == json_mac.as_slice() { - println!("Password is correct"); - } else { - println!("Password is incorrect"); - } - }, - Err(e) => { - println!("Error: {}", e); - }, + io::stdin().read_to_string(&mut password).map_err(|_| ())?; + password = password.trim().to_string(); + + let _ = File::open(path).map_err(|e| { + println!("Error: {}", e); + () + })?; + + let json_body = fs::read_to_string(path).map_err(|_| ())?; + let keystore: Keystore = serde_json::from_str(&json_body).map_err(|_| ())?; + + let ciphertext = hex::decode(&keystore.crypto.ciphertext).map_err(|_| ())?; + let iv = hex::decode(&keystore.crypto.cipherparams.iv).map_err(|_| ())?; + let salt = hex::decode(&keystore.crypto.kdfparams.salt).map_err(|_| ())?; + let json_mac = hex::decode(&keystore.crypto.mac).map_err(|_| ())?; + + let n = keystore.crypto.kdfparams.n as f64; + let r = keystore.crypto.kdfparams.r as u64; + let p = keystore.crypto.kdfparams.p as u64; + let dklen = keystore.crypto.kdfparams.dklen as usize; + + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).map_err(|_| ())?; + + let mut derived_key = vec![0u8; 32]; + scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).map_err(|_| ())?; + + let derived_key_first_half = derived_key[0..16].to_vec(); + let derived_key_second_half = derived_key[16..32].to_vec(); + + let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).map_err(|_| ())?; + input_mac.update(&ciphertext); + let computed_mac = input_mac.finalize().into_bytes(); + + if computed_mac.as_slice() == json_mac.as_slice() { + println!("Password is correct"); + Self::decrypt_secret_key(&derived_key_first_half, &iv, &ciphertext); + Ok((derived_key_first_half, iv, ciphertext)) + } else { + println!("Password is incorrect"); + Err(()) } } + + + // MjNhZjk1NTY4MmQxNmRiNDZhZjY1ZjkwOTY2NjZjMWU1N2Y4NTY2MDBlMzAyMzhlNTFmMDMyZTZjYjVlOWQyODkwYTQ5YzQ1OGMyMTU5NDUzMTQxZDU0YWM0YWE0M2FmZjhiZWUwMDJhMzQyM2E3NTkyNWY0NjUyOGVkYmQxYmM= + // MjNhZjk1NTY4MmQxNmRiNDZhZjY1ZjkwOTY2NjZjMWU1N2Y4NTY2MDBlMzAyMzhlNTFmMDMyZTZjYjVlOWQyODkwYTQ5YzQ1OGMyMTU5NDUzMTQxZDU0YWM0YWE0M2FmZjhiZWUwMDJhMzQyM2E3NTkyNWY0NjUyOGVkYmQxYmM= + pub fn decrypt_secret_key( + key_first_half: &Vec, + iv: &Vec, + ciphertext: &Vec, + ) -> String { + let mut cipher = Ctr128BE::::new(key_first_half.as_slice().into(), iv.as_slice().into()); + let mut decrypted = ciphertext.to_vec(); + cipher.apply_keystream(&mut decrypted); + // This is the secret key from the pem -> base64::encode(hex::encode(decrypted)); + // println!("MBLAC PV_KEY: {}", base64::encode(hex::encode(decrypted))); + "ok".to_string() + } + + } From 51c98beec8f5b4064230fb2547d8ee434055b672 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 19 Aug 2024 13:59:16 +0300 Subject: [PATCH 04/15] keystore tx sign done --- sdk/core/src/wallet.rs | 114 ++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 4b664fe4fd..0519c0413d 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,7 +1,7 @@ extern crate rand; use std::{ - fs::{self, File}, + fs::{self}, io::{self, Read}, }; @@ -27,6 +27,8 @@ use crate::{ const EGLD_COIN_TYPE: u32 = 508; const HARDENED: u32 = 0x80000000; +const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; +const KDF_SCRYPT: &str = "scrypt"; type HmacSha512 = Hmac; type HmacSha256 = Hmac; @@ -36,6 +38,20 @@ pub struct Wallet { priv_key: PrivateKey, } +#[derive(Clone, Debug)] +pub struct DecryptionParams { + pub derived_key_first_half: Vec, + pub iv: Vec, + pub ciphertext: Vec, +} + +#[derive(Debug)] +pub enum WalletError { + InvalidPassword, + InvalidKdf, + InvalidCipher, +} + impl Wallet { // GenerateMnemonic will generate a new mnemonic value using the bip39 implementation pub fn generate_mnemonic() -> Mnemonic { @@ -120,11 +136,13 @@ impl Wallet { Ok(Self { priv_key: pri_key }) } - // HeadCanon pub fn from_keystore_secret(file_path: &str) -> Result { - let (x, y, z) = Self::validate_keystore_password(file_path).unwrap(); - let priv_key = PrivateKey::from_hex_str(Self::decrypt_secret_key(&x, &y,& z).as_str())?; - Ok(Self {priv_key: priv_key}) + let decyption_params = Self::validate_keystore_password(file_path).unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); + let priv_key = + PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; + Ok(Self { priv_key }) } pub fn address(&self) -> Address { @@ -148,69 +166,69 @@ impl Wallet { self.priv_key.sign(tx_bytes) } - pub fn validate_keystore_password(path: &str) -> Result<(Vec, Vec, Vec), ()> { + pub fn validate_keystore_password(path: &str) -> Result { println!( "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." ); let mut password = String::new(); - io::stdin().read_to_string(&mut password).map_err(|_| ())?; + io::stdin().read_to_string(&mut password).unwrap(); password = password.trim().to_string(); - - let _ = File::open(path).map_err(|e| { - println!("Error: {}", e); - () - })?; - - let json_body = fs::read_to_string(path).map_err(|_| ())?; - let keystore: Keystore = serde_json::from_str(&json_body).map_err(|_| ())?; - - let ciphertext = hex::decode(&keystore.crypto.ciphertext).map_err(|_| ())?; - let iv = hex::decode(&keystore.crypto.cipherparams.iv).map_err(|_| ())?; - let salt = hex::decode(&keystore.crypto.kdfparams.salt).map_err(|_| ())?; - let json_mac = hex::decode(&keystore.crypto.mac).map_err(|_| ())?; - + + let json_body = fs::read_to_string(path).unwrap(); + let keystore: Keystore = serde_json::from_str(&json_body).unwrap(); + let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap(); + + let cipher = &keystore.crypto.cipher; + if cipher != CIPHER_ALGORITHM_AES_128_CTR { + return Err(WalletError::InvalidCipher); + } + + let iv = hex::decode(&keystore.crypto.cipherparams.iv).unwrap(); + let salt = hex::decode(&keystore.crypto.kdfparams.salt).unwrap(); + let json_mac = hex::decode(&keystore.crypto.mac).unwrap(); + + let kdf = &keystore.crypto.kdf; + if kdf != KDF_SCRYPT { + return Err(WalletError::InvalidKdf); + } let n = keystore.crypto.kdfparams.n as f64; let r = keystore.crypto.kdfparams.r as u64; let p = keystore.crypto.kdfparams.p as u64; let dklen = keystore.crypto.kdfparams.dklen as usize; - - let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).map_err(|_| ())?; - + + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); + let mut derived_key = vec![0u8; 32]; - scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).map_err(|_| ())?; - + scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); + let derived_key_first_half = derived_key[0..16].to_vec(); let derived_key_second_half = derived_key[16..32].to_vec(); - - let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).map_err(|_| ())?; + + let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); input_mac.update(&ciphertext); let computed_mac = input_mac.finalize().into_bytes(); - + if computed_mac.as_slice() == json_mac.as_slice() { println!("Password is correct"); - Self::decrypt_secret_key(&derived_key_first_half, &iv, &ciphertext); - Ok((derived_key_first_half, iv, ciphertext)) + Ok(DecryptionParams { + derived_key_first_half, + iv, + ciphertext, + }) } else { println!("Password is incorrect"); - Err(()) + Err(WalletError::InvalidPassword) } } - - - // MjNhZjk1NTY4MmQxNmRiNDZhZjY1ZjkwOTY2NjZjMWU1N2Y4NTY2MDBlMzAyMzhlNTFmMDMyZTZjYjVlOWQyODkwYTQ5YzQ1OGMyMTU5NDUzMTQxZDU0YWM0YWE0M2FmZjhiZWUwMDJhMzQyM2E3NTkyNWY0NjUyOGVkYmQxYmM= - // MjNhZjk1NTY4MmQxNmRiNDZhZjY1ZjkwOTY2NjZjMWU1N2Y4NTY2MDBlMzAyMzhlNTFmMDMyZTZjYjVlOWQyODkwYTQ5YzQ1OGMyMTU5NDUzMTQxZDU0YWM0YWE0M2FmZjhiZWUwMDJhMzQyM2E3NTkyNWY0NjUyOGVkYmQxYmM= - pub fn decrypt_secret_key( - key_first_half: &Vec, - iv: &Vec, - ciphertext: &Vec, - ) -> String { - let mut cipher = Ctr128BE::::new(key_first_half.as_slice().into(), iv.as_slice().into()); - let mut decrypted = ciphertext.to_vec(); - cipher.apply_keystream(&mut decrypted); - // This is the secret key from the pem -> base64::encode(hex::encode(decrypted)); - // println!("MBLAC PV_KEY: {}", base64::encode(hex::encode(decrypted))); - "ok".to_string() - } + pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> String { + let mut cipher = Ctr128BE::::new( + decryption_params.derived_key_first_half.as_slice().into(), + decryption_params.iv.as_slice().into(), + ); + let mut decrypted = decryption_params.ciphertext.to_vec(); + cipher.apply_keystream(&mut decrypted); + hex::encode(decrypted).to_string() + } } From b08efaa3689f288df4d93b268142fb9b7da6ab33 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 19 Aug 2024 17:19:21 +0300 Subject: [PATCH 05/15] update cargo lock --- Cargo.lock | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab52414004..f0558fc8b1 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2033,10 +2033,7 @@ dependencies = [ name = "multiversx-sc-meta" version = "0.52.3" dependencies = [ -<<<<<<< Updated upstream -======= "bip39", ->>>>>>> Stashed changes "clap", "colored", "common-path", From fd3bda9284cc0729593af8a88cd657374b622735 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 14 Aug 2024 20:19:29 +0300 Subject: [PATCH 06/15] keystore signing done --- Cargo.lock | 100 +++++++++++++++++++++++++++---- sdk/core/Cargo.toml | 3 + sdk/core/src/data/keystore.rs | 35 +++++++++++ sdk/core/src/data/mod.rs | 1 + sdk/core/src/wallet.rs | 110 ++++++++++++++++++++++++++++++++-- 5 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 sdk/core/src/data/keystore.rs diff --git a/Cargo.lock b/Cargo.lock index 08f2acc6ae..f0558fc8b1 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -197,15 +208,15 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64" -version = "0.22.1" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-features" @@ -435,6 +446,16 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.11" @@ -670,6 +691,15 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1527,6 +1557,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "interact" version = "0.0.0" @@ -1994,7 +2033,7 @@ dependencies = [ name = "multiversx-sc-meta" version = "0.52.3" dependencies = [ - "base64 0.13.1", + "bip39", "clap", "colored", "common-path", @@ -2003,7 +2042,6 @@ dependencies = [ "multiversx-sc", "multiversx-sc-meta-lib", "multiversx-sc-snippets", - "multiversx-sdk", "pathdiff", "reqwest", "ruplacer", @@ -2045,7 +2083,7 @@ dependencies = [ name = "multiversx-sc-scenario" version = "0.52.3" dependencies = [ - "base64 0.22.1", + "base64", "bech32", "colored", "hex", @@ -2069,7 +2107,7 @@ dependencies = [ name = "multiversx-sc-snippets" version = "0.52.3" dependencies = [ - "base64 0.22.1", + "base64", "env_logger", "futures", "hex", @@ -2092,10 +2130,12 @@ dependencies = [ name = "multiversx-sdk" version = "0.5.0" dependencies = [ + "aes", "anyhow", - "base64 0.22.1", + "base64", "bech32", "bip39", + "ctr", "hex", "hmac", "itertools", @@ -2104,6 +2144,7 @@ dependencies = [ "pem", "rand 0.8.5", "reqwest", + "scrypt", "serde", "serde_json", "serde_repr", @@ -2390,6 +2431,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -2423,6 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "hmac", ] [[package]] @@ -2431,7 +2484,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.1", + "base64", "serde", ] @@ -2763,7 +2816,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -2933,7 +2986,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -2960,6 +3013,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3000,6 +3062,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.8", +] + [[package]] name = "second-contract" version = "0.0.0" diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index 463b4ce130..32e170618f 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -35,3 +35,6 @@ bech32 = "0.9" itertools = "0.13.0" pem = "3.0.2" log = "0.4.17" +scrypt = "0.11" +aes = "0.8" +ctr = "0.9.2" \ No newline at end of file diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs new file mode 100644 index 0000000000..fd3a618ce7 --- /dev/null +++ b/sdk/core/src/data/keystore.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CryptoParams { + pub iv: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KdfParams { + pub dklen: u32, + pub salt: String, + pub n: u32, + pub r: u32, + pub p: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Crypto { + pub ciphertext: String, + pub cipherparams: CryptoParams, + pub cipher: String, + pub kdf: String, + pub kdfparams: KdfParams, + pub mac: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Keystore { + pub version: u32, + pub kind: String, + pub id: String, + pub address: String, + pub bech32: String, + pub crypto: Crypto, +} \ No newline at end of file diff --git a/sdk/core/src/data/mod.rs b/sdk/core/src/data/mod.rs index 797ec6e1ce..9a270211df 100644 --- a/sdk/core/src/data/mod.rs +++ b/sdk/core/src/data/mod.rs @@ -3,6 +3,7 @@ pub mod account_storage; pub mod address; pub mod esdt; pub mod hyperblock; +pub mod keystore; pub mod network_config; pub mod network_economics; pub mod network_status; diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index ab1b0a2f37..0519c0413d 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,11 +1,19 @@ extern crate rand; +use std::{ + fs::{self}, + io::{self, Read}, +}; + +use aes::{cipher::KeyIvInit, Aes128}; use anyhow::Result; use bip39::{Language, Mnemonic}; +use ctr::{cipher::StreamCipher, Ctr128BE}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; +use scrypt::{scrypt, Params}; use serde_json::json; -use sha2::{Digest, Sha512}; +use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; use zeroize::Zeroize; @@ -14,19 +22,36 @@ use crate::{ private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, public_key::PublicKey, }, - data::{address::Address, transaction::Transaction}, + data::{address::Address, keystore::Keystore, transaction::Transaction}, }; const EGLD_COIN_TYPE: u32 = 508; const HARDENED: u32 = 0x80000000; +const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; +const KDF_SCRYPT: &str = "scrypt"; -type HmacSha521 = Hmac; +type HmacSha512 = Hmac; +type HmacSha256 = Hmac; #[derive(Copy, Clone, Debug)] pub struct Wallet { priv_key: PrivateKey, } +#[derive(Clone, Debug)] +pub struct DecryptionParams { + pub derived_key_first_half: Vec, + pub iv: Vec, + pub ciphertext: Vec, +} + +#[derive(Debug)] +pub enum WalletError { + InvalidPassword, + InvalidKdf, + InvalidCipher, +} + impl Wallet { // GenerateMnemonic will generate a new mnemonic value using the bip39 implementation pub fn generate_mnemonic() -> Mnemonic { @@ -63,7 +88,7 @@ impl Wallet { let hardened_child_padding: u8 = 0; let mut digest = - HmacSha521::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); digest.update(&seed); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); let mut key = intermediary[..serialized_key_len].to_vec(); @@ -83,7 +108,7 @@ impl Wallet { buff.push(child_idx as u8); digest = - HmacSha521::new_from_slice(&chain_code).expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size"); digest.update(&buff); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); key = intermediary[..serialized_key_len].to_vec(); @@ -111,6 +136,15 @@ impl Wallet { Ok(Self { priv_key: pri_key }) } + pub fn from_keystore_secret(file_path: &str) -> Result { + let decyption_params = Self::validate_keystore_password(file_path).unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); + let priv_key = + PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; + Ok(Self { priv_key }) + } + pub fn address(&self) -> Address { let public_key = PublicKey::from(&self.priv_key); Address::from(&public_key) @@ -131,4 +165,70 @@ impl Wallet { self.priv_key.sign(tx_bytes) } + + pub fn validate_keystore_password(path: &str) -> Result { + println!( + "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." + ); + let mut password = String::new(); + io::stdin().read_to_string(&mut password).unwrap(); + password = password.trim().to_string(); + + let json_body = fs::read_to_string(path).unwrap(); + let keystore: Keystore = serde_json::from_str(&json_body).unwrap(); + let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap(); + + let cipher = &keystore.crypto.cipher; + if cipher != CIPHER_ALGORITHM_AES_128_CTR { + return Err(WalletError::InvalidCipher); + } + + let iv = hex::decode(&keystore.crypto.cipherparams.iv).unwrap(); + let salt = hex::decode(&keystore.crypto.kdfparams.salt).unwrap(); + let json_mac = hex::decode(&keystore.crypto.mac).unwrap(); + + let kdf = &keystore.crypto.kdf; + if kdf != KDF_SCRYPT { + return Err(WalletError::InvalidKdf); + } + let n = keystore.crypto.kdfparams.n as f64; + let r = keystore.crypto.kdfparams.r as u64; + let p = keystore.crypto.kdfparams.p as u64; + let dklen = keystore.crypto.kdfparams.dklen as usize; + + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); + + let mut derived_key = vec![0u8; 32]; + scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); + + let derived_key_first_half = derived_key[0..16].to_vec(); + let derived_key_second_half = derived_key[16..32].to_vec(); + + let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + input_mac.update(&ciphertext); + let computed_mac = input_mac.finalize().into_bytes(); + + if computed_mac.as_slice() == json_mac.as_slice() { + println!("Password is correct"); + Ok(DecryptionParams { + derived_key_first_half, + iv, + ciphertext, + }) + } else { + println!("Password is incorrect"); + Err(WalletError::InvalidPassword) + } + } + + pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> String { + let mut cipher = Ctr128BE::::new( + decryption_params.derived_key_first_half.as_slice().into(), + decryption_params.iv.as_slice().into(), + ); + let mut decrypted = decryption_params.ciphertext.to_vec(); + cipher.apply_keystream(&mut decrypted); + + hex::encode(decrypted).to_string() + } } From bcdf79332de7c1e8be28f02bf05e68f60cdd5465 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 19 Aug 2024 18:54:14 +0300 Subject: [PATCH 07/15] keystore pem conversion --- framework/meta/src/cmd/wallet.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 7fcf28899a..f08501ce03 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -27,26 +27,38 @@ fn convert(convert_args: &WalletConvertArgs) { let out_format = &convert_args.to; let mut mnemonic_str = String::new(); + let mut private_key_str = String::new(); + let mut public_key_str = String::new(); 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', ""); + let mnemonic = Mnemonic::parse(mnemonic_str).unwrap(); + (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic); }, 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() }, }, + ("keystore-secret", "pem") => match infile { + Some(file) => { + let private_key = Wallet::get_private_key_from_keystore_secret(file).unwrap(); + private_key_str = private_key.to_string(); + let public_key = PublicKey::from(&private_key); + public_key_str = public_key.to_string(); + }, + None => { + panic!("Input file is required for keystore-secret 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()); match outfile { @@ -90,7 +102,7 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) { } } -fn get_wallet_keys(mnemonic: Mnemonic) -> (String, String) { +fn get_wallet_keys_mnemonic(mnemonic: Mnemonic) -> (String, String) { let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); let public_key = PublicKey::from(&private_key); @@ -111,7 +123,7 @@ fn new(new_args: &WalletNewArgs) { 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) = get_wallet_keys_mnemonic(mnemonic); let address = get_wallet_address(private_key_str.as_str()); println!("Wallet address: {}", address); From 3c40fe8327385d51f3845671a0c9709e6cd0c482 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Tue, 20 Aug 2024 10:40:24 +0300 Subject: [PATCH 08/15] password prompt separated --- sdk/core/src/data/keystore.rs | 15 ++++++++++- sdk/core/src/wallet.rs | 47 +++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs index fd3a618ce7..2564d70a2e 100644 --- a/sdk/core/src/data/keystore.rs +++ b/sdk/core/src/data/keystore.rs @@ -1,5 +1,11 @@ use serde::{Deserialize, Serialize}; +#[derive(Debug)] +pub enum WalletError { + InvalidPassword, + InvalidKdf, + InvalidCipher, +} #[derive(Debug, Serialize, Deserialize)] pub struct CryptoParams { pub iv: String, @@ -32,4 +38,11 @@ pub struct Keystore { pub address: String, pub bech32: String, pub crypto: Crypto, -} \ No newline at end of file +} + +#[derive(Clone, Debug)] +pub struct DecryptionParams { + pub derived_key_first_half: Vec, + pub iv: Vec, + pub ciphertext: Vec, +} diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 0519c0413d..b2856b74da 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -22,7 +22,11 @@ use crate::{ private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, public_key::PublicKey, }, - data::{address::Address, keystore::Keystore, transaction::Transaction}, + data::{ + address::Address, + keystore::{DecryptionParams, Keystore, WalletError}, + transaction::Transaction, + }, }; const EGLD_COIN_TYPE: u32 = 508; @@ -38,20 +42,6 @@ pub struct Wallet { priv_key: PrivateKey, } -#[derive(Clone, Debug)] -pub struct DecryptionParams { - pub derived_key_first_half: Vec, - pub iv: Vec, - pub ciphertext: Vec, -} - -#[derive(Debug)] -pub enum WalletError { - InvalidPassword, - InvalidKdf, - InvalidCipher, -} - impl Wallet { // GenerateMnemonic will generate a new mnemonic value using the bip39 implementation pub fn generate_mnemonic() -> Mnemonic { @@ -137,14 +127,27 @@ impl Wallet { } pub fn from_keystore_secret(file_path: &str) -> Result { - let decyption_params = Self::validate_keystore_password(file_path).unwrap_or_else(|e| { - panic!("Error: {:?}", e); - }); + let decyption_params = + Self::validate_keystore_password(file_path, Self::get_keystore_password()) + .unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); let priv_key = PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; Ok(Self { priv_key }) } + pub fn get_private_key_from_keystore_secret(file_path: &str) -> Result { + let decyption_params = + Self::validate_keystore_password(file_path, Self::get_keystore_password()) + .unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); + let priv_key = + PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; + Ok(priv_key) + } + pub fn address(&self) -> Address { let public_key = PublicKey::from(&self.priv_key); Address::from(&public_key) @@ -166,14 +169,20 @@ impl Wallet { self.priv_key.sign(tx_bytes) } - pub fn validate_keystore_password(path: &str) -> Result { + fn get_keystore_password() -> String { println!( "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." ); let mut password = String::new(); io::stdin().read_to_string(&mut password).unwrap(); password = password.trim().to_string(); + password + } + pub fn validate_keystore_password( + path: &str, + password: String, + ) -> Result { let json_body = fs::read_to_string(path).unwrap(); let keystore: Keystore = serde_json::from_str(&json_body).unwrap(); let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap(); From 8cd7b602a1a9fa7723cac150cb73feafaffe51e5 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 19 Aug 2024 13:59:16 +0300 Subject: [PATCH 09/15] keystore tx sign done --- sdk/core/src/wallet.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index b2856b74da..43898bed6d 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -42,6 +42,20 @@ pub struct Wallet { priv_key: PrivateKey, } +#[derive(Clone, Debug)] +pub struct DecryptionParams { + pub derived_key_first_half: Vec, + pub iv: Vec, + pub ciphertext: Vec, +} + +#[derive(Debug)] +pub enum WalletError { + InvalidPassword, + InvalidKdf, + InvalidCipher, +} + impl Wallet { // GenerateMnemonic will generate a new mnemonic value using the bip39 implementation pub fn generate_mnemonic() -> Mnemonic { From 794831ec3e9961468b862a1b2d9dcf144374b26a Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Tue, 20 Aug 2024 18:18:10 +0300 Subject: [PATCH 10/15] draft pem->keystore conversion --- Cargo.lock | 10 +++ framework/meta/src/cmd/wallet.rs | 87 ++++++++++++++++++------- sdk/core/Cargo.toml | 3 +- sdk/core/src/data/keystore.rs | 8 ++- sdk/core/src/wallet.rs | 105 ++++++++++++++++++++++--------- 5 files changed, 157 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0558fc8b1..fecbf652fe 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2151,6 +2151,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "tokio", + "uuid", "zeroize", ] @@ -3724,6 +3725,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] + [[package]] name = "vault" version = "0.0.0" diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index f08501ce03..8073dc55a4 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,5 +1,8 @@ use core::str; -use multiversx_sc::types; +use multiversx_sc::{ + api::{uncallable::UncallableApi, BigIntApiImpl, ExternalViewApi, ManagedTypeApi}, + types::{self, ManagedBuffer}, +}; use std::{ fs::{self, File}, io::{self, Read, Write}, @@ -8,9 +11,13 @@ use std::{ use crate::cli::{WalletAction, WalletArgs, WalletBech32Args, WalletConvertArgs, WalletNewArgs}; use bip39::Mnemonic; use multiversx_sc_snippets::sdk::{ - crypto::public_key::PublicKey, data::address::Address, utils::base64_encode, wallet::Wallet, + crypto::public_key::PublicKey, + data::address::Address, + utils::{base64_decode, base64_encode}, + wallet::Wallet, }; use multiversx_sc_snippets::{hex, imports::Bech32Address}; + pub fn wallet(args: &WalletArgs) { let command = &args.command; match command { @@ -19,7 +26,6 @@ pub fn wallet(args: &WalletArgs) { WalletAction::Convert(convert_args) => convert(convert_args), } } - fn convert(convert_args: &WalletConvertArgs) { let infile = convert_args.infile.as_ref(); let outfile = convert_args.outfile.as_ref(); @@ -34,13 +40,14 @@ fn convert(convert_args: &WalletConvertArgs) { ("mnemonic", "pem") => match infile { Some(file) => { mnemonic_str = fs::read_to_string(file).unwrap(); - mnemonic_str = mnemonic_str.replace('\n', ""); - let mnemonic = Mnemonic::parse(mnemonic_str).unwrap(); - (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic); + (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic_str); + write_resulted_wallet(&public_key_str, &private_key_str, out_format, 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) = get_wallet_keys_mnemonic(mnemonic_str); + write_resulted_wallet(&public_key_str, &private_key_str, out_format, outfile); }, }, ("keystore-secret", "pem") => match infile { @@ -49,32 +56,65 @@ fn convert(convert_args: &WalletConvertArgs) { private_key_str = private_key.to_string(); let public_key = PublicKey::from(&private_key); public_key_str = public_key.to_string(); + write_resulted_wallet(&public_key_str, &private_key_str, out_format, outfile); }, None => { panic!("Input file is required for keystore-secret format"); }, }, + ("pem", "keystore-secret") => match infile { + Some(file) => { + let pem_content = fs::read_to_string(file).unwrap(); + let lines: Vec<&str> = pem_content.split("\n").collect(); + let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); + let pem_decoded_keys = base64_decode(pem_encoded_keys); + let (private_key, public_key) = + pem_decoded_keys.split_at(pem_decoded_keys.len() / 2); + private_key_str = String::from_utf8(private_key.to_vec()).unwrap(); + public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); + + let address = get_wallet_address(&private_key_str); + let hex_decoded_keys = hex::decode(&private_key_str).unwrap(); + + Wallet::encrypt_keystore( + hex_decoded_keys.as_slice(), + &address, + &public_key_str, + &Wallet::get_keystore_password(), + ); + }, + None => { + panic!("Input file is required for pem format"); + }, + }, _ => { println!("Unsupported conversion"); }, } +} - let address = get_wallet_address(private_key_str.as_str()); - - match outfile { - Some(outfile) => { - generate_pem( - &address, - private_key_str.as_str(), - public_key_str.as_str(), - outfile, - ); +fn write_resulted_wallet( + public_key: &str, + private_key: &str, + format: &str, + outfile: Option<&String>, +) { + let address = get_wallet_address(private_key); + match format { + "pem" => match outfile { + Some(outfile) => { + generate_pem(&address, private_key, public_key, outfile); + }, + None => { + let pem_content = generate_pem_content(&address, private_key, public_key); + print!("{}", pem_content); + }, }, - None => { - let pem_content = - generate_pem_content(&address, private_key_str.as_str(), public_key_str.as_str()); - print!("{}", pem_content); + "keystore-secret" => match outfile { + Some(outfile) => {}, + None => {}, }, + _ => {}, } } @@ -102,7 +142,8 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) { } } -fn get_wallet_keys_mnemonic(mnemonic: Mnemonic) -> (String, String) { +fn get_wallet_keys_mnemonic(mnemonic_str: String) -> (String, String) { + let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap(); let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); let public_key = PublicKey::from(&private_key); @@ -123,7 +164,7 @@ fn new(new_args: &WalletNewArgs) { let mnemonic = Wallet::generate_mnemonic(); println!("Mnemonic: {}", mnemonic); - let (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic); + let (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic.to_string()); let address = get_wallet_address(private_key_str.as_str()); println!("Wallet address: {}", address); diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index 32e170618f..e84f8cd4cb 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -37,4 +37,5 @@ pem = "3.0.2" log = "0.4.17" scrypt = "0.11" aes = "0.8" -ctr = "0.9.2" \ No newline at end of file +ctr = "0.9.2" +uuid = {version = "1.10.0", features = ["v4"]} \ No newline at end of file diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs index 2564d70a2e..1b3c656ef1 100644 --- a/sdk/core/src/data/keystore.rs +++ b/sdk/core/src/data/keystore.rs @@ -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, @@ -44,5 +50,5 @@ pub struct Keystore { pub struct DecryptionParams { pub derived_key_first_half: Vec, pub iv: Vec, - pub ciphertext: Vec, + pub data: Vec, } diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 43898bed6d..c93bcdbfb1 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -3,6 +3,7 @@ extern crate rand; use std::{ fs::{self}, io::{self, Read}, + ops::Add, }; use aes::{cipher::KeyIvInit, Aes128}; @@ -11,7 +12,8 @@ use bip39::{Language, Mnemonic}; use ctr::{cipher::StreamCipher, Ctr128BE}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; -use scrypt::{scrypt, Params}; +use rand::{CryptoRng, RngCore}; +use scrypt::{scrypt, Params, Scrypt}; use serde_json::json; use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; @@ -19,16 +21,14 @@ use zeroize::Zeroize; use crate::{ crypto::{ - private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, - public_key::PublicKey, - }, - data::{ - address::Address, - keystore::{DecryptionParams, Keystore, WalletError}, - transaction::Transaction, + private_key::{self, PrivateKey, PRIVATE_KEY_LENGTH}, + public_key::{self, PublicKey}, }, + data::{address::Address, keystore::*, transaction::Transaction}, }; +use uuid::Uuid; + const EGLD_COIN_TYPE: u32 = 508; const HARDENED: u32 = 0x80000000; const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; @@ -42,20 +42,6 @@ pub struct Wallet { priv_key: PrivateKey, } -#[derive(Clone, Debug)] -pub struct DecryptionParams { - pub derived_key_first_half: Vec, - pub iv: Vec, - pub ciphertext: Vec, -} - -#[derive(Debug)] -pub enum WalletError { - InvalidPassword, - InvalidKdf, - InvalidCipher, -} - impl Wallet { // GenerateMnemonic will generate a new mnemonic value using the bip39 implementation pub fn generate_mnemonic() -> Mnemonic { @@ -146,8 +132,9 @@ impl Wallet { .unwrap_or_else(|e| { panic!("Error: {:?}", e); }); - let priv_key = - PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; + let priv_key = PrivateKey::from_hex_str( + hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), + )?; Ok(Self { priv_key }) } @@ -157,8 +144,9 @@ impl Wallet { .unwrap_or_else(|e| { panic!("Error: {:?}", e); }); - let priv_key = - PrivateKey::from_hex_str(Self::decrypt_secret_key(decyption_params).as_str())?; + let priv_key = PrivateKey::from_hex_str( + hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), + )?; Ok(priv_key) } @@ -183,7 +171,7 @@ impl Wallet { self.priv_key.sign(tx_bytes) } - fn get_keystore_password() -> String { + pub fn get_keystore_password() -> String { println!( "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." ); @@ -236,7 +224,7 @@ impl Wallet { Ok(DecryptionParams { derived_key_first_half, iv, - ciphertext, + data: ciphertext, }) } else { println!("Password is incorrect"); @@ -244,14 +232,69 @@ impl Wallet { } } - pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> String { + pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> Vec { let mut cipher = Ctr128BE::::new( decryption_params.derived_key_first_half.as_slice().into(), decryption_params.iv.as_slice().into(), ); - let mut decrypted = decryption_params.ciphertext.to_vec(); + let mut decrypted = decryption_params.data.to_vec(); cipher.apply_keystream(&mut decrypted); - hex::encode(decrypted).to_string() + decrypted + } + + pub fn encrypt_keystore(data: &[u8], address: &Address, public_key: &str, password: &str) { + let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P, KDF_DKLEN).unwrap(); + let mut rand_salt: [u8; 32] = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut rand_salt); + let salt_hex = hex::encode(rand_salt); + + let mut rand_iv: [u8; 16] = [0u8; 16]; + rand::thread_rng().fill_bytes(&mut rand_iv); + let iv_hex = hex::encode(rand_iv); + + let mut derived_key = vec![0u8; 32]; + scrypt(password.as_bytes(), &rand_salt, ¶ms, &mut derived_key).unwrap(); + + let derived_key_first_half = derived_key[0..16].to_vec(); + let derived_key_second_half = derived_key[16..32].to_vec(); + + let decryption_params = DecryptionParams { + derived_key_first_half, + iv: rand_iv.to_vec(), + data: data.to_vec(), + }; + + let ciphertext = Self::decrypt_secret_key(decryption_params); + println!("Ciphertext: {:?}", ciphertext); + + let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + h.update(&ciphertext); + let mac = h.finalize().into_bytes(); + + let keystore = Keystore { + crypto: Crypto { + cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(), + cipherparams: CryptoParams { iv: iv_hex }, + ciphertext: hex::encode(&ciphertext), + kdf: KDF_SCRYPT.to_string(), + kdfparams: KdfParams { + salt: salt_hex, + n: KDF_N, + r: KDF_R, + p: KDF_P, + dklen: KDF_DKLEN as u32, + }, + mac: hex::encode(mac), + }, + id: Uuid::new_v4().to_string(), + version: KEYSTORE_VERSION, + kind: "secretKey".to_string(), + address: public_key.to_string(), + bech32: address.to_string(), + }; + + let keystore_json = serde_json::to_string_pretty(&keystore).unwrap(); + println!("{}", keystore_json); } } From eab87731b6776316393b2ec87f3edb3ec697ff66 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 21 Aug 2024 11:36:13 +0300 Subject: [PATCH 11/15] pem <-> keystore done --- framework/meta/src/cmd/wallet.rs | 105 ++++++++++++++----------------- sdk/core/src/wallet.rs | 24 ++++--- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 8073dc55a4..28dd868bbf 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,15 +1,8 @@ use core::str; -use multiversx_sc::{ - api::{uncallable::UncallableApi, BigIntApiImpl, ExternalViewApi, ManagedTypeApi}, - types::{self, ManagedBuffer}, -}; -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, @@ -17,6 +10,10 @@ use multiversx_sc_snippets::sdk::{ 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; @@ -33,21 +30,21 @@ fn convert(convert_args: &WalletConvertArgs) { let out_format = &convert_args.to; let mut mnemonic_str = String::new(); - let mut private_key_str = String::new(); - let mut public_key_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(); (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic_str); - write_resulted_wallet(&public_key_str, &private_key_str, out_format, outfile); + 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(); (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic_str); - write_resulted_wallet(&public_key_str, &private_key_str, out_format, outfile); + write_resulted_pem(&public_key_str, &private_key_str, outfile); }, }, ("keystore-secret", "pem") => match infile { @@ -56,7 +53,7 @@ fn convert(convert_args: &WalletConvertArgs) { private_key_str = private_key.to_string(); let public_key = PublicKey::from(&private_key); public_key_str = public_key.to_string(); - write_resulted_wallet(&public_key_str, &private_key_str, out_format, outfile); + write_resulted_pem(&public_key_str, &private_key_str, outfile); }, None => { panic!("Input file is required for keystore-secret format"); @@ -65,7 +62,7 @@ fn convert(convert_args: &WalletConvertArgs) { ("pem", "keystore-secret") => match infile { Some(file) => { let pem_content = fs::read_to_string(file).unwrap(); - let lines: Vec<&str> = pem_content.split("\n").collect(); + let lines: Vec<&str> = pem_content.split('\n').collect(); let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); let pem_decoded_keys = base64_decode(pem_encoded_keys); let (private_key, public_key) = @@ -74,14 +71,15 @@ fn convert(convert_args: &WalletConvertArgs) { public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); let address = get_wallet_address(&private_key_str); - let hex_decoded_keys = hex::decode(&private_key_str).unwrap(); + let hex_decoded_keys = hex::decode(&pem_decoded_keys).unwrap(); - Wallet::encrypt_keystore( + 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"); @@ -93,28 +91,30 @@ fn convert(convert_args: &WalletConvertArgs) { } } -fn write_resulted_wallet( - public_key: &str, - private_key: &str, - format: &str, - outfile: Option<&String>, -) { +fn write_resulted_pem(public_key: &str, private_key: &str, outfile: Option<&String>) { let address = get_wallet_address(private_key); - match format { - "pem" => match outfile { - Some(outfile) => { - generate_pem(&address, private_key, public_key, outfile); - }, - None => { - let pem_content = generate_pem_content(&address, private_key, public_key); - print!("{}", pem_content); - }, + match outfile { + Some(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(); }, - "keystore-secret" => match outfile { - Some(outfile) => {}, - None => {}, + None => { + 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); }, - _ => {}, } } @@ -159,8 +159,8 @@ fn get_wallet_address(private_key: &str) -> 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 let mnemonic = Wallet::generate_mnemonic(); println!("Mnemonic: {}", mnemonic); @@ -169,30 +169,19 @@ fn new(new_args: &WalletNewArgs) { 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(_) => { + println!("Unsupported format"); + }, + None => { + println!("No format provided"); + }, } } -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); diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index c93bcdbfb1..63b5a320f6 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,9 +1,9 @@ extern crate rand; +use core::str; use std::{ fs::{self}, io::{self, Read}, - ops::Add, }; use aes::{cipher::KeyIvInit, Aes128}; @@ -12,8 +12,8 @@ use bip39::{Language, Mnemonic}; use ctr::{cipher::StreamCipher, Ctr128BE}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; -use rand::{CryptoRng, RngCore}; -use scrypt::{scrypt, Params, Scrypt}; +use rand::RngCore; +use scrypt::{scrypt, Params}; use serde_json::json; use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; @@ -21,8 +21,8 @@ use zeroize::Zeroize; use crate::{ crypto::{ - private_key::{self, PrivateKey, PRIVATE_KEY_LENGTH}, - public_key::{self, PublicKey}, + private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, + public_key::PublicKey, }, data::{address::Address, keystore::*, transaction::Transaction}, }; @@ -243,7 +243,12 @@ impl Wallet { decrypted } - pub fn encrypt_keystore(data: &[u8], address: &Address, public_key: &str, password: &str) { + pub fn encrypt_keystore( + data: &[u8], + address: &Address, + public_key: &str, + password: &str, + ) -> String { let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P, KDF_DKLEN).unwrap(); let mut rand_salt: [u8; 32] = [0u8; 32]; rand::thread_rng().fill_bytes(&mut rand_salt); @@ -266,12 +271,10 @@ impl Wallet { }; let ciphertext = Self::decrypt_secret_key(decryption_params); - println!("Ciphertext: {:?}", ciphertext); let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); h.update(&ciphertext); let mac = h.finalize().into_bytes(); - let keystore = Keystore { crypto: Crypto { cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(), @@ -294,7 +297,8 @@ impl Wallet { bech32: address.to_string(), }; - let keystore_json = serde_json::to_string_pretty(&keystore).unwrap(); - println!("{}", keystore_json); + let mut keystore_json = serde_json::to_string_pretty(&keystore).unwrap(); + keystore_json.push('\n'); + keystore_json } } From 8bed4dc8625fd979f1c5bd52c43044366a045669 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Mon, 12 Aug 2024 15:50:43 +0300 Subject: [PATCH 12/15] pem <-> keystore conversion done --- Cargo.lock | 92 ++++++++ framework/meta/Cargo.toml | 1 + framework/meta/src/cli/cli_args_standalone.rs | 61 ++++++ framework/meta/src/cli/cli_standalone_main.rs | 4 + framework/meta/src/cmd.rs | 1 + framework/meta/src/cmd/wallet.rs | 205 ++++++++++++++++++ sdk/core/Cargo.toml | 4 + sdk/core/src/data/keystore.rs | 54 +++++ sdk/core/src/data/mod.rs | 1 + sdk/core/src/wallet.rs | 180 ++++++++++++++- 10 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 framework/meta/src/cmd/wallet.rs create mode 100644 sdk/core/src/data/keystore.rs diff --git a/Cargo.lock b/Cargo.lock index abeb3c37e1..fecbf652fe 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -201,6 +212,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "basic-features" version = "0.0.0" @@ -429,6 +446,16 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.11" @@ -664,6 +691,15 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1521,6 +1557,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "interact" version = "0.0.0" @@ -1988,6 +2033,7 @@ dependencies = [ name = "multiversx-sc-meta" version = "0.52.3" dependencies = [ + "bip39", "clap", "colored", "common-path", @@ -2084,10 +2130,12 @@ dependencies = [ name = "multiversx-sdk" version = "0.5.0" dependencies = [ + "aes", "anyhow", "base64", "bech32", "bip39", + "ctr", "hex", "hmac", "itertools", @@ -2096,12 +2144,14 @@ dependencies = [ "pem", "rand 0.8.5", "reqwest", + "scrypt", "serde", "serde_json", "serde_repr", "sha2 0.10.8", "sha3", "tokio", + "uuid", "zeroize", ] @@ -2382,6 +2432,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -2415,6 +2476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "hmac", ] [[package]] @@ -2952,6 +3014,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2992,6 +3063,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.8", +] + [[package]] name = "second-contract" version = "0.0.0" @@ -3642,6 +3725,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] + [[package]] name = "vault" version = "0.0.0" diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index bd6775ed1a..fba796a3d0 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" +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 0be68d805f..ce2c3b66a6 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -78,6 +78,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)] @@ -411,3 +417,58 @@ 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 = "bech32", + about = "Encodes/decodes a bech32 address to/from hex" + )] + Bech32(WalletBech32Args), + #[command(name = "convert", about = "Converts a wallet")] + Convert(WalletConvertArgs), +} + +#[derive(Clone, PartialEq, Eq, Debug, Parser)] +#[command(propagate_version = true)] +pub struct WalletArgs { + #[command(subcommand)] + pub command: WalletAction, +} + +#[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, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct WalletConvertArgs { + #[arg(long = "in-format", verbatim_doc_comment)] + pub from: String, + + #[arg(long = "out-format", verbatim_doc_comment)] + pub to: String, + + #[arg(long = "infile", verbatim_doc_comment)] + pub infile: Option, + + #[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/cli/cli_standalone_main.rs b/framework/meta/src/cli/cli_standalone_main.rs index 9a8e065627..c749ff0056 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; @@ -46,6 +47,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 2a3ebe65cd..3263d36688 100644 --- a/framework/meta/src/cmd.rs +++ b/framework/meta/src/cmd.rs @@ -10,3 +10,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..28dd868bbf --- /dev/null +++ b/framework/meta/src/cmd/wallet.rs @@ -0,0 +1,205 @@ +use core::str; + +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_decode, 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 { + WalletAction::New(new_args) => new(new_args), + WalletAction::Bech32(bech32_args) => bech32_conversion(bech32_args), + WalletAction::Convert(convert_args) => convert(convert_args), + } +} +fn convert(convert_args: &WalletConvertArgs) { + let infile = convert_args.infile.as_ref(); + let outfile = convert_args.outfile.as_ref(); + let in_format = &convert_args.from; + 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(); + (private_key_str, public_key_str) = 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(); + (private_key_str, public_key_str) = 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).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_content = fs::read_to_string(file).unwrap(); + let lines: Vec<&str> = pem_content.split('\n').collect(); + let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); + let pem_decoded_keys = base64_decode(pem_encoded_keys); + let (private_key, public_key) = + pem_decoded_keys.split_at(pem_decoded_keys.len() / 2); + private_key_str = String::from_utf8(private_key.to_vec()).unwrap(); + public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); + + 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"); + }, + } +} + +fn write_resulted_pem(public_key: &str, private_key: &str, outfile: Option<&String>) { + let address = get_wallet_address(private_key); + match outfile { + Some(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, 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(); + + 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_str: String) -> (String, String) { + let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap(); + 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_deref(); + let outfile = new_args.outfile.as_ref(); // Handle outfile as Option<&str> if it's an Option + let mnemonic = Wallet::generate_mnemonic(); + println!("Mnemonic: {}", mnemonic); + + let (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic.to_string()); + let address = get_wallet_address(private_key_str.as_str()); + + println!("Wallet address: {}", address); + + match format { + Some("pem") => { + write_resulted_pem(public_key_str.as_str(), private_key_str.as_str(), outfile); + }, + Some(_) => { + println!("Unsupported format"); + }, + None => { + println!("No format provided"); + }, + } +} + +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); + + // Split the base64 string into 64-character lines + let formatted_key = concat_keys_b64 + .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 {}-----\n", + address.to_bech32_string().unwrap(), + formatted_key, + address.to_bech32_string().unwrap() + ); + + pem_content +} diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index 463b4ce130..e84f8cd4cb 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -35,3 +35,7 @@ bech32 = "0.9" itertools = "0.13.0" pem = "3.0.2" log = "0.4.17" +scrypt = "0.11" +aes = "0.8" +ctr = "0.9.2" +uuid = {version = "1.10.0", features = ["v4"]} \ No newline at end of file diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs new file mode 100644 index 0000000000..1b3c656ef1 --- /dev/null +++ b/sdk/core/src/data/keystore.rs @@ -0,0 +1,54 @@ +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, + InvalidKdf, + InvalidCipher, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct CryptoParams { + pub iv: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KdfParams { + pub dklen: u32, + pub salt: String, + pub n: u32, + pub r: u32, + pub p: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Crypto { + pub ciphertext: String, + pub cipherparams: CryptoParams, + pub cipher: String, + pub kdf: String, + pub kdfparams: KdfParams, + pub mac: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Keystore { + pub version: u32, + pub kind: String, + pub id: String, + pub address: String, + pub bech32: String, + pub crypto: Crypto, +} + +#[derive(Clone, Debug)] +pub struct DecryptionParams { + pub derived_key_first_half: Vec, + pub iv: Vec, + pub data: Vec, +} diff --git a/sdk/core/src/data/mod.rs b/sdk/core/src/data/mod.rs index 797ec6e1ce..9a270211df 100644 --- a/sdk/core/src/data/mod.rs +++ b/sdk/core/src/data/mod.rs @@ -3,6 +3,7 @@ pub mod account_storage; pub mod address; pub mod esdt; pub mod hyperblock; +pub mod keystore; pub mod network_config; pub mod network_economics; pub mod network_status; diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index ab1b0a2f37..63b5a320f6 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,11 +1,21 @@ extern crate rand; +use core::str; +use std::{ + fs::{self}, + io::{self, Read}, +}; + +use aes::{cipher::KeyIvInit, Aes128}; use anyhow::Result; use bip39::{Language, Mnemonic}; +use ctr::{cipher::StreamCipher, Ctr128BE}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; +use rand::RngCore; +use scrypt::{scrypt, Params}; use serde_json::json; -use sha2::{Digest, Sha512}; +use sha2::{Digest, Sha256, Sha512}; use sha3::Keccak256; use zeroize::Zeroize; @@ -14,13 +24,18 @@ use crate::{ private_key::{PrivateKey, PRIVATE_KEY_LENGTH}, public_key::PublicKey, }, - data::{address::Address, transaction::Transaction}, + data::{address::Address, keystore::*, transaction::Transaction}, }; +use uuid::Uuid; + const EGLD_COIN_TYPE: u32 = 508; const HARDENED: u32 = 0x80000000; +const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; +const KDF_SCRYPT: &str = "scrypt"; -type HmacSha521 = Hmac; +type HmacSha512 = Hmac; +type HmacSha256 = Hmac; #[derive(Copy, Clone, Debug)] pub struct Wallet { @@ -63,7 +78,7 @@ impl Wallet { let hardened_child_padding: u8 = 0; let mut digest = - HmacSha521::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); digest.update(&seed); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); let mut key = intermediary[..serialized_key_len].to_vec(); @@ -83,7 +98,7 @@ impl Wallet { buff.push(child_idx as u8); digest = - HmacSha521::new_from_slice(&chain_code).expect("HMAC can take key of any size"); + HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size"); digest.update(&buff); let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); key = intermediary[..serialized_key_len].to_vec(); @@ -111,6 +126,30 @@ impl Wallet { Ok(Self { priv_key: pri_key }) } + pub fn from_keystore_secret(file_path: &str) -> Result { + let decyption_params = + Self::validate_keystore_password(file_path, Self::get_keystore_password()) + .unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); + let priv_key = PrivateKey::from_hex_str( + hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), + )?; + Ok(Self { priv_key }) + } + + pub fn get_private_key_from_keystore_secret(file_path: &str) -> Result { + let decyption_params = + Self::validate_keystore_password(file_path, Self::get_keystore_password()) + .unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); + let priv_key = PrivateKey::from_hex_str( + hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), + )?; + Ok(priv_key) + } + pub fn address(&self) -> Address { let public_key = PublicKey::from(&self.priv_key); Address::from(&public_key) @@ -131,4 +170,135 @@ impl Wallet { self.priv_key.sign(tx_bytes) } + + pub fn get_keystore_password() -> String { + println!( + "Insert password. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." + ); + let mut password = String::new(); + io::stdin().read_to_string(&mut password).unwrap(); + password = password.trim().to_string(); + password + } + + pub fn validate_keystore_password( + path: &str, + password: String, + ) -> Result { + let json_body = fs::read_to_string(path).unwrap(); + let keystore: Keystore = serde_json::from_str(&json_body).unwrap(); + let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap(); + + let cipher = &keystore.crypto.cipher; + if cipher != CIPHER_ALGORITHM_AES_128_CTR { + return Err(WalletError::InvalidCipher); + } + + let iv = hex::decode(&keystore.crypto.cipherparams.iv).unwrap(); + let salt = hex::decode(&keystore.crypto.kdfparams.salt).unwrap(); + let json_mac = hex::decode(&keystore.crypto.mac).unwrap(); + + let kdf = &keystore.crypto.kdf; + if kdf != KDF_SCRYPT { + return Err(WalletError::InvalidKdf); + } + let n = keystore.crypto.kdfparams.n as f64; + let r = keystore.crypto.kdfparams.r as u64; + let p = keystore.crypto.kdfparams.p as u64; + let dklen = keystore.crypto.kdfparams.dklen as usize; + + let params = Params::new(n.log2() as u8, r as u32, p as u32, dklen).unwrap(); + + let mut derived_key = vec![0u8; 32]; + scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); + + let derived_key_first_half = derived_key[0..16].to_vec(); + let derived_key_second_half = derived_key[16..32].to_vec(); + + let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + input_mac.update(&ciphertext); + let computed_mac = input_mac.finalize().into_bytes(); + + if computed_mac.as_slice() == json_mac.as_slice() { + println!("Password is correct"); + Ok(DecryptionParams { + derived_key_first_half, + iv, + data: ciphertext, + }) + } else { + println!("Password is incorrect"); + Err(WalletError::InvalidPassword) + } + } + + pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> Vec { + let mut cipher = Ctr128BE::::new( + decryption_params.derived_key_first_half.as_slice().into(), + decryption_params.iv.as_slice().into(), + ); + let mut decrypted = decryption_params.data.to_vec(); + cipher.apply_keystream(&mut decrypted); + + decrypted + } + + pub fn encrypt_keystore( + data: &[u8], + address: &Address, + public_key: &str, + password: &str, + ) -> String { + let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P, KDF_DKLEN).unwrap(); + let mut rand_salt: [u8; 32] = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut rand_salt); + let salt_hex = hex::encode(rand_salt); + + let mut rand_iv: [u8; 16] = [0u8; 16]; + rand::thread_rng().fill_bytes(&mut rand_iv); + let iv_hex = hex::encode(rand_iv); + + let mut derived_key = vec![0u8; 32]; + scrypt(password.as_bytes(), &rand_salt, ¶ms, &mut derived_key).unwrap(); + + let derived_key_first_half = derived_key[0..16].to_vec(); + let derived_key_second_half = derived_key[16..32].to_vec(); + + let decryption_params = DecryptionParams { + derived_key_first_half, + iv: rand_iv.to_vec(), + data: data.to_vec(), + }; + + let ciphertext = Self::decrypt_secret_key(decryption_params); + + let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + h.update(&ciphertext); + let mac = h.finalize().into_bytes(); + let keystore = Keystore { + crypto: Crypto { + cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(), + cipherparams: CryptoParams { iv: iv_hex }, + ciphertext: hex::encode(&ciphertext), + kdf: KDF_SCRYPT.to_string(), + kdfparams: KdfParams { + salt: salt_hex, + n: KDF_N, + r: KDF_R, + p: KDF_P, + dklen: KDF_DKLEN as u32, + }, + mac: hex::encode(mac), + }, + id: Uuid::new_v4().to_string(), + version: KEYSTORE_VERSION, + kind: "secretKey".to_string(), + address: public_key.to_string(), + bech32: address.to_string(), + }; + + let mut keystore_json = serde_json::to_string_pretty(&keystore).unwrap(); + keystore_json.push('\n'); + keystore_json + } } From 871a0fb13fcd31cfc2f303e37a88c4e36a43a06c Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 21 Aug 2024 14:41:13 +0300 Subject: [PATCH 13/15] small refactors + conversion unit tests --- framework/meta/src/cmd/wallet.rs | 41 ++++++-------------- framework/meta/tests/wallet_test.rs | 59 +++++++++++++++++++++++++++++ sdk/core/src/wallet.rs | 48 ++++++++++++++++++----- 3 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 framework/meta/tests/wallet_test.rs diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 28dd868bbf..51da69ad43 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -1,13 +1,9 @@ use core::str; 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_decode, base64_encode}, - wallet::Wallet, + crypto::public_key::PublicKey, data::address::Address, utils::base64_encode, wallet::Wallet, }; use multiversx_sc_snippets::{hex, imports::Bech32Address}; use std::{ @@ -37,19 +33,23 @@ fn convert(convert_args: &WalletConvertArgs) { ("mnemonic", "pem") => match infile { Some(file) => { mnemonic_str = fs::read_to_string(file).unwrap(); - (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic_str); + (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(); - (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic_str); + (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).unwrap(); + 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(); @@ -61,17 +61,11 @@ fn convert(convert_args: &WalletConvertArgs) { }, ("pem", "keystore-secret") => match infile { Some(file) => { - let pem_content = fs::read_to_string(file).unwrap(); - let lines: Vec<&str> = pem_content.split('\n').collect(); - let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); - let pem_decoded_keys = base64_decode(pem_encoded_keys); - let (private_key, public_key) = - pem_decoded_keys.split_at(pem_decoded_keys.len() / 2); - private_key_str = String::from_utf8(private_key.to_vec()).unwrap(); - public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); + 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 hex_decoded_keys = hex::decode(pem_decoded_keys).unwrap(); let json_result = Wallet::encrypt_keystore( hex_decoded_keys.as_slice(), @@ -142,17 +136,6 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) { } } -fn get_wallet_keys_mnemonic(mnemonic_str: String) -> (String, String) { - let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap(); - 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() @@ -164,7 +147,7 @@ fn new(new_args: &WalletNewArgs) { let mnemonic = Wallet::generate_mnemonic(); println!("Mnemonic: {}", mnemonic); - let (private_key_str, public_key_str) = get_wallet_keys_mnemonic(mnemonic.to_string()); + 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); diff --git a/framework/meta/tests/wallet_test.rs b/framework/meta/tests/wallet_test.rs new file mode 100644 index 0000000000..6420a7f4c6 --- /dev/null +++ b/framework/meta/tests/wallet_test.rs @@ -0,0 +1,59 @@ +use std::fs; + +use multiversx_sc_meta::cli::{WalletAction, WalletArgs, WalletConvertArgs}; +use multiversx_sc_meta::cmd::wallet::wallet; +use multiversx_sc_snippets::imports::Wallet; + +const ALICE_PEM_PATH: &str = "../snippets/src/test_wallets/alice.pem"; +const ALICE_KEYSTORE_PATH: &str = "alice.json"; + +// Insert a password for your keystore, followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows +fn create_keystore_from_pem() { + let wallet_convert_args = WalletConvertArgs { + infile: Some(ALICE_PEM_PATH.to_string()), + outfile: Some(ALICE_KEYSTORE_PATH.to_string()), + from: "pem".to_string(), + to: "keystore-secret".to_string(), + }; + let wallet_args = WalletArgs { + command: WalletAction::Convert(wallet_convert_args), + }; + wallet(&wallet_args); +} + +// Insert "1234" as password for your keystore, followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows +#[test] +fn test_wallet_convert_pem_to_keystore() { + let keystore_password = "1234"; + create_keystore_from_pem(); + 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, keystore_password) + .unwrap() + .to_string(), + private_key_pem + ); + fs::remove_file(ALICE_KEYSTORE_PATH).unwrap(); +} + +// Insert the same password twice, each followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows +#[test] +fn test_wallet_convert_keystore_to_pem() { + create_keystore_from_pem(); + let wallet_convert_args = WalletConvertArgs { + infile: Some(ALICE_KEYSTORE_PATH.to_string()), + outfile: Some("alice_test.pem".to_string()), + from: "keystore-secret".to_string(), + to: "pem".to_string(), + }; + let wallet_args = WalletArgs { + command: WalletAction::Convert(wallet_convert_args), + }; + wallet(&wallet_args); + assert_eq!( + Wallet::get_pem_decoded_content(ALICE_PEM_PATH), + Wallet::get_pem_decoded_content("alice_test.pem") + ); + fs::remove_file("alice_test.pem").unwrap(); + fs::remove_file(ALICE_KEYSTORE_PATH).unwrap(); +} diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 63b5a320f6..fb873df8ff 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -25,6 +25,7 @@ use crate::{ public_key::PublicKey, }, data::{address::Address, keystore::*, transaction::Transaction}, + utils::base64_decode, }; use uuid::Uuid; @@ -108,9 +109,20 @@ impl Wallet { PrivateKey::from_bytes(key.as_slice()).unwrap() } + pub fn get_wallet_keys_mnemonic(mnemonic_str: String) -> (String, String) { + let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap(); + let private_key = Self::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()) + } + pub fn from_private_key(priv_key: &str) -> Result { - let pri_key = PrivateKey::from_hex_str(priv_key)?; - Ok(Self { priv_key: pri_key }) + let priv_key = PrivateKey::from_hex_str(priv_key)?; + Ok(Self { priv_key }) } pub fn from_pem_file(file_path: &str) -> Result { @@ -126,6 +138,22 @@ impl Wallet { Ok(Self { priv_key: pri_key }) } + pub fn get_pem_decoded_content(file: &str) -> Vec { + let pem_content = fs::read_to_string(file).unwrap(); + let lines: Vec<&str> = pem_content.split('\n').collect(); + let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); + base64_decode(pem_encoded_keys) + } + + pub fn get_wallet_keys_pem(file: &str) -> (String, String) { + let pem_decoded_keys = Self::get_pem_decoded_content(file); + let (private_key, public_key) = pem_decoded_keys.split_at(pem_decoded_keys.len() / 2); + let private_key_str = String::from_utf8(private_key.to_vec()).unwrap(); + let public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); + + (private_key_str, public_key_str) + } + pub fn from_keystore_secret(file_path: &str) -> Result { let decyption_params = Self::validate_keystore_password(file_path, Self::get_keystore_password()) @@ -138,12 +166,14 @@ impl Wallet { Ok(Self { priv_key }) } - pub fn get_private_key_from_keystore_secret(file_path: &str) -> Result { - let decyption_params = - Self::validate_keystore_password(file_path, Self::get_keystore_password()) - .unwrap_or_else(|e| { - panic!("Error: {:?}", e); - }); + pub fn get_private_key_from_keystore_secret( + file_path: &str, + password: &str, + ) -> Result { + let decyption_params = Self::validate_keystore_password(file_path, password.to_string()) + .unwrap_or_else(|e| { + panic!("Error: {:?}", e); + }); let priv_key = PrivateKey::from_hex_str( hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), )?; @@ -297,7 +327,7 @@ impl Wallet { bech32: address.to_string(), }; - let mut keystore_json = serde_json::to_string_pretty(&keystore).unwrap(); + let mut keystore_json: String = serde_json::to_string_pretty(&keystore).unwrap(); keystore_json.push('\n'); keystore_json } From 1d897df99feb71f934c3fc0f77802e65ceba37b4 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 21 Aug 2024 17:46:41 +0300 Subject: [PATCH 14/15] added keystore wallet generation to sc-meta --- framework/meta/src/cmd/wallet.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 51da69ad43..752c84e522 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -156,12 +156,21 @@ fn new(new_args: &WalletNewArgs) { 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 => { - println!("No format provided"); - }, + None => {}, } } From 86540925ac355a6ab1b323eddaff181a111d4545 Mon Sep 17 00:00:00 2001 From: Andrei Vasilescu Date: Wed, 21 Aug 2024 18:35:56 +0300 Subject: [PATCH 15/15] fixed unittests --- framework/meta/src/cmd/wallet.rs | 2 +- framework/meta/tests/wallet_test.rs | 110 ++++++++++++++++++---------- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet.rs index 42a241dc7e..ba8a9f6132 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet.rs @@ -175,7 +175,7 @@ fn new(new_args: &WalletNewArgs) { } } -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); diff --git a/framework/meta/tests/wallet_test.rs b/framework/meta/tests/wallet_test.rs index 6420a7f4c6..ce1627ff64 100644 --- a/framework/meta/tests/wallet_test.rs +++ b/framework/meta/tests/wallet_test.rs @@ -1,59 +1,91 @@ -use std::fs; +use std::fs::{self, File}; +use std::io::Write; -use multiversx_sc_meta::cli::{WalletAction, WalletArgs, WalletConvertArgs}; -use multiversx_sc_meta::cmd::wallet::wallet; -use multiversx_sc_snippets::imports::Wallet; +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: &str = "alice.json"; - -// Insert a password for your keystore, followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows -fn create_keystore_from_pem() { - let wallet_convert_args = WalletConvertArgs { - infile: Some(ALICE_PEM_PATH.to_string()), - outfile: Some(ALICE_KEYSTORE_PATH.to_string()), - from: "pem".to_string(), - to: "keystore-secret".to_string(), - }; - let wallet_args = WalletArgs { - command: WalletAction::Convert(wallet_convert_args), - }; - wallet(&wallet_args); +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 } -// Insert "1234" as password for your keystore, followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows #[test] fn test_wallet_convert_pem_to_keystore() { - let keystore_password = "1234"; - create_keystore_from_pem(); + 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, keystore_password) + 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).unwrap(); + fs::remove_file(ALICE_KEYSTORE_PATH_TEST).unwrap(); } -// Insert the same password twice, each followed by (CTRL+D) for Linux/Mac or (CTRL+Z) for Windows #[test] fn test_wallet_convert_keystore_to_pem() { - create_keystore_from_pem(); - let wallet_convert_args = WalletConvertArgs { - infile: Some(ALICE_KEYSTORE_PATH.to_string()), - outfile: Some("alice_test.pem".to_string()), - from: "keystore-secret".to_string(), - to: "pem".to_string(), - }; - let wallet_args = WalletArgs { - command: WalletAction::Convert(wallet_convert_args), - }; - wallet(&wallet_args); + 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_pem_decoded_content(ALICE_PEM_PATH), - Wallet::get_pem_decoded_content("alice_test.pem") + 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_test.pem").unwrap(); - fs::remove_file(ALICE_KEYSTORE_PATH).unwrap(); + + fs::remove_file(ALICE_PEM_PATH_TEST).unwrap(); + fs::remove_file(ALICE_KEYSTORE_PATH_TEST).unwrap(); }