From 7a336c677e80e0405e523891c5622c1c18cff332 Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 31 Oct 2019 01:45:17 +0530 Subject: [PATCH 01/25] Add test to understand flow of key storage --- account_manager/Cargo.toml | 3 + account_manager/src/keystore.rs | 89 ++++++++++++ account_manager/src/main.rs | 250 ++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 account_manager/src/keystore.rs create mode 100644 account_manager/src/main.rs diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index ad91ad4efe2..3e11370c54e 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dev-dependencies] tempdir = "0.3" +tree_hash = "0.1" [dependencies] bls = { path = "../eth2/utils/bls" } @@ -26,3 +27,5 @@ rayon = "1.2.0" eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" } web3 = "0.8.0" futures = "0.1.25" +parity-crypto = "0.4.2" +rand = "0.7.2" diff --git a/account_manager/src/keystore.rs b/account_manager/src/keystore.rs new file mode 100644 index 00000000000..225bcc67638 --- /dev/null +++ b/account_manager/src/keystore.rs @@ -0,0 +1,89 @@ +#[cfg(test)] +mod tests { + use parity_crypto::aes::{decrypt_128_ctr, encrypt_128_ctr}; + use parity_crypto::digest; + use parity_crypto::pbkdf2::{sha256, Salt, Secret}; + use rand::prelude::*; + use types::Keypair; + + struct Keystore { + // Cipher stuff + pub cipher_message: Vec, + pub iv: Vec, + // Checksum stuff + pub checksum: Vec, + // kdf stuff + pub iterations: u32, + pub dk_len: u32, + pub salt: Vec, + } + + #[test] + fn test_keystore() { + let keypair = Keypair::random(); + let password = "bythepowerofgrayskull"; + // Derive decryption key from password + let iterations = 1000; + let dk_len = 32; + let salt = rand::thread_rng().gen::<[u8; 32]>(); + let mut decryption_key = [0; 32]; + + // Run the kdf on the password to derive decryption key + sha256( + iterations, + Salt(&salt), + Secret(password.as_bytes()), + &mut decryption_key, + ); + // Encryption params + let iv = rand::thread_rng().gen::<[u8; 16]>(); + let mut cipher_message = vec![0; 48]; + // Encrypt bls secret key with first 16 bytes as aes key + encrypt_128_ctr( + &decryption_key[0..16], + &iv, + &keypair.sk.as_raw().as_bytes(), + &mut cipher_message, + ) + .unwrap(); + // Generate checksum + let mut pre_image: Vec = decryption_key[16..32].to_owned(); // last 16 bytes of decryption key + pre_image.append(&mut cipher_message.clone()); + let checksum_message: Vec = digest::sha256(&pre_image).to_owned(); + + // Generate keystore + let keystore = Keystore { + cipher_message, + iv: iv.to_owned().to_vec(), + checksum: checksum_message, + iterations, + dk_len, + salt: salt.to_owned().to_vec(), + }; + + // Regnerate decryption key + let mut decryption_key1 = [0; 32]; + sha256( + keystore.iterations, + Salt(&keystore.salt), + Secret(password.as_bytes()), + &mut decryption_key1, + ); + // Verify checksum + let mut dk_slice = decryption_key1[16..32].to_owned(); + dk_slice.append(&mut keystore.cipher_message.clone()); + let checksum: Vec = digest::sha256(&dk_slice).to_owned(); + assert_eq!(checksum, keystore.checksum); + + // Verify receovered sk + let mut sk = vec![0; 48]; + decrypt_128_ctr( + &decryption_key[0..16], + &keystore.iv, + &keystore.cipher_message, + &mut sk, + ) + .unwrap(); + assert_eq!(sk, keypair.sk.as_raw().as_bytes()); + } +} diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs new file mode 100644 index 00000000000..47018647688 --- /dev/null +++ b/account_manager/src/main.rs @@ -0,0 +1,250 @@ +use bls::Keypair; +use clap::{App, Arg, SubCommand}; +use slog::{crit, debug, info, o, Drain}; +use std::fs; +use std::path::PathBuf; +use types::test_utils::generate_deterministic_keypair; +use types::DepositData; +use validator_client::Config as ValidatorClientConfig; +mod deposit; +mod keystore; + +pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; +pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml"; + +fn main() { + // Logging + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::CompactFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + let mut log = slog::Logger::root(drain, o!()); + + // CLI + let matches = App::new("Lighthouse Accounts Manager") + .version("0.0.1") + .author("Sigma Prime ") + .about("Eth 2.0 Accounts Manager") + .arg( + Arg::with_name("logfile") + .long("logfile") + .value_name("logfile") + .help("File path where output will be written.") + .takes_value(true), + ) + .arg( + Arg::with_name("datadir") + .long("datadir") + .short("d") + .value_name("DIR") + .help("Data directory for keys and databases.") + .takes_value(true), + ) + .subcommand( + SubCommand::with_name("generate") + .about("Generates a new validator private key") + .version("0.0.1") + .author("Sigma Prime "), + ) + .subcommand( + SubCommand::with_name("generate_deterministic") + .about("Generates a deterministic validator private key FOR TESTING") + .version("0.0.1") + .author("Sigma Prime ") + .arg( + Arg::with_name("validator index") + .long("index") + .short("i") + .value_name("index") + .help("The index of the validator, for which the test key is generated") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("validator count") + .long("validator_count") + .short("n") + .value_name("validator_count") + .help("If supplied along with `index`, generates keys `i..i + n`.") + .takes_value(true) + .default_value("1"), + ), + ) + .subcommand( + SubCommand::with_name("generate_deposit_params") + .about("Generates deposit parameters for submitting to the deposit contract") + .version("0.0.1") + .author("Sigma Prime ") + .arg( + Arg::with_name("validator_sk_path") + .long("validator_sk_path") + .short("v") + .value_name("validator_sk_path") + .help("Path to the validator private key directory") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("withdrawal_sk_path") + .long("withdrawal_sk_path") + .short("w") + .value_name("withdrawal_sk_path") + .help("Path to the withdrawal private key directory") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("deposit_amount") + .long("deposit_amount") + .short("a") + .value_name("deposit_amount") + .help("Deposit amount in GWEI") + .takes_value(true) + .required(true), + ), + ) + .get_matches(); + + let data_dir = match matches + .value_of("datadir") + .and_then(|v| Some(PathBuf::from(v))) + { + Some(v) => v, + None => { + // use the default + let mut default_dir = match dirs::home_dir() { + Some(v) => v, + None => { + crit!(log, "Failed to find a home directory"); + return; + } + }; + default_dir.push(DEFAULT_DATA_DIR); + default_dir + } + }; + + // create the directory if needed + match fs::create_dir_all(&data_dir) { + Ok(_) => {} + Err(e) => { + crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); + return; + } + } + + let mut client_config = ValidatorClientConfig::default(); + + // Ensure the `data_dir` in the config matches that supplied to the CLI. + client_config.data_dir = data_dir.clone(); + + if let Err(e) = client_config.apply_cli_args(&matches, &mut log) { + crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => format!("{:?}", e)); + return; + }; + + // Log configuration + info!(log, ""; + "data_dir" => &client_config.data_dir.to_str()); + + match matches.subcommand() { + ("generate", Some(_)) => generate_random(&client_config, &log), + ("generate_deterministic", Some(m)) => { + if let Some(string) = m.value_of("validator index") { + let i: usize = string.parse().expect("Invalid validator index"); + if let Some(string) = m.value_of("validator count") { + let n: usize = string.parse().expect("Invalid end validator count"); + + let indices: Vec = (i..i + n).collect(); + generate_deterministic_multiple(&indices, &client_config, &log) + } else { + generate_deterministic(i, &client_config, &log) + } + } + } + ("generate_deposit_params", Some(m)) => { + let validator_kp_path = m + .value_of("validator_sk_path") + .expect("generating deposit params requires validator key path") + .parse::() + .expect("Must be a valid path"); + let withdrawal_kp_path = m + .value_of("withdrawal_sk_path") + .expect("generating deposit params requires withdrawal key path") + .parse::() + .expect("Must be a valid path"); + let amount = m + .value_of("deposit_amount") + .expect("generating deposit params requires deposit amount") + .parse::() + .expect("Must be a valid u64"); + let deposit = generate_deposit_params( + validator_kp_path, + withdrawal_kp_path, + amount, + &client_config, + &log, + ); + // TODO: just printing for now. Decide how to process + println!("Deposit data: {:?}", deposit); + } + _ => { + crit!( + log, + "The account manager must be run with a subcommand. See help for more information." + ); + } + } +} + +fn generate_random(config: &ValidatorClientConfig, log: &slog::Logger) { + save_key(&Keypair::random(), config, log) +} + +fn generate_deterministic_multiple( + validator_indices: &[usize], + config: &ValidatorClientConfig, + log: &slog::Logger, +) { + for validator_index in validator_indices { + generate_deterministic(*validator_index, config, log) + } +} + +fn generate_deterministic( + validator_index: usize, + config: &ValidatorClientConfig, + log: &slog::Logger, +) { + save_key( + &generate_deterministic_keypair(validator_index), + config, + log, + ) +} + +fn save_key(keypair: &Keypair, config: &ValidatorClientConfig, log: &slog::Logger) { + let key_path: PathBuf = config + .save_key(&keypair) + .expect("Unable to save newly generated private key."); + debug!( + log, + "Keypair generated {:?}, saved to: {:?}", + keypair.identifier(), + key_path.to_string_lossy() + ); +} + +fn generate_deposit_params( + vk_path: PathBuf, + wk_path: PathBuf, + amount: u64, + config: &ValidatorClientConfig, + log: &slog::Logger, +) -> DepositData { + let vk: Keypair = config.read_keypair_file(vk_path).unwrap(); + let wk: Keypair = config.read_keypair_file(wk_path).unwrap(); + + let spec = types::ChainSpec::default(); + debug!(log, "Generating deposit parameters"); + deposit::generate_deposit_params(vk, &wk, amount, &spec) +} From 44e4e29a458741184d3fb65254b969709830e153 Mon Sep 17 00:00:00 2001 From: pawan Date: Fri, 1 Nov 2019 00:59:26 +0530 Subject: [PATCH 02/25] First commit --- account_manager/Cargo.toml | 1 + account_manager/src/cipher.rs | 34 +++++++ account_manager/src/kdf.rs | 53 +++++++++++ account_manager/src/keystore.rs | 164 +++++++++++++++++--------------- account_manager/src/main.rs | 2 + 5 files changed, 175 insertions(+), 79 deletions(-) create mode 100644 account_manager/src/cipher.rs create mode 100644 account_manager/src/kdf.rs diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 3e11370c54e..f0c526242b6 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -29,3 +29,4 @@ web3 = "0.8.0" futures = "0.1.25" parity-crypto = "0.4.2" rand = "0.7.2" +rust-crypto = "0.2.36" diff --git a/account_manager/src/cipher.rs b/account_manager/src/cipher.rs new file mode 100644 index 00000000000..64b33e84914 --- /dev/null +++ b/account_manager/src/cipher.rs @@ -0,0 +1,34 @@ +use crypto::aes::{ctr, KeySize}; + +const IV_SIZE: usize = 16; + +pub struct CipherMessage { + pub cipher: C, + pub message: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Aes128Ctr { + pub iv: Vec, +} + +impl Cipher for Aes128Ctr { + fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec { + // TODO: sanity checks + let mut ct = vec![0; pt.len()]; + ctr(KeySize::KeySize128, key, &self.iv).process(pt, &mut ct); + ct + } + + fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec { + // TODO: sanity checks + let mut pt = vec![0; ct.len()]; + ctr(KeySize::KeySize128, key, &self.iv).process(ct, &mut pt); + pt + } +} + +pub trait Cipher { + fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec; + fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec; +} diff --git a/account_manager/src/kdf.rs b/account_manager/src/kdf.rs new file mode 100644 index 00000000000..13880e9c539 --- /dev/null +++ b/account_manager/src/kdf.rs @@ -0,0 +1,53 @@ +use crypto::sha2::Sha256; +use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Prf { + HmacSha256, +} + +impl Prf { + // TODO: is password what should be passed here? + pub fn mac(&self, password: &[u8]) -> impl Mac { + match &self { + _hmac_sha256 => Hmac::new(Sha256::new(), password), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Pbkdf2 { + pub c: u32, + pub dklen: u32, + pub prf: Prf, + pub salt: Vec, +} + +impl Kdf for Pbkdf2 { + fn derive_key(&self, password: &str) -> Vec { + let mut dk = [0u8; 32]; + let mut mac = self.prf.mac(password.as_bytes()); + pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, &mut dk); + dk.to_vec() + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Scrypt { + pub dklen: u32, + pub n: u32, + pub r: u32, + pub p: u32, + pub salt: Vec, +} + +impl Kdf for Scrypt { + fn derive_key(&self, password: &str) -> Vec { + unimplemented!() + } +} + +pub trait Kdf { + /// Derive the key from the password + fn derive_key(&self, password: &str) -> Vec; +} diff --git a/account_manager/src/keystore.rs b/account_manager/src/keystore.rs index 225bcc67638..89acf17ec29 100644 --- a/account_manager/src/keystore.rs +++ b/account_manager/src/keystore.rs @@ -1,89 +1,95 @@ -#[cfg(test)] -mod tests { - use parity_crypto::aes::{decrypt_128_ctr, encrypt_128_ctr}; - use parity_crypto::digest; - use parity_crypto::pbkdf2::{sha256, Salt, Secret}; - use rand::prelude::*; - use types::Keypair; +use crate::cipher::{Aes128Ctr, Cipher, CipherMessage}; +use crate::kdf::{Kdf, Pbkdf2, Prf}; +use crypto::digest::Digest; +use crypto::sha2::Sha256; - struct Keystore { - // Cipher stuff - pub cipher_message: Vec, - pub iv: Vec, - // Checksum stuff - pub checksum: Vec, - // kdf stuff - pub iterations: u32, - pub dk_len: u32, - pub salt: Vec, - } +use rand::prelude::*; - #[test] - fn test_keystore() { - let keypair = Keypair::random(); - let password = "bythepowerofgrayskull"; - // Derive decryption key from password - let iterations = 1000; - let dk_len = 32; - let salt = rand::thread_rng().gen::<[u8; 32]>(); - let mut decryption_key = [0; 32]; +#[derive(Debug, PartialEq, Clone)] +pub struct Checksum(String); + +/// Crypto +pub struct Crypto { + /// Key derivation function parameters + pub kdf: K, + /// Checksum for password verification + pub checksum: Checksum, + /// CipherParams parameters + pub cipher: CipherMessage, +} + +impl Crypto { + pub fn encrypt(password: String, secret: &[u8], kdf: K, cipher: C) -> Self { + // Generate derived key + let derived_key = kdf.derive_key(&password); + // Encrypt secret + let cipher_message = cipher.encrypt(&derived_key[0..16], secret); - // Run the kdf on the password to derive decryption key - sha256( - iterations, - Salt(&salt), - Secret(password.as_bytes()), - &mut decryption_key, - ); - // Encryption params - let iv = rand::thread_rng().gen::<[u8; 16]>(); - let mut cipher_message = vec![0; 48]; - // Encrypt bls secret key with first 16 bytes as aes key - encrypt_128_ctr( - &decryption_key[0..16], - &iv, - &keypair.sk.as_raw().as_bytes(), - &mut cipher_message, - ) - .unwrap(); // Generate checksum - let mut pre_image: Vec = decryption_key[16..32].to_owned(); // last 16 bytes of decryption key + let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key pre_image.append(&mut cipher_message.clone()); - let checksum_message: Vec = digest::sha256(&pre_image).to_owned(); + // create a Sha256 object + let mut hasher = Sha256::new(); + hasher.input(&pre_image); + // read hash digest + let checksum = hasher.result_str(); + Crypto { + kdf, + checksum: Checksum(checksum), + cipher: CipherMessage { + cipher, + message: cipher_message, + }, + } + } - // Generate keystore - let keystore = Keystore { - cipher_message, - iv: iv.to_owned().to_vec(), - checksum: checksum_message, - iterations, - dk_len, - salt: salt.to_owned().to_vec(), - }; + pub fn decrypt(&self, password: String) -> Vec { + // Genrate derived key + let derived_key = self.kdf.derive_key(&password); + // Regenerate checksum + let mut pre_image: Vec = derived_key[16..32].to_owned(); + pre_image.append(&mut self.cipher.message.clone()); + // create a Sha256 object + let mut hasher = Sha256::new(); + hasher.input(&pre_image); + // read hash digest + let checksum = hasher.result_str(); + debug_assert_eq!(checksum, self.checksum.0); + let secret = self + .cipher + .cipher + .decrypt(&derived_key[0..16], &self.cipher.message); + return secret; + } +} + +#[cfg(test)] +mod tests { + use super::*; - // Regnerate decryption key - let mut decryption_key1 = [0; 32]; - sha256( - keystore.iterations, - Salt(&keystore.salt), - Secret(password.as_bytes()), - &mut decryption_key1, - ); - // Verify checksum - let mut dk_slice = decryption_key1[16..32].to_owned(); - dk_slice.append(&mut keystore.cipher_message.clone()); - let checksum: Vec = digest::sha256(&dk_slice).to_owned(); - assert_eq!(checksum, keystore.checksum); + #[test] + fn test_vector() { + let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; + let salt_str = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; + let iv_str = "264daa3f303d7259501c93d997d84fe6"; + let password = "testpassword".to_string(); - // Verify receovered sk - let mut sk = vec![0; 48]; - decrypt_128_ctr( - &decryption_key[0..16], - &keystore.iv, - &keystore.cipher_message, - &mut sk, - ) - .unwrap(); - assert_eq!(sk, keypair.sk.as_raw().as_bytes()); + let salt = hex::decode(salt_str).unwrap(); + let iv = hex::decode(iv_str).unwrap(); + let kdf = Pbkdf2 { + c: 262144, + dklen: 32, + prf: Prf::HmacSha256, + salt: salt.to_vec(), + }; + let cipher = Aes128Ctr { iv: iv }; + let secret = hex::decode(secret_str).unwrap(); + let crypto = Crypto::encrypt(password.clone(), &secret, kdf, cipher); + println!("Cipher is {:?}", hex::encode(crypto.cipher.message.clone())); + println!("Checksum is {:?}", crypto.checksum); + let recovered_secret = crypto.decrypt(password); + let recovered_secret_str = hex::encode(recovered_secret); + println!("Secret is {:?}", recovered_secret_str); + assert_eq!(recovered_secret_str, secret_str); } } diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 47018647688..10993485ad3 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -6,7 +6,9 @@ use std::path::PathBuf; use types::test_utils::generate_deterministic_keypair; use types::DepositData; use validator_client::Config as ValidatorClientConfig; +mod cipher; mod deposit; +mod kdf; mod keystore; pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; From 4dd39e4543517084e93cb5b521cb17906194b0a2 Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 4 Nov 2019 15:18:49 +0530 Subject: [PATCH 03/25] Committing to save trait stuff --- account_manager/Cargo.toml | 6 +- account_manager/src/cipher.rs | 18 +++- account_manager/src/kdf.rs | 114 ++++++++++++++++++++++---- account_manager/src/keystore.rs | 140 ++++++++++++++++++++++---------- account_manager/src/main.rs | 4 - account_manager/src/module.rs | 7 ++ 6 files changed, 223 insertions(+), 66 deletions(-) create mode 100644 account_manager/src/module.rs diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index f0c526242b6..5d50a8104b7 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -21,7 +21,6 @@ deposit_contract = { path = "../eth2/utils/deposit_contract" } libc = "0.2.65" eth2_ssz = { path = "../eth2/utils/ssz" } eth2_ssz_derive = { path = "../eth2/utils/ssz_derive" } -hex = "0.3" validator_client = { path = "../validator_client" } rayon = "1.2.0" eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" } @@ -30,3 +29,8 @@ futures = "0.1.25" parity-crypto = "0.4.2" rand = "0.7.2" rust-crypto = "0.2.36" +hex = "0.4.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + + diff --git a/account_manager/src/cipher.rs b/account_manager/src/cipher.rs index 64b33e84914..dd4ef940c8d 100644 --- a/account_manager/src/cipher.rs +++ b/account_manager/src/cipher.rs @@ -1,5 +1,5 @@ +use crate::module::CryptoModule; use crypto::aes::{ctr, KeySize}; - const IV_SIZE: usize = 16; pub struct CipherMessage { @@ -32,3 +32,19 @@ pub trait Cipher { fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec; fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec; } + +impl CryptoModule for CipherMessage { + type Params = C; + + fn function(&self) -> String { + "aes-128-ctr".to_string() + } + + fn params(&self) -> &Self::Params { + &self.cipher + } + + fn message(&self) -> Vec { + self.message.clone() + } +} diff --git a/account_manager/src/kdf.rs b/account_manager/src/kdf.rs index 13880e9c539..e660e5485d8 100644 --- a/account_manager/src/kdf.rs +++ b/account_manager/src/kdf.rs @@ -1,21 +1,9 @@ +use crate::module::CryptoModule; use crypto::sha2::Sha256; use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; -#[derive(Debug, PartialEq, Clone)] -pub enum Prf { - HmacSha256, -} - -impl Prf { - // TODO: is password what should be passed here? - pub fn mac(&self, password: &[u8]) -> impl Mac { - match &self { - _hmac_sha256 => Hmac::new(Sha256::new(), password), - } - } -} - -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Pbkdf2 { pub c: u32, pub dklen: u32, @@ -32,7 +20,7 @@ impl Kdf for Pbkdf2 { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Scrypt { pub dklen: u32, pub n: u32, @@ -40,14 +28,104 @@ pub struct Scrypt { pub p: u32, pub salt: Vec, } +const fn num_bits() -> usize { + std::mem::size_of::() * 8 +} + +fn log_2(x: u32) -> u32 { + assert!(x > 0); + num_bits::() as u32 - x.leading_zeros() - 1 +} impl Kdf for Scrypt { fn derive_key(&self, password: &str) -> Vec { - unimplemented!() + let mut dk = [0u8; 32]; + let params = scrypt::ScryptParams::new(log_2(self.p) as u8, self.r, self.p); + scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk); + dk.to_vec() } } -pub trait Kdf { +pub trait Kdf: Serialize + DeserializeOwned { /// Derive the key from the password fn derive_key(&self, password: &str) -> Vec; } + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct KdfModule { + function: String, + params: Kdf, + message: String, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub enum Prf { + #[serde(rename = "hmac-sha256")] + HmacSha256, +} + +impl Prf { + // TODO: is password what should be passed here? + pub fn mac(&self, password: &[u8]) -> impl Mac { + match &self { + _hmac_sha256 => Hmac::new(Sha256::new(), password), + } + } +} + +impl CryptoModule for Scrypt { + type Params = Scrypt; + + fn function(&self) -> String { + "scrypt".to_string() + } + + fn params(&self) -> &Self::Params { + &self + } + + fn message(&self) -> Vec { + Vec::new() + } +} + +impl CryptoModule for Pbkdf2 { + type Params = Pbkdf2; + + fn function(&self) -> String { + "pbkdf2".to_string() + } + + fn params(&self) -> &Self::Params { + &self + } + + fn message(&self) -> Vec { + Vec::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_log() { + let scrypt = Scrypt { + dklen: 32, + n: 262144, + r: 8, + p: 1, + salt: vec![0; 32], + }; + + let p = Pbkdf2 { + dklen: 32, + c: 262144, + prf: Prf::HmacSha256, + salt: vec![0; 32], + }; + let serialized = serde_json::to_string(&p).unwrap(); + println!("{}", serialized); + } +} diff --git a/account_manager/src/keystore.rs b/account_manager/src/keystore.rs index 89acf17ec29..81d8c24dc4f 100644 --- a/account_manager/src/keystore.rs +++ b/account_manager/src/keystore.rs @@ -1,13 +1,36 @@ -use crate::cipher::{Aes128Ctr, Cipher, CipherMessage}; -use crate::kdf::{Kdf, Pbkdf2, Prf}; +use crate::cipher::{Cipher, CipherMessage}; +use crate::kdf::Kdf; +use crate::module::CryptoModule; use crypto::digest::Digest; use crypto::sha2::Sha256; -use rand::prelude::*; - #[derive(Debug, PartialEq, Clone)] pub struct Checksum(String); +impl Checksum { + pub fn gen_checksum(message: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.input(message); + hasher.result_str() + } +} + +impl CryptoModule for Checksum { + type Params = (); + + fn function(&self) -> String { + "sha256".to_string() + } + + fn params(&self) -> &Self::Params { + &() + } + + fn message(&self) -> Vec { + self.0.as_bytes().to_vec() + } +} + /// Crypto pub struct Crypto { /// Key derivation function parameters @@ -28,11 +51,7 @@ impl Crypto { // Generate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key pre_image.append(&mut cipher_message.clone()); - // create a Sha256 object - let mut hasher = Sha256::new(); - hasher.input(&pre_image); - // read hash digest - let checksum = hasher.result_str(); + let checksum = Checksum::gen_checksum(&pre_image); Crypto { kdf, checksum: Checksum(checksum), @@ -49,11 +68,7 @@ impl Crypto { // Regenerate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); pre_image.append(&mut self.cipher.message.clone()); - // create a Sha256 object - let mut hasher = Sha256::new(); - hasher.input(&pre_image); - // read hash digest - let checksum = hasher.result_str(); + let checksum = Checksum::gen_checksum(&pre_image); debug_assert_eq!(checksum, self.checksum.0); let secret = self .cipher @@ -63,33 +78,74 @@ impl Crypto { } } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; +// use parity_crypto::aes::{decrypt_128_ctr, encrypt_128_ctr}; +// use parity_crypto::digest; +// use parity_crypto::pbkdf2::{sha256, Salt, Secret}; - #[test] - fn test_vector() { - let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; - let salt_str = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; - let iv_str = "264daa3f303d7259501c93d997d84fe6"; - let password = "testpassword".to_string(); +// #[test] +// fn test_vector() { +// let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; +// let salt_str = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; +// let iv_str = "264daa3f303d7259501c93d997d84fe6"; +// let password = "testpassword".to_string(); - let salt = hex::decode(salt_str).unwrap(); - let iv = hex::decode(iv_str).unwrap(); - let kdf = Pbkdf2 { - c: 262144, - dklen: 32, - prf: Prf::HmacSha256, - salt: salt.to_vec(), - }; - let cipher = Aes128Ctr { iv: iv }; - let secret = hex::decode(secret_str).unwrap(); - let crypto = Crypto::encrypt(password.clone(), &secret, kdf, cipher); - println!("Cipher is {:?}", hex::encode(crypto.cipher.message.clone())); - println!("Checksum is {:?}", crypto.checksum); - let recovered_secret = crypto.decrypt(password); - let recovered_secret_str = hex::encode(recovered_secret); - println!("Secret is {:?}", recovered_secret_str); - assert_eq!(recovered_secret_str, secret_str); - } -} +// let salt = hex::decode(salt_str).unwrap(); +// // let other = derive_key(password.as_bytes(), &salt, 262144, 1, 8); +// // println!("{:?}", other); +// let iv = hex::decode(iv_str).unwrap(); +// let kdf = Scrypt { +// n: 262144, +// dklen: 32, +// p: 1, +// r: 8, +// salt: salt.to_vec(), +// }; +// let dk = kdf.derive_key(&password); +// println!("Dk {}", hex::encode(dk)); +// // let cipher = Aes128Ctr { iv: iv }; +// // let secret = hex::decode(secret_str).unwrap(); +// // let crypto = Crypto::encrypt(password.clone(), &secret, kdf, cipher); +// // println!("Cipher is {:?}", hex::encode(crypto.cipher.message.clone())); +// // println!("Checksum is {:?}", crypto.checksum); +// // let recovered_secret = crypto.decrypt(password); +// // let recovered_secret_str = hex::encode(recovered_secret); +// // println!("Secret is {:?}", recovered_secret_str); +// // assert_eq!(recovered_secret_str, secret_str); +// } + +// #[test] +// fn test_keystore() { +// let password = "testpassword"; +// let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; +// let salt_str = "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"; +// let iv_str = "264daa3f303d7259501c93d997d84fe6"; +// // Derive decryption key from password +// let iterations = 262144; +// let dk_len = 32; +// let salt = hex::decode(salt_str).unwrap(); +// let mut decryption_key = [0; 32]; +// // Run the kdf on the password to derive decryption key +// sha256( +// iterations, +// Salt(&salt), +// Secret(password.as_bytes()), +// &mut decryption_key, +// ); +// // Encryption params +// let iv = hex::decode(iv_str).unwrap(); +// let mut cipher_message = vec![0; 48]; +// let secret = hex::decode(secret_str).unwrap(); +// // Encrypt bls secret key with first 16 bytes as aes key +// encrypt_128_ctr(&decryption_key[0..16], &iv, &secret, &mut cipher_message).unwrap(); +// // Generate checksum +// let mut pre_image: Vec = decryption_key[16..32].to_owned(); // last 16 bytes of decryption key +// pre_image.append(&mut cipher_message.clone()); +// let checksum_message: Vec = digest::sha256(&pre_image).to_owned(); + +// println!("Ciphertext: {}", hex::encode(cipher_message)); +// println!("Checksum: {}", hex::encode(checksum_message)); +// } +// } diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 10993485ad3..d22b7a2cf81 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -6,11 +6,7 @@ use std::path::PathBuf; use types::test_utils::generate_deterministic_keypair; use types::DepositData; use validator_client::Config as ValidatorClientConfig; -mod cipher; mod deposit; -mod kdf; -mod keystore; - pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml"; diff --git a/account_manager/src/module.rs b/account_manager/src/module.rs new file mode 100644 index 00000000000..c5830b72d06 --- /dev/null +++ b/account_manager/src/module.rs @@ -0,0 +1,7 @@ +pub trait CryptoModule { + type Params; + + fn function(&self) -> String; + fn params(&self) -> &Self::Params; + fn message(&self) -> Vec; +} From cc56ac83e3bb944d96b8429d9103025bfefce77b Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 4 Nov 2019 16:56:26 +0530 Subject: [PATCH 04/25] Working naive design --- account_manager/src/checksum.rs | 25 ++++ account_manager/src/cipher.rs | 44 +++--- account_manager/src/kdf.rs | 95 ++++--------- account_manager/src/keystore.rs | 235 +++++++++++++++----------------- 4 files changed, 178 insertions(+), 221 deletions(-) create mode 100644 account_manager/src/checksum.rs diff --git a/account_manager/src/checksum.rs b/account_manager/src/checksum.rs new file mode 100644 index 00000000000..dbbff8ddb5d --- /dev/null +++ b/account_manager/src/checksum.rs @@ -0,0 +1,25 @@ +use crypto::digest::Digest; +use crypto::sha2::Sha256; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct ChecksumModule { + pub function: String, + pub params: (), + pub message: String, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Checksum(String); + +impl Checksum { + pub fn gen_checksum(message: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.input(message); + hasher.result_str() + } + + pub fn function() -> String { + "sha256".to_string() + } +} diff --git a/account_manager/src/cipher.rs b/account_manager/src/cipher.rs index dd4ef940c8d..03b6c908228 100644 --- a/account_manager/src/cipher.rs +++ b/account_manager/src/cipher.rs @@ -1,26 +1,29 @@ -use crate::module::CryptoModule; use crypto::aes::{ctr, KeySize}; +use serde::{Deserialize, Serialize}; + const IV_SIZE: usize = 16; -pub struct CipherMessage { - pub cipher: C, - pub message: Vec, +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct CipherModule { + pub function: String, + pub params: Cipher, + pub message: String, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Aes128Ctr { pub iv: Vec, } -impl Cipher for Aes128Ctr { - fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec { +impl Aes128Ctr { + pub fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec { // TODO: sanity checks let mut ct = vec![0; pt.len()]; ctr(KeySize::KeySize128, key, &self.iv).process(pt, &mut ct); ct } - fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec { + pub fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec { // TODO: sanity checks let mut pt = vec![0; ct.len()]; ctr(KeySize::KeySize128, key, &self.iv).process(ct, &mut pt); @@ -28,23 +31,16 @@ impl Cipher for Aes128Ctr { } } -pub trait Cipher { - fn encrypt(&self, key: &[u8], pt: &[u8]) -> Vec; - fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec; +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Cipher { + Aes128Ctr(Aes128Ctr), } -impl CryptoModule for CipherMessage { - type Params = C; - - fn function(&self) -> String { - "aes-128-ctr".to_string() - } - - fn params(&self) -> &Self::Params { - &self.cipher - } - - fn message(&self) -> Vec { - self.message.clone() +impl Cipher { + pub fn function(&self) -> String { + match &self { + Cipher::Aes128Ctr(_) => "aes-128-ctr".to_string(), + } } } diff --git a/account_manager/src/kdf.rs b/account_manager/src/kdf.rs index e660e5485d8..e6c57a7d0a6 100644 --- a/account_manager/src/kdf.rs +++ b/account_manager/src/kdf.rs @@ -1,7 +1,6 @@ -use crate::module::CryptoModule; use crypto::sha2::Sha256; use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Pbkdf2 { @@ -11,8 +10,8 @@ pub struct Pbkdf2 { pub salt: Vec, } -impl Kdf for Pbkdf2 { - fn derive_key(&self, password: &str) -> Vec { +impl Pbkdf2 { + pub fn derive_key(&self, password: &str) -> Vec { let mut dk = [0u8; 32]; let mut mac = self.prf.mac(password.as_bytes()); pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, &mut dk); @@ -37,25 +36,36 @@ fn log_2(x: u32) -> u32 { num_bits::() as u32 - x.leading_zeros() - 1 } -impl Kdf for Scrypt { - fn derive_key(&self, password: &str) -> Vec { +impl Scrypt { + pub fn derive_key(&self, password: &str) -> Vec { let mut dk = [0u8; 32]; - let params = scrypt::ScryptParams::new(log_2(self.p) as u8, self.r, self.p); + let params = scrypt::ScryptParams::new(log_2(self.n) as u8, self.r, self.p); scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk); dk.to_vec() } } -pub trait Kdf: Serialize + DeserializeOwned { - /// Derive the key from the password - fn derive_key(&self, password: &str) -> Vec; +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Kdf { + Scrypt(Scrypt), + Pbkdf2(Pbkdf2), +} + +impl Kdf { + pub fn function(&self) -> String { + match &self { + Kdf::Pbkdf2(_) => "pbkdf2".to_string(), + Kdf::Scrypt(_) => "scrypt".to_string(), + } + } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct KdfModule { - function: String, - params: Kdf, - message: String, +pub struct KdfModule { + pub function: String, + pub params: Kdf, + pub message: String, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -72,60 +82,3 @@ impl Prf { } } } - -impl CryptoModule for Scrypt { - type Params = Scrypt; - - fn function(&self) -> String { - "scrypt".to_string() - } - - fn params(&self) -> &Self::Params { - &self - } - - fn message(&self) -> Vec { - Vec::new() - } -} - -impl CryptoModule for Pbkdf2 { - type Params = Pbkdf2; - - fn function(&self) -> String { - "pbkdf2".to_string() - } - - fn params(&self) -> &Self::Params { - &self - } - - fn message(&self) -> Vec { - Vec::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_log() { - let scrypt = Scrypt { - dklen: 32, - n: 262144, - r: 8, - p: 1, - salt: vec![0; 32], - }; - - let p = Pbkdf2 { - dklen: 32, - c: 262144, - prf: Prf::HmacSha256, - salt: vec![0; 32], - }; - let serialized = serde_json::to_string(&p).unwrap(); - println!("{}", serialized); - } -} diff --git a/account_manager/src/keystore.rs b/account_manager/src/keystore.rs index 81d8c24dc4f..bbd4b3ce727 100644 --- a/account_manager/src/keystore.rs +++ b/account_manager/src/keystore.rs @@ -1,151 +1,134 @@ -use crate::cipher::{Cipher, CipherMessage}; -use crate::kdf::Kdf; -use crate::module::CryptoModule; -use crypto::digest::Digest; -use crypto::sha2::Sha256; - -#[derive(Debug, PartialEq, Clone)] -pub struct Checksum(String); - -impl Checksum { - pub fn gen_checksum(message: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.input(message); - hasher.result_str() - } -} - -impl CryptoModule for Checksum { - type Params = (); - - fn function(&self) -> String { - "sha256".to_string() - } - - fn params(&self) -> &Self::Params { - &() - } - - fn message(&self) -> Vec { - self.0.as_bytes().to_vec() - } -} +use crate::checksum::{Checksum, ChecksumModule}; +use crate::cipher::{Cipher, CipherModule}; +use crate::kdf::{Kdf, KdfModule}; +use serde::{Deserialize, Serialize}; /// Crypto -pub struct Crypto { - /// Key derivation function parameters - pub kdf: K, - /// Checksum for password verification - pub checksum: Checksum, - /// CipherParams parameters - pub cipher: CipherMessage, +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Crypto { + pub kdf: KdfModule, + pub checksum: ChecksumModule, + pub cipher: CipherModule, } -impl Crypto { - pub fn encrypt(password: String, secret: &[u8], kdf: K, cipher: C) -> Self { +impl Crypto { + pub fn encrypt(password: String, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self { // Generate derived key - let derived_key = kdf.derive_key(&password); + let derived_key = match &kdf { + Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), + Kdf::Scrypt(scrypt) => scrypt.derive_key(&password), + }; // Encrypt secret - let cipher_message = cipher.encrypt(&derived_key[0..16], secret); + let cipher_message = match &cipher { + Cipher::Aes128Ctr(cipher) => cipher.encrypt(&derived_key[0..16], secret), + }; // Generate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key pre_image.append(&mut cipher_message.clone()); let checksum = Checksum::gen_checksum(&pre_image); Crypto { - kdf, - checksum: Checksum(checksum), - cipher: CipherMessage { - cipher, - message: cipher_message, + kdf: KdfModule { + function: kdf.function(), + params: kdf.clone(), + message: "".to_string(), + }, + checksum: ChecksumModule { + function: Checksum::function(), + params: (), + message: checksum, + }, + cipher: CipherModule { + function: cipher.function(), + params: cipher.clone(), + message: hex::encode(cipher_message), }, } } pub fn decrypt(&self, password: String) -> Vec { // Genrate derived key - let derived_key = self.kdf.derive_key(&password); + let derived_key = match &self.kdf.params { + Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), + Kdf::Scrypt(scrypt) => scrypt.derive_key(&password), + }; // Regenerate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); - pre_image.append(&mut self.cipher.message.clone()); + pre_image.append(&mut hex::decode(self.cipher.message.clone()).unwrap()); let checksum = Checksum::gen_checksum(&pre_image); - debug_assert_eq!(checksum, self.checksum.0); - let secret = self - .cipher - .cipher - .decrypt(&derived_key[0..16], &self.cipher.message); + debug_assert_eq!(checksum, self.checksum.message); + let secret = match &self.cipher.params { + Cipher::Aes128Ctr(cipher) => cipher.decrypt( + &derived_key[0..16], + &hex::decode(self.cipher.message.clone()).unwrap(), + ), + }; return secret; } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use parity_crypto::aes::{decrypt_128_ctr, encrypt_128_ctr}; -// use parity_crypto::digest; -// use parity_crypto::pbkdf2::{sha256, Salt, Secret}; - -// #[test] -// fn test_vector() { -// let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; -// let salt_str = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; -// let iv_str = "264daa3f303d7259501c93d997d84fe6"; -// let password = "testpassword".to_string(); - -// let salt = hex::decode(salt_str).unwrap(); -// // let other = derive_key(password.as_bytes(), &salt, 262144, 1, 8); -// // println!("{:?}", other); -// let iv = hex::decode(iv_str).unwrap(); -// let kdf = Scrypt { -// n: 262144, -// dklen: 32, -// p: 1, -// r: 8, -// salt: salt.to_vec(), -// }; -// let dk = kdf.derive_key(&password); -// println!("Dk {}", hex::encode(dk)); -// // let cipher = Aes128Ctr { iv: iv }; -// // let secret = hex::decode(secret_str).unwrap(); -// // let crypto = Crypto::encrypt(password.clone(), &secret, kdf, cipher); -// // println!("Cipher is {:?}", hex::encode(crypto.cipher.message.clone())); -// // println!("Checksum is {:?}", crypto.checksum); -// // let recovered_secret = crypto.decrypt(password); -// // let recovered_secret_str = hex::encode(recovered_secret); -// // println!("Secret is {:?}", recovered_secret_str); -// // assert_eq!(recovered_secret_str, secret_str); -// } - -// #[test] -// fn test_keystore() { -// let password = "testpassword"; -// let secret_str = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"; -// let salt_str = "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"; -// let iv_str = "264daa3f303d7259501c93d997d84fe6"; -// // Derive decryption key from password -// let iterations = 262144; -// let dk_len = 32; -// let salt = hex::decode(salt_str).unwrap(); -// let mut decryption_key = [0; 32]; -// // Run the kdf on the password to derive decryption key -// sha256( -// iterations, -// Salt(&salt), -// Secret(password.as_bytes()), -// &mut decryption_key, -// ); -// // Encryption params -// let iv = hex::decode(iv_str).unwrap(); -// let mut cipher_message = vec![0; 48]; -// let secret = hex::decode(secret_str).unwrap(); -// // Encrypt bls secret key with first 16 bytes as aes key -// encrypt_128_ctr(&decryption_key[0..16], &iv, &secret, &mut cipher_message).unwrap(); -// // Generate checksum -// let mut pre_image: Vec = decryption_key[16..32].to_owned(); // last 16 bytes of decryption key -// pre_image.append(&mut cipher_message.clone()); -// let checksum_message: Vec = digest::sha256(&pre_image).to_owned(); - -// println!("Ciphertext: {}", hex::encode(cipher_message)); -// println!("Checksum: {}", hex::encode(checksum_message)); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::cipher::{Aes128Ctr, Cipher}; + use crate::kdf::{Kdf, Pbkdf2, Prf, Scrypt}; + + #[test] + fn test_pbkdf2() { + let secret = + hex::decode("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") + .unwrap(); + let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") + .unwrap(); + let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); + let password = "testpassword".to_string(); + + let kdf = Kdf::Pbkdf2(Pbkdf2 { + dklen: 32, + c: 262144, + prf: Prf::HmacSha256, + salt: salt, + }); + + let cipher = Cipher::Aes128Ctr(Aes128Ctr { iv }); + + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); + let json = serde_json::to_string(&keystore).unwrap(); + println!("{}", json); + + let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); + let recovered_secret = recovered_keystore.decrypt(password); + + assert_eq!(recovered_secret, secret); + } + + #[test] + fn test_scrypt() { + let secret = + hex::decode("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") + .unwrap(); + let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") + .unwrap(); + let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); + let password = "testpassword".to_string(); + + let kdf = Kdf::Scrypt(Scrypt { + dklen: 32, + n: 262144, + r: 8, + p: 1, + salt: salt, + }); + + let cipher = Cipher::Aes128Ctr(Aes128Ctr { iv }); + + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); + let json = serde_json::to_string(&keystore).unwrap(); + println!("{}", json); + + let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); + let recovered_secret = recovered_keystore.decrypt(password); + + assert_eq!(recovered_secret, secret); + } +} From f668763444bd843c5bfff17612b6071f9ad28adc Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 4 Nov 2019 17:47:41 +0530 Subject: [PATCH 05/25] Add keystore struct --- account_manager/src/keystore/mod.rs | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 account_manager/src/keystore/mod.rs diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs new file mode 100644 index 00000000000..b6d6f108c4b --- /dev/null +++ b/account_manager/src/keystore/mod.rs @@ -0,0 +1,62 @@ +mod checksum; +mod cipher; +mod crypto; +mod kdf; +mod module; +use crate::keystore::cipher::Cipher; +use crate::keystore::crypto::Crypto; +use crate::keystore::kdf::Kdf; +use bls::SecretKey; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Version { + #[serde(rename = "4")] + V4, +} + +impl Default for Version { + fn default() -> Self { + Version::V4 + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Keystore { + crypto: Crypto, + uuid: Uuid, + version: Version, +} + +impl Keystore { + pub fn to_keystore(secret_key: &SecretKey, password: String) -> Keystore { + let crypto = Crypto::encrypt( + password, + &secret_key.as_raw().as_bytes(), + Kdf::default(), + Cipher::default(), + ); + let uuid = Uuid::new_v4(); + let version = Version::default(); + Keystore { + crypto, + uuid, + version, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bls::Keypair; + #[test] + fn test_json() { + let keypair = Keypair::random(); + let password = "testpassword".to_string(); + let keystore = Keystore::to_keystore(&keypair.sk, password); + + println!("{}", serde_json::to_string(&keystore).unwrap()); + } +} From dfbb40aad0599dfda651c16de579031e41efd8be Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 4 Nov 2019 17:48:33 +0530 Subject: [PATCH 06/25] Move keystore files into their own module --- account_manager/Cargo.toml | 1 + .../src/{ => keystore}/checksum.rs | 0 account_manager/src/{ => keystore}/cipher.rs | 11 +++++- .../src/{keystore.rs => keystore/crypto.rs} | 26 +++++++++----- account_manager/src/{ => keystore}/kdf.rs | 35 +++++++++++++++++++ account_manager/src/{ => keystore}/module.rs | 0 account_manager/src/main.rs | 1 + 7 files changed, 65 insertions(+), 9 deletions(-) rename account_manager/src/{ => keystore}/checksum.rs (100%) rename account_manager/src/{ => keystore}/cipher.rs (82%) rename account_manager/src/{keystore.rs => keystore/crypto.rs} (86%) rename account_manager/src/{ => keystore}/kdf.rs (72%) rename account_manager/src/{ => keystore}/module.rs (100%) diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 5d50a8104b7..1e9613645a3 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -32,5 +32,6 @@ rust-crypto = "0.2.36" hex = "0.4.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +uuid = { version = "0.8", features = ["serde", "v4"] } diff --git a/account_manager/src/checksum.rs b/account_manager/src/keystore/checksum.rs similarity index 100% rename from account_manager/src/checksum.rs rename to account_manager/src/keystore/checksum.rs diff --git a/account_manager/src/cipher.rs b/account_manager/src/keystore/cipher.rs similarity index 82% rename from account_manager/src/cipher.rs rename to account_manager/src/keystore/cipher.rs index 03b6c908228..60c8a0461d4 100644 --- a/account_manager/src/cipher.rs +++ b/account_manager/src/keystore/cipher.rs @@ -1,5 +1,7 @@ use crypto::aes::{ctr, KeySize}; +use rand::prelude::*; use serde::{Deserialize, Serialize}; +use std::default::Default; const IV_SIZE: usize = 16; @@ -12,7 +14,7 @@ pub struct CipherModule { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Aes128Ctr { - pub iv: Vec, + pub iv: [u8; 16], } impl Aes128Ctr { @@ -37,6 +39,13 @@ pub enum Cipher { Aes128Ctr(Aes128Ctr), } +impl Default for Cipher { + fn default() -> Self { + let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>(); + Cipher::Aes128Ctr(Aes128Ctr { iv }) + } +} + impl Cipher { pub fn function(&self) -> String { match &self { diff --git a/account_manager/src/keystore.rs b/account_manager/src/keystore/crypto.rs similarity index 86% rename from account_manager/src/keystore.rs rename to account_manager/src/keystore/crypto.rs index bbd4b3ce727..15100935ca9 100644 --- a/account_manager/src/keystore.rs +++ b/account_manager/src/keystore/crypto.rs @@ -1,9 +1,8 @@ -use crate::checksum::{Checksum, ChecksumModule}; -use crate::cipher::{Cipher, CipherModule}; -use crate::kdf::{Kdf, KdfModule}; +use crate::keystore::checksum::{Checksum, ChecksumModule}; +use crate::keystore::cipher::{Cipher, CipherModule}; +use crate::keystore::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; -/// Crypto #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Crypto { pub kdf: KdfModule, @@ -70,8 +69,15 @@ impl Crypto { #[cfg(test)] mod tests { use super::*; - use crate::cipher::{Aes128Ctr, Cipher}; - use crate::kdf::{Kdf, Pbkdf2, Prf, Scrypt}; + use crate::keystore::cipher::{Aes128Ctr, Cipher}; + use crate::keystore::kdf::{Kdf, Pbkdf2, Prf, Scrypt}; + + fn from_slice(bytes: &[u8]) -> [u8; 16] { + let mut array = [0; 16]; + let bytes = &bytes[..array.len()]; // panics if not enough data + array.copy_from_slice(bytes); + array + } #[test] fn test_pbkdf2() { @@ -90,7 +96,9 @@ mod tests { salt: salt, }); - let cipher = Cipher::Aes128Ctr(Aes128Ctr { iv }); + let cipher = Cipher::Aes128Ctr(Aes128Ctr { + iv: from_slice(&iv), + }); let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); let json = serde_json::to_string(&keystore).unwrap(); @@ -120,7 +128,9 @@ mod tests { salt: salt, }); - let cipher = Cipher::Aes128Ctr(Aes128Ctr { iv }); + let cipher = Cipher::Aes128Ctr(Aes128Ctr { + iv: from_slice(&iv), + }); let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); let json = serde_json::to_string(&keystore).unwrap(); diff --git a/account_manager/src/kdf.rs b/account_manager/src/keystore/kdf.rs similarity index 72% rename from account_manager/src/kdf.rs rename to account_manager/src/keystore/kdf.rs index e6c57a7d0a6..0ecde162055 100644 --- a/account_manager/src/kdf.rs +++ b/account_manager/src/keystore/kdf.rs @@ -1,6 +1,8 @@ use crypto::sha2::Sha256; use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; +use rand::prelude::*; use serde::{Deserialize, Serialize}; +use std::default::Default; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Pbkdf2 { @@ -9,6 +11,18 @@ pub struct Pbkdf2 { pub prf: Prf, pub salt: Vec, } +impl Default for Pbkdf2 { + // TODO: verify size of salt + fn default() -> Self { + let salt = rand::thread_rng().gen::<[u8; 32]>(); + Pbkdf2 { + dklen: 32, + c: 262144, + prf: Prf::HmacSha256, + salt: salt.to_vec(), + } + } +} impl Pbkdf2 { pub fn derive_key(&self, password: &str) -> Vec { @@ -39,12 +53,27 @@ fn log_2(x: u32) -> u32 { impl Scrypt { pub fn derive_key(&self, password: &str) -> Vec { let mut dk = [0u8; 32]; + // TODO: verify `N` is power of 2 let params = scrypt::ScryptParams::new(log_2(self.n) as u8, self.r, self.p); scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk); dk.to_vec() } } +impl Default for Scrypt { + // TODO: verify size of salt + fn default() -> Self { + let salt = rand::thread_rng().gen::<[u8; 32]>(); + Scrypt { + dklen: 32, + n: 262144, + r: 8, + p: 1, + salt: salt.to_vec(), + } + } +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Kdf { @@ -52,6 +81,12 @@ pub enum Kdf { Pbkdf2(Pbkdf2), } +impl Default for Kdf { + fn default() -> Self { + Kdf::Pbkdf2(Pbkdf2::default()) + } +} + impl Kdf { pub fn function(&self) -> String { match &self { diff --git a/account_manager/src/module.rs b/account_manager/src/keystore/module.rs similarity index 100% rename from account_manager/src/module.rs rename to account_manager/src/keystore/module.rs diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index d22b7a2cf81..1a181202912 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -7,6 +7,7 @@ use types::test_utils::generate_deterministic_keypair; use types::DepositData; use validator_client::Config as ValidatorClientConfig; mod deposit; +mod keystore; pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml"; From d6204a5b51b40ee4e3986ef161d3e07ce4167232 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 5 Nov 2019 01:29:28 +0530 Subject: [PATCH 07/25] Add serde (de)serialize_with magic --- account_manager/src/keystore/cipher.rs | 51 +++++++++++++++++++++++++- account_manager/src/keystore/crypto.rs | 35 +++++++++++------- account_manager/src/keystore/kdf.rs | 45 ++++++++++++++++++++++- account_manager/src/keystore/mod.rs | 8 ++-- 4 files changed, 120 insertions(+), 19 deletions(-) diff --git a/account_manager/src/keystore/cipher.rs b/account_manager/src/keystore/cipher.rs index 60c8a0461d4..c74817babfa 100644 --- a/account_manager/src/keystore/cipher.rs +++ b/account_manager/src/keystore/cipher.rs @@ -1,8 +1,15 @@ use crypto::aes::{ctr, KeySize}; use rand::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; +fn from_slice(bytes: &[u8]) -> [u8; 16] { + let mut array = [0; 16]; + let bytes = &bytes[..array.len()]; // panics if not enough data + array.copy_from_slice(bytes); + array +} + const IV_SIZE: usize = 16; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -14,6 +21,8 @@ pub struct CipherModule { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Aes128Ctr { + #[serde(serialize_with = "serialize_iv")] + #[serde(deserialize_with = "deserialize_iv")] pub iv: [u8; 16], } @@ -33,6 +42,34 @@ impl Aes128Ctr { } } +fn serialize_iv(x: &[u8], s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(&hex::encode(x)) +} + +fn deserialize_iv<'de, D>(deserializer: D) -> Result<[u8; 16], D::Error> +where + D: de::Deserializer<'de>, +{ + struct StringVisitor; + impl<'de> de::Visitor<'de> for StringVisitor { + type Value = [u8; 16]; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("String should be hex format and 16 bytes in length") + } + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let bytes = hex::decode(v).map_err(E::custom)?; + Ok(from_slice(&bytes)) + } + } + deserializer.deserialize_any(StringVisitor) +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Cipher { @@ -53,3 +90,15 @@ impl Cipher { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde() { + // let json = r#"{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}"#; + // let data: Pbkdf2 = serde_json::from_str(&json).unwrap(); + // println!("{:?}", data); + } +} diff --git a/account_manager/src/keystore/crypto.rs b/account_manager/src/keystore/crypto.rs index 15100935ca9..81bee4279bd 100644 --- a/account_manager/src/keystore/crypto.rs +++ b/account_manager/src/keystore/crypto.rs @@ -11,7 +11,12 @@ pub struct Crypto { } impl Crypto { - pub fn encrypt(password: String, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self { + pub fn encrypt( + password: String, + secret: &[u8], + kdf: Kdf, + cipher: Cipher, + ) -> Result { // Generate derived key let derived_key = match &kdf { Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), @@ -26,7 +31,7 @@ impl Crypto { let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key pre_image.append(&mut cipher_message.clone()); let checksum = Checksum::gen_checksum(&pre_image); - Crypto { + Ok(Crypto { kdf: KdfModule { function: kdf.function(), params: kdf.clone(), @@ -42,10 +47,10 @@ impl Crypto { params: cipher.clone(), message: hex::encode(cipher_message), }, - } + }) } - pub fn decrypt(&self, password: String) -> Vec { + pub fn decrypt(&self, password: String) -> Result { // Genrate derived key let derived_key = match &self.kdf.params { Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), @@ -53,16 +58,20 @@ impl Crypto { }; // Regenerate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); - pre_image.append(&mut hex::decode(self.cipher.message.clone()).unwrap()); + pre_image.append( + &mut hex::decode(self.cipher.message.clone()) + .map_err(|e| format!("Cipher message should be in hex: {}", e))?, + ); let checksum = Checksum::gen_checksum(&pre_image); debug_assert_eq!(checksum, self.checksum.message); let secret = match &self.cipher.params { Cipher::Aes128Ctr(cipher) => cipher.decrypt( &derived_key[0..16], - &hex::decode(self.cipher.message.clone()).unwrap(), + &hex::decode(self.cipher.message.clone()) + .map_err(|e| format!("Cipher message should be in hex: {}", e))?, ), }; - return secret; + Ok(hex::encode(secret)) } } @@ -100,14 +109,14 @@ mod tests { iv: from_slice(&iv), }); - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); let json = serde_json::to_string(&keystore).unwrap(); println!("{}", json); let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); - let recovered_secret = recovered_keystore.decrypt(password); + let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(recovered_secret, secret); + assert_eq!(hex::encode(secret), recovered_secret); } #[test] @@ -132,13 +141,13 @@ mod tests { iv: from_slice(&iv), }); - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); let json = serde_json::to_string(&keystore).unwrap(); println!("{}", json); let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); - let recovered_secret = recovered_keystore.decrypt(password); + let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(recovered_secret, secret); + assert_eq!(hex::encode(secret), recovered_secret); } } diff --git a/account_manager/src/keystore/kdf.rs b/account_manager/src/keystore/kdf.rs index 0ecde162055..52b82514951 100644 --- a/account_manager/src/keystore/kdf.rs +++ b/account_manager/src/keystore/kdf.rs @@ -1,7 +1,7 @@ use crypto::sha2::Sha256; use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; use rand::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -9,6 +9,8 @@ pub struct Pbkdf2 { pub c: u32, pub dklen: u32, pub prf: Prf, + #[serde(serialize_with = "serialize_salt")] + #[serde(deserialize_with = "deserialize_salt")] pub salt: Vec, } impl Default for Pbkdf2 { @@ -39,6 +41,8 @@ pub struct Scrypt { pub n: u32, pub r: u32, pub p: u32, + #[serde(serialize_with = "serialize_salt")] + #[serde(deserialize_with = "deserialize_salt")] pub salt: Vec, } const fn num_bits() -> usize { @@ -74,6 +78,33 @@ impl Default for Scrypt { } } +fn serialize_salt(x: &Vec, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(&hex::encode(x)) +} + +fn deserialize_salt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + struct StringVisitor; + impl<'de> de::Visitor<'de> for StringVisitor { + type Value = Vec; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("String should be hex format") + } + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + hex::decode(v).map_err(E::custom) + } + } + deserializer.deserialize_any(StringVisitor) +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Kdf { @@ -117,3 +148,15 @@ impl Prf { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde() { + let json = r#"{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}"#; + let data: Pbkdf2 = serde_json::from_str(&json).unwrap(); + println!("{:?}", data); + } +} diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index b6d6f108c4b..a376682bee1 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -30,20 +30,20 @@ pub struct Keystore { } impl Keystore { - pub fn to_keystore(secret_key: &SecretKey, password: String) -> Keystore { + pub fn to_keystore(secret_key: &SecretKey, password: String) -> Result { let crypto = Crypto::encrypt( password, &secret_key.as_raw().as_bytes(), Kdf::default(), Cipher::default(), - ); + )?; let uuid = Uuid::new_v4(); let version = Version::default(); - Keystore { + Ok(Keystore { crypto, uuid, version, - } + }) } } From 9716c4110a0be7782f8bc0cda783a8502a3cbce3 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 5 Nov 2019 11:29:31 +0530 Subject: [PATCH 08/25] Add keystore test --- account_manager/src/keystore/checksum.rs | 3 ++- account_manager/src/keystore/cipher.rs | 8 ++++---- account_manager/src/keystore/crypto.rs | 11 ++++++----- account_manager/src/keystore/mod.rs | 17 ++++++++++++++--- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/account_manager/src/keystore/checksum.rs b/account_manager/src/keystore/checksum.rs index dbbff8ddb5d..0ca759db66a 100644 --- a/account_manager/src/keystore/checksum.rs +++ b/account_manager/src/keystore/checksum.rs @@ -1,11 +1,12 @@ use crypto::digest::Digest; use crypto::sha2::Sha256; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ChecksumModule { pub function: String, - pub params: (), + pub params: BTreeMap<(), ()>, // TODO: need a better way to encode empty json object pub message: String, } diff --git a/account_manager/src/keystore/cipher.rs b/account_manager/src/keystore/cipher.rs index c74817babfa..02fbcdc0dfe 100644 --- a/account_manager/src/keystore/cipher.rs +++ b/account_manager/src/keystore/cipher.rs @@ -3,15 +3,15 @@ use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; -fn from_slice(bytes: &[u8]) -> [u8; 16] { - let mut array = [0; 16]; +const IV_SIZE: usize = 16; + +fn from_slice(bytes: &[u8]) -> [u8; IV_SIZE] { + let mut array = [0; IV_SIZE]; let bytes = &bytes[..array.len()]; // panics if not enough data array.copy_from_slice(bytes); array } -const IV_SIZE: usize = 16; - #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct CipherModule { pub function: String, diff --git a/account_manager/src/keystore/crypto.rs b/account_manager/src/keystore/crypto.rs index 81bee4279bd..d8159a6128f 100644 --- a/account_manager/src/keystore/crypto.rs +++ b/account_manager/src/keystore/crypto.rs @@ -2,6 +2,7 @@ use crate::keystore::checksum::{Checksum, ChecksumModule}; use crate::keystore::cipher::{Cipher, CipherModule}; use crate::keystore::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Crypto { @@ -39,7 +40,7 @@ impl Crypto { }, checksum: ChecksumModule { function: Checksum::function(), - params: (), + params: BTreeMap::new(), message: checksum, }, cipher: CipherModule { @@ -50,7 +51,7 @@ impl Crypto { }) } - pub fn decrypt(&self, password: String) -> Result { + pub fn decrypt(&self, password: String) -> Result, String> { // Genrate derived key let derived_key = match &self.kdf.params { Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), @@ -71,7 +72,7 @@ impl Crypto { .map_err(|e| format!("Cipher message should be in hex: {}", e))?, ), }; - Ok(hex::encode(secret)) + Ok(secret) } } @@ -116,7 +117,7 @@ mod tests { let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(hex::encode(secret), recovered_secret); + assert_eq!(secret, recovered_secret); } #[test] @@ -148,6 +149,6 @@ mod tests { let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(hex::encode(secret), recovered_secret); + assert_eq!(secret, recovered_secret); } } diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index a376682bee1..ca25adbee5b 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -45,6 +45,13 @@ impl Keystore { version, }) } + + pub fn from_keystore(keystore_str: String, password: String) -> Result { + let keystore: Keystore = serde_json::from_str(&keystore_str) + .map_err(|e| format!("Keystore file invalid: {}", e))?; + let sk = keystore.crypto.decrypt(password)?; + SecretKey::from_bytes(&sk).map_err(|e| format!("Invalid secret key {:?}", e)) + } } #[cfg(test)] @@ -52,11 +59,15 @@ mod tests { use super::*; use bls::Keypair; #[test] - fn test_json() { + fn test_keystore() { let keypair = Keypair::random(); let password = "testpassword".to_string(); - let keystore = Keystore::to_keystore(&keypair.sk, password); + let keystore = Keystore::to_keystore(&keypair.sk, password.clone()).unwrap(); + + let json_str = serde_json::to_string(&keystore).unwrap(); + println!("{}", json_str); - println!("{}", serde_json::to_string(&keystore).unwrap()); + let sk = Keystore::from_keystore(json_str, password).unwrap(); + assert_eq!(sk, keypair.sk); } } From d80131626a695efa256f92078487cd670772da28 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 5 Nov 2019 12:13:12 +0530 Subject: [PATCH 09/25] Fix tests --- account_manager/src/keystore/cipher.rs | 12 ------------ account_manager/src/keystore/crypto.rs | 17 ++++++++++++++--- account_manager/src/keystore/kdf.rs | 12 ------------ 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/account_manager/src/keystore/cipher.rs b/account_manager/src/keystore/cipher.rs index 02fbcdc0dfe..1cc892f49a6 100644 --- a/account_manager/src/keystore/cipher.rs +++ b/account_manager/src/keystore/cipher.rs @@ -90,15 +90,3 @@ impl Cipher { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_serde() { - // let json = r#"{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}"#; - // let data: Pbkdf2 = serde_json::from_str(&json).unwrap(); - // println!("{:?}", data); - } -} diff --git a/account_manager/src/keystore/crypto.rs b/account_manager/src/keystore/crypto.rs index d8159a6128f..c85536c8154 100644 --- a/account_manager/src/keystore/crypto.rs +++ b/account_manager/src/keystore/crypto.rs @@ -27,7 +27,6 @@ impl Crypto { let cipher_message = match &cipher { Cipher::Aes128Ctr(cipher) => cipher.encrypt(&derived_key[0..16], secret), }; - // Generate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key pre_image.append(&mut cipher_message.clone()); @@ -92,8 +91,10 @@ mod tests { #[test] fn test_pbkdf2() { let secret = - hex::decode("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") + hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") .unwrap(); + let expected_checksum = "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8"; + let expected_cipher = "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48"; let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); @@ -111,6 +112,10 @@ mod tests { }); let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); + + assert_eq!(expected_checksum, keystore.checksum.message); + assert_eq!(expected_cipher, keystore.cipher.message); + let json = serde_json::to_string(&keystore).unwrap(); println!("{}", json); @@ -123,8 +128,10 @@ mod tests { #[test] fn test_scrypt() { let secret = - hex::decode("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") + hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") .unwrap(); + let expected_checksum = "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb"; + let expected_cipher = "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30"; let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); @@ -143,6 +150,10 @@ mod tests { }); let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); + + assert_eq!(expected_checksum, keystore.checksum.message); + assert_eq!(expected_cipher, keystore.cipher.message); + let json = serde_json::to_string(&keystore).unwrap(); println!("{}", json); diff --git a/account_manager/src/keystore/kdf.rs b/account_manager/src/keystore/kdf.rs index 52b82514951..02941b48568 100644 --- a/account_manager/src/keystore/kdf.rs +++ b/account_manager/src/keystore/kdf.rs @@ -148,15 +148,3 @@ impl Prf { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_serde() { - let json = r#"{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}"#; - let data: Pbkdf2 = serde_json::from_str(&json).unwrap(); - println!("{:?}", data); - } -} From 30027b1a57103c30a26ec69a71af0114fb63ed98 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 5 Nov 2019 17:16:21 +0530 Subject: [PATCH 10/25] Add comments and minor fixes --- account_manager/src/keystore/checksum.rs | 2 + account_manager/src/keystore/cipher.rs | 7 +++- account_manager/src/keystore/crypto.rs | 30 ++++++++------ account_manager/src/keystore/kdf.rs | 51 +++++++++++++++--------- account_manager/src/keystore/mod.rs | 40 +++++++++++-------- account_manager/src/keystore/module.rs | 7 ---- 6 files changed, 82 insertions(+), 55 deletions(-) delete mode 100644 account_manager/src/keystore/module.rs diff --git a/account_manager/src/keystore/checksum.rs b/account_manager/src/keystore/checksum.rs index 0ca759db66a..4edcf191d47 100644 --- a/account_manager/src/keystore/checksum.rs +++ b/account_manager/src/keystore/checksum.rs @@ -3,6 +3,7 @@ use crypto::sha2::Sha256; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// Checksum module for `Keystore`. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ChecksumModule { pub function: String, @@ -14,6 +15,7 @@ pub struct ChecksumModule { pub struct Checksum(String); impl Checksum { + /// Generate checksum using checksum function. pub fn gen_checksum(message: &[u8]) -> String { let mut hasher = Sha256::new(); hasher.input(message); diff --git a/account_manager/src/keystore/cipher.rs b/account_manager/src/keystore/cipher.rs index 1cc892f49a6..40d03eb38f3 100644 --- a/account_manager/src/keystore/cipher.rs +++ b/account_manager/src/keystore/cipher.rs @@ -5,6 +5,7 @@ use std::default::Default; const IV_SIZE: usize = 16; +/// Convert slice to fixed length array. fn from_slice(bytes: &[u8]) -> [u8; IV_SIZE] { let mut array = [0; IV_SIZE]; let bytes = &bytes[..array.len()]; // panics if not enough data @@ -12,6 +13,7 @@ fn from_slice(bytes: &[u8]) -> [u8; IV_SIZE] { array } +/// Cipher module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct CipherModule { pub function: String, @@ -19,6 +21,7 @@ pub struct CipherModule { pub message: String, } +/// Parameters for AES128 with ctr mode. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Aes128Ctr { #[serde(serialize_with = "serialize_iv")] @@ -42,6 +45,7 @@ impl Aes128Ctr { } } +/// Serialize `iv` to its hex representation. fn serialize_iv(x: &[u8], s: S) -> Result where S: Serializer, @@ -49,13 +53,14 @@ where s.serialize_str(&hex::encode(x)) } +/// Deserialize `iv` from its hex representation to bytes. fn deserialize_iv<'de, D>(deserializer: D) -> Result<[u8; 16], D::Error> where D: de::Deserializer<'de>, { struct StringVisitor; impl<'de> de::Visitor<'de> for StringVisitor { - type Value = [u8; 16]; + type Value = [u8; IV_SIZE]; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("String should be hex format and 16 bytes in length") } diff --git a/account_manager/src/keystore/crypto.rs b/account_manager/src/keystore/crypto.rs index c85536c8154..17cbdc06e59 100644 --- a/account_manager/src/keystore/crypto.rs +++ b/account_manager/src/keystore/crypto.rs @@ -4,6 +4,7 @@ use crate::keystore::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// Crypto module for keystore. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Crypto { pub kdf: KdfModule, @@ -12,12 +13,9 @@ pub struct Crypto { } impl Crypto { - pub fn encrypt( - password: String, - secret: &[u8], - kdf: Kdf, - cipher: Cipher, - ) -> Result { + /// Generate crypto module for `Keystore` given the password, + /// secret to encrypt, kdf params and cipher params. + pub fn encrypt(password: String, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self { // Generate derived key let derived_key = match &kdf { Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), @@ -28,10 +26,10 @@ impl Crypto { Cipher::Aes128Ctr(cipher) => cipher.encrypt(&derived_key[0..16], secret), }; // Generate checksum - let mut pre_image: Vec = derived_key[16..32].to_owned(); // last 16 bytes of decryption key + let mut pre_image: Vec = derived_key[16..32].to_owned(); pre_image.append(&mut cipher_message.clone()); let checksum = Checksum::gen_checksum(&pre_image); - Ok(Crypto { + Crypto { kdf: KdfModule { function: kdf.function(), params: kdf.clone(), @@ -47,9 +45,13 @@ impl Crypto { params: cipher.clone(), message: hex::encode(cipher_message), }, - }) + } } + /// Recover the secret present in the Keystore given the correct password. + /// + /// An error will be returned if `cipher.message` is not in hex format or + /// if password is incorrect. pub fn decrypt(&self, password: String) -> Result, String> { // Genrate derived key let derived_key = match &self.kdf.params { @@ -63,7 +65,11 @@ impl Crypto { .map_err(|e| format!("Cipher message should be in hex: {}", e))?, ); let checksum = Checksum::gen_checksum(&pre_image); - debug_assert_eq!(checksum, self.checksum.message); + + // `password` is incorrect if checksums don't match + if checksum != self.checksum.message { + return Err("Incorrect password. Checksum does not match".into()); + } let secret = match &self.cipher.params { Cipher::Aes128Ctr(cipher) => cipher.decrypt( &derived_key[0..16], @@ -111,7 +117,7 @@ mod tests { iv: from_slice(&iv), }); - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); assert_eq!(expected_checksum, keystore.checksum.message); assert_eq!(expected_cipher, keystore.cipher.message); @@ -149,7 +155,7 @@ mod tests { iv: from_slice(&iv), }); - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher).unwrap(); + let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); assert_eq!(expected_checksum, keystore.checksum.message); assert_eq!(expected_cipher, keystore.cipher.message); diff --git a/account_manager/src/keystore/kdf.rs b/account_manager/src/keystore/kdf.rs index 02941b48568..3039b1b6527 100644 --- a/account_manager/src/keystore/kdf.rs +++ b/account_manager/src/keystore/kdf.rs @@ -4,6 +4,11 @@ use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; +// TODO: verify size of salt +const SALT_SIZE: usize = 32; +const DECRYPTION_KEY_SIZE: u32 = 32; + +/// Parameters for `pbkdf2` key derivation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Pbkdf2 { pub c: u32, @@ -14,27 +19,28 @@ pub struct Pbkdf2 { pub salt: Vec, } impl Default for Pbkdf2 { - // TODO: verify size of salt fn default() -> Self { - let salt = rand::thread_rng().gen::<[u8; 32]>(); + let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); Pbkdf2 { - dklen: 32, + dklen: DECRYPTION_KEY_SIZE, c: 262144, - prf: Prf::HmacSha256, + prf: Prf::default(), salt: salt.to_vec(), } } } impl Pbkdf2 { + /// Derive key from password. pub fn derive_key(&self, password: &str) -> Vec { - let mut dk = [0u8; 32]; + let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; let mut mac = self.prf.mac(password.as_bytes()); pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, &mut dk); dk.to_vec() } } +/// Parameters for `scrypt` key derivation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Scrypt { pub dklen: u32, @@ -45,31 +51,31 @@ pub struct Scrypt { #[serde(deserialize_with = "deserialize_salt")] pub salt: Vec, } -const fn num_bits() -> usize { - std::mem::size_of::() * 8 -} -fn log_2(x: u32) -> u32 { - assert!(x > 0); - num_bits::() as u32 - x.leading_zeros() - 1 +/// Compute floor of log2 of a u32. +fn log2_int(x: u32) -> u32 { + if x == 0 { + return 0; + } + 31 - x.leading_zeros() } impl Scrypt { pub fn derive_key(&self, password: &str) -> Vec { - let mut dk = [0u8; 32]; - // TODO: verify `N` is power of 2 - let params = scrypt::ScryptParams::new(log_2(self.n) as u8, self.r, self.p); + let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; + // Assert that `n` is power of 2 + debug_assert_eq!(self.n, 2u32.pow(log2_int(self.n))); + let params = scrypt::ScryptParams::new(log2_int(self.n) as u8, self.r, self.p); scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk); dk.to_vec() } } impl Default for Scrypt { - // TODO: verify size of salt fn default() -> Self { - let salt = rand::thread_rng().gen::<[u8; 32]>(); + let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); Scrypt { - dklen: 32, + dklen: DECRYPTION_KEY_SIZE, n: 262144, r: 8, p: 1, @@ -78,6 +84,7 @@ impl Default for Scrypt { } } +/// Serialize `salt` to its hex representation. fn serialize_salt(x: &Vec, s: S) -> Result where S: Serializer, @@ -85,6 +92,7 @@ where s.serialize_str(&hex::encode(x)) } +/// Deserialize `salt` from its hex representation to bytes. fn deserialize_salt<'de, D>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -127,6 +135,7 @@ impl Kdf { } } +/// KDF module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct KdfModule { pub function: String, @@ -134,6 +143,7 @@ pub struct KdfModule { pub message: String, } +/// PRF for use in `pbkdf2`. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Prf { #[serde(rename = "hmac-sha256")] @@ -141,10 +151,15 @@ pub enum Prf { } impl Prf { - // TODO: is password what should be passed here? pub fn mac(&self, password: &[u8]) -> impl Mac { match &self { _hmac_sha256 => Hmac::new(Sha256::new(), password), } } } + +impl Default for Prf { + fn default() -> Self { + Prf::HmacSha256 + } +} diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index ca25adbee5b..41846a84e44 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -2,14 +2,14 @@ mod checksum; mod cipher; mod crypto; mod kdf; -mod module; use crate::keystore::cipher::Cipher; use crate::keystore::crypto::Crypto; use crate::keystore::kdf::Kdf; -use bls::SecretKey; +use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use uuid::Uuid; +/// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Version { #[serde(rename = "4")] @@ -30,27 +30,34 @@ pub struct Keystore { } impl Keystore { - pub fn to_keystore(secret_key: &SecretKey, password: String) -> Result { + /// Generate `Keystore` object for a BLS12-381 secret key from a + /// keypair and password. + pub fn to_keystore(keypair: &Keypair, password: String) -> Self { let crypto = Crypto::encrypt( password, - &secret_key.as_raw().as_bytes(), + &keypair.sk.as_raw().as_bytes(), Kdf::default(), Cipher::default(), - )?; + ); let uuid = Uuid::new_v4(); let version = Version::default(); - Ok(Keystore { + Keystore { crypto, uuid, version, - }) + } } - pub fn from_keystore(keystore_str: String, password: String) -> Result { - let keystore: Keystore = serde_json::from_str(&keystore_str) - .map_err(|e| format!("Keystore file invalid: {}", e))?; - let sk = keystore.crypto.decrypt(password)?; - SecretKey::from_bytes(&sk).map_err(|e| format!("Invalid secret key {:?}", e)) + /// Regenerate a BLS12-381 `Keypair` given the `Keystore` object and + /// the correct password. + /// + /// An error is returned if the secret in the `Keystore` is not a valid + /// BLS12-381 secret key or if the password provided is incorrect. + pub fn from_keystore(&self, password: String) -> Result { + let sk = SecretKey::from_bytes(&self.crypto.decrypt(password)?) + .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; + let pk = PublicKey::from_secret_key(&sk); + Ok(Keypair { sk, pk }) } } @@ -62,12 +69,11 @@ mod tests { fn test_keystore() { let keypair = Keypair::random(); let password = "testpassword".to_string(); - let keystore = Keystore::to_keystore(&keypair.sk, password.clone()).unwrap(); + let keystore = Keystore::to_keystore(&keypair, password.clone()); let json_str = serde_json::to_string(&keystore).unwrap(); - println!("{}", json_str); - - let sk = Keystore::from_keystore(json_str, password).unwrap(); - assert_eq!(sk, keypair.sk); + let recovered_keystore: Keystore = serde_json::from_str(&json_str).unwrap(); + let recovered_keypair = recovered_keystore.from_keystore(password).unwrap(); + assert_eq!(keypair, recovered_keypair); } } diff --git a/account_manager/src/keystore/module.rs b/account_manager/src/keystore/module.rs deleted file mode 100644 index c5830b72d06..00000000000 --- a/account_manager/src/keystore/module.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub trait CryptoModule { - type Params; - - fn function(&self) -> String; - fn params(&self) -> &Self::Params; - fn message(&self) -> Vec; -} From 5f4dddfb19bc3caf90a3ec50fae1eadcde51c6a5 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 5 Nov 2019 17:37:26 +0530 Subject: [PATCH 11/25] Pass optional params to `to_keystore` function --- account_manager/src/keystore/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index 41846a84e44..c8b6ecb473b 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -31,13 +31,18 @@ pub struct Keystore { impl Keystore { /// Generate `Keystore` object for a BLS12-381 secret key from a - /// keypair and password. - pub fn to_keystore(keypair: &Keypair, password: String) -> Self { + /// keypair and password. Optionally, provide params for kdf and cipher. + pub fn to_keystore( + keypair: &Keypair, + password: String, + kdf: Option, + cipher: Option, + ) -> Self { let crypto = Crypto::encrypt( password, &keypair.sk.as_raw().as_bytes(), - Kdf::default(), - Cipher::default(), + kdf.unwrap_or_default(), + cipher.unwrap_or_default(), ); let uuid = Uuid::new_v4(); let version = Version::default(); @@ -69,7 +74,7 @@ mod tests { fn test_keystore() { let keypair = Keypair::random(); let password = "testpassword".to_string(); - let keystore = Keystore::to_keystore(&keypair, password.clone()); + let keystore = Keystore::to_keystore(&keypair, password.clone(), None, None); let json_str = serde_json::to_string(&keystore).unwrap(); let recovered_keystore: Keystore = serde_json::from_str(&json_str).unwrap(); From 3fe10a7d8e8befe3c01efd53ae583a1e7d868993 Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 6 Nov 2019 11:25:03 +0530 Subject: [PATCH 12/25] Add `path` field to keystore --- account_manager/src/keystore/mod.rs | 8 ++++++++ account_manager/src/main.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index c8b6ecb473b..574d7245efd 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -7,6 +7,8 @@ use crate::keystore::crypto::Crypto; use crate::keystore::kdf::Kdf; use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::path::PathBuf; use uuid::Uuid; /// Version for `Keystore`. @@ -22,10 +24,14 @@ impl Default for Version { } } +/// TODO: Implement `path` according to +/// https://github.com/ethereum/EIPs/blob/de52c7ef2e44f2ab95d6aa4b90245c3c969aaf9f/EIPS/eip-2334.md +/// For now, `path` is set to en empty string. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { crypto: Crypto, uuid: Uuid, + path: String, version: Version, } @@ -46,9 +52,11 @@ impl Keystore { ); let uuid = Uuid::new_v4(); let version = Version::default(); + let path = "".to_string(); Keystore { crypto, uuid, + path, version, } } diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 1a181202912..b3e2f297ee0 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -7,7 +7,7 @@ use types::test_utils::generate_deterministic_keypair; use types::DepositData; use validator_client::Config as ValidatorClientConfig; mod deposit; -mod keystore; +pub mod keystore; pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml"; From 3238325879e34bbec91ce7c092e47e07e8f5a9b3 Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 6 Nov 2019 11:25:45 +0530 Subject: [PATCH 13/25] Add function to read Keystore from file --- account_manager/src/keystore/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index 574d7245efd..fa888d3ed45 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -61,12 +61,26 @@ impl Keystore { } } + /// Regenerate a BLS12-381 `Keypair` given path to the keystore file and + /// the correct password. + /// + /// An error is returned if the secret in the file does not contain a valid + /// `Keystore` or if the secret contained is not a + /// BLS12-381 secret key or if the password provided is incorrect. + pub fn read_keystore_file(keystore_path: PathBuf, password: String) -> Result { + let mut key_file = File::open(keystore_path.clone()) + .map_err(|e| format!("Unable to open keystore file: {}", e))?; + let keystore: Keystore = serde_json::from_reader(&mut key_file) + .map_err(|e| format!("Invalid keystore format: {:?}", e))?; + keystore.from_keystore(password) + } + /// Regenerate a BLS12-381 `Keypair` given the `Keystore` object and /// the correct password. /// /// An error is returned if the secret in the `Keystore` is not a valid /// BLS12-381 secret key or if the password provided is incorrect. - pub fn from_keystore(&self, password: String) -> Result { + fn from_keystore(&self, password: String) -> Result { let sk = SecretKey::from_bytes(&self.crypto.decrypt(password)?) .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; let pk = PublicKey::from_secret_key(&sk); From b78c9fdd7e3bc5d4cc53358a9048145e7fe68bae Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 6 Nov 2019 13:15:39 +0530 Subject: [PATCH 14/25] Add test vectors and fix Version serialization --- account_manager/Cargo.toml | 1 + account_manager/src/keystore/mod.rs | 104 ++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 1e9613645a3..b6452f170f4 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -32,6 +32,7 @@ rust-crypto = "0.2.36" hex = "0.4.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_repr = "0.1" uuid = { version = "0.8", features = ["serde", "v4"] } diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index fa888d3ed45..317c174e0aa 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -7,15 +7,16 @@ use crate::keystore::crypto::Crypto; use crate::keystore::kdf::Kdf; use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; +use serde_repr::*; use std::fs::File; use std::path::PathBuf; use uuid::Uuid; /// Version for `Keystore`. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] +#[repr(u8)] pub enum Version { - #[serde(rename = "4")] - V4, + V4 = 4, } impl Default for Version { @@ -72,35 +73,98 @@ impl Keystore { .map_err(|e| format!("Unable to open keystore file: {}", e))?; let keystore: Keystore = serde_json::from_reader(&mut key_file) .map_err(|e| format!("Invalid keystore format: {:?}", e))?; - keystore.from_keystore(password) + let sk = SecretKey::from_bytes(&keystore.from_keystore(password)?) + .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; + let pk = PublicKey::from_secret_key(&sk); + Ok(Keypair { sk, pk }) } - /// Regenerate a BLS12-381 `Keypair` given the `Keystore` object and + /// Regenerate keystore secret from given the `Keystore` object and /// the correct password. /// - /// An error is returned if the secret in the `Keystore` is not a valid - /// BLS12-381 secret key or if the password provided is incorrect. - fn from_keystore(&self, password: String) -> Result { - let sk = SecretKey::from_bytes(&self.crypto.decrypt(password)?) - .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; - let pk = PublicKey::from_secret_key(&sk); - Ok(Keypair { sk, pk }) + /// An error is returned if the password provided is incorrect or if + /// keystore does not contain valid hex strings. + fn from_keystore(&self, password: String) -> Result, String> { + Ok(self.crypto.decrypt(password)?) } } #[cfg(test)] mod tests { use super::*; - use bls::Keypair; #[test] - fn test_keystore() { - let keypair = Keypair::random(); + fn test_vectors() { + let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; let password = "testpassword".to_string(); - let keystore = Keystore::to_keystore(&keypair, password.clone(), None, None); + let scrypt_test_vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; - let json_str = serde_json::to_string(&keystore).unwrap(); - let recovered_keystore: Keystore = serde_json::from_str(&json_str).unwrap(); - let recovered_keypair = recovered_keystore.from_keystore(password).unwrap(); - assert_eq!(keypair, recovered_keypair); + let pbkdf2_test_vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + let test_vectors = vec![scrypt_test_vector, pbkdf2_test_vector]; + for test in test_vectors { + let keystore: Keystore = serde_json::from_str(test).unwrap(); + let secret = keystore.from_keystore(password.clone()).unwrap(); + assert_eq!(secret, hex::decode(expected_secret).unwrap()) + } } } From 7ebe307df62b06105aab161a0180e1205b3e955e Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 7 Nov 2019 16:39:42 +0530 Subject: [PATCH 15/25] Checksum params is empty object --- account_manager/src/keystore/checksum.rs | 3 +-- account_manager/src/keystore/crypto.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/account_manager/src/keystore/checksum.rs b/account_manager/src/keystore/checksum.rs index 4edcf191d47..805c6681629 100644 --- a/account_manager/src/keystore/checksum.rs +++ b/account_manager/src/keystore/checksum.rs @@ -1,13 +1,12 @@ use crypto::digest::Digest; use crypto::sha2::Sha256; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; /// Checksum module for `Keystore`. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ChecksumModule { pub function: String, - pub params: BTreeMap<(), ()>, // TODO: need a better way to encode empty json object + pub params: serde_json::Value, // Empty json object pub message: String, } diff --git a/account_manager/src/keystore/crypto.rs b/account_manager/src/keystore/crypto.rs index 17cbdc06e59..1268a99a5b5 100644 --- a/account_manager/src/keystore/crypto.rs +++ b/account_manager/src/keystore/crypto.rs @@ -2,7 +2,6 @@ use crate::keystore::checksum::{Checksum, ChecksumModule}; use crate::keystore::cipher::{Cipher, CipherModule}; use crate::keystore::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; /// Crypto module for keystore. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -37,7 +36,7 @@ impl Crypto { }, checksum: ChecksumModule { function: Checksum::function(), - params: BTreeMap::new(), + params: serde_json::Value::Object(serde_json::Map::default()), message: checksum, }, cipher: CipherModule { From 9bb864746fc6c2da96c4272bb5d0d8c8e7e1d943 Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 7 Nov 2019 17:27:39 +0530 Subject: [PATCH 16/25] Add public key to Keystore --- account_manager/src/keystore/mod.rs | 44 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index 317c174e0aa..941b5550059 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -12,6 +12,8 @@ use std::fs::File; use std::path::PathBuf; use uuid::Uuid; +pub const PRIVATE_KEY_BYTES: usize = 48; + /// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] #[repr(u8)] @@ -26,13 +28,14 @@ impl Default for Version { } /// TODO: Implement `path` according to -/// https://github.com/ethereum/EIPs/blob/de52c7ef2e44f2ab95d6aa4b90245c3c969aaf9f/EIPS/eip-2334.md +/// https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md /// For now, `path` is set to en empty string. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { crypto: Crypto, uuid: Uuid, path: String, + pubkey: String, version: Version, } @@ -58,6 +61,7 @@ impl Keystore { crypto, uuid, path, + pubkey: keypair.pk.as_hex_string()[2..].to_string(), version, } } @@ -73,22 +77,37 @@ impl Keystore { .map_err(|e| format!("Unable to open keystore file: {}", e))?; let keystore: Keystore = serde_json::from_reader(&mut key_file) .map_err(|e| format!("Invalid keystore format: {:?}", e))?; - let sk = SecretKey::from_bytes(&keystore.from_keystore(password)?) - .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; - let pk = PublicKey::from_secret_key(&sk); - Ok(Keypair { sk, pk }) + return keystore.from_keystore(password); } - /// Regenerate keystore secret from given the `Keystore` object and + /// Regenerate a BLS12-381 `Keypair` from given the `Keystore` object and /// the correct password. /// /// An error is returned if the password provided is incorrect or if - /// keystore does not contain valid hex strings. - fn from_keystore(&self, password: String) -> Result, String> { - Ok(self.crypto.decrypt(password)?) + /// keystore does not contain valid hex strings or if the secret contained is not a + /// BLS12-381 secret key. + fn from_keystore(&self, password: String) -> Result { + let sk_bytes = self.crypto.decrypt(password)?; + if sk_bytes.len() != 32 { + return Err(format!("Invalid secret key size: {:?}", sk_bytes)); + } + let padded_sk_bytes = pad_secret_key(&sk_bytes); + let sk = SecretKey::from_bytes(&padded_sk_bytes) + .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; + let pk = PublicKey::from_secret_key(&sk); + debug_assert_eq!(pk.as_hex_string()[2..].to_string(), self.pubkey); + Ok(Keypair { sk, pk }) } } +/// Pad 0's to a 32 bytes BLS secret key to make it compatible with the Milagro library +/// Note: Milagro library only accepts 48 byte bls12 381 private keys. +fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] { + let mut bytes = [0; PRIVATE_KEY_BYTES]; + bytes[PRIVATE_KEY_BYTES - sk.len()..].copy_from_slice(sk); + bytes +} + #[cfg(test)] mod tests { use super::*; @@ -123,6 +142,7 @@ mod tests { "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" } }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", "path": "", "version": 4 @@ -155,6 +175,7 @@ mod tests { "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" } }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", "path": "m/12381/60/0/0", "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", "version": 4 @@ -163,8 +184,9 @@ mod tests { let test_vectors = vec![scrypt_test_vector, pbkdf2_test_vector]; for test in test_vectors { let keystore: Keystore = serde_json::from_str(test).unwrap(); - let secret = keystore.from_keystore(password.clone()).unwrap(); - assert_eq!(secret, hex::decode(expected_secret).unwrap()) + let keypair = keystore.from_keystore(password.clone()).unwrap(); + let expected_sk = pad_secret_key(&hex::decode(expected_secret).unwrap()); + assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk.to_vec()) } } } From f8e7f57cc52dbe6ac9597f401482346a3681ac86 Mon Sep 17 00:00:00 2001 From: pawan Date: Fri, 8 Nov 2019 15:19:40 +0530 Subject: [PATCH 17/25] Add function for saving keystore into file --- account_manager/src/keystore/mod.rs | 58 ++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/account_manager/src/keystore/mod.rs b/account_manager/src/keystore/mod.rs index 941b5550059..ec28603861b 100644 --- a/account_manager/src/keystore/mod.rs +++ b/account_manager/src/keystore/mod.rs @@ -10,9 +10,11 @@ use serde::{Deserialize, Serialize}; use serde_repr::*; use std::fs::File; use std::path::PathBuf; +use time; use uuid::Uuid; -pub const PRIVATE_KEY_BYTES: usize = 48; +const PRIVATE_KEY_BYTES: usize = 48; +const VALIDATOR_FOLDER_NAME: &str = "validators"; /// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] @@ -32,21 +34,22 @@ impl Default for Version { /// For now, `path` is set to en empty string. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { - crypto: Crypto, - uuid: Uuid, - path: String, - pubkey: String, - version: Version, + pub crypto: Crypto, + pub uuid: Uuid, + pub path: String, + pub pubkey: String, + pub version: Version, } impl Keystore { /// Generate `Keystore` object for a BLS12-381 secret key from a - /// keypair and password. Optionally, provide params for kdf and cipher. + /// keypair and password. Optionally, provide params for kdf, cipher and a uuid. pub fn to_keystore( keypair: &Keypair, password: String, kdf: Option, cipher: Option, + uuid: Option, ) -> Self { let crypto = Crypto::encrypt( password, @@ -54,9 +57,9 @@ impl Keystore { kdf.unwrap_or_default(), cipher.unwrap_or_default(), ); - let uuid = Uuid::new_v4(); + let uuid = uuid.unwrap_or(Uuid::new_v4()); let version = Version::default(); - let path = "".to_string(); + let path = String::new(); Keystore { crypto, uuid, @@ -98,6 +101,30 @@ impl Keystore { debug_assert_eq!(pk.as_hex_string()[2..].to_string(), self.pubkey); Ok(Keypair { sk, pk }) } + + /// Save keystore into appropriate file and directory. + /// Return the path of the keystore file. + pub fn save_keystore(&self, base_path: PathBuf, key_type: KeyType) -> Result { + let validator_path = base_path.join(VALIDATOR_FOLDER_NAME); + validator_path.join(self.uuid.to_string()); + + let mut file_name = match key_type { + KeyType::Voting => "voting-".to_string(), + KeyType::Withdrawal => "withdrawal-".to_string(), + }; + file_name.push_str(&get_utc_time()); + file_name.push_str(&self.uuid.to_string()); + + let keystore_path = validator_path.join(file_name); + std::fs::create_dir_all(&validator_path) + .map_err(|e| format!("Cannot create directory: {}", e))?; + + let keystore_file = File::create(&keystore_path) + .map_err(|e| format!("Cannot create keystore file: {}", e))?; + serde_json::to_writer_pretty(keystore_file, &self) + .map_err(|e| format!("Error writing keystore into file: {}", e))?; + Ok(keystore_path) + } } /// Pad 0's to a 32 bytes BLS secret key to make it compatible with the Milagro library @@ -108,6 +135,19 @@ fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] { bytes } +/// Return UTC time. +fn get_utc_time() -> String { + let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()) + .expect("Time-format string is valid."); + format!("UTC--{}--", timestamp) +} + +#[derive(PartialEq)] +pub enum KeyType { + Voting, + Withdrawal, +} + #[cfg(test)] mod tests { use super::*; From 2777b495e7039d8cbd2fe6f4e1617b4cecddd59e Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 8 Jan 2020 00:41:04 +0530 Subject: [PATCH 18/25] Deleted account_manager main.rs --- account_manager/src/main.rs | 249 ------------------------------------ 1 file changed, 249 deletions(-) delete mode 100644 account_manager/src/main.rs diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs deleted file mode 100644 index b3e2f297ee0..00000000000 --- a/account_manager/src/main.rs +++ /dev/null @@ -1,249 +0,0 @@ -use bls::Keypair; -use clap::{App, Arg, SubCommand}; -use slog::{crit, debug, info, o, Drain}; -use std::fs; -use std::path::PathBuf; -use types::test_utils::generate_deterministic_keypair; -use types::DepositData; -use validator_client::Config as ValidatorClientConfig; -mod deposit; -pub mod keystore; -pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; -pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml"; - -fn main() { - // Logging - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::CompactFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - let mut log = slog::Logger::root(drain, o!()); - - // CLI - let matches = App::new("Lighthouse Accounts Manager") - .version("0.0.1") - .author("Sigma Prime ") - .about("Eth 2.0 Accounts Manager") - .arg( - Arg::with_name("logfile") - .long("logfile") - .value_name("logfile") - .help("File path where output will be written.") - .takes_value(true), - ) - .arg( - Arg::with_name("datadir") - .long("datadir") - .short("d") - .value_name("DIR") - .help("Data directory for keys and databases.") - .takes_value(true), - ) - .subcommand( - SubCommand::with_name("generate") - .about("Generates a new validator private key") - .version("0.0.1") - .author("Sigma Prime "), - ) - .subcommand( - SubCommand::with_name("generate_deterministic") - .about("Generates a deterministic validator private key FOR TESTING") - .version("0.0.1") - .author("Sigma Prime ") - .arg( - Arg::with_name("validator index") - .long("index") - .short("i") - .value_name("index") - .help("The index of the validator, for which the test key is generated") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("validator count") - .long("validator_count") - .short("n") - .value_name("validator_count") - .help("If supplied along with `index`, generates keys `i..i + n`.") - .takes_value(true) - .default_value("1"), - ), - ) - .subcommand( - SubCommand::with_name("generate_deposit_params") - .about("Generates deposit parameters for submitting to the deposit contract") - .version("0.0.1") - .author("Sigma Prime ") - .arg( - Arg::with_name("validator_sk_path") - .long("validator_sk_path") - .short("v") - .value_name("validator_sk_path") - .help("Path to the validator private key directory") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("withdrawal_sk_path") - .long("withdrawal_sk_path") - .short("w") - .value_name("withdrawal_sk_path") - .help("Path to the withdrawal private key directory") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("deposit_amount") - .long("deposit_amount") - .short("a") - .value_name("deposit_amount") - .help("Deposit amount in GWEI") - .takes_value(true) - .required(true), - ), - ) - .get_matches(); - - let data_dir = match matches - .value_of("datadir") - .and_then(|v| Some(PathBuf::from(v))) - { - Some(v) => v, - None => { - // use the default - let mut default_dir = match dirs::home_dir() { - Some(v) => v, - None => { - crit!(log, "Failed to find a home directory"); - return; - } - }; - default_dir.push(DEFAULT_DATA_DIR); - default_dir - } - }; - - // create the directory if needed - match fs::create_dir_all(&data_dir) { - Ok(_) => {} - Err(e) => { - crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e)); - return; - } - } - - let mut client_config = ValidatorClientConfig::default(); - - // Ensure the `data_dir` in the config matches that supplied to the CLI. - client_config.data_dir = data_dir.clone(); - - if let Err(e) = client_config.apply_cli_args(&matches, &mut log) { - crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => format!("{:?}", e)); - return; - }; - - // Log configuration - info!(log, ""; - "data_dir" => &client_config.data_dir.to_str()); - - match matches.subcommand() { - ("generate", Some(_)) => generate_random(&client_config, &log), - ("generate_deterministic", Some(m)) => { - if let Some(string) = m.value_of("validator index") { - let i: usize = string.parse().expect("Invalid validator index"); - if let Some(string) = m.value_of("validator count") { - let n: usize = string.parse().expect("Invalid end validator count"); - - let indices: Vec = (i..i + n).collect(); - generate_deterministic_multiple(&indices, &client_config, &log) - } else { - generate_deterministic(i, &client_config, &log) - } - } - } - ("generate_deposit_params", Some(m)) => { - let validator_kp_path = m - .value_of("validator_sk_path") - .expect("generating deposit params requires validator key path") - .parse::() - .expect("Must be a valid path"); - let withdrawal_kp_path = m - .value_of("withdrawal_sk_path") - .expect("generating deposit params requires withdrawal key path") - .parse::() - .expect("Must be a valid path"); - let amount = m - .value_of("deposit_amount") - .expect("generating deposit params requires deposit amount") - .parse::() - .expect("Must be a valid u64"); - let deposit = generate_deposit_params( - validator_kp_path, - withdrawal_kp_path, - amount, - &client_config, - &log, - ); - // TODO: just printing for now. Decide how to process - println!("Deposit data: {:?}", deposit); - } - _ => { - crit!( - log, - "The account manager must be run with a subcommand. See help for more information." - ); - } - } -} - -fn generate_random(config: &ValidatorClientConfig, log: &slog::Logger) { - save_key(&Keypair::random(), config, log) -} - -fn generate_deterministic_multiple( - validator_indices: &[usize], - config: &ValidatorClientConfig, - log: &slog::Logger, -) { - for validator_index in validator_indices { - generate_deterministic(*validator_index, config, log) - } -} - -fn generate_deterministic( - validator_index: usize, - config: &ValidatorClientConfig, - log: &slog::Logger, -) { - save_key( - &generate_deterministic_keypair(validator_index), - config, - log, - ) -} - -fn save_key(keypair: &Keypair, config: &ValidatorClientConfig, log: &slog::Logger) { - let key_path: PathBuf = config - .save_key(&keypair) - .expect("Unable to save newly generated private key."); - debug!( - log, - "Keypair generated {:?}, saved to: {:?}", - keypair.identifier(), - key_path.to_string_lossy() - ); -} - -fn generate_deposit_params( - vk_path: PathBuf, - wk_path: PathBuf, - amount: u64, - config: &ValidatorClientConfig, - log: &slog::Logger, -) -> DepositData { - let vk: Keypair = config.read_keypair_file(vk_path).unwrap(); - let wk: Keypair = config.read_keypair_file(wk_path).unwrap(); - - let spec = types::ChainSpec::default(); - debug!(log, "Generating deposit parameters"); - deposit::generate_deposit_params(vk, &wk, amount, &spec) -} From ae631d4d41bfa515761525e1b2ad27bf5bf2f253 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 26 Nov 2019 18:56:20 +0530 Subject: [PATCH 19/25] Move keystore module to validator_client --- Cargo.lock | 5 +++ validator_client/Cargo.toml | 5 +++ .../src/keystore/checksum.rs | 0 .../src/keystore/cipher.rs | 0 .../src/keystore/crypto.rs | 0 .../src/keystore/kdf.rs | 0 .../src/keystore/mod.rs | 39 ------------------- validator_client/src/lib.rs | 1 + 8 files changed, 11 insertions(+), 39 deletions(-) rename {account_manager => validator_client}/src/keystore/checksum.rs (100%) rename {account_manager => validator_client}/src/keystore/cipher.rs (100%) rename {account_manager => validator_client}/src/keystore/crypto.rs (100%) rename {account_manager => validator_client}/src/keystore/kdf.rs (100%) rename {account_manager => validator_client}/src/keystore/mod.rs (83%) diff --git a/Cargo.lock b/Cargo.lock index 080508580f6..dc3cd9d5899 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4674,20 +4674,25 @@ dependencies = [ "lighthouse_bootstrap 0.1.0", "logging 0.1.0", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "remote_beacon_node 0.1.0", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "slog-term 2.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "slot_clock 0.1.0", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "tree_hash 0.1.1", "types 0.1.0", + "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index b38bfff39d4..3a550bc65d8 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -20,6 +20,7 @@ types = { path = "../eth2/types" } serde = "1.0.102" serde_derive = "1.0.102" serde_json = "1.0.41" +serde_repr = "0.1" slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] } slog-async = "2.3.0" slog-term = "2.4.2" @@ -41,3 +42,7 @@ bls = { path = "../eth2/utils/bls" } remote_beacon_node = { path = "../eth2/utils/remote_beacon_node" } tempdir = "0.3" rayon = "1.2.0" +rand = "0.7.2" +rust-crypto = "0.2.36" +uuid = { version = "0.8", features = ["serde", "v4"] } +time = "0.1.42" \ No newline at end of file diff --git a/account_manager/src/keystore/checksum.rs b/validator_client/src/keystore/checksum.rs similarity index 100% rename from account_manager/src/keystore/checksum.rs rename to validator_client/src/keystore/checksum.rs diff --git a/account_manager/src/keystore/cipher.rs b/validator_client/src/keystore/cipher.rs similarity index 100% rename from account_manager/src/keystore/cipher.rs rename to validator_client/src/keystore/cipher.rs diff --git a/account_manager/src/keystore/crypto.rs b/validator_client/src/keystore/crypto.rs similarity index 100% rename from account_manager/src/keystore/crypto.rs rename to validator_client/src/keystore/crypto.rs diff --git a/account_manager/src/keystore/kdf.rs b/validator_client/src/keystore/kdf.rs similarity index 100% rename from account_manager/src/keystore/kdf.rs rename to validator_client/src/keystore/kdf.rs diff --git a/account_manager/src/keystore/mod.rs b/validator_client/src/keystore/mod.rs similarity index 83% rename from account_manager/src/keystore/mod.rs rename to validator_client/src/keystore/mod.rs index ec28603861b..8b0167b848c 100644 --- a/account_manager/src/keystore/mod.rs +++ b/validator_client/src/keystore/mod.rs @@ -10,11 +10,9 @@ use serde::{Deserialize, Serialize}; use serde_repr::*; use std::fs::File; use std::path::PathBuf; -use time; use uuid::Uuid; const PRIVATE_KEY_BYTES: usize = 48; -const VALIDATOR_FOLDER_NAME: &str = "validators"; /// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] @@ -101,30 +99,6 @@ impl Keystore { debug_assert_eq!(pk.as_hex_string()[2..].to_string(), self.pubkey); Ok(Keypair { sk, pk }) } - - /// Save keystore into appropriate file and directory. - /// Return the path of the keystore file. - pub fn save_keystore(&self, base_path: PathBuf, key_type: KeyType) -> Result { - let validator_path = base_path.join(VALIDATOR_FOLDER_NAME); - validator_path.join(self.uuid.to_string()); - - let mut file_name = match key_type { - KeyType::Voting => "voting-".to_string(), - KeyType::Withdrawal => "withdrawal-".to_string(), - }; - file_name.push_str(&get_utc_time()); - file_name.push_str(&self.uuid.to_string()); - - let keystore_path = validator_path.join(file_name); - std::fs::create_dir_all(&validator_path) - .map_err(|e| format!("Cannot create directory: {}", e))?; - - let keystore_file = File::create(&keystore_path) - .map_err(|e| format!("Cannot create keystore file: {}", e))?; - serde_json::to_writer_pretty(keystore_file, &self) - .map_err(|e| format!("Error writing keystore into file: {}", e))?; - Ok(keystore_path) - } } /// Pad 0's to a 32 bytes BLS secret key to make it compatible with the Milagro library @@ -135,19 +109,6 @@ fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] { bytes } -/// Return UTC time. -fn get_utc_time() -> String { - let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()) - .expect("Time-format string is valid."); - format!("UTC--{}--", timestamp) -} - -#[derive(PartialEq)] -pub enum KeyType { - Voting, - Withdrawal, -} - #[cfg(test)] mod tests { use super::*; diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index a9660ef8529..b40b679e151 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -4,6 +4,7 @@ mod cli; mod config; mod duties_service; mod fork_service; +mod keystore; mod notifier; mod validator_store; From 9c366eb17ba9032ff39eb6088ce7fe0c8ae74570 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 26 Nov 2019 18:57:25 +0530 Subject: [PATCH 20/25] Add save_keystore method to validator_directory --- validator_client/src/validator_directory.rs | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index 15d2edd9a24..a9965a323aa 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -1,3 +1,4 @@ +use crate::keystore::Keystore; use bls::get_withdrawal_credentials; use deposit_contract::eth1_tx_data; use hex; @@ -12,6 +13,7 @@ use types::{ test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair, PublicKey, SecretKey, Signature, }; +use uuid::Uuid; const VOTING_KEY_PREFIX: &str = "voting"; const WITHDRAWAL_KEY_PREFIX: &str = "withdrawal"; @@ -22,11 +24,33 @@ fn keypair_file(prefix: &str) -> String { format!("{}_keypair", prefix) } +/// Returns the filename of a keystore file. +fn keystore_file(keystore: &Keystore, prefix: &str) -> String { + format!( + "{}-{}-{}", + prefix, + &get_utc_time(), + keystore.uuid.to_string() + ) +} + /// Returns the name of the folder to be generated for a validator with the given voting key. fn dir_name(voting_pubkey: &PublicKey) -> String { format!("0x{}", hex::encode(voting_pubkey.as_ssz_bytes())) } +/// Returns the name of folder to be generated for a keystore with a given uuid. +fn dir_name_keystore(uuid: &Uuid) -> String { + uuid.to_string() +} + +/// Return UTC time. +fn get_utc_time() -> String { + let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()) + .expect("Time-format string is valid."); + format!("UTC--{}--", timestamp) +} + /// Represents the files/objects for each dedicated lighthouse validator directory. /// /// Generally lives in `~/.lighthouse/validators/`. @@ -198,6 +222,22 @@ impl ValidatorDirectoryBuilder { Ok(self) } + pub fn create_keystore_directory( + keystore: &Keystore, + base_path: PathBuf, + ) -> Result<(), String> { + let directory = base_path.join(dir_name_keystore(&keystore.uuid)); + if directory.exists() { + return Err(format!( + "Validator keystore directory already exists: {:?}", + directory + )); + } + fs::create_dir_all(&directory) + .map_err(|e| format!("Unable to create keystore validator directory: {}", e))?; + Ok(()) + } + pub fn write_keypair_files(self) -> Result { let voting_keypair = self .voting_keypair @@ -241,6 +281,35 @@ impl ValidatorDirectoryBuilder { Ok(()) } + pub fn save_keystore( + &self, + base_path: PathBuf, + keystore: &Keystore, + file_prefix: &str, + ) -> Result<(), String> { + let directory = base_path.join(dir_name_keystore(&keystore.uuid)); + let path = directory.join(keystore_file(&keystore, file_prefix)); + + if path.exists() { + return Err(format!("Keystore file already exists at: {:?}", path)); + } + + let file = File::create(&path).map_err(|e| format!("Unable to create file: {}", e))?; + + // Ensure file has correct permissions. + let mut perm = file + .metadata() + .map_err(|e| format!("Unable to get file metadata: {}", e))? + .permissions(); + perm.set_mode((libc::S_IWUSR | libc::S_IRUSR) as u32); + file.set_permissions(perm) + .map_err(|e| format!("Unable to set file permissions: {}", e))?; + + serde_json::to_writer_pretty(file, &keystore) + .map_err(|e| format!("Error writing keystore into file: {}", e))?; + Ok(()) + } + pub fn write_eth1_data_file(mut self) -> Result { let voting_keypair = self .voting_keypair From 4e1a0d0474ec924b828b199a6a9faa0bf5f1e49c Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 26 Nov 2019 19:21:55 +0530 Subject: [PATCH 21/25] Add load_keystore function. Minor refactorings --- validator_client/src/keystore/mod.rs | 26 +++++---------------- validator_client/src/validator_directory.rs | 13 +++++++++++ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/validator_client/src/keystore/mod.rs b/validator_client/src/keystore/mod.rs index 8b0167b848c..b3fec45cc21 100644 --- a/validator_client/src/keystore/mod.rs +++ b/validator_client/src/keystore/mod.rs @@ -8,8 +8,6 @@ use crate::keystore::kdf::Kdf; use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use serde_repr::*; -use std::fs::File; -use std::path::PathBuf; use uuid::Uuid; const PRIVATE_KEY_BYTES: usize = 48; @@ -42,7 +40,7 @@ pub struct Keystore { impl Keystore { /// Generate `Keystore` object for a BLS12-381 secret key from a /// keypair and password. Optionally, provide params for kdf, cipher and a uuid. - pub fn to_keystore( + pub fn new( keypair: &Keypair, password: String, kdf: Option, @@ -67,27 +65,13 @@ impl Keystore { } } - /// Regenerate a BLS12-381 `Keypair` given path to the keystore file and - /// the correct password. - /// - /// An error is returned if the secret in the file does not contain a valid - /// `Keystore` or if the secret contained is not a - /// BLS12-381 secret key or if the password provided is incorrect. - pub fn read_keystore_file(keystore_path: PathBuf, password: String) -> Result { - let mut key_file = File::open(keystore_path.clone()) - .map_err(|e| format!("Unable to open keystore file: {}", e))?; - let keystore: Keystore = serde_json::from_reader(&mut key_file) - .map_err(|e| format!("Invalid keystore format: {:?}", e))?; - return keystore.from_keystore(password); - } - /// Regenerate a BLS12-381 `Keypair` from given the `Keystore` object and /// the correct password. /// /// An error is returned if the password provided is incorrect or if /// keystore does not contain valid hex strings or if the secret contained is not a /// BLS12-381 secret key. - fn from_keystore(&self, password: String) -> Result { + fn to_keypair(&self, password: String) -> Result { let sk_bytes = self.crypto.decrypt(password)?; if sk_bytes.len() != 32 { return Err(format!("Invalid secret key size: {:?}", sk_bytes)); @@ -96,7 +80,9 @@ impl Keystore { let sk = SecretKey::from_bytes(&padded_sk_bytes) .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; let pk = PublicKey::from_secret_key(&sk); - debug_assert_eq!(pk.as_hex_string()[2..].to_string(), self.pubkey); + if pk.as_hex_string()[2..].to_string() != self.pubkey { + return Err(format!("Decoded pubkey doesn't match keystore pubkey")); + } Ok(Keypair { sk, pk }) } } @@ -185,7 +171,7 @@ mod tests { let test_vectors = vec![scrypt_test_vector, pbkdf2_test_vector]; for test in test_vectors { let keystore: Keystore = serde_json::from_str(test).unwrap(); - let keypair = keystore.from_keystore(password.clone()).unwrap(); + let keypair = keystore.to_keypair(password.clone()).unwrap(); let expected_sk = pad_secret_key(&hex::decode(expected_secret).unwrap()); assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk.to_vec()) } diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index a9965a323aa..83003793141 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -105,6 +105,19 @@ fn load_keypair(base_path: PathBuf, file_prefix: &str) -> Result Result { + if !path.exists() { + return Err(format!("Keypair file does not exist: {:?}", path)); + } + + let mut key_file = + File::open(path.clone()).map_err(|e| format!("Unable to open keystore file: {}", e))?; + let keystore: Keystore = serde_json::from_reader(&mut key_file) + .map_err(|e| format!("Invalid keystore format: {:?}", e))?; + Ok(keystore) +} + /// Load eth1_deposit_data from file. fn load_eth1_deposit_data(base_path: PathBuf) -> Result, String> { let path = base_path.join(ETH1_DEPOSIT_DATA_FILE); From a56e5a3a24485829e1b1442df87aae941debace3 Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 8 Jan 2020 00:52:19 +0530 Subject: [PATCH 22/25] Fixed dependencies --- Cargo.lock | 29 +++++++++++++++++++++++++++++ account_manager/Cargo.toml | 12 +----------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc3cd9d5899..f88b86cb3e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3476,6 +3476,18 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -3486,6 +3498,11 @@ name = "rustc-hex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_version" version = "0.2.3" @@ -4652,6 +4669,15 @@ dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "validator_client" version = "0.1.0" @@ -5420,8 +5446,10 @@ dependencies = [ "checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" "checksum rlp 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3a44d5ae8afcb238af8b75640907edc6c931efcfab2c854e81ed35fa080f84cd" "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" +"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "403bb3a286107a04825a5f82e1270acc1e14028d3d554d7a1e08914549575ab8" +"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" "checksum rw-stream-sink 0.1.2 (git+https://github.com/SigP/rust-libp2p/?rev=c0c71fa4d7e6621cafe2c087d1aa1bc60dd9116e)" = "" @@ -5535,6 +5563,7 @@ dependencies = [ "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index b6452f170f4..ad91ad4efe2 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" [dev-dependencies] tempdir = "0.3" -tree_hash = "0.1" [dependencies] bls = { path = "../eth2/utils/bls" } @@ -21,18 +20,9 @@ deposit_contract = { path = "../eth2/utils/deposit_contract" } libc = "0.2.65" eth2_ssz = { path = "../eth2/utils/ssz" } eth2_ssz_derive = { path = "../eth2/utils/ssz_derive" } +hex = "0.3" validator_client = { path = "../validator_client" } rayon = "1.2.0" eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" } web3 = "0.8.0" futures = "0.1.25" -parity-crypto = "0.4.2" -rand = "0.7.2" -rust-crypto = "0.2.36" -hex = "0.4.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_repr = "0.1" -uuid = { version = "0.8", features = ["serde", "v4"] } - - From 3a93b983ac3d93b5462cd30ec02770b7b6ad8f79 Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 5 Mar 2020 10:28:50 +0530 Subject: [PATCH 23/25] Address some review comments --- Cargo.lock | 1 + validator_client/Cargo.toml | 3 ++- validator_client/src/keystore/cipher.rs | 12 ++++++++---- validator_client/src/keystore/crypto.rs | 9 ++++----- validator_client/src/keystore/kdf.rs | 8 ++++---- validator_client/src/keystore/mod.rs | 1 + 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f88b86cb3e3..55e117b8122 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4719,6 +4719,7 @@ dependencies = [ "tree_hash 0.1.1", "types 0.1.0", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 3a550bc65d8..9ec575f0087 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -45,4 +45,5 @@ rayon = "1.2.0" rand = "0.7.2" rust-crypto = "0.2.36" uuid = { version = "0.8", features = ["serde", "v4"] } -time = "0.1.42" \ No newline at end of file +time = "0.1.42" +zeroize = "1.0" \ No newline at end of file diff --git a/validator_client/src/keystore/cipher.rs b/validator_client/src/keystore/cipher.rs index 40d03eb38f3..e95d1ee5e13 100644 --- a/validator_client/src/keystore/cipher.rs +++ b/validator_client/src/keystore/cipher.rs @@ -6,11 +6,15 @@ use std::default::Default; const IV_SIZE: usize = 16; /// Convert slice to fixed length array. -fn from_slice(bytes: &[u8]) -> [u8; IV_SIZE] { +/// Returns `None` if slice has len > `IV_SIZE` +fn from_slice(bytes: &[u8]) -> Option<[u8; IV_SIZE]> { + if bytes.len() != 16 { + return None; + } let mut array = [0; IV_SIZE]; - let bytes = &bytes[..array.len()]; // panics if not enough data + let bytes = &bytes[..array.len()]; array.copy_from_slice(bytes); - array + Some(array) } /// Cipher module representation. @@ -69,7 +73,7 @@ where E: de::Error, { let bytes = hex::decode(v).map_err(E::custom)?; - Ok(from_slice(&bytes)) + from_slice(&bytes).ok_or_else(|| E::custom(format!("IV should have length 16 bytes"))) } } deserializer.deserialize_any(StringVisitor) diff --git a/validator_client/src/keystore/crypto.rs b/validator_client/src/keystore/crypto.rs index 1268a99a5b5..a439ab3dbc9 100644 --- a/validator_client/src/keystore/crypto.rs +++ b/validator_client/src/keystore/crypto.rs @@ -52,7 +52,7 @@ impl Crypto { /// An error will be returned if `cipher.message` is not in hex format or /// if password is incorrect. pub fn decrypt(&self, password: String) -> Result, String> { - // Genrate derived key + // Generate derived key let derived_key = match &self.kdf.params { Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), Kdf::Scrypt(scrypt) => scrypt.derive_key(&password), @@ -80,6 +80,7 @@ impl Crypto { } } +// Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[cfg(test)] mod tests { use super::*; @@ -109,7 +110,7 @@ mod tests { dklen: 32, c: 262144, prf: Prf::HmacSha256, - salt: salt, + salt, }); let cipher = Cipher::Aes128Ctr(Aes128Ctr { @@ -122,7 +123,6 @@ mod tests { assert_eq!(expected_cipher, keystore.cipher.message); let json = serde_json::to_string(&keystore).unwrap(); - println!("{}", json); let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); @@ -147,7 +147,7 @@ mod tests { n: 262144, r: 8, p: 1, - salt: salt, + salt, }); let cipher = Cipher::Aes128Ctr(Aes128Ctr { @@ -160,7 +160,6 @@ mod tests { assert_eq!(expected_cipher, keystore.cipher.message); let json = serde_json::to_string(&keystore).unwrap(); - println!("{}", json); let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); diff --git a/validator_client/src/keystore/kdf.rs b/validator_client/src/keystore/kdf.rs index 3039b1b6527..930afb7c158 100644 --- a/validator_client/src/keystore/kdf.rs +++ b/validator_client/src/keystore/kdf.rs @@ -32,11 +32,11 @@ impl Default for Pbkdf2 { impl Pbkdf2 { /// Derive key from password. - pub fn derive_key(&self, password: &str) -> Vec { + pub fn derive_key(&self, password: &str) -> [u8; DECRYPTION_KEY_SIZE as usize] { let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; let mut mac = self.prf.mac(password.as_bytes()); pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, &mut dk); - dk.to_vec() + dk } } @@ -61,13 +61,13 @@ fn log2_int(x: u32) -> u32 { } impl Scrypt { - pub fn derive_key(&self, password: &str) -> Vec { + pub fn derive_key(&self, password: &str) -> [u8; DECRYPTION_KEY_SIZE as usize] { let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; // Assert that `n` is power of 2 debug_assert_eq!(self.n, 2u32.pow(log2_int(self.n))); let params = scrypt::ScryptParams::new(log2_int(self.n) as u8, self.r, self.p); scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk); - dk.to_vec() + dk } } diff --git a/validator_client/src/keystore/mod.rs b/validator_client/src/keystore/mod.rs index b3fec45cc21..24583cfadd2 100644 --- a/validator_client/src/keystore/mod.rs +++ b/validator_client/src/keystore/mod.rs @@ -95,6 +95,7 @@ fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] { bytes } +// Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[cfg(test)] mod tests { use super::*; From 1933b753b0e44e23954a0670557eaa819d6934f2 Mon Sep 17 00:00:00 2001 From: pawan Date: Fri, 6 Mar 2020 19:28:44 +0530 Subject: [PATCH 24/25] Add Password newtype; derive Zeroize --- Cargo.lock | 15 +++++++ validator_client/Cargo.toml | 2 +- validator_client/src/keystore/crypto.rs | 43 +++++++++++++++++---- validator_client/src/keystore/mod.rs | 31 +++++++++++---- validator_client/src/validator_directory.rs | 4 +- 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55e117b8122..20c47848b49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5136,6 +5136,9 @@ dependencies = [ name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "zeroize_derive" @@ -5158,6 +5161,17 @@ dependencies = [ "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" @@ -5608,3 +5622,4 @@ dependencies = [ "checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" "checksum zeroize_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3f07490820219949839d0027b965ffdd659d75be9220c00798762e36c6cd281" "checksum zeroize_derive 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "080616bd0e31f36095288bb0acdf1f78ef02c2fa15527d7e993f2a6c7591643e" +"checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 9ec575f0087..4962b4b9aab 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -46,4 +46,4 @@ rand = "0.7.2" rust-crypto = "0.2.36" uuid = { version = "0.8", features = ["serde", "v4"] } time = "0.1.42" -zeroize = "1.0" \ No newline at end of file +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } diff --git a/validator_client/src/keystore/crypto.rs b/validator_client/src/keystore/crypto.rs index a439ab3dbc9..30d8ba9e439 100644 --- a/validator_client/src/keystore/crypto.rs +++ b/validator_client/src/keystore/crypto.rs @@ -2,6 +2,35 @@ use crate::keystore::checksum::{Checksum, ChecksumModule}; use crate::keystore::cipher::{Cipher, CipherModule}; use crate::keystore::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; +use std::fmt; +use zeroize::Zeroize; + +#[derive(Zeroize, Clone, PartialEq)] +#[zeroize(drop)] +pub struct Password(String); + +impl fmt::Display for Password { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "******") + } +} +impl Password { + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl From for Password { + fn from(s: String) -> Password { + Password(s) + } +} + +impl<'a> From<&'a str> for Password { + fn from(s: &'a str) -> Password { + Password::from(String::from(s)) + } +} /// Crypto module for keystore. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -14,11 +43,11 @@ pub struct Crypto { impl Crypto { /// Generate crypto module for `Keystore` given the password, /// secret to encrypt, kdf params and cipher params. - pub fn encrypt(password: String, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self { + pub fn encrypt(password: Password, secret: &[u8], kdf: Kdf, cipher: Cipher) -> Self { // Generate derived key let derived_key = match &kdf { - Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), - Kdf::Scrypt(scrypt) => scrypt.derive_key(&password), + Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(password.as_str()), + Kdf::Scrypt(scrypt) => scrypt.derive_key(password.as_str()), }; // Encrypt secret let cipher_message = match &cipher { @@ -51,11 +80,11 @@ impl Crypto { /// /// An error will be returned if `cipher.message` is not in hex format or /// if password is incorrect. - pub fn decrypt(&self, password: String) -> Result, String> { + pub fn decrypt(&self, password: Password) -> Result, String> { // Generate derived key let derived_key = match &self.kdf.params { - Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(&password), - Kdf::Scrypt(scrypt) => scrypt.derive_key(&password), + Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(password.as_str()), + Kdf::Scrypt(scrypt) => scrypt.derive_key(password.as_str()), }; // Regenerate checksum let mut pre_image: Vec = derived_key[16..32].to_owned(); @@ -104,7 +133,7 @@ mod tests { let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); - let password = "testpassword".to_string(); + let password = Password("testpassword".to_string()); let kdf = Kdf::Pbkdf2(Pbkdf2 { dklen: 32, diff --git a/validator_client/src/keystore/mod.rs b/validator_client/src/keystore/mod.rs index 24583cfadd2..509ce66c4c0 100644 --- a/validator_client/src/keystore/mod.rs +++ b/validator_client/src/keystore/mod.rs @@ -9,9 +9,23 @@ use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use serde_repr::*; use uuid::Uuid; +use zeroize::Zeroize; + +pub use crate::keystore::crypto::Password; const PRIVATE_KEY_BYTES: usize = 48; +/// Wrapper over BLS secret key that is compatible with Milagro. +#[derive(Zeroize)] +#[zeroize(drop)] +struct MilagroSecretKey([u8; PRIVATE_KEY_BYTES]); + +impl MilagroSecretKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + /// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] #[repr(u8)] @@ -42,7 +56,7 @@ impl Keystore { /// keypair and password. Optionally, provide params for kdf, cipher and a uuid. pub fn new( keypair: &Keypair, - password: String, + password: Password, kdf: Option, cipher: Option, uuid: Option, @@ -71,13 +85,13 @@ impl Keystore { /// An error is returned if the password provided is incorrect or if /// keystore does not contain valid hex strings or if the secret contained is not a /// BLS12-381 secret key. - fn to_keypair(&self, password: String) -> Result { + pub fn to_keypair(&self, password: Password) -> Result { let sk_bytes = self.crypto.decrypt(password)?; if sk_bytes.len() != 32 { return Err(format!("Invalid secret key size: {:?}", sk_bytes)); } let padded_sk_bytes = pad_secret_key(&sk_bytes); - let sk = SecretKey::from_bytes(&padded_sk_bytes) + let sk = SecretKey::from_bytes(padded_sk_bytes.as_ref()) .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; let pk = PublicKey::from_secret_key(&sk); if pk.as_hex_string()[2..].to_string() != self.pubkey { @@ -89,10 +103,10 @@ impl Keystore { /// Pad 0's to a 32 bytes BLS secret key to make it compatible with the Milagro library /// Note: Milagro library only accepts 48 byte bls12 381 private keys. -fn pad_secret_key(sk: &[u8]) -> [u8; PRIVATE_KEY_BYTES] { +fn pad_secret_key(sk: &[u8]) -> MilagroSecretKey { let mut bytes = [0; PRIVATE_KEY_BYTES]; bytes[PRIVATE_KEY_BYTES - sk.len()..].copy_from_slice(sk); - bytes + MilagroSecretKey(bytes) } // Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases @@ -102,7 +116,7 @@ mod tests { #[test] fn test_vectors() { let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; - let password = "testpassword".to_string(); + let password: Password = "testpassword".into(); let scrypt_test_vector = r#" { "crypto": { @@ -174,7 +188,10 @@ mod tests { let keystore: Keystore = serde_json::from_str(test).unwrap(); let keypair = keystore.to_keypair(password.clone()).unwrap(); let expected_sk = pad_secret_key(&hex::decode(expected_secret).unwrap()); - assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk.to_vec()) + assert_eq!( + keypair.sk.as_raw().as_bytes(), + expected_sk.as_ref().to_vec() + ) } } } diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index 83003793141..4e5228db41c 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -1,4 +1,4 @@ -use crate::keystore::Keystore; +use crate::keystore::{Keystore, Password}; use bls::get_withdrawal_credentials; use deposit_contract::eth1_tx_data; use hex; @@ -106,7 +106,7 @@ fn load_keypair(base_path: PathBuf, file_prefix: &str) -> Result Result { +fn load_keystore(path: PathBuf) -> Result { if !path.exists() { return Err(format!("Keypair file does not exist: {:?}", path)); } From 7fbc971a6659d1598d30920f602e8697088bddea Mon Sep 17 00:00:00 2001 From: pawan Date: Fri, 6 Mar 2020 19:55:51 +0530 Subject: [PATCH 25/25] Fix test --- validator_client/src/keystore/crypto.rs | 4 ++-- validator_client/src/validator_directory.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/validator_client/src/keystore/crypto.rs b/validator_client/src/keystore/crypto.rs index 30d8ba9e439..31a8b29560c 100644 --- a/validator_client/src/keystore/crypto.rs +++ b/validator_client/src/keystore/crypto.rs @@ -133,7 +133,7 @@ mod tests { let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); - let password = Password("testpassword".to_string()); + let password: Password = "testpassword".into(); let kdf = Kdf::Pbkdf2(Pbkdf2 { dklen: 32, @@ -169,7 +169,7 @@ mod tests { let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); - let password = "testpassword".to_string(); + let password: Password = "testpassword".into(); let kdf = Kdf::Scrypt(Scrypt { dklen: 32, diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index 4e5228db41c..35620f6c982 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -1,4 +1,4 @@ -use crate::keystore::{Keystore, Password}; +use crate::keystore::Keystore; use bls::get_withdrawal_credentials; use deposit_contract::eth1_tx_data; use hex;