From 7a336c677e80e0405e523891c5622c1c18cff332 Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 31 Oct 2019 01:45:17 +0530 Subject: [PATCH 001/118] 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 002/118] 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 003/118] 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 004/118] 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 005/118] 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 006/118] 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 007/118] 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 008/118] 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 009/118] 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 010/118] 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 011/118] 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 012/118] 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 013/118] 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 014/118] 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 015/118] 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 016/118] 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 017/118] 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 018/118] 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 019/118] 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 020/118] 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 021/118] 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 022/118] 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 023/118] 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 024/118] 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 025/118] 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; From fbb4984686810a0d0eeab7db534b31a18fe00995 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 28 Apr 2020 13:02:42 +1000 Subject: [PATCH 026/118] Move keystore into own crate --- Cargo.lock | 20 ++++++++++++++++--- Cargo.toml | 1 + eth2/utils/eth2_keystore/Cargo.toml | 19 ++++++++++++++++++ .../utils/eth2_keystore/src}/checksum.rs | 0 .../utils/eth2_keystore/src}/cipher.rs | 0 .../utils/eth2_keystore/src}/crypto.rs | 10 +++++----- .../utils/eth2_keystore/src}/kdf.rs | 0 .../utils/eth2_keystore/src/lib.rs | 9 +++++---- validator_client/Cargo.toml | 6 ++---- validator_client/src/lib.rs | 1 - validator_client/src/validator_directory.rs | 2 +- 11 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 eth2/utils/eth2_keystore/Cargo.toml rename {validator_client/src/keystore => eth2/utils/eth2_keystore/src}/checksum.rs (100%) rename {validator_client/src/keystore => eth2/utils/eth2_keystore/src}/cipher.rs (100%) rename {validator_client/src/keystore => eth2/utils/eth2_keystore/src}/crypto.rs (96%) rename {validator_client/src/keystore => eth2/utils/eth2_keystore/src}/kdf.rs (100%) rename validator_client/src/keystore/mod.rs => eth2/utils/eth2_keystore/src/lib.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index dcdef874e20..a2c6dd5ddbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1192,6 +1192,22 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "eth2_keystore" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "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]] name = "eth2_ssz" version = "0.1.2" @@ -4770,6 +4786,7 @@ dependencies = [ "error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "eth2_config 0.2.0", "eth2_interop_keypairs 0.2.0", + "eth2_keystore 0.1.0", "eth2_ssz 0.1.2", "eth2_ssz_derive 0.1.0", "exit-future 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4778,11 +4795,9 @@ dependencies = [ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "logging 0.2.0", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (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.2.0", "rest_types 0.2.0", - "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4799,7 +4814,6 @@ dependencies = [ "types 0.2.0", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "web3 0.10.0 (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/Cargo.toml b/Cargo.toml index 453577d231a..01b55c9268d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/deposit_contract", "eth2/utils/eth2_config", "eth2/utils/eth2_interop_keypairs", + "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", "eth2/utils/logging", "eth2/utils/eth2_hashing", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml new file mode 100644 index 00000000000..3cf2582da7e --- /dev/null +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "eth2_keystore" +version = "0.1.0" +authors = ["Pawan Dhananjay "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.7.2" +rust-crypto = "0.2.36" +uuid = { version = "0.8", features = ["serde", "v4"] } +time = "0.1.42" +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +serde = "1.0.102" +serde_repr = "0.1" +hex = "0.3" +bls = { path = "../bls" } +serde_json = "1.0.41" diff --git a/validator_client/src/keystore/checksum.rs b/eth2/utils/eth2_keystore/src/checksum.rs similarity index 100% rename from validator_client/src/keystore/checksum.rs rename to eth2/utils/eth2_keystore/src/checksum.rs diff --git a/validator_client/src/keystore/cipher.rs b/eth2/utils/eth2_keystore/src/cipher.rs similarity index 100% rename from validator_client/src/keystore/cipher.rs rename to eth2/utils/eth2_keystore/src/cipher.rs diff --git a/validator_client/src/keystore/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs similarity index 96% rename from validator_client/src/keystore/crypto.rs rename to eth2/utils/eth2_keystore/src/crypto.rs index 31a8b29560c..1e27ffbad72 100644 --- a/validator_client/src/keystore/crypto.rs +++ b/eth2/utils/eth2_keystore/src/crypto.rs @@ -1,6 +1,6 @@ -use crate::keystore::checksum::{Checksum, ChecksumModule}; -use crate::keystore::cipher::{Cipher, CipherModule}; -use crate::keystore::kdf::{Kdf, KdfModule}; +use crate::checksum::{Checksum, ChecksumModule}; +use crate::cipher::{Cipher, CipherModule}; +use crate::kdf::{Kdf, KdfModule}; use serde::{Deserialize, Serialize}; use std::fmt; use zeroize::Zeroize; @@ -113,8 +113,8 @@ impl Crypto { #[cfg(test)] mod tests { use super::*; - use crate::keystore::cipher::{Aes128Ctr, Cipher}; - use crate::keystore::kdf::{Kdf, Pbkdf2, Prf, Scrypt}; + use crate::cipher::{Aes128Ctr, Cipher}; + use crate::kdf::{Kdf, Pbkdf2, Prf, Scrypt}; fn from_slice(bytes: &[u8]) -> [u8; 16] { let mut array = [0; 16]; diff --git a/validator_client/src/keystore/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs similarity index 100% rename from validator_client/src/keystore/kdf.rs rename to eth2/utils/eth2_keystore/src/kdf.rs diff --git a/validator_client/src/keystore/mod.rs b/eth2/utils/eth2_keystore/src/lib.rs similarity index 97% rename from validator_client/src/keystore/mod.rs rename to eth2/utils/eth2_keystore/src/lib.rs index 509ce66c4c0..30aa68906c6 100644 --- a/validator_client/src/keystore/mod.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -2,16 +2,17 @@ mod checksum; mod cipher; mod crypto; mod kdf; -use crate::keystore::cipher::Cipher; -use crate::keystore::crypto::Crypto; -use crate::keystore::kdf::Kdf; + +use crate::cipher::Cipher; +use crate::crypto::Crypto; +use crate::kdf::Kdf; use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use serde_repr::*; use uuid::Uuid; use zeroize::Zeroize; -pub use crate::keystore::crypto::Password; +pub use crate::crypto::Password; const PRIVATE_KEY_BYTES: usize = 48; diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 004256abf51..fa1dbad66d2 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -42,9 +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" -zeroize = { version = "1.0.0", features = ["zeroize_derive"] } web3 = "0.10.0" +time = "0.1.42" +eth2_keystore = { path = "../eth2/utils/eth2_keystore" } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index d1126080a87..ec7e2a743d2 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -4,7 +4,6 @@ mod cli; mod config; mod duties_service; mod fork_service; -mod keystore; mod notifier; mod validator_store; diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index 66e613678c6..625e433d900 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -1,6 +1,6 @@ -use crate::keystore::Keystore; use bls::get_withdrawal_credentials; use deposit_contract::{encode_eth1_tx_data, DEPOSIT_GAS}; +use eth2_keystore::Keystore; use futures::{Future, IntoFuture}; use hex; use ssz::{Decode, Encode}; From e8b504653271ffefdf79c0973aedfa048480054e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 29 Apr 2020 12:13:48 +1000 Subject: [PATCH 027/118] Remove padding --- eth2/utils/eth2_keystore/src/lib.rs | 33 ++++------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 30aa68906c6..343b3cb0a06 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -10,23 +10,9 @@ use bls::{Keypair, PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use serde_repr::*; use uuid::Uuid; -use zeroize::Zeroize; pub use crate::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)] @@ -91,8 +77,7 @@ impl Keystore { 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.as_ref()) + let sk = SecretKey::from_bytes(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 { @@ -102,18 +87,11 @@ 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]) -> MilagroSecretKey { - let mut bytes = [0; PRIVATE_KEY_BYTES]; - bytes[PRIVATE_KEY_BYTES - sk.len()..].copy_from_slice(sk); - MilagroSecretKey(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::*; + #[test] fn test_vectors() { let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; @@ -188,11 +166,8 @@ mod tests { for test in test_vectors { 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.as_ref().to_vec() - ) + let expected_sk = hex::decode(expected_secret).unwrap(); + assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) } } } From e8348d9d409482f5a3fd336c03332221ea55e977 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 29 Apr 2020 15:03:36 +1000 Subject: [PATCH 028/118] Add error enum, zeroize more things --- Cargo.lock | 1 + eth2/utils/eth2_keystore/Cargo.toml | 1 + eth2/utils/eth2_keystore/src/checksum.rs | 11 ++--- eth2/utils/eth2_keystore/src/cipher.rs | 29 ++++++++++-- eth2/utils/eth2_keystore/src/crypto.rs | 52 ++++++++++------------ eth2/utils/eth2_keystore/src/kdf.rs | 56 ++++++++++++++++++++---- eth2/utils/eth2_keystore/src/lib.rs | 28 +++++++++--- 7 files changed, 126 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33e26ebd102..46278af19a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1197,6 +1197,7 @@ name = "eth2_keystore" version = "0.1.0" dependencies = [ "bls 0.2.0", + "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index 3cf2582da7e..cf25ee7e575 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -16,4 +16,5 @@ serde = "1.0.102" serde_repr = "0.1" hex = "0.3" bls = { path = "../bls" } +eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" diff --git a/eth2/utils/eth2_keystore/src/checksum.rs b/eth2/utils/eth2_keystore/src/checksum.rs index 805c6681629..609d8ece462 100644 --- a/eth2/utils/eth2_keystore/src/checksum.rs +++ b/eth2/utils/eth2_keystore/src/checksum.rs @@ -1,3 +1,4 @@ +use crate::kdf::DerivedKey; use crypto::digest::Digest; use crypto::sha2::Sha256; use serde::{Deserialize, Serialize}; @@ -11,13 +12,13 @@ pub struct ChecksumModule { } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct Checksum(String); +pub struct Sha256Checksum(String); -impl Checksum { - /// Generate checksum using checksum function. - pub fn gen_checksum(message: &[u8]) -> String { +impl Sha256Checksum { + pub fn generate(derived_key: &DerivedKey, cipher_message: &[u8]) -> String { let mut hasher = Sha256::new(); - hasher.input(message); + hasher.input(derived_key.checksum_slice()); + hasher.input(cipher_message); hasher.result_str() } diff --git a/eth2/utils/eth2_keystore/src/cipher.rs b/eth2/utils/eth2_keystore/src/cipher.rs index e95d1ee5e13..54855a19dee 100644 --- a/eth2/utils/eth2_keystore/src/cipher.rs +++ b/eth2/utils/eth2_keystore/src/cipher.rs @@ -2,9 +2,32 @@ use crypto::aes::{ctr, KeySize}; use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; +use zeroize::Zeroize; const IV_SIZE: usize = 16; +#[derive(Zeroize, Clone, PartialEq)] +#[zeroize(drop)] +pub struct PlainText(Vec); + +impl PlainText { + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} + /// Convert slice to fixed length array. /// Returns `None` if slice has len > `IV_SIZE` fn from_slice(bytes: &[u8]) -> Option<[u8; IV_SIZE]> { @@ -41,10 +64,10 @@ impl Aes128Ctr { ct } - pub fn decrypt(&self, key: &[u8], ct: &[u8]) -> Vec { + pub fn decrypt(&self, key: &[u8], ct: &[u8]) -> PlainText { // TODO: sanity checks - let mut pt = vec![0; ct.len()]; - ctr(KeySize::KeySize128, key, &self.iv).process(ct, &mut pt); + let mut pt = PlainText::zero(ct.len()); + ctr(KeySize::KeySize128, key, &self.iv).process(ct, &mut pt.as_mut_bytes()); pt } } diff --git a/eth2/utils/eth2_keystore/src/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs index 1e27ffbad72..abfe90c52a5 100644 --- a/eth2/utils/eth2_keystore/src/crypto.rs +++ b/eth2/utils/eth2_keystore/src/crypto.rs @@ -1,6 +1,7 @@ -use crate::checksum::{Checksum, ChecksumModule}; -use crate::cipher::{Cipher, CipherModule}; +use crate::checksum::{ChecksumModule, Sha256Checksum}; +use crate::cipher::{Cipher, CipherModule, PlainText}; use crate::kdf::{Kdf, KdfModule}; +use crate::Error; use serde::{Deserialize, Serialize}; use std::fmt; use zeroize::Zeroize; @@ -20,12 +21,14 @@ impl Password { } } +#[cfg(test)] impl From for Password { fn from(s: String) -> Password { Password(s) } } +#[cfg(test)] impl<'a> From<&'a str> for Password { fn from(s: &'a str) -> Password { Password::from(String::from(s)) @@ -50,13 +53,10 @@ impl Crypto { Kdf::Scrypt(scrypt) => scrypt.derive_key(password.as_str()), }; // Encrypt secret - let cipher_message = match &cipher { - Cipher::Aes128Ctr(cipher) => cipher.encrypt(&derived_key[0..16], secret), + let cipher_message: Vec = match &cipher { + Cipher::Aes128Ctr(cipher) => cipher.encrypt(derived_key.aes_key(), secret), }; - // Generate checksum - 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); + Crypto { kdf: KdfModule { function: kdf.function(), @@ -64,9 +64,9 @@ impl Crypto { message: "".to_string(), }, checksum: ChecksumModule { - function: Checksum::function(), + function: Sha256Checksum::function(), params: serde_json::Value::Object(serde_json::Map::default()), - message: checksum, + message: Sha256Checksum::generate(&derived_key, &cipher_message), }, cipher: CipherModule { function: cipher.function(), @@ -80,31 +80,25 @@ 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: Password) -> Result, String> { + pub fn decrypt(&self, password: Password) -> Result { + let cipher_message = + hex::decode(self.cipher.message.clone()).map_err(Error::InvalidCipherMessageHex)?; + // Generate derived key let derived_key = match &self.kdf.params { 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(); - 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); - - // `password` is incorrect if checksums don't match - if checksum != self.checksum.message { - return Err("Incorrect password. Checksum does not match".into()); + + // Mismatching `password` indicates an invalid password. + if Sha256Checksum::generate(&derived_key, &cipher_message) != self.checksum.message { + return Err(Error::InvalidPassword); } + let secret = match &self.cipher.params { - Cipher::Aes128Ctr(cipher) => cipher.decrypt( - &derived_key[0..16], - &hex::decode(self.cipher.message.clone()) - .map_err(|e| format!("Cipher message should be in hex: {}", e))?, - ), + Cipher::Aes128Ctr(cipher) => cipher.decrypt(&derived_key.aes_key(), &cipher_message), }; + Ok(secret) } } @@ -156,7 +150,7 @@ mod tests { let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(secret, recovered_secret); + assert_eq!(secret, recovered_secret.as_bytes()); } #[test] @@ -193,6 +187,6 @@ mod tests { let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - assert_eq!(secret, recovered_secret); + assert_eq!(secret, recovered_secret.as_bytes()); } } diff --git a/eth2/utils/eth2_keystore/src/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs index 930afb7c158..5a2cec48126 100644 --- a/eth2/utils/eth2_keystore/src/kdf.rs +++ b/eth2/utils/eth2_keystore/src/kdf.rs @@ -3,10 +3,48 @@ use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; use std::default::Default; +use zeroize::Zeroize; // TODO: verify size of salt const SALT_SIZE: usize = 32; -const DECRYPTION_KEY_SIZE: u32 = 32; +const DECRYPTION_KEY_SIZE: usize = 32; +const DKLEN: u32 = 32; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct DerivedKey([u8; DECRYPTION_KEY_SIZE]); + +impl DerivedKey { + /// Instantiates `Self` with a all-zeros byte array. + fn zero() -> Self { + Self([0; DECRYPTION_KEY_SIZE]) + } + + /// Returns a mutable reference to the underlying byte array. + fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns the `DK_slice` bytes used for checksum comparison. + /// + /// ## Reference + /// + /// # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure + pub fn checksum_slice(&self) -> &[u8] { + &self.0[16..32] + } + + /// Returns the aes-128-ctr key. + /// + /// Only the first 16 bytes of the decryption_key are used as the AES key. + /// + /// ## Reference + /// + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#secret-decryption + pub fn aes_key(&self) -> &[u8] { + &self.0[0..16] + } +} /// Parameters for `pbkdf2` key derivation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -22,7 +60,7 @@ impl Default for Pbkdf2 { fn default() -> Self { let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); Pbkdf2 { - dklen: DECRYPTION_KEY_SIZE, + dklen: DKLEN, c: 262144, prf: Prf::default(), salt: salt.to_vec(), @@ -32,10 +70,10 @@ impl Default for Pbkdf2 { impl Pbkdf2 { /// Derive key from password. - pub fn derive_key(&self, password: &str) -> [u8; DECRYPTION_KEY_SIZE as usize] { - let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; + pub fn derive_key(&self, password: &str) -> DerivedKey { + let mut dk = DerivedKey::zero(); let mut mac = self.prf.mac(password.as_bytes()); - pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, &mut dk); + pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, dk.as_mut_bytes()); dk } } @@ -61,12 +99,12 @@ fn log2_int(x: u32) -> u32 { } impl Scrypt { - pub fn derive_key(&self, password: &str) -> [u8; DECRYPTION_KEY_SIZE as usize] { - let mut dk = [0u8; DECRYPTION_KEY_SIZE as usize]; + pub fn derive_key(&self, password: &str) -> DerivedKey { + let mut dk = DerivedKey::zero(); // 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); + scrypt::scrypt(password.as_bytes(), &self.salt, ¶ms, &mut dk.0); dk } } @@ -75,7 +113,7 @@ impl Default for Scrypt { fn default() -> Self { let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); Scrypt { - dklen: DECRYPTION_KEY_SIZE, + dklen: DKLEN, n: 262144, r: 8, p: 1, diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 343b3cb0a06..7e83a1fae4b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -7,12 +7,16 @@ use crate::cipher::Cipher; use crate::crypto::Crypto; use crate::kdf::Kdf; use bls::{Keypair, PublicKey, SecretKey}; +use hex::FromHexError; use serde::{Deserialize, Serialize}; use serde_repr::*; +use ssz::DecodeError; use uuid::Uuid; pub use crate::crypto::Password; +const SECRET_KEY_LEN: usize = 32; + /// Version for `Keystore`. #[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] #[repr(u8)] @@ -26,6 +30,15 @@ impl Default for Version { } } +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidSecretKeyLen { len: usize, expected: usize }, + InvalidCipherMessageHex(FromHexError), + InvalidPassword, + InvalidSecretKeyBytes(DecodeError), + PublicKeyMismatch, +} + /// TODO: Implement `path` according to /// https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md /// For now, `path` is set to en empty string. @@ -72,16 +85,19 @@ 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. - pub fn to_keypair(&self, password: Password) -> 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)); + if sk_bytes.len() != SECRET_KEY_LEN { + return Err(Error::InvalidSecretKeyLen { + len: sk_bytes.len(), + expected: SECRET_KEY_LEN, + }); } - let sk = SecretKey::from_bytes(sk_bytes.as_ref()) - .map_err(|e| format!("Invalid secret key in keystore {:?}", e))?; + let sk = + SecretKey::from_bytes(sk_bytes.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; let pk = PublicKey::from_secret_key(&sk); if pk.as_hex_string()[2..].to_string() != self.pubkey { - return Err(format!("Decoded pubkey doesn't match keystore pubkey")); + return Err(Error::PublicKeyMismatch); } Ok(Keypair { sk, pk }) } From 80024f6f3ad3500631c1bf417c425585f60ffdf6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 29 Apr 2020 15:05:22 +1000 Subject: [PATCH 029/118] Fix comment --- eth2/utils/eth2_keystore/src/kdf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/eth2_keystore/src/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs index 5a2cec48126..69139de8f8f 100644 --- a/eth2/utils/eth2_keystore/src/kdf.rs +++ b/eth2/utils/eth2_keystore/src/kdf.rs @@ -29,7 +29,7 @@ impl DerivedKey { /// /// ## Reference /// - /// # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure pub fn checksum_slice(&self) -> &[u8] { &self.0[16..32] } From 9671f4ab456d8d15e13a871c0dd723553f0ab6d8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 May 2020 16:20:11 +1000 Subject: [PATCH 030/118] Add keystore builder --- eth2/utils/eth2_keystore/src/crypto.rs | 1 + eth2/utils/eth2_keystore/src/lib.rs | 75 +++++++++++++++++--------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs index abfe90c52a5..695ac4b1e1e 100644 --- a/eth2/utils/eth2_keystore/src/crypto.rs +++ b/eth2/utils/eth2_keystore/src/crypto.rs @@ -15,6 +15,7 @@ impl fmt::Display for Password { write!(f, "******") } } + impl Password { pub fn as_str(&self) -> &str { self.0.as_str() diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 7e83a1fae4b..5b15c5fcf80 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -37,6 +37,41 @@ pub enum Error { InvalidPassword, InvalidSecretKeyBytes(DecodeError), PublicKeyMismatch, + EmptyPassword, +} + +pub struct KeystoreBuilder<'a> { + keypair: &'a Keypair, + password: Password, + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, +} + +impl<'a> KeystoreBuilder<'a> { + pub fn new(keypair: &'a Keypair, password: Password) -> Result { + if password.as_str() == "" { + Err(Error::EmptyPassword) + } else { + Ok(Self { + keypair, + password, + kdf: <_>::default(), + cipher: <_>::default(), + uuid: Uuid::new_v4(), + }) + } + } + + pub fn build(self) -> Keystore { + Keystore::new( + self.keypair, + self.password, + self.kdf, + self.cipher, + self.uuid, + ) + } } /// TODO: Implement `path` according to @@ -44,38 +79,25 @@ pub enum Error { /// For now, `path` is set to en empty string. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { - pub crypto: Crypto, - pub uuid: Uuid, - pub path: String, - pub pubkey: String, - pub version: Version, + crypto: Crypto, + uuid: Uuid, + path: String, + pubkey: String, + version: Version, } 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 new( - keypair: &Keypair, - password: Password, - kdf: Option, - cipher: Option, - uuid: Option, - ) -> Self { - let crypto = Crypto::encrypt( - password, - &keypair.sk.as_raw().as_bytes(), - kdf.unwrap_or_default(), - cipher.unwrap_or_default(), - ); - let uuid = uuid.unwrap_or(Uuid::new_v4()); - let version = Version::default(); - let path = String::new(); + /// keypair and password. + fn new(keypair: &Keypair, password: Password, kdf: Kdf, cipher: Cipher, uuid: Uuid) -> Self { + let crypto = Crypto::encrypt(password, &keypair.sk.as_raw().as_bytes(), kdf, cipher); + Keystore { crypto, uuid, - path, + path: String::new(), pubkey: keypair.pk.as_hex_string()[2..].to_string(), - version, + version: Version::default(), } } @@ -101,6 +123,11 @@ impl Keystore { } Ok(Keypair { sk, pk }) } + + /// Returns the UUID for the keystore. + pub fn uuid(&self) -> &Uuid { + &self.uuid + } } // Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases From 1f30901f20c9ba50c25a2b8821c90f369b5d9c61 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 May 2020 17:01:25 +1000 Subject: [PATCH 031/118] Remove keystore stuff from val client --- Cargo.lock | 3 - validator_client/Cargo.toml | 3 - validator_client/src/validator_directory.rs | 82 --------------------- 3 files changed, 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f8ab1c12bc..b3a3bc0f9c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4788,7 +4788,6 @@ dependencies = [ "error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "eth2_config 0.2.0", "eth2_interop_keypairs 0.2.0", - "eth2_keystore 0.1.0", "eth2_ssz 0.1.2", "eth2_ssz_derive 0.1.0", "exit-future 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4809,12 +4808,10 @@ dependencies = [ "slog-term 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "slot_clock 0.2.0", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (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.13 (registry+https://github.com/rust-lang/crates.io-index)", "tree_hash 0.1.1", "types 0.2.0", - "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index fa1dbad66d2..22f01b2eef3 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -42,7 +42,4 @@ bls = { path = "../eth2/utils/bls" } remote_beacon_node = { path = "../eth2/utils/remote_beacon_node" } tempdir = "0.3" rayon = "1.2.0" -uuid = { version = "0.8", features = ["serde", "v4"] } web3 = "0.10.0" -time = "0.1.42" -eth2_keystore = { path = "../eth2/utils/eth2_keystore" } diff --git a/validator_client/src/validator_directory.rs b/validator_client/src/validator_directory.rs index 625e433d900..197e1cb44eb 100644 --- a/validator_client/src/validator_directory.rs +++ b/validator_client/src/validator_directory.rs @@ -1,6 +1,5 @@ use bls::get_withdrawal_credentials; use deposit_contract::{encode_eth1_tx_data, DEPOSIT_GAS}; -use eth2_keystore::Keystore; use futures::{Future, IntoFuture}; use hex; use ssz::{Decode, Encode}; @@ -14,7 +13,6 @@ use types::{ test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair, PublicKey, SecretKey, Signature, }; -use uuid::Uuid; use web3::{ types::{Address, TransactionRequest, U256}, Transport, Web3, @@ -29,33 +27,11 @@ 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/`. @@ -110,19 +86,6 @@ 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); @@ -240,22 +203,6 @@ 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 @@ -299,35 +246,6 @@ 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(()) - } - fn get_deposit_data(&self) -> Result<(Vec, u64), String> { let voting_keypair = self .voting_keypair From 7bdeef05d1885949926759713bb0a0a7dd9618cd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 May 2020 17:02:38 +1000 Subject: [PATCH 032/118] Add more tests, comments --- eth2/utils/eth2_keystore/src/crypto.rs | 1 - eth2/utils/eth2_keystore/src/lib.rs | 97 +++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs index 695ac4b1e1e..39704235f47 100644 --- a/eth2/utils/eth2_keystore/src/crypto.rs +++ b/eth2/utils/eth2_keystore/src/crypto.rs @@ -22,7 +22,6 @@ impl Password { } } -#[cfg(test)] impl From for Password { fn from(s: String) -> Password { Password(s) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 5b15c5fcf80..33d6220c5bc 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -15,6 +15,7 @@ use uuid::Uuid; pub use crate::crypto::Password; +/// The byte-length of a BLS secret key. const SECRET_KEY_LEN: usize = 32; /// Version for `Keystore`. @@ -38,8 +39,11 @@ pub enum Error { InvalidSecretKeyBytes(DecodeError), PublicKeyMismatch, EmptyPassword, + UnableToSerialize, + InvalidJson, } +/// Constructs a `Keystore`. pub struct KeystoreBuilder<'a> { keypair: &'a Keypair, password: Password, @@ -49,6 +53,11 @@ pub struct KeystoreBuilder<'a> { } impl<'a> KeystoreBuilder<'a> { + /// Creates a new builder. + /// + /// ## Errors + /// + /// Returns `Error::EmptyPassword` if `password == ""`. pub fn new(keypair: &'a Keypair, password: Password) -> Result { if password.as_str() == "" { Err(Error::EmptyPassword) @@ -63,6 +72,7 @@ impl<'a> KeystoreBuilder<'a> { } } + /// Consumes `self`, returning a `Keystore`. pub fn build(self) -> Keystore { Keystore::new( self.keypair, @@ -74,9 +84,9 @@ impl<'a> KeystoreBuilder<'a> { } } -/// TODO: Implement `path` according to -/// https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md -/// For now, `path` is set to en empty string. +/// Provides a BLS keystore as defined in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +/// +/// Use `KeystoreBuilder` to create a new keystore. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { crypto: Crypto, @@ -95,32 +105,45 @@ impl Keystore { Keystore { crypto, uuid, + // TODO: Implement `path` according to + // https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md + // For now, `path` is set to en empty string. path: String::new(), pubkey: keypair.pk.as_hex_string()[2..].to_string(), version: Version::default(), } } - /// Regenerate a BLS12-381 `Keypair` from given the `Keystore` object and - /// the correct password. + /// Regenerate a BLS12-381 `Keypair` from `self` 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. - pub fn to_keypair(&self, password: Password) -> Result { + /// ## Errors + /// + /// - The provided password is incorrect. + /// - The keystore is badly formed. + pub fn decrypt_keypair(&self, password: Password) -> Result { + // Decrypt cipher-text into plain-text. let sk_bytes = self.crypto.decrypt(password)?; + + // Verify that secret key material is correct length. if sk_bytes.len() != SECRET_KEY_LEN { return Err(Error::InvalidSecretKeyLen { len: sk_bytes.len(), expected: SECRET_KEY_LEN, }); } + + // Instantiate a `SecretKey`. let sk = SecretKey::from_bytes(sk_bytes.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; + + // Derive a `PublicKey` from `SecretKey`. let pk = PublicKey::from_secret_key(&sk); + + // Verify that the derived `PublicKey` matches `self`. if pk.as_hex_string()[2..].to_string() != self.pubkey { return Err(Error::PublicKeyMismatch); } + Ok(Keypair { sk, pk }) } @@ -128,13 +151,65 @@ impl Keystore { pub fn uuid(&self) -> &Uuid { &self.uuid } + + /// Returns `self` encoded as a JSON object. + pub fn to_json_string(&self) -> Result { + serde_json::to_string(self).map_err(|_| Error::UnableToSerialize) + } + + /// Returns `self` encoded as a JSON object. + pub fn from_json_str(json_string: &str) -> Result { + serde_json::from_str(json_string).map_err(|_| Error::InvalidJson) + } } -// Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[cfg(test)] mod tests { use super::*; + fn password() -> Password { + "ilikecats".to_string().into() + } + + fn bad_password() -> Password { + "idontlikecats".to_string().into() + } + + #[test] + fn empty_password() { + assert_eq!( + KeystoreBuilder::new(&Keypair::random(), "".into()) + .err() + .unwrap(), + Error::EmptyPassword + ); + } + + #[test] + fn string_round_trip() { + let keypair = Keypair::random(); + + let keystore = KeystoreBuilder::new(&keypair, password()).unwrap().build(); + + let json = keystore.to_json_string().unwrap(); + let decoded = Keystore::from_json_str(&json).unwrap(); + + assert_eq!( + decoded.decrypt_keypair(bad_password()).err().unwrap(), + Error::InvalidPassword, + "should not decrypt with bad password" + ); + + assert_eq!( + decoded.decrypt_keypair(password()).unwrap(), + keypair, + "should decrypt with good password" + ); + } + + // Test cases taken from: + // + // https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[test] fn test_vectors() { let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; @@ -208,7 +283,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.to_keypair(password.clone()).unwrap(); + let keypair = keystore.decrypt_keypair(password.clone()).unwrap(); let expected_sk = hex::decode(expected_secret).unwrap(); assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) } From e911e535a5d8cc9a30161ecfe0cb7e4c31d22e84 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 May 2020 18:30:01 +1000 Subject: [PATCH 033/118] Add more comments, test vectors --- eth2/utils/eth2_keystore/src/cipher.rs | 5 ++ eth2/utils/eth2_keystore/src/lib.rs | 90 +++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/eth2/utils/eth2_keystore/src/cipher.rs b/eth2/utils/eth2_keystore/src/cipher.rs index 54855a19dee..c22298bbc5d 100644 --- a/eth2/utils/eth2_keystore/src/cipher.rs +++ b/eth2/utils/eth2_keystore/src/cipher.rs @@ -6,23 +6,28 @@ use zeroize::Zeroize; const IV_SIZE: usize = 16; +/// Provides wrapper around `Vec` that implements zeroize. #[derive(Zeroize, Clone, PartialEq)] #[zeroize(drop)] pub struct PlainText(Vec); impl PlainText { + /// Instantiate self with `len` zeros. pub fn zero(len: usize) -> Self { Self(vec![0; len]) } + /// The byte-length of `self` pub fn len(&self) -> usize { self.0.len() } + /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + /// Returns a mutable reference to the underlying bytes. pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 } diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 33d6220c5bc..bc0676d7360 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -211,7 +211,7 @@ mod tests { // // https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[test] - fn test_vectors() { + fn eip_2335_test_vectors() { let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; let password: Password = "testpassword".into(); let scrypt_test_vector = r#" @@ -288,4 +288,92 @@ mod tests { assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) } } + + #[test] + fn json_invalid_version() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 5 + } + "#; + + assert_eq!( + Keystore::from_json_str(&vector).err().unwrap(), + Error::InvalidJson + ); + } + + #[test] + fn json_bad_checksum() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cd" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_eq!( + Keystore::from_json_str(&vector) + .unwrap() + .decrypt_keypair("testpassword".into()) + .err() + .unwrap(), + Error::InvalidPassword + ); + } } From 5ec380522883de65bb8223d3266ccc901de5bd4b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 11:06:35 +1000 Subject: [PATCH 034/118] Progress on improving JSON validation --- eth2/utils/eth2_keystore/src/checksum.rs | 58 +++++++- eth2/utils/eth2_keystore/src/crypto.rs | 4 +- eth2/utils/eth2_keystore/src/kdf.rs | 38 ++++- eth2/utils/eth2_keystore/src/lib.rs | 181 ++++++++++++++++++++++- 4 files changed, 265 insertions(+), 16 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/checksum.rs b/eth2/utils/eth2_keystore/src/checksum.rs index 609d8ece462..ccd6a5403c7 100644 --- a/eth2/utils/eth2_keystore/src/checksum.rs +++ b/eth2/utils/eth2_keystore/src/checksum.rs @@ -2,12 +2,62 @@ use crate::kdf::DerivedKey; use crypto::digest::Digest; use crypto::sha2::Sha256; use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use std::convert::TryFrom; + +/// Used for ensuring that serde only decodes valid checksum functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum ChecksumFunction { + Sha256, +} + +impl Into for ChecksumFunction { + fn into(self) -> String { + match self { + ChecksumFunction::Sha256 => "sha256".into(), + } + } +} + +impl TryFrom for ChecksumFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "sha256" => Ok(ChecksumFunction::Sha256), + other => Err(format!("Unsupported checksum function: {}", other)), + } + } +} + +/// Used for ensuring serde only decode an empty map +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "Value", into = "Value")] +pub struct EmptyMap; + +impl Into for EmptyMap { + fn into(self) -> Value { + Value::Object(Map::default()) + } +} + +impl TryFrom for EmptyMap { + type Error = &'static str; + + fn try_from(v: Value) -> Result { + match v { + Value::Object(map) if map.is_empty() => Ok(Self), + _ => Err("Checksum params must be an empty map"), + } + } +} /// Checksum module for `Keystore`. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ChecksumModule { - pub function: String, - pub params: serde_json::Value, // Empty json object + pub function: ChecksumFunction, + pub params: EmptyMap, pub message: String, } @@ -22,7 +72,7 @@ impl Sha256Checksum { hasher.result_str() } - pub fn function() -> String { - "sha256".to_string() + pub fn function() -> ChecksumFunction { + ChecksumFunction::Sha256 } } diff --git a/eth2/utils/eth2_keystore/src/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs index 39704235f47..4c1dfa15b90 100644 --- a/eth2/utils/eth2_keystore/src/crypto.rs +++ b/eth2/utils/eth2_keystore/src/crypto.rs @@ -1,4 +1,4 @@ -use crate::checksum::{ChecksumModule, Sha256Checksum}; +use crate::checksum::{ChecksumModule, EmptyMap, Sha256Checksum}; use crate::cipher::{Cipher, CipherModule, PlainText}; use crate::kdf::{Kdf, KdfModule}; use crate::Error; @@ -65,7 +65,7 @@ impl Crypto { }, checksum: ChecksumModule { function: Sha256Checksum::function(), - params: serde_json::Value::Object(serde_json::Map::default()), + params: EmptyMap, message: Sha256Checksum::generate(&derived_key, &cipher_message), }, cipher: CipherModule { diff --git a/eth2/utils/eth2_keystore/src/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs index 69139de8f8f..0f4dd80f8af 100644 --- a/eth2/utils/eth2_keystore/src/kdf.rs +++ b/eth2/utils/eth2_keystore/src/kdf.rs @@ -2,6 +2,7 @@ use crypto::sha2::Sha256; use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; +use std::convert::TryFrom; use std::default::Default; use zeroize::Zeroize; @@ -165,10 +166,39 @@ impl Default for Kdf { } impl Kdf { - pub fn function(&self) -> String { + pub fn function(&self) -> KdfFunction { match &self { - Kdf::Pbkdf2(_) => "pbkdf2".to_string(), - Kdf::Scrypt(_) => "scrypt".to_string(), + Kdf::Pbkdf2(_) => KdfFunction::Pbkdf2, + Kdf::Scrypt(_) => KdfFunction::Scrypt, + } + } +} + +/// Used for ensuring that serde only decodes valid KDF functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum KdfFunction { + Scrypt, + Pbkdf2, +} + +impl Into for KdfFunction { + fn into(self) -> String { + match self { + KdfFunction::Scrypt => "scrypt".into(), + KdfFunction::Pbkdf2 => "pbkdf2".into(), + } + } +} + +impl TryFrom for KdfFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "scrypt" => Ok(KdfFunction::Scrypt), + "pbkdf2" => Ok(KdfFunction::Pbkdf2), + other => Err(format!("Unsupported kdf function: {}", other)), } } } @@ -176,7 +206,7 @@ impl Kdf { /// KDF module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct KdfModule { - pub function: String, + pub function: KdfFunction, pub params: Kdf, pub message: String, } diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index bc0676d7360..bd9e3e63309 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -40,7 +40,7 @@ pub enum Error { PublicKeyMismatch, EmptyPassword, UnableToSerialize, - InvalidJson, + InvalidJson(String), } /// Constructs a `Keystore`. @@ -159,7 +159,7 @@ impl Keystore { /// Returns `self` encoded as a JSON object. pub fn from_json_str(json_string: &str) -> Result { - serde_json::from_str(json_string).map_err(|_| Error::InvalidJson) + serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) } } @@ -325,10 +325,10 @@ mod tests { } "#; - assert_eq!( - Keystore::from_json_str(&vector).err().unwrap(), - Error::InvalidJson - ); + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } } #[test] @@ -376,4 +376,173 @@ mod tests { Error::InvalidPassword ); } + + #[test] + fn json_invalid_kdf_function() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "not-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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_missing_scrypt_param() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_checksum_function() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "not-sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_checksum_params() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": { + "cats": "lol" + }, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } } From 53a774c9fdaf72a5c451237e64c452d5059828c2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 11:20:10 +1000 Subject: [PATCH 035/118] More JSON verification --- eth2/utils/eth2_keystore/src/cipher.rs | 36 ++- eth2/utils/eth2_keystore/src/kdf.rs | 2 + eth2/utils/eth2_keystore/src/lib.rs | 291 +++++++++++++++++++++++++ 3 files changed, 325 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/cipher.rs b/eth2/utils/eth2_keystore/src/cipher.rs index c22298bbc5d..2e80e6f7e1c 100644 --- a/eth2/utils/eth2_keystore/src/cipher.rs +++ b/eth2/utils/eth2_keystore/src/cipher.rs @@ -1,6 +1,7 @@ use crypto::aes::{ctr, KeySize}; use rand::prelude::*; use serde::{de, Deserialize, Serialize, Serializer}; +use std::convert::TryFrom; use std::default::Default; use zeroize::Zeroize; @@ -45,16 +46,43 @@ fn from_slice(bytes: &[u8]) -> Option<[u8; IV_SIZE]> { Some(array) } +/// Used for ensuring that serde only decodes valid cipher functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum CipherFunction { + Aes128Ctr, +} + +impl Into for CipherFunction { + fn into(self) -> String { + match self { + CipherFunction::Aes128Ctr => "aes-128-ctr".into(), + } + } +} + +impl TryFrom for CipherFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "aes-128-ctr" => Ok(CipherFunction::Aes128Ctr), + other => Err(format!("Unsupported cipher function: {}", other)), + } + } +} + /// Cipher module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct CipherModule { - pub function: String, + pub function: CipherFunction, pub params: Cipher, pub message: String, } /// Parameters for AES128 with ctr mode. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Aes128Ctr { #[serde(serialize_with = "serialize_iv")] #[serde(deserialize_with = "deserialize_iv")] @@ -108,7 +136,7 @@ where } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(untagged)] +#[serde(untagged, deny_unknown_fields)] pub enum Cipher { Aes128Ctr(Aes128Ctr), } @@ -121,9 +149,9 @@ impl Default for Cipher { } impl Cipher { - pub fn function(&self) -> String { + pub fn function(&self) -> CipherFunction { match &self { - Cipher::Aes128Ctr(_) => "aes-128-ctr".to_string(), + Cipher::Aes128Ctr(_) => CipherFunction::Aes128Ctr, } } } diff --git a/eth2/utils/eth2_keystore/src/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs index 0f4dd80f8af..e4d8a7106f7 100644 --- a/eth2/utils/eth2_keystore/src/kdf.rs +++ b/eth2/utils/eth2_keystore/src/kdf.rs @@ -49,6 +49,7 @@ impl DerivedKey { /// Parameters for `pbkdf2` key derivation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Pbkdf2 { pub c: u32, pub dklen: u32, @@ -81,6 +82,7 @@ impl Pbkdf2 { /// Parameters for `scrypt` key derivation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Scrypt { pub dklen: u32, pub n: u32, diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index bd9e3e63309..e5decbe3132 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -460,6 +460,49 @@ mod tests { } } + #[test] + fn json_invalid_additional_scrypt_param() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "cats": 42 + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + #[test] fn json_invalid_checksum_function() { let vector = r#" @@ -545,4 +588,252 @@ mod tests { _ => panic!("expected invalid json error"), } } + + #[test] + fn json_invalid_cipher_function() { + let 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": "not-aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_additional_cipher_param() { + let 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", + "cat": 42 + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_missing_cipher_param() { + let 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": {}, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_missing_pubkey() { + let 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 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_missing_path() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } + + #[test] + fn json_invalid_missing_version() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "" + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } + } } From 4dd993116b924248a7a9fb878fc47fa2f42d3591 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 13:09:26 +1000 Subject: [PATCH 036/118] Start moving JSON into own mod --- eth2/utils/eth2_keystore/src/derived_key.rs | 38 +++ .../src/json_keystore/checksum_module.rs | 74 ++++++ .../src/json_keystore/cipher_module.rs | 63 +++++ .../src/json_keystore/hex_bytes.rs | 43 ++++ .../src/json_keystore/kdf_module.rs | 105 ++++++++ .../eth2_keystore/src/json_keystore/mod.rs | 44 ++++ eth2/utils/eth2_keystore/src/lib.rs | 240 ++++++++++++++---- eth2/utils/eth2_keystore/src/password.rs | 28 ++ eth2/utils/eth2_keystore/src/plain_text.rs | 28 ++ 9 files changed, 613 insertions(+), 50 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/derived_key.rs create mode 100644 eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs create mode 100644 eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs create mode 100644 eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs create mode 100644 eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs create mode 100644 eth2/utils/eth2_keystore/src/json_keystore/mod.rs create mode 100644 eth2/utils/eth2_keystore/src/password.rs create mode 100644 eth2/utils/eth2_keystore/src/plain_text.rs diff --git a/eth2/utils/eth2_keystore/src/derived_key.rs b/eth2/utils/eth2_keystore/src/derived_key.rs new file mode 100644 index 00000000000..57f6e6f6330 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/derived_key.rs @@ -0,0 +1,38 @@ +use crate::DKLEN; +use zeroize::Zeroize; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct DerivedKey([u8; DKLEN as usize]); + +impl DerivedKey { + /// Instantiates `Self` with a all-zeros byte array. + pub fn zero() -> Self { + Self([0; DKLEN as usize]) + } + + /// Returns a mutable reference to the underlying byte array. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns the `DK_slice` bytes used for checksum comparison. + /// + /// ## Reference + /// + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure + pub fn checksum_slice(&self) -> &[u8] { + &self.0[16..32] + } + + /// Returns the aes-128-ctr key. + /// + /// Only the first 16 bytes of the decryption_key are used as the AES key. + /// + /// ## Reference + /// + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#secret-decryption + pub fn aes_key(&self) -> &[u8] { + &self.0[0..16] + } +} diff --git a/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs new file mode 100644 index 00000000000..521dc2dc606 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs @@ -0,0 +1,74 @@ +//! Defines the JSON representation of the "checksum" module. +//! +//! This file **MUST NOT** contain any logic beyond what is required to serialize/deserialize the +//! data structures. Specifically, there should not be any actual crypto logic in this file. + +use super::hex_bytes::HexBytes; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use std::convert::TryFrom; + +/// Used for ensuring that serde only decodes valid checksum functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum ChecksumFunction { + Sha256, +} + +impl Into for ChecksumFunction { + fn into(self) -> String { + match self { + ChecksumFunction::Sha256 => "sha256".into(), + } + } +} + +impl TryFrom for ChecksumFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "sha256" => Ok(ChecksumFunction::Sha256), + other => Err(format!("Unsupported checksum function: {}", other)), + } + } +} + +/// Used for ensuring serde only decode an empty map +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "Value", into = "Value")] +pub struct EmptyMap; + +impl Into for EmptyMap { + fn into(self) -> Value { + Value::Object(Map::default()) + } +} + +impl TryFrom for EmptyMap { + type Error = &'static str; + + fn try_from(v: Value) -> Result { + match v { + Value::Object(map) if map.is_empty() => Ok(Self), + _ => Err("Checksum params must be an empty map"), + } + } +} + +/// Checksum module for `Keystore`. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct ChecksumModule { + pub function: ChecksumFunction, + pub params: EmptyMap, + pub message: HexBytes, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Sha256Checksum(String); + +impl Sha256Checksum { + pub fn function() -> ChecksumFunction { + ChecksumFunction::Sha256 + } +} diff --git a/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs new file mode 100644 index 00000000000..6af5db2a895 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs @@ -0,0 +1,63 @@ +//! Defines the JSON representation of the "cipher" module. +//! +//! This file **MUST NOT** contain any logic beyond what is required to serialize/deserialize the +//! data structures. Specifically, there should not be any actual crypto logic in this file. + +use super::hex_bytes::HexBytes; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// Used for ensuring that serde only decodes valid cipher functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum CipherFunction { + Aes128Ctr, +} + +impl Into for CipherFunction { + fn into(self) -> String { + match self { + CipherFunction::Aes128Ctr => "aes-128-ctr".into(), + } + } +} + +impl TryFrom for CipherFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "aes-128-ctr" => Ok(CipherFunction::Aes128Ctr), + other => Err(format!("Unsupported cipher function: {}", other)), + } + } +} + +/// Cipher module representation. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct CipherModule { + pub function: CipherFunction, + pub params: Cipher, + pub message: HexBytes, +} + +/// Parameters for AES128 with ctr mode. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Aes128Ctr { + pub iv: HexBytes, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum Cipher { + Aes128Ctr(Aes128Ctr), +} + +impl Cipher { + pub fn function(&self) -> CipherFunction { + match &self { + Cipher::Aes128Ctr(_) => CipherFunction::Aes128Ctr, + } + } +} diff --git a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs new file mode 100644 index 00000000000..e05882a6588 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// Used for ensuring that serde only decodes valid checksum functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub struct HexBytes(Vec); + +impl HexBytes { + pub fn empty() -> Self { + Self(vec![]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl From> for HexBytes { + fn from(vec: Vec) -> Self { + Self(vec) + } +} + +impl Into for HexBytes { + fn into(self) -> String { + hex::encode(self.0) + } +} + +impl TryFrom for HexBytes { + type Error = String; + + fn try_from(s: String) -> Result { + hex::decode(s) + .map(Self) + .map_err(|e| format!("Invalid hex: {}", e)) + } +} diff --git a/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs new file mode 100644 index 00000000000..79c6e2a243a --- /dev/null +++ b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs @@ -0,0 +1,105 @@ +//! Defines the JSON representation of the "kdf" module. +//! +//! This file **MUST NOT** contain any logic beyond what is required to serialize/deserialize the +//! data structures. Specifically, there should not be any actual crypto logic in this file. + +use super::hex_bytes::HexBytes; +use crypto::sha2::Sha256; +use crypto::{hmac::Hmac, mac::Mac}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// KDF module representation. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct KdfModule { + pub function: KdfFunction, + pub params: Kdf, + pub message: HexBytes, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum Kdf { + Scrypt(Scrypt), + Pbkdf2(Pbkdf2), +} + +impl Kdf { + pub fn function(&self) -> KdfFunction { + match &self { + Kdf::Pbkdf2(_) => KdfFunction::Pbkdf2, + Kdf::Scrypt(_) => KdfFunction::Scrypt, + } + } +} + +/// PRF for use in `pbkdf2`. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub enum Prf { + #[serde(rename = "hmac-sha256")] + HmacSha256, +} + +impl Prf { + 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 + } +} + +/// Parameters for `pbkdf2` key derivation. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Pbkdf2 { + pub c: u32, + pub dklen: u32, + pub prf: Prf, + pub salt: HexBytes, +} + +/// Used for ensuring that serde only decodes valid KDF functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum KdfFunction { + Scrypt, + Pbkdf2, +} + +impl Into for KdfFunction { + fn into(self) -> String { + match self { + KdfFunction::Scrypt => "scrypt".into(), + KdfFunction::Pbkdf2 => "pbkdf2".into(), + } + } +} + +impl TryFrom for KdfFunction { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "scrypt" => Ok(KdfFunction::Scrypt), + "pbkdf2" => Ok(KdfFunction::Pbkdf2), + other => Err(format!("Unsupported kdf function: {}", other)), + } + } +} + +/// Parameters for `scrypt` key derivation. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Scrypt { + pub dklen: u32, + pub n: u32, + pub r: u32, + pub p: u32, + pub salt: HexBytes, +} diff --git a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs new file mode 100644 index 00000000000..01e0844744a --- /dev/null +++ b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs @@ -0,0 +1,44 @@ +mod checksum_module; +mod cipher_module; +mod hex_bytes; +mod kdf_module; + +pub use checksum_module::{ChecksumModule, EmptyMap, Sha256Checksum}; +pub use cipher_module::{Aes128Ctr, Cipher, CipherModule}; +pub use hex_bytes::HexBytes; +pub use kdf_module::{Kdf, KdfModule, Pbkdf2, Prf}; +pub use uuid::Uuid; + +use serde::{Deserialize, Serialize}; +use serde_repr::*; + +/// JSON representation of [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335) keystore. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct JsonKeystore { + pub crypto: Crypto, + pub uuid: Uuid, + pub path: String, + pub pubkey: String, + pub version: Version, +} + +/// Version for `JsonKeystore`. +#[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum Version { + V4 = 4, +} + +impl Version { + pub fn four() -> Self { + Version::V4 + } +} + +/// Crypto module for keystore. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Crypto { + pub kdf: KdfModule, + pub checksum: ChecksumModule, + pub cipher: CipherModule, +} diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index e5decbe3132..e1ff41ff248 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -1,35 +1,33 @@ -mod checksum; -mod cipher; -mod crypto; -mod kdf; - -use crate::cipher::Cipher; -use crate::crypto::Crypto; -use crate::kdf::Kdf; +mod derived_key; +mod json_keystore; +mod password; +mod plain_text; + use bls::{Keypair, PublicKey, SecretKey}; +use crypto::{digest::Digest, sha2::Sha256}; +use derived_key::DerivedKey; use hex::FromHexError; +use json_keystore::{ + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, + KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, +}; +use password::Password; +use plain_text::PlainText; +use rand::prelude::*; use serde::{Deserialize, Serialize}; -use serde_repr::*; use ssz::DecodeError; use uuid::Uuid; -pub use crate::crypto::Password; - /// The byte-length of a BLS secret key. const SECRET_KEY_LEN: usize = 32; - -/// Version for `Keystore`. -#[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum Version { - V4 = 4, -} - -impl Default for Version { - fn default() -> Self { - Version::V4 - } -} +/// The default byte length of the salt used to seed the KDF. +const SALT_SIZE: usize = 32; +/// The length of the derived key. +const DKLEN: u32 = 32; +// TODO: comment +const IV_SIZE: usize = 16; +// TODO: comment +const HASH_SIZE: usize = 32; #[derive(Debug, PartialEq)] pub enum Error { @@ -41,6 +39,7 @@ pub enum Error { EmptyPassword, UnableToSerialize, InvalidJson(String), + IncorrectIvSize { expected: usize, len: usize }, } /// Constructs a `Keystore`. @@ -62,19 +61,27 @@ impl<'a> KeystoreBuilder<'a> { if password.as_str() == "" { Err(Error::EmptyPassword) } else { + let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); + let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); + Ok(Self { keypair, password, - kdf: <_>::default(), - cipher: <_>::default(), + kdf: Kdf::Pbkdf2(Pbkdf2 { + dklen: DKLEN, + c: 262144, + prf: Prf::default(), + salt: salt.to_vec().into(), + }), + cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), uuid: Uuid::new_v4(), }) } } /// Consumes `self`, returning a `Keystore`. - pub fn build(self) -> Keystore { - Keystore::new( + pub fn build(self) -> Result { + Keystore::encrypt( self.keypair, self.password, self.kdf, @@ -89,29 +96,73 @@ impl<'a> KeystoreBuilder<'a> { /// Use `KeystoreBuilder` to create a new keystore. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Keystore { - crypto: Crypto, - uuid: Uuid, - path: String, - pubkey: String, - version: Version, + json: JsonKeystore, } impl Keystore { /// Generate `Keystore` object for a BLS12-381 secret key from a /// keypair and password. - fn new(keypair: &Keypair, password: Password, kdf: Kdf, cipher: Cipher, uuid: Uuid) -> Self { - let crypto = Crypto::encrypt(password, &keypair.sk.as_raw().as_bytes(), kdf, cipher); - - Keystore { - crypto, - uuid, - // TODO: Implement `path` according to - // https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md - // For now, `path` is set to en empty string. - path: String::new(), - pubkey: keypair.pk.as_hex_string()[2..].to_string(), - version: Version::default(), - } + fn encrypt( + keypair: &Keypair, + password: Password, + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, + ) -> Result { + // Generate derived key + let derived_key = derive_key(&password, &kdf); + + // TODO: the keypair secret isn't zeroized. + let secret = keypair.sk.as_raw().as_bytes(); + + // Encrypt secret + let cipher_message: Vec = match &cipher { + Cipher::Aes128Ctr(params) => { + // TODO: sanity checks + // TODO: check IV size + // cipher.encrypt(derived_key.aes_key(), &keypair.sk.as_raw().as_bytes()) + let mut cipher_text = vec![0; secret.len()]; + + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + derived_key.aes_key(), + &get_iv(params.iv.as_bytes())?, + ) + .process(&secret, &mut cipher_text); + cipher_text + } + }; + + let crypto = Crypto { + kdf: KdfModule { + function: kdf.function(), + params: kdf.clone(), + message: HexBytes::empty(), + }, + checksum: ChecksumModule { + function: Sha256Checksum::function(), + params: EmptyMap, + message: generate_checksum(&derived_key, &cipher_message), + }, + cipher: CipherModule { + function: cipher.function(), + params: cipher.clone(), + message: cipher_message.into(), + }, + }; + + Ok(Keystore { + json: JsonKeystore { + crypto, + uuid, + // TODO: Implement `path` according to + // https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md + // For now, `path` is set to en empty string. + path: String::new(), + pubkey: keypair.pk.as_hex_string()[2..].to_string(), + version: Version::four(), + }, + }) } /// Regenerate a BLS12-381 `Keypair` from `self` and the correct password. @@ -121,8 +172,31 @@ impl Keystore { /// - The provided password is incorrect. /// - The keystore is badly formed. pub fn decrypt_keypair(&self, password: Password) -> Result { - // Decrypt cipher-text into plain-text. - let sk_bytes = self.crypto.decrypt(password)?; + let cipher_message = &self.json.crypto.cipher.message; + + // Generate derived key + let derived_key = derive_key(&password, &self.json.crypto.kdf.params); + + // Mismatching checksum indicates an invalid password. + if generate_checksum(&derived_key, cipher_message.as_bytes()) + != self.json.crypto.checksum.message + { + return Err(Error::InvalidPassword); + } + + let sk_bytes = match &self.json.crypto.cipher.params { + Cipher::Aes128Ctr(params) => { + // cipher.decrypt(&derived_key.aes_key(), &cipher_message) + let mut pt = PlainText::zero(cipher_message.len()); + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + derived_key.aes_key(), + &get_iv(¶ms.iv.as_bytes())?, + ) + .process(cipher_message.as_bytes(), pt.as_mut_bytes()); + pt + } + }; // Verify that secret key material is correct length. if sk_bytes.len() != SECRET_KEY_LEN { @@ -140,7 +214,7 @@ impl Keystore { let pk = PublicKey::from_secret_key(&sk); // Verify that the derived `PublicKey` matches `self`. - if pk.as_hex_string()[2..].to_string() != self.pubkey { + if pk.as_hex_string()[2..].to_string() != self.json.pubkey { return Err(Error::PublicKeyMismatch); } @@ -149,7 +223,7 @@ impl Keystore { /// Returns the UUID for the keystore. pub fn uuid(&self) -> &Uuid { - &self.uuid + &self.json.uuid } /// Returns `self` encoded as a JSON object. @@ -163,6 +237,71 @@ impl Keystore { } } +fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { + let mut hasher = Sha256::new(); + hasher.input(derived_key.checksum_slice()); + hasher.input(cipher_message); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { + // Generate derived key + match &kdf { + Kdf::Pbkdf2(params) => { + let mut dk = DerivedKey::zero(); + let mut mac = params.prf.mac(password.as_bytes()); + crypto::pbkdf2::pbkdf2( + &mut mac, + params.salt.as_bytes(), + params.c, + dk.as_mut_bytes(), + ); + dk + } + Kdf::Scrypt(params) => { + let mut dk = DerivedKey::zero(); + + // Assert that `n` is power of 2 + debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); + + crypto::scrypt::scrypt( + password.as_bytes(), + params.salt.as_bytes(), + &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), + dk.as_mut_bytes(), + ); + dk + } + } +} + +// TODO: what says IV _must_ be 4 bytes? +fn get_iv(bytes: &[u8]) -> Result<[u8; IV_SIZE], Error> { + if bytes.len() == IV_SIZE { + let mut iv = [0; IV_SIZE]; + iv.copy_from_slice(bytes); + Ok(iv) + } else { + Err(Error::IncorrectIvSize { + expected: IV_SIZE, + len: bytes.len(), + }) + } +} + +/// Compute floor of log2 of a u32. +fn log2_int(x: u32) -> u32 { + if x == 0 { + return 0; + } + 31 - x.leading_zeros() +} + +/* #[cfg(test)] mod tests { use super::*; @@ -837,3 +976,4 @@ mod tests { } } } +*/ diff --git a/eth2/utils/eth2_keystore/src/password.rs b/eth2/utils/eth2_keystore/src/password.rs new file mode 100644 index 00000000000..5624f416f9c --- /dev/null +++ b/eth2/utils/eth2_keystore/src/password.rs @@ -0,0 +1,28 @@ +use zeroize::Zeroize; + +#[derive(Zeroize, Clone, PartialEq)] +#[zeroize(drop)] +pub struct Password(String); + +impl Password { + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_str().as_bytes() + } +} + +impl From for Password { + fn from(s: String) -> Password { + Password(s) + } +} + +#[cfg(test)] +impl<'a> From<&'a str> for Password { + fn from(s: &'a str) -> Password { + Password::from(String::from(s)) + } +} diff --git a/eth2/utils/eth2_keystore/src/plain_text.rs b/eth2/utils/eth2_keystore/src/plain_text.rs new file mode 100644 index 00000000000..7d8a7c377fe --- /dev/null +++ b/eth2/utils/eth2_keystore/src/plain_text.rs @@ -0,0 +1,28 @@ +use zeroize::Zeroize; + +/// Provides wrapper around `Vec` that implements zeroize. +#[derive(Zeroize, Clone, PartialEq)] +#[zeroize(drop)] +pub struct PlainText(Vec); + +impl PlainText { + /// Instantiate self with `len` zeros. + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + /// The byte-length of `self` + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} From 0358ce3e6720e041034d32ec5538fba4ec072f18 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 14:54:30 +1000 Subject: [PATCH 037/118] Remove old code --- eth2/utils/eth2_keystore/src/checksum.rs | 78 -- eth2/utils/eth2_keystore/src/cipher.rs | 157 ---- eth2/utils/eth2_keystore/src/crypto.rs | 192 ----- eth2/utils/eth2_keystore/src/kdf.rs | 235 ------ eth2/utils/eth2_keystore/src/lib.rs | 741 +----------------- eth2/utils/eth2_keystore/src/password.rs | 1 - eth2/utils/eth2_keystore/src/plain_text.rs | 6 + .../eth2_keystore/tests/eip2335_vectors.rs | 86 ++ eth2/utils/eth2_keystore/tests/json.rs | 551 +++++++++++++ eth2/utils/eth2_keystore/tests/tests.rs | 47 ++ 10 files changed, 722 insertions(+), 1372 deletions(-) delete mode 100644 eth2/utils/eth2_keystore/src/checksum.rs delete mode 100644 eth2/utils/eth2_keystore/src/cipher.rs delete mode 100644 eth2/utils/eth2_keystore/src/crypto.rs delete mode 100644 eth2/utils/eth2_keystore/src/kdf.rs create mode 100644 eth2/utils/eth2_keystore/tests/eip2335_vectors.rs create mode 100644 eth2/utils/eth2_keystore/tests/json.rs create mode 100644 eth2/utils/eth2_keystore/tests/tests.rs diff --git a/eth2/utils/eth2_keystore/src/checksum.rs b/eth2/utils/eth2_keystore/src/checksum.rs deleted file mode 100644 index ccd6a5403c7..00000000000 --- a/eth2/utils/eth2_keystore/src/checksum.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::kdf::DerivedKey; -use crypto::digest::Digest; -use crypto::sha2::Sha256; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; -use std::convert::TryFrom; - -/// Used for ensuring that serde only decodes valid checksum functions. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] -pub enum ChecksumFunction { - Sha256, -} - -impl Into for ChecksumFunction { - fn into(self) -> String { - match self { - ChecksumFunction::Sha256 => "sha256".into(), - } - } -} - -impl TryFrom for ChecksumFunction { - type Error = String; - - fn try_from(s: String) -> Result { - match s.as_ref() { - "sha256" => Ok(ChecksumFunction::Sha256), - other => Err(format!("Unsupported checksum function: {}", other)), - } - } -} - -/// Used for ensuring serde only decode an empty map -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(try_from = "Value", into = "Value")] -pub struct EmptyMap; - -impl Into for EmptyMap { - fn into(self) -> Value { - Value::Object(Map::default()) - } -} - -impl TryFrom for EmptyMap { - type Error = &'static str; - - fn try_from(v: Value) -> Result { - match v { - Value::Object(map) if map.is_empty() => Ok(Self), - _ => Err("Checksum params must be an empty map"), - } - } -} - -/// Checksum module for `Keystore`. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct ChecksumModule { - pub function: ChecksumFunction, - pub params: EmptyMap, - pub message: String, -} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct Sha256Checksum(String); - -impl Sha256Checksum { - pub fn generate(derived_key: &DerivedKey, cipher_message: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.input(derived_key.checksum_slice()); - hasher.input(cipher_message); - hasher.result_str() - } - - pub fn function() -> ChecksumFunction { - ChecksumFunction::Sha256 - } -} diff --git a/eth2/utils/eth2_keystore/src/cipher.rs b/eth2/utils/eth2_keystore/src/cipher.rs deleted file mode 100644 index 2e80e6f7e1c..00000000000 --- a/eth2/utils/eth2_keystore/src/cipher.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crypto::aes::{ctr, KeySize}; -use rand::prelude::*; -use serde::{de, Deserialize, Serialize, Serializer}; -use std::convert::TryFrom; -use std::default::Default; -use zeroize::Zeroize; - -const IV_SIZE: usize = 16; - -/// Provides wrapper around `Vec` that implements zeroize. -#[derive(Zeroize, Clone, PartialEq)] -#[zeroize(drop)] -pub struct PlainText(Vec); - -impl PlainText { - /// Instantiate self with `len` zeros. - pub fn zero(len: usize) -> Self { - Self(vec![0; len]) - } - - /// The byte-length of `self` - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns a reference to the underlying bytes. - pub fn as_bytes(&self) -> &[u8] { - &self.0 - } - - /// Returns a mutable reference to the underlying bytes. - pub fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -/// Convert slice to fixed length array. -/// 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()]; - array.copy_from_slice(bytes); - Some(array) -} - -/// Used for ensuring that serde only decodes valid cipher functions. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] -pub enum CipherFunction { - Aes128Ctr, -} - -impl Into for CipherFunction { - fn into(self) -> String { - match self { - CipherFunction::Aes128Ctr => "aes-128-ctr".into(), - } - } -} - -impl TryFrom for CipherFunction { - type Error = String; - - fn try_from(s: String) -> Result { - match s.as_ref() { - "aes-128-ctr" => Ok(CipherFunction::Aes128Ctr), - other => Err(format!("Unsupported cipher function: {}", other)), - } - } -} - -/// Cipher module representation. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct CipherModule { - pub function: CipherFunction, - pub params: Cipher, - pub message: String, -} - -/// Parameters for AES128 with ctr mode. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Aes128Ctr { - #[serde(serialize_with = "serialize_iv")] - #[serde(deserialize_with = "deserialize_iv")] - pub iv: [u8; 16], -} - -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 - } - - pub fn decrypt(&self, key: &[u8], ct: &[u8]) -> PlainText { - // TODO: sanity checks - let mut pt = PlainText::zero(ct.len()); - ctr(KeySize::KeySize128, key, &self.iv).process(ct, &mut pt.as_mut_bytes()); - pt - } -} - -/// Serialize `iv` to its hex representation. -fn serialize_iv(x: &[u8], s: S) -> Result -where - S: Serializer, -{ - 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; 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") - } - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - let bytes = hex::decode(v).map_err(E::custom)?; - from_slice(&bytes).ok_or_else(|| E::custom(format!("IV should have length 16 bytes"))) - } - } - deserializer.deserialize_any(StringVisitor) -} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(untagged, deny_unknown_fields)] -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) -> CipherFunction { - match &self { - Cipher::Aes128Ctr(_) => CipherFunction::Aes128Ctr, - } - } -} diff --git a/eth2/utils/eth2_keystore/src/crypto.rs b/eth2/utils/eth2_keystore/src/crypto.rs deleted file mode 100644 index 4c1dfa15b90..00000000000 --- a/eth2/utils/eth2_keystore/src/crypto.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::checksum::{ChecksumModule, EmptyMap, Sha256Checksum}; -use crate::cipher::{Cipher, CipherModule, PlainText}; -use crate::kdf::{Kdf, KdfModule}; -use crate::Error; -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) - } -} - -#[cfg(test)] -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)] -pub struct Crypto { - pub kdf: KdfModule, - pub checksum: ChecksumModule, - pub cipher: CipherModule, -} - -impl Crypto { - /// Generate crypto module for `Keystore` given the password, - /// secret to encrypt, kdf params and cipher params. - 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.as_str()), - Kdf::Scrypt(scrypt) => scrypt.derive_key(password.as_str()), - }; - // Encrypt secret - let cipher_message: Vec = match &cipher { - Cipher::Aes128Ctr(cipher) => cipher.encrypt(derived_key.aes_key(), secret), - }; - - Crypto { - kdf: KdfModule { - function: kdf.function(), - params: kdf.clone(), - message: "".to_string(), - }, - checksum: ChecksumModule { - function: Sha256Checksum::function(), - params: EmptyMap, - message: Sha256Checksum::generate(&derived_key, &cipher_message), - }, - cipher: CipherModule { - function: cipher.function(), - 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: Password) -> Result { - let cipher_message = - hex::decode(self.cipher.message.clone()).map_err(Error::InvalidCipherMessageHex)?; - - // Generate derived key - let derived_key = match &self.kdf.params { - Kdf::Pbkdf2(pbkdf2) => pbkdf2.derive_key(password.as_str()), - Kdf::Scrypt(scrypt) => scrypt.derive_key(password.as_str()), - }; - - // Mismatching `password` indicates an invalid password. - if Sha256Checksum::generate(&derived_key, &cipher_message) != self.checksum.message { - return Err(Error::InvalidPassword); - } - - let secret = match &self.cipher.params { - Cipher::Aes128Ctr(cipher) => cipher.decrypt(&derived_key.aes_key(), &cipher_message), - }; - - Ok(secret) - } -} - -// Test cases taken from https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases -#[cfg(test)] -mod tests { - use super::*; - use crate::cipher::{Aes128Ctr, Cipher}; - use crate::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() { - let secret = - hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") - .unwrap(); - let expected_checksum = "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8"; - let expected_cipher = "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48"; - let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - .unwrap(); - let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); - let password: Password = "testpassword".into(); - - let kdf = Kdf::Pbkdf2(Pbkdf2 { - dklen: 32, - c: 262144, - prf: Prf::HmacSha256, - salt, - }); - - let cipher = Cipher::Aes128Ctr(Aes128Ctr { - iv: from_slice(&iv), - }); - - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); - - assert_eq!(expected_checksum, keystore.checksum.message); - assert_eq!(expected_cipher, keystore.cipher.message); - - let json = serde_json::to_string(&keystore).unwrap(); - - let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); - let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - - assert_eq!(secret, recovered_secret.as_bytes()); - } - - #[test] - fn test_scrypt() { - let secret = - hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") - .unwrap(); - let expected_checksum = "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb"; - let expected_cipher = "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30"; - let salt = hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - .unwrap(); - let iv = hex::decode("264daa3f303d7259501c93d997d84fe6").unwrap(); - let password: Password = "testpassword".into(); - - let kdf = Kdf::Scrypt(Scrypt { - dklen: 32, - n: 262144, - r: 8, - p: 1, - salt, - }); - - let cipher = Cipher::Aes128Ctr(Aes128Ctr { - iv: from_slice(&iv), - }); - - let keystore = Crypto::encrypt(password.clone(), &secret, kdf, cipher); - - assert_eq!(expected_checksum, keystore.checksum.message); - assert_eq!(expected_cipher, keystore.cipher.message); - - let json = serde_json::to_string(&keystore).unwrap(); - - let recovered_keystore: Crypto = serde_json::from_str(&json).unwrap(); - let recovered_secret = recovered_keystore.decrypt(password).unwrap(); - - assert_eq!(secret, recovered_secret.as_bytes()); - } -} diff --git a/eth2/utils/eth2_keystore/src/kdf.rs b/eth2/utils/eth2_keystore/src/kdf.rs deleted file mode 100644 index e4d8a7106f7..00000000000 --- a/eth2/utils/eth2_keystore/src/kdf.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crypto::sha2::Sha256; -use crypto::{hmac::Hmac, mac::Mac, pbkdf2, scrypt}; -use rand::prelude::*; -use serde::{de, Deserialize, Serialize, Serializer}; -use std::convert::TryFrom; -use std::default::Default; -use zeroize::Zeroize; - -// TODO: verify size of salt -const SALT_SIZE: usize = 32; -const DECRYPTION_KEY_SIZE: usize = 32; -const DKLEN: u32 = 32; - -#[derive(Zeroize)] -#[zeroize(drop)] -pub struct DerivedKey([u8; DECRYPTION_KEY_SIZE]); - -impl DerivedKey { - /// Instantiates `Self` with a all-zeros byte array. - fn zero() -> Self { - Self([0; DECRYPTION_KEY_SIZE]) - } - - /// Returns a mutable reference to the underlying byte array. - fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } - - /// Returns the `DK_slice` bytes used for checksum comparison. - /// - /// ## Reference - /// - /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure - pub fn checksum_slice(&self) -> &[u8] { - &self.0[16..32] - } - - /// Returns the aes-128-ctr key. - /// - /// Only the first 16 bytes of the decryption_key are used as the AES key. - /// - /// ## Reference - /// - /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#secret-decryption - pub fn aes_key(&self) -> &[u8] { - &self.0[0..16] - } -} - -/// Parameters for `pbkdf2` key derivation. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -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 { - fn default() -> Self { - let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); - Pbkdf2 { - dklen: DKLEN, - c: 262144, - prf: Prf::default(), - salt: salt.to_vec(), - } - } -} - -impl Pbkdf2 { - /// Derive key from password. - pub fn derive_key(&self, password: &str) -> DerivedKey { - let mut dk = DerivedKey::zero(); - let mut mac = self.prf.mac(password.as_bytes()); - pbkdf2::pbkdf2(&mut mac, &self.salt, self.c, dk.as_mut_bytes()); - dk - } -} - -/// Parameters for `scrypt` key derivation. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Scrypt { - pub dklen: u32, - pub n: u32, - pub r: u32, - pub p: u32, - #[serde(serialize_with = "serialize_salt")] - #[serde(deserialize_with = "deserialize_salt")] - pub salt: Vec, -} - -/// 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) -> DerivedKey { - let mut dk = DerivedKey::zero(); - // 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.0); - dk - } -} - -impl Default for Scrypt { - fn default() -> Self { - let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); - Scrypt { - dklen: DKLEN, - n: 262144, - r: 8, - p: 1, - salt: salt.to_vec(), - } - } -} - -/// Serialize `salt` to its hex representation. -fn serialize_salt(x: &Vec, s: S) -> Result -where - S: Serializer, -{ - 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>, -{ - 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 { - Scrypt(Scrypt), - Pbkdf2(Pbkdf2), -} - -impl Default for Kdf { - fn default() -> Self { - Kdf::Pbkdf2(Pbkdf2::default()) - } -} - -impl Kdf { - pub fn function(&self) -> KdfFunction { - match &self { - Kdf::Pbkdf2(_) => KdfFunction::Pbkdf2, - Kdf::Scrypt(_) => KdfFunction::Scrypt, - } - } -} - -/// Used for ensuring that serde only decodes valid KDF functions. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] -pub enum KdfFunction { - Scrypt, - Pbkdf2, -} - -impl Into for KdfFunction { - fn into(self) -> String { - match self { - KdfFunction::Scrypt => "scrypt".into(), - KdfFunction::Pbkdf2 => "pbkdf2".into(), - } - } -} - -impl TryFrom for KdfFunction { - type Error = String; - - fn try_from(s: String) -> Result { - match s.as_ref() { - "scrypt" => Ok(KdfFunction::Scrypt), - "pbkdf2" => Ok(KdfFunction::Pbkdf2), - other => Err(format!("Unsupported kdf function: {}", other)), - } - } -} - -/// KDF module representation. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct KdfModule { - pub function: KdfFunction, - pub params: Kdf, - pub message: String, -} - -/// PRF for use in `pbkdf2`. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub enum Prf { - #[serde(rename = "hmac-sha256")] - HmacSha256, -} - -impl Prf { - 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/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index e1ff41ff248..67200e72e29 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,9 @@ mod json_keystore; mod password; mod plain_text; +pub use password::Password; +pub use uuid::Uuid; + use bls::{Keypair, PublicKey, SecretKey}; use crypto::{digest::Digest, sha2::Sha256}; use derived_key::DerivedKey; @@ -11,12 +14,10 @@ use json_keystore::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, }; -use password::Password; use plain_text::PlainText; use rand::prelude::*; use serde::{Deserialize, Serialize}; use ssz::DecodeError; -use uuid::Uuid; /// The byte-length of a BLS secret key. const SECRET_KEY_LEN: usize = 32; @@ -95,6 +96,7 @@ impl<'a> KeystoreBuilder<'a> { /// /// Use `KeystoreBuilder` to create a new keystore. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Keystore { json: JsonKeystore, } @@ -112,15 +114,13 @@ impl Keystore { // Generate derived key let derived_key = derive_key(&password, &kdf); - // TODO: the keypair secret isn't zeroized. - let secret = keypair.sk.as_raw().as_bytes(); + let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); - // Encrypt secret + // Encrypt secret. let cipher_message: Vec = match &cipher { Cipher::Aes128Ctr(params) => { // TODO: sanity checks // TODO: check IV size - // cipher.encrypt(derived_key.aes_key(), &keypair.sk.as_raw().as_bytes()) let mut cipher_text = vec![0; secret.len()]; crypto::aes::ctr( @@ -128,32 +128,30 @@ impl Keystore { derived_key.aes_key(), &get_iv(params.iv.as_bytes())?, ) - .process(&secret, &mut cipher_text); + .process(secret.as_bytes(), &mut cipher_text); cipher_text } }; - let crypto = Crypto { - kdf: KdfModule { - function: kdf.function(), - params: kdf.clone(), - message: HexBytes::empty(), - }, - checksum: ChecksumModule { - function: Sha256Checksum::function(), - params: EmptyMap, - message: generate_checksum(&derived_key, &cipher_message), - }, - cipher: CipherModule { - function: cipher.function(), - params: cipher.clone(), - message: cipher_message.into(), - }, - }; - Ok(Keystore { json: JsonKeystore { - crypto, + crypto: Crypto { + kdf: KdfModule { + function: kdf.function(), + params: kdf.clone(), + message: HexBytes::empty(), + }, + checksum: ChecksumModule { + function: Sha256Checksum::function(), + params: EmptyMap, + message: generate_checksum(&derived_key, &cipher_message), + }, + cipher: CipherModule { + function: cipher.function(), + params: cipher.clone(), + message: cipher_message.into(), + }, + }, uuid, // TODO: Implement `path` according to // https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md @@ -237,6 +235,8 @@ impl Keystore { } } +/// Generates a checksum to indicate that the `derived_key` is associated with the +/// `cipher_message`. fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { let mut hasher = Sha256::new(); hasher.input(derived_key.checksum_slice()); @@ -248,23 +248,22 @@ fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexByte digest.into() } +/// Derive a private key from the given `password` using the given `kdf` (key derivation function). fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { - // Generate derived key + let mut dk = DerivedKey::zero(); + match &kdf { Kdf::Pbkdf2(params) => { - let mut dk = DerivedKey::zero(); let mut mac = params.prf.mac(password.as_bytes()); + crypto::pbkdf2::pbkdf2( &mut mac, params.salt.as_bytes(), params.c, dk.as_mut_bytes(), ); - dk } Kdf::Scrypt(params) => { - let mut dk = DerivedKey::zero(); - // Assert that `n` is power of 2 debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); @@ -274,9 +273,10 @@ fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), dk.as_mut_bytes(), ); - dk } } + + dk } // TODO: what says IV _must_ be 4 bytes? @@ -300,680 +300,3 @@ fn log2_int(x: u32) -> u32 { } 31 - x.leading_zeros() } - -/* -#[cfg(test)] -mod tests { - use super::*; - - fn password() -> Password { - "ilikecats".to_string().into() - } - - fn bad_password() -> Password { - "idontlikecats".to_string().into() - } - - #[test] - fn empty_password() { - assert_eq!( - KeystoreBuilder::new(&Keypair::random(), "".into()) - .err() - .unwrap(), - Error::EmptyPassword - ); - } - - #[test] - fn string_round_trip() { - let keypair = Keypair::random(); - - let keystore = KeystoreBuilder::new(&keypair, password()).unwrap().build(); - - let json = keystore.to_json_string().unwrap(); - let decoded = Keystore::from_json_str(&json).unwrap(); - - assert_eq!( - decoded.decrypt_keypair(bad_password()).err().unwrap(), - Error::InvalidPassword, - "should not decrypt with bad password" - ); - - assert_eq!( - decoded.decrypt_keypair(password()).unwrap(), - keypair, - "should decrypt with good password" - ); - } - - // Test cases taken from: - // - // https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases - #[test] - fn eip_2335_test_vectors() { - let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; - let password: Password = "testpassword".into(); - 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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - 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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "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 keypair = keystore.decrypt_keypair(password.clone()).unwrap(); - let expected_sk = hex::decode(expected_secret).unwrap(); - assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) - } - } - - #[test] - fn json_invalid_version() { - let 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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 5 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_bad_checksum() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cd" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - assert_eq!( - Keystore::from_json_str(&vector) - .unwrap() - .decrypt_keypair("testpassword".into()) - .err() - .unwrap(), - Error::InvalidPassword - ); - } - - #[test] - fn json_invalid_kdf_function() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "not-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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_missing_scrypt_param() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_additional_scrypt_param() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - "cats": 42 - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_checksum_function() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "not-sha256", - "params": {}, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_checksum_params() { - let vector = r#" - { - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": { - "cats": "lol" - }, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_cipher_function() { - let 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": "not-aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_additional_cipher_param() { - let 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", - "cat": 42 - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_missing_cipher_param() { - let 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": {}, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_missing_pubkey() { - let 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 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_missing_path() { - let 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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "version": 4 - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } - - #[test] - fn json_invalid_missing_version() { - let 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" - } - }, - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "path": "" - } - "#; - - match Keystore::from_json_str(&vector) { - Err(Error::InvalidJson(_)) => {} - _ => panic!("expected invalid json error"), - } - } -} -*/ diff --git a/eth2/utils/eth2_keystore/src/password.rs b/eth2/utils/eth2_keystore/src/password.rs index 5624f416f9c..b3fea49477e 100644 --- a/eth2/utils/eth2_keystore/src/password.rs +++ b/eth2/utils/eth2_keystore/src/password.rs @@ -20,7 +20,6 @@ impl From for Password { } } -#[cfg(test)] impl<'a> From<&'a str> for Password { fn from(s: &'a str) -> Password { Password::from(String::from(s)) diff --git a/eth2/utils/eth2_keystore/src/plain_text.rs b/eth2/utils/eth2_keystore/src/plain_text.rs index 7d8a7c377fe..eb8454d3630 100644 --- a/eth2/utils/eth2_keystore/src/plain_text.rs +++ b/eth2/utils/eth2_keystore/src/plain_text.rs @@ -26,3 +26,9 @@ impl PlainText { &mut self.0 } } + +impl From> for PlainText { + fn from(vec: Vec) -> Self { + Self(vec) + } +} diff --git a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs new file mode 100644 index 00000000000..cb70554890c --- /dev/null +++ b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs @@ -0,0 +1,86 @@ +#![cfg(test)] + +use eth2_keystore::{Keystore, Password}; + +// Test cases taken from: +// +// https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases +#[test] +fn eip2335_test_vectors() { + let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + let password: Password = "testpassword".into(); + 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "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 keypair = keystore.decrypt_keypair(password.clone()).unwrap(); + let expected_sk = hex::decode(expected_secret).unwrap(); + assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) + } +} diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs new file mode 100644 index 00000000000..8dd79c4b3d8 --- /dev/null +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -0,0 +1,551 @@ +#![cfg(test)] + +use eth2_keystore::{Error, Keystore}; + +#[test] +fn version() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 5 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn json_bad_checksum() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cd" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_eq!( + Keystore::from_json_str(&vector) + .unwrap() + .decrypt_keypair("testpassword".into()) + .err() + .unwrap(), + Error::InvalidPassword + ); +} + +#[test] +fn kdf_function() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "not-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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn missing_scrypt_param() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_scrypt_param() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "cats": 42 + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn checksum_function() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "not-sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn checksum_params() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": { + "cats": "lol" + }, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn cipher_function() { + let 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": "not-aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_cipher_param() { + let 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", + "cat": 42 + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn missing_cipher_param() { + let 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": {}, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn missing_pubkey() { + let 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 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn missing_path() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn missing_version() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "" + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} diff --git a/eth2/utils/eth2_keystore/tests/tests.rs b/eth2/utils/eth2_keystore/tests/tests.rs new file mode 100644 index 00000000000..ffe0e68fcbc --- /dev/null +++ b/eth2/utils/eth2_keystore/tests/tests.rs @@ -0,0 +1,47 @@ +#![cfg(test)] + +use bls::Keypair; +use eth2_keystore::{Error, Keystore, KeystoreBuilder, Password}; + +fn password() -> Password { + "ilikecats".to_string().into() +} + +fn bad_password() -> Password { + "idontlikecats".to_string().into() +} + +#[test] +fn empty_password() { + assert_eq!( + KeystoreBuilder::new(&Keypair::random(), "".into()) + .err() + .unwrap(), + Error::EmptyPassword + ); +} + +#[test] +fn string_round_trip() { + let keypair = Keypair::random(); + + let keystore = KeystoreBuilder::new(&keypair, password()) + .unwrap() + .build() + .unwrap(); + + let json = keystore.to_json_string().unwrap(); + let decoded = Keystore::from_json_str(&json).unwrap(); + + assert_eq!( + decoded.decrypt_keypair(bad_password()).err().unwrap(), + Error::InvalidPassword, + "should not decrypt with bad password" + ); + + assert_eq!( + decoded.decrypt_keypair(password()).unwrap(), + keypair, + "should decrypt with good password" + ); +} From 016068a58567c7e90e68f181cf0ba56810be47d8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 15:11:42 +1000 Subject: [PATCH 038/118] Add more tests, reader/writers --- eth2/utils/eth2_keystore/Cargo.toml | 4 +- eth2/utils/eth2_keystore/src/lib.rs | 32 +++++++++++----- eth2/utils/eth2_keystore/tests/tests.rs | 49 +++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index cf25ee7e575..917a3f63ba5 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -10,7 +10,6 @@ edition = "2018" rand = "0.7.2" rust-crypto = "0.2.36" uuid = { version = "0.8", features = ["serde", "v4"] } -time = "0.1.42" zeroize = { version = "1.0.0", features = ["zeroize_derive"] } serde = "1.0.102" serde_repr = "0.1" @@ -18,3 +17,6 @@ hex = "0.3" bls = { path = "../bls" } eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" + +[dev-dependencies] +tempfile = "3.1.0" diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 67200e72e29..b1f0087e7ea 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -9,7 +9,6 @@ pub use uuid::Uuid; use bls::{Keypair, PublicKey, SecretKey}; use crypto::{digest::Digest, sha2::Sha256}; use derived_key::DerivedKey; -use hex::FromHexError; use json_keystore::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, @@ -18,6 +17,7 @@ use plain_text::PlainText; use rand::prelude::*; use serde::{Deserialize, Serialize}; use ssz::DecodeError; +use std::io::{Read, Write}; /// The byte-length of a BLS secret key. const SECRET_KEY_LEN: usize = 32; @@ -33,13 +33,14 @@ const HASH_SIZE: usize = 32; #[derive(Debug, PartialEq)] pub enum Error { InvalidSecretKeyLen { len: usize, expected: usize }, - InvalidCipherMessageHex(FromHexError), InvalidPassword, InvalidSecretKeyBytes(DecodeError), PublicKeyMismatch, EmptyPassword, - UnableToSerialize, + UnableToSerialize(String), InvalidJson(String), + WriteError(String), + ReadError(String), IncorrectIvSize { expected: usize, len: usize }, } @@ -50,6 +51,7 @@ pub struct KeystoreBuilder<'a> { kdf: Kdf, cipher: Cipher, uuid: Uuid, + path: String, } impl<'a> KeystoreBuilder<'a> { @@ -58,7 +60,7 @@ impl<'a> KeystoreBuilder<'a> { /// ## Errors /// /// Returns `Error::EmptyPassword` if `password == ""`. - pub fn new(keypair: &'a Keypair, password: Password) -> Result { + pub fn new(keypair: &'a Keypair, password: Password, path: String) -> Result { if password.as_str() == "" { Err(Error::EmptyPassword) } else { @@ -76,6 +78,7 @@ impl<'a> KeystoreBuilder<'a> { }), cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), uuid: Uuid::new_v4(), + path, }) } } @@ -88,6 +91,7 @@ impl<'a> KeystoreBuilder<'a> { self.kdf, self.cipher, self.uuid, + self.path, ) } } @@ -110,6 +114,7 @@ impl Keystore { kdf: Kdf, cipher: Cipher, uuid: Uuid, + path: String, ) -> Result { // Generate derived key let derived_key = derive_key(&password, &kdf); @@ -153,10 +158,7 @@ impl Keystore { }, }, uuid, - // TODO: Implement `path` according to - // https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md - // For now, `path` is set to en empty string. - path: String::new(), + path, pubkey: keypair.pk.as_hex_string()[2..].to_string(), version: Version::four(), }, @@ -224,15 +226,25 @@ impl Keystore { &self.json.uuid } - /// Returns `self` encoded as a JSON object. + /// Encodes `self` as a JSON object. pub fn to_json_string(&self) -> Result { - serde_json::to_string(self).map_err(|_| Error::UnableToSerialize) + serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) } /// Returns `self` encoded as a JSON object. pub fn from_json_str(json_string: &str) -> Result { serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) } + + /// Encodes self as a JSON object to the given `writer`. + pub fn to_json_writer(&self, writer: W) -> Result<(), Error> { + serde_json::to_writer(writer, self).map_err(|e| Error::WriteError(format!("{}", e))) + } + + /// Instantiates `self` from a JSON `reader`. + pub fn from_json_reader(reader: R) -> Result { + serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) + } } /// Generates a checksum to indicate that the `derived_key` is associated with the diff --git a/eth2/utils/eth2_keystore/tests/tests.rs b/eth2/utils/eth2_keystore/tests/tests.rs index ffe0e68fcbc..e19b2f100d0 100644 --- a/eth2/utils/eth2_keystore/tests/tests.rs +++ b/eth2/utils/eth2_keystore/tests/tests.rs @@ -2,8 +2,10 @@ use bls::Keypair; use eth2_keystore::{Error, Keystore, KeystoreBuilder, Password}; +use std::fs::OpenOptions; +use tempfile::tempdir; -fn password() -> Password { +fn good_password() -> Password { "ilikecats".to_string().into() } @@ -14,7 +16,7 @@ fn bad_password() -> Password { #[test] fn empty_password() { assert_eq!( - KeystoreBuilder::new(&Keypair::random(), "".into()) + KeystoreBuilder::new(&Keypair::random(), "".into(), "".into()) .err() .unwrap(), Error::EmptyPassword @@ -25,7 +27,7 @@ fn empty_password() { fn string_round_trip() { let keypair = Keypair::random(); - let keystore = KeystoreBuilder::new(&keypair, password()) + let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) .unwrap() .build() .unwrap(); @@ -40,7 +42,46 @@ fn string_round_trip() { ); assert_eq!( - decoded.decrypt_keypair(password()).unwrap(), + decoded.decrypt_keypair(good_password()).unwrap(), + keypair, + "should decrypt with good password" + ); +} + +#[test] +fn file() { + let keypair = Keypair::random(); + let dir = tempdir().unwrap(); + let path = dir.path().join("keystore.json"); + + let get_file = || { + OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(path.clone()) + .expect("should create file") + }; + + let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) + .unwrap() + .build() + .unwrap(); + + keystore + .to_json_writer(&mut get_file()) + .expect("should write to file"); + + let decoded = Keystore::from_json_reader(&mut get_file()).expect("should read from file"); + + assert_eq!( + decoded.decrypt_keypair(bad_password()).err().unwrap(), + Error::InvalidPassword, + "should not decrypt with bad password" + ); + + assert_eq!( + decoded.decrypt_keypair(good_password()).unwrap(), keypair, "should decrypt with good password" ); From deea9d5ed613ed65321b9a92043625e119b58597 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 15:45:57 +1000 Subject: [PATCH 039/118] Tidy --- Cargo.lock | 2 +- eth2/utils/eth2_keystore/src/lib.rs | 70 ++++++++++++++--------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3a3bc0f9c7..e3220c27706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1205,7 +1205,7 @@ dependencies = [ "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", ] diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index b1f0087e7ea..9740355eed6 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -1,3 +1,6 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + mod derived_key; mod json_keystore; mod password; @@ -22,12 +25,24 @@ use std::io::{Read, Write}; /// The byte-length of a BLS secret key. const SECRET_KEY_LEN: usize = 32; /// The default byte length of the salt used to seed the KDF. +/// +/// NOTE: there is no clear guidance in EIP-2335 regarding the size of this salt. Neither +/// [pbkdf2](https://www.ietf.org/rfc/rfc2898.txt) or [scrypt](https://tools.ietf.org/html/rfc7914) +/// make a clear statement about what size it should be, however 32-bytes certainly seems +/// reasonable and larger than their examples. const SALT_SIZE: usize = 32; /// The length of the derived key. const DKLEN: u32 = 32; -// TODO: comment +/// Size of the IV (initialization vector) used for aes-128-ctr encryption of private key material. +/// +/// NOTE: the EIP-2335 test vectors use a 16-byte IV whilst RFC3868 uses an 8-byte IV. Reference: +/// +/// - https://tools.ietf.org/html/rfc3686 +/// - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 +/// +/// I (Paul H) have raised with this Carl B., the author of EIP2335 and await a response. const IV_SIZE: usize = 16; -// TODO: comment +/// The byte size of a SHA256 hash. const HASH_SIZE: usize = 32; #[derive(Debug, PartialEq)] @@ -116,25 +131,20 @@ impl Keystore { uuid: Uuid, path: String, ) -> Result { - // Generate derived key let derived_key = derive_key(&password, &kdf); let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); // Encrypt secret. - let cipher_message: Vec = match &cipher { + let mut cipher_text = vec![0; secret.len()]; + match &cipher { Cipher::Aes128Ctr(params) => { - // TODO: sanity checks - // TODO: check IV size - let mut cipher_text = vec![0; secret.len()]; - crypto::aes::ctr( crypto::aes::KeySize::KeySize128, derived_key.aes_key(), - &get_iv(params.iv.as_bytes())?, + params.iv.as_bytes(), ) .process(secret.as_bytes(), &mut cipher_text); - cipher_text } }; @@ -149,12 +159,12 @@ impl Keystore { checksum: ChecksumModule { function: Sha256Checksum::function(), params: EmptyMap, - message: generate_checksum(&derived_key, &cipher_message), + message: generate_checksum(&derived_key, &cipher_text), }, cipher: CipherModule { function: cipher.function(), params: cipher.clone(), - message: cipher_message.into(), + message: cipher_text.into(), }, }, uuid, @@ -184,31 +194,35 @@ impl Keystore { return Err(Error::InvalidPassword); } - let sk_bytes = match &self.json.crypto.cipher.params { + let mut plain_text = PlainText::zero(cipher_message.len()); + match &self.json.crypto.cipher.params { Cipher::Aes128Ctr(params) => { - // cipher.decrypt(&derived_key.aes_key(), &cipher_message) - let mut pt = PlainText::zero(cipher_message.len()); crypto::aes::ctr( crypto::aes::KeySize::KeySize128, derived_key.aes_key(), - &get_iv(¶ms.iv.as_bytes())?, + // NOTE: we do not check the size of the `iv` as there is no guidance about + // this on EIP-2335. + // + // Reference: + // + // - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 + params.iv.as_bytes(), ) - .process(cipher_message.as_bytes(), pt.as_mut_bytes()); - pt + .process(cipher_message.as_bytes(), plain_text.as_mut_bytes()); } }; // Verify that secret key material is correct length. - if sk_bytes.len() != SECRET_KEY_LEN { + if plain_text.len() != SECRET_KEY_LEN { return Err(Error::InvalidSecretKeyLen { - len: sk_bytes.len(), + len: plain_text.len(), expected: SECRET_KEY_LEN, }); } // Instantiate a `SecretKey`. let sk = - SecretKey::from_bytes(sk_bytes.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; + SecretKey::from_bytes(plain_text.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; // Derive a `PublicKey` from `SecretKey`. let pk = PublicKey::from_secret_key(&sk); @@ -291,20 +305,6 @@ fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { dk } -// TODO: what says IV _must_ be 4 bytes? -fn get_iv(bytes: &[u8]) -> Result<[u8; IV_SIZE], Error> { - if bytes.len() == IV_SIZE { - let mut iv = [0; IV_SIZE]; - iv.copy_from_slice(bytes); - Ok(iv) - } else { - Err(Error::IncorrectIvSize { - expected: IV_SIZE, - len: bytes.len(), - }) - } -} - /// Compute floor of log2 of a u32. fn log2_int(x: u32) -> u32 { if x == 0 { From b2ebb7cf71a3f5a52f76fedd3567b280b58b77f7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 15:54:05 +1000 Subject: [PATCH 040/118] Move keystore into own file --- eth2/utils/eth2_keystore/src/derived_key.rs | 2 +- .../eth2_keystore/src/json_keystore/mod.rs | 6 + eth2/utils/eth2_keystore/src/keystore.rs | 308 ++++++++++++++++++ eth2/utils/eth2_keystore/src/lib.rs | 306 +---------------- eth2/utils/eth2_keystore/tests/json.rs | 123 +++++++ 5 files changed, 440 insertions(+), 305 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/keystore.rs diff --git a/eth2/utils/eth2_keystore/src/derived_key.rs b/eth2/utils/eth2_keystore/src/derived_key.rs index 57f6e6f6330..9b396f09a9d 100644 --- a/eth2/utils/eth2_keystore/src/derived_key.rs +++ b/eth2/utils/eth2_keystore/src/derived_key.rs @@ -1,4 +1,4 @@ -use crate::DKLEN; +use crate::keystore::DKLEN; use zeroize::Zeroize; #[derive(Zeroize)] diff --git a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs index 01e0844744a..680f468c4a4 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs @@ -1,3 +1,9 @@ +//! This module intends to separate the JSON representation of the keystore from the actual crypto +//! logic. +//! +//! This module **MUST NOT** contain any logic beyond what is required to serialize/deserialize the +//! data structures. Specifically, there should not be any actual crypto logic in this file. + mod checksum_module; mod cipher_module; mod hex_bytes; diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs new file mode 100644 index 00000000000..e1090e4de03 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -0,0 +1,308 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + +use crate::derived_key::DerivedKey; +use crate::json_keystore::{ + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, + KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, +}; +use crate::plain_text::PlainText; +use crate::Password; +use crate::Uuid; +use bls::{Keypair, PublicKey, SecretKey}; +use crypto::{digest::Digest, sha2::Sha256}; +use rand::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz::DecodeError; +use std::io::{Read, Write}; + +/// The byte-length of a BLS secret key. +const SECRET_KEY_LEN: usize = 32; +/// The default byte length of the salt used to seed the KDF. +/// +/// NOTE: there is no clear guidance in EIP-2335 regarding the size of this salt. Neither +/// [pbkdf2](https://www.ietf.org/rfc/rfc2898.txt) or [scrypt](https://tools.ietf.org/html/rfc7914) +/// make a clear statement about what size it should be, however 32-bytes certainly seems +/// reasonable and larger than their examples. +const SALT_SIZE: usize = 32; +/// The length of the derived key. +pub const DKLEN: u32 = 32; +/// Size of the IV (initialization vector) used for aes-128-ctr encryption of private key material. +/// +/// NOTE: the EIP-2335 test vectors use a 16-byte IV whilst RFC3868 uses an 8-byte IV. Reference: +/// +/// - https://tools.ietf.org/html/rfc3686 +/// - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 +/// +/// I (Paul H) have raised with this Carl B., the author of EIP2335 and await a response. +const IV_SIZE: usize = 16; +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; + +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidSecretKeyLen { len: usize, expected: usize }, + InvalidPassword, + InvalidSecretKeyBytes(DecodeError), + PublicKeyMismatch, + EmptyPassword, + UnableToSerialize(String), + InvalidJson(String), + WriteError(String), + ReadError(String), + IncorrectIvSize { expected: usize, len: usize }, +} + +/// Constructs a `Keystore`. +pub struct KeystoreBuilder<'a> { + keypair: &'a Keypair, + password: Password, + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, + path: String, +} + +impl<'a> KeystoreBuilder<'a> { + /// Creates a new builder. + /// + /// ## Errors + /// + /// Returns `Error::EmptyPassword` if `password == ""`. + pub fn new(keypair: &'a Keypair, password: Password, path: String) -> Result { + if password.as_str() == "" { + Err(Error::EmptyPassword) + } else { + let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); + let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); + + Ok(Self { + keypair, + password, + kdf: Kdf::Pbkdf2(Pbkdf2 { + dklen: DKLEN, + c: 262144, + prf: Prf::default(), + salt: salt.to_vec().into(), + }), + cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), + uuid: Uuid::new_v4(), + path, + }) + } + } + + /// Consumes `self`, returning a `Keystore`. + pub fn build(self) -> Result { + Keystore::encrypt( + self.keypair, + self.password, + self.kdf, + self.cipher, + self.uuid, + self.path, + ) + } +} + +/// Provides a BLS keystore as defined in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +/// +/// Use `KeystoreBuilder` to create a new keystore. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Keystore { + json: JsonKeystore, +} + +impl Keystore { + /// Generate `Keystore` object for a BLS12-381 secret key from a + /// keypair and password. + fn encrypt( + keypair: &Keypair, + password: Password, + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, + path: String, + ) -> Result { + let derived_key = derive_key(&password, &kdf); + + let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); + + // Encrypt secret. + let mut cipher_text = vec![0; secret.len()]; + match &cipher { + Cipher::Aes128Ctr(params) => { + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + derived_key.aes_key(), + params.iv.as_bytes(), + ) + .process(secret.as_bytes(), &mut cipher_text); + } + }; + + Ok(Keystore { + json: JsonKeystore { + crypto: Crypto { + kdf: KdfModule { + function: kdf.function(), + params: kdf.clone(), + message: HexBytes::empty(), + }, + checksum: ChecksumModule { + function: Sha256Checksum::function(), + params: EmptyMap, + message: generate_checksum(&derived_key, &cipher_text), + }, + cipher: CipherModule { + function: cipher.function(), + params: cipher.clone(), + message: cipher_text.into(), + }, + }, + uuid, + path, + pubkey: keypair.pk.as_hex_string()[2..].to_string(), + version: Version::four(), + }, + }) + } + + /// Regenerate a BLS12-381 `Keypair` from `self` and the correct password. + /// + /// ## Errors + /// + /// - The provided password is incorrect. + /// - The keystore is badly formed. + pub fn decrypt_keypair(&self, password: Password) -> Result { + let cipher_message = &self.json.crypto.cipher.message; + + // Generate derived key + let derived_key = derive_key(&password, &self.json.crypto.kdf.params); + + // Mismatching checksum indicates an invalid password. + if generate_checksum(&derived_key, cipher_message.as_bytes()) + != self.json.crypto.checksum.message + { + return Err(Error::InvalidPassword); + } + + let mut plain_text = PlainText::zero(cipher_message.len()); + match &self.json.crypto.cipher.params { + Cipher::Aes128Ctr(params) => { + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + derived_key.aes_key(), + // NOTE: we do not check the size of the `iv` as there is no guidance about + // this on EIP-2335. + // + // Reference: + // + // - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 + params.iv.as_bytes(), + ) + .process(cipher_message.as_bytes(), plain_text.as_mut_bytes()); + } + }; + + // Verify that secret key material is correct length. + if plain_text.len() != SECRET_KEY_LEN { + return Err(Error::InvalidSecretKeyLen { + len: plain_text.len(), + expected: SECRET_KEY_LEN, + }); + } + + // Instantiate a `SecretKey`. + let sk = + SecretKey::from_bytes(plain_text.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; + + // Derive a `PublicKey` from `SecretKey`. + let pk = PublicKey::from_secret_key(&sk); + + // Verify that the derived `PublicKey` matches `self`. + if pk.as_hex_string()[2..].to_string() != self.json.pubkey { + return Err(Error::PublicKeyMismatch); + } + + Ok(Keypair { sk, pk }) + } + + /// Returns the UUID for the keystore. + pub fn uuid(&self) -> &Uuid { + &self.json.uuid + } + + /// Encodes `self` as a JSON object. + pub fn to_json_string(&self) -> Result { + serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) + } + + /// Returns `self` encoded as a JSON object. + pub fn from_json_str(json_string: &str) -> Result { + serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) + } + + /// Encodes self as a JSON object to the given `writer`. + pub fn to_json_writer(&self, writer: W) -> Result<(), Error> { + serde_json::to_writer(writer, self).map_err(|e| Error::WriteError(format!("{}", e))) + } + + /// Instantiates `self` from a JSON `reader`. + pub fn from_json_reader(reader: R) -> Result { + serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) + } +} + +/// Generates a checksum to indicate that the `derived_key` is associated with the +/// `cipher_message`. +fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { + let mut hasher = Sha256::new(); + hasher.input(derived_key.checksum_slice()); + hasher.input(cipher_message); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +/// Derive a private key from the given `password` using the given `kdf` (key derivation function). +fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { + let mut dk = DerivedKey::zero(); + + match &kdf { + Kdf::Pbkdf2(params) => { + let mut mac = params.prf.mac(password.as_bytes()); + + crypto::pbkdf2::pbkdf2( + &mut mac, + params.salt.as_bytes(), + params.c, + dk.as_mut_bytes(), + ); + } + Kdf::Scrypt(params) => { + // Assert that `n` is power of 2 + debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); + + crypto::scrypt::scrypt( + password.as_bytes(), + params.salt.as_bytes(), + &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), + dk.as_mut_bytes(), + ); + } + } + + dk +} + +/// Compute floor of log2 of a u32. +fn log2_int(x: u32) -> u32 { + if x == 0 { + return 0; + } + 31 - x.leading_zeros() +} diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 9740355eed6..e907a656077 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,312 +3,10 @@ mod derived_key; mod json_keystore; +mod keystore; mod password; mod plain_text; +pub use keystore::{Error, Keystore, KeystoreBuilder}; pub use password::Password; pub use uuid::Uuid; - -use bls::{Keypair, PublicKey, SecretKey}; -use crypto::{digest::Digest, sha2::Sha256}; -use derived_key::DerivedKey; -use json_keystore::{ - Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, - KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, -}; -use plain_text::PlainText; -use rand::prelude::*; -use serde::{Deserialize, Serialize}; -use ssz::DecodeError; -use std::io::{Read, Write}; - -/// The byte-length of a BLS secret key. -const SECRET_KEY_LEN: usize = 32; -/// The default byte length of the salt used to seed the KDF. -/// -/// NOTE: there is no clear guidance in EIP-2335 regarding the size of this salt. Neither -/// [pbkdf2](https://www.ietf.org/rfc/rfc2898.txt) or [scrypt](https://tools.ietf.org/html/rfc7914) -/// make a clear statement about what size it should be, however 32-bytes certainly seems -/// reasonable and larger than their examples. -const SALT_SIZE: usize = 32; -/// The length of the derived key. -const DKLEN: u32 = 32; -/// Size of the IV (initialization vector) used for aes-128-ctr encryption of private key material. -/// -/// NOTE: the EIP-2335 test vectors use a 16-byte IV whilst RFC3868 uses an 8-byte IV. Reference: -/// -/// - https://tools.ietf.org/html/rfc3686 -/// - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 -/// -/// I (Paul H) have raised with this Carl B., the author of EIP2335 and await a response. -const IV_SIZE: usize = 16; -/// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; - -#[derive(Debug, PartialEq)] -pub enum Error { - InvalidSecretKeyLen { len: usize, expected: usize }, - InvalidPassword, - InvalidSecretKeyBytes(DecodeError), - PublicKeyMismatch, - EmptyPassword, - UnableToSerialize(String), - InvalidJson(String), - WriteError(String), - ReadError(String), - IncorrectIvSize { expected: usize, len: usize }, -} - -/// Constructs a `Keystore`. -pub struct KeystoreBuilder<'a> { - keypair: &'a Keypair, - password: Password, - kdf: Kdf, - cipher: Cipher, - uuid: Uuid, - path: String, -} - -impl<'a> KeystoreBuilder<'a> { - /// Creates a new builder. - /// - /// ## Errors - /// - /// Returns `Error::EmptyPassword` if `password == ""`. - pub fn new(keypair: &'a Keypair, password: Password, path: String) -> Result { - if password.as_str() == "" { - Err(Error::EmptyPassword) - } else { - let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); - let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); - - Ok(Self { - keypair, - password, - kdf: Kdf::Pbkdf2(Pbkdf2 { - dklen: DKLEN, - c: 262144, - prf: Prf::default(), - salt: salt.to_vec().into(), - }), - cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), - uuid: Uuid::new_v4(), - path, - }) - } - } - - /// Consumes `self`, returning a `Keystore`. - pub fn build(self) -> Result { - Keystore::encrypt( - self.keypair, - self.password, - self.kdf, - self.cipher, - self.uuid, - self.path, - ) - } -} - -/// Provides a BLS keystore as defined in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). -/// -/// Use `KeystoreBuilder` to create a new keystore. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Keystore { - json: JsonKeystore, -} - -impl Keystore { - /// Generate `Keystore` object for a BLS12-381 secret key from a - /// keypair and password. - fn encrypt( - keypair: &Keypair, - password: Password, - kdf: Kdf, - cipher: Cipher, - uuid: Uuid, - path: String, - ) -> Result { - let derived_key = derive_key(&password, &kdf); - - let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); - - // Encrypt secret. - let mut cipher_text = vec![0; secret.len()]; - match &cipher { - Cipher::Aes128Ctr(params) => { - crypto::aes::ctr( - crypto::aes::KeySize::KeySize128, - derived_key.aes_key(), - params.iv.as_bytes(), - ) - .process(secret.as_bytes(), &mut cipher_text); - } - }; - - Ok(Keystore { - json: JsonKeystore { - crypto: Crypto { - kdf: KdfModule { - function: kdf.function(), - params: kdf.clone(), - message: HexBytes::empty(), - }, - checksum: ChecksumModule { - function: Sha256Checksum::function(), - params: EmptyMap, - message: generate_checksum(&derived_key, &cipher_text), - }, - cipher: CipherModule { - function: cipher.function(), - params: cipher.clone(), - message: cipher_text.into(), - }, - }, - uuid, - path, - pubkey: keypair.pk.as_hex_string()[2..].to_string(), - version: Version::four(), - }, - }) - } - - /// Regenerate a BLS12-381 `Keypair` from `self` and the correct password. - /// - /// ## Errors - /// - /// - The provided password is incorrect. - /// - The keystore is badly formed. - pub fn decrypt_keypair(&self, password: Password) -> Result { - let cipher_message = &self.json.crypto.cipher.message; - - // Generate derived key - let derived_key = derive_key(&password, &self.json.crypto.kdf.params); - - // Mismatching checksum indicates an invalid password. - if generate_checksum(&derived_key, cipher_message.as_bytes()) - != self.json.crypto.checksum.message - { - return Err(Error::InvalidPassword); - } - - let mut plain_text = PlainText::zero(cipher_message.len()); - match &self.json.crypto.cipher.params { - Cipher::Aes128Ctr(params) => { - crypto::aes::ctr( - crypto::aes::KeySize::KeySize128, - derived_key.aes_key(), - // NOTE: we do not check the size of the `iv` as there is no guidance about - // this on EIP-2335. - // - // Reference: - // - // - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 - params.iv.as_bytes(), - ) - .process(cipher_message.as_bytes(), plain_text.as_mut_bytes()); - } - }; - - // Verify that secret key material is correct length. - if plain_text.len() != SECRET_KEY_LEN { - return Err(Error::InvalidSecretKeyLen { - len: plain_text.len(), - expected: SECRET_KEY_LEN, - }); - } - - // Instantiate a `SecretKey`. - let sk = - SecretKey::from_bytes(plain_text.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; - - // Derive a `PublicKey` from `SecretKey`. - let pk = PublicKey::from_secret_key(&sk); - - // Verify that the derived `PublicKey` matches `self`. - if pk.as_hex_string()[2..].to_string() != self.json.pubkey { - return Err(Error::PublicKeyMismatch); - } - - Ok(Keypair { sk, pk }) - } - - /// Returns the UUID for the keystore. - pub fn uuid(&self) -> &Uuid { - &self.json.uuid - } - - /// Encodes `self` as a JSON object. - pub fn to_json_string(&self) -> Result { - serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) - } - - /// Returns `self` encoded as a JSON object. - pub fn from_json_str(json_string: &str) -> Result { - serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) - } - - /// Encodes self as a JSON object to the given `writer`. - pub fn to_json_writer(&self, writer: W) -> Result<(), Error> { - serde_json::to_writer(writer, self).map_err(|e| Error::WriteError(format!("{}", e))) - } - - /// Instantiates `self` from a JSON `reader`. - pub fn from_json_reader(reader: R) -> Result { - serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) - } -} - -/// Generates a checksum to indicate that the `derived_key` is associated with the -/// `cipher_message`. -fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { - let mut hasher = Sha256::new(); - hasher.input(derived_key.checksum_slice()); - hasher.input(cipher_message); - - let mut digest = vec![0; HASH_SIZE]; - hasher.result(&mut digest); - - digest.into() -} - -/// Derive a private key from the given `password` using the given `kdf` (key derivation function). -fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { - let mut dk = DerivedKey::zero(); - - match &kdf { - Kdf::Pbkdf2(params) => { - let mut mac = params.prf.mac(password.as_bytes()); - - crypto::pbkdf2::pbkdf2( - &mut mac, - params.salt.as_bytes(), - params.c, - dk.as_mut_bytes(), - ); - } - Kdf::Scrypt(params) => { - // Assert that `n` is power of 2 - debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); - - crypto::scrypt::scrypt( - password.as_bytes(), - params.salt.as_bytes(), - &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), - dk.as_mut_bytes(), - ); - } - } - - dk -} - -/// Compute floor of log2 of a u32. -fn log2_int(x: u32) -> u32 { - if x == 0 { - return 0; - } - 31 - x.leading_zeros() -} diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs index 8dd79c4b3d8..d21a9310773 100644 --- a/eth2/utils/eth2_keystore/tests/json.rs +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -549,3 +549,126 @@ fn missing_version() { _ => panic!("expected invalid json error"), } } + +#[test] +fn pbkdf2_bad_hmac() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "bad-hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn pbkdf2_additional_parameter() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "cats": 42 + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn pbkdf2_missing_parameter() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} From 5b48f444040fb4f6de72491e87f3b3af8558422a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 15:58:39 +1000 Subject: [PATCH 041/118] Move more logic into keystore file --- eth2/utils/eth2_keystore/src/derived_key.rs | 23 ++++----------------- eth2/utils/eth2_keystore/src/keystore.rs | 6 +++--- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/derived_key.rs b/eth2/utils/eth2_keystore/src/derived_key.rs index 9b396f09a9d..9ebb723bfff 100644 --- a/eth2/utils/eth2_keystore/src/derived_key.rs +++ b/eth2/utils/eth2_keystore/src/derived_key.rs @@ -6,7 +6,7 @@ use zeroize::Zeroize; pub struct DerivedKey([u8; DKLEN as usize]); impl DerivedKey { - /// Instantiates `Self` with a all-zeros byte array. + /// Instantiates `Self` with an all-zeros byte array. pub fn zero() -> Self { Self([0; DKLEN as usize]) } @@ -16,23 +16,8 @@ impl DerivedKey { &mut self.0 } - /// Returns the `DK_slice` bytes used for checksum comparison. - /// - /// ## Reference - /// - /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#procedure - pub fn checksum_slice(&self) -> &[u8] { - &self.0[16..32] - } - - /// Returns the aes-128-ctr key. - /// - /// Only the first 16 bytes of the decryption_key are used as the AES key. - /// - /// ## Reference - /// - /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2335.md#secret-decryption - pub fn aes_key(&self) -> &[u8] { - &self.0[0..16] + /// Returns a reference to the underlying byte array. + pub fn as_bytes(&self) -> &[u8] { + &self.0 } } diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index e1090e4de03..1b835f7c4ec 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -135,7 +135,7 @@ impl Keystore { Cipher::Aes128Ctr(params) => { crypto::aes::ctr( crypto::aes::KeySize::KeySize128, - derived_key.aes_key(), + &derived_key.as_bytes()[0..16], params.iv.as_bytes(), ) .process(secret.as_bytes(), &mut cipher_text); @@ -193,7 +193,7 @@ impl Keystore { Cipher::Aes128Ctr(params) => { crypto::aes::ctr( crypto::aes::KeySize::KeySize128, - derived_key.aes_key(), + &derived_key.as_bytes()[0..16], // NOTE: we do not check the size of the `iv` as there is no guidance about // this on EIP-2335. // @@ -259,7 +259,7 @@ impl Keystore { /// `cipher_message`. fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { let mut hasher = Sha256::new(); - hasher.input(derived_key.checksum_slice()); + hasher.input(&derived_key.as_bytes()[16..32]); hasher.input(cipher_message); let mut digest = vec![0; HASH_SIZE]; From 646b8f9a089526eee72749dff1ad27fa5b1db373 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 16:06:39 +1000 Subject: [PATCH 042/118] Tidy --- eth2/utils/eth2_keystore/src/derived_key.rs | 1 + eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs | 2 +- eth2/utils/eth2_keystore/src/password.rs | 3 +++ eth2/utils/eth2_keystore/src/plain_text.rs | 2 +- validator_client/Cargo.toml | 1 - 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/derived_key.rs b/eth2/utils/eth2_keystore/src/derived_key.rs index 9ebb723bfff..c61d9329297 100644 --- a/eth2/utils/eth2_keystore/src/derived_key.rs +++ b/eth2/utils/eth2_keystore/src/derived_key.rs @@ -1,6 +1,7 @@ use crate::keystore::DKLEN; use zeroize::Zeroize; +/// Provides wrapper around `[u8; DKLEN]` that implements `Zeroize`. #[derive(Zeroize)] #[zeroize(drop)] pub struct DerivedKey([u8; DKLEN as usize]); diff --git a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs index e05882a6588..20e2c82990e 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use std::convert::TryFrom; -/// Used for ensuring that serde only decodes valid checksum functions. +/// To allow serde to encode/decode byte arrays from HEX ASCII strings. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub struct HexBytes(Vec); diff --git a/eth2/utils/eth2_keystore/src/password.rs b/eth2/utils/eth2_keystore/src/password.rs index b3fea49477e..d616d5054b3 100644 --- a/eth2/utils/eth2_keystore/src/password.rs +++ b/eth2/utils/eth2_keystore/src/password.rs @@ -1,14 +1,17 @@ use zeroize::Zeroize; +/// Provides a wrapper around `String` that implements `Zeroize`. #[derive(Zeroize, Clone, PartialEq)] #[zeroize(drop)] pub struct Password(String); impl Password { + /// Returns a reference to the underlying `String`. pub fn as_str(&self) -> &str { self.0.as_str() } + /// Returns a reference to the underlying `String`, as bytes. pub fn as_bytes(&self) -> &[u8] { self.0.as_str().as_bytes() } diff --git a/eth2/utils/eth2_keystore/src/plain_text.rs b/eth2/utils/eth2_keystore/src/plain_text.rs index eb8454d3630..a643cdfb1fa 100644 --- a/eth2/utils/eth2_keystore/src/plain_text.rs +++ b/eth2/utils/eth2_keystore/src/plain_text.rs @@ -1,6 +1,6 @@ use zeroize::Zeroize; -/// Provides wrapper around `Vec` that implements zeroize. +/// Provides wrapper around `Vec` that implements `Zeroize`. #[derive(Zeroize, Clone, PartialEq)] #[zeroize(drop)] pub struct PlainText(Vec); diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 22f01b2eef3..68dfc906d07 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -20,7 +20,6 @@ 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" From 4b28c01a062835b11fb52503362d1adb1c0aba66 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 16:11:05 +1000 Subject: [PATCH 043/118] Tidy --- Cargo.lock | 1 - eth2/utils/eth2_keystore/src/keystore.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3220c27706..dcf830ef2dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4802,7 +4802,6 @@ dependencies = [ "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (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.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "slog-term 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index 1b835f7c4ec..deb5990db3a 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -147,7 +147,7 @@ impl Keystore { crypto: Crypto { kdf: KdfModule { function: kdf.function(), - params: kdf.clone(), + params: kdf, message: HexBytes::empty(), }, checksum: ChecksumModule { @@ -157,7 +157,7 @@ impl Keystore { }, cipher: CipherModule { function: cipher.function(), - params: cipher.clone(), + params: cipher, message: cipher_text.into(), }, }, From d1f313596478b22688d6fadfd6e228d31a5f97e8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 10:27:32 +1000 Subject: [PATCH 044/118] Allow for odd-character hex --- .../src/json_keystore/hex_bytes.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs index 20e2c82990e..dfa03d84885 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs @@ -36,8 +36,41 @@ impl TryFrom for HexBytes { type Error = String; fn try_from(s: String) -> Result { + // Left-pad with a zero if there is not an even number of hex digits to ensure + // `hex::decode` doesn't return an error. + let s = if s.len() % 2 != 0 { + format!("0{}", s) + } else { + s + }; + hex::decode(s) .map(Self) .map_err(|e| format!("Invalid hex: {}", e)) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn decode(json: &str) -> Vec { + serde_json::from_str::(&format!("\"{}\"", json)) + .expect("should decode json") + .as_bytes() + .to_vec() + } + + #[test] + fn odd_hex_bytes() { + let empty: Vec = vec![]; + + assert_eq!(decode(""), empty, "should decode nothing"); + assert_eq!(decode("00"), vec![0], "should decode 00"); + assert_eq!(decode("0"), vec![0], "should decode 0"); + assert_eq!(decode("01"), vec![1], "should decode 01"); + assert_eq!(decode("1"), vec![1], "should decode 1"); + assert_eq!(decode("0101"), vec![1, 1], "should decode 0101"); + assert_eq!(decode("101"), vec![1, 1], "should decode 101"); + } +} From 03b5e69f982ecaa76dcd429cf05bbffde89b8f3d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 11:33:56 +1000 Subject: [PATCH 045/118] Add more json missing field checks --- .../src/json_keystore/checksum_module.rs | 1 + .../src/json_keystore/cipher_module.rs | 1 + .../src/json_keystore/kdf_module.rs | 1 + .../eth2_keystore/src/json_keystore/mod.rs | 2 + eth2/utils/eth2_keystore/tests/json.rs | 259 +++++++++++++++++- 5 files changed, 263 insertions(+), 1 deletion(-) diff --git a/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs index 521dc2dc606..c0a3f4d2a4a 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs @@ -58,6 +58,7 @@ impl TryFrom for EmptyMap { /// Checksum module for `Keystore`. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ChecksumModule { pub function: ChecksumFunction, pub params: EmptyMap, diff --git a/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs index 6af5db2a895..5300b2f8b28 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs @@ -35,6 +35,7 @@ impl TryFrom for CipherFunction { /// Cipher module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct CipherModule { pub function: CipherFunction, pub params: Cipher, diff --git a/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs index 79c6e2a243a..80430ecf35a 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs @@ -11,6 +11,7 @@ use std::convert::TryFrom; /// KDF module representation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct KdfModule { pub function: KdfFunction, pub params: Kdf, diff --git a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs index 680f468c4a4..5e884fa9835 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs @@ -20,6 +20,7 @@ use serde_repr::*; /// JSON representation of [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335) keystore. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct JsonKeystore { pub crypto: Crypto, pub uuid: Uuid, @@ -43,6 +44,7 @@ impl Version { /// Crypto module for keystore. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Crypto { pub kdf: KdfModule, pub checksum: ChecksumModule, diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs index d21a9310773..cb38589c79b 100644 --- a/eth2/utils/eth2_keystore/tests/json.rs +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -2,8 +2,265 @@ use eth2_keystore::{Error, Keystore}; +/// A valid keystore we can mutate to ensure our JSON encoding is strict. +/// +/// If this test doesn't pass then it all previous tests are unreliable. #[test] -fn version() { +fn reference_scrypt() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert!(Keystore::from_json_str(&vector).is_ok()); +} + +#[test] +fn additional_top_level_key() { + let vector = r#" + { + "cats": 42, + "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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_cipher_key() { + let 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": { + "cats": 42, + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_checksum_key() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "cats": 42, + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_kdf_key() { + let vector = r#" + { + "crypto": { + "kdf": { + "cats": 42, + "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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn additional_crypto_key() { + let vector = r#" + { + "crypto": { + "cats": 42, + "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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + +#[test] +fn bad_version() { let vector = r#" { "crypto": { From d2c43a6105d11d0db8ad995d5e76f3a728844281 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 11:46:48 +1000 Subject: [PATCH 046/118] Use scrypt by default --- eth2/utils/eth2_keystore/src/json_keystore/mod.rs | 2 +- eth2/utils/eth2_keystore/src/keystore.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs index 5e884fa9835..0ebfad01f7e 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs @@ -12,7 +12,7 @@ mod kdf_module; pub use checksum_module::{ChecksumModule, EmptyMap, Sha256Checksum}; pub use cipher_module::{Aes128Ctr, Cipher, CipherModule}; pub use hex_bytes::HexBytes; -pub use kdf_module::{Kdf, KdfModule, Pbkdf2, Prf}; +pub use kdf_module::{Kdf, KdfModule, Pbkdf2, Prf, Scrypt}; pub use uuid::Uuid; use serde::{Deserialize, Serialize}; diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index deb5990db3a..57e15e38b90 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -4,7 +4,7 @@ use crate::derived_key::DerivedKey; use crate::json_keystore::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, - KdfModule, Pbkdf2, Prf, Sha256Checksum, Version, + KdfModule, Scrypt, Sha256Checksum, Version, }; use crate::plain_text::PlainText; use crate::Password; @@ -79,10 +79,12 @@ impl<'a> KeystoreBuilder<'a> { Ok(Self { keypair, password, - kdf: Kdf::Pbkdf2(Pbkdf2 { + // Using scrypt as the default algorithm due to its memory hardness properties. + kdf: Kdf::Scrypt(Scrypt { dklen: DKLEN, - c: 262144, - prf: Prf::default(), + n: 262144, + p: 1, + r: 8, salt: salt.to_vec().into(), }), cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), From b137e8206b79f189863d60b201d183cae296580a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 11:47:03 +1000 Subject: [PATCH 047/118] Tidy, address comments --- .../src/json_keystore/checksum_module.rs | 2 +- eth2/utils/eth2_keystore/src/keystore.rs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs index c0a3f4d2a4a..bbcc418185d 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/checksum_module.rs @@ -34,7 +34,7 @@ impl TryFrom for ChecksumFunction { } } -/// Used for ensuring serde only decode an empty map +/// Used for ensuring serde only decodes an empty map. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(try_from = "Value", into = "Value")] pub struct EmptyMap; diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index 57e15e38b90..4a7b900f82e 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -23,7 +23,7 @@ const SECRET_KEY_LEN: usize = 32; /// NOTE: there is no clear guidance in EIP-2335 regarding the size of this salt. Neither /// [pbkdf2](https://www.ietf.org/rfc/rfc2898.txt) or [scrypt](https://tools.ietf.org/html/rfc7914) /// make a clear statement about what size it should be, however 32-bytes certainly seems -/// reasonable and larger than their examples. +/// reasonable and larger than the EITF examples. const SALT_SIZE: usize = 32; /// The length of the derived key. pub const DKLEN: u32 = 32; @@ -34,7 +34,14 @@ pub const DKLEN: u32 = 32; /// - https://tools.ietf.org/html/rfc3686 /// - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 /// -/// I (Paul H) have raised with this Carl B., the author of EIP2335 and await a response. +/// Comment from Carl B, author of EIP-2335: +/// +/// AES CTR IV's should be the same length as the internal blocks in my understanding. (The IV is +/// the first block input.) +/// +/// As far as I know, AES-128-CTR is not defined by the IETF, but by NIST in SP800-38A. +/// (https://csrc.nist.gov/publications/detail/sp/800-38a/final) The test vectors in this standard +/// are 16 bytes. const IV_SIZE: usize = 16; /// The byte size of a SHA256 hash. const HASH_SIZE: usize = 32; @@ -66,6 +73,8 @@ pub struct KeystoreBuilder<'a> { impl<'a> KeystoreBuilder<'a> { /// Creates a new builder. /// + /// Generates the KDF `salt` and AES `IV` using `rand::thread_rng()`. + /// /// ## Errors /// /// Returns `Error::EmptyPassword` if `password == ""`. @@ -241,7 +250,7 @@ impl Keystore { serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) } - /// Returns `self` encoded as a JSON object. + /// Returns `self` from an encoded JSON object. pub fn from_json_str(json_string: &str) -> Result { serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) } From 6a14a5161f2ef55f4f59d1f3ab1b9357d7d7fdca Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 12:15:53 +1000 Subject: [PATCH 048/118] Test path and uuid in vectors --- eth2/utils/eth2_keystore/src/keystore.rs | 7 +++ .../eth2_keystore/tests/eip2335_vectors.rs | 53 +++++++++++++------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index 4a7b900f82e..da78c3720b0 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -245,6 +245,13 @@ impl Keystore { &self.json.uuid } + /// Returns the path for the keystore. + /// + /// Note: the path is not validated, it is simply whatever string the keystore provided. + pub fn path(&self) -> &str { + &self.json.path + } + /// Encodes `self` as a JSON object. pub fn to_json_string(&self) -> Result { serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) diff --git a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs index cb70554890c..215657a659e 100644 --- a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs +++ b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs @@ -1,15 +1,25 @@ +//! Test cases taken from: +//! +//! https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases + #![cfg(test)] -use eth2_keystore::{Keystore, Password}; +use eth2_keystore::{Keystore, Uuid}; + +const EXPECTED_SECRET: &str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const PASSWORD: &str = "testpassword"; + +pub fn decode_and_check_sk(json: &str) -> Keystore { + let keystore = Keystore::from_json_str(json).expect("should decode keystore json"); + let expected_sk = hex::decode(EXPECTED_SECRET).unwrap(); + let keypair = keystore.decrypt_keypair(PASSWORD.into()).unwrap(); + assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk); + keystore +} -// Test cases taken from: -// -// https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases #[test] -fn eip2335_test_vectors() { - let expected_secret = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; - let password: Password = "testpassword".into(); - let scrypt_test_vector = r#" +fn eip2335_test_vector_scrypt() { + let vector = r#" { "crypto": { "kdf": { @@ -43,7 +53,18 @@ fn eip2335_test_vectors() { } "#; - let pbkdf2_test_vector = r#" + let keystore = decode_and_check_sk(&vector); + assert_eq!( + *keystore.uuid(), + Uuid::parse_str("1d85ae20-35c5-4611-98e8-aa14a633906f").unwrap(), + "uuid" + ); + assert_eq!(keystore.path(), "", "path"); +} + +#[test] +fn eip2335_test_vector_pbkdf() { + let vector = r#" { "crypto": { "kdf": { @@ -76,11 +97,11 @@ fn eip2335_test_vectors() { } "#; - 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.decrypt_keypair(password.clone()).unwrap(); - let expected_sk = hex::decode(expected_secret).unwrap(); - assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk) - } + let keystore = decode_and_check_sk(&vector); + assert_eq!( + *keystore.uuid(), + Uuid::parse_str("64625def-3331-4eea-ab6f-782f3ed16a83").unwrap(), + "uuid" + ); + assert_eq!(keystore.path(), "m/12381/60/0/0", "path"); } From 3330b2b8f6e54cdc549ce52a6b52b0f82083d45e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 12:16:57 +1000 Subject: [PATCH 049/118] Fix comment --- eth2/utils/eth2_keystore/tests/eip2335_vectors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs index 215657a659e..fc91e592105 100644 --- a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs +++ b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs @@ -1,6 +1,6 @@ //! Test cases taken from: //! -//! https://github.com/CarlBeek/EIPs/blob/bls_keystore/EIPS/eip-2335.md#test-cases +//! https://eips.ethereum.org/EIPS/eip-2335 #![cfg(test)] From 75566c785a7fdc6decf47194e4256bfd4e12079a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 12:46:08 +1000 Subject: [PATCH 050/118] Add checks for kdf params --- eth2/utils/eth2_keystore/src/keystore.rs | 37 +++- eth2/utils/eth2_keystore/tests/json.rs | 43 +++- eth2/utils/eth2_keystore/tests/params.rs | 252 +++++++++++++++++++++++ eth2/utils/eth2_keystore/tests/tests.rs | 25 +++ 4 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 eth2/utils/eth2_keystore/tests/params.rs diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index da78c3720b0..172472ece98 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -57,6 +57,8 @@ pub enum Error { InvalidJson(String), WriteError(String), ReadError(String), + InvalidPbkdf2Param, + InvalidScryptParam, IncorrectIvSize { expected: usize, len: usize }, } @@ -136,7 +138,7 @@ impl Keystore { uuid: Uuid, path: String, ) -> Result { - let derived_key = derive_key(&password, &kdf); + let derived_key = derive_key(&password, &kdf)?; let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); @@ -190,7 +192,7 @@ impl Keystore { let cipher_message = &self.json.crypto.cipher.message; // Generate derived key - let derived_key = derive_key(&password, &self.json.crypto.kdf.params); + let derived_key = derive_key(&password, &self.json.crypto.kdf.params)?; // Mismatching checksum indicates an invalid password. if generate_checksum(&derived_key, cipher_message.as_bytes()) @@ -287,13 +289,25 @@ fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexByte } /// Derive a private key from the given `password` using the given `kdf` (key derivation function). -fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { +fn derive_key(password: &Password, kdf: &Kdf) -> Result { let mut dk = DerivedKey::zero(); match &kdf { Kdf::Pbkdf2(params) => { let mut mac = params.prf.mac(password.as_bytes()); + // RFC2898 declares that `c` must be a "positive integer" and the `crypto` crate panics + // if it is `0`. + // + // Both of these seem fairly convincing that it shouldn't be 0. + // + // Reference: + // + // https://www.ietf.org/rfc/rfc2898.txt + if params.c == 0 { + return Err(Error::InvalidPbkdf2Param); + } + crypto::pbkdf2::pbkdf2( &mut mac, params.salt.as_bytes(), @@ -302,9 +316,22 @@ fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { ); } Kdf::Scrypt(params) => { - // Assert that `n` is power of 2 + // Assert that `n` is power of 2. debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); + // RFC7914 declares that all these parameters must be greater than 1: + // + // - `N`: costParameter. + // - `r`: blockSize. + // - `p`: parallelizationParameter + // + // Reference: + // + // https://tools.ietf.org/html/rfc7914 + if params.n == 0 || params.r == 0 || params.p == 0 { + return Err(Error::InvalidScryptParam); + } + crypto::scrypt::scrypt( password.as_bytes(), params.salt.as_bytes(), @@ -314,7 +341,7 @@ fn derive_key(password: &Password, kdf: &Kdf) -> DerivedKey { } } - dk + Ok(dk) } /// Compute floor of log2 of a u32. diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs index cb38589c79b..50ffb84c75d 100644 --- a/eth2/utils/eth2_keystore/tests/json.rs +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -6,7 +6,7 @@ use eth2_keystore::{Error, Keystore}; /// /// If this test doesn't pass then it all previous tests are unreliable. #[test] -fn reference_scrypt() { +fn scrypt_reference() { let vector = r#" { "crypto": { @@ -44,6 +44,47 @@ fn reference_scrypt() { assert!(Keystore::from_json_str(&vector).is_ok()); } +#[test] +fn pbkdf2_reference() { + let 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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + #[test] fn additional_top_level_key() { let vector = r#" diff --git a/eth2/utils/eth2_keystore/tests/params.rs b/eth2/utils/eth2_keystore/tests/params.rs new file mode 100644 index 00000000000..8066e3ae377 --- /dev/null +++ b/eth2/utils/eth2_keystore/tests/params.rs @@ -0,0 +1,252 @@ +#![cfg(test)] + +use eth2_keystore::{Error, Keystore}; + +const PASSWORD: &str = "testpassword"; + +fn decrypt_error(vector: &str) -> Error { + Keystore::from_json_str(&vector) + .unwrap() + .decrypt_keypair(PASSWORD.into()) + .err() + .unwrap() +} + +fn assert_decrypts(vector: &str) { + Keystore::from_json_str(&vector) + .unwrap() + .decrypt_keypair(PASSWORD.into()) + .unwrap(); +} + +#[test] +fn scrypt_zero_n() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 0, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_eq!(decrypt_error(vector), Error::InvalidScryptParam); +} + +#[test] +fn scrypt_zero_p() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 0, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_eq!(decrypt_error(vector), Error::InvalidScryptParam); +} + +#[test] +fn scrypt_zero_r() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 0, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_eq!(decrypt_error(vector), Error::InvalidScryptParam); +} + +#[test] +fn scrypt_zero_dklen() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 0, + "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" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + assert_decrypts(vector) +} + +#[test] +fn pbkdf2_zero_c() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 0, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + assert_eq!(decrypt_error(vector), Error::InvalidPbkdf2Param); +} + +#[test] +fn pbkdf2_zero_dken() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 0, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#; + + assert_decrypts(vector) +} diff --git a/eth2/utils/eth2_keystore/tests/tests.rs b/eth2/utils/eth2_keystore/tests/tests.rs index e19b2f100d0..06e245be180 100644 --- a/eth2/utils/eth2_keystore/tests/tests.rs +++ b/eth2/utils/eth2_keystore/tests/tests.rs @@ -86,3 +86,28 @@ fn file() { "should decrypt with good password" ); } + +#[test] +fn scrypt_params() { + let keypair = Keypair::random(); + + let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) + .unwrap() + .build() + .unwrap(); + + let json = keystore.to_json_string().unwrap(); + let decoded = Keystore::from_json_str(&json).unwrap(); + + assert_eq!( + decoded.decrypt_keypair(bad_password()).err().unwrap(), + Error::InvalidPassword, + "should not decrypt with bad password" + ); + + assert_eq!( + decoded.decrypt_keypair(good_password()).unwrap(), + keypair, + "should decrypt with good password" + ); +} From 5d7bd3f788905c9f0aaa6edf9f929d4ed0aacaa2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 12:54:26 +1000 Subject: [PATCH 051/118] Enforce empty kdf message --- .../src/json_keystore/hex_bytes.rs | 4 -- .../src/json_keystore/kdf_module.rs | 24 ++++++++++- .../eth2_keystore/src/json_keystore/mod.rs | 2 +- eth2/utils/eth2_keystore/src/keystore.rs | 6 +-- eth2/utils/eth2_keystore/tests/json.rs | 42 +++++++++++++++++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs index dfa03d84885..92d8d16b29b 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs @@ -7,10 +7,6 @@ use std::convert::TryFrom; pub struct HexBytes(Vec); impl HexBytes { - pub fn empty() -> Self { - Self(vec![]) - } - pub fn as_bytes(&self) -> &[u8] { &self.0 } diff --git a/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs index 80430ecf35a..1a595c45cfb 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs @@ -15,7 +15,29 @@ use std::convert::TryFrom; pub struct KdfModule { pub function: KdfFunction, pub params: Kdf, - pub message: HexBytes, + pub message: EmptyString, +} + +/// Used for ensuring serde only decodes an empty string. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub struct EmptyString; + +impl Into for EmptyString { + fn into(self) -> String { + "".into() + } +} + +impl TryFrom for EmptyString { + type Error = &'static str; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "" => Ok(Self), + _ => Err("kdf message must be empty"), + } + } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs index 0ebfad01f7e..3706082eaa6 100644 --- a/eth2/utils/eth2_keystore/src/json_keystore/mod.rs +++ b/eth2/utils/eth2_keystore/src/json_keystore/mod.rs @@ -12,7 +12,7 @@ mod kdf_module; pub use checksum_module::{ChecksumModule, EmptyMap, Sha256Checksum}; pub use cipher_module::{Aes128Ctr, Cipher, CipherModule}; pub use hex_bytes::HexBytes; -pub use kdf_module::{Kdf, KdfModule, Pbkdf2, Prf, Scrypt}; +pub use kdf_module::{EmptyString, Kdf, KdfModule, Pbkdf2, Prf, Scrypt}; pub use uuid::Uuid; use serde::{Deserialize, Serialize}; diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index 172472ece98..ec96f81aa39 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -3,8 +3,8 @@ use crate::derived_key::DerivedKey; use crate::json_keystore::{ - Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, HexBytes, JsonKeystore, Kdf, - KdfModule, Scrypt, Sha256Checksum, Version, + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, HexBytes, + JsonKeystore, Kdf, KdfModule, Scrypt, Sha256Checksum, Version, }; use crate::plain_text::PlainText; use crate::Password; @@ -161,7 +161,7 @@ impl Keystore { kdf: KdfModule { function: kdf.function(), params: kdf, - message: HexBytes::empty(), + message: EmptyString, }, checksum: ChecksumModule { function: Sha256Checksum::function(), diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs index 50ffb84c75d..694e4de172e 100644 --- a/eth2/utils/eth2_keystore/tests/json.rs +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -600,6 +600,48 @@ fn checksum_params() { } } +#[test] +fn kdf_message() { + let vector = r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "1" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + } + "#; + + match Keystore::from_json_str(&vector) { + Err(Error::InvalidJson(_)) => {} + _ => panic!("expected invalid json error"), + } +} + #[test] fn cipher_function() { let vector = r#" From b02951e7340e9122af629b08f158c7c59e16c0c8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 16:13:17 +1000 Subject: [PATCH 052/118] Expose json_keystore mod --- eth2/utils/eth2_keystore/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index e907a656077..36728103eb2 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -2,11 +2,12 @@ //! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). mod derived_key; -mod json_keystore; mod keystore; mod password; mod plain_text; +pub mod json_keystore; + pub use keystore::{Error, Keystore, KeystoreBuilder}; pub use password::Password; pub use uuid::Uuid; From d893356ecd871f7da5dd453c6c7268dd4ccb4c5b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 19:55:58 +1000 Subject: [PATCH 053/118] First commits on path derivation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 eth2/utils/eth2_keystore/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 36728103eb2..0a7117d2617 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -4,6 +4,7 @@ mod derived_key; mod keystore; mod password; +mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs new file mode 100644 index 00000000000..dcfa26c8966 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -0,0 +1,50 @@ +use crate::plain_text::PlainText; +use crypto::{digest::Digest, sha2::Sha256}; + +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; +/// The digest size (in octets) of the hash function (SHA256) +const K: usize = HASH_SIZE; +/// The size of the lamport array. +const LAMPORT_ARRAY_SIZE: usize = 255; +/// The HKDF output size (in octets) +const L: usize = K * LAMPORT_ARRAY_SIZE; + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { + hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) +} + +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { + let mut hasher = Sha256::new(); + hasher.input(salt); + hasher.input(ikm); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { + let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); + + debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); + + for i in 0..LAMPORT_ARRAY_SIZE { + let mut hasher = Sha256::new(); + + hasher.input(prk); + + if let Some(prev) = okm.last() { + hasher.input(&prev[..]); + } + + hasher.input(&[i as u8]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.push(digest); + } + + okm +} From aa8028f95943a7040bcec94c37e59110f02b2c09 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 13:33:11 +1000 Subject: [PATCH 054/118] Progress with implementation --- Cargo.lock | 1 + eth2/utils/eth2_keystore/Cargo.toml | 1 + eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 674 ++++++++++++++++++++++++++- 4 files changed, 658 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d184550f7fe..b32c2057a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "bls 0.2.0", "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index 917a3f63ba5..8e83f2e759d 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.3" bls = { path = "../bls" } eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" +num-bigint = "0.2.6" [dev-dependencies] tempfile = "3.1.0" diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 0a7117d2617..9980cd6b75b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,7 @@ mod derived_key; mod keystore; +mod lamport_secret_key; mod password; mod path; mod plain_text; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index dcfa26c8966..699278fb332 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,20 +1,90 @@ -use crate::plain_text::PlainText; +use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; use crypto::{digest::Digest, sha2::Sha256}; +use num_bigint::BigUint; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; -/// The digest size (in octets) of the hash function (SHA256) -const K: usize = HASH_SIZE; +pub const HASH_SIZE: usize = 32; /// The size of the lamport array. -const LAMPORT_ARRAY_SIZE: usize = 255; -/// The HKDF output size (in octets) -const L: usize = K * LAMPORT_ARRAY_SIZE; +pub const LAMPORT_ARRAY_SIZE: u8 = 255; -fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { +pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + +fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { + hkdf_mod_r(seed) +} + +fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { + let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); + hkdf_mod_r(&compressed_lamport_pk) +} + +fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); + + // TODO: don't do all this extra work with extraction. + let lamport_bytes = lamport + .iter_chunks() + .map(|a| a.to_vec()) + .flatten() + .collect::>(); + + // TODO: justify 48. + mod_r(&lamport_bytes[0..48]) +} + +fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(bytes.len(), HASH_SIZE); + + let n = BigUint::from_bytes_be(bytes); + let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); + let x = (n % r).to_bytes_be(); + + debug_assert!(x.len() <= HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output +} + +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { + let salt = index.to_be_bytes(); + let not_ikm = flip_bits(ikm); + + let lamports = [ + ikm_to_lamport_sk(ikm, &salt), + ikm_to_lamport_sk(¬_ikm, &salt), + ]; + + let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + + lamports + .iter() + .map(LamportSecretKey::iter_chunks) + .flatten() + .enumerate() + .for_each(|(i, chunk)| { + let output_slice = lamport_pk + .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) + .expect("lamport_pk must have adequate capacity"); + + let mut hasher = Sha256::new(); + hasher.input(chunk); + hasher.result(output_slice); + }); + + let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut hasher = Sha256::new(); + hasher.input(&lamport_pk); + hasher.result(&mut compressed_lamport_pk); + + compressed_lamport_pk +} + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) } -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { +fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { let mut hasher = Sha256::new(); hasher.input(salt); hasher.input(ikm); @@ -25,26 +95,592 @@ fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { digest.into() } -fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { - let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); - - debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); +fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { + let mut okm = LamportSecretKey::zero(); for i in 0..LAMPORT_ARRAY_SIZE { let mut hasher = Sha256::new(); hasher.input(prk); - if let Some(prev) = okm.last() { - hasher.input(&prev[..]); + if let Some(prev) = i.checked_sub(1) { + hasher.input(okm.get_chunk(prev)) } - hasher.input(&[i as u8]); + hasher.input(&[i + 1]); - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.push(digest); + hasher.result(okm.get_mut_chunk(i)); } okm } + +fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(input.len(), HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + + for (i, byte) in input.iter().enumerate() { + output[i] = !byte + } + + output +} + +#[cfg(test)] +mod test { + use super::*; + + struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + lamport_0: Vec<&'static str>, + lamport_1: Vec<&'static str>, + compressed_lamport_sk: &'static str, + child_sk: &'static str, + } + + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec<[u8; HASH_SIZE]>, + lamport_1: Vec<[u8; HASH_SIZE]>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + todo!() + } + } + + fn get_raw_vector() -> RawTestVector { + RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: + "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + lamport_0: vec![ + "0x7b4a587eac94d7f56843e718a04965d4832ef826419b4001a3ad0ba77eb44a3b", + "0x90f45a712112122429412921ece5c30eb2a6daf739dc9034fc79424daeb5eff6", + "0xd061c2799de00b2be90eb1cc295f4c31e22d4b45c59a9b9b2554379bea7783cb", + "0x3ad17e4cda2913b5180557fbe7db04b5ba440ce8bb035ae27878d66fbfa50d2c", + "0xf5b954490933ad47f8bf612d4a4f329b3aa8914b1b83d59e15e271e2a087e002", + "0x95d68d505bf4ff3e5149bc5499cf4b2f00686c674a29a8d903f70e569557d867", + "0x1b59c76d9bb2170b220a87833582ede5970d4a336d91c99a812825afe963e056", + "0x4310ff73cfbbf7b81c39ecbf1412da33e9388c1a95d71a75e51fe12256551ceb", + "0xee696343f823e5716e16747f3bbae2fc6de233fe10eea8e45b4579018da0874f", + "0xae12a437aaa7ae59f7d8328944b6a2b973a43565c55d5807dc2faf223a33aa73", + "0x2a3ae0b47f145bab629452661ff7741f111272e33ec571030d0eb222e1ed1390", + "0x1a3ea396e8cbd1d97733ef4753d6840b42c0795d2d693f18e6f0e7b3fff2beb2", + "0x472429d0643c888bfdfe6e6ccfdeee6d345d60c6710859ac29fc289fd3656347", + "0xa32d4d955949b8bed0eb20f586d8fd516d6ddec84fbbc36998d692633c349822", + "0xe5ac8ac5ee1d40e53a7abf36e8269d5d5fce450a87feae8e59f432a44bcc7666", + "0xddf9e497ed78032fbd72d9b8abd5204d81c3475f29afa44cdf1ded8ea72dd1dc", + "0x945c62e88fb1e5f3c15ff57cd5eb1586ee93ec5ec80154c5a9c50241c5adae0a", + "0xc8868b50fc8423c96b7efa1ede4d3203a6b835dbeb6b2ababc58397e6b31d9dd", + "0x66de9bd86b50e2b6a755310520af655759c1753bff34b79a5cd63d6811fc8c65", + "0x5b13786c6068df7735343e5591393bea8aee92ac5826d6132bf4f5ebf1098776", + "0xa2038fc7d8e3cb2eda2bd303cfa76a9e5d8b88293918bec8b2fc03be75684f14", + "0x47a13f6b2308a50eded830fdee7c504bf49d1fe6a95e337b0825d0d77a520129", + "0xb534cdddcf1aa1c6b4cbba46d1db31b766d958e0a0306450bc031d1e3ed79d97", + "0x54aa051b754c31658377f7bff00b7deaa861e74cb12e1eb84216666e19b23d69", + "0x0220d57f63435948818eb376367b113c188e37451c216380f65d1ad55f73f527", + "0xf9dd2e391565534a4db84980433bf5a56250f45fe294fce2679bcf115522c081", + "0x1166591ee2ca59b9f4e525900f085141be8879c66ef18529968babeb87c44814", + "0xf4fa2e8de39bdbeb29b64d8b440d3a6c9a6ca5bdce543877eaee93c11bd70ab8", + "0x07f466d73b93db283b3f7bfaf9c39ae296adc376ab307ef12312631d0926790e", + "0xb2ecff93acb4fa44c1dbf8464b81734a863b6d7142b02f5c008907ea4dc9aaa1", + "0xa1d9c342f6c293ac6ef8b5013cba82c4bad6ed7024d782948cb23cd490039ba1", + "0xc7d04a639ba00517ece4dbc5ef4aaf20e0ccde6e4a24c28936fabe93dec594db", + "0xe3cbb9810472d9dd1cdb5eed2f74b67ea60e973d2d2e897bd64728c9b1aa0679", + "0xe36884703413958ff2aba7a1f138a26d0ac0a371270f0169219beb00a5add5f0", + "0xe5ea300a09895b3f98de5232d92a36d5611cbcf9aaf9e7bb20cf6d1696ad1cb4", + "0xc136cda884e18175ab45148ed4f9d0d1a3c5e11ad0275058e61ae48eb151a81f", + "0x3ee1101e944c040021187e93b6e0beb1048c75fb74f3fdd67756b1c8517a311f", + "0x016964fd6fc32b9ad07a630949596715dee84d78230640368ff0929a280cf3a2", + "0xe33865fc03120b94333bb754fd097dc0f90e69ff6fd221d6aae59fcf2d762d76", + "0xe80bb3515a09ac6ecb4ec59de22701cdf954b1ae8a677fd85508c5b041f28058", + "0x3889af7cd325141ec288021ede136652a0411d20364005b9d3ca9102cb368f57", + "0x18dad0bc975cf8800addd54c7867389d3f7fe1b97d348bd8412a6cbfb75c520a", + "0x09035218686061ee91bd2ad57dc6fb6da7243b8177a153484524b2b228da5314", + "0x688fd7a97551c64eae33f91abb073a46eafbbacd5595c6bac2e57dd536acdfe2", + "0x1fc164dce565a1d0da59cc8048b334cc5eb84bf04de2399ddb847c22a7e32ab7", + "0xa2a340ba05c8a30dd1cab886a926b761758eba0e41b5c4c5dfd4a42f249655c1", + "0xc43dffe01479db836a6a1a74564b297fad0d69c6b06cf593f6db9f26b4f307d5", + "0x73cef7f3ff724a30a79e1dca74cef74954afeefa2e476c4dec65afe50c16c5c4", + "0xa54002253ab7b95cc5b664b3f08976400475cc56f170b939f6792e730ff5170b", + "0x9ade43053d41afebc002f09476dffd1b13ecbf67f810791540b92ca56d5e63e4", + "0x234e7cbfbe45b22a871db26738fa05de09213a925439d7f3e5108132e521b280", + "0x066b712417332c7cfca871fb1bb5839f0341acf9266229603a3eddbc8a93b59f", + "0xb5857acdcf636330da2cfcc99c81d9fdbd20c506a3c0e4f4f6a139d2a64f051c", + "0xe119908a150a49704b6bbba2c470cd619a0ae10dd9736e8d491890e3c8509fff", + "0xb8a5c5dbb51e6cb73cca95b4ad63ea3c7399cd16b05ab6261535495b3af2ca51", + "0x05624a1d4d2d2a31160bc48a6314bbf13eaddf56cddb0f0aa4ed3fb87f8b479f", + "0x483daceff1c3baa0ed0f3be7e534eebf5f4aed424ecd804edfbf5c56b3476b50", + "0x424d04694e7ae673707c77eb1c6d0996d250cfab6832ee3506a12e0384a3c5c9", + "0xa11fed0ed8057966bfe7136a15a814d06a516fbc9d44aeef87c509137a26190e", + "0x3694d22d1bc64658f3adbe2cc9f1716aee889066e0950e0b7a2fd576ed36bb76", + "0x49a13000a87f39f93d0ae9c3a4cfccbf440c0a75cce4c9d70dac627b6d6958b3", + "0xb3ff0cdd878d5ac1cb12e7d0b300d649fdd008800d498ae4f9fbf9510c74249a", + "0xe52a867cfb87d2fe7102d23d8d64925f7b75ca3f7d6bb763f7337352c255e0be", + "0x6513b372e4e557cca59979e48ec27620e9d7cdb238fcf4a9f19c3ba502963be0", + "0x9f69d82d4d51736902a987c8b5c30c2b25a895f2af5d2c846667ff6768bcc774", + "0x049a220dbe3340749f94643a429cb3cba3c92b561dc756a733d652d838728ab3", + "0x4fa2cd877aa115b476082b11053309f3537fa03d9158085f5f3f4bab6083e6da", + "0xed12db4069eb9f347735816afcee3fe43d4a6999fef8240b91bf4b05447d734f", + "0x3ecbe5eda469278f68548c450836a05cc500864664c7dda9b7526f084a891032", + "0x690d8f928fc61949c22e18cceaa2a446f8e1b65bd2e7af9e0a8e8284134ab3d2", + "0x99e09167a09f8261e7e8571d19148b7d7a75990d0702d9d582a2e4a96ac34f8e", + "0x6d33931693ed7c2e1d080b6a37da52c279a06cec5f534305819f7adf7db0afe3", + "0xc4b735462a9a656e28a52b1d4992ea9dea826b858971d698453a4be534d6bb70", + "0xedf92b10302dc41f8d362b360f4c2ef551d50e2ded012312c964002d2afc46d7", + "0x58f6691cca081ae5c3661dd171b87cc49c90359bb03cc0e57e503f7fcf14aefc", + "0x5d29b8b4ee295a73c4a8618927b3d14b76c7da049133a2257192b10be8c17a6a", + "0x646802fa42801e0ae24011fb4f62e87219ef1da01f7fc14bf8d6bd2d9e7c21f1", + "0x23abf45eee65cc4c1e95ccab42ad280a00bb3b14d243e2021a684075f900141e", + "0x2b1ae95c975bf9c387eae506fdb5e58afd2d198f00a21cd3fddb5855e8021e4d", + "0x0ef9f6e1c0583493d343e75f9c0c557fa6da0dc12b17a96c5757292916b72ee3", + "0x04c7fc76195c64a3285af14161077c045ff6ddbb67c0ff91b080f98eb6781e5c", + "0xba12679b97027d0e7076e6d19086c07792eaa7f78350842fbef8ddf5bcd3ecc0", + "0xcead458e6799df4d2f6cbf7f13cb3afec3441a354816e3071856ed49cbdbb1a7", + "0xbe6c56256556bb5c6727a1d9cb641d969677f56bb5ad7f8f7a7c9cfd128427b4", + "0xc80f11963ff40cb1888054b83c0463d32f737f2e7d42098e639023db0dfc84d4", + "0xac80006c1296bcfde86697efebb87fb0fddfb70dd34dd2ee4c152482af4687eb", + "0xbb7d13ce184249df4576fc3d13351e1683500e48726cd4198423f14f9094068b", + "0x1b2d9c40c55bd7362664fa46c1268e094d56c8193e3d991c08dc7a6e4ca14fa1", + "0x9bd236254d0565f5b2d24552d4b4d732de43b0adaa64ecd8be3efc6508577591", + "0x38078cefccc04e8312d79e0636e0e3157434c50a2ad4e3e87cc6584c41eec8b5", + "0xb5d15a8527ff3fa254ba61ffceb02d2570b53361894f351a9e839c0bb716857d", + "0x6763dad684bf2e914f40ae0a7ee0cdf12c97f41fc05a485d5991b4daad21a3f8", + "0xc80363c20df589333ecbe05bd5f2c19942ebc2593626dc50d00835c40fb8d005", + "0x48502b56ae93acd2794f847cbe825525d5d5f59f0f75c67aff84e5338776b3af", + "0xfd8e033493ba8af264a855a78ab07f37d936351d2879b95928909ed8df1b4f91", + "0x11f75bee9eac7356e65ebc7f004ccdc1da80807380d69143293d1421f50b1c97", + "0x903a88a3ebe84ca1c52a752b1faffa9ca1daedac9cbf1aa70942efc9beb44b79", + "0x2c0dcd68837f32a69da651045ad836b8cd6b48f2c8c5d73a3bd3bba6148d345a", + "0x0aa0f49b3476f3fdb6393f2ab601e0009586090b72ee54a525734f51598960d5", + "0xf7a789f013f702731656c562caa15b04cb7c9957376c4d80b8839167bb7fa626", + "0x4e0be1b19e305d82db3fd8affd67b0d2559da3edbfb08d19632a5cc46a90ed07", + "0x3caaccfc546d84d543eaf4f4c50c9c8fd831c12a8de56fdb9dfd04cc082882fe", + "0x894f6a01fd34f0642077e22981752011678548eb70eb55e8072c1caffc16fe02", + "0xae7eb54adaa68679348ea3537a49be669d1d61001fbab9fac259ba727dbc9a1a", + "0x291a1cbdceff957b5a65440ab67fb8672de881230fe3108a15ca487c2662c2c7", + "0x891d43b867137bf8beb9df4da2d951b5984a266a8cd74ec1593801d005f83f08", + "0xc558407f6491b37a10835e0ad7ce74f4e368aa49157a28873f7229310cb2d7fd", + "0x9ce061b0a072e1fe645f3479dac089b5bfb78cfa6cfbe5fd603bcdb504711315", + "0xa8e30d07b09275115dd96472ecf9bc316581caf307735176ca226d4cd9022925", + "0x918ee6d2efba7757266577691203f973cf4f4cac10f7d5f86acd2a797ff66583", + "0xfa31ba95e15d1635d087522f3d0da9cf7acac4ed6d0ac672654032a3c39244a6", + "0xf2952b58f015d6733af06938cd1f82fbddb3b796823bee7a3dbffa04efc117c2", + "0x46f8f742d3683de010ede528128d1181e8819f4252474f51371a177bfa518fa4", + "0x4ca1cc80094f2910cf83a9e65ad70e234690ffb9142793911ec7cf71663545b3", + "0x381965037b5725c71bfa6989d4c432f6611de8e8ec387f3cfc0dcb1a15191b73", + "0x2562b88ed3b86ba188be056805a3b7a47cb1a3f630d0e2f39647b0792ec6b7d8", + "0x565f6d14e7f22724f06d40f54465ad40d265b6de072b34a09d6e37a97a118cd8", + "0xc2982c861ad3278063b4a5f584eaf866db684cc4e712d64230fc9ee33bb4253b", + "0xfd806c91927e549d8d400ab7aa68dbe60af988fbabf228483ab0c8de7dab7eee", + "0xafae6ff16c168a3a3b5c2f1742d3f89fa4777c4bd0108f174014debf8f4d629c", + "0xaf5a4be694de5e53632be9f1a49bd582bf76002259460719197079c8c4be7e66", + "0xa8df4a4b4c5bf7a4498a11186f8bb7679137395f28e5c2179589e1c1f26504b5", + "0xce8b77c64c646bb6023f3efaed21ca2e928e21517422b124362cf8f4d9667405", + "0x62e67a8c423bc6c6c73e6cd8939c5c1b110f1a38b2ab75566988823762087693", + "0x7e778f29937daaa272d06c62d6bf3c9c0112d45a3df1689c602d828b5a315a9f", + "0xe9b5abd46c2377e602ff329050afa08afe152f4b0861db8a887be910ff1570bf", + "0xa267b1b2ccd5d96ae8a916b0316f06fafb886b3bb41286b20763a656e3ca0052", + "0xb8ed85a67a64b3453888a10dedf4705bd27719664deff0996a51bb82bc07194f", + "0x57907c3c88848f9e27bc21dd8e7b9d61de48765f64d0e943e7a6bb94cc2021ab", + "0xd2f6f1141a3b76bf9bf581d49091142944c7f9f323578f5bdd5522ba32291243", + "0xc89f104200ed4c5d5f7046d99e68ae6f8ec31e2eeceb568eb05087e3aa546a74", + "0xc9f367fae45c39299693b134229bb6dd0da112fd1a7d19b7f4772c01e5cbe479", + "0x64e2d4ad51948764dd578d26357e29e8e4d076d65c05cffdf8211b624fefe9ac", + "0xf9a9b4e6d5be7fc051df8ecd9c389d16b1af86c749308e6a23f7ff4871f0ba9a", + "0x0d2b2a228b86ebf9499e1bf7674335087ced2eb35ce0eb90954a0f75751a2bf4", + "0xff8531b45420a960d6e48ca75d77758c25733abde83cd4a6160beae978aa735e", + "0xd6d412bd1cb96a2b568d30e7986b7e8994ca92fd65756a758295499e11ea52b6", + "0xad8533fccbecdd4a0b00d648bfe992360d265f7be70c41d9631cefad5d4fe2f6", + "0x31fbf2afb8d5cc896d517cfc5201ee24527e8d283f9c37ca10233bef01000a20", + "0x2fd67b7365efc258131eb410f46bf3b1cbd3e9c76fd6e9c3e86c9ff1054116ff", + "0xab6aa29f33d18244be26b23abadb39679a8aa56dafc0dd7b87b672df5f5f5db6", + "0xbad3b0f401ca0a53a3d465de5cecd57769ec9d4df2c04b78f8c342a7ed35bbee", + "0xbdc24d46e471835d83ce8c5b9ecbe675aab2fd8f7831c548e8efd268c2ee2232", + "0x87265fabd7397d08f0729f13a2f3a25bbc8c874b6b50f65715c92b62f665f925", + "0xa379fd268e7ff392c067c2dd823996f72714bf3f936d5eeded71298859f834cb", + "0xf3ab452c9599ebfbb234f72a86f3062aed12ae1f634abbe542ff60f5cefc1fcf", + "0x2b17ebb053a3034c07da36ed2ba42c25ad8e61dec87b5527f5e1c755eb55405a", + "0x305b40321bd67bf48bfd121ee4d5d347268578bd4b8344560046594771a11129", + "0xe7029c9bea020770d77fe06ca53b521b180ad6a9e747545aadc1c74beef7241c", + "0xabc357cec0f4351a5ada22483d3b103890392f8d8f9cb8073a61969ed1be4e08", + "0x97f88c301946508428044d05584dc41af2e6a0de946de7d7f5269c05468afe20", + "0xbdc08fe8d6f9a05ad8350626b622ad8eec80c52331d154a3860c98676719cfbd", + "0x161590fc9f7fcf4eaba2f950cf588e6da79e921f139d3c2d7ebe017003a4799e", + "0x91b658db75bc3d1954bfde2ef4bc12980ff1688e09d0537f170c9ab47c162320", + "0x76d995f121406a63ce26502e7ec2b653c221cda357694a8d53897a99e6ce731e", + "0x3d6b2009586aceb7232c01259bb9428523c02b0f42c2100ec0d392418260c403", + "0x14ca74ecbc8ec0c67444c6cb661a2bce907aa2a1453b11f16002b815b94a1c49", + "0x553b4dc88554ebe7b0a3bd0813104fd1165a1f950ceace11f5841aa74b756d85", + "0x4025bf4ad86751a156d447ce3cabafde9b688efcdafd8aa4be69e670f8a06d9e", + "0x74260cf266997d19225e9a0351a9acfa17471fccdf5edc9ccc3bb0d23ef551c5", + "0xf9dbca3e16d234e448cf03877746baeb62a8a25c261eff42498b1813565c752a", + "0x2652ec98e05c1b6920fb6ddc3b57e366d514ffa4b35d068f73b5603c47f68f2f", + "0x83f090efeb36db91eb3d4dfbb17335c733fce7c64317d0d3324d7caaaf880af5", + "0x1e86257f1151fb7022ed9ed00fb961a9a9989e58791fb72043bb63ed0811791c", + "0xd59e4dcc97cba88a48c2a9a2b29f79125099a39f74f4fb418547de8389cd5d15", + "0x875a19b152fe1eb3fe1de288fa9a84864a84a79bac30b1dbd70587b519a9770e", + "0x9c9dc2d3c8f2f6814cfc61b42ee0852bbaf3f523e0409dd5df3081b750a5b301", + "0xf6f7f81c51581c2e5861a00b66c476862424151dd750efeb20b7663d552a2e94", + "0x723fcb7ca43a42483b31443d4be9b756b34927176f91a391c71d0b774c73a299", + "0x2b02d8acf63bc8f528706ed4d5463a58e9428d5b71d577fd5daa13ba48ac56cf", + "0x2ff6911f574c0f0498fc6199da129446b40fca35ccbf362bc76534ba71c7ca22", + "0x1ef4b959b11bc87b11e4a5f84b4d757c6bdcfad874acec9a6c9eee23dc4bbe1b", + "0x68e2df9f512be9f64b7e3a2dee462149dac50780073d78b569a20256aea5f751", + "0xd1a3682e12b90ae1eab27fc5dc2aef3b8e4dbb813925e9a91e58d6c9832767b6", + "0x75778ccc102d98c5e0b4b83f7d4ef7fe8bc7263cc3317723001cb0b314d1e9e8", + "0xc7f44e2cead108dc167f0036ac8a278d3549cc3dd5cc067d074ccad9b1d9f8d4", + "0x4cba0223c5df2796b0ee9fbc084d69f10e6aedda8f0cf86171bebb156ede676c", + "0x628deda825661f586a5713e43c806fdd55e1a53fbe90a4ddb5f3786570740954", + "0xfc82a253bc7e0ac96252b238fbb411a54e0adf78d089f804a7fc83a4959b401e", + "0x72a6491f5daae0ceb85b61a5ed69009dd2a167c64cb35cabf38b846e27268e9d", + "0xee139a913d4fcf25ba54bb36fc8051b91f2ec73ba820cc193c46fb2f7c37a106", + "0x7f75021f2b1d0c78859478e27f6f40646b5776c060f1a5f6f0944c840a0121f8", + "0x5b60a1b78feca1d2602ac8110d263ad6b3663cbf49e6bdc1077b4b80af2feb6f", + "0xd61f15d80b1e88469b6a76ed6a6a2b94143b6acc3bd717357264818f9f2d5c6d", + "0xea85da1780b3879a4d81b685ba40b91c060866abd5080b30fbbb41730724a7dd", + "0xb9b9da9461e83153f3ae0af59fbd61febfde39eb6ac72db5ed014797495d4c26", + "0xf737762fe8665df8475ff341b3762aaeb90e52974fe5612f5efd0fc1c409d7f8", + "0xaaa25d934a1d5aa6b2a1863704d7a7f04794ed210883582c1f798be5ca046cf7", + "0x932f46d0b6444145221b647f9d3801b6cb8b1450a1a531a959abdaacf2b5656b", + "0xf4a8b0e52f843ad27635c4f5a467fbf98ba06ba9a2b93a8a97170b5c41bf4958", + "0x196ed380785ee2925307ec904161dc02a4596a55499e5b0a3897f95485b3e74a", + "0x772e829a405219e4f8cd93a1ef15c250be85c828c1e29ef6b3f7b46958a85b44", + "0xd66cfc9af9941515d788f9f5e3b56fddb92464173ddb67b83bf265e7ea502170", + "0xf5b040bfc246425278e2423b1953d8ad518de911cf04d16c67d8580a09f90e62", + "0xd2d18b2ae8a53dde14b4000e5e7e414505825f50401a3797dd8820cf510dc448", + "0xc01dcc064e644266739cd0ec7edf92fc2ef8e92e0beedf0e8aa30efcff1644fe", + "0x24720d325913ba137daf031924ad3bfaa1c8c00a53a2d048fe5667aef45efce3", + "0x70a24e1c89b3ea78d76ef458d498dcb5b8561d484853b2a8b2adcd61869857df", + "0x0ff3313997f14e1b1dcd80f1d62c58aaefb19efd7c0ea15dde21aa4e2a516e80", + "0x960c1f50062a4df851638f42c0259b6e0a0217300884f13a3c5c8d94adb34f21", + "0xb71ca7cc8578149da556131268f4625b51620dfc3a6e9fbd47f5df03afbd410e", + "0xa1a3eeec0addec7b9e15f416a07608a1b5d94f0b42d5c203b8ced03a07484f5b", + "0xa4bb8b059aa122ca4652115b83b17af80cfbea0d3e1e8979a396a667f94e85f3", + "0x31c4d2f252167fe2a4d41944224a80b2f1afaf76f8dd6a3d52d71751849e44bb", + "0x79642dd6a255f96c9efe569304d58c327a441448db0431aa81fe072d0d359b52", + "0x42a4b504714aba1b67defe9458fff0c8cb1f216dcab28263cef67a65693b2036", + "0xe3d2f6a9d882d0f026ef316940dfcbf131342060ea28944475fe1f56392c9ad2", + "0x986af9aeff236394a0afa83823e643e76f7624e9bfd47d5468f9b83758a86caa", + "0xafe2de6ede50ee351d63ed38d1f2ae5203174c731f41bbed95db467461ad5492", + "0x9ad40f0785fe1c8a5e4c3342b3c91987cd47a862ece6573674b52fa0456f697a", + "0xde4cde6d0fc6def3a89b79da0e01accdbec049f1c9471d13a5d59286bd679af1", + "0xecd0d1f70116d6b3ae21c57fb06ad90eed33d040e2c5c3d12714b3be934fa5ce", + "0x3c53c5bf2d1b1d4038e1f0e8a2e6d12e0d4613d5cd12562578b6909921224c10", + "0x36087382b37e9e306642cc6e867e0fb2971b6b2b28b6caf2f9c96b790e8db70a", + "0xa957496d6a4218a19998f90282d05bd93e6baabf55e55e8a5f74a933a4dec045", + "0x077d6f094e8467a21f02c67753565ec5755156015d4e86f1f82a22f9cf21c869", + "0x12dd3b1f29e1462ca392c12388a77c58044151154cf86f23873f92a99b6bb762", + "0x7fdbcdedcc02ecf16657792bd8ef4fa4adeee497f30207d4cc060eb0d528b26b", + "0x245554b12bf8edf9e9732d6e2fa50958376e355cb695515c94676e64c6e97009", + "0xccd3b1841b517f7853e35f85471710777e437a8665e352a0b61c7d7083c3babc", + "0xd970545a326dcd92e31310d1fdce3703dff8ef7c0f3411dfa74fab8b4b0763ac", + "0xd24163068918e2783f9e79c8f2dcc1c5ebac7796ce63070c364837aac91ee239", + "0x256a330055357e20691e53ca5be846507c2f02cfde09cafb5809106f0af9180e", + "0xfa446a5d1876c2051811af2a341a35dbcd3f7f8e2e4f816f501139d27dd7cd82", + "0xbafbc7a8f871d95736a41e5721605d37e7532e41eb1426897e33a72ed2f0bf1d", + "0x8055af9a105b6cf17cfeb3f5320e7dab1a6480500ff03a16c437dfec0724c290", + "0x1de6ee3e989497c1cc7ca1d16b7b01b2f336524aa2f75a823eaa1716c3a1a294", + "0x12bb9508d646dda515745d104199f71276d188b3e164083ad27dfdcdc68e290b", + "0x7ea9f9939ad4f3b44fe7b780e0587da4417c34459b2996b3a449bb5b3ff8c8cb", + "0xa88d2f8f35bc669aa6480ce82571df65fea366834670b4084910c7bb6a735dde", + "0x9486e045adb387a550b3c7a603c30e07ed8625d322d1158f4c424d30befe4a65", + "0xb283a70ba539fe1945be096cb90edb993fac77e8bf53616bde35cdcaa04ab732", + "0xab39a81558e9309831a2caf03e9df22e8233e20b1769f16e613debcdb8e2610f", + "0x1fc12540473fbbad97c08770c41f517ce19dc7106aa2be2e9b77867046627509", + "0xec33dbec9d655c4c581e07d1c40a587cf3217bc8168a81521b2d0021bd0ec133", + "0xc8699e3b41846bc291209bbb9c06f565f66c6ccecbf03ebc27593e798c21fe94", + "0x240d7eae209c19d453b666c669190db22db06279386aa30710b6edb885f6df94", + "0xb181c07071a750fc7638dd67e868dddbeeee8e8e0dcbc862539ee2084674a89e", + "0xb8792555c891b3cbfddda308749122a105938a80909c2013637289e115429625", + "0xfe3e9e5b4a5271d19a569fee6faee31814e55f156ba843b6e8f8dc439d60e67a", + "0x912e9ba3b996717f89d58f1e64243d9cca133614394e6ae776e2936cf1a9a859", + "0xa0671c91a21fdfd50e877afa9fe3974aa3913855a2a478ae2c242bcdb71c73d7", + "0x5b55d171b346db9ba27b67105b2b4800ca5ba06931ed6bd1bafb89d31e6472e6", + "0x68438458f1af7bd0103ef33f8bc5853fa857b8c1f84b843882d8c328c595940d", + "0x21fe319fe8c08c1d00f977d33d4a6f18aecaa1fc7855b157b653d2d3cbd8357f", + "0x23cce560bc31f68e699ece60f21dd7951c53c292b3f5522b9683eb2b3c85fc53", + "0x917fa32d172c352e5a77ac079df84401cdd960110c93aa9df51046d1525a9b49", + "0x3fc397180b65585305b88fe500f2ec17bc4dccb2ec254dbb72ffb40979f14641", + "0xf35fb569e7a78a1443b673251ac70384abea7f92432953ca9c0f31c356be9bd9", + "0x7955afa3cd34deb909cd031415e1079f44b76f3d6b0aaf772088445aaff77d08", + "0x45c0ca029356bf6ecfc845065054c06024977786b6fbfaea74b773d9b26f0e6c", + "0xe5c1dac2a6181f7c46ab77f2e99a719504cb1f3e3c89d720428d019cb142c156", + "0x677b0e575afcccf9ddefc9470e96a6cfff155e626600b660247b7121b17b030a", + "0xbeed763e9a38277efe57b834a946d05964844b1f51dba2c92a5f3b8d0b7c67d0", + "0x962b17ed1a9343d8ebfae3873162eef13734985f528ca06c90b0c1e68adfdd89", + ], + lamport_1: vec![ + "0xb3a3a79f061862f46825c00fec4005fb8c8c3462a1eb0416d0ebe9028436d3a9", + "0x6692676ce3b07f4c5ad4c67dc2cf1dfa784043a0e95dd6965e59dc00b9eaff2d", + "0xbf7b849feb312db230e6e2383681b9e35c064e2d037cbc3c9cc9cd49220e80c9", + "0xa54e391dd3b717ea818f5954eec17b4a393a12830e28fabd62cbcecf509c17dc", + "0x8d26d800ac3d4453c211ef35e9e5bb23d3b9ede74f26c1c417d6549c3110314d", + "0xbb8153e24a52398d92480553236850974576876c7da561651bc551498f184d10", + "0x0d30e0e203dc4197f01f0c1aba409321fbf94ec7216e47ab89a66fb45e295eff", + "0x01dc81417e36e527776bf37a3f9d74a4cf01a7fb8e1f407f6bd525743865791d", + "0xa6318e8a57bec438245a6834f44eb9b7fb77def1554d137ea12320fc572f42c9", + "0xd25db9df4575b595130b6159a2e8040d3879c1d877743d960bf9aa88363fbf9f", + "0x61bb8baeb2b92a4f47bb2c8569a1c68df31b3469e634d5e74221bc7065f07a96", + "0xb18962aee4db140c237c24fec7fd073b400b2e56b0d503f8bc74a9114bf183bf", + "0x205473cc0cdab4c8d0c6aeceda9262c225b9db2b7033babfe48b7e919751a2c6", + "0xc5aa7df7552e5bb17a08497b82d8b119f93463ccb67282960aee306e0787f228", + "0x36da99e7d38ce6d7eab90ea109ba26615ad75233f65b3ae5056fba79c0c6682a", + "0xd68b71bba6266b68aec0df39b7c2311e54d46a3eab35f07a9fe60d70f52eec58", + "0xbbe56f1274ada484277add5cb8c90ef687d0b69a4c95da29e32730d90a2d059f", + "0x0982d1d1c15a560339d9151dae5c05e995647624261022bbedce5dce8a220a31", + "0x8ef54ad546d2c6144fc26e1e2ef92919c676d7a76cfdfb5c6a64f09a54e82e71", + "0x1e3ac0133eef9cdbeb590f14685ce86180d02b0eea3ef600fd515c38992b1f26", + "0x642e6b1c4bec3d4ba0ff2f15fbd69dcb57e4ba8785582e1bc2b452f0c139b590", + "0xca713c8cf4afa9c5d0c2db4fc684a8a233b3b01c219b577f0a053548bedf8201", + "0xd0569ba4e1f6c02c69018b9877d6a409659cb5e0aa086df107c2cc57aaba62da", + "0x4ebe68755e14b74973e7f0fa374b87cee9c370439318f5783c734f00bb13e4b5", + "0x788b5292dc5295ae4d0ea0be345034af97a61eec206fda885bbc0f049678c574", + "0x0ebd88acd4ae195d1d3982038ced5af1b6f32a07349cf7fffbff3ce410c10df2", + "0xc7faf0a49234d149036c151381d38427b74bae9bd1601fc71663e603bc15a690", + "0xc5247bf09ebe9fa4e1013240a1f88c703f25a1437196c71ee02ca3033a61f946", + "0x719f8c68113d9f9118b4281e1f42c16060def3e3eeef15f0a10620e886dc988f", + "0x28da4f8d9051a8b4d6158503402bdb6c49ba2fb1174344f97b569c8f640504e6", + "0x96f6773576af69f7888b40b0a15bc18cc9ec8ca5e1bb88a5de58795c6ddf678e", + "0x8d80d188a4e7b85607deccf654a58616b6607a0299dd8c3f1165c453fd33d2e4", + "0x9c08dcc4f914486d33aa24d10b89fd0aabcc635aa2f1715dfb1a18bf4e66692a", + "0x0ff7045b5f6584cc22c140f064dec0692762aa7b9dfa1defc7535e9a76a83e35", + "0x8e2dae66fa93857b39929b8fc531a230a7cfdd2c449f9f52675ab5b5176461d5", + "0xf449017c5d429f9a671d9cc6983aafd0c70dd39b26a142a1d7f0773de091ac41", + "0xed3d4cab2d44fec0d5125a97b3e365a77620db671ecdda1b3c429048e2ebdae6", + "0x836a332a84ee2f4f5bf24697df79ed4680b4f3a9d87c50665f46edaeed309144", + "0x7a79278754a4788e5c1cf3b9145edb55a2ba0428ac1c867912b5406bb7c4ce96", + "0x51e6e2ba81958328b38fd0f052208178cec82a9c9abd403311234e93aff7fa70", + "0x217ec3ec7021599e4f34410d2c14a8552fff0bc8f6894ebb52ec79bf6ec80dc9", + "0x8a95bf197d8e359edabab1a77f5a6d04851263352aa46830f287d4e0564f0be0", + "0x60d0cbfb87340b7c92831872b48997ce715da91c576296df215070c6c20046d4", + "0x1739fbca476c540d081b3f699a97387b68af5d14be52a0768d5185bc9b26961b", + "0xac277974f945a02d89a0f8275e02de9353e960e319879a4ef137676b537a7240", + "0x959b7640821904ba10efe8561e442fbdf137ccb030aee7472d10095223e320ba", + "0xdba61c8785a64cb332342ab0510126c92a7d61f6a8178c5860d018d3dad571c6", + "0xc191fb6a92eb1f1fb9e7eb2bdecd7ec3b2380dd79c3198b3620ea00968f2bd74", + "0x16ef4e88e182dfc03e17dc9efaa4a9fbf4ff8cb143304a4a7a9c75d306729832", + "0x39080e4124ca577ff2718dfbcb3415a4220c5a7a4108729e0d87bd05adda5970", + "0xa29a740eef233956baff06e5b11c90ed7500d7947bada6da1c6b5d9336fc37b6", + "0x7fda7050e6be2675251d35376bacc895813620d245397ab57812391d503716ee", + "0x401e0bf36af9992deb87efb6a64aaf0a4bc9f5ad7b9241456b3d5cd650418337", + "0x814e70c57410e62593ebc351fdeb91522fe011db310fcf07e54ac3f6fefe6be5", + "0x03c1e52ecbef0d79a4682af142f012dc6b037a51f972a284fc7973b1b2c66dcf", + "0x57b22fb091447c279f8d47bdcc6a801a946ce78339e8cd2665423dfcdd58c671", + "0x53aeb39ab6d7d4375dc4880985233cba6a1be144289e13cf0bd04c203257d51b", + "0x795e5d1af4becbca66c8f1a2e751dcc8e15d7055b6fc09d0e053fa026f16f48f", + "0x1cd02dcd183103796f7961add835a7ad0ba636842f412643967c58fe9545bee4", + "0x55fc1550be9abf92cacb630acf58bad11bf734114ebe502978a261cc38a4dd70", + "0x6a044e0ea5c361d3fb2ca1ba795301e7eb63db4e8a0314638f42e358ea9cfc3e", + "0x57d9f15d4db199cbcb7cbd6524c52a1b799d52b0277b5a270d2985fcee1e2acb", + "0x66c78c412e586bd01febc3e4d909cc278134e74d51d6f60e0a55b35df6fb5b09", + "0x1076799e15a49d6b15c2486032f5e0b50f43c11bc076c401e0779d224e33f6fc", + "0x5f70e3a2714d8b4483cf3155865ba792197e957f5b3a6234e4c408bf2e55119d", + "0x9b105b0f89a05eb1ff7caed74cf9573dc55ac8bc4881529487b3700f5842de16", + "0x1753571b3cfadca4277c59aee89f607d1b1e3a6aa515d9051bafb2f0d8ce0daa", + "0x4014fff940b0950706926a19906a370ccbd652836dab678c82c539c00989201a", + "0x0423fa59ee58035a0beb9653841036101b2d5903ddeabddabf697dbc6f168e61", + "0x78f6781673d991f9138aa1f5142214232d6e3d6986acb6cc7fb000e1a055f425", + "0x21b8a1f6733b5762499bf2de90c9ef06af1c6c8b3ddb3a04cce949caad723197", + "0x83847957e909153312b5bd9a1a37db0bd6c72a417024a69df3e18512973a18b4", + "0x948addf423afd0c813647cfe32725bc55773167d5065539e6a3b50e6ebbdab38", + "0x0b0485d1bec07504a2e5e3a89addd6f25d497cd37a0c04bc38355f8bdb01cd48", + "0x31be8bda5143d39ea2655e9eca6a294791ca7854a829904d8574bedc5057ddc4", + "0x16a0d2d657fadce0d81264320e42e504f4d39b931dff9888f861f3cc78753f99", + "0xb43786061420c5231bf1ff638cb210f89bf4cd2d3e8bafbf34f497c9a298a13b", + "0x1f5986cbd7107d2a3cbc1826ec6908d976addbf9ae78f647c1d159cd5397e1bd", + "0xa883ccdbfd91fad436be7a4e2e74b7796c0aadfe03b7eea036d492eaf74a1a6f", + "0x5bc9eb77bbbf589db48bca436360d5fc1d74b9195237f11946349951f2a9f7f6", + "0xb6bc86de74a887a5dceb012d58c62399897141cbcc51bad9cb882f53991f499c", + "0xa6c3260e7c2dd13f26cf22bf4cd667688142ff7a3511ec895bc8f92ebfa694b6", + "0xb97da27e17d26608ef3607d83634d6e55736af10cc7e4744940a3e35d926c2ad", + "0x9df44067c2dc947c2f8e07ecc90ba54db11eac891569061a8a8821f8f9773694", + "0x865cc98e373800825e2b5ead6c21ac9112ff25a0dc2ab0ed61b16dc30a4a7cd7", + "0xe06a5b157570c5e010a52f332cacd4e131b7aed9555a5f4b5a1c9c4606caca75", + "0x824eccb5cf079b5943c4d17771d7f77555a964a106245607cedac33b7a14922e", + "0xe86f721d7a3b52524057862547fc72de58d88728868f395887057153bccaa566", + "0x3344e76d79f019459188344fb1744c93565c7a35799621d7f4505f5b6119ac82", + "0x401b3589bdd1b0407854565329e3f22251657912e27e1fb2d978bf41c435c3ac", + "0xb12fd0b2567eb14a562e710a6e46eef5e280187bf1411f5573bb86ecbe05e328", + "0xe6dc27bab027cbd9fbb5d80054a3f25b576bd0b4902527a0fc6d0de0e45a3f9f", + "0x1de222f0e731001c60518fc8d2be7d7a48cc84e0570f03516c70975fdf7dc882", + "0xb8ff6563e719fc182e15bbe678cf045696711244aacc7ce4833c72d2d108b1b9", + "0x53e28ac2df219bcbbc9b90272e623d3f6ca3221e57113023064426eff0e2f4f2", + "0x8a4e0776f03819e1f35b3325f20f793d026ccae9a769d6e0f987466e00bd1ce7", + "0x2f65f20089a31f79c2c0ce668991f4440b576ecf05776c1f6abea5e9b14b570f", + "0x448e124079a48f62d0d79b96d5ed1ffb86610561b10d5c4236280b01f8f1f406", + "0x419b34eca1440c847f7bff9e948c9913075d8e13c270e67f64380a3f31de9bb2", + "0x2f6e4fee667acaa81ba8e51172b8329ed936d57e9756fb31f635632dbc2709b7", + "0xdd5afc79e8540fcee6a896c43887bd59c9de5d61b3d1b86539faeb41a14b251d", + "0xc707bed926a46cc451a6b05e642b6098368dbdbf14528c4c28733d5d005af516", + "0x153e850b606eb8a05eacecc04db4b560d007305e664bbfe01595cb69d26b8597", + "0x1b91cc07570c812bb329d025e85ef520132981337d7ffc3d84003f81a90bf7a7", + "0x4ca32e77a12951a95356ca348639ebc451170280d979e91b13316844f65ed42a", + "0xe49ea1998e360bd68771bd69c3cd4cf406b41ccca4386378bec66ea210c40084", + "0x01aaffbde1a672d253e0e317603c2dc1d0f752100d9e853f840bca96e57f314c", + "0x170d0befcbbaafb317c8684213a4989368332f66e889824cc4becf148f808146", + "0x56f973308edf5732a60aa3e7899ae1162c7a2c7b528c3315237e20f9125b34e0", + "0x66c54fd5f6d480cab0640e9f3ec1a4eafbafc0501528f57bb0d5c78fd03068ef", + "0xaca6c83f665c64d76fbc4858da9f264ead3b6ecdc3d7437bb800ef7240abffb9", + "0xf1d4e02e7c85a92d634d16b12dc99e1d6ec9eae3d8dfbca77e7c609e226d0ce7", + "0x094352545250e843ced1d3c6c7957e78c7d8ff80c470974778930adbe9a4ed1a", + "0x76efa93070d78b73e12eb1efa7f36d49e7944ddcc3a043b916466ee83dca52ce", + "0x1772a2970588ddb584eadf02178cdb52a98ab6ea8a4036d29e59f179d7ba0543", + "0xe4bbf2d97d65331ac9f680f864208a9074d1def3c2433458c808427e0d1d3167", + "0x8ccfb5252b22c77ea631e03d491ea76eb9b74bc02072c3749f3e9d63323b44df", + "0x9e212a9bdf4e7ac0730a0cecd0f6cc49afc7e3eca7a15d0f5f5a68f72e45363b", + "0x52e548ea6445aae3f75509782a7ab1f4f02c2a85cdd0dc928370f8c76ae8802d", + "0xb62e7d73bf76c07e1a6f822a8544b78c96a6ba4f5c9b792546d94b56ca12c8b9", + "0x595cb0e985bae9c59af151bc748a50923921a195bbec226a02157f3b2e066f5b", + "0x1c7aa6b36f402cec990bafefbdbb845fc6c185c7e08b6114a71dd388fe236d32", + "0x01ee2ff1a1e88858934a420258e9478585b059c587024e5ec0a77944821f798c", + "0x420a963a139637bffa43cb007360b9f7d305ee46b6a694b0db91db09618fc2e5", + "0x5a8e2ad20f8da35f7c885e9af93e50009929357f1f4b38a6c3073e8f58fae49e", + "0x52a405fdd84c9dd01d1da5e9d1c4ba95cb261b53bf714c651767ffa2f9e9ad81", + "0xa1a334c901a6d5adc8bac20b7df025e906f7c4cfc0996bfe2c62144691c21990", + "0xb789a00252f0b34bded3cb14ae969effcf3eb29d97b05a578c3be8a9e479c213", + "0xb9dbf7e9ddb638a515da245845bea53d07becdf3f8d1ec17de11d495624c8eab", + "0xaf566b41f5ed0c026fa8bc709533d3fa7a5c5d69b03c39971f32e14ab523fa3d", + "0x8121e0b2d9b106bb2aefd364fd6a450d88b88ee1f5e4aad7c0fcd8508653a112", + "0x8581c1be74279216b93e0a0d7272f4d6385f6f68be3eef3758d5f68b62ee7b6c", + "0x85386f009278f9a1f828404fa1bbfa02dfb9d896554f0a52678eb6ec8feadc55", + "0xf483ed167d92a0035ac65a1cfdb7906e4952f74ae3a1d86324d21f241daffcb7", + "0x3872485e2a520a350884accd990a1860e789dd0d0664ad14f50186a92c7be7be", + "0xc6c1a3301933019105f5650cabcb22bfbf221965ffcfc1329315b24ea3d77fd4", + "0xcee901330a60d212a867805ce0c28f53c6cc718f52156c9e74390d18f5df6280", + "0xa67ae793b1cd1a828a607bae418755c84dbb61adf00833d4c61a94665363284f", + "0x80d8159873b517aa6815ccd7c8ed7cfb74f84298d703a6c5a2f9d7d4d984ddde", + "0x1de5a8b915f2d9b45c97a8e134871e2effb576d05f4922b577ade8e3cd747a79", + "0x6ea17c5ece9b97dddb8b2101b923941a91e4b35e33d536ab4ff15b647579e1f5", + "0xcb78631e09bc1d79908ce1d3e0b6768c54b272a1a5f8b3b52485f98d6bba9245", + "0xd7c38f9d3ffdc626fe996218c008f5c69498a8a899c7fd1d63fbb03e1d2a073f", + "0x72cdef54267088d466244a92e4e6f10742ae5e6f7f6a615eef0da049a82068f9", + "0x60b3c490ba8c502656f9c0ed37c47283e74fe1bc7f0e9f651cbc76552a0d88eb", + "0x56bd0c66987a6f3761d677097be9440ea192c1cb0f5ec38f42789abe347e0ea9", + "0x3caac3e480f62320028f6f938ee147b4c78e88a183c464a0c9fb0df937ae30c1", + "0x7a4d2f11bddda1281aba5a160df4b814d23aef07669affe421a861fac2b4ec0f", + "0x9bb4d11299922dc309a4523959298a666ebe4063a9ee3bad1b93988ed59fb933", + "0x957323fffbaf8f938354662452115ae5acba1290f0d3f7b2a671f0359c109292", + "0x877624e31497d32e83559e67057c7a605fb888ed8e31ba68e89e02220eac7096", + "0x8456546ae97470ff6ea98daf8ae632e59b309bd3ff8e9211f7d21728620ed1e5", + "0xbacb26f574a00f466ce354e846718ffe3f3a64897d14d5ffb01afcf22f95e72b", + "0x0228743a6e543004c6617bf2c9a7eba1f92ebd0072fb0383cb2700c3aed38ba0", + "0x04f093f0f93c594549436860058371fb44e8daf78d6e5f563ba63a46b61ddbf0", + "0x0ba17c1ec93429ceaff08eb81195c9844821b64f2b5363926c2a6662f83fb930", + "0xd71605d8446878c677f146837090797e888416cfc9dc4e79ab11776cc6639d3f", + "0x33dde958dc5a6796138c453224d4d6e7f2ae740cceef3b52a8b669eb4b9691a1", + "0x3c39838295d1495e90e61ce59f6fcc693b31c292d02d31759719df6fe3214559", + "0x8aecc66f38644296cf0e6693863d57a243a31a4929130e22ab44cb6157b1af41", + "0xdf7153a7eab9521f2b37124067166c72de8f342249ac0e0f5350bd32f1251053", + "0xa498840b58897cf3bed3981b94c86d85536dfebbc437d276031ebd9352e171eb", + "0xb1df15a081042ab665458223a0449ffc71a10f85f3d977beb20380958fd92262", + "0x15d3bdbdee2a61b01d7a6b72a5482f6714358eedf4bece7bb8458e100caf8fba", + "0x0c96b7a0ea09c3ef758424ffb93654ce1520571e32e1f83aecbeded2388c3a7a", + "0xb4a3a8023266d141ecd7c8a7ca5282a825410b263bc11c7d6cab0587c9b5446e", + "0xf38f535969d9592416d8329932b3a571c6eacf1763de10fb7b309d3078b9b8d4", + "0x5a1e7b1c3b3943158341ce6d7f9f74ae481975250d89ae4d69b2fcd4c092eb4e", + "0xdad31e707d352f6cca78840f402f2ac9292094b51f55048abf0d2badfeff5463", + "0x097e290170068e014ceda3dd47b28ede57ff7f916940294a13c9d4aa2dc98aad", + "0x22e2dcedb6bb7f8ace1e43facaa502daa7513e523be98daf82163d2a76a1e0be", + "0x7ef2b211ab710137e3e8c78b72744bf9de81c2adde007aef6e9ce92a05e7a2c5", + "0x49b427805fc5186f31fdd1df9d4c3f51962ab74e15229e813072ec481c18c717", + "0xe60f6caa09fa803d97613d58762e4ff7f22f47d5c30b9d0116cdc6a357de4464", + "0xab3507b37ee92f026c72cc1559331630bc1c7335b374e4418d0d02687df1a9dd", + "0x50825ae74319c9adebc8909ed7fc461702db8230c59975e8add09ad5e7a647ab", + "0x0ee8e9c1d8a527a42fb8c2c8e9e51faf727cffc23ee22b5a95828f2790e87a29", + "0x675c21c290ddb40bec0302f36fbcd2d1832717a4bc05d113c6118a62bc8f9aca", + "0x580bafab24f673317b533148d7226d485e211eaa3d6e2be2529a83ca842b58a7", + "0x540e474776cae597af24c147dc1ae0f70a6233e98cf5c3ce31f38b830b75c99a", + "0x36eaf9f286e0f356eaaf8d81f71cc52c81d9ebc838c3b4859009f8567a224d16", + "0x0e2cbbb40954be047d02b1450a3dbd2350506448425dc25fd5faf3a66ee8f5c4", + "0x7eb0390cfe4c4eb120bbe693e87adc8ecab51d5fd8ce8f911c8ff07fad8cbe20", + "0xbf77589f5c2ebb465b8d7936f6260a18a243f59bd87390ee22cf579f6f020285", + "0x695b96bb28693f6928777591ef64146466d27521280a295936a52ec60707c565", + "0x22a0d018cbd4274caa8b9e7fb132e0a7ed787874046ca683a7d81d1c7c8b8f15", + "0x84092b122bb35e5ad85407b4b55f33707b86e0238c7970a8583f3c44308ed1d9", + "0xea346067ca67255235f9cae949f06e4b6c93846a7abc7c8c8cd786e9c4b3e4bc", + "0xa6df0716b125dc696b5d0e520cb49c1c089397c754efc146792e95bc58cc7159", + "0x7377b5d3953029fc597fb10bb6479ee34133d38f08783fbb61c7d070f34ea66f", + "0x7d79b00ffb976a10cd24476a394c8ed22f93837c51a58a3ddc7418153a5a8ea1", + "0x01e55182e80dff26cc3e06bb736b4a63745bde8ae28c604fa7fb97d99de5f416", + "0x062a2d5a207f8d540764d09648afecbf5033b13aec239f722b9033a762acf18b", + "0x48be60a3221d98b4d62f0b89d3bef74c70878dd65c6f79b34c2c36d0ddaa1da0", + "0x41e11f33543cf045c1a99419379ea31523d153bdf664549286b16207b9648c85", + "0xeef4d30b4700813414763a199e7cc6ab0faec65ef8b514faa01c6aa520c76334", + "0xea7cfe990422663417715e7859fc935ca47f47c943a1254044b6bc5934c94bc8", + "0xbbd3c834e5403b98a0ca346c915a23310f3d58880786628bc6cfbe05ba29c3c5", + "0xe216379f385bc9995ae0f37f1409a78d475c56b8aeb4ee434326724ec20124f7", + "0xdd328a1eee19d09b6fef06e252f8ad0ae328fbf900ef745f5950896803a3899d", + "0xa16fde34b0d743919feb0781eca0c525a499d279119af823cb3a8817000335db", + "0x7a28d108c59b83b12c85cd9aabc1d1d994a9a0329ae7b64a32aadcd61ebe50e3", + "0xb28bc82fceae74312eb837a805f0a8a01c0f669b99bb03fde31c4d58bedff89b", + "0x1b0d8f37d349781e846900b51a90c828aa384afe9b8ee1f88aeb8dba4b3168f2", + "0xbfd0301ff964c286c3331a30e09e0916da6f484e9c9596dbf1cae3cc902dbf9e", + "0xbb8254cb9ef6b485b8fb6caeafe45f920affc30f6b9d671e9a454530536f4fef", + "0xcad2317cf63dfa7147ded5c7e15f5f72e78f42d635e638f1ece6bc722ca3638b", + "0xb6c6e856fd45117f54775142f2b38f31114539d8943bcbcf823f6c7650c001e4", + "0x869f1baa35684c8f67a5bc99b294187852e6c85243a2f36481d0891d8b043020", + "0x14c6ccf145ee40ff56e3810058d2fba9a943ffc7c7087c48a08b2451c13dc788", + "0x263c1bcb712890f155b7e256cefa4abf92fe4380f3ffc11c627d5e4e30864d18", + "0x69f4eaf655e31ad7f7a725cd415ce7e45dd4a8396ac416950d42ed33155c3487", + "0x47e8eec2c5e33c9a54fe1f9b09e7744b614fb16531c36b862aa899424be13b05", + "0x5c985de270e62c44f0b49157882e8e83641b906ce47959e337fe8423e125a2eb", + "0x4e13b11e13202439bb5de5eea3bb75d2d7bf90f91411163ade06161a9cf424db", + "0x583a8fa159bb74fa175d72f4e1705e9a3b8ffe26ec5ad6e720444b99288f1213", + "0x903d2a746a98dfe2ee2632606d57a9b0fa6d8ccd895bb18c2245fd91f8a43676", + "0xa35a51330316012d81ec7249e3f2b0c9d7fcbb99dd98c62fe880d0a152587f51", + "0x33818a7beb91730c7b359b5e23f68a27b429967ea646d1ea99c314353f644218", + "0x183650af1e0b67f0e7acb59f8c72cc0e60acc13896184db2a3e4613f65b70a8b", + "0x857ff2974bef960e520937481c2047938a718cea0b709282ed4c2b0dbe2ef8fa", + "0x95a367ecb9a401e98a4f66f964fb0ece783da86536410a2082c5dbb3fc865799", + "0x56c606a736ac8268aedadd330d2681e7c7919af0fe855f6c1c3d5c837aa92338", + "0x5c97f7abf30c6d0d4c23e762c026b94a6052a444df4ed942e91975419f68a3a4", + "0x0b571de27d2022158a3128ae44d23a8136e7dd2dee74421aa4d6ed15ee1090a0", + "0xa17f6bc934a2f3c33cea594fee8c96c1290feec934316ebbbd9efab4937bf9f9", + "0x9ff57d70f27aad7281841e76435285fd27f10dad256b3f5cabde4ddc51b70eff", + "0xafa3071a847215b3ccdf51954aa7cb3dd2e6e2a39800042fc42009da705508b2", + "0x5e3bea33e4ac6f7c50a077d19571b1796e403549b1ce7b15e09905a0cc5a4acf", + "0x0dc7ba994e632ab95f3ecb7848312798810cf761d1c776181882d17fd6dda075", + "0xb4f7158679dad9f7370a2f64fbe617a40092849d17453b4f50a93ca8c6885844", + "0x094564b00f53c6f27c121fd8adfe1685b258b259e585a67b57c85efb804c57b2", + "0x9cd21a4249ba3fccffad550cdb8409dc12d8b74a7192874b6bafe2363886f318", + "0xbb22e0dad55cb315c564c038686419d40ef7f13af2143a28455bf445f6e10393", + "0x2a71d5e00821178c2cd39e7501e07da5cca6680eb7cdbe996f52dccafadb3735", + "0x9619406093b121e044a5b403bb1713ae160aeb52ad441f82dc6c63e4b323b969", + "0x3b8bd1d82c6d67ae707e19b889f1cb1f7bba912f12ae4284298f3a70c3644c79", + "0xd7a70c50d47d48785b299dbea01bf03ef18b8495de3c35cb265bc8f3295c4e15", + "0x8802ecce8dd6b6190af8ac79aafda3479c29f548d65e5798c0ca51a529b19108", + "0x4b630e1df52ec5fd650f4a4e76b3eeddda39e1e9eab996f6d3f02eefdf690990", + "0x0bfbff60fcf7f411d469f7f6f0a58ca305fd84eb529ee3ac73c00174793d723e", + "0x535f78b5f3a99a1c498e2c19dc1acb0fbbaba8972ba1d7d66936c28ab3667ebe", + "0x06ba92d8129db98fec1b75f9489a394022854f22f2e9b9450b187a6fc0d94a86", + "0xb7ae275ba10f80fb618a2cf949d5ad2e3ae24eb2eb37dcf1ec8c8b148d3ba27f", + "0xb275579bcf2584d9794dd3fc7f999902b13d33a9095e1980d506678e9c263de1", + "0x843ccd52a81e33d03ad2702b4ef68f07ca0419d4495df848bff16d4965689e48", + "0xde8b779ca7250f0eb867d5abdffd1d28c72a5a884d794383fc93ca40e5bf6276", + "0x6b789a2befccb8788941c9b006e496b7f1b03dbb8e530ba339db0247a78a2850", + "0xfccd4dca80bc52f9418f26b0528690255e320055327a34b50caf088235d2f660", + "0x18479ebfbe86c1e94cd05c70cb6cace6443bd9fdac7e01e9c9535a9e85141f2f", + "0x5350c8f3296441db954a261238c88a3a0c51ab418a234d566985f2809e211148", + "0xa5636614135361d03a381ba9f6168e2fd0bd2c1105f9b4e347c414df8759dea3", + "0xe7bb69e600992e6bd41c88a714f50f450153f1a05d0ddb4213a3fc4ba1f48c3f", + "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", + "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", + ], + compressed_lamport_sk: + "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", + child_sk: + "7419543105316279183937430842449358701327973165530407166294956473095303972104" + } + } +} From 030702f42fdb7a2a9355cdd10cfc0acc0ae724e2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 15:19:20 +1000 Subject: [PATCH 055/118] More progress --- eth2/utils/eth2_keystore/src/path.rs | 101 ++++++++++++++++++++------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 699278fb332..acd985059eb 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -19,22 +19,15 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { - let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); - - // TODO: don't do all this extra work with extraction. - let lamport_bytes = lamport - .iter_chunks() - .map(|a| a.to_vec()) - .flatten() - .collect::>(); - // TODO: justify 48. - mod_r(&lamport_bytes[0..48]) + let okm = &hkdf_expand_basic( + hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), + 48, + ); + mod_r(&okm) } fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(bytes.len(), HASH_SIZE); - let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); @@ -42,7 +35,7 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { debug_assert!(x.len() <= HASH_SIZE); let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output[HASH_SIZE - x.len()..].copy_from_slice(&x); output } @@ -115,6 +108,37 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } +// TODO: zeroize. +fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; HASH_SIZE]; + + let mut hasher = Sha256::new(); + hasher.input(prk); + hasher.input(&[]); // TODO: remove this? + hasher.input(&[1]); + hasher.result(&mut okm[..]); + + let mut i = 0; + while okm.len() < l { + i += 1; + + let mut hasher = Sha256::new(); + hasher.input(prk); + if i == 1 { + hasher.input(&[]); // TODO: remove this line? + } else { + hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? + } + hasher.input(&[i]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.extend_from_slice(&mut digest); + } + + okm[0..l].to_vec() +} + fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -131,29 +155,58 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec>, + lamport_1: Vec>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + #[test] + fn eip2333_intermediate_vector() { + let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); + assert_eq!( + &master_sk[..], + &vectors.master_sk[..], + "master_sk should match" + ); + } + struct RawTestVector { seed: &'static str, master_sk: &'static str, child_index: u32, lamport_0: Vec<&'static str>, lamport_1: Vec<&'static str>, - compressed_lamport_sk: &'static str, + compressed_lamport_pk: &'static str, child_sk: &'static str, } - struct TestVector { - seed: Vec, - master_sk: Vec, - child_index: u32, - lamport_0: Vec<[u8; HASH_SIZE]>, - lamport_1: Vec<[u8; HASH_SIZE]>, - compressed_lamport_pk: Vec, - child_sk: Vec, + fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") + } + + fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() } impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { - todo!() + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + lamport_0: raw.lamport_0.into_iter().map(hex_to_vec).collect(), + lamport_1: raw.lamport_1.into_iter().map(hex_to_vec).collect(), + compressed_lamport_pk: hex_to_vec(raw.compressed_lamport_pk), + child_sk: int_to_vec(raw.child_sk), + } } } @@ -677,7 +730,7 @@ mod test { "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", ], - compressed_lamport_sk: + compressed_lamport_pk: "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" From b474789039df543a40b901af0e19f626729673ad Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:29:35 +1000 Subject: [PATCH 056/118] Passing intermediate test vectors --- .../eth2_keystore/src/lamport_secret_key.rs | 49 ++++++++ eth2/utils/eth2_keystore/src/path.rs | 106 ++++++++++-------- 2 files changed, 108 insertions(+), 47 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/lamport_secret_key.rs diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs new file mode 100644 index 00000000000..01329d7ede9 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs @@ -0,0 +1,49 @@ +use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use std::iter::Iterator; +use zeroize::Zeroize; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); + +impl LamportSecretKey { + /// Instantiates `Self` with all chunks set to zero. + pub fn zero() -> Self { + Self(vec![[0; HASH_SIZE]; LAMPORT_ARRAY_SIZE as usize]) + } + + /// Instantiates `Self` from a flat buffer of `HASH_SIZE * LAMPORT_ARRAY_SIZE` bytes. + /// + /// ## Panics + /// + /// If an incorrect number of bytes is supplied. + pub fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!( + bytes.len(), + HASH_SIZE * LAMPORT_ARRAY_SIZE as usize, + "incorrect byte length" + ); + + let mut this = Self::zero(); + + for i in 0..LAMPORT_ARRAY_SIZE { + let iu = i as usize; + this.get_mut_chunk(i) + .copy_from_slice(&bytes[iu * HASH_SIZE..(iu + 1) * HASH_SIZE]) + } + + this + } + + pub fn get_chunk(&self, i: u8) -> &[u8] { + &self.0[i as usize] + } + + pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { + &mut self.0[i as usize] + } + + pub fn iter_chunks(&self) -> impl Iterator { + self.0.iter() + } +} diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index acd985059eb..db3d2f9063d 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, sha2::Sha256}; +use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -9,6 +9,8 @@ pub const LAMPORT_ARRAY_SIZE: u8 = 255; pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; +pub const BLS_KEY_LEN: usize = 48; + fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } @@ -19,11 +21,9 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); // TODO: justify 48. - let okm = &hkdf_expand_basic( - hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), - 48, - ); + let okm = &hkdf_expand(&prk, BLS_KEY_LEN); mod_r(&okm) } @@ -44,8 +44,8 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let not_ikm = flip_bits(ikm); let lamports = [ - ikm_to_lamport_sk(ikm, &salt), - ikm_to_lamport_sk(¬_ikm, &salt), + ikm_to_lamport_sk(&salt, ikm), + ikm_to_lamport_sk(&salt, ¬_ikm), ]; let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; @@ -74,20 +74,24 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { - hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) + let prk = hkdf_extract(salt, ikm); + let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(&okm) } -fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { - let mut hasher = Sha256::new(); - hasher.input(salt); - hasher.input(ikm); - - let mut digest = vec![0; HASH_SIZE]; - hasher.result(&mut digest); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { + let mut prk = [0; HASH_SIZE]; + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); + prk +} - digest.into() +fn hkdf_expand(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; l]; + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); + okm } +/* fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { let mut okm = LamportSecretKey::zero(); @@ -107,37 +111,7 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } - -// TODO: zeroize. -fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; HASH_SIZE]; - - let mut hasher = Sha256::new(); - hasher.input(prk); - hasher.input(&[]); // TODO: remove this? - hasher.input(&[1]); - hasher.result(&mut okm[..]); - - let mut i = 0; - while okm.len() < l { - i += 1; - - let mut hasher = Sha256::new(); - hasher.input(prk); - if i == 1 { - hasher.input(&[]); // TODO: remove this line? - } else { - hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? - } - hasher.input(&[i]); - - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.extend_from_slice(&mut digest); - } - - okm[0..l].to_vec() -} +*/ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -168,12 +142,50 @@ mod test { #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); assert_eq!( &master_sk[..], &vectors.master_sk[..], "master_sk should match" ); + + let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + assert_eq!( + lamport_0 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_0, + "lamport_0 should match" + ); + + let lamport_1 = ikm_to_lamport_sk( + &vectors.child_index.to_be_bytes()[..], + &flip_bits(&master_sk), + ); + assert_eq!( + lamport_1 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_1, + "lamport_1 should match" + ); + + let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + assert_eq!( + &compressed_lamport_pk[..], + &vectors.compressed_lamport_pk[..], + "compressed_lamport_pk should match" + ); + + let child_sk = derive_child_sk(&master_sk, vectors.child_index); + assert_eq!( + &child_sk[..], + &vectors.child_sk[..], + "child_sk should match" + ); } struct RawTestVector { From 7778ce648a7d08d01d8707db27d47237c6afacf9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:54:33 +1000 Subject: [PATCH 057/118] Tidy, add comments --- eth2/utils/eth2_keystore/src/path.rs | 83 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index db3d2f9063d..4060d78ff51 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -4,29 +4,53 @@ use num_bigint::BigUint; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; + /// The size of the lamport array. +/// +/// Indirectly defined in EIP-2333. pub const LAMPORT_ARRAY_SIZE: u8 = 255; +/// The order of the BLS 12-381 curve. +/// +/// Defined in EIP-2333. pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; -pub const BLS_KEY_LEN: usize = 48; +/// The `L` value used in the `hdkf_mod_r` function. +/// +/// In EIP-2333 this value is defined as: +/// +/// `ceil((1.5 * ceil(log2(r))) / 8)` +pub const MOD_R_L: usize = 48; +/// Derives the "master" BLS secret key from some `seed` bytes. +/// +/// Equivalent to `derive_master_SK` in EIP-2333. fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } +/// From the given `parent_sk`, derives a child key at index`. +/// +/// Equivalent to `derive_child_SK` in EIP-2333. fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); hkdf_mod_r(&compressed_lamport_pk) } +/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// BLS private key within the order of the BLS-381 curve. +/// +/// Equivalent to `HKDF_mod_r` in EIP-2333. fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - // TODO: justify 48. - let okm = &hkdf_expand(&prk, BLS_KEY_LEN); + let okm = &hkdf_expand(&prk, MOD_R_L); mod_r(&okm) } +/// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the +/// BLS-381 curve. +/// +/// This function is a part of the `HKDF_mod_r` function in EIP-2333. fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); @@ -39,6 +63,9 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { output } +/// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). +/// +/// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); @@ -73,48 +100,42 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { compressed_lamport_pk } +/// Generates a Lamport secret key from the `ikm` (initial key material). +/// +/// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); LamportSecretKey::from_bytes(&okm) } +/// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { let mut prk = [0; HASH_SIZE]; crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); prk } +/// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_expand(prk: &[u8], l: usize) -> Vec { let mut okm = vec![0; l]; crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); okm } -/* -fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { - let mut okm = LamportSecretKey::zero(); - - for i in 0..LAMPORT_ARRAY_SIZE { - let mut hasher = Sha256::new(); - - hasher.input(prk); - - if let Some(prev) = i.checked_sub(1) { - hasher.input(okm.get_chunk(prev)) - } - - hasher.input(&[i + 1]); - - hasher.result(okm.get_mut_chunk(i)); - } - - okm -} -*/ - +/// Flips each bit in the `input`. +/// +/// Equivalent to `flip_bits` in EIP-2333. +/// +/// ## Panics +/// +/// If `input` is not32-bytes. fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(input.len(), HASH_SIZE); + assert_eq!(input.len(), HASH_SIZE); let mut output = [0; HASH_SIZE]; @@ -129,6 +150,7 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + /// Contains the test vectors in a format that's easy for us to test against. struct TestVector { seed: Vec, master_sk: Vec, @@ -139,6 +161,9 @@ mod test { child_sk: Vec, } + /// "Test Vector with Intermediate values" from: + /// + /// https://eips.ethereum.org/EIPS/eip-2333 #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); @@ -188,6 +213,7 @@ mod test { ); } + /// Struct to deal with easy copy-paste from specification test vectors. struct RawTestVector { seed: &'static str, master_sk: &'static str, @@ -198,16 +224,20 @@ mod test { child_sk: &'static str, } + /// Converts 0x-prefixed hex to bytes. fn hex_to_vec(hex: &str) -> Vec { hex::decode(&hex[2..]).expect("should decode hex as vec") } + /// Converts an integer represented as a string to a big-endian byte array. fn int_to_vec(int_str: &str) -> Vec { BigUint::parse_bytes(int_str.as_bytes(), 10) .expect("must be able to parse int") .to_bytes_be() } + /// Converts from a format that's easy to copy-paste from the spec into a format that's easy to + /// test with. impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { TestVector { @@ -222,6 +252,7 @@ mod test { } } + /// Returns the copy-paste values from the spec. fn get_raw_vector() -> RawTestVector { RawTestVector { seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", From 36f8b571280419e2d64509b13c5fcab6c60327b6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:06:17 +1000 Subject: [PATCH 058/118] Add DerivedKey structs --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 9980cd6b75b..8c8e44e39db 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -12,4 +12,5 @@ pub mod json_keystore; pub use keystore::{Error, Keystore, KeystoreBuilder}; pub use password::Password; +pub use path::MasterKey; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 4060d78ff51..08d077a87f0 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ -use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; +use crate::lamport_secret_key::LamportSecretKey; +use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -22,6 +22,27 @@ pub const R: &str = "52435875175126190479447740508185965837690552500527637822603 /// `ceil((1.5 * ceil(log2(r))) / 8)` pub const MOD_R_L: usize = 48; +/// A BLS secret key that is derived from some `seed`, or generated as a child from some other +/// `DerivedKey`. +pub struct DerivedKey([u8; HASH_SIZE]); + +impl DerivedKey { + /// Instantiates `Self` from some seed of any length. + pub fn from_seed(seed: &[u8]) -> Self { + Self(derive_master_sk(seed)) + } + + /// Derives a child key from the secret `Self` at some `index`. + pub fn derive_child(&self, index: u32) -> DerivedKey { + Self(derive_child_sk(&self.0, index)) + } + + /// Returns the secret BLS key in `self`. + pub fn secret(&self) -> &[u8] { + &self.0 + } +} + /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. From ed435f9fe63f41c1f36c00c60e742486960125bb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:21:27 +1000 Subject: [PATCH 059/118] Move key derivation into own crate --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + eth2/utils/eth2_key_derivation/Cargo.toml | 15 +++++++++++++++ .../src/lamport_secret_key.rs | 4 ---- eth2/utils/eth2_key_derivation/src/lib.rs | 7 +++++++ .../src/path.rs | 0 eth2/utils/eth2_keystore/src/lib.rs | 3 --- 7 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 eth2/utils/eth2_key_derivation/Cargo.toml rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/lamport_secret_key.rs (93%) create mode 100644 eth2/utils/eth2_key_derivation/src/lib.rs rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b32c2057a7d..d19862eb5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,16 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth2_keystore" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 593bcd255a6..b2ee8e1120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/deposit_contract", "eth2/utils/eth2_config", "eth2/utils/eth2_interop_keypairs", + "eth2/utils/eth2_key_derivation", "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", "eth2/utils/logging", diff --git a/eth2/utils/eth2_key_derivation/Cargo.toml b/eth2/utils/eth2_key_derivation/Cargo.toml new file mode 100644 index 00000000000..61bfb5b8166 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "eth2_key_derivation" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "0.2.36" +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +num-bigint = "0.2.6" + +[dev-dependencies] +hex = "0.3" diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs similarity index 93% rename from eth2/utils/eth2_keystore/src/lamport_secret_key.rs rename to eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 01329d7ede9..e3a8a93990d 100644 --- a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -35,10 +35,6 @@ impl LamportSecretKey { this } - pub fn get_chunk(&self, i: u8) -> &[u8] { - &self.0[i as usize] - } - pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs new file mode 100644 index 00000000000..d25469000e9 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -0,0 +1,7 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + +mod lamport_secret_key; +mod path; + +pub use path::DerivedKey; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_key_derivation/src/path.rs similarity index 100% rename from eth2/utils/eth2_keystore/src/path.rs rename to eth2/utils/eth2_key_derivation/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 8c8e44e39db..36728103eb2 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,14 +3,11 @@ mod derived_key; mod keystore; -mod lamport_secret_key; mod password; -mod path; mod plain_text; pub mod json_keystore; pub use keystore::{Error, Keystore, KeystoreBuilder}; pub use password::Password; -pub use path::MasterKey; pub use uuid::Uuid; From 2c9bf1ebb075d504272c6fd9037f5dcfab6054e2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 10:03:27 +1000 Subject: [PATCH 060/118] Add zeroize structs --- .../src/{path.rs => derived_key.rs} | 90 ++++++++++--------- .../src/lamport_secret_key.rs | 2 +- eth2/utils/eth2_key_derivation/src/lib.rs | 10 ++- .../eth2_key_derivation/src/secret_bytes.rs | 23 +++++ .../eth2_key_derivation/src/secret_hash.rs | 22 +++++ 5 files changed, 98 insertions(+), 49 deletions(-) rename eth2/utils/eth2_key_derivation/src/{path.rs => derived_key.rs} (95%) create mode 100644 eth2/utils/eth2_key_derivation/src/secret_bytes.rs create mode 100644 eth2/utils/eth2_key_derivation/src/secret_hash.rs diff --git a/eth2/utils/eth2_key_derivation/src/path.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs similarity index 95% rename from eth2/utils/eth2_key_derivation/src/path.rs rename to eth2/utils/eth2_key_derivation/src/derived_key.rs index 08d077a87f0..cd1a5b65e2c 100644 --- a/eth2/utils/eth2_key_derivation/src/path.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -1,4 +1,6 @@ -use crate::lamport_secret_key::LamportSecretKey; +use crate::{ + lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, secret_hash::SecretHash, +}; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; @@ -24,7 +26,7 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. -pub struct DerivedKey([u8; HASH_SIZE]); +pub struct DerivedKey(SecretHash); impl DerivedKey { /// Instantiates `Self` from some seed of any length. @@ -34,69 +36,70 @@ impl DerivedKey { /// Derives a child key from the secret `Self` at some `index`. pub fn derive_child(&self, index: u32) -> DerivedKey { - Self(derive_child_sk(&self.0, index)) + Self(derive_child_sk(self.0.as_bytes(), index)) } /// Returns the secret BLS key in `self`. pub fn secret(&self) -> &[u8] { - &self.0 + self.0.as_bytes() } } /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. -fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { +fn derive_master_sk(seed: &[u8]) -> SecretHash { hkdf_mod_r(seed) } /// From the given `parent_sk`, derives a child key at index`. /// /// Equivalent to `derive_child_SK` in EIP-2333. -fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); - hkdf_mod_r(&compressed_lamport_pk) + hkdf_mod_r(compressed_lamport_pk.as_bytes()) } -/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// From the `ikm` (initial key material), performs a HKDF-Extract and HKDF-Expand to generate a /// BLS private key within the order of the BLS-381 curve. /// /// Equivalent to `HKDF_mod_r` in EIP-2333. -fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { +fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - let okm = &hkdf_expand(&prk, MOD_R_L); - mod_r(&okm) + let okm = &hkdf_expand(prk.as_bytes(), MOD_R_L); + mod_r(okm.as_bytes()) } /// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the /// BLS-381 curve. /// /// This function is a part of the `HKDF_mod_r` function in EIP-2333. -fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { +fn mod_r(bytes: &[u8]) -> SecretHash { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); debug_assert!(x.len() <= HASH_SIZE); - let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - x.len()..].copy_from_slice(&x); + let mut output = SecretHash::zero(); + output.as_mut_bytes()[HASH_SIZE - x.len()..].copy_from_slice(&x); output } /// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). /// /// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. -fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); let lamports = [ ikm_to_lamport_sk(&salt, ikm), - ikm_to_lamport_sk(&salt, ¬_ikm), + ikm_to_lamport_sk(&salt, not_ikm.as_bytes()), ]; - let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + let mut lamport_pk = SecretBytes::zero(HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2); + let pk_bytes = lamport_pk.as_mut_bytes(); lamports .iter() @@ -104,19 +107,15 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { .flatten() .enumerate() .for_each(|(i, chunk)| { - let output_slice = lamport_pk - .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) - .expect("lamport_pk must have adequate capacity"); - let mut hasher = Sha256::new(); hasher.input(chunk); - hasher.result(output_slice); + hasher.result(&mut pk_bytes[i * HASH_SIZE..(i + 1) * HASH_SIZE]); }); - let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut compressed_lamport_pk = SecretHash::zero(); let mut hasher = Sha256::new(); - hasher.input(&lamport_pk); - hasher.result(&mut compressed_lamport_pk); + hasher.input(lamport_pk.as_bytes()); + hasher.result(compressed_lamport_pk.as_mut_bytes()); compressed_lamport_pk } @@ -126,25 +125,25 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { /// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); - let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); - LamportSecretKey::from_bytes(&okm) + let okm = hkdf_expand(prk.as_bytes(), HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(okm.as_bytes()) } /// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { - let mut prk = [0; HASH_SIZE]; - crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> SecretHash { + let mut prk = SecretHash::zero(); + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, prk.as_mut_bytes()); prk } /// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_expand(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; l]; - crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); +fn hkdf_expand(prk: &[u8], l: usize) -> SecretBytes { + let mut okm = SecretBytes::zero(l); + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], okm.as_mut_bytes()); okm } @@ -154,14 +153,15 @@ fn hkdf_expand(prk: &[u8], l: usize) -> Vec { /// /// ## Panics /// -/// If `input` is not32-bytes. -fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { +/// If `input` is not 32-bytes. +fn flip_bits(input: &[u8]) -> SecretHash { assert_eq!(input.len(), HASH_SIZE); - let mut output = [0; HASH_SIZE]; + let mut output = SecretHash::zero(); + let output_bytes = output.as_mut_bytes(); for (i, byte) in input.iter().enumerate() { - output[i] = !byte + output_bytes[i] = !byte } output @@ -191,12 +191,13 @@ mod test { let master_sk = derive_master_sk(&vectors.seed); assert_eq!( - &master_sk[..], + master_sk.as_bytes(), &vectors.master_sk[..], "master_sk should match" ); - let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + let lamport_0 = + ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], master_sk.as_bytes()); assert_eq!( lamport_0 .iter_chunks() @@ -208,7 +209,7 @@ mod test { let lamport_1 = ikm_to_lamport_sk( &vectors.child_index.to_be_bytes()[..], - &flip_bits(&master_sk), + flip_bits(master_sk.as_bytes()).as_bytes(), ); assert_eq!( lamport_1 @@ -219,16 +220,17 @@ mod test { "lamport_1 should match" ); - let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + let compressed_lamport_pk = + parent_sk_to_lamport_pk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &compressed_lamport_pk[..], + compressed_lamport_pk.as_bytes(), &vectors.compressed_lamport_pk[..], "compressed_lamport_pk should match" ); - let child_sk = derive_child_sk(&master_sk, vectors.child_index); + let child_sk = derive_child_sk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &child_sk[..], + child_sk.as_bytes(), &vectors.child_sk[..], "child_sk should match" ); diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index e3a8a93990d..2c64601e505 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -1,4 +1,4 @@ -use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs index d25469000e9..f6ed44c4df5 100644 --- a/eth2/utils/eth2_key_derivation/src/lib.rs +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -1,7 +1,9 @@ -//! Provides a JSON keystore for a BLS keypair, as specified by -//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +//! Provides path-based hierarchical BLS key derivation, as specified by +//! [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +mod derived_key; mod lamport_secret_key; -mod path; +mod secret_bytes; +mod secret_hash; -pub use path::DerivedKey; +pub use derived_key::DerivedKey; diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs new file mode 100644 index 00000000000..c4eee4b8f52 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -0,0 +1,23 @@ +use zeroize::Zeroize; + +/// Provides a wrapper around a `Vec` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + /// Instantiates `Self` with an all-zeros byte array of length `len`. + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs new file mode 100644 index 00000000000..9c1bbb27b8b --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -0,0 +1,22 @@ +use crate::derived_key::HASH_SIZE; +use zeroize::Zeroize; + +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretHash([u8; HASH_SIZE]); + +impl SecretHash { + /// Instantiates `Self` with all zeros. + pub fn zero() -> Self { + Self([0; HASH_SIZE]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} From bcbc31ddf97c456101b6e7f2d7116db8b6edd47a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:15 +1000 Subject: [PATCH 061/118] Return error for empty seed --- .../eth2_key_derivation/src/derived_key.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index cd1a5b65e2c..ea1eb1fe0b1 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -29,13 +29,23 @@ pub const MOD_R_L: usize = 48; pub struct DerivedKey(SecretHash); impl DerivedKey { - /// Instantiates `Self` from some seed of any length. - pub fn from_seed(seed: &[u8]) -> Self { - Self(derive_master_sk(seed)) + /// Instantiates `Self` from some secret seed bytes. + /// + /// The key is generated deterministically; the same `seed` will always return the same `Self`. + /// + /// ## Errors + /// + /// Returns `Err(())` if `seed.is_empty()`, otherwise always returns `Ok(self)`. + pub fn from_seed(seed: &[u8]) -> Result { + if seed.is_empty() { + Err(()) + } else { + Ok(Self(derive_master_sk(seed))) + } } /// Derives a child key from the secret `Self` at some `index`. - pub fn derive_child(&self, index: u32) -> DerivedKey { + pub fn child(&self, index: u32) -> DerivedKey { Self(derive_child_sk(self.0.as_bytes(), index)) } From 7f95ddb39888676fa65adb29dca70371113dc50d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:33 +1000 Subject: [PATCH 062/118] Add tests --- .../tests/eip2333_vectors.rs | 102 ++++++++++++++++++ eth2/utils/eth2_key_derivation/tests/tests.rs | 28 +++++ 2 files changed, 130 insertions(+) create mode 100644 eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs create mode 100644 eth2/utils/eth2_key_derivation/tests/tests.rs diff --git a/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs new file mode 100644 index 00000000000..42a3728b291 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs @@ -0,0 +1,102 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; +use num_bigint::BigUint; + +/// Contains the test vectors in a format that's easy for us to test against. +struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + child_sk: Vec, +} + +/// Struct to deal with easy copy-paste from specification test vectors. +struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + child_sk: &'static str, +} + +/// Converts from a format that's easy to copy-paste from the spec into a format that's easy to +/// test with. +impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + child_sk: int_to_vec(raw.child_sk), + } + } +} + +/// Converts 0x-prefixed hex to bytes. +fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") +} + +/// Converts an integer represented as a string to a big-endian byte array. +fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() +} + +/// Asserts that our code matches the given test vector. +fn assert_vector_passes(raw: RawTestVector) { + let vector: TestVector = raw.into(); + + let master = DerivedKey::from_seed(&vector.seed).unwrap(); + assert_eq!(master.secret(), &vector.master_sk[..], "master"); + + let child = master.child(vector.child_index); + assert_eq!(child.secret(), &vector.child_sk[..], "child"); +} + +/* + * The following test vectors are obtained from: + * + * https://eips.ethereum.org/EIPS/eip-2333 + */ + +#[test] +fn eip2333_test_case_0() { + assert_vector_passes(RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" + }) +} + +#[test] +fn eip2333_test_case_1() { + assert_vector_passes(RawTestVector { + seed: "0x3141592653589793238462643383279502884197169399375105820974944592", + master_sk: "46029459550803682895343812821003080589696405386150182061394330539196052371668", + child_index: 3141592653, + child_sk: "43469287647733616183478983885105537266268532274998688773496918571876759327260", + }) +} + +#[test] +fn eip2333_test_case_2() { + assert_vector_passes(RawTestVector { + seed: "0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00", + master_sk: "45379166311535261329029945990467475187325618028073620882733843918126031931161", + child_index: 4294967295, + child_sk: "46475244006136701976831062271444482037125148379128114617927607151318277762946", + }) +} + +#[test] +fn eip2333_test_case_3() { + assert_vector_passes(RawTestVector { + seed: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + master_sk: "31740500954810567003972734830331791822878290325762596213711963944729383643688", + child_index: 42, + child_sk: "51041472511529980987749393477251359993058329222191894694692317000136653813011", + }) +} diff --git a/eth2/utils/eth2_key_derivation/tests/tests.rs b/eth2/utils/eth2_key_derivation/tests/tests.rs new file mode 100644 index 00000000000..b18a7b0e267 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/tests.rs @@ -0,0 +1,28 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; + +#[test] +fn empty_seed() { + assert!( + DerivedKey::from_seed(&[]).is_err(), + "empty seed should fail" + ); +} + +#[test] +fn deterministic() { + assert_eq!( + DerivedKey::from_seed(&[42]).unwrap().secret(), + DerivedKey::from_seed(&[42]).unwrap().secret() + ); +} + +#[test] +fn children_deterministic() { + let master = DerivedKey::from_seed(&[42]).unwrap(); + assert_eq!( + master.child(u32::max_value()).secret(), + master.child(u32::max_value()).secret(), + ) +} From 9fda9f6c677ff6502a1322ecde957a243d22565b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:55:30 +1000 Subject: [PATCH 063/118] Tidy --- eth2/utils/eth2_key_derivation/src/derived_key.rs | 7 +++++++ .../eth2_key_derivation/src/lamport_secret_key.rs | 5 +++++ eth2/utils/eth2_key_derivation/src/secret_bytes.rs | 12 ++++++------ eth2/utils/eth2_key_derivation/src/secret_hash.rs | 4 +++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index ea1eb1fe0b1..b91f0bff800 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -3,6 +3,7 @@ use crate::{ }; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; +use zeroize::Zeroize; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; @@ -26,6 +27,12 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. +/// +/// Implements `Zeroize` on `Drop`. +// It's not strictly necessary that `DerivedKey` implements `Zeroize`, but it seems prudent to be a +// little over-cautious here; we don't require high-speed key generation at this stage. +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DerivedKey(SecretHash); impl DerivedKey { diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 2c64601e505..aa6dbb39323 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -2,6 +2,9 @@ use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; +/// A Lamport secret key as specified in [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +/// +/// Implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); @@ -35,10 +38,12 @@ impl LamportSecretKey { this } + /// Returns a reference to the `i`th `HASH_SIZE` chunk of `self`. pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } + /// Returns an iterator over `LAMPORT_ARRAY_SIZE` chunks of `HASH_SIZE` bytes. pub fn iter_chunks(&self) -> impl Iterator { self.0.iter() } diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs index c4eee4b8f52..5cedd05b1d3 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -1,6 +1,6 @@ use zeroize::Zeroize; -/// Provides a wrapper around a `Vec` that implements `Zeroize`. +/// Provides a wrapper around a `Vec` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretBytes(Vec); @@ -11,13 +11,13 @@ impl SecretBytes { Self(vec![0; len]) } - /// Returns a mutable reference to the underlying bytes. - pub fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } - /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } } diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs index 9c1bbb27b8b..0d9cfac1100 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_hash.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -1,7 +1,7 @@ use crate::derived_key::HASH_SIZE; use zeroize::Zeroize; -/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretHash([u8; HASH_SIZE]); @@ -12,10 +12,12 @@ impl SecretHash { Self([0; HASH_SIZE]) } + /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + /// Returns a mutable reference to the underlying bytes. pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 } From af3d9266b844ef71777f569a2694ad50425d64e7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 19:55:58 +1000 Subject: [PATCH 064/118] First commits on path derivation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 eth2/utils/eth2_keystore/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 36728103eb2..0a7117d2617 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -4,6 +4,7 @@ mod derived_key; mod keystore; mod password; +mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs new file mode 100644 index 00000000000..dcfa26c8966 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -0,0 +1,50 @@ +use crate::plain_text::PlainText; +use crypto::{digest::Digest, sha2::Sha256}; + +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; +/// The digest size (in octets) of the hash function (SHA256) +const K: usize = HASH_SIZE; +/// The size of the lamport array. +const LAMPORT_ARRAY_SIZE: usize = 255; +/// The HKDF output size (in octets) +const L: usize = K * LAMPORT_ARRAY_SIZE; + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { + hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) +} + +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { + let mut hasher = Sha256::new(); + hasher.input(salt); + hasher.input(ikm); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { + let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); + + debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); + + for i in 0..LAMPORT_ARRAY_SIZE { + let mut hasher = Sha256::new(); + + hasher.input(prk); + + if let Some(prev) = okm.last() { + hasher.input(&prev[..]); + } + + hasher.input(&[i as u8]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.push(digest); + } + + okm +} From 71c8449e0eab73dc058e6c8503de32d8e4a551d1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 13:33:11 +1000 Subject: [PATCH 065/118] Progress with implementation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 674 ++++++++++++++++++++++++++- 2 files changed, 656 insertions(+), 19 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 0a7117d2617..9980cd6b75b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,7 @@ mod derived_key; mod keystore; +mod lamport_secret_key; mod password; mod path; mod plain_text; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index dcfa26c8966..699278fb332 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,20 +1,90 @@ -use crate::plain_text::PlainText; +use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; use crypto::{digest::Digest, sha2::Sha256}; +use num_bigint::BigUint; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; -/// The digest size (in octets) of the hash function (SHA256) -const K: usize = HASH_SIZE; +pub const HASH_SIZE: usize = 32; /// The size of the lamport array. -const LAMPORT_ARRAY_SIZE: usize = 255; -/// The HKDF output size (in octets) -const L: usize = K * LAMPORT_ARRAY_SIZE; +pub const LAMPORT_ARRAY_SIZE: u8 = 255; -fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { +pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + +fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { + hkdf_mod_r(seed) +} + +fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { + let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); + hkdf_mod_r(&compressed_lamport_pk) +} + +fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); + + // TODO: don't do all this extra work with extraction. + let lamport_bytes = lamport + .iter_chunks() + .map(|a| a.to_vec()) + .flatten() + .collect::>(); + + // TODO: justify 48. + mod_r(&lamport_bytes[0..48]) +} + +fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(bytes.len(), HASH_SIZE); + + let n = BigUint::from_bytes_be(bytes); + let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); + let x = (n % r).to_bytes_be(); + + debug_assert!(x.len() <= HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output +} + +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { + let salt = index.to_be_bytes(); + let not_ikm = flip_bits(ikm); + + let lamports = [ + ikm_to_lamport_sk(ikm, &salt), + ikm_to_lamport_sk(¬_ikm, &salt), + ]; + + let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + + lamports + .iter() + .map(LamportSecretKey::iter_chunks) + .flatten() + .enumerate() + .for_each(|(i, chunk)| { + let output_slice = lamport_pk + .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) + .expect("lamport_pk must have adequate capacity"); + + let mut hasher = Sha256::new(); + hasher.input(chunk); + hasher.result(output_slice); + }); + + let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut hasher = Sha256::new(); + hasher.input(&lamport_pk); + hasher.result(&mut compressed_lamport_pk); + + compressed_lamport_pk +} + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) } -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { +fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { let mut hasher = Sha256::new(); hasher.input(salt); hasher.input(ikm); @@ -25,26 +95,592 @@ fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { digest.into() } -fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { - let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); - - debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); +fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { + let mut okm = LamportSecretKey::zero(); for i in 0..LAMPORT_ARRAY_SIZE { let mut hasher = Sha256::new(); hasher.input(prk); - if let Some(prev) = okm.last() { - hasher.input(&prev[..]); + if let Some(prev) = i.checked_sub(1) { + hasher.input(okm.get_chunk(prev)) } - hasher.input(&[i as u8]); + hasher.input(&[i + 1]); - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.push(digest); + hasher.result(okm.get_mut_chunk(i)); } okm } + +fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(input.len(), HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + + for (i, byte) in input.iter().enumerate() { + output[i] = !byte + } + + output +} + +#[cfg(test)] +mod test { + use super::*; + + struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + lamport_0: Vec<&'static str>, + lamport_1: Vec<&'static str>, + compressed_lamport_sk: &'static str, + child_sk: &'static str, + } + + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec<[u8; HASH_SIZE]>, + lamport_1: Vec<[u8; HASH_SIZE]>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + todo!() + } + } + + fn get_raw_vector() -> RawTestVector { + RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: + "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + lamport_0: vec![ + "0x7b4a587eac94d7f56843e718a04965d4832ef826419b4001a3ad0ba77eb44a3b", + "0x90f45a712112122429412921ece5c30eb2a6daf739dc9034fc79424daeb5eff6", + "0xd061c2799de00b2be90eb1cc295f4c31e22d4b45c59a9b9b2554379bea7783cb", + "0x3ad17e4cda2913b5180557fbe7db04b5ba440ce8bb035ae27878d66fbfa50d2c", + "0xf5b954490933ad47f8bf612d4a4f329b3aa8914b1b83d59e15e271e2a087e002", + "0x95d68d505bf4ff3e5149bc5499cf4b2f00686c674a29a8d903f70e569557d867", + "0x1b59c76d9bb2170b220a87833582ede5970d4a336d91c99a812825afe963e056", + "0x4310ff73cfbbf7b81c39ecbf1412da33e9388c1a95d71a75e51fe12256551ceb", + "0xee696343f823e5716e16747f3bbae2fc6de233fe10eea8e45b4579018da0874f", + "0xae12a437aaa7ae59f7d8328944b6a2b973a43565c55d5807dc2faf223a33aa73", + "0x2a3ae0b47f145bab629452661ff7741f111272e33ec571030d0eb222e1ed1390", + "0x1a3ea396e8cbd1d97733ef4753d6840b42c0795d2d693f18e6f0e7b3fff2beb2", + "0x472429d0643c888bfdfe6e6ccfdeee6d345d60c6710859ac29fc289fd3656347", + "0xa32d4d955949b8bed0eb20f586d8fd516d6ddec84fbbc36998d692633c349822", + "0xe5ac8ac5ee1d40e53a7abf36e8269d5d5fce450a87feae8e59f432a44bcc7666", + "0xddf9e497ed78032fbd72d9b8abd5204d81c3475f29afa44cdf1ded8ea72dd1dc", + "0x945c62e88fb1e5f3c15ff57cd5eb1586ee93ec5ec80154c5a9c50241c5adae0a", + "0xc8868b50fc8423c96b7efa1ede4d3203a6b835dbeb6b2ababc58397e6b31d9dd", + "0x66de9bd86b50e2b6a755310520af655759c1753bff34b79a5cd63d6811fc8c65", + "0x5b13786c6068df7735343e5591393bea8aee92ac5826d6132bf4f5ebf1098776", + "0xa2038fc7d8e3cb2eda2bd303cfa76a9e5d8b88293918bec8b2fc03be75684f14", + "0x47a13f6b2308a50eded830fdee7c504bf49d1fe6a95e337b0825d0d77a520129", + "0xb534cdddcf1aa1c6b4cbba46d1db31b766d958e0a0306450bc031d1e3ed79d97", + "0x54aa051b754c31658377f7bff00b7deaa861e74cb12e1eb84216666e19b23d69", + "0x0220d57f63435948818eb376367b113c188e37451c216380f65d1ad55f73f527", + "0xf9dd2e391565534a4db84980433bf5a56250f45fe294fce2679bcf115522c081", + "0x1166591ee2ca59b9f4e525900f085141be8879c66ef18529968babeb87c44814", + "0xf4fa2e8de39bdbeb29b64d8b440d3a6c9a6ca5bdce543877eaee93c11bd70ab8", + "0x07f466d73b93db283b3f7bfaf9c39ae296adc376ab307ef12312631d0926790e", + "0xb2ecff93acb4fa44c1dbf8464b81734a863b6d7142b02f5c008907ea4dc9aaa1", + "0xa1d9c342f6c293ac6ef8b5013cba82c4bad6ed7024d782948cb23cd490039ba1", + "0xc7d04a639ba00517ece4dbc5ef4aaf20e0ccde6e4a24c28936fabe93dec594db", + "0xe3cbb9810472d9dd1cdb5eed2f74b67ea60e973d2d2e897bd64728c9b1aa0679", + "0xe36884703413958ff2aba7a1f138a26d0ac0a371270f0169219beb00a5add5f0", + "0xe5ea300a09895b3f98de5232d92a36d5611cbcf9aaf9e7bb20cf6d1696ad1cb4", + "0xc136cda884e18175ab45148ed4f9d0d1a3c5e11ad0275058e61ae48eb151a81f", + "0x3ee1101e944c040021187e93b6e0beb1048c75fb74f3fdd67756b1c8517a311f", + "0x016964fd6fc32b9ad07a630949596715dee84d78230640368ff0929a280cf3a2", + "0xe33865fc03120b94333bb754fd097dc0f90e69ff6fd221d6aae59fcf2d762d76", + "0xe80bb3515a09ac6ecb4ec59de22701cdf954b1ae8a677fd85508c5b041f28058", + "0x3889af7cd325141ec288021ede136652a0411d20364005b9d3ca9102cb368f57", + "0x18dad0bc975cf8800addd54c7867389d3f7fe1b97d348bd8412a6cbfb75c520a", + "0x09035218686061ee91bd2ad57dc6fb6da7243b8177a153484524b2b228da5314", + "0x688fd7a97551c64eae33f91abb073a46eafbbacd5595c6bac2e57dd536acdfe2", + "0x1fc164dce565a1d0da59cc8048b334cc5eb84bf04de2399ddb847c22a7e32ab7", + "0xa2a340ba05c8a30dd1cab886a926b761758eba0e41b5c4c5dfd4a42f249655c1", + "0xc43dffe01479db836a6a1a74564b297fad0d69c6b06cf593f6db9f26b4f307d5", + "0x73cef7f3ff724a30a79e1dca74cef74954afeefa2e476c4dec65afe50c16c5c4", + "0xa54002253ab7b95cc5b664b3f08976400475cc56f170b939f6792e730ff5170b", + "0x9ade43053d41afebc002f09476dffd1b13ecbf67f810791540b92ca56d5e63e4", + "0x234e7cbfbe45b22a871db26738fa05de09213a925439d7f3e5108132e521b280", + "0x066b712417332c7cfca871fb1bb5839f0341acf9266229603a3eddbc8a93b59f", + "0xb5857acdcf636330da2cfcc99c81d9fdbd20c506a3c0e4f4f6a139d2a64f051c", + "0xe119908a150a49704b6bbba2c470cd619a0ae10dd9736e8d491890e3c8509fff", + "0xb8a5c5dbb51e6cb73cca95b4ad63ea3c7399cd16b05ab6261535495b3af2ca51", + "0x05624a1d4d2d2a31160bc48a6314bbf13eaddf56cddb0f0aa4ed3fb87f8b479f", + "0x483daceff1c3baa0ed0f3be7e534eebf5f4aed424ecd804edfbf5c56b3476b50", + "0x424d04694e7ae673707c77eb1c6d0996d250cfab6832ee3506a12e0384a3c5c9", + "0xa11fed0ed8057966bfe7136a15a814d06a516fbc9d44aeef87c509137a26190e", + "0x3694d22d1bc64658f3adbe2cc9f1716aee889066e0950e0b7a2fd576ed36bb76", + "0x49a13000a87f39f93d0ae9c3a4cfccbf440c0a75cce4c9d70dac627b6d6958b3", + "0xb3ff0cdd878d5ac1cb12e7d0b300d649fdd008800d498ae4f9fbf9510c74249a", + "0xe52a867cfb87d2fe7102d23d8d64925f7b75ca3f7d6bb763f7337352c255e0be", + "0x6513b372e4e557cca59979e48ec27620e9d7cdb238fcf4a9f19c3ba502963be0", + "0x9f69d82d4d51736902a987c8b5c30c2b25a895f2af5d2c846667ff6768bcc774", + "0x049a220dbe3340749f94643a429cb3cba3c92b561dc756a733d652d838728ab3", + "0x4fa2cd877aa115b476082b11053309f3537fa03d9158085f5f3f4bab6083e6da", + "0xed12db4069eb9f347735816afcee3fe43d4a6999fef8240b91bf4b05447d734f", + "0x3ecbe5eda469278f68548c450836a05cc500864664c7dda9b7526f084a891032", + "0x690d8f928fc61949c22e18cceaa2a446f8e1b65bd2e7af9e0a8e8284134ab3d2", + "0x99e09167a09f8261e7e8571d19148b7d7a75990d0702d9d582a2e4a96ac34f8e", + "0x6d33931693ed7c2e1d080b6a37da52c279a06cec5f534305819f7adf7db0afe3", + "0xc4b735462a9a656e28a52b1d4992ea9dea826b858971d698453a4be534d6bb70", + "0xedf92b10302dc41f8d362b360f4c2ef551d50e2ded012312c964002d2afc46d7", + "0x58f6691cca081ae5c3661dd171b87cc49c90359bb03cc0e57e503f7fcf14aefc", + "0x5d29b8b4ee295a73c4a8618927b3d14b76c7da049133a2257192b10be8c17a6a", + "0x646802fa42801e0ae24011fb4f62e87219ef1da01f7fc14bf8d6bd2d9e7c21f1", + "0x23abf45eee65cc4c1e95ccab42ad280a00bb3b14d243e2021a684075f900141e", + "0x2b1ae95c975bf9c387eae506fdb5e58afd2d198f00a21cd3fddb5855e8021e4d", + "0x0ef9f6e1c0583493d343e75f9c0c557fa6da0dc12b17a96c5757292916b72ee3", + "0x04c7fc76195c64a3285af14161077c045ff6ddbb67c0ff91b080f98eb6781e5c", + "0xba12679b97027d0e7076e6d19086c07792eaa7f78350842fbef8ddf5bcd3ecc0", + "0xcead458e6799df4d2f6cbf7f13cb3afec3441a354816e3071856ed49cbdbb1a7", + "0xbe6c56256556bb5c6727a1d9cb641d969677f56bb5ad7f8f7a7c9cfd128427b4", + "0xc80f11963ff40cb1888054b83c0463d32f737f2e7d42098e639023db0dfc84d4", + "0xac80006c1296bcfde86697efebb87fb0fddfb70dd34dd2ee4c152482af4687eb", + "0xbb7d13ce184249df4576fc3d13351e1683500e48726cd4198423f14f9094068b", + "0x1b2d9c40c55bd7362664fa46c1268e094d56c8193e3d991c08dc7a6e4ca14fa1", + "0x9bd236254d0565f5b2d24552d4b4d732de43b0adaa64ecd8be3efc6508577591", + "0x38078cefccc04e8312d79e0636e0e3157434c50a2ad4e3e87cc6584c41eec8b5", + "0xb5d15a8527ff3fa254ba61ffceb02d2570b53361894f351a9e839c0bb716857d", + "0x6763dad684bf2e914f40ae0a7ee0cdf12c97f41fc05a485d5991b4daad21a3f8", + "0xc80363c20df589333ecbe05bd5f2c19942ebc2593626dc50d00835c40fb8d005", + "0x48502b56ae93acd2794f847cbe825525d5d5f59f0f75c67aff84e5338776b3af", + "0xfd8e033493ba8af264a855a78ab07f37d936351d2879b95928909ed8df1b4f91", + "0x11f75bee9eac7356e65ebc7f004ccdc1da80807380d69143293d1421f50b1c97", + "0x903a88a3ebe84ca1c52a752b1faffa9ca1daedac9cbf1aa70942efc9beb44b79", + "0x2c0dcd68837f32a69da651045ad836b8cd6b48f2c8c5d73a3bd3bba6148d345a", + "0x0aa0f49b3476f3fdb6393f2ab601e0009586090b72ee54a525734f51598960d5", + "0xf7a789f013f702731656c562caa15b04cb7c9957376c4d80b8839167bb7fa626", + "0x4e0be1b19e305d82db3fd8affd67b0d2559da3edbfb08d19632a5cc46a90ed07", + "0x3caaccfc546d84d543eaf4f4c50c9c8fd831c12a8de56fdb9dfd04cc082882fe", + "0x894f6a01fd34f0642077e22981752011678548eb70eb55e8072c1caffc16fe02", + "0xae7eb54adaa68679348ea3537a49be669d1d61001fbab9fac259ba727dbc9a1a", + "0x291a1cbdceff957b5a65440ab67fb8672de881230fe3108a15ca487c2662c2c7", + "0x891d43b867137bf8beb9df4da2d951b5984a266a8cd74ec1593801d005f83f08", + "0xc558407f6491b37a10835e0ad7ce74f4e368aa49157a28873f7229310cb2d7fd", + "0x9ce061b0a072e1fe645f3479dac089b5bfb78cfa6cfbe5fd603bcdb504711315", + "0xa8e30d07b09275115dd96472ecf9bc316581caf307735176ca226d4cd9022925", + "0x918ee6d2efba7757266577691203f973cf4f4cac10f7d5f86acd2a797ff66583", + "0xfa31ba95e15d1635d087522f3d0da9cf7acac4ed6d0ac672654032a3c39244a6", + "0xf2952b58f015d6733af06938cd1f82fbddb3b796823bee7a3dbffa04efc117c2", + "0x46f8f742d3683de010ede528128d1181e8819f4252474f51371a177bfa518fa4", + "0x4ca1cc80094f2910cf83a9e65ad70e234690ffb9142793911ec7cf71663545b3", + "0x381965037b5725c71bfa6989d4c432f6611de8e8ec387f3cfc0dcb1a15191b73", + "0x2562b88ed3b86ba188be056805a3b7a47cb1a3f630d0e2f39647b0792ec6b7d8", + "0x565f6d14e7f22724f06d40f54465ad40d265b6de072b34a09d6e37a97a118cd8", + "0xc2982c861ad3278063b4a5f584eaf866db684cc4e712d64230fc9ee33bb4253b", + "0xfd806c91927e549d8d400ab7aa68dbe60af988fbabf228483ab0c8de7dab7eee", + "0xafae6ff16c168a3a3b5c2f1742d3f89fa4777c4bd0108f174014debf8f4d629c", + "0xaf5a4be694de5e53632be9f1a49bd582bf76002259460719197079c8c4be7e66", + "0xa8df4a4b4c5bf7a4498a11186f8bb7679137395f28e5c2179589e1c1f26504b5", + "0xce8b77c64c646bb6023f3efaed21ca2e928e21517422b124362cf8f4d9667405", + "0x62e67a8c423bc6c6c73e6cd8939c5c1b110f1a38b2ab75566988823762087693", + "0x7e778f29937daaa272d06c62d6bf3c9c0112d45a3df1689c602d828b5a315a9f", + "0xe9b5abd46c2377e602ff329050afa08afe152f4b0861db8a887be910ff1570bf", + "0xa267b1b2ccd5d96ae8a916b0316f06fafb886b3bb41286b20763a656e3ca0052", + "0xb8ed85a67a64b3453888a10dedf4705bd27719664deff0996a51bb82bc07194f", + "0x57907c3c88848f9e27bc21dd8e7b9d61de48765f64d0e943e7a6bb94cc2021ab", + "0xd2f6f1141a3b76bf9bf581d49091142944c7f9f323578f5bdd5522ba32291243", + "0xc89f104200ed4c5d5f7046d99e68ae6f8ec31e2eeceb568eb05087e3aa546a74", + "0xc9f367fae45c39299693b134229bb6dd0da112fd1a7d19b7f4772c01e5cbe479", + "0x64e2d4ad51948764dd578d26357e29e8e4d076d65c05cffdf8211b624fefe9ac", + "0xf9a9b4e6d5be7fc051df8ecd9c389d16b1af86c749308e6a23f7ff4871f0ba9a", + "0x0d2b2a228b86ebf9499e1bf7674335087ced2eb35ce0eb90954a0f75751a2bf4", + "0xff8531b45420a960d6e48ca75d77758c25733abde83cd4a6160beae978aa735e", + "0xd6d412bd1cb96a2b568d30e7986b7e8994ca92fd65756a758295499e11ea52b6", + "0xad8533fccbecdd4a0b00d648bfe992360d265f7be70c41d9631cefad5d4fe2f6", + "0x31fbf2afb8d5cc896d517cfc5201ee24527e8d283f9c37ca10233bef01000a20", + "0x2fd67b7365efc258131eb410f46bf3b1cbd3e9c76fd6e9c3e86c9ff1054116ff", + "0xab6aa29f33d18244be26b23abadb39679a8aa56dafc0dd7b87b672df5f5f5db6", + "0xbad3b0f401ca0a53a3d465de5cecd57769ec9d4df2c04b78f8c342a7ed35bbee", + "0xbdc24d46e471835d83ce8c5b9ecbe675aab2fd8f7831c548e8efd268c2ee2232", + "0x87265fabd7397d08f0729f13a2f3a25bbc8c874b6b50f65715c92b62f665f925", + "0xa379fd268e7ff392c067c2dd823996f72714bf3f936d5eeded71298859f834cb", + "0xf3ab452c9599ebfbb234f72a86f3062aed12ae1f634abbe542ff60f5cefc1fcf", + "0x2b17ebb053a3034c07da36ed2ba42c25ad8e61dec87b5527f5e1c755eb55405a", + "0x305b40321bd67bf48bfd121ee4d5d347268578bd4b8344560046594771a11129", + "0xe7029c9bea020770d77fe06ca53b521b180ad6a9e747545aadc1c74beef7241c", + "0xabc357cec0f4351a5ada22483d3b103890392f8d8f9cb8073a61969ed1be4e08", + "0x97f88c301946508428044d05584dc41af2e6a0de946de7d7f5269c05468afe20", + "0xbdc08fe8d6f9a05ad8350626b622ad8eec80c52331d154a3860c98676719cfbd", + "0x161590fc9f7fcf4eaba2f950cf588e6da79e921f139d3c2d7ebe017003a4799e", + "0x91b658db75bc3d1954bfde2ef4bc12980ff1688e09d0537f170c9ab47c162320", + "0x76d995f121406a63ce26502e7ec2b653c221cda357694a8d53897a99e6ce731e", + "0x3d6b2009586aceb7232c01259bb9428523c02b0f42c2100ec0d392418260c403", + "0x14ca74ecbc8ec0c67444c6cb661a2bce907aa2a1453b11f16002b815b94a1c49", + "0x553b4dc88554ebe7b0a3bd0813104fd1165a1f950ceace11f5841aa74b756d85", + "0x4025bf4ad86751a156d447ce3cabafde9b688efcdafd8aa4be69e670f8a06d9e", + "0x74260cf266997d19225e9a0351a9acfa17471fccdf5edc9ccc3bb0d23ef551c5", + "0xf9dbca3e16d234e448cf03877746baeb62a8a25c261eff42498b1813565c752a", + "0x2652ec98e05c1b6920fb6ddc3b57e366d514ffa4b35d068f73b5603c47f68f2f", + "0x83f090efeb36db91eb3d4dfbb17335c733fce7c64317d0d3324d7caaaf880af5", + "0x1e86257f1151fb7022ed9ed00fb961a9a9989e58791fb72043bb63ed0811791c", + "0xd59e4dcc97cba88a48c2a9a2b29f79125099a39f74f4fb418547de8389cd5d15", + "0x875a19b152fe1eb3fe1de288fa9a84864a84a79bac30b1dbd70587b519a9770e", + "0x9c9dc2d3c8f2f6814cfc61b42ee0852bbaf3f523e0409dd5df3081b750a5b301", + "0xf6f7f81c51581c2e5861a00b66c476862424151dd750efeb20b7663d552a2e94", + "0x723fcb7ca43a42483b31443d4be9b756b34927176f91a391c71d0b774c73a299", + "0x2b02d8acf63bc8f528706ed4d5463a58e9428d5b71d577fd5daa13ba48ac56cf", + "0x2ff6911f574c0f0498fc6199da129446b40fca35ccbf362bc76534ba71c7ca22", + "0x1ef4b959b11bc87b11e4a5f84b4d757c6bdcfad874acec9a6c9eee23dc4bbe1b", + "0x68e2df9f512be9f64b7e3a2dee462149dac50780073d78b569a20256aea5f751", + "0xd1a3682e12b90ae1eab27fc5dc2aef3b8e4dbb813925e9a91e58d6c9832767b6", + "0x75778ccc102d98c5e0b4b83f7d4ef7fe8bc7263cc3317723001cb0b314d1e9e8", + "0xc7f44e2cead108dc167f0036ac8a278d3549cc3dd5cc067d074ccad9b1d9f8d4", + "0x4cba0223c5df2796b0ee9fbc084d69f10e6aedda8f0cf86171bebb156ede676c", + "0x628deda825661f586a5713e43c806fdd55e1a53fbe90a4ddb5f3786570740954", + "0xfc82a253bc7e0ac96252b238fbb411a54e0adf78d089f804a7fc83a4959b401e", + "0x72a6491f5daae0ceb85b61a5ed69009dd2a167c64cb35cabf38b846e27268e9d", + "0xee139a913d4fcf25ba54bb36fc8051b91f2ec73ba820cc193c46fb2f7c37a106", + "0x7f75021f2b1d0c78859478e27f6f40646b5776c060f1a5f6f0944c840a0121f8", + "0x5b60a1b78feca1d2602ac8110d263ad6b3663cbf49e6bdc1077b4b80af2feb6f", + "0xd61f15d80b1e88469b6a76ed6a6a2b94143b6acc3bd717357264818f9f2d5c6d", + "0xea85da1780b3879a4d81b685ba40b91c060866abd5080b30fbbb41730724a7dd", + "0xb9b9da9461e83153f3ae0af59fbd61febfde39eb6ac72db5ed014797495d4c26", + "0xf737762fe8665df8475ff341b3762aaeb90e52974fe5612f5efd0fc1c409d7f8", + "0xaaa25d934a1d5aa6b2a1863704d7a7f04794ed210883582c1f798be5ca046cf7", + "0x932f46d0b6444145221b647f9d3801b6cb8b1450a1a531a959abdaacf2b5656b", + "0xf4a8b0e52f843ad27635c4f5a467fbf98ba06ba9a2b93a8a97170b5c41bf4958", + "0x196ed380785ee2925307ec904161dc02a4596a55499e5b0a3897f95485b3e74a", + "0x772e829a405219e4f8cd93a1ef15c250be85c828c1e29ef6b3f7b46958a85b44", + "0xd66cfc9af9941515d788f9f5e3b56fddb92464173ddb67b83bf265e7ea502170", + "0xf5b040bfc246425278e2423b1953d8ad518de911cf04d16c67d8580a09f90e62", + "0xd2d18b2ae8a53dde14b4000e5e7e414505825f50401a3797dd8820cf510dc448", + "0xc01dcc064e644266739cd0ec7edf92fc2ef8e92e0beedf0e8aa30efcff1644fe", + "0x24720d325913ba137daf031924ad3bfaa1c8c00a53a2d048fe5667aef45efce3", + "0x70a24e1c89b3ea78d76ef458d498dcb5b8561d484853b2a8b2adcd61869857df", + "0x0ff3313997f14e1b1dcd80f1d62c58aaefb19efd7c0ea15dde21aa4e2a516e80", + "0x960c1f50062a4df851638f42c0259b6e0a0217300884f13a3c5c8d94adb34f21", + "0xb71ca7cc8578149da556131268f4625b51620dfc3a6e9fbd47f5df03afbd410e", + "0xa1a3eeec0addec7b9e15f416a07608a1b5d94f0b42d5c203b8ced03a07484f5b", + "0xa4bb8b059aa122ca4652115b83b17af80cfbea0d3e1e8979a396a667f94e85f3", + "0x31c4d2f252167fe2a4d41944224a80b2f1afaf76f8dd6a3d52d71751849e44bb", + "0x79642dd6a255f96c9efe569304d58c327a441448db0431aa81fe072d0d359b52", + "0x42a4b504714aba1b67defe9458fff0c8cb1f216dcab28263cef67a65693b2036", + "0xe3d2f6a9d882d0f026ef316940dfcbf131342060ea28944475fe1f56392c9ad2", + "0x986af9aeff236394a0afa83823e643e76f7624e9bfd47d5468f9b83758a86caa", + "0xafe2de6ede50ee351d63ed38d1f2ae5203174c731f41bbed95db467461ad5492", + "0x9ad40f0785fe1c8a5e4c3342b3c91987cd47a862ece6573674b52fa0456f697a", + "0xde4cde6d0fc6def3a89b79da0e01accdbec049f1c9471d13a5d59286bd679af1", + "0xecd0d1f70116d6b3ae21c57fb06ad90eed33d040e2c5c3d12714b3be934fa5ce", + "0x3c53c5bf2d1b1d4038e1f0e8a2e6d12e0d4613d5cd12562578b6909921224c10", + "0x36087382b37e9e306642cc6e867e0fb2971b6b2b28b6caf2f9c96b790e8db70a", + "0xa957496d6a4218a19998f90282d05bd93e6baabf55e55e8a5f74a933a4dec045", + "0x077d6f094e8467a21f02c67753565ec5755156015d4e86f1f82a22f9cf21c869", + "0x12dd3b1f29e1462ca392c12388a77c58044151154cf86f23873f92a99b6bb762", + "0x7fdbcdedcc02ecf16657792bd8ef4fa4adeee497f30207d4cc060eb0d528b26b", + "0x245554b12bf8edf9e9732d6e2fa50958376e355cb695515c94676e64c6e97009", + "0xccd3b1841b517f7853e35f85471710777e437a8665e352a0b61c7d7083c3babc", + "0xd970545a326dcd92e31310d1fdce3703dff8ef7c0f3411dfa74fab8b4b0763ac", + "0xd24163068918e2783f9e79c8f2dcc1c5ebac7796ce63070c364837aac91ee239", + "0x256a330055357e20691e53ca5be846507c2f02cfde09cafb5809106f0af9180e", + "0xfa446a5d1876c2051811af2a341a35dbcd3f7f8e2e4f816f501139d27dd7cd82", + "0xbafbc7a8f871d95736a41e5721605d37e7532e41eb1426897e33a72ed2f0bf1d", + "0x8055af9a105b6cf17cfeb3f5320e7dab1a6480500ff03a16c437dfec0724c290", + "0x1de6ee3e989497c1cc7ca1d16b7b01b2f336524aa2f75a823eaa1716c3a1a294", + "0x12bb9508d646dda515745d104199f71276d188b3e164083ad27dfdcdc68e290b", + "0x7ea9f9939ad4f3b44fe7b780e0587da4417c34459b2996b3a449bb5b3ff8c8cb", + "0xa88d2f8f35bc669aa6480ce82571df65fea366834670b4084910c7bb6a735dde", + "0x9486e045adb387a550b3c7a603c30e07ed8625d322d1158f4c424d30befe4a65", + "0xb283a70ba539fe1945be096cb90edb993fac77e8bf53616bde35cdcaa04ab732", + "0xab39a81558e9309831a2caf03e9df22e8233e20b1769f16e613debcdb8e2610f", + "0x1fc12540473fbbad97c08770c41f517ce19dc7106aa2be2e9b77867046627509", + "0xec33dbec9d655c4c581e07d1c40a587cf3217bc8168a81521b2d0021bd0ec133", + "0xc8699e3b41846bc291209bbb9c06f565f66c6ccecbf03ebc27593e798c21fe94", + "0x240d7eae209c19d453b666c669190db22db06279386aa30710b6edb885f6df94", + "0xb181c07071a750fc7638dd67e868dddbeeee8e8e0dcbc862539ee2084674a89e", + "0xb8792555c891b3cbfddda308749122a105938a80909c2013637289e115429625", + "0xfe3e9e5b4a5271d19a569fee6faee31814e55f156ba843b6e8f8dc439d60e67a", + "0x912e9ba3b996717f89d58f1e64243d9cca133614394e6ae776e2936cf1a9a859", + "0xa0671c91a21fdfd50e877afa9fe3974aa3913855a2a478ae2c242bcdb71c73d7", + "0x5b55d171b346db9ba27b67105b2b4800ca5ba06931ed6bd1bafb89d31e6472e6", + "0x68438458f1af7bd0103ef33f8bc5853fa857b8c1f84b843882d8c328c595940d", + "0x21fe319fe8c08c1d00f977d33d4a6f18aecaa1fc7855b157b653d2d3cbd8357f", + "0x23cce560bc31f68e699ece60f21dd7951c53c292b3f5522b9683eb2b3c85fc53", + "0x917fa32d172c352e5a77ac079df84401cdd960110c93aa9df51046d1525a9b49", + "0x3fc397180b65585305b88fe500f2ec17bc4dccb2ec254dbb72ffb40979f14641", + "0xf35fb569e7a78a1443b673251ac70384abea7f92432953ca9c0f31c356be9bd9", + "0x7955afa3cd34deb909cd031415e1079f44b76f3d6b0aaf772088445aaff77d08", + "0x45c0ca029356bf6ecfc845065054c06024977786b6fbfaea74b773d9b26f0e6c", + "0xe5c1dac2a6181f7c46ab77f2e99a719504cb1f3e3c89d720428d019cb142c156", + "0x677b0e575afcccf9ddefc9470e96a6cfff155e626600b660247b7121b17b030a", + "0xbeed763e9a38277efe57b834a946d05964844b1f51dba2c92a5f3b8d0b7c67d0", + "0x962b17ed1a9343d8ebfae3873162eef13734985f528ca06c90b0c1e68adfdd89", + ], + lamport_1: vec![ + "0xb3a3a79f061862f46825c00fec4005fb8c8c3462a1eb0416d0ebe9028436d3a9", + "0x6692676ce3b07f4c5ad4c67dc2cf1dfa784043a0e95dd6965e59dc00b9eaff2d", + "0xbf7b849feb312db230e6e2383681b9e35c064e2d037cbc3c9cc9cd49220e80c9", + "0xa54e391dd3b717ea818f5954eec17b4a393a12830e28fabd62cbcecf509c17dc", + "0x8d26d800ac3d4453c211ef35e9e5bb23d3b9ede74f26c1c417d6549c3110314d", + "0xbb8153e24a52398d92480553236850974576876c7da561651bc551498f184d10", + "0x0d30e0e203dc4197f01f0c1aba409321fbf94ec7216e47ab89a66fb45e295eff", + "0x01dc81417e36e527776bf37a3f9d74a4cf01a7fb8e1f407f6bd525743865791d", + "0xa6318e8a57bec438245a6834f44eb9b7fb77def1554d137ea12320fc572f42c9", + "0xd25db9df4575b595130b6159a2e8040d3879c1d877743d960bf9aa88363fbf9f", + "0x61bb8baeb2b92a4f47bb2c8569a1c68df31b3469e634d5e74221bc7065f07a96", + "0xb18962aee4db140c237c24fec7fd073b400b2e56b0d503f8bc74a9114bf183bf", + "0x205473cc0cdab4c8d0c6aeceda9262c225b9db2b7033babfe48b7e919751a2c6", + "0xc5aa7df7552e5bb17a08497b82d8b119f93463ccb67282960aee306e0787f228", + "0x36da99e7d38ce6d7eab90ea109ba26615ad75233f65b3ae5056fba79c0c6682a", + "0xd68b71bba6266b68aec0df39b7c2311e54d46a3eab35f07a9fe60d70f52eec58", + "0xbbe56f1274ada484277add5cb8c90ef687d0b69a4c95da29e32730d90a2d059f", + "0x0982d1d1c15a560339d9151dae5c05e995647624261022bbedce5dce8a220a31", + "0x8ef54ad546d2c6144fc26e1e2ef92919c676d7a76cfdfb5c6a64f09a54e82e71", + "0x1e3ac0133eef9cdbeb590f14685ce86180d02b0eea3ef600fd515c38992b1f26", + "0x642e6b1c4bec3d4ba0ff2f15fbd69dcb57e4ba8785582e1bc2b452f0c139b590", + "0xca713c8cf4afa9c5d0c2db4fc684a8a233b3b01c219b577f0a053548bedf8201", + "0xd0569ba4e1f6c02c69018b9877d6a409659cb5e0aa086df107c2cc57aaba62da", + "0x4ebe68755e14b74973e7f0fa374b87cee9c370439318f5783c734f00bb13e4b5", + "0x788b5292dc5295ae4d0ea0be345034af97a61eec206fda885bbc0f049678c574", + "0x0ebd88acd4ae195d1d3982038ced5af1b6f32a07349cf7fffbff3ce410c10df2", + "0xc7faf0a49234d149036c151381d38427b74bae9bd1601fc71663e603bc15a690", + "0xc5247bf09ebe9fa4e1013240a1f88c703f25a1437196c71ee02ca3033a61f946", + "0x719f8c68113d9f9118b4281e1f42c16060def3e3eeef15f0a10620e886dc988f", + "0x28da4f8d9051a8b4d6158503402bdb6c49ba2fb1174344f97b569c8f640504e6", + "0x96f6773576af69f7888b40b0a15bc18cc9ec8ca5e1bb88a5de58795c6ddf678e", + "0x8d80d188a4e7b85607deccf654a58616b6607a0299dd8c3f1165c453fd33d2e4", + "0x9c08dcc4f914486d33aa24d10b89fd0aabcc635aa2f1715dfb1a18bf4e66692a", + "0x0ff7045b5f6584cc22c140f064dec0692762aa7b9dfa1defc7535e9a76a83e35", + "0x8e2dae66fa93857b39929b8fc531a230a7cfdd2c449f9f52675ab5b5176461d5", + "0xf449017c5d429f9a671d9cc6983aafd0c70dd39b26a142a1d7f0773de091ac41", + "0xed3d4cab2d44fec0d5125a97b3e365a77620db671ecdda1b3c429048e2ebdae6", + "0x836a332a84ee2f4f5bf24697df79ed4680b4f3a9d87c50665f46edaeed309144", + "0x7a79278754a4788e5c1cf3b9145edb55a2ba0428ac1c867912b5406bb7c4ce96", + "0x51e6e2ba81958328b38fd0f052208178cec82a9c9abd403311234e93aff7fa70", + "0x217ec3ec7021599e4f34410d2c14a8552fff0bc8f6894ebb52ec79bf6ec80dc9", + "0x8a95bf197d8e359edabab1a77f5a6d04851263352aa46830f287d4e0564f0be0", + "0x60d0cbfb87340b7c92831872b48997ce715da91c576296df215070c6c20046d4", + "0x1739fbca476c540d081b3f699a97387b68af5d14be52a0768d5185bc9b26961b", + "0xac277974f945a02d89a0f8275e02de9353e960e319879a4ef137676b537a7240", + "0x959b7640821904ba10efe8561e442fbdf137ccb030aee7472d10095223e320ba", + "0xdba61c8785a64cb332342ab0510126c92a7d61f6a8178c5860d018d3dad571c6", + "0xc191fb6a92eb1f1fb9e7eb2bdecd7ec3b2380dd79c3198b3620ea00968f2bd74", + "0x16ef4e88e182dfc03e17dc9efaa4a9fbf4ff8cb143304a4a7a9c75d306729832", + "0x39080e4124ca577ff2718dfbcb3415a4220c5a7a4108729e0d87bd05adda5970", + "0xa29a740eef233956baff06e5b11c90ed7500d7947bada6da1c6b5d9336fc37b6", + "0x7fda7050e6be2675251d35376bacc895813620d245397ab57812391d503716ee", + "0x401e0bf36af9992deb87efb6a64aaf0a4bc9f5ad7b9241456b3d5cd650418337", + "0x814e70c57410e62593ebc351fdeb91522fe011db310fcf07e54ac3f6fefe6be5", + "0x03c1e52ecbef0d79a4682af142f012dc6b037a51f972a284fc7973b1b2c66dcf", + "0x57b22fb091447c279f8d47bdcc6a801a946ce78339e8cd2665423dfcdd58c671", + "0x53aeb39ab6d7d4375dc4880985233cba6a1be144289e13cf0bd04c203257d51b", + "0x795e5d1af4becbca66c8f1a2e751dcc8e15d7055b6fc09d0e053fa026f16f48f", + "0x1cd02dcd183103796f7961add835a7ad0ba636842f412643967c58fe9545bee4", + "0x55fc1550be9abf92cacb630acf58bad11bf734114ebe502978a261cc38a4dd70", + "0x6a044e0ea5c361d3fb2ca1ba795301e7eb63db4e8a0314638f42e358ea9cfc3e", + "0x57d9f15d4db199cbcb7cbd6524c52a1b799d52b0277b5a270d2985fcee1e2acb", + "0x66c78c412e586bd01febc3e4d909cc278134e74d51d6f60e0a55b35df6fb5b09", + "0x1076799e15a49d6b15c2486032f5e0b50f43c11bc076c401e0779d224e33f6fc", + "0x5f70e3a2714d8b4483cf3155865ba792197e957f5b3a6234e4c408bf2e55119d", + "0x9b105b0f89a05eb1ff7caed74cf9573dc55ac8bc4881529487b3700f5842de16", + "0x1753571b3cfadca4277c59aee89f607d1b1e3a6aa515d9051bafb2f0d8ce0daa", + "0x4014fff940b0950706926a19906a370ccbd652836dab678c82c539c00989201a", + "0x0423fa59ee58035a0beb9653841036101b2d5903ddeabddabf697dbc6f168e61", + "0x78f6781673d991f9138aa1f5142214232d6e3d6986acb6cc7fb000e1a055f425", + "0x21b8a1f6733b5762499bf2de90c9ef06af1c6c8b3ddb3a04cce949caad723197", + "0x83847957e909153312b5bd9a1a37db0bd6c72a417024a69df3e18512973a18b4", + "0x948addf423afd0c813647cfe32725bc55773167d5065539e6a3b50e6ebbdab38", + "0x0b0485d1bec07504a2e5e3a89addd6f25d497cd37a0c04bc38355f8bdb01cd48", + "0x31be8bda5143d39ea2655e9eca6a294791ca7854a829904d8574bedc5057ddc4", + "0x16a0d2d657fadce0d81264320e42e504f4d39b931dff9888f861f3cc78753f99", + "0xb43786061420c5231bf1ff638cb210f89bf4cd2d3e8bafbf34f497c9a298a13b", + "0x1f5986cbd7107d2a3cbc1826ec6908d976addbf9ae78f647c1d159cd5397e1bd", + "0xa883ccdbfd91fad436be7a4e2e74b7796c0aadfe03b7eea036d492eaf74a1a6f", + "0x5bc9eb77bbbf589db48bca436360d5fc1d74b9195237f11946349951f2a9f7f6", + "0xb6bc86de74a887a5dceb012d58c62399897141cbcc51bad9cb882f53991f499c", + "0xa6c3260e7c2dd13f26cf22bf4cd667688142ff7a3511ec895bc8f92ebfa694b6", + "0xb97da27e17d26608ef3607d83634d6e55736af10cc7e4744940a3e35d926c2ad", + "0x9df44067c2dc947c2f8e07ecc90ba54db11eac891569061a8a8821f8f9773694", + "0x865cc98e373800825e2b5ead6c21ac9112ff25a0dc2ab0ed61b16dc30a4a7cd7", + "0xe06a5b157570c5e010a52f332cacd4e131b7aed9555a5f4b5a1c9c4606caca75", + "0x824eccb5cf079b5943c4d17771d7f77555a964a106245607cedac33b7a14922e", + "0xe86f721d7a3b52524057862547fc72de58d88728868f395887057153bccaa566", + "0x3344e76d79f019459188344fb1744c93565c7a35799621d7f4505f5b6119ac82", + "0x401b3589bdd1b0407854565329e3f22251657912e27e1fb2d978bf41c435c3ac", + "0xb12fd0b2567eb14a562e710a6e46eef5e280187bf1411f5573bb86ecbe05e328", + "0xe6dc27bab027cbd9fbb5d80054a3f25b576bd0b4902527a0fc6d0de0e45a3f9f", + "0x1de222f0e731001c60518fc8d2be7d7a48cc84e0570f03516c70975fdf7dc882", + "0xb8ff6563e719fc182e15bbe678cf045696711244aacc7ce4833c72d2d108b1b9", + "0x53e28ac2df219bcbbc9b90272e623d3f6ca3221e57113023064426eff0e2f4f2", + "0x8a4e0776f03819e1f35b3325f20f793d026ccae9a769d6e0f987466e00bd1ce7", + "0x2f65f20089a31f79c2c0ce668991f4440b576ecf05776c1f6abea5e9b14b570f", + "0x448e124079a48f62d0d79b96d5ed1ffb86610561b10d5c4236280b01f8f1f406", + "0x419b34eca1440c847f7bff9e948c9913075d8e13c270e67f64380a3f31de9bb2", + "0x2f6e4fee667acaa81ba8e51172b8329ed936d57e9756fb31f635632dbc2709b7", + "0xdd5afc79e8540fcee6a896c43887bd59c9de5d61b3d1b86539faeb41a14b251d", + "0xc707bed926a46cc451a6b05e642b6098368dbdbf14528c4c28733d5d005af516", + "0x153e850b606eb8a05eacecc04db4b560d007305e664bbfe01595cb69d26b8597", + "0x1b91cc07570c812bb329d025e85ef520132981337d7ffc3d84003f81a90bf7a7", + "0x4ca32e77a12951a95356ca348639ebc451170280d979e91b13316844f65ed42a", + "0xe49ea1998e360bd68771bd69c3cd4cf406b41ccca4386378bec66ea210c40084", + "0x01aaffbde1a672d253e0e317603c2dc1d0f752100d9e853f840bca96e57f314c", + "0x170d0befcbbaafb317c8684213a4989368332f66e889824cc4becf148f808146", + "0x56f973308edf5732a60aa3e7899ae1162c7a2c7b528c3315237e20f9125b34e0", + "0x66c54fd5f6d480cab0640e9f3ec1a4eafbafc0501528f57bb0d5c78fd03068ef", + "0xaca6c83f665c64d76fbc4858da9f264ead3b6ecdc3d7437bb800ef7240abffb9", + "0xf1d4e02e7c85a92d634d16b12dc99e1d6ec9eae3d8dfbca77e7c609e226d0ce7", + "0x094352545250e843ced1d3c6c7957e78c7d8ff80c470974778930adbe9a4ed1a", + "0x76efa93070d78b73e12eb1efa7f36d49e7944ddcc3a043b916466ee83dca52ce", + "0x1772a2970588ddb584eadf02178cdb52a98ab6ea8a4036d29e59f179d7ba0543", + "0xe4bbf2d97d65331ac9f680f864208a9074d1def3c2433458c808427e0d1d3167", + "0x8ccfb5252b22c77ea631e03d491ea76eb9b74bc02072c3749f3e9d63323b44df", + "0x9e212a9bdf4e7ac0730a0cecd0f6cc49afc7e3eca7a15d0f5f5a68f72e45363b", + "0x52e548ea6445aae3f75509782a7ab1f4f02c2a85cdd0dc928370f8c76ae8802d", + "0xb62e7d73bf76c07e1a6f822a8544b78c96a6ba4f5c9b792546d94b56ca12c8b9", + "0x595cb0e985bae9c59af151bc748a50923921a195bbec226a02157f3b2e066f5b", + "0x1c7aa6b36f402cec990bafefbdbb845fc6c185c7e08b6114a71dd388fe236d32", + "0x01ee2ff1a1e88858934a420258e9478585b059c587024e5ec0a77944821f798c", + "0x420a963a139637bffa43cb007360b9f7d305ee46b6a694b0db91db09618fc2e5", + "0x5a8e2ad20f8da35f7c885e9af93e50009929357f1f4b38a6c3073e8f58fae49e", + "0x52a405fdd84c9dd01d1da5e9d1c4ba95cb261b53bf714c651767ffa2f9e9ad81", + "0xa1a334c901a6d5adc8bac20b7df025e906f7c4cfc0996bfe2c62144691c21990", + "0xb789a00252f0b34bded3cb14ae969effcf3eb29d97b05a578c3be8a9e479c213", + "0xb9dbf7e9ddb638a515da245845bea53d07becdf3f8d1ec17de11d495624c8eab", + "0xaf566b41f5ed0c026fa8bc709533d3fa7a5c5d69b03c39971f32e14ab523fa3d", + "0x8121e0b2d9b106bb2aefd364fd6a450d88b88ee1f5e4aad7c0fcd8508653a112", + "0x8581c1be74279216b93e0a0d7272f4d6385f6f68be3eef3758d5f68b62ee7b6c", + "0x85386f009278f9a1f828404fa1bbfa02dfb9d896554f0a52678eb6ec8feadc55", + "0xf483ed167d92a0035ac65a1cfdb7906e4952f74ae3a1d86324d21f241daffcb7", + "0x3872485e2a520a350884accd990a1860e789dd0d0664ad14f50186a92c7be7be", + "0xc6c1a3301933019105f5650cabcb22bfbf221965ffcfc1329315b24ea3d77fd4", + "0xcee901330a60d212a867805ce0c28f53c6cc718f52156c9e74390d18f5df6280", + "0xa67ae793b1cd1a828a607bae418755c84dbb61adf00833d4c61a94665363284f", + "0x80d8159873b517aa6815ccd7c8ed7cfb74f84298d703a6c5a2f9d7d4d984ddde", + "0x1de5a8b915f2d9b45c97a8e134871e2effb576d05f4922b577ade8e3cd747a79", + "0x6ea17c5ece9b97dddb8b2101b923941a91e4b35e33d536ab4ff15b647579e1f5", + "0xcb78631e09bc1d79908ce1d3e0b6768c54b272a1a5f8b3b52485f98d6bba9245", + "0xd7c38f9d3ffdc626fe996218c008f5c69498a8a899c7fd1d63fbb03e1d2a073f", + "0x72cdef54267088d466244a92e4e6f10742ae5e6f7f6a615eef0da049a82068f9", + "0x60b3c490ba8c502656f9c0ed37c47283e74fe1bc7f0e9f651cbc76552a0d88eb", + "0x56bd0c66987a6f3761d677097be9440ea192c1cb0f5ec38f42789abe347e0ea9", + "0x3caac3e480f62320028f6f938ee147b4c78e88a183c464a0c9fb0df937ae30c1", + "0x7a4d2f11bddda1281aba5a160df4b814d23aef07669affe421a861fac2b4ec0f", + "0x9bb4d11299922dc309a4523959298a666ebe4063a9ee3bad1b93988ed59fb933", + "0x957323fffbaf8f938354662452115ae5acba1290f0d3f7b2a671f0359c109292", + "0x877624e31497d32e83559e67057c7a605fb888ed8e31ba68e89e02220eac7096", + "0x8456546ae97470ff6ea98daf8ae632e59b309bd3ff8e9211f7d21728620ed1e5", + "0xbacb26f574a00f466ce354e846718ffe3f3a64897d14d5ffb01afcf22f95e72b", + "0x0228743a6e543004c6617bf2c9a7eba1f92ebd0072fb0383cb2700c3aed38ba0", + "0x04f093f0f93c594549436860058371fb44e8daf78d6e5f563ba63a46b61ddbf0", + "0x0ba17c1ec93429ceaff08eb81195c9844821b64f2b5363926c2a6662f83fb930", + "0xd71605d8446878c677f146837090797e888416cfc9dc4e79ab11776cc6639d3f", + "0x33dde958dc5a6796138c453224d4d6e7f2ae740cceef3b52a8b669eb4b9691a1", + "0x3c39838295d1495e90e61ce59f6fcc693b31c292d02d31759719df6fe3214559", + "0x8aecc66f38644296cf0e6693863d57a243a31a4929130e22ab44cb6157b1af41", + "0xdf7153a7eab9521f2b37124067166c72de8f342249ac0e0f5350bd32f1251053", + "0xa498840b58897cf3bed3981b94c86d85536dfebbc437d276031ebd9352e171eb", + "0xb1df15a081042ab665458223a0449ffc71a10f85f3d977beb20380958fd92262", + "0x15d3bdbdee2a61b01d7a6b72a5482f6714358eedf4bece7bb8458e100caf8fba", + "0x0c96b7a0ea09c3ef758424ffb93654ce1520571e32e1f83aecbeded2388c3a7a", + "0xb4a3a8023266d141ecd7c8a7ca5282a825410b263bc11c7d6cab0587c9b5446e", + "0xf38f535969d9592416d8329932b3a571c6eacf1763de10fb7b309d3078b9b8d4", + "0x5a1e7b1c3b3943158341ce6d7f9f74ae481975250d89ae4d69b2fcd4c092eb4e", + "0xdad31e707d352f6cca78840f402f2ac9292094b51f55048abf0d2badfeff5463", + "0x097e290170068e014ceda3dd47b28ede57ff7f916940294a13c9d4aa2dc98aad", + "0x22e2dcedb6bb7f8ace1e43facaa502daa7513e523be98daf82163d2a76a1e0be", + "0x7ef2b211ab710137e3e8c78b72744bf9de81c2adde007aef6e9ce92a05e7a2c5", + "0x49b427805fc5186f31fdd1df9d4c3f51962ab74e15229e813072ec481c18c717", + "0xe60f6caa09fa803d97613d58762e4ff7f22f47d5c30b9d0116cdc6a357de4464", + "0xab3507b37ee92f026c72cc1559331630bc1c7335b374e4418d0d02687df1a9dd", + "0x50825ae74319c9adebc8909ed7fc461702db8230c59975e8add09ad5e7a647ab", + "0x0ee8e9c1d8a527a42fb8c2c8e9e51faf727cffc23ee22b5a95828f2790e87a29", + "0x675c21c290ddb40bec0302f36fbcd2d1832717a4bc05d113c6118a62bc8f9aca", + "0x580bafab24f673317b533148d7226d485e211eaa3d6e2be2529a83ca842b58a7", + "0x540e474776cae597af24c147dc1ae0f70a6233e98cf5c3ce31f38b830b75c99a", + "0x36eaf9f286e0f356eaaf8d81f71cc52c81d9ebc838c3b4859009f8567a224d16", + "0x0e2cbbb40954be047d02b1450a3dbd2350506448425dc25fd5faf3a66ee8f5c4", + "0x7eb0390cfe4c4eb120bbe693e87adc8ecab51d5fd8ce8f911c8ff07fad8cbe20", + "0xbf77589f5c2ebb465b8d7936f6260a18a243f59bd87390ee22cf579f6f020285", + "0x695b96bb28693f6928777591ef64146466d27521280a295936a52ec60707c565", + "0x22a0d018cbd4274caa8b9e7fb132e0a7ed787874046ca683a7d81d1c7c8b8f15", + "0x84092b122bb35e5ad85407b4b55f33707b86e0238c7970a8583f3c44308ed1d9", + "0xea346067ca67255235f9cae949f06e4b6c93846a7abc7c8c8cd786e9c4b3e4bc", + "0xa6df0716b125dc696b5d0e520cb49c1c089397c754efc146792e95bc58cc7159", + "0x7377b5d3953029fc597fb10bb6479ee34133d38f08783fbb61c7d070f34ea66f", + "0x7d79b00ffb976a10cd24476a394c8ed22f93837c51a58a3ddc7418153a5a8ea1", + "0x01e55182e80dff26cc3e06bb736b4a63745bde8ae28c604fa7fb97d99de5f416", + "0x062a2d5a207f8d540764d09648afecbf5033b13aec239f722b9033a762acf18b", + "0x48be60a3221d98b4d62f0b89d3bef74c70878dd65c6f79b34c2c36d0ddaa1da0", + "0x41e11f33543cf045c1a99419379ea31523d153bdf664549286b16207b9648c85", + "0xeef4d30b4700813414763a199e7cc6ab0faec65ef8b514faa01c6aa520c76334", + "0xea7cfe990422663417715e7859fc935ca47f47c943a1254044b6bc5934c94bc8", + "0xbbd3c834e5403b98a0ca346c915a23310f3d58880786628bc6cfbe05ba29c3c5", + "0xe216379f385bc9995ae0f37f1409a78d475c56b8aeb4ee434326724ec20124f7", + "0xdd328a1eee19d09b6fef06e252f8ad0ae328fbf900ef745f5950896803a3899d", + "0xa16fde34b0d743919feb0781eca0c525a499d279119af823cb3a8817000335db", + "0x7a28d108c59b83b12c85cd9aabc1d1d994a9a0329ae7b64a32aadcd61ebe50e3", + "0xb28bc82fceae74312eb837a805f0a8a01c0f669b99bb03fde31c4d58bedff89b", + "0x1b0d8f37d349781e846900b51a90c828aa384afe9b8ee1f88aeb8dba4b3168f2", + "0xbfd0301ff964c286c3331a30e09e0916da6f484e9c9596dbf1cae3cc902dbf9e", + "0xbb8254cb9ef6b485b8fb6caeafe45f920affc30f6b9d671e9a454530536f4fef", + "0xcad2317cf63dfa7147ded5c7e15f5f72e78f42d635e638f1ece6bc722ca3638b", + "0xb6c6e856fd45117f54775142f2b38f31114539d8943bcbcf823f6c7650c001e4", + "0x869f1baa35684c8f67a5bc99b294187852e6c85243a2f36481d0891d8b043020", + "0x14c6ccf145ee40ff56e3810058d2fba9a943ffc7c7087c48a08b2451c13dc788", + "0x263c1bcb712890f155b7e256cefa4abf92fe4380f3ffc11c627d5e4e30864d18", + "0x69f4eaf655e31ad7f7a725cd415ce7e45dd4a8396ac416950d42ed33155c3487", + "0x47e8eec2c5e33c9a54fe1f9b09e7744b614fb16531c36b862aa899424be13b05", + "0x5c985de270e62c44f0b49157882e8e83641b906ce47959e337fe8423e125a2eb", + "0x4e13b11e13202439bb5de5eea3bb75d2d7bf90f91411163ade06161a9cf424db", + "0x583a8fa159bb74fa175d72f4e1705e9a3b8ffe26ec5ad6e720444b99288f1213", + "0x903d2a746a98dfe2ee2632606d57a9b0fa6d8ccd895bb18c2245fd91f8a43676", + "0xa35a51330316012d81ec7249e3f2b0c9d7fcbb99dd98c62fe880d0a152587f51", + "0x33818a7beb91730c7b359b5e23f68a27b429967ea646d1ea99c314353f644218", + "0x183650af1e0b67f0e7acb59f8c72cc0e60acc13896184db2a3e4613f65b70a8b", + "0x857ff2974bef960e520937481c2047938a718cea0b709282ed4c2b0dbe2ef8fa", + "0x95a367ecb9a401e98a4f66f964fb0ece783da86536410a2082c5dbb3fc865799", + "0x56c606a736ac8268aedadd330d2681e7c7919af0fe855f6c1c3d5c837aa92338", + "0x5c97f7abf30c6d0d4c23e762c026b94a6052a444df4ed942e91975419f68a3a4", + "0x0b571de27d2022158a3128ae44d23a8136e7dd2dee74421aa4d6ed15ee1090a0", + "0xa17f6bc934a2f3c33cea594fee8c96c1290feec934316ebbbd9efab4937bf9f9", + "0x9ff57d70f27aad7281841e76435285fd27f10dad256b3f5cabde4ddc51b70eff", + "0xafa3071a847215b3ccdf51954aa7cb3dd2e6e2a39800042fc42009da705508b2", + "0x5e3bea33e4ac6f7c50a077d19571b1796e403549b1ce7b15e09905a0cc5a4acf", + "0x0dc7ba994e632ab95f3ecb7848312798810cf761d1c776181882d17fd6dda075", + "0xb4f7158679dad9f7370a2f64fbe617a40092849d17453b4f50a93ca8c6885844", + "0x094564b00f53c6f27c121fd8adfe1685b258b259e585a67b57c85efb804c57b2", + "0x9cd21a4249ba3fccffad550cdb8409dc12d8b74a7192874b6bafe2363886f318", + "0xbb22e0dad55cb315c564c038686419d40ef7f13af2143a28455bf445f6e10393", + "0x2a71d5e00821178c2cd39e7501e07da5cca6680eb7cdbe996f52dccafadb3735", + "0x9619406093b121e044a5b403bb1713ae160aeb52ad441f82dc6c63e4b323b969", + "0x3b8bd1d82c6d67ae707e19b889f1cb1f7bba912f12ae4284298f3a70c3644c79", + "0xd7a70c50d47d48785b299dbea01bf03ef18b8495de3c35cb265bc8f3295c4e15", + "0x8802ecce8dd6b6190af8ac79aafda3479c29f548d65e5798c0ca51a529b19108", + "0x4b630e1df52ec5fd650f4a4e76b3eeddda39e1e9eab996f6d3f02eefdf690990", + "0x0bfbff60fcf7f411d469f7f6f0a58ca305fd84eb529ee3ac73c00174793d723e", + "0x535f78b5f3a99a1c498e2c19dc1acb0fbbaba8972ba1d7d66936c28ab3667ebe", + "0x06ba92d8129db98fec1b75f9489a394022854f22f2e9b9450b187a6fc0d94a86", + "0xb7ae275ba10f80fb618a2cf949d5ad2e3ae24eb2eb37dcf1ec8c8b148d3ba27f", + "0xb275579bcf2584d9794dd3fc7f999902b13d33a9095e1980d506678e9c263de1", + "0x843ccd52a81e33d03ad2702b4ef68f07ca0419d4495df848bff16d4965689e48", + "0xde8b779ca7250f0eb867d5abdffd1d28c72a5a884d794383fc93ca40e5bf6276", + "0x6b789a2befccb8788941c9b006e496b7f1b03dbb8e530ba339db0247a78a2850", + "0xfccd4dca80bc52f9418f26b0528690255e320055327a34b50caf088235d2f660", + "0x18479ebfbe86c1e94cd05c70cb6cace6443bd9fdac7e01e9c9535a9e85141f2f", + "0x5350c8f3296441db954a261238c88a3a0c51ab418a234d566985f2809e211148", + "0xa5636614135361d03a381ba9f6168e2fd0bd2c1105f9b4e347c414df8759dea3", + "0xe7bb69e600992e6bd41c88a714f50f450153f1a05d0ddb4213a3fc4ba1f48c3f", + "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", + "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", + ], + compressed_lamport_sk: + "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", + child_sk: + "7419543105316279183937430842449358701327973165530407166294956473095303972104" + } + } +} From ab4115ec73f42e249719fec96eff61867472a3ea Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:21:27 +1000 Subject: [PATCH 066/118] Move key derivation into own crate --- eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs | 0 eth2/utils/eth2_keystore/src/lib.rs | 2 -- 2 files changed, 2 deletions(-) rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs (100%) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_key_derivation/src/path.rs similarity index 100% rename from eth2/utils/eth2_keystore/src/path.rs rename to eth2/utils/eth2_key_derivation/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 9980cd6b75b..36728103eb2 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,9 +3,7 @@ mod derived_key; mod keystore; -mod lamport_secret_key; mod password; -mod path; mod plain_text; pub mod json_keystore; From cf3830bb563bb1a63fcf1eb5cb4c9837f437ab8d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 16:10:17 +1000 Subject: [PATCH 067/118] Start defining JSON wallet --- Cargo.lock | 12 +++++++++ Cargo.toml | 1 + eth2/utils/eth2_wallet/Cargo.toml | 15 +++++++++++ eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 25 +++++++++++++++++++ eth2/utils/eth2_wallet/src/lib.rs | 1 + 5 files changed, 54 insertions(+) create mode 100644 eth2/utils/eth2_wallet/Cargo.toml create mode 100644 eth2/utils/eth2_wallet/src/json_wallet/mod.rs create mode 100644 eth2/utils/eth2_wallet/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d19862eb5f5..fe2c37cd8ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1274,6 +1274,18 @@ dependencies = [ "types 0.2.0", ] +[[package]] +name = "eth2_wallet" +version = "0.1.0" +dependencies = [ + "eth2_key_derivation 0.1.0", + "eth2_keystore 0.1.0", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethabi" version = "12.0.0" diff --git a/Cargo.toml b/Cargo.toml index b2ee8e1120f..50ad1c962af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "eth2/utils/eth2_key_derivation", "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", + "eth2/utils/eth2_wallet", "eth2/utils/logging", "eth2/utils/eth2_hashing", "eth2/utils/hashmap_delay", diff --git a/eth2/utils/eth2_wallet/Cargo.toml b/eth2/utils/eth2_wallet/Cargo.toml new file mode 100644 index 00000000000..6471aceb2dd --- /dev/null +++ b/eth2/utils/eth2_wallet/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "eth2_wallet" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0.102" +serde_json = "1.0.41" +serde_repr = "0.1" +uuid = { version = "0.8", features = ["serde", "v4"] } +eth2_keystore = { path = "../eth2_keystore" } +eth2_key_derivation = { path = "../eth2_key_derivation" } diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs new file mode 100644 index 00000000000..eda1d7a1762 --- /dev/null +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::*; +pub use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct JsonWallet { + name: String, + nextaccount: u32, + uuid: Uuid, + version: Version, +} + +/// Version for `JsonWallet`. +#[derive(Debug, Clone, PartialEq, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum Version { + V1 = 1, +} + +impl Version { + pub fn one() -> Self { + Version::V1 + } +} diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs new file mode 100644 index 00000000000..5b8c06754ac --- /dev/null +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -0,0 +1 @@ +mod json_wallet; From 7bcb68ca5c2d8d3496a6c96c7b6ed2224ac93c26 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 16:51:09 +1000 Subject: [PATCH 068/118] Add progress --- eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 3 +++ eth2/utils/eth2_wallet/src/lib.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs index eda1d7a1762..f7eb083bea7 100644 --- a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -1,10 +1,13 @@ +use eth2_keystore::json_keystore::Crypto; use serde::{Deserialize, Serialize}; use serde_repr::*; + pub use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct JsonWallet { + crypto: Crypto, name: String, nextaccount: u32, uuid: Uuid, diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index 5b8c06754ac..9adf2d1fd0f 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -1 +1,2 @@ mod json_wallet; +mod wallet; From 702d0098fa602b310fe24d3a3a061210f044896a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 17:16:26 +1000 Subject: [PATCH 069/118] Split out encrypt/decrypt --- eth2/utils/eth2_keystore/src/keystore.rs | 129 ++++++++++++++--------- eth2/utils/eth2_keystore/src/lib.rs | 2 +- 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index ec96f81aa39..5ed24825efc 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -3,8 +3,8 @@ use crate::derived_key::DerivedKey; use crate::json_keystore::{ - Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, HexBytes, - JsonKeystore, Kdf, KdfModule, Scrypt, Sha256Checksum, Version, + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonKeystore, + Kdf, KdfModule, Scrypt, Sha256Checksum, Version, }; use crate::plain_text::PlainText; use crate::Password; @@ -138,22 +138,9 @@ impl Keystore { uuid: Uuid, path: String, ) -> Result { - let derived_key = derive_key(&password, &kdf)?; - let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); - // Encrypt secret. - let mut cipher_text = vec![0; secret.len()]; - match &cipher { - Cipher::Aes128Ctr(params) => { - crypto::aes::ctr( - crypto::aes::KeySize::KeySize128, - &derived_key.as_bytes()[0..16], - params.iv.as_bytes(), - ) - .process(secret.as_bytes(), &mut cipher_text); - } - }; + let (cipher_text, checksum) = encrypt(&secret, &password, &kdf, &cipher)?; Ok(Keystore { json: JsonKeystore { @@ -166,7 +153,7 @@ impl Keystore { checksum: ChecksumModule { function: Sha256Checksum::function(), params: EmptyMap, - message: generate_checksum(&derived_key, &cipher_text), + message: checksum.to_vec().into(), }, cipher: CipherModule { function: cipher.function(), @@ -189,35 +176,7 @@ impl Keystore { /// - The provided password is incorrect. /// - The keystore is badly formed. pub fn decrypt_keypair(&self, password: Password) -> Result { - let cipher_message = &self.json.crypto.cipher.message; - - // Generate derived key - let derived_key = derive_key(&password, &self.json.crypto.kdf.params)?; - - // Mismatching checksum indicates an invalid password. - if generate_checksum(&derived_key, cipher_message.as_bytes()) - != self.json.crypto.checksum.message - { - return Err(Error::InvalidPassword); - } - - let mut plain_text = PlainText::zero(cipher_message.len()); - match &self.json.crypto.cipher.params { - Cipher::Aes128Ctr(params) => { - crypto::aes::ctr( - crypto::aes::KeySize::KeySize128, - &derived_key.as_bytes()[0..16], - // NOTE: we do not check the size of the `iv` as there is no guidance about - // this on EIP-2335. - // - // Reference: - // - // - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 - params.iv.as_bytes(), - ) - .process(cipher_message.as_bytes(), plain_text.as_mut_bytes()); - } - }; + let plain_text = decrypt(&password, &self.json.crypto)?; // Verify that secret key material is correct length. if plain_text.len() != SECRET_KEY_LEN { @@ -275,17 +234,87 @@ impl Keystore { } } +/// Returns `(cipher_text, checksum)` for the given `plain_text` encrypted with `Cipher` using a +/// key derived from `password` via the `Kdf` (key derivation function). +/// +/// ## Errors +/// +/// - The `kdf` is badly formed (e.g., has some values set to zero). +pub fn encrypt( + plain_text: &PlainText, + password: &Password, + kdf: &Kdf, + cipher: &Cipher, +) -> Result<(Vec, [u8; HASH_SIZE]), Error> { + let derived_key = derive_key(&password, &kdf)?; + + // Encrypt secret. + let mut cipher_text = vec![0; plain_text.len()]; + match &cipher { + Cipher::Aes128Ctr(params) => { + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + &derived_key.as_bytes()[0..16], + params.iv.as_bytes(), + ) + .process(plain_text.as_bytes(), &mut cipher_text); + } + }; + + let checksum = generate_checksum(&derived_key, &cipher_text); + + Ok((cipher_text, checksum)) +} + +/// Regenerate some `plain_text` from the given `password` and `crypto`. +/// +/// ## Errors +/// +/// - The provided password is incorrect. +/// - The `crypto.kdf` is badly formed (e.g., has some values set to zero). +pub fn decrypt(password: &Password, crypto: &Crypto) -> Result { + let cipher_message = &crypto.cipher.message; + + // Generate derived key + let derived_key = derive_key(&password, &crypto.kdf.params)?; + + // Mismatching checksum indicates an invalid password. + if &generate_checksum(&derived_key, cipher_message.as_bytes())[..] + != crypto.checksum.message.as_bytes() + { + return Err(Error::InvalidPassword); + } + + let mut plain_text = PlainText::zero(cipher_message.len()); + match &crypto.cipher.params { + Cipher::Aes128Ctr(params) => { + crypto::aes::ctr( + crypto::aes::KeySize::KeySize128, + &derived_key.as_bytes()[0..16], + // NOTE: we do not check the size of the `iv` as there is no guidance about + // this on EIP-2335. + // + // Reference: + // + // - https://github.com/ethereum/EIPs/issues/2339#issuecomment-623865023 + params.iv.as_bytes(), + ) + .process(cipher_message.as_bytes(), plain_text.as_mut_bytes()); + } + }; + Ok(plain_text) +} + /// Generates a checksum to indicate that the `derived_key` is associated with the /// `cipher_message`. -fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> HexBytes { +fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> [u8; HASH_SIZE] { let mut hasher = Sha256::new(); hasher.input(&derived_key.as_bytes()[16..32]); hasher.input(cipher_message); - let mut digest = vec![0; HASH_SIZE]; + let mut digest = [0; HASH_SIZE]; hasher.result(&mut digest); - - digest.into() + digest } /// Derive a private key from the given `password` using the given `kdf` (key derivation function). diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 36728103eb2..56174762158 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -8,6 +8,6 @@ mod plain_text; pub mod json_keystore; -pub use keystore::{Error, Keystore, KeystoreBuilder}; +pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; pub use uuid::Uuid; From 6073b78a3ed307bdd3307832b21b896e3ea80ec8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 19:55:58 +1000 Subject: [PATCH 070/118] First commits on path derivation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 eth2/utils/eth2_keystore/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 56174762158..eeb3dde833b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -4,6 +4,7 @@ mod derived_key; mod keystore; mod password; +mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs new file mode 100644 index 00000000000..dcfa26c8966 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -0,0 +1,50 @@ +use crate::plain_text::PlainText; +use crypto::{digest::Digest, sha2::Sha256}; + +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; +/// The digest size (in octets) of the hash function (SHA256) +const K: usize = HASH_SIZE; +/// The size of the lamport array. +const LAMPORT_ARRAY_SIZE: usize = 255; +/// The HKDF output size (in octets) +const L: usize = K * LAMPORT_ARRAY_SIZE; + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { + hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) +} + +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { + let mut hasher = Sha256::new(); + hasher.input(salt); + hasher.input(ikm); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { + let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); + + debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); + + for i in 0..LAMPORT_ARRAY_SIZE { + let mut hasher = Sha256::new(); + + hasher.input(prk); + + if let Some(prev) = okm.last() { + hasher.input(&prev[..]); + } + + hasher.input(&[i as u8]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.push(digest); + } + + okm +} From 136ca3b00c6c81f01c681903a88ad18687cbb6b9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 13:33:11 +1000 Subject: [PATCH 071/118] Progress with implementation --- Cargo.lock | 1 + eth2/utils/eth2_keystore/Cargo.toml | 1 + eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 674 ++++++++++++++++++++++++++- 4 files changed, 658 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d184550f7fe..b32c2057a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "bls 0.2.0", "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index 917a3f63ba5..8e83f2e759d 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.3" bls = { path = "../bls" } eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" +num-bigint = "0.2.6" [dev-dependencies] tempfile = "3.1.0" diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index eeb3dde833b..51231797d6f 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,7 @@ mod derived_key; mod keystore; +mod lamport_secret_key; mod password; mod path; mod plain_text; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index dcfa26c8966..699278fb332 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,20 +1,90 @@ -use crate::plain_text::PlainText; +use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; use crypto::{digest::Digest, sha2::Sha256}; +use num_bigint::BigUint; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; -/// The digest size (in octets) of the hash function (SHA256) -const K: usize = HASH_SIZE; +pub const HASH_SIZE: usize = 32; /// The size of the lamport array. -const LAMPORT_ARRAY_SIZE: usize = 255; -/// The HKDF output size (in octets) -const L: usize = K * LAMPORT_ARRAY_SIZE; +pub const LAMPORT_ARRAY_SIZE: u8 = 255; -fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { +pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + +fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { + hkdf_mod_r(seed) +} + +fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { + let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); + hkdf_mod_r(&compressed_lamport_pk) +} + +fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); + + // TODO: don't do all this extra work with extraction. + let lamport_bytes = lamport + .iter_chunks() + .map(|a| a.to_vec()) + .flatten() + .collect::>(); + + // TODO: justify 48. + mod_r(&lamport_bytes[0..48]) +} + +fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(bytes.len(), HASH_SIZE); + + let n = BigUint::from_bytes_be(bytes); + let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); + let x = (n % r).to_bytes_be(); + + debug_assert!(x.len() <= HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output +} + +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { + let salt = index.to_be_bytes(); + let not_ikm = flip_bits(ikm); + + let lamports = [ + ikm_to_lamport_sk(ikm, &salt), + ikm_to_lamport_sk(¬_ikm, &salt), + ]; + + let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + + lamports + .iter() + .map(LamportSecretKey::iter_chunks) + .flatten() + .enumerate() + .for_each(|(i, chunk)| { + let output_slice = lamport_pk + .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) + .expect("lamport_pk must have adequate capacity"); + + let mut hasher = Sha256::new(); + hasher.input(chunk); + hasher.result(output_slice); + }); + + let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut hasher = Sha256::new(); + hasher.input(&lamport_pk); + hasher.result(&mut compressed_lamport_pk); + + compressed_lamport_pk +} + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) } -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { +fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { let mut hasher = Sha256::new(); hasher.input(salt); hasher.input(ikm); @@ -25,26 +95,592 @@ fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { digest.into() } -fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { - let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); - - debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); +fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { + let mut okm = LamportSecretKey::zero(); for i in 0..LAMPORT_ARRAY_SIZE { let mut hasher = Sha256::new(); hasher.input(prk); - if let Some(prev) = okm.last() { - hasher.input(&prev[..]); + if let Some(prev) = i.checked_sub(1) { + hasher.input(okm.get_chunk(prev)) } - hasher.input(&[i as u8]); + hasher.input(&[i + 1]); - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.push(digest); + hasher.result(okm.get_mut_chunk(i)); } okm } + +fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(input.len(), HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + + for (i, byte) in input.iter().enumerate() { + output[i] = !byte + } + + output +} + +#[cfg(test)] +mod test { + use super::*; + + struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + lamport_0: Vec<&'static str>, + lamport_1: Vec<&'static str>, + compressed_lamport_sk: &'static str, + child_sk: &'static str, + } + + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec<[u8; HASH_SIZE]>, + lamport_1: Vec<[u8; HASH_SIZE]>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + todo!() + } + } + + fn get_raw_vector() -> RawTestVector { + RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: + "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + lamport_0: vec![ + "0x7b4a587eac94d7f56843e718a04965d4832ef826419b4001a3ad0ba77eb44a3b", + "0x90f45a712112122429412921ece5c30eb2a6daf739dc9034fc79424daeb5eff6", + "0xd061c2799de00b2be90eb1cc295f4c31e22d4b45c59a9b9b2554379bea7783cb", + "0x3ad17e4cda2913b5180557fbe7db04b5ba440ce8bb035ae27878d66fbfa50d2c", + "0xf5b954490933ad47f8bf612d4a4f329b3aa8914b1b83d59e15e271e2a087e002", + "0x95d68d505bf4ff3e5149bc5499cf4b2f00686c674a29a8d903f70e569557d867", + "0x1b59c76d9bb2170b220a87833582ede5970d4a336d91c99a812825afe963e056", + "0x4310ff73cfbbf7b81c39ecbf1412da33e9388c1a95d71a75e51fe12256551ceb", + "0xee696343f823e5716e16747f3bbae2fc6de233fe10eea8e45b4579018da0874f", + "0xae12a437aaa7ae59f7d8328944b6a2b973a43565c55d5807dc2faf223a33aa73", + "0x2a3ae0b47f145bab629452661ff7741f111272e33ec571030d0eb222e1ed1390", + "0x1a3ea396e8cbd1d97733ef4753d6840b42c0795d2d693f18e6f0e7b3fff2beb2", + "0x472429d0643c888bfdfe6e6ccfdeee6d345d60c6710859ac29fc289fd3656347", + "0xa32d4d955949b8bed0eb20f586d8fd516d6ddec84fbbc36998d692633c349822", + "0xe5ac8ac5ee1d40e53a7abf36e8269d5d5fce450a87feae8e59f432a44bcc7666", + "0xddf9e497ed78032fbd72d9b8abd5204d81c3475f29afa44cdf1ded8ea72dd1dc", + "0x945c62e88fb1e5f3c15ff57cd5eb1586ee93ec5ec80154c5a9c50241c5adae0a", + "0xc8868b50fc8423c96b7efa1ede4d3203a6b835dbeb6b2ababc58397e6b31d9dd", + "0x66de9bd86b50e2b6a755310520af655759c1753bff34b79a5cd63d6811fc8c65", + "0x5b13786c6068df7735343e5591393bea8aee92ac5826d6132bf4f5ebf1098776", + "0xa2038fc7d8e3cb2eda2bd303cfa76a9e5d8b88293918bec8b2fc03be75684f14", + "0x47a13f6b2308a50eded830fdee7c504bf49d1fe6a95e337b0825d0d77a520129", + "0xb534cdddcf1aa1c6b4cbba46d1db31b766d958e0a0306450bc031d1e3ed79d97", + "0x54aa051b754c31658377f7bff00b7deaa861e74cb12e1eb84216666e19b23d69", + "0x0220d57f63435948818eb376367b113c188e37451c216380f65d1ad55f73f527", + "0xf9dd2e391565534a4db84980433bf5a56250f45fe294fce2679bcf115522c081", + "0x1166591ee2ca59b9f4e525900f085141be8879c66ef18529968babeb87c44814", + "0xf4fa2e8de39bdbeb29b64d8b440d3a6c9a6ca5bdce543877eaee93c11bd70ab8", + "0x07f466d73b93db283b3f7bfaf9c39ae296adc376ab307ef12312631d0926790e", + "0xb2ecff93acb4fa44c1dbf8464b81734a863b6d7142b02f5c008907ea4dc9aaa1", + "0xa1d9c342f6c293ac6ef8b5013cba82c4bad6ed7024d782948cb23cd490039ba1", + "0xc7d04a639ba00517ece4dbc5ef4aaf20e0ccde6e4a24c28936fabe93dec594db", + "0xe3cbb9810472d9dd1cdb5eed2f74b67ea60e973d2d2e897bd64728c9b1aa0679", + "0xe36884703413958ff2aba7a1f138a26d0ac0a371270f0169219beb00a5add5f0", + "0xe5ea300a09895b3f98de5232d92a36d5611cbcf9aaf9e7bb20cf6d1696ad1cb4", + "0xc136cda884e18175ab45148ed4f9d0d1a3c5e11ad0275058e61ae48eb151a81f", + "0x3ee1101e944c040021187e93b6e0beb1048c75fb74f3fdd67756b1c8517a311f", + "0x016964fd6fc32b9ad07a630949596715dee84d78230640368ff0929a280cf3a2", + "0xe33865fc03120b94333bb754fd097dc0f90e69ff6fd221d6aae59fcf2d762d76", + "0xe80bb3515a09ac6ecb4ec59de22701cdf954b1ae8a677fd85508c5b041f28058", + "0x3889af7cd325141ec288021ede136652a0411d20364005b9d3ca9102cb368f57", + "0x18dad0bc975cf8800addd54c7867389d3f7fe1b97d348bd8412a6cbfb75c520a", + "0x09035218686061ee91bd2ad57dc6fb6da7243b8177a153484524b2b228da5314", + "0x688fd7a97551c64eae33f91abb073a46eafbbacd5595c6bac2e57dd536acdfe2", + "0x1fc164dce565a1d0da59cc8048b334cc5eb84bf04de2399ddb847c22a7e32ab7", + "0xa2a340ba05c8a30dd1cab886a926b761758eba0e41b5c4c5dfd4a42f249655c1", + "0xc43dffe01479db836a6a1a74564b297fad0d69c6b06cf593f6db9f26b4f307d5", + "0x73cef7f3ff724a30a79e1dca74cef74954afeefa2e476c4dec65afe50c16c5c4", + "0xa54002253ab7b95cc5b664b3f08976400475cc56f170b939f6792e730ff5170b", + "0x9ade43053d41afebc002f09476dffd1b13ecbf67f810791540b92ca56d5e63e4", + "0x234e7cbfbe45b22a871db26738fa05de09213a925439d7f3e5108132e521b280", + "0x066b712417332c7cfca871fb1bb5839f0341acf9266229603a3eddbc8a93b59f", + "0xb5857acdcf636330da2cfcc99c81d9fdbd20c506a3c0e4f4f6a139d2a64f051c", + "0xe119908a150a49704b6bbba2c470cd619a0ae10dd9736e8d491890e3c8509fff", + "0xb8a5c5dbb51e6cb73cca95b4ad63ea3c7399cd16b05ab6261535495b3af2ca51", + "0x05624a1d4d2d2a31160bc48a6314bbf13eaddf56cddb0f0aa4ed3fb87f8b479f", + "0x483daceff1c3baa0ed0f3be7e534eebf5f4aed424ecd804edfbf5c56b3476b50", + "0x424d04694e7ae673707c77eb1c6d0996d250cfab6832ee3506a12e0384a3c5c9", + "0xa11fed0ed8057966bfe7136a15a814d06a516fbc9d44aeef87c509137a26190e", + "0x3694d22d1bc64658f3adbe2cc9f1716aee889066e0950e0b7a2fd576ed36bb76", + "0x49a13000a87f39f93d0ae9c3a4cfccbf440c0a75cce4c9d70dac627b6d6958b3", + "0xb3ff0cdd878d5ac1cb12e7d0b300d649fdd008800d498ae4f9fbf9510c74249a", + "0xe52a867cfb87d2fe7102d23d8d64925f7b75ca3f7d6bb763f7337352c255e0be", + "0x6513b372e4e557cca59979e48ec27620e9d7cdb238fcf4a9f19c3ba502963be0", + "0x9f69d82d4d51736902a987c8b5c30c2b25a895f2af5d2c846667ff6768bcc774", + "0x049a220dbe3340749f94643a429cb3cba3c92b561dc756a733d652d838728ab3", + "0x4fa2cd877aa115b476082b11053309f3537fa03d9158085f5f3f4bab6083e6da", + "0xed12db4069eb9f347735816afcee3fe43d4a6999fef8240b91bf4b05447d734f", + "0x3ecbe5eda469278f68548c450836a05cc500864664c7dda9b7526f084a891032", + "0x690d8f928fc61949c22e18cceaa2a446f8e1b65bd2e7af9e0a8e8284134ab3d2", + "0x99e09167a09f8261e7e8571d19148b7d7a75990d0702d9d582a2e4a96ac34f8e", + "0x6d33931693ed7c2e1d080b6a37da52c279a06cec5f534305819f7adf7db0afe3", + "0xc4b735462a9a656e28a52b1d4992ea9dea826b858971d698453a4be534d6bb70", + "0xedf92b10302dc41f8d362b360f4c2ef551d50e2ded012312c964002d2afc46d7", + "0x58f6691cca081ae5c3661dd171b87cc49c90359bb03cc0e57e503f7fcf14aefc", + "0x5d29b8b4ee295a73c4a8618927b3d14b76c7da049133a2257192b10be8c17a6a", + "0x646802fa42801e0ae24011fb4f62e87219ef1da01f7fc14bf8d6bd2d9e7c21f1", + "0x23abf45eee65cc4c1e95ccab42ad280a00bb3b14d243e2021a684075f900141e", + "0x2b1ae95c975bf9c387eae506fdb5e58afd2d198f00a21cd3fddb5855e8021e4d", + "0x0ef9f6e1c0583493d343e75f9c0c557fa6da0dc12b17a96c5757292916b72ee3", + "0x04c7fc76195c64a3285af14161077c045ff6ddbb67c0ff91b080f98eb6781e5c", + "0xba12679b97027d0e7076e6d19086c07792eaa7f78350842fbef8ddf5bcd3ecc0", + "0xcead458e6799df4d2f6cbf7f13cb3afec3441a354816e3071856ed49cbdbb1a7", + "0xbe6c56256556bb5c6727a1d9cb641d969677f56bb5ad7f8f7a7c9cfd128427b4", + "0xc80f11963ff40cb1888054b83c0463d32f737f2e7d42098e639023db0dfc84d4", + "0xac80006c1296bcfde86697efebb87fb0fddfb70dd34dd2ee4c152482af4687eb", + "0xbb7d13ce184249df4576fc3d13351e1683500e48726cd4198423f14f9094068b", + "0x1b2d9c40c55bd7362664fa46c1268e094d56c8193e3d991c08dc7a6e4ca14fa1", + "0x9bd236254d0565f5b2d24552d4b4d732de43b0adaa64ecd8be3efc6508577591", + "0x38078cefccc04e8312d79e0636e0e3157434c50a2ad4e3e87cc6584c41eec8b5", + "0xb5d15a8527ff3fa254ba61ffceb02d2570b53361894f351a9e839c0bb716857d", + "0x6763dad684bf2e914f40ae0a7ee0cdf12c97f41fc05a485d5991b4daad21a3f8", + "0xc80363c20df589333ecbe05bd5f2c19942ebc2593626dc50d00835c40fb8d005", + "0x48502b56ae93acd2794f847cbe825525d5d5f59f0f75c67aff84e5338776b3af", + "0xfd8e033493ba8af264a855a78ab07f37d936351d2879b95928909ed8df1b4f91", + "0x11f75bee9eac7356e65ebc7f004ccdc1da80807380d69143293d1421f50b1c97", + "0x903a88a3ebe84ca1c52a752b1faffa9ca1daedac9cbf1aa70942efc9beb44b79", + "0x2c0dcd68837f32a69da651045ad836b8cd6b48f2c8c5d73a3bd3bba6148d345a", + "0x0aa0f49b3476f3fdb6393f2ab601e0009586090b72ee54a525734f51598960d5", + "0xf7a789f013f702731656c562caa15b04cb7c9957376c4d80b8839167bb7fa626", + "0x4e0be1b19e305d82db3fd8affd67b0d2559da3edbfb08d19632a5cc46a90ed07", + "0x3caaccfc546d84d543eaf4f4c50c9c8fd831c12a8de56fdb9dfd04cc082882fe", + "0x894f6a01fd34f0642077e22981752011678548eb70eb55e8072c1caffc16fe02", + "0xae7eb54adaa68679348ea3537a49be669d1d61001fbab9fac259ba727dbc9a1a", + "0x291a1cbdceff957b5a65440ab67fb8672de881230fe3108a15ca487c2662c2c7", + "0x891d43b867137bf8beb9df4da2d951b5984a266a8cd74ec1593801d005f83f08", + "0xc558407f6491b37a10835e0ad7ce74f4e368aa49157a28873f7229310cb2d7fd", + "0x9ce061b0a072e1fe645f3479dac089b5bfb78cfa6cfbe5fd603bcdb504711315", + "0xa8e30d07b09275115dd96472ecf9bc316581caf307735176ca226d4cd9022925", + "0x918ee6d2efba7757266577691203f973cf4f4cac10f7d5f86acd2a797ff66583", + "0xfa31ba95e15d1635d087522f3d0da9cf7acac4ed6d0ac672654032a3c39244a6", + "0xf2952b58f015d6733af06938cd1f82fbddb3b796823bee7a3dbffa04efc117c2", + "0x46f8f742d3683de010ede528128d1181e8819f4252474f51371a177bfa518fa4", + "0x4ca1cc80094f2910cf83a9e65ad70e234690ffb9142793911ec7cf71663545b3", + "0x381965037b5725c71bfa6989d4c432f6611de8e8ec387f3cfc0dcb1a15191b73", + "0x2562b88ed3b86ba188be056805a3b7a47cb1a3f630d0e2f39647b0792ec6b7d8", + "0x565f6d14e7f22724f06d40f54465ad40d265b6de072b34a09d6e37a97a118cd8", + "0xc2982c861ad3278063b4a5f584eaf866db684cc4e712d64230fc9ee33bb4253b", + "0xfd806c91927e549d8d400ab7aa68dbe60af988fbabf228483ab0c8de7dab7eee", + "0xafae6ff16c168a3a3b5c2f1742d3f89fa4777c4bd0108f174014debf8f4d629c", + "0xaf5a4be694de5e53632be9f1a49bd582bf76002259460719197079c8c4be7e66", + "0xa8df4a4b4c5bf7a4498a11186f8bb7679137395f28e5c2179589e1c1f26504b5", + "0xce8b77c64c646bb6023f3efaed21ca2e928e21517422b124362cf8f4d9667405", + "0x62e67a8c423bc6c6c73e6cd8939c5c1b110f1a38b2ab75566988823762087693", + "0x7e778f29937daaa272d06c62d6bf3c9c0112d45a3df1689c602d828b5a315a9f", + "0xe9b5abd46c2377e602ff329050afa08afe152f4b0861db8a887be910ff1570bf", + "0xa267b1b2ccd5d96ae8a916b0316f06fafb886b3bb41286b20763a656e3ca0052", + "0xb8ed85a67a64b3453888a10dedf4705bd27719664deff0996a51bb82bc07194f", + "0x57907c3c88848f9e27bc21dd8e7b9d61de48765f64d0e943e7a6bb94cc2021ab", + "0xd2f6f1141a3b76bf9bf581d49091142944c7f9f323578f5bdd5522ba32291243", + "0xc89f104200ed4c5d5f7046d99e68ae6f8ec31e2eeceb568eb05087e3aa546a74", + "0xc9f367fae45c39299693b134229bb6dd0da112fd1a7d19b7f4772c01e5cbe479", + "0x64e2d4ad51948764dd578d26357e29e8e4d076d65c05cffdf8211b624fefe9ac", + "0xf9a9b4e6d5be7fc051df8ecd9c389d16b1af86c749308e6a23f7ff4871f0ba9a", + "0x0d2b2a228b86ebf9499e1bf7674335087ced2eb35ce0eb90954a0f75751a2bf4", + "0xff8531b45420a960d6e48ca75d77758c25733abde83cd4a6160beae978aa735e", + "0xd6d412bd1cb96a2b568d30e7986b7e8994ca92fd65756a758295499e11ea52b6", + "0xad8533fccbecdd4a0b00d648bfe992360d265f7be70c41d9631cefad5d4fe2f6", + "0x31fbf2afb8d5cc896d517cfc5201ee24527e8d283f9c37ca10233bef01000a20", + "0x2fd67b7365efc258131eb410f46bf3b1cbd3e9c76fd6e9c3e86c9ff1054116ff", + "0xab6aa29f33d18244be26b23abadb39679a8aa56dafc0dd7b87b672df5f5f5db6", + "0xbad3b0f401ca0a53a3d465de5cecd57769ec9d4df2c04b78f8c342a7ed35bbee", + "0xbdc24d46e471835d83ce8c5b9ecbe675aab2fd8f7831c548e8efd268c2ee2232", + "0x87265fabd7397d08f0729f13a2f3a25bbc8c874b6b50f65715c92b62f665f925", + "0xa379fd268e7ff392c067c2dd823996f72714bf3f936d5eeded71298859f834cb", + "0xf3ab452c9599ebfbb234f72a86f3062aed12ae1f634abbe542ff60f5cefc1fcf", + "0x2b17ebb053a3034c07da36ed2ba42c25ad8e61dec87b5527f5e1c755eb55405a", + "0x305b40321bd67bf48bfd121ee4d5d347268578bd4b8344560046594771a11129", + "0xe7029c9bea020770d77fe06ca53b521b180ad6a9e747545aadc1c74beef7241c", + "0xabc357cec0f4351a5ada22483d3b103890392f8d8f9cb8073a61969ed1be4e08", + "0x97f88c301946508428044d05584dc41af2e6a0de946de7d7f5269c05468afe20", + "0xbdc08fe8d6f9a05ad8350626b622ad8eec80c52331d154a3860c98676719cfbd", + "0x161590fc9f7fcf4eaba2f950cf588e6da79e921f139d3c2d7ebe017003a4799e", + "0x91b658db75bc3d1954bfde2ef4bc12980ff1688e09d0537f170c9ab47c162320", + "0x76d995f121406a63ce26502e7ec2b653c221cda357694a8d53897a99e6ce731e", + "0x3d6b2009586aceb7232c01259bb9428523c02b0f42c2100ec0d392418260c403", + "0x14ca74ecbc8ec0c67444c6cb661a2bce907aa2a1453b11f16002b815b94a1c49", + "0x553b4dc88554ebe7b0a3bd0813104fd1165a1f950ceace11f5841aa74b756d85", + "0x4025bf4ad86751a156d447ce3cabafde9b688efcdafd8aa4be69e670f8a06d9e", + "0x74260cf266997d19225e9a0351a9acfa17471fccdf5edc9ccc3bb0d23ef551c5", + "0xf9dbca3e16d234e448cf03877746baeb62a8a25c261eff42498b1813565c752a", + "0x2652ec98e05c1b6920fb6ddc3b57e366d514ffa4b35d068f73b5603c47f68f2f", + "0x83f090efeb36db91eb3d4dfbb17335c733fce7c64317d0d3324d7caaaf880af5", + "0x1e86257f1151fb7022ed9ed00fb961a9a9989e58791fb72043bb63ed0811791c", + "0xd59e4dcc97cba88a48c2a9a2b29f79125099a39f74f4fb418547de8389cd5d15", + "0x875a19b152fe1eb3fe1de288fa9a84864a84a79bac30b1dbd70587b519a9770e", + "0x9c9dc2d3c8f2f6814cfc61b42ee0852bbaf3f523e0409dd5df3081b750a5b301", + "0xf6f7f81c51581c2e5861a00b66c476862424151dd750efeb20b7663d552a2e94", + "0x723fcb7ca43a42483b31443d4be9b756b34927176f91a391c71d0b774c73a299", + "0x2b02d8acf63bc8f528706ed4d5463a58e9428d5b71d577fd5daa13ba48ac56cf", + "0x2ff6911f574c0f0498fc6199da129446b40fca35ccbf362bc76534ba71c7ca22", + "0x1ef4b959b11bc87b11e4a5f84b4d757c6bdcfad874acec9a6c9eee23dc4bbe1b", + "0x68e2df9f512be9f64b7e3a2dee462149dac50780073d78b569a20256aea5f751", + "0xd1a3682e12b90ae1eab27fc5dc2aef3b8e4dbb813925e9a91e58d6c9832767b6", + "0x75778ccc102d98c5e0b4b83f7d4ef7fe8bc7263cc3317723001cb0b314d1e9e8", + "0xc7f44e2cead108dc167f0036ac8a278d3549cc3dd5cc067d074ccad9b1d9f8d4", + "0x4cba0223c5df2796b0ee9fbc084d69f10e6aedda8f0cf86171bebb156ede676c", + "0x628deda825661f586a5713e43c806fdd55e1a53fbe90a4ddb5f3786570740954", + "0xfc82a253bc7e0ac96252b238fbb411a54e0adf78d089f804a7fc83a4959b401e", + "0x72a6491f5daae0ceb85b61a5ed69009dd2a167c64cb35cabf38b846e27268e9d", + "0xee139a913d4fcf25ba54bb36fc8051b91f2ec73ba820cc193c46fb2f7c37a106", + "0x7f75021f2b1d0c78859478e27f6f40646b5776c060f1a5f6f0944c840a0121f8", + "0x5b60a1b78feca1d2602ac8110d263ad6b3663cbf49e6bdc1077b4b80af2feb6f", + "0xd61f15d80b1e88469b6a76ed6a6a2b94143b6acc3bd717357264818f9f2d5c6d", + "0xea85da1780b3879a4d81b685ba40b91c060866abd5080b30fbbb41730724a7dd", + "0xb9b9da9461e83153f3ae0af59fbd61febfde39eb6ac72db5ed014797495d4c26", + "0xf737762fe8665df8475ff341b3762aaeb90e52974fe5612f5efd0fc1c409d7f8", + "0xaaa25d934a1d5aa6b2a1863704d7a7f04794ed210883582c1f798be5ca046cf7", + "0x932f46d0b6444145221b647f9d3801b6cb8b1450a1a531a959abdaacf2b5656b", + "0xf4a8b0e52f843ad27635c4f5a467fbf98ba06ba9a2b93a8a97170b5c41bf4958", + "0x196ed380785ee2925307ec904161dc02a4596a55499e5b0a3897f95485b3e74a", + "0x772e829a405219e4f8cd93a1ef15c250be85c828c1e29ef6b3f7b46958a85b44", + "0xd66cfc9af9941515d788f9f5e3b56fddb92464173ddb67b83bf265e7ea502170", + "0xf5b040bfc246425278e2423b1953d8ad518de911cf04d16c67d8580a09f90e62", + "0xd2d18b2ae8a53dde14b4000e5e7e414505825f50401a3797dd8820cf510dc448", + "0xc01dcc064e644266739cd0ec7edf92fc2ef8e92e0beedf0e8aa30efcff1644fe", + "0x24720d325913ba137daf031924ad3bfaa1c8c00a53a2d048fe5667aef45efce3", + "0x70a24e1c89b3ea78d76ef458d498dcb5b8561d484853b2a8b2adcd61869857df", + "0x0ff3313997f14e1b1dcd80f1d62c58aaefb19efd7c0ea15dde21aa4e2a516e80", + "0x960c1f50062a4df851638f42c0259b6e0a0217300884f13a3c5c8d94adb34f21", + "0xb71ca7cc8578149da556131268f4625b51620dfc3a6e9fbd47f5df03afbd410e", + "0xa1a3eeec0addec7b9e15f416a07608a1b5d94f0b42d5c203b8ced03a07484f5b", + "0xa4bb8b059aa122ca4652115b83b17af80cfbea0d3e1e8979a396a667f94e85f3", + "0x31c4d2f252167fe2a4d41944224a80b2f1afaf76f8dd6a3d52d71751849e44bb", + "0x79642dd6a255f96c9efe569304d58c327a441448db0431aa81fe072d0d359b52", + "0x42a4b504714aba1b67defe9458fff0c8cb1f216dcab28263cef67a65693b2036", + "0xe3d2f6a9d882d0f026ef316940dfcbf131342060ea28944475fe1f56392c9ad2", + "0x986af9aeff236394a0afa83823e643e76f7624e9bfd47d5468f9b83758a86caa", + "0xafe2de6ede50ee351d63ed38d1f2ae5203174c731f41bbed95db467461ad5492", + "0x9ad40f0785fe1c8a5e4c3342b3c91987cd47a862ece6573674b52fa0456f697a", + "0xde4cde6d0fc6def3a89b79da0e01accdbec049f1c9471d13a5d59286bd679af1", + "0xecd0d1f70116d6b3ae21c57fb06ad90eed33d040e2c5c3d12714b3be934fa5ce", + "0x3c53c5bf2d1b1d4038e1f0e8a2e6d12e0d4613d5cd12562578b6909921224c10", + "0x36087382b37e9e306642cc6e867e0fb2971b6b2b28b6caf2f9c96b790e8db70a", + "0xa957496d6a4218a19998f90282d05bd93e6baabf55e55e8a5f74a933a4dec045", + "0x077d6f094e8467a21f02c67753565ec5755156015d4e86f1f82a22f9cf21c869", + "0x12dd3b1f29e1462ca392c12388a77c58044151154cf86f23873f92a99b6bb762", + "0x7fdbcdedcc02ecf16657792bd8ef4fa4adeee497f30207d4cc060eb0d528b26b", + "0x245554b12bf8edf9e9732d6e2fa50958376e355cb695515c94676e64c6e97009", + "0xccd3b1841b517f7853e35f85471710777e437a8665e352a0b61c7d7083c3babc", + "0xd970545a326dcd92e31310d1fdce3703dff8ef7c0f3411dfa74fab8b4b0763ac", + "0xd24163068918e2783f9e79c8f2dcc1c5ebac7796ce63070c364837aac91ee239", + "0x256a330055357e20691e53ca5be846507c2f02cfde09cafb5809106f0af9180e", + "0xfa446a5d1876c2051811af2a341a35dbcd3f7f8e2e4f816f501139d27dd7cd82", + "0xbafbc7a8f871d95736a41e5721605d37e7532e41eb1426897e33a72ed2f0bf1d", + "0x8055af9a105b6cf17cfeb3f5320e7dab1a6480500ff03a16c437dfec0724c290", + "0x1de6ee3e989497c1cc7ca1d16b7b01b2f336524aa2f75a823eaa1716c3a1a294", + "0x12bb9508d646dda515745d104199f71276d188b3e164083ad27dfdcdc68e290b", + "0x7ea9f9939ad4f3b44fe7b780e0587da4417c34459b2996b3a449bb5b3ff8c8cb", + "0xa88d2f8f35bc669aa6480ce82571df65fea366834670b4084910c7bb6a735dde", + "0x9486e045adb387a550b3c7a603c30e07ed8625d322d1158f4c424d30befe4a65", + "0xb283a70ba539fe1945be096cb90edb993fac77e8bf53616bde35cdcaa04ab732", + "0xab39a81558e9309831a2caf03e9df22e8233e20b1769f16e613debcdb8e2610f", + "0x1fc12540473fbbad97c08770c41f517ce19dc7106aa2be2e9b77867046627509", + "0xec33dbec9d655c4c581e07d1c40a587cf3217bc8168a81521b2d0021bd0ec133", + "0xc8699e3b41846bc291209bbb9c06f565f66c6ccecbf03ebc27593e798c21fe94", + "0x240d7eae209c19d453b666c669190db22db06279386aa30710b6edb885f6df94", + "0xb181c07071a750fc7638dd67e868dddbeeee8e8e0dcbc862539ee2084674a89e", + "0xb8792555c891b3cbfddda308749122a105938a80909c2013637289e115429625", + "0xfe3e9e5b4a5271d19a569fee6faee31814e55f156ba843b6e8f8dc439d60e67a", + "0x912e9ba3b996717f89d58f1e64243d9cca133614394e6ae776e2936cf1a9a859", + "0xa0671c91a21fdfd50e877afa9fe3974aa3913855a2a478ae2c242bcdb71c73d7", + "0x5b55d171b346db9ba27b67105b2b4800ca5ba06931ed6bd1bafb89d31e6472e6", + "0x68438458f1af7bd0103ef33f8bc5853fa857b8c1f84b843882d8c328c595940d", + "0x21fe319fe8c08c1d00f977d33d4a6f18aecaa1fc7855b157b653d2d3cbd8357f", + "0x23cce560bc31f68e699ece60f21dd7951c53c292b3f5522b9683eb2b3c85fc53", + "0x917fa32d172c352e5a77ac079df84401cdd960110c93aa9df51046d1525a9b49", + "0x3fc397180b65585305b88fe500f2ec17bc4dccb2ec254dbb72ffb40979f14641", + "0xf35fb569e7a78a1443b673251ac70384abea7f92432953ca9c0f31c356be9bd9", + "0x7955afa3cd34deb909cd031415e1079f44b76f3d6b0aaf772088445aaff77d08", + "0x45c0ca029356bf6ecfc845065054c06024977786b6fbfaea74b773d9b26f0e6c", + "0xe5c1dac2a6181f7c46ab77f2e99a719504cb1f3e3c89d720428d019cb142c156", + "0x677b0e575afcccf9ddefc9470e96a6cfff155e626600b660247b7121b17b030a", + "0xbeed763e9a38277efe57b834a946d05964844b1f51dba2c92a5f3b8d0b7c67d0", + "0x962b17ed1a9343d8ebfae3873162eef13734985f528ca06c90b0c1e68adfdd89", + ], + lamport_1: vec![ + "0xb3a3a79f061862f46825c00fec4005fb8c8c3462a1eb0416d0ebe9028436d3a9", + "0x6692676ce3b07f4c5ad4c67dc2cf1dfa784043a0e95dd6965e59dc00b9eaff2d", + "0xbf7b849feb312db230e6e2383681b9e35c064e2d037cbc3c9cc9cd49220e80c9", + "0xa54e391dd3b717ea818f5954eec17b4a393a12830e28fabd62cbcecf509c17dc", + "0x8d26d800ac3d4453c211ef35e9e5bb23d3b9ede74f26c1c417d6549c3110314d", + "0xbb8153e24a52398d92480553236850974576876c7da561651bc551498f184d10", + "0x0d30e0e203dc4197f01f0c1aba409321fbf94ec7216e47ab89a66fb45e295eff", + "0x01dc81417e36e527776bf37a3f9d74a4cf01a7fb8e1f407f6bd525743865791d", + "0xa6318e8a57bec438245a6834f44eb9b7fb77def1554d137ea12320fc572f42c9", + "0xd25db9df4575b595130b6159a2e8040d3879c1d877743d960bf9aa88363fbf9f", + "0x61bb8baeb2b92a4f47bb2c8569a1c68df31b3469e634d5e74221bc7065f07a96", + "0xb18962aee4db140c237c24fec7fd073b400b2e56b0d503f8bc74a9114bf183bf", + "0x205473cc0cdab4c8d0c6aeceda9262c225b9db2b7033babfe48b7e919751a2c6", + "0xc5aa7df7552e5bb17a08497b82d8b119f93463ccb67282960aee306e0787f228", + "0x36da99e7d38ce6d7eab90ea109ba26615ad75233f65b3ae5056fba79c0c6682a", + "0xd68b71bba6266b68aec0df39b7c2311e54d46a3eab35f07a9fe60d70f52eec58", + "0xbbe56f1274ada484277add5cb8c90ef687d0b69a4c95da29e32730d90a2d059f", + "0x0982d1d1c15a560339d9151dae5c05e995647624261022bbedce5dce8a220a31", + "0x8ef54ad546d2c6144fc26e1e2ef92919c676d7a76cfdfb5c6a64f09a54e82e71", + "0x1e3ac0133eef9cdbeb590f14685ce86180d02b0eea3ef600fd515c38992b1f26", + "0x642e6b1c4bec3d4ba0ff2f15fbd69dcb57e4ba8785582e1bc2b452f0c139b590", + "0xca713c8cf4afa9c5d0c2db4fc684a8a233b3b01c219b577f0a053548bedf8201", + "0xd0569ba4e1f6c02c69018b9877d6a409659cb5e0aa086df107c2cc57aaba62da", + "0x4ebe68755e14b74973e7f0fa374b87cee9c370439318f5783c734f00bb13e4b5", + "0x788b5292dc5295ae4d0ea0be345034af97a61eec206fda885bbc0f049678c574", + "0x0ebd88acd4ae195d1d3982038ced5af1b6f32a07349cf7fffbff3ce410c10df2", + "0xc7faf0a49234d149036c151381d38427b74bae9bd1601fc71663e603bc15a690", + "0xc5247bf09ebe9fa4e1013240a1f88c703f25a1437196c71ee02ca3033a61f946", + "0x719f8c68113d9f9118b4281e1f42c16060def3e3eeef15f0a10620e886dc988f", + "0x28da4f8d9051a8b4d6158503402bdb6c49ba2fb1174344f97b569c8f640504e6", + "0x96f6773576af69f7888b40b0a15bc18cc9ec8ca5e1bb88a5de58795c6ddf678e", + "0x8d80d188a4e7b85607deccf654a58616b6607a0299dd8c3f1165c453fd33d2e4", + "0x9c08dcc4f914486d33aa24d10b89fd0aabcc635aa2f1715dfb1a18bf4e66692a", + "0x0ff7045b5f6584cc22c140f064dec0692762aa7b9dfa1defc7535e9a76a83e35", + "0x8e2dae66fa93857b39929b8fc531a230a7cfdd2c449f9f52675ab5b5176461d5", + "0xf449017c5d429f9a671d9cc6983aafd0c70dd39b26a142a1d7f0773de091ac41", + "0xed3d4cab2d44fec0d5125a97b3e365a77620db671ecdda1b3c429048e2ebdae6", + "0x836a332a84ee2f4f5bf24697df79ed4680b4f3a9d87c50665f46edaeed309144", + "0x7a79278754a4788e5c1cf3b9145edb55a2ba0428ac1c867912b5406bb7c4ce96", + "0x51e6e2ba81958328b38fd0f052208178cec82a9c9abd403311234e93aff7fa70", + "0x217ec3ec7021599e4f34410d2c14a8552fff0bc8f6894ebb52ec79bf6ec80dc9", + "0x8a95bf197d8e359edabab1a77f5a6d04851263352aa46830f287d4e0564f0be0", + "0x60d0cbfb87340b7c92831872b48997ce715da91c576296df215070c6c20046d4", + "0x1739fbca476c540d081b3f699a97387b68af5d14be52a0768d5185bc9b26961b", + "0xac277974f945a02d89a0f8275e02de9353e960e319879a4ef137676b537a7240", + "0x959b7640821904ba10efe8561e442fbdf137ccb030aee7472d10095223e320ba", + "0xdba61c8785a64cb332342ab0510126c92a7d61f6a8178c5860d018d3dad571c6", + "0xc191fb6a92eb1f1fb9e7eb2bdecd7ec3b2380dd79c3198b3620ea00968f2bd74", + "0x16ef4e88e182dfc03e17dc9efaa4a9fbf4ff8cb143304a4a7a9c75d306729832", + "0x39080e4124ca577ff2718dfbcb3415a4220c5a7a4108729e0d87bd05adda5970", + "0xa29a740eef233956baff06e5b11c90ed7500d7947bada6da1c6b5d9336fc37b6", + "0x7fda7050e6be2675251d35376bacc895813620d245397ab57812391d503716ee", + "0x401e0bf36af9992deb87efb6a64aaf0a4bc9f5ad7b9241456b3d5cd650418337", + "0x814e70c57410e62593ebc351fdeb91522fe011db310fcf07e54ac3f6fefe6be5", + "0x03c1e52ecbef0d79a4682af142f012dc6b037a51f972a284fc7973b1b2c66dcf", + "0x57b22fb091447c279f8d47bdcc6a801a946ce78339e8cd2665423dfcdd58c671", + "0x53aeb39ab6d7d4375dc4880985233cba6a1be144289e13cf0bd04c203257d51b", + "0x795e5d1af4becbca66c8f1a2e751dcc8e15d7055b6fc09d0e053fa026f16f48f", + "0x1cd02dcd183103796f7961add835a7ad0ba636842f412643967c58fe9545bee4", + "0x55fc1550be9abf92cacb630acf58bad11bf734114ebe502978a261cc38a4dd70", + "0x6a044e0ea5c361d3fb2ca1ba795301e7eb63db4e8a0314638f42e358ea9cfc3e", + "0x57d9f15d4db199cbcb7cbd6524c52a1b799d52b0277b5a270d2985fcee1e2acb", + "0x66c78c412e586bd01febc3e4d909cc278134e74d51d6f60e0a55b35df6fb5b09", + "0x1076799e15a49d6b15c2486032f5e0b50f43c11bc076c401e0779d224e33f6fc", + "0x5f70e3a2714d8b4483cf3155865ba792197e957f5b3a6234e4c408bf2e55119d", + "0x9b105b0f89a05eb1ff7caed74cf9573dc55ac8bc4881529487b3700f5842de16", + "0x1753571b3cfadca4277c59aee89f607d1b1e3a6aa515d9051bafb2f0d8ce0daa", + "0x4014fff940b0950706926a19906a370ccbd652836dab678c82c539c00989201a", + "0x0423fa59ee58035a0beb9653841036101b2d5903ddeabddabf697dbc6f168e61", + "0x78f6781673d991f9138aa1f5142214232d6e3d6986acb6cc7fb000e1a055f425", + "0x21b8a1f6733b5762499bf2de90c9ef06af1c6c8b3ddb3a04cce949caad723197", + "0x83847957e909153312b5bd9a1a37db0bd6c72a417024a69df3e18512973a18b4", + "0x948addf423afd0c813647cfe32725bc55773167d5065539e6a3b50e6ebbdab38", + "0x0b0485d1bec07504a2e5e3a89addd6f25d497cd37a0c04bc38355f8bdb01cd48", + "0x31be8bda5143d39ea2655e9eca6a294791ca7854a829904d8574bedc5057ddc4", + "0x16a0d2d657fadce0d81264320e42e504f4d39b931dff9888f861f3cc78753f99", + "0xb43786061420c5231bf1ff638cb210f89bf4cd2d3e8bafbf34f497c9a298a13b", + "0x1f5986cbd7107d2a3cbc1826ec6908d976addbf9ae78f647c1d159cd5397e1bd", + "0xa883ccdbfd91fad436be7a4e2e74b7796c0aadfe03b7eea036d492eaf74a1a6f", + "0x5bc9eb77bbbf589db48bca436360d5fc1d74b9195237f11946349951f2a9f7f6", + "0xb6bc86de74a887a5dceb012d58c62399897141cbcc51bad9cb882f53991f499c", + "0xa6c3260e7c2dd13f26cf22bf4cd667688142ff7a3511ec895bc8f92ebfa694b6", + "0xb97da27e17d26608ef3607d83634d6e55736af10cc7e4744940a3e35d926c2ad", + "0x9df44067c2dc947c2f8e07ecc90ba54db11eac891569061a8a8821f8f9773694", + "0x865cc98e373800825e2b5ead6c21ac9112ff25a0dc2ab0ed61b16dc30a4a7cd7", + "0xe06a5b157570c5e010a52f332cacd4e131b7aed9555a5f4b5a1c9c4606caca75", + "0x824eccb5cf079b5943c4d17771d7f77555a964a106245607cedac33b7a14922e", + "0xe86f721d7a3b52524057862547fc72de58d88728868f395887057153bccaa566", + "0x3344e76d79f019459188344fb1744c93565c7a35799621d7f4505f5b6119ac82", + "0x401b3589bdd1b0407854565329e3f22251657912e27e1fb2d978bf41c435c3ac", + "0xb12fd0b2567eb14a562e710a6e46eef5e280187bf1411f5573bb86ecbe05e328", + "0xe6dc27bab027cbd9fbb5d80054a3f25b576bd0b4902527a0fc6d0de0e45a3f9f", + "0x1de222f0e731001c60518fc8d2be7d7a48cc84e0570f03516c70975fdf7dc882", + "0xb8ff6563e719fc182e15bbe678cf045696711244aacc7ce4833c72d2d108b1b9", + "0x53e28ac2df219bcbbc9b90272e623d3f6ca3221e57113023064426eff0e2f4f2", + "0x8a4e0776f03819e1f35b3325f20f793d026ccae9a769d6e0f987466e00bd1ce7", + "0x2f65f20089a31f79c2c0ce668991f4440b576ecf05776c1f6abea5e9b14b570f", + "0x448e124079a48f62d0d79b96d5ed1ffb86610561b10d5c4236280b01f8f1f406", + "0x419b34eca1440c847f7bff9e948c9913075d8e13c270e67f64380a3f31de9bb2", + "0x2f6e4fee667acaa81ba8e51172b8329ed936d57e9756fb31f635632dbc2709b7", + "0xdd5afc79e8540fcee6a896c43887bd59c9de5d61b3d1b86539faeb41a14b251d", + "0xc707bed926a46cc451a6b05e642b6098368dbdbf14528c4c28733d5d005af516", + "0x153e850b606eb8a05eacecc04db4b560d007305e664bbfe01595cb69d26b8597", + "0x1b91cc07570c812bb329d025e85ef520132981337d7ffc3d84003f81a90bf7a7", + "0x4ca32e77a12951a95356ca348639ebc451170280d979e91b13316844f65ed42a", + "0xe49ea1998e360bd68771bd69c3cd4cf406b41ccca4386378bec66ea210c40084", + "0x01aaffbde1a672d253e0e317603c2dc1d0f752100d9e853f840bca96e57f314c", + "0x170d0befcbbaafb317c8684213a4989368332f66e889824cc4becf148f808146", + "0x56f973308edf5732a60aa3e7899ae1162c7a2c7b528c3315237e20f9125b34e0", + "0x66c54fd5f6d480cab0640e9f3ec1a4eafbafc0501528f57bb0d5c78fd03068ef", + "0xaca6c83f665c64d76fbc4858da9f264ead3b6ecdc3d7437bb800ef7240abffb9", + "0xf1d4e02e7c85a92d634d16b12dc99e1d6ec9eae3d8dfbca77e7c609e226d0ce7", + "0x094352545250e843ced1d3c6c7957e78c7d8ff80c470974778930adbe9a4ed1a", + "0x76efa93070d78b73e12eb1efa7f36d49e7944ddcc3a043b916466ee83dca52ce", + "0x1772a2970588ddb584eadf02178cdb52a98ab6ea8a4036d29e59f179d7ba0543", + "0xe4bbf2d97d65331ac9f680f864208a9074d1def3c2433458c808427e0d1d3167", + "0x8ccfb5252b22c77ea631e03d491ea76eb9b74bc02072c3749f3e9d63323b44df", + "0x9e212a9bdf4e7ac0730a0cecd0f6cc49afc7e3eca7a15d0f5f5a68f72e45363b", + "0x52e548ea6445aae3f75509782a7ab1f4f02c2a85cdd0dc928370f8c76ae8802d", + "0xb62e7d73bf76c07e1a6f822a8544b78c96a6ba4f5c9b792546d94b56ca12c8b9", + "0x595cb0e985bae9c59af151bc748a50923921a195bbec226a02157f3b2e066f5b", + "0x1c7aa6b36f402cec990bafefbdbb845fc6c185c7e08b6114a71dd388fe236d32", + "0x01ee2ff1a1e88858934a420258e9478585b059c587024e5ec0a77944821f798c", + "0x420a963a139637bffa43cb007360b9f7d305ee46b6a694b0db91db09618fc2e5", + "0x5a8e2ad20f8da35f7c885e9af93e50009929357f1f4b38a6c3073e8f58fae49e", + "0x52a405fdd84c9dd01d1da5e9d1c4ba95cb261b53bf714c651767ffa2f9e9ad81", + "0xa1a334c901a6d5adc8bac20b7df025e906f7c4cfc0996bfe2c62144691c21990", + "0xb789a00252f0b34bded3cb14ae969effcf3eb29d97b05a578c3be8a9e479c213", + "0xb9dbf7e9ddb638a515da245845bea53d07becdf3f8d1ec17de11d495624c8eab", + "0xaf566b41f5ed0c026fa8bc709533d3fa7a5c5d69b03c39971f32e14ab523fa3d", + "0x8121e0b2d9b106bb2aefd364fd6a450d88b88ee1f5e4aad7c0fcd8508653a112", + "0x8581c1be74279216b93e0a0d7272f4d6385f6f68be3eef3758d5f68b62ee7b6c", + "0x85386f009278f9a1f828404fa1bbfa02dfb9d896554f0a52678eb6ec8feadc55", + "0xf483ed167d92a0035ac65a1cfdb7906e4952f74ae3a1d86324d21f241daffcb7", + "0x3872485e2a520a350884accd990a1860e789dd0d0664ad14f50186a92c7be7be", + "0xc6c1a3301933019105f5650cabcb22bfbf221965ffcfc1329315b24ea3d77fd4", + "0xcee901330a60d212a867805ce0c28f53c6cc718f52156c9e74390d18f5df6280", + "0xa67ae793b1cd1a828a607bae418755c84dbb61adf00833d4c61a94665363284f", + "0x80d8159873b517aa6815ccd7c8ed7cfb74f84298d703a6c5a2f9d7d4d984ddde", + "0x1de5a8b915f2d9b45c97a8e134871e2effb576d05f4922b577ade8e3cd747a79", + "0x6ea17c5ece9b97dddb8b2101b923941a91e4b35e33d536ab4ff15b647579e1f5", + "0xcb78631e09bc1d79908ce1d3e0b6768c54b272a1a5f8b3b52485f98d6bba9245", + "0xd7c38f9d3ffdc626fe996218c008f5c69498a8a899c7fd1d63fbb03e1d2a073f", + "0x72cdef54267088d466244a92e4e6f10742ae5e6f7f6a615eef0da049a82068f9", + "0x60b3c490ba8c502656f9c0ed37c47283e74fe1bc7f0e9f651cbc76552a0d88eb", + "0x56bd0c66987a6f3761d677097be9440ea192c1cb0f5ec38f42789abe347e0ea9", + "0x3caac3e480f62320028f6f938ee147b4c78e88a183c464a0c9fb0df937ae30c1", + "0x7a4d2f11bddda1281aba5a160df4b814d23aef07669affe421a861fac2b4ec0f", + "0x9bb4d11299922dc309a4523959298a666ebe4063a9ee3bad1b93988ed59fb933", + "0x957323fffbaf8f938354662452115ae5acba1290f0d3f7b2a671f0359c109292", + "0x877624e31497d32e83559e67057c7a605fb888ed8e31ba68e89e02220eac7096", + "0x8456546ae97470ff6ea98daf8ae632e59b309bd3ff8e9211f7d21728620ed1e5", + "0xbacb26f574a00f466ce354e846718ffe3f3a64897d14d5ffb01afcf22f95e72b", + "0x0228743a6e543004c6617bf2c9a7eba1f92ebd0072fb0383cb2700c3aed38ba0", + "0x04f093f0f93c594549436860058371fb44e8daf78d6e5f563ba63a46b61ddbf0", + "0x0ba17c1ec93429ceaff08eb81195c9844821b64f2b5363926c2a6662f83fb930", + "0xd71605d8446878c677f146837090797e888416cfc9dc4e79ab11776cc6639d3f", + "0x33dde958dc5a6796138c453224d4d6e7f2ae740cceef3b52a8b669eb4b9691a1", + "0x3c39838295d1495e90e61ce59f6fcc693b31c292d02d31759719df6fe3214559", + "0x8aecc66f38644296cf0e6693863d57a243a31a4929130e22ab44cb6157b1af41", + "0xdf7153a7eab9521f2b37124067166c72de8f342249ac0e0f5350bd32f1251053", + "0xa498840b58897cf3bed3981b94c86d85536dfebbc437d276031ebd9352e171eb", + "0xb1df15a081042ab665458223a0449ffc71a10f85f3d977beb20380958fd92262", + "0x15d3bdbdee2a61b01d7a6b72a5482f6714358eedf4bece7bb8458e100caf8fba", + "0x0c96b7a0ea09c3ef758424ffb93654ce1520571e32e1f83aecbeded2388c3a7a", + "0xb4a3a8023266d141ecd7c8a7ca5282a825410b263bc11c7d6cab0587c9b5446e", + "0xf38f535969d9592416d8329932b3a571c6eacf1763de10fb7b309d3078b9b8d4", + "0x5a1e7b1c3b3943158341ce6d7f9f74ae481975250d89ae4d69b2fcd4c092eb4e", + "0xdad31e707d352f6cca78840f402f2ac9292094b51f55048abf0d2badfeff5463", + "0x097e290170068e014ceda3dd47b28ede57ff7f916940294a13c9d4aa2dc98aad", + "0x22e2dcedb6bb7f8ace1e43facaa502daa7513e523be98daf82163d2a76a1e0be", + "0x7ef2b211ab710137e3e8c78b72744bf9de81c2adde007aef6e9ce92a05e7a2c5", + "0x49b427805fc5186f31fdd1df9d4c3f51962ab74e15229e813072ec481c18c717", + "0xe60f6caa09fa803d97613d58762e4ff7f22f47d5c30b9d0116cdc6a357de4464", + "0xab3507b37ee92f026c72cc1559331630bc1c7335b374e4418d0d02687df1a9dd", + "0x50825ae74319c9adebc8909ed7fc461702db8230c59975e8add09ad5e7a647ab", + "0x0ee8e9c1d8a527a42fb8c2c8e9e51faf727cffc23ee22b5a95828f2790e87a29", + "0x675c21c290ddb40bec0302f36fbcd2d1832717a4bc05d113c6118a62bc8f9aca", + "0x580bafab24f673317b533148d7226d485e211eaa3d6e2be2529a83ca842b58a7", + "0x540e474776cae597af24c147dc1ae0f70a6233e98cf5c3ce31f38b830b75c99a", + "0x36eaf9f286e0f356eaaf8d81f71cc52c81d9ebc838c3b4859009f8567a224d16", + "0x0e2cbbb40954be047d02b1450a3dbd2350506448425dc25fd5faf3a66ee8f5c4", + "0x7eb0390cfe4c4eb120bbe693e87adc8ecab51d5fd8ce8f911c8ff07fad8cbe20", + "0xbf77589f5c2ebb465b8d7936f6260a18a243f59bd87390ee22cf579f6f020285", + "0x695b96bb28693f6928777591ef64146466d27521280a295936a52ec60707c565", + "0x22a0d018cbd4274caa8b9e7fb132e0a7ed787874046ca683a7d81d1c7c8b8f15", + "0x84092b122bb35e5ad85407b4b55f33707b86e0238c7970a8583f3c44308ed1d9", + "0xea346067ca67255235f9cae949f06e4b6c93846a7abc7c8c8cd786e9c4b3e4bc", + "0xa6df0716b125dc696b5d0e520cb49c1c089397c754efc146792e95bc58cc7159", + "0x7377b5d3953029fc597fb10bb6479ee34133d38f08783fbb61c7d070f34ea66f", + "0x7d79b00ffb976a10cd24476a394c8ed22f93837c51a58a3ddc7418153a5a8ea1", + "0x01e55182e80dff26cc3e06bb736b4a63745bde8ae28c604fa7fb97d99de5f416", + "0x062a2d5a207f8d540764d09648afecbf5033b13aec239f722b9033a762acf18b", + "0x48be60a3221d98b4d62f0b89d3bef74c70878dd65c6f79b34c2c36d0ddaa1da0", + "0x41e11f33543cf045c1a99419379ea31523d153bdf664549286b16207b9648c85", + "0xeef4d30b4700813414763a199e7cc6ab0faec65ef8b514faa01c6aa520c76334", + "0xea7cfe990422663417715e7859fc935ca47f47c943a1254044b6bc5934c94bc8", + "0xbbd3c834e5403b98a0ca346c915a23310f3d58880786628bc6cfbe05ba29c3c5", + "0xe216379f385bc9995ae0f37f1409a78d475c56b8aeb4ee434326724ec20124f7", + "0xdd328a1eee19d09b6fef06e252f8ad0ae328fbf900ef745f5950896803a3899d", + "0xa16fde34b0d743919feb0781eca0c525a499d279119af823cb3a8817000335db", + "0x7a28d108c59b83b12c85cd9aabc1d1d994a9a0329ae7b64a32aadcd61ebe50e3", + "0xb28bc82fceae74312eb837a805f0a8a01c0f669b99bb03fde31c4d58bedff89b", + "0x1b0d8f37d349781e846900b51a90c828aa384afe9b8ee1f88aeb8dba4b3168f2", + "0xbfd0301ff964c286c3331a30e09e0916da6f484e9c9596dbf1cae3cc902dbf9e", + "0xbb8254cb9ef6b485b8fb6caeafe45f920affc30f6b9d671e9a454530536f4fef", + "0xcad2317cf63dfa7147ded5c7e15f5f72e78f42d635e638f1ece6bc722ca3638b", + "0xb6c6e856fd45117f54775142f2b38f31114539d8943bcbcf823f6c7650c001e4", + "0x869f1baa35684c8f67a5bc99b294187852e6c85243a2f36481d0891d8b043020", + "0x14c6ccf145ee40ff56e3810058d2fba9a943ffc7c7087c48a08b2451c13dc788", + "0x263c1bcb712890f155b7e256cefa4abf92fe4380f3ffc11c627d5e4e30864d18", + "0x69f4eaf655e31ad7f7a725cd415ce7e45dd4a8396ac416950d42ed33155c3487", + "0x47e8eec2c5e33c9a54fe1f9b09e7744b614fb16531c36b862aa899424be13b05", + "0x5c985de270e62c44f0b49157882e8e83641b906ce47959e337fe8423e125a2eb", + "0x4e13b11e13202439bb5de5eea3bb75d2d7bf90f91411163ade06161a9cf424db", + "0x583a8fa159bb74fa175d72f4e1705e9a3b8ffe26ec5ad6e720444b99288f1213", + "0x903d2a746a98dfe2ee2632606d57a9b0fa6d8ccd895bb18c2245fd91f8a43676", + "0xa35a51330316012d81ec7249e3f2b0c9d7fcbb99dd98c62fe880d0a152587f51", + "0x33818a7beb91730c7b359b5e23f68a27b429967ea646d1ea99c314353f644218", + "0x183650af1e0b67f0e7acb59f8c72cc0e60acc13896184db2a3e4613f65b70a8b", + "0x857ff2974bef960e520937481c2047938a718cea0b709282ed4c2b0dbe2ef8fa", + "0x95a367ecb9a401e98a4f66f964fb0ece783da86536410a2082c5dbb3fc865799", + "0x56c606a736ac8268aedadd330d2681e7c7919af0fe855f6c1c3d5c837aa92338", + "0x5c97f7abf30c6d0d4c23e762c026b94a6052a444df4ed942e91975419f68a3a4", + "0x0b571de27d2022158a3128ae44d23a8136e7dd2dee74421aa4d6ed15ee1090a0", + "0xa17f6bc934a2f3c33cea594fee8c96c1290feec934316ebbbd9efab4937bf9f9", + "0x9ff57d70f27aad7281841e76435285fd27f10dad256b3f5cabde4ddc51b70eff", + "0xafa3071a847215b3ccdf51954aa7cb3dd2e6e2a39800042fc42009da705508b2", + "0x5e3bea33e4ac6f7c50a077d19571b1796e403549b1ce7b15e09905a0cc5a4acf", + "0x0dc7ba994e632ab95f3ecb7848312798810cf761d1c776181882d17fd6dda075", + "0xb4f7158679dad9f7370a2f64fbe617a40092849d17453b4f50a93ca8c6885844", + "0x094564b00f53c6f27c121fd8adfe1685b258b259e585a67b57c85efb804c57b2", + "0x9cd21a4249ba3fccffad550cdb8409dc12d8b74a7192874b6bafe2363886f318", + "0xbb22e0dad55cb315c564c038686419d40ef7f13af2143a28455bf445f6e10393", + "0x2a71d5e00821178c2cd39e7501e07da5cca6680eb7cdbe996f52dccafadb3735", + "0x9619406093b121e044a5b403bb1713ae160aeb52ad441f82dc6c63e4b323b969", + "0x3b8bd1d82c6d67ae707e19b889f1cb1f7bba912f12ae4284298f3a70c3644c79", + "0xd7a70c50d47d48785b299dbea01bf03ef18b8495de3c35cb265bc8f3295c4e15", + "0x8802ecce8dd6b6190af8ac79aafda3479c29f548d65e5798c0ca51a529b19108", + "0x4b630e1df52ec5fd650f4a4e76b3eeddda39e1e9eab996f6d3f02eefdf690990", + "0x0bfbff60fcf7f411d469f7f6f0a58ca305fd84eb529ee3ac73c00174793d723e", + "0x535f78b5f3a99a1c498e2c19dc1acb0fbbaba8972ba1d7d66936c28ab3667ebe", + "0x06ba92d8129db98fec1b75f9489a394022854f22f2e9b9450b187a6fc0d94a86", + "0xb7ae275ba10f80fb618a2cf949d5ad2e3ae24eb2eb37dcf1ec8c8b148d3ba27f", + "0xb275579bcf2584d9794dd3fc7f999902b13d33a9095e1980d506678e9c263de1", + "0x843ccd52a81e33d03ad2702b4ef68f07ca0419d4495df848bff16d4965689e48", + "0xde8b779ca7250f0eb867d5abdffd1d28c72a5a884d794383fc93ca40e5bf6276", + "0x6b789a2befccb8788941c9b006e496b7f1b03dbb8e530ba339db0247a78a2850", + "0xfccd4dca80bc52f9418f26b0528690255e320055327a34b50caf088235d2f660", + "0x18479ebfbe86c1e94cd05c70cb6cace6443bd9fdac7e01e9c9535a9e85141f2f", + "0x5350c8f3296441db954a261238c88a3a0c51ab418a234d566985f2809e211148", + "0xa5636614135361d03a381ba9f6168e2fd0bd2c1105f9b4e347c414df8759dea3", + "0xe7bb69e600992e6bd41c88a714f50f450153f1a05d0ddb4213a3fc4ba1f48c3f", + "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", + "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", + ], + compressed_lamport_sk: + "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", + child_sk: + "7419543105316279183937430842449358701327973165530407166294956473095303972104" + } + } +} From fe5fa2a8ae613537a7228cef936055b93b520971 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 15:19:20 +1000 Subject: [PATCH 072/118] More progress --- eth2/utils/eth2_keystore/src/path.rs | 101 ++++++++++++++++++++------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 699278fb332..acd985059eb 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -19,22 +19,15 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { - let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); - - // TODO: don't do all this extra work with extraction. - let lamport_bytes = lamport - .iter_chunks() - .map(|a| a.to_vec()) - .flatten() - .collect::>(); - // TODO: justify 48. - mod_r(&lamport_bytes[0..48]) + let okm = &hkdf_expand_basic( + hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), + 48, + ); + mod_r(&okm) } fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(bytes.len(), HASH_SIZE); - let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); @@ -42,7 +35,7 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { debug_assert!(x.len() <= HASH_SIZE); let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output[HASH_SIZE - x.len()..].copy_from_slice(&x); output } @@ -115,6 +108,37 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } +// TODO: zeroize. +fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; HASH_SIZE]; + + let mut hasher = Sha256::new(); + hasher.input(prk); + hasher.input(&[]); // TODO: remove this? + hasher.input(&[1]); + hasher.result(&mut okm[..]); + + let mut i = 0; + while okm.len() < l { + i += 1; + + let mut hasher = Sha256::new(); + hasher.input(prk); + if i == 1 { + hasher.input(&[]); // TODO: remove this line? + } else { + hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? + } + hasher.input(&[i]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.extend_from_slice(&mut digest); + } + + okm[0..l].to_vec() +} + fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -131,29 +155,58 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec>, + lamport_1: Vec>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + #[test] + fn eip2333_intermediate_vector() { + let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); + assert_eq!( + &master_sk[..], + &vectors.master_sk[..], + "master_sk should match" + ); + } + struct RawTestVector { seed: &'static str, master_sk: &'static str, child_index: u32, lamport_0: Vec<&'static str>, lamport_1: Vec<&'static str>, - compressed_lamport_sk: &'static str, + compressed_lamport_pk: &'static str, child_sk: &'static str, } - struct TestVector { - seed: Vec, - master_sk: Vec, - child_index: u32, - lamport_0: Vec<[u8; HASH_SIZE]>, - lamport_1: Vec<[u8; HASH_SIZE]>, - compressed_lamport_pk: Vec, - child_sk: Vec, + fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") + } + + fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() } impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { - todo!() + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + lamport_0: raw.lamport_0.into_iter().map(hex_to_vec).collect(), + lamport_1: raw.lamport_1.into_iter().map(hex_to_vec).collect(), + compressed_lamport_pk: hex_to_vec(raw.compressed_lamport_pk), + child_sk: int_to_vec(raw.child_sk), + } } } @@ -677,7 +730,7 @@ mod test { "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", ], - compressed_lamport_sk: + compressed_lamport_pk: "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" From 0900e4bf0275f1045a8bf251c7d3efdf3ae20119 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:29:35 +1000 Subject: [PATCH 073/118] Passing intermediate test vectors --- .../eth2_keystore/src/lamport_secret_key.rs | 49 ++++++++ eth2/utils/eth2_keystore/src/path.rs | 106 ++++++++++-------- 2 files changed, 108 insertions(+), 47 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/lamport_secret_key.rs diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs new file mode 100644 index 00000000000..01329d7ede9 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs @@ -0,0 +1,49 @@ +use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use std::iter::Iterator; +use zeroize::Zeroize; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); + +impl LamportSecretKey { + /// Instantiates `Self` with all chunks set to zero. + pub fn zero() -> Self { + Self(vec![[0; HASH_SIZE]; LAMPORT_ARRAY_SIZE as usize]) + } + + /// Instantiates `Self` from a flat buffer of `HASH_SIZE * LAMPORT_ARRAY_SIZE` bytes. + /// + /// ## Panics + /// + /// If an incorrect number of bytes is supplied. + pub fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!( + bytes.len(), + HASH_SIZE * LAMPORT_ARRAY_SIZE as usize, + "incorrect byte length" + ); + + let mut this = Self::zero(); + + for i in 0..LAMPORT_ARRAY_SIZE { + let iu = i as usize; + this.get_mut_chunk(i) + .copy_from_slice(&bytes[iu * HASH_SIZE..(iu + 1) * HASH_SIZE]) + } + + this + } + + pub fn get_chunk(&self, i: u8) -> &[u8] { + &self.0[i as usize] + } + + pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { + &mut self.0[i as usize] + } + + pub fn iter_chunks(&self) -> impl Iterator { + self.0.iter() + } +} diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index acd985059eb..db3d2f9063d 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, sha2::Sha256}; +use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -9,6 +9,8 @@ pub const LAMPORT_ARRAY_SIZE: u8 = 255; pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; +pub const BLS_KEY_LEN: usize = 48; + fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } @@ -19,11 +21,9 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); // TODO: justify 48. - let okm = &hkdf_expand_basic( - hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), - 48, - ); + let okm = &hkdf_expand(&prk, BLS_KEY_LEN); mod_r(&okm) } @@ -44,8 +44,8 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let not_ikm = flip_bits(ikm); let lamports = [ - ikm_to_lamport_sk(ikm, &salt), - ikm_to_lamport_sk(¬_ikm, &salt), + ikm_to_lamport_sk(&salt, ikm), + ikm_to_lamport_sk(&salt, ¬_ikm), ]; let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; @@ -74,20 +74,24 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { - hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) + let prk = hkdf_extract(salt, ikm); + let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(&okm) } -fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { - let mut hasher = Sha256::new(); - hasher.input(salt); - hasher.input(ikm); - - let mut digest = vec![0; HASH_SIZE]; - hasher.result(&mut digest); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { + let mut prk = [0; HASH_SIZE]; + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); + prk +} - digest.into() +fn hkdf_expand(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; l]; + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); + okm } +/* fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { let mut okm = LamportSecretKey::zero(); @@ -107,37 +111,7 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } - -// TODO: zeroize. -fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; HASH_SIZE]; - - let mut hasher = Sha256::new(); - hasher.input(prk); - hasher.input(&[]); // TODO: remove this? - hasher.input(&[1]); - hasher.result(&mut okm[..]); - - let mut i = 0; - while okm.len() < l { - i += 1; - - let mut hasher = Sha256::new(); - hasher.input(prk); - if i == 1 { - hasher.input(&[]); // TODO: remove this line? - } else { - hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? - } - hasher.input(&[i]); - - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.extend_from_slice(&mut digest); - } - - okm[0..l].to_vec() -} +*/ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -168,12 +142,50 @@ mod test { #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); assert_eq!( &master_sk[..], &vectors.master_sk[..], "master_sk should match" ); + + let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + assert_eq!( + lamport_0 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_0, + "lamport_0 should match" + ); + + let lamport_1 = ikm_to_lamport_sk( + &vectors.child_index.to_be_bytes()[..], + &flip_bits(&master_sk), + ); + assert_eq!( + lamport_1 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_1, + "lamport_1 should match" + ); + + let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + assert_eq!( + &compressed_lamport_pk[..], + &vectors.compressed_lamport_pk[..], + "compressed_lamport_pk should match" + ); + + let child_sk = derive_child_sk(&master_sk, vectors.child_index); + assert_eq!( + &child_sk[..], + &vectors.child_sk[..], + "child_sk should match" + ); } struct RawTestVector { From a54859e2e84c3f96c91ee94389ffd06e40ae12dd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:54:33 +1000 Subject: [PATCH 074/118] Tidy, add comments --- eth2/utils/eth2_keystore/src/path.rs | 83 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index db3d2f9063d..4060d78ff51 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -4,29 +4,53 @@ use num_bigint::BigUint; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; + /// The size of the lamport array. +/// +/// Indirectly defined in EIP-2333. pub const LAMPORT_ARRAY_SIZE: u8 = 255; +/// The order of the BLS 12-381 curve. +/// +/// Defined in EIP-2333. pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; -pub const BLS_KEY_LEN: usize = 48; +/// The `L` value used in the `hdkf_mod_r` function. +/// +/// In EIP-2333 this value is defined as: +/// +/// `ceil((1.5 * ceil(log2(r))) / 8)` +pub const MOD_R_L: usize = 48; +/// Derives the "master" BLS secret key from some `seed` bytes. +/// +/// Equivalent to `derive_master_SK` in EIP-2333. fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } +/// From the given `parent_sk`, derives a child key at index`. +/// +/// Equivalent to `derive_child_SK` in EIP-2333. fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); hkdf_mod_r(&compressed_lamport_pk) } +/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// BLS private key within the order of the BLS-381 curve. +/// +/// Equivalent to `HKDF_mod_r` in EIP-2333. fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - // TODO: justify 48. - let okm = &hkdf_expand(&prk, BLS_KEY_LEN); + let okm = &hkdf_expand(&prk, MOD_R_L); mod_r(&okm) } +/// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the +/// BLS-381 curve. +/// +/// This function is a part of the `HKDF_mod_r` function in EIP-2333. fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); @@ -39,6 +63,9 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { output } +/// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). +/// +/// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); @@ -73,48 +100,42 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { compressed_lamport_pk } +/// Generates a Lamport secret key from the `ikm` (initial key material). +/// +/// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); LamportSecretKey::from_bytes(&okm) } +/// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { let mut prk = [0; HASH_SIZE]; crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); prk } +/// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_expand(prk: &[u8], l: usize) -> Vec { let mut okm = vec![0; l]; crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); okm } -/* -fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { - let mut okm = LamportSecretKey::zero(); - - for i in 0..LAMPORT_ARRAY_SIZE { - let mut hasher = Sha256::new(); - - hasher.input(prk); - - if let Some(prev) = i.checked_sub(1) { - hasher.input(okm.get_chunk(prev)) - } - - hasher.input(&[i + 1]); - - hasher.result(okm.get_mut_chunk(i)); - } - - okm -} -*/ - +/// Flips each bit in the `input`. +/// +/// Equivalent to `flip_bits` in EIP-2333. +/// +/// ## Panics +/// +/// If `input` is not32-bytes. fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(input.len(), HASH_SIZE); + assert_eq!(input.len(), HASH_SIZE); let mut output = [0; HASH_SIZE]; @@ -129,6 +150,7 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + /// Contains the test vectors in a format that's easy for us to test against. struct TestVector { seed: Vec, master_sk: Vec, @@ -139,6 +161,9 @@ mod test { child_sk: Vec, } + /// "Test Vector with Intermediate values" from: + /// + /// https://eips.ethereum.org/EIPS/eip-2333 #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); @@ -188,6 +213,7 @@ mod test { ); } + /// Struct to deal with easy copy-paste from specification test vectors. struct RawTestVector { seed: &'static str, master_sk: &'static str, @@ -198,16 +224,20 @@ mod test { child_sk: &'static str, } + /// Converts 0x-prefixed hex to bytes. fn hex_to_vec(hex: &str) -> Vec { hex::decode(&hex[2..]).expect("should decode hex as vec") } + /// Converts an integer represented as a string to a big-endian byte array. fn int_to_vec(int_str: &str) -> Vec { BigUint::parse_bytes(int_str.as_bytes(), 10) .expect("must be able to parse int") .to_bytes_be() } + /// Converts from a format that's easy to copy-paste from the spec into a format that's easy to + /// test with. impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { TestVector { @@ -222,6 +252,7 @@ mod test { } } + /// Returns the copy-paste values from the spec. fn get_raw_vector() -> RawTestVector { RawTestVector { seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", From c3efd9ed4b108ba4d339df2501b0d9b08ae09c73 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:06:17 +1000 Subject: [PATCH 075/118] Add DerivedKey structs --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 51231797d6f..1032d4e597f 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -12,4 +12,5 @@ pub mod json_keystore; pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; +pub use path::MasterKey; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 4060d78ff51..08d077a87f0 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ -use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; +use crate::lamport_secret_key::LamportSecretKey; +use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -22,6 +22,27 @@ pub const R: &str = "52435875175126190479447740508185965837690552500527637822603 /// `ceil((1.5 * ceil(log2(r))) / 8)` pub const MOD_R_L: usize = 48; +/// A BLS secret key that is derived from some `seed`, or generated as a child from some other +/// `DerivedKey`. +pub struct DerivedKey([u8; HASH_SIZE]); + +impl DerivedKey { + /// Instantiates `Self` from some seed of any length. + pub fn from_seed(seed: &[u8]) -> Self { + Self(derive_master_sk(seed)) + } + + /// Derives a child key from the secret `Self` at some `index`. + pub fn derive_child(&self, index: u32) -> DerivedKey { + Self(derive_child_sk(&self.0, index)) + } + + /// Returns the secret BLS key in `self`. + pub fn secret(&self) -> &[u8] { + &self.0 + } +} + /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. From f5ccaa2b62995af8808055c9a128cad8f5fc6800 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:21:27 +1000 Subject: [PATCH 076/118] Move key derivation into own crate --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + eth2/utils/eth2_key_derivation/Cargo.toml | 15 +++++++++++++++ .../src/lamport_secret_key.rs | 4 ---- eth2/utils/eth2_key_derivation/src/lib.rs | 7 +++++++ .../src/path.rs | 0 eth2/utils/eth2_keystore/src/lib.rs | 3 --- 7 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 eth2/utils/eth2_key_derivation/Cargo.toml rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/lamport_secret_key.rs (93%) create mode 100644 eth2/utils/eth2_key_derivation/src/lib.rs rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b32c2057a7d..d19862eb5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,16 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth2_keystore" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 593bcd255a6..b2ee8e1120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/deposit_contract", "eth2/utils/eth2_config", "eth2/utils/eth2_interop_keypairs", + "eth2/utils/eth2_key_derivation", "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", "eth2/utils/logging", diff --git a/eth2/utils/eth2_key_derivation/Cargo.toml b/eth2/utils/eth2_key_derivation/Cargo.toml new file mode 100644 index 00000000000..61bfb5b8166 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "eth2_key_derivation" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "0.2.36" +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +num-bigint = "0.2.6" + +[dev-dependencies] +hex = "0.3" diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs similarity index 93% rename from eth2/utils/eth2_keystore/src/lamport_secret_key.rs rename to eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 01329d7ede9..e3a8a93990d 100644 --- a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -35,10 +35,6 @@ impl LamportSecretKey { this } - pub fn get_chunk(&self, i: u8) -> &[u8] { - &self.0[i as usize] - } - pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs new file mode 100644 index 00000000000..d25469000e9 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -0,0 +1,7 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + +mod lamport_secret_key; +mod path; + +pub use path::DerivedKey; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_key_derivation/src/path.rs similarity index 100% rename from eth2/utils/eth2_keystore/src/path.rs rename to eth2/utils/eth2_key_derivation/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 1032d4e597f..56174762158 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,14 +3,11 @@ mod derived_key; mod keystore; -mod lamport_secret_key; mod password; -mod path; mod plain_text; pub mod json_keystore; pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; -pub use path::MasterKey; pub use uuid::Uuid; From a83cb968da9fd131166f8323d188c2b36238e77e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 10:03:27 +1000 Subject: [PATCH 077/118] Add zeroize structs --- .../src/{path.rs => derived_key.rs} | 90 ++++++++++--------- .../src/lamport_secret_key.rs | 2 +- eth2/utils/eth2_key_derivation/src/lib.rs | 10 ++- .../eth2_key_derivation/src/secret_bytes.rs | 23 +++++ .../eth2_key_derivation/src/secret_hash.rs | 22 +++++ 5 files changed, 98 insertions(+), 49 deletions(-) rename eth2/utils/eth2_key_derivation/src/{path.rs => derived_key.rs} (95%) create mode 100644 eth2/utils/eth2_key_derivation/src/secret_bytes.rs create mode 100644 eth2/utils/eth2_key_derivation/src/secret_hash.rs diff --git a/eth2/utils/eth2_key_derivation/src/path.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs similarity index 95% rename from eth2/utils/eth2_key_derivation/src/path.rs rename to eth2/utils/eth2_key_derivation/src/derived_key.rs index 08d077a87f0..cd1a5b65e2c 100644 --- a/eth2/utils/eth2_key_derivation/src/path.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -1,4 +1,6 @@ -use crate::lamport_secret_key::LamportSecretKey; +use crate::{ + lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, secret_hash::SecretHash, +}; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; @@ -24,7 +26,7 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. -pub struct DerivedKey([u8; HASH_SIZE]); +pub struct DerivedKey(SecretHash); impl DerivedKey { /// Instantiates `Self` from some seed of any length. @@ -34,69 +36,70 @@ impl DerivedKey { /// Derives a child key from the secret `Self` at some `index`. pub fn derive_child(&self, index: u32) -> DerivedKey { - Self(derive_child_sk(&self.0, index)) + Self(derive_child_sk(self.0.as_bytes(), index)) } /// Returns the secret BLS key in `self`. pub fn secret(&self) -> &[u8] { - &self.0 + self.0.as_bytes() } } /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. -fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { +fn derive_master_sk(seed: &[u8]) -> SecretHash { hkdf_mod_r(seed) } /// From the given `parent_sk`, derives a child key at index`. /// /// Equivalent to `derive_child_SK` in EIP-2333. -fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); - hkdf_mod_r(&compressed_lamport_pk) + hkdf_mod_r(compressed_lamport_pk.as_bytes()) } -/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// From the `ikm` (initial key material), performs a HKDF-Extract and HKDF-Expand to generate a /// BLS private key within the order of the BLS-381 curve. /// /// Equivalent to `HKDF_mod_r` in EIP-2333. -fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { +fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - let okm = &hkdf_expand(&prk, MOD_R_L); - mod_r(&okm) + let okm = &hkdf_expand(prk.as_bytes(), MOD_R_L); + mod_r(okm.as_bytes()) } /// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the /// BLS-381 curve. /// /// This function is a part of the `HKDF_mod_r` function in EIP-2333. -fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { +fn mod_r(bytes: &[u8]) -> SecretHash { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); debug_assert!(x.len() <= HASH_SIZE); - let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - x.len()..].copy_from_slice(&x); + let mut output = SecretHash::zero(); + output.as_mut_bytes()[HASH_SIZE - x.len()..].copy_from_slice(&x); output } /// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). /// /// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. -fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); let lamports = [ ikm_to_lamport_sk(&salt, ikm), - ikm_to_lamport_sk(&salt, ¬_ikm), + ikm_to_lamport_sk(&salt, not_ikm.as_bytes()), ]; - let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + let mut lamport_pk = SecretBytes::zero(HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2); + let pk_bytes = lamport_pk.as_mut_bytes(); lamports .iter() @@ -104,19 +107,15 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { .flatten() .enumerate() .for_each(|(i, chunk)| { - let output_slice = lamport_pk - .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) - .expect("lamport_pk must have adequate capacity"); - let mut hasher = Sha256::new(); hasher.input(chunk); - hasher.result(output_slice); + hasher.result(&mut pk_bytes[i * HASH_SIZE..(i + 1) * HASH_SIZE]); }); - let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut compressed_lamport_pk = SecretHash::zero(); let mut hasher = Sha256::new(); - hasher.input(&lamport_pk); - hasher.result(&mut compressed_lamport_pk); + hasher.input(lamport_pk.as_bytes()); + hasher.result(compressed_lamport_pk.as_mut_bytes()); compressed_lamport_pk } @@ -126,25 +125,25 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { /// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); - let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); - LamportSecretKey::from_bytes(&okm) + let okm = hkdf_expand(prk.as_bytes(), HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(okm.as_bytes()) } /// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { - let mut prk = [0; HASH_SIZE]; - crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> SecretHash { + let mut prk = SecretHash::zero(); + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, prk.as_mut_bytes()); prk } /// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_expand(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; l]; - crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); +fn hkdf_expand(prk: &[u8], l: usize) -> SecretBytes { + let mut okm = SecretBytes::zero(l); + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], okm.as_mut_bytes()); okm } @@ -154,14 +153,15 @@ fn hkdf_expand(prk: &[u8], l: usize) -> Vec { /// /// ## Panics /// -/// If `input` is not32-bytes. -fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { +/// If `input` is not 32-bytes. +fn flip_bits(input: &[u8]) -> SecretHash { assert_eq!(input.len(), HASH_SIZE); - let mut output = [0; HASH_SIZE]; + let mut output = SecretHash::zero(); + let output_bytes = output.as_mut_bytes(); for (i, byte) in input.iter().enumerate() { - output[i] = !byte + output_bytes[i] = !byte } output @@ -191,12 +191,13 @@ mod test { let master_sk = derive_master_sk(&vectors.seed); assert_eq!( - &master_sk[..], + master_sk.as_bytes(), &vectors.master_sk[..], "master_sk should match" ); - let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + let lamport_0 = + ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], master_sk.as_bytes()); assert_eq!( lamport_0 .iter_chunks() @@ -208,7 +209,7 @@ mod test { let lamport_1 = ikm_to_lamport_sk( &vectors.child_index.to_be_bytes()[..], - &flip_bits(&master_sk), + flip_bits(master_sk.as_bytes()).as_bytes(), ); assert_eq!( lamport_1 @@ -219,16 +220,17 @@ mod test { "lamport_1 should match" ); - let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + let compressed_lamport_pk = + parent_sk_to_lamport_pk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &compressed_lamport_pk[..], + compressed_lamport_pk.as_bytes(), &vectors.compressed_lamport_pk[..], "compressed_lamport_pk should match" ); - let child_sk = derive_child_sk(&master_sk, vectors.child_index); + let child_sk = derive_child_sk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &child_sk[..], + child_sk.as_bytes(), &vectors.child_sk[..], "child_sk should match" ); diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index e3a8a93990d..2c64601e505 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -1,4 +1,4 @@ -use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs index d25469000e9..f6ed44c4df5 100644 --- a/eth2/utils/eth2_key_derivation/src/lib.rs +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -1,7 +1,9 @@ -//! Provides a JSON keystore for a BLS keypair, as specified by -//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +//! Provides path-based hierarchical BLS key derivation, as specified by +//! [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +mod derived_key; mod lamport_secret_key; -mod path; +mod secret_bytes; +mod secret_hash; -pub use path::DerivedKey; +pub use derived_key::DerivedKey; diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs new file mode 100644 index 00000000000..c4eee4b8f52 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -0,0 +1,23 @@ +use zeroize::Zeroize; + +/// Provides a wrapper around a `Vec` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + /// Instantiates `Self` with an all-zeros byte array of length `len`. + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs new file mode 100644 index 00000000000..9c1bbb27b8b --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -0,0 +1,22 @@ +use crate::derived_key::HASH_SIZE; +use zeroize::Zeroize; + +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretHash([u8; HASH_SIZE]); + +impl SecretHash { + /// Instantiates `Self` with all zeros. + pub fn zero() -> Self { + Self([0; HASH_SIZE]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} From 8144b4ceb9a17131c59e69f153b2fc665ee9936f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:15 +1000 Subject: [PATCH 078/118] Return error for empty seed --- .../eth2_key_derivation/src/derived_key.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index cd1a5b65e2c..ea1eb1fe0b1 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -29,13 +29,23 @@ pub const MOD_R_L: usize = 48; pub struct DerivedKey(SecretHash); impl DerivedKey { - /// Instantiates `Self` from some seed of any length. - pub fn from_seed(seed: &[u8]) -> Self { - Self(derive_master_sk(seed)) + /// Instantiates `Self` from some secret seed bytes. + /// + /// The key is generated deterministically; the same `seed` will always return the same `Self`. + /// + /// ## Errors + /// + /// Returns `Err(())` if `seed.is_empty()`, otherwise always returns `Ok(self)`. + pub fn from_seed(seed: &[u8]) -> Result { + if seed.is_empty() { + Err(()) + } else { + Ok(Self(derive_master_sk(seed))) + } } /// Derives a child key from the secret `Self` at some `index`. - pub fn derive_child(&self, index: u32) -> DerivedKey { + pub fn child(&self, index: u32) -> DerivedKey { Self(derive_child_sk(self.0.as_bytes(), index)) } From 13d8489c94b5a25654d6640d7283b42d82d5444c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:33 +1000 Subject: [PATCH 079/118] Add tests --- .../tests/eip2333_vectors.rs | 102 ++++++++++++++++++ eth2/utils/eth2_key_derivation/tests/tests.rs | 28 +++++ 2 files changed, 130 insertions(+) create mode 100644 eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs create mode 100644 eth2/utils/eth2_key_derivation/tests/tests.rs diff --git a/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs new file mode 100644 index 00000000000..42a3728b291 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs @@ -0,0 +1,102 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; +use num_bigint::BigUint; + +/// Contains the test vectors in a format that's easy for us to test against. +struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + child_sk: Vec, +} + +/// Struct to deal with easy copy-paste from specification test vectors. +struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + child_sk: &'static str, +} + +/// Converts from a format that's easy to copy-paste from the spec into a format that's easy to +/// test with. +impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + child_sk: int_to_vec(raw.child_sk), + } + } +} + +/// Converts 0x-prefixed hex to bytes. +fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") +} + +/// Converts an integer represented as a string to a big-endian byte array. +fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() +} + +/// Asserts that our code matches the given test vector. +fn assert_vector_passes(raw: RawTestVector) { + let vector: TestVector = raw.into(); + + let master = DerivedKey::from_seed(&vector.seed).unwrap(); + assert_eq!(master.secret(), &vector.master_sk[..], "master"); + + let child = master.child(vector.child_index); + assert_eq!(child.secret(), &vector.child_sk[..], "child"); +} + +/* + * The following test vectors are obtained from: + * + * https://eips.ethereum.org/EIPS/eip-2333 + */ + +#[test] +fn eip2333_test_case_0() { + assert_vector_passes(RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" + }) +} + +#[test] +fn eip2333_test_case_1() { + assert_vector_passes(RawTestVector { + seed: "0x3141592653589793238462643383279502884197169399375105820974944592", + master_sk: "46029459550803682895343812821003080589696405386150182061394330539196052371668", + child_index: 3141592653, + child_sk: "43469287647733616183478983885105537266268532274998688773496918571876759327260", + }) +} + +#[test] +fn eip2333_test_case_2() { + assert_vector_passes(RawTestVector { + seed: "0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00", + master_sk: "45379166311535261329029945990467475187325618028073620882733843918126031931161", + child_index: 4294967295, + child_sk: "46475244006136701976831062271444482037125148379128114617927607151318277762946", + }) +} + +#[test] +fn eip2333_test_case_3() { + assert_vector_passes(RawTestVector { + seed: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + master_sk: "31740500954810567003972734830331791822878290325762596213711963944729383643688", + child_index: 42, + child_sk: "51041472511529980987749393477251359993058329222191894694692317000136653813011", + }) +} diff --git a/eth2/utils/eth2_key_derivation/tests/tests.rs b/eth2/utils/eth2_key_derivation/tests/tests.rs new file mode 100644 index 00000000000..b18a7b0e267 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/tests.rs @@ -0,0 +1,28 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; + +#[test] +fn empty_seed() { + assert!( + DerivedKey::from_seed(&[]).is_err(), + "empty seed should fail" + ); +} + +#[test] +fn deterministic() { + assert_eq!( + DerivedKey::from_seed(&[42]).unwrap().secret(), + DerivedKey::from_seed(&[42]).unwrap().secret() + ); +} + +#[test] +fn children_deterministic() { + let master = DerivedKey::from_seed(&[42]).unwrap(); + assert_eq!( + master.child(u32::max_value()).secret(), + master.child(u32::max_value()).secret(), + ) +} From aed5ca6581a6fdd8187e12ae5dad9434f9acc3c7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:55:30 +1000 Subject: [PATCH 080/118] Tidy --- eth2/utils/eth2_key_derivation/src/derived_key.rs | 7 +++++++ .../eth2_key_derivation/src/lamport_secret_key.rs | 5 +++++ eth2/utils/eth2_key_derivation/src/secret_bytes.rs | 12 ++++++------ eth2/utils/eth2_key_derivation/src/secret_hash.rs | 4 +++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index ea1eb1fe0b1..b91f0bff800 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -3,6 +3,7 @@ use crate::{ }; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; +use zeroize::Zeroize; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; @@ -26,6 +27,12 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. +/// +/// Implements `Zeroize` on `Drop`. +// It's not strictly necessary that `DerivedKey` implements `Zeroize`, but it seems prudent to be a +// little over-cautious here; we don't require high-speed key generation at this stage. +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DerivedKey(SecretHash); impl DerivedKey { diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 2c64601e505..aa6dbb39323 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -2,6 +2,9 @@ use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; +/// A Lamport secret key as specified in [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +/// +/// Implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); @@ -35,10 +38,12 @@ impl LamportSecretKey { this } + /// Returns a reference to the `i`th `HASH_SIZE` chunk of `self`. pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } + /// Returns an iterator over `LAMPORT_ARRAY_SIZE` chunks of `HASH_SIZE` bytes. pub fn iter_chunks(&self) -> impl Iterator { self.0.iter() } diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs index c4eee4b8f52..5cedd05b1d3 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -1,6 +1,6 @@ use zeroize::Zeroize; -/// Provides a wrapper around a `Vec` that implements `Zeroize`. +/// Provides a wrapper around a `Vec` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretBytes(Vec); @@ -11,13 +11,13 @@ impl SecretBytes { Self(vec![0; len]) } - /// Returns a mutable reference to the underlying bytes. - pub fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } - /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } } diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs index 9c1bbb27b8b..0d9cfac1100 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_hash.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -1,7 +1,7 @@ use crate::derived_key::HASH_SIZE; use zeroize::Zeroize; -/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretHash([u8; HASH_SIZE]); @@ -12,10 +12,12 @@ impl SecretHash { Self([0; HASH_SIZE]) } + /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + /// Returns a mutable reference to the underlying bytes. pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 } From 1d74ca9865c526f2f5be42325629be441b27cfc5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 17:53:59 +1000 Subject: [PATCH 081/118] Add progress --- eth2/utils/eth2_wallet/src/wallet.rs | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 eth2/utils/eth2_wallet/src/wallet.rs diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs new file mode 100644 index 00000000000..734b92081b5 --- /dev/null +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -0,0 +1,52 @@ +use crate::json_wallet::JsonWallet; +use eth2_keystore::{ + encrypt, + json_keystore::{Cipher, Kdf}, + Password, +}; +use uuid::Uuid; + +pub use eth2_keystore::Error; + +pub struct Wallet { + json: JsonWallet, +} + +impl Wallet { + pub fn encrypt( + seed: &[u8], + password: Password, + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, + name: String, + ) -> Result { + let (cipher_text, checksum) = encrypt(&seed, &password, &kdf, &cipher)?; + + Ok(Self { + json: JsonWallet { + crypto: Crypto { + kdf: KdfModule { + function: kdf.function(), + params: kdf, + message: EmptyString, + }, + checksum: ChecksumModule { + function: Sha256Checksum::function(), + params: EmptyMap, + message: checksum.to_vec().into(), + }, + cipher: CipherModule { + function: cipher.function(), + params: cipher, + message: cipher_text.into(), + }, + }, + uuid, + nextaccount: 0, + version: Version::one(), + title, + }, + }) + } +} From 908be4a2685e5c3173c4be46f7e579936bff9421 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 17:56:39 +1000 Subject: [PATCH 082/118] Replace some password usage with slice --- eth2/utils/eth2_keystore/src/keystore.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index 5ed24825efc..ef7e07f2d75 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -109,7 +109,7 @@ impl<'a> KeystoreBuilder<'a> { pub fn build(self) -> Result { Keystore::encrypt( self.keypair, - self.password, + self.password.as_bytes(), self.kdf, self.cipher, self.uuid, @@ -132,7 +132,7 @@ impl Keystore { /// keypair and password. fn encrypt( keypair: &Keypair, - password: Password, + password: &[u8], kdf: Kdf, cipher: Cipher, uuid: Uuid, @@ -140,7 +140,7 @@ impl Keystore { ) -> Result { let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); - let (cipher_text, checksum) = encrypt(&secret, &password, &kdf, &cipher)?; + let (cipher_text, checksum) = encrypt(secret.as_bytes(), password, &kdf, &cipher)?; Ok(Keystore { json: JsonKeystore { @@ -176,7 +176,7 @@ impl Keystore { /// - The provided password is incorrect. /// - The keystore is badly formed. pub fn decrypt_keypair(&self, password: Password) -> Result { - let plain_text = decrypt(&password, &self.json.crypto)?; + let plain_text = decrypt(password.as_bytes(), &self.json.crypto)?; // Verify that secret key material is correct length. if plain_text.len() != SECRET_KEY_LEN { @@ -241,8 +241,8 @@ impl Keystore { /// /// - The `kdf` is badly formed (e.g., has some values set to zero). pub fn encrypt( - plain_text: &PlainText, - password: &Password, + plain_text: &[u8], + password: &[u8], kdf: &Kdf, cipher: &Cipher, ) -> Result<(Vec, [u8; HASH_SIZE]), Error> { @@ -257,7 +257,7 @@ pub fn encrypt( &derived_key.as_bytes()[0..16], params.iv.as_bytes(), ) - .process(plain_text.as_bytes(), &mut cipher_text); + .process(plain_text, &mut cipher_text); } }; @@ -272,11 +272,11 @@ pub fn encrypt( /// /// - The provided password is incorrect. /// - The `crypto.kdf` is badly formed (e.g., has some values set to zero). -pub fn decrypt(password: &Password, crypto: &Crypto) -> Result { +pub fn decrypt(password: &[u8], crypto: &Crypto) -> Result { let cipher_message = &crypto.cipher.message; // Generate derived key - let derived_key = derive_key(&password, &crypto.kdf.params)?; + let derived_key = derive_key(password, &crypto.kdf.params)?; // Mismatching checksum indicates an invalid password. if &generate_checksum(&derived_key, cipher_message.as_bytes())[..] @@ -318,12 +318,12 @@ fn generate_checksum(derived_key: &DerivedKey, cipher_message: &[u8]) -> [u8; HA } /// Derive a private key from the given `password` using the given `kdf` (key derivation function). -fn derive_key(password: &Password, kdf: &Kdf) -> Result { +fn derive_key(password: &[u8], kdf: &Kdf) -> Result { let mut dk = DerivedKey::zero(); match &kdf { Kdf::Pbkdf2(params) => { - let mut mac = params.prf.mac(password.as_bytes()); + let mut mac = params.prf.mac(password); // RFC2898 declares that `c` must be a "positive integer" and the `crypto` crate panics // if it is `0`. @@ -362,7 +362,7 @@ fn derive_key(password: &Password, kdf: &Kdf) -> Result { } crypto::scrypt::scrypt( - password.as_bytes(), + password, params.salt.as_bytes(), &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), dk.as_mut_bytes(), From 90ee979f60d8b3ccbb6e8a549d44986688c518b5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 19:55:58 +1000 Subject: [PATCH 083/118] First commits on path derivation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 eth2/utils/eth2_keystore/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 56174762158..eeb3dde833b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -4,6 +4,7 @@ mod derived_key; mod keystore; mod password; +mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs new file mode 100644 index 00000000000..dcfa26c8966 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -0,0 +1,50 @@ +use crate::plain_text::PlainText; +use crypto::{digest::Digest, sha2::Sha256}; + +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; +/// The digest size (in octets) of the hash function (SHA256) +const K: usize = HASH_SIZE; +/// The size of the lamport array. +const LAMPORT_ARRAY_SIZE: usize = 255; +/// The HKDF output size (in octets) +const L: usize = K * LAMPORT_ARRAY_SIZE; + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { + hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) +} + +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { + let mut hasher = Sha256::new(); + hasher.input(salt); + hasher.input(ikm); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { + let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); + + debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); + + for i in 0..LAMPORT_ARRAY_SIZE { + let mut hasher = Sha256::new(); + + hasher.input(prk); + + if let Some(prev) = okm.last() { + hasher.input(&prev[..]); + } + + hasher.input(&[i as u8]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.push(digest); + } + + okm +} From d69773854cdaba554919e6d2e0c69480feedda7b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 13:33:11 +1000 Subject: [PATCH 084/118] Progress with implementation --- Cargo.lock | 1 + eth2/utils/eth2_keystore/Cargo.toml | 1 + eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 674 ++++++++++++++++++++++++++- 4 files changed, 658 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d184550f7fe..b32c2057a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "bls 0.2.0", "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index 917a3f63ba5..8e83f2e759d 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.3" bls = { path = "../bls" } eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" +num-bigint = "0.2.6" [dev-dependencies] tempfile = "3.1.0" diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index eeb3dde833b..51231797d6f 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,7 @@ mod derived_key; mod keystore; +mod lamport_secret_key; mod password; mod path; mod plain_text; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index dcfa26c8966..699278fb332 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,20 +1,90 @@ -use crate::plain_text::PlainText; +use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; use crypto::{digest::Digest, sha2::Sha256}; +use num_bigint::BigUint; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; -/// The digest size (in octets) of the hash function (SHA256) -const K: usize = HASH_SIZE; +pub const HASH_SIZE: usize = 32; /// The size of the lamport array. -const LAMPORT_ARRAY_SIZE: usize = 255; -/// The HKDF output size (in octets) -const L: usize = K * LAMPORT_ARRAY_SIZE; +pub const LAMPORT_ARRAY_SIZE: u8 = 255; -fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { +pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + +fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { + hkdf_mod_r(seed) +} + +fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { + let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); + hkdf_mod_r(&compressed_lamport_pk) +} + +fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); + + // TODO: don't do all this extra work with extraction. + let lamport_bytes = lamport + .iter_chunks() + .map(|a| a.to_vec()) + .flatten() + .collect::>(); + + // TODO: justify 48. + mod_r(&lamport_bytes[0..48]) +} + +fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(bytes.len(), HASH_SIZE); + + let n = BigUint::from_bytes_be(bytes); + let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); + let x = (n % r).to_bytes_be(); + + debug_assert!(x.len() <= HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output +} + +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { + let salt = index.to_be_bytes(); + let not_ikm = flip_bits(ikm); + + let lamports = [ + ikm_to_lamport_sk(ikm, &salt), + ikm_to_lamport_sk(¬_ikm, &salt), + ]; + + let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + + lamports + .iter() + .map(LamportSecretKey::iter_chunks) + .flatten() + .enumerate() + .for_each(|(i, chunk)| { + let output_slice = lamport_pk + .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) + .expect("lamport_pk must have adequate capacity"); + + let mut hasher = Sha256::new(); + hasher.input(chunk); + hasher.result(output_slice); + }); + + let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut hasher = Sha256::new(); + hasher.input(&lamport_pk); + hasher.result(&mut compressed_lamport_pk); + + compressed_lamport_pk +} + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) } -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { +fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { let mut hasher = Sha256::new(); hasher.input(salt); hasher.input(ikm); @@ -25,26 +95,592 @@ fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { digest.into() } -fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { - let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); - - debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); +fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { + let mut okm = LamportSecretKey::zero(); for i in 0..LAMPORT_ARRAY_SIZE { let mut hasher = Sha256::new(); hasher.input(prk); - if let Some(prev) = okm.last() { - hasher.input(&prev[..]); + if let Some(prev) = i.checked_sub(1) { + hasher.input(okm.get_chunk(prev)) } - hasher.input(&[i as u8]); + hasher.input(&[i + 1]); - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.push(digest); + hasher.result(okm.get_mut_chunk(i)); } okm } + +fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(input.len(), HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + + for (i, byte) in input.iter().enumerate() { + output[i] = !byte + } + + output +} + +#[cfg(test)] +mod test { + use super::*; + + struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + lamport_0: Vec<&'static str>, + lamport_1: Vec<&'static str>, + compressed_lamport_sk: &'static str, + child_sk: &'static str, + } + + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec<[u8; HASH_SIZE]>, + lamport_1: Vec<[u8; HASH_SIZE]>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + todo!() + } + } + + fn get_raw_vector() -> RawTestVector { + RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: + "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + lamport_0: vec![ + "0x7b4a587eac94d7f56843e718a04965d4832ef826419b4001a3ad0ba77eb44a3b", + "0x90f45a712112122429412921ece5c30eb2a6daf739dc9034fc79424daeb5eff6", + "0xd061c2799de00b2be90eb1cc295f4c31e22d4b45c59a9b9b2554379bea7783cb", + "0x3ad17e4cda2913b5180557fbe7db04b5ba440ce8bb035ae27878d66fbfa50d2c", + "0xf5b954490933ad47f8bf612d4a4f329b3aa8914b1b83d59e15e271e2a087e002", + "0x95d68d505bf4ff3e5149bc5499cf4b2f00686c674a29a8d903f70e569557d867", + "0x1b59c76d9bb2170b220a87833582ede5970d4a336d91c99a812825afe963e056", + "0x4310ff73cfbbf7b81c39ecbf1412da33e9388c1a95d71a75e51fe12256551ceb", + "0xee696343f823e5716e16747f3bbae2fc6de233fe10eea8e45b4579018da0874f", + "0xae12a437aaa7ae59f7d8328944b6a2b973a43565c55d5807dc2faf223a33aa73", + "0x2a3ae0b47f145bab629452661ff7741f111272e33ec571030d0eb222e1ed1390", + "0x1a3ea396e8cbd1d97733ef4753d6840b42c0795d2d693f18e6f0e7b3fff2beb2", + "0x472429d0643c888bfdfe6e6ccfdeee6d345d60c6710859ac29fc289fd3656347", + "0xa32d4d955949b8bed0eb20f586d8fd516d6ddec84fbbc36998d692633c349822", + "0xe5ac8ac5ee1d40e53a7abf36e8269d5d5fce450a87feae8e59f432a44bcc7666", + "0xddf9e497ed78032fbd72d9b8abd5204d81c3475f29afa44cdf1ded8ea72dd1dc", + "0x945c62e88fb1e5f3c15ff57cd5eb1586ee93ec5ec80154c5a9c50241c5adae0a", + "0xc8868b50fc8423c96b7efa1ede4d3203a6b835dbeb6b2ababc58397e6b31d9dd", + "0x66de9bd86b50e2b6a755310520af655759c1753bff34b79a5cd63d6811fc8c65", + "0x5b13786c6068df7735343e5591393bea8aee92ac5826d6132bf4f5ebf1098776", + "0xa2038fc7d8e3cb2eda2bd303cfa76a9e5d8b88293918bec8b2fc03be75684f14", + "0x47a13f6b2308a50eded830fdee7c504bf49d1fe6a95e337b0825d0d77a520129", + "0xb534cdddcf1aa1c6b4cbba46d1db31b766d958e0a0306450bc031d1e3ed79d97", + "0x54aa051b754c31658377f7bff00b7deaa861e74cb12e1eb84216666e19b23d69", + "0x0220d57f63435948818eb376367b113c188e37451c216380f65d1ad55f73f527", + "0xf9dd2e391565534a4db84980433bf5a56250f45fe294fce2679bcf115522c081", + "0x1166591ee2ca59b9f4e525900f085141be8879c66ef18529968babeb87c44814", + "0xf4fa2e8de39bdbeb29b64d8b440d3a6c9a6ca5bdce543877eaee93c11bd70ab8", + "0x07f466d73b93db283b3f7bfaf9c39ae296adc376ab307ef12312631d0926790e", + "0xb2ecff93acb4fa44c1dbf8464b81734a863b6d7142b02f5c008907ea4dc9aaa1", + "0xa1d9c342f6c293ac6ef8b5013cba82c4bad6ed7024d782948cb23cd490039ba1", + "0xc7d04a639ba00517ece4dbc5ef4aaf20e0ccde6e4a24c28936fabe93dec594db", + "0xe3cbb9810472d9dd1cdb5eed2f74b67ea60e973d2d2e897bd64728c9b1aa0679", + "0xe36884703413958ff2aba7a1f138a26d0ac0a371270f0169219beb00a5add5f0", + "0xe5ea300a09895b3f98de5232d92a36d5611cbcf9aaf9e7bb20cf6d1696ad1cb4", + "0xc136cda884e18175ab45148ed4f9d0d1a3c5e11ad0275058e61ae48eb151a81f", + "0x3ee1101e944c040021187e93b6e0beb1048c75fb74f3fdd67756b1c8517a311f", + "0x016964fd6fc32b9ad07a630949596715dee84d78230640368ff0929a280cf3a2", + "0xe33865fc03120b94333bb754fd097dc0f90e69ff6fd221d6aae59fcf2d762d76", + "0xe80bb3515a09ac6ecb4ec59de22701cdf954b1ae8a677fd85508c5b041f28058", + "0x3889af7cd325141ec288021ede136652a0411d20364005b9d3ca9102cb368f57", + "0x18dad0bc975cf8800addd54c7867389d3f7fe1b97d348bd8412a6cbfb75c520a", + "0x09035218686061ee91bd2ad57dc6fb6da7243b8177a153484524b2b228da5314", + "0x688fd7a97551c64eae33f91abb073a46eafbbacd5595c6bac2e57dd536acdfe2", + "0x1fc164dce565a1d0da59cc8048b334cc5eb84bf04de2399ddb847c22a7e32ab7", + "0xa2a340ba05c8a30dd1cab886a926b761758eba0e41b5c4c5dfd4a42f249655c1", + "0xc43dffe01479db836a6a1a74564b297fad0d69c6b06cf593f6db9f26b4f307d5", + "0x73cef7f3ff724a30a79e1dca74cef74954afeefa2e476c4dec65afe50c16c5c4", + "0xa54002253ab7b95cc5b664b3f08976400475cc56f170b939f6792e730ff5170b", + "0x9ade43053d41afebc002f09476dffd1b13ecbf67f810791540b92ca56d5e63e4", + "0x234e7cbfbe45b22a871db26738fa05de09213a925439d7f3e5108132e521b280", + "0x066b712417332c7cfca871fb1bb5839f0341acf9266229603a3eddbc8a93b59f", + "0xb5857acdcf636330da2cfcc99c81d9fdbd20c506a3c0e4f4f6a139d2a64f051c", + "0xe119908a150a49704b6bbba2c470cd619a0ae10dd9736e8d491890e3c8509fff", + "0xb8a5c5dbb51e6cb73cca95b4ad63ea3c7399cd16b05ab6261535495b3af2ca51", + "0x05624a1d4d2d2a31160bc48a6314bbf13eaddf56cddb0f0aa4ed3fb87f8b479f", + "0x483daceff1c3baa0ed0f3be7e534eebf5f4aed424ecd804edfbf5c56b3476b50", + "0x424d04694e7ae673707c77eb1c6d0996d250cfab6832ee3506a12e0384a3c5c9", + "0xa11fed0ed8057966bfe7136a15a814d06a516fbc9d44aeef87c509137a26190e", + "0x3694d22d1bc64658f3adbe2cc9f1716aee889066e0950e0b7a2fd576ed36bb76", + "0x49a13000a87f39f93d0ae9c3a4cfccbf440c0a75cce4c9d70dac627b6d6958b3", + "0xb3ff0cdd878d5ac1cb12e7d0b300d649fdd008800d498ae4f9fbf9510c74249a", + "0xe52a867cfb87d2fe7102d23d8d64925f7b75ca3f7d6bb763f7337352c255e0be", + "0x6513b372e4e557cca59979e48ec27620e9d7cdb238fcf4a9f19c3ba502963be0", + "0x9f69d82d4d51736902a987c8b5c30c2b25a895f2af5d2c846667ff6768bcc774", + "0x049a220dbe3340749f94643a429cb3cba3c92b561dc756a733d652d838728ab3", + "0x4fa2cd877aa115b476082b11053309f3537fa03d9158085f5f3f4bab6083e6da", + "0xed12db4069eb9f347735816afcee3fe43d4a6999fef8240b91bf4b05447d734f", + "0x3ecbe5eda469278f68548c450836a05cc500864664c7dda9b7526f084a891032", + "0x690d8f928fc61949c22e18cceaa2a446f8e1b65bd2e7af9e0a8e8284134ab3d2", + "0x99e09167a09f8261e7e8571d19148b7d7a75990d0702d9d582a2e4a96ac34f8e", + "0x6d33931693ed7c2e1d080b6a37da52c279a06cec5f534305819f7adf7db0afe3", + "0xc4b735462a9a656e28a52b1d4992ea9dea826b858971d698453a4be534d6bb70", + "0xedf92b10302dc41f8d362b360f4c2ef551d50e2ded012312c964002d2afc46d7", + "0x58f6691cca081ae5c3661dd171b87cc49c90359bb03cc0e57e503f7fcf14aefc", + "0x5d29b8b4ee295a73c4a8618927b3d14b76c7da049133a2257192b10be8c17a6a", + "0x646802fa42801e0ae24011fb4f62e87219ef1da01f7fc14bf8d6bd2d9e7c21f1", + "0x23abf45eee65cc4c1e95ccab42ad280a00bb3b14d243e2021a684075f900141e", + "0x2b1ae95c975bf9c387eae506fdb5e58afd2d198f00a21cd3fddb5855e8021e4d", + "0x0ef9f6e1c0583493d343e75f9c0c557fa6da0dc12b17a96c5757292916b72ee3", + "0x04c7fc76195c64a3285af14161077c045ff6ddbb67c0ff91b080f98eb6781e5c", + "0xba12679b97027d0e7076e6d19086c07792eaa7f78350842fbef8ddf5bcd3ecc0", + "0xcead458e6799df4d2f6cbf7f13cb3afec3441a354816e3071856ed49cbdbb1a7", + "0xbe6c56256556bb5c6727a1d9cb641d969677f56bb5ad7f8f7a7c9cfd128427b4", + "0xc80f11963ff40cb1888054b83c0463d32f737f2e7d42098e639023db0dfc84d4", + "0xac80006c1296bcfde86697efebb87fb0fddfb70dd34dd2ee4c152482af4687eb", + "0xbb7d13ce184249df4576fc3d13351e1683500e48726cd4198423f14f9094068b", + "0x1b2d9c40c55bd7362664fa46c1268e094d56c8193e3d991c08dc7a6e4ca14fa1", + "0x9bd236254d0565f5b2d24552d4b4d732de43b0adaa64ecd8be3efc6508577591", + "0x38078cefccc04e8312d79e0636e0e3157434c50a2ad4e3e87cc6584c41eec8b5", + "0xb5d15a8527ff3fa254ba61ffceb02d2570b53361894f351a9e839c0bb716857d", + "0x6763dad684bf2e914f40ae0a7ee0cdf12c97f41fc05a485d5991b4daad21a3f8", + "0xc80363c20df589333ecbe05bd5f2c19942ebc2593626dc50d00835c40fb8d005", + "0x48502b56ae93acd2794f847cbe825525d5d5f59f0f75c67aff84e5338776b3af", + "0xfd8e033493ba8af264a855a78ab07f37d936351d2879b95928909ed8df1b4f91", + "0x11f75bee9eac7356e65ebc7f004ccdc1da80807380d69143293d1421f50b1c97", + "0x903a88a3ebe84ca1c52a752b1faffa9ca1daedac9cbf1aa70942efc9beb44b79", + "0x2c0dcd68837f32a69da651045ad836b8cd6b48f2c8c5d73a3bd3bba6148d345a", + "0x0aa0f49b3476f3fdb6393f2ab601e0009586090b72ee54a525734f51598960d5", + "0xf7a789f013f702731656c562caa15b04cb7c9957376c4d80b8839167bb7fa626", + "0x4e0be1b19e305d82db3fd8affd67b0d2559da3edbfb08d19632a5cc46a90ed07", + "0x3caaccfc546d84d543eaf4f4c50c9c8fd831c12a8de56fdb9dfd04cc082882fe", + "0x894f6a01fd34f0642077e22981752011678548eb70eb55e8072c1caffc16fe02", + "0xae7eb54adaa68679348ea3537a49be669d1d61001fbab9fac259ba727dbc9a1a", + "0x291a1cbdceff957b5a65440ab67fb8672de881230fe3108a15ca487c2662c2c7", + "0x891d43b867137bf8beb9df4da2d951b5984a266a8cd74ec1593801d005f83f08", + "0xc558407f6491b37a10835e0ad7ce74f4e368aa49157a28873f7229310cb2d7fd", + "0x9ce061b0a072e1fe645f3479dac089b5bfb78cfa6cfbe5fd603bcdb504711315", + "0xa8e30d07b09275115dd96472ecf9bc316581caf307735176ca226d4cd9022925", + "0x918ee6d2efba7757266577691203f973cf4f4cac10f7d5f86acd2a797ff66583", + "0xfa31ba95e15d1635d087522f3d0da9cf7acac4ed6d0ac672654032a3c39244a6", + "0xf2952b58f015d6733af06938cd1f82fbddb3b796823bee7a3dbffa04efc117c2", + "0x46f8f742d3683de010ede528128d1181e8819f4252474f51371a177bfa518fa4", + "0x4ca1cc80094f2910cf83a9e65ad70e234690ffb9142793911ec7cf71663545b3", + "0x381965037b5725c71bfa6989d4c432f6611de8e8ec387f3cfc0dcb1a15191b73", + "0x2562b88ed3b86ba188be056805a3b7a47cb1a3f630d0e2f39647b0792ec6b7d8", + "0x565f6d14e7f22724f06d40f54465ad40d265b6de072b34a09d6e37a97a118cd8", + "0xc2982c861ad3278063b4a5f584eaf866db684cc4e712d64230fc9ee33bb4253b", + "0xfd806c91927e549d8d400ab7aa68dbe60af988fbabf228483ab0c8de7dab7eee", + "0xafae6ff16c168a3a3b5c2f1742d3f89fa4777c4bd0108f174014debf8f4d629c", + "0xaf5a4be694de5e53632be9f1a49bd582bf76002259460719197079c8c4be7e66", + "0xa8df4a4b4c5bf7a4498a11186f8bb7679137395f28e5c2179589e1c1f26504b5", + "0xce8b77c64c646bb6023f3efaed21ca2e928e21517422b124362cf8f4d9667405", + "0x62e67a8c423bc6c6c73e6cd8939c5c1b110f1a38b2ab75566988823762087693", + "0x7e778f29937daaa272d06c62d6bf3c9c0112d45a3df1689c602d828b5a315a9f", + "0xe9b5abd46c2377e602ff329050afa08afe152f4b0861db8a887be910ff1570bf", + "0xa267b1b2ccd5d96ae8a916b0316f06fafb886b3bb41286b20763a656e3ca0052", + "0xb8ed85a67a64b3453888a10dedf4705bd27719664deff0996a51bb82bc07194f", + "0x57907c3c88848f9e27bc21dd8e7b9d61de48765f64d0e943e7a6bb94cc2021ab", + "0xd2f6f1141a3b76bf9bf581d49091142944c7f9f323578f5bdd5522ba32291243", + "0xc89f104200ed4c5d5f7046d99e68ae6f8ec31e2eeceb568eb05087e3aa546a74", + "0xc9f367fae45c39299693b134229bb6dd0da112fd1a7d19b7f4772c01e5cbe479", + "0x64e2d4ad51948764dd578d26357e29e8e4d076d65c05cffdf8211b624fefe9ac", + "0xf9a9b4e6d5be7fc051df8ecd9c389d16b1af86c749308e6a23f7ff4871f0ba9a", + "0x0d2b2a228b86ebf9499e1bf7674335087ced2eb35ce0eb90954a0f75751a2bf4", + "0xff8531b45420a960d6e48ca75d77758c25733abde83cd4a6160beae978aa735e", + "0xd6d412bd1cb96a2b568d30e7986b7e8994ca92fd65756a758295499e11ea52b6", + "0xad8533fccbecdd4a0b00d648bfe992360d265f7be70c41d9631cefad5d4fe2f6", + "0x31fbf2afb8d5cc896d517cfc5201ee24527e8d283f9c37ca10233bef01000a20", + "0x2fd67b7365efc258131eb410f46bf3b1cbd3e9c76fd6e9c3e86c9ff1054116ff", + "0xab6aa29f33d18244be26b23abadb39679a8aa56dafc0dd7b87b672df5f5f5db6", + "0xbad3b0f401ca0a53a3d465de5cecd57769ec9d4df2c04b78f8c342a7ed35bbee", + "0xbdc24d46e471835d83ce8c5b9ecbe675aab2fd8f7831c548e8efd268c2ee2232", + "0x87265fabd7397d08f0729f13a2f3a25bbc8c874b6b50f65715c92b62f665f925", + "0xa379fd268e7ff392c067c2dd823996f72714bf3f936d5eeded71298859f834cb", + "0xf3ab452c9599ebfbb234f72a86f3062aed12ae1f634abbe542ff60f5cefc1fcf", + "0x2b17ebb053a3034c07da36ed2ba42c25ad8e61dec87b5527f5e1c755eb55405a", + "0x305b40321bd67bf48bfd121ee4d5d347268578bd4b8344560046594771a11129", + "0xe7029c9bea020770d77fe06ca53b521b180ad6a9e747545aadc1c74beef7241c", + "0xabc357cec0f4351a5ada22483d3b103890392f8d8f9cb8073a61969ed1be4e08", + "0x97f88c301946508428044d05584dc41af2e6a0de946de7d7f5269c05468afe20", + "0xbdc08fe8d6f9a05ad8350626b622ad8eec80c52331d154a3860c98676719cfbd", + "0x161590fc9f7fcf4eaba2f950cf588e6da79e921f139d3c2d7ebe017003a4799e", + "0x91b658db75bc3d1954bfde2ef4bc12980ff1688e09d0537f170c9ab47c162320", + "0x76d995f121406a63ce26502e7ec2b653c221cda357694a8d53897a99e6ce731e", + "0x3d6b2009586aceb7232c01259bb9428523c02b0f42c2100ec0d392418260c403", + "0x14ca74ecbc8ec0c67444c6cb661a2bce907aa2a1453b11f16002b815b94a1c49", + "0x553b4dc88554ebe7b0a3bd0813104fd1165a1f950ceace11f5841aa74b756d85", + "0x4025bf4ad86751a156d447ce3cabafde9b688efcdafd8aa4be69e670f8a06d9e", + "0x74260cf266997d19225e9a0351a9acfa17471fccdf5edc9ccc3bb0d23ef551c5", + "0xf9dbca3e16d234e448cf03877746baeb62a8a25c261eff42498b1813565c752a", + "0x2652ec98e05c1b6920fb6ddc3b57e366d514ffa4b35d068f73b5603c47f68f2f", + "0x83f090efeb36db91eb3d4dfbb17335c733fce7c64317d0d3324d7caaaf880af5", + "0x1e86257f1151fb7022ed9ed00fb961a9a9989e58791fb72043bb63ed0811791c", + "0xd59e4dcc97cba88a48c2a9a2b29f79125099a39f74f4fb418547de8389cd5d15", + "0x875a19b152fe1eb3fe1de288fa9a84864a84a79bac30b1dbd70587b519a9770e", + "0x9c9dc2d3c8f2f6814cfc61b42ee0852bbaf3f523e0409dd5df3081b750a5b301", + "0xf6f7f81c51581c2e5861a00b66c476862424151dd750efeb20b7663d552a2e94", + "0x723fcb7ca43a42483b31443d4be9b756b34927176f91a391c71d0b774c73a299", + "0x2b02d8acf63bc8f528706ed4d5463a58e9428d5b71d577fd5daa13ba48ac56cf", + "0x2ff6911f574c0f0498fc6199da129446b40fca35ccbf362bc76534ba71c7ca22", + "0x1ef4b959b11bc87b11e4a5f84b4d757c6bdcfad874acec9a6c9eee23dc4bbe1b", + "0x68e2df9f512be9f64b7e3a2dee462149dac50780073d78b569a20256aea5f751", + "0xd1a3682e12b90ae1eab27fc5dc2aef3b8e4dbb813925e9a91e58d6c9832767b6", + "0x75778ccc102d98c5e0b4b83f7d4ef7fe8bc7263cc3317723001cb0b314d1e9e8", + "0xc7f44e2cead108dc167f0036ac8a278d3549cc3dd5cc067d074ccad9b1d9f8d4", + "0x4cba0223c5df2796b0ee9fbc084d69f10e6aedda8f0cf86171bebb156ede676c", + "0x628deda825661f586a5713e43c806fdd55e1a53fbe90a4ddb5f3786570740954", + "0xfc82a253bc7e0ac96252b238fbb411a54e0adf78d089f804a7fc83a4959b401e", + "0x72a6491f5daae0ceb85b61a5ed69009dd2a167c64cb35cabf38b846e27268e9d", + "0xee139a913d4fcf25ba54bb36fc8051b91f2ec73ba820cc193c46fb2f7c37a106", + "0x7f75021f2b1d0c78859478e27f6f40646b5776c060f1a5f6f0944c840a0121f8", + "0x5b60a1b78feca1d2602ac8110d263ad6b3663cbf49e6bdc1077b4b80af2feb6f", + "0xd61f15d80b1e88469b6a76ed6a6a2b94143b6acc3bd717357264818f9f2d5c6d", + "0xea85da1780b3879a4d81b685ba40b91c060866abd5080b30fbbb41730724a7dd", + "0xb9b9da9461e83153f3ae0af59fbd61febfde39eb6ac72db5ed014797495d4c26", + "0xf737762fe8665df8475ff341b3762aaeb90e52974fe5612f5efd0fc1c409d7f8", + "0xaaa25d934a1d5aa6b2a1863704d7a7f04794ed210883582c1f798be5ca046cf7", + "0x932f46d0b6444145221b647f9d3801b6cb8b1450a1a531a959abdaacf2b5656b", + "0xf4a8b0e52f843ad27635c4f5a467fbf98ba06ba9a2b93a8a97170b5c41bf4958", + "0x196ed380785ee2925307ec904161dc02a4596a55499e5b0a3897f95485b3e74a", + "0x772e829a405219e4f8cd93a1ef15c250be85c828c1e29ef6b3f7b46958a85b44", + "0xd66cfc9af9941515d788f9f5e3b56fddb92464173ddb67b83bf265e7ea502170", + "0xf5b040bfc246425278e2423b1953d8ad518de911cf04d16c67d8580a09f90e62", + "0xd2d18b2ae8a53dde14b4000e5e7e414505825f50401a3797dd8820cf510dc448", + "0xc01dcc064e644266739cd0ec7edf92fc2ef8e92e0beedf0e8aa30efcff1644fe", + "0x24720d325913ba137daf031924ad3bfaa1c8c00a53a2d048fe5667aef45efce3", + "0x70a24e1c89b3ea78d76ef458d498dcb5b8561d484853b2a8b2adcd61869857df", + "0x0ff3313997f14e1b1dcd80f1d62c58aaefb19efd7c0ea15dde21aa4e2a516e80", + "0x960c1f50062a4df851638f42c0259b6e0a0217300884f13a3c5c8d94adb34f21", + "0xb71ca7cc8578149da556131268f4625b51620dfc3a6e9fbd47f5df03afbd410e", + "0xa1a3eeec0addec7b9e15f416a07608a1b5d94f0b42d5c203b8ced03a07484f5b", + "0xa4bb8b059aa122ca4652115b83b17af80cfbea0d3e1e8979a396a667f94e85f3", + "0x31c4d2f252167fe2a4d41944224a80b2f1afaf76f8dd6a3d52d71751849e44bb", + "0x79642dd6a255f96c9efe569304d58c327a441448db0431aa81fe072d0d359b52", + "0x42a4b504714aba1b67defe9458fff0c8cb1f216dcab28263cef67a65693b2036", + "0xe3d2f6a9d882d0f026ef316940dfcbf131342060ea28944475fe1f56392c9ad2", + "0x986af9aeff236394a0afa83823e643e76f7624e9bfd47d5468f9b83758a86caa", + "0xafe2de6ede50ee351d63ed38d1f2ae5203174c731f41bbed95db467461ad5492", + "0x9ad40f0785fe1c8a5e4c3342b3c91987cd47a862ece6573674b52fa0456f697a", + "0xde4cde6d0fc6def3a89b79da0e01accdbec049f1c9471d13a5d59286bd679af1", + "0xecd0d1f70116d6b3ae21c57fb06ad90eed33d040e2c5c3d12714b3be934fa5ce", + "0x3c53c5bf2d1b1d4038e1f0e8a2e6d12e0d4613d5cd12562578b6909921224c10", + "0x36087382b37e9e306642cc6e867e0fb2971b6b2b28b6caf2f9c96b790e8db70a", + "0xa957496d6a4218a19998f90282d05bd93e6baabf55e55e8a5f74a933a4dec045", + "0x077d6f094e8467a21f02c67753565ec5755156015d4e86f1f82a22f9cf21c869", + "0x12dd3b1f29e1462ca392c12388a77c58044151154cf86f23873f92a99b6bb762", + "0x7fdbcdedcc02ecf16657792bd8ef4fa4adeee497f30207d4cc060eb0d528b26b", + "0x245554b12bf8edf9e9732d6e2fa50958376e355cb695515c94676e64c6e97009", + "0xccd3b1841b517f7853e35f85471710777e437a8665e352a0b61c7d7083c3babc", + "0xd970545a326dcd92e31310d1fdce3703dff8ef7c0f3411dfa74fab8b4b0763ac", + "0xd24163068918e2783f9e79c8f2dcc1c5ebac7796ce63070c364837aac91ee239", + "0x256a330055357e20691e53ca5be846507c2f02cfde09cafb5809106f0af9180e", + "0xfa446a5d1876c2051811af2a341a35dbcd3f7f8e2e4f816f501139d27dd7cd82", + "0xbafbc7a8f871d95736a41e5721605d37e7532e41eb1426897e33a72ed2f0bf1d", + "0x8055af9a105b6cf17cfeb3f5320e7dab1a6480500ff03a16c437dfec0724c290", + "0x1de6ee3e989497c1cc7ca1d16b7b01b2f336524aa2f75a823eaa1716c3a1a294", + "0x12bb9508d646dda515745d104199f71276d188b3e164083ad27dfdcdc68e290b", + "0x7ea9f9939ad4f3b44fe7b780e0587da4417c34459b2996b3a449bb5b3ff8c8cb", + "0xa88d2f8f35bc669aa6480ce82571df65fea366834670b4084910c7bb6a735dde", + "0x9486e045adb387a550b3c7a603c30e07ed8625d322d1158f4c424d30befe4a65", + "0xb283a70ba539fe1945be096cb90edb993fac77e8bf53616bde35cdcaa04ab732", + "0xab39a81558e9309831a2caf03e9df22e8233e20b1769f16e613debcdb8e2610f", + "0x1fc12540473fbbad97c08770c41f517ce19dc7106aa2be2e9b77867046627509", + "0xec33dbec9d655c4c581e07d1c40a587cf3217bc8168a81521b2d0021bd0ec133", + "0xc8699e3b41846bc291209bbb9c06f565f66c6ccecbf03ebc27593e798c21fe94", + "0x240d7eae209c19d453b666c669190db22db06279386aa30710b6edb885f6df94", + "0xb181c07071a750fc7638dd67e868dddbeeee8e8e0dcbc862539ee2084674a89e", + "0xb8792555c891b3cbfddda308749122a105938a80909c2013637289e115429625", + "0xfe3e9e5b4a5271d19a569fee6faee31814e55f156ba843b6e8f8dc439d60e67a", + "0x912e9ba3b996717f89d58f1e64243d9cca133614394e6ae776e2936cf1a9a859", + "0xa0671c91a21fdfd50e877afa9fe3974aa3913855a2a478ae2c242bcdb71c73d7", + "0x5b55d171b346db9ba27b67105b2b4800ca5ba06931ed6bd1bafb89d31e6472e6", + "0x68438458f1af7bd0103ef33f8bc5853fa857b8c1f84b843882d8c328c595940d", + "0x21fe319fe8c08c1d00f977d33d4a6f18aecaa1fc7855b157b653d2d3cbd8357f", + "0x23cce560bc31f68e699ece60f21dd7951c53c292b3f5522b9683eb2b3c85fc53", + "0x917fa32d172c352e5a77ac079df84401cdd960110c93aa9df51046d1525a9b49", + "0x3fc397180b65585305b88fe500f2ec17bc4dccb2ec254dbb72ffb40979f14641", + "0xf35fb569e7a78a1443b673251ac70384abea7f92432953ca9c0f31c356be9bd9", + "0x7955afa3cd34deb909cd031415e1079f44b76f3d6b0aaf772088445aaff77d08", + "0x45c0ca029356bf6ecfc845065054c06024977786b6fbfaea74b773d9b26f0e6c", + "0xe5c1dac2a6181f7c46ab77f2e99a719504cb1f3e3c89d720428d019cb142c156", + "0x677b0e575afcccf9ddefc9470e96a6cfff155e626600b660247b7121b17b030a", + "0xbeed763e9a38277efe57b834a946d05964844b1f51dba2c92a5f3b8d0b7c67d0", + "0x962b17ed1a9343d8ebfae3873162eef13734985f528ca06c90b0c1e68adfdd89", + ], + lamport_1: vec![ + "0xb3a3a79f061862f46825c00fec4005fb8c8c3462a1eb0416d0ebe9028436d3a9", + "0x6692676ce3b07f4c5ad4c67dc2cf1dfa784043a0e95dd6965e59dc00b9eaff2d", + "0xbf7b849feb312db230e6e2383681b9e35c064e2d037cbc3c9cc9cd49220e80c9", + "0xa54e391dd3b717ea818f5954eec17b4a393a12830e28fabd62cbcecf509c17dc", + "0x8d26d800ac3d4453c211ef35e9e5bb23d3b9ede74f26c1c417d6549c3110314d", + "0xbb8153e24a52398d92480553236850974576876c7da561651bc551498f184d10", + "0x0d30e0e203dc4197f01f0c1aba409321fbf94ec7216e47ab89a66fb45e295eff", + "0x01dc81417e36e527776bf37a3f9d74a4cf01a7fb8e1f407f6bd525743865791d", + "0xa6318e8a57bec438245a6834f44eb9b7fb77def1554d137ea12320fc572f42c9", + "0xd25db9df4575b595130b6159a2e8040d3879c1d877743d960bf9aa88363fbf9f", + "0x61bb8baeb2b92a4f47bb2c8569a1c68df31b3469e634d5e74221bc7065f07a96", + "0xb18962aee4db140c237c24fec7fd073b400b2e56b0d503f8bc74a9114bf183bf", + "0x205473cc0cdab4c8d0c6aeceda9262c225b9db2b7033babfe48b7e919751a2c6", + "0xc5aa7df7552e5bb17a08497b82d8b119f93463ccb67282960aee306e0787f228", + "0x36da99e7d38ce6d7eab90ea109ba26615ad75233f65b3ae5056fba79c0c6682a", + "0xd68b71bba6266b68aec0df39b7c2311e54d46a3eab35f07a9fe60d70f52eec58", + "0xbbe56f1274ada484277add5cb8c90ef687d0b69a4c95da29e32730d90a2d059f", + "0x0982d1d1c15a560339d9151dae5c05e995647624261022bbedce5dce8a220a31", + "0x8ef54ad546d2c6144fc26e1e2ef92919c676d7a76cfdfb5c6a64f09a54e82e71", + "0x1e3ac0133eef9cdbeb590f14685ce86180d02b0eea3ef600fd515c38992b1f26", + "0x642e6b1c4bec3d4ba0ff2f15fbd69dcb57e4ba8785582e1bc2b452f0c139b590", + "0xca713c8cf4afa9c5d0c2db4fc684a8a233b3b01c219b577f0a053548bedf8201", + "0xd0569ba4e1f6c02c69018b9877d6a409659cb5e0aa086df107c2cc57aaba62da", + "0x4ebe68755e14b74973e7f0fa374b87cee9c370439318f5783c734f00bb13e4b5", + "0x788b5292dc5295ae4d0ea0be345034af97a61eec206fda885bbc0f049678c574", + "0x0ebd88acd4ae195d1d3982038ced5af1b6f32a07349cf7fffbff3ce410c10df2", + "0xc7faf0a49234d149036c151381d38427b74bae9bd1601fc71663e603bc15a690", + "0xc5247bf09ebe9fa4e1013240a1f88c703f25a1437196c71ee02ca3033a61f946", + "0x719f8c68113d9f9118b4281e1f42c16060def3e3eeef15f0a10620e886dc988f", + "0x28da4f8d9051a8b4d6158503402bdb6c49ba2fb1174344f97b569c8f640504e6", + "0x96f6773576af69f7888b40b0a15bc18cc9ec8ca5e1bb88a5de58795c6ddf678e", + "0x8d80d188a4e7b85607deccf654a58616b6607a0299dd8c3f1165c453fd33d2e4", + "0x9c08dcc4f914486d33aa24d10b89fd0aabcc635aa2f1715dfb1a18bf4e66692a", + "0x0ff7045b5f6584cc22c140f064dec0692762aa7b9dfa1defc7535e9a76a83e35", + "0x8e2dae66fa93857b39929b8fc531a230a7cfdd2c449f9f52675ab5b5176461d5", + "0xf449017c5d429f9a671d9cc6983aafd0c70dd39b26a142a1d7f0773de091ac41", + "0xed3d4cab2d44fec0d5125a97b3e365a77620db671ecdda1b3c429048e2ebdae6", + "0x836a332a84ee2f4f5bf24697df79ed4680b4f3a9d87c50665f46edaeed309144", + "0x7a79278754a4788e5c1cf3b9145edb55a2ba0428ac1c867912b5406bb7c4ce96", + "0x51e6e2ba81958328b38fd0f052208178cec82a9c9abd403311234e93aff7fa70", + "0x217ec3ec7021599e4f34410d2c14a8552fff0bc8f6894ebb52ec79bf6ec80dc9", + "0x8a95bf197d8e359edabab1a77f5a6d04851263352aa46830f287d4e0564f0be0", + "0x60d0cbfb87340b7c92831872b48997ce715da91c576296df215070c6c20046d4", + "0x1739fbca476c540d081b3f699a97387b68af5d14be52a0768d5185bc9b26961b", + "0xac277974f945a02d89a0f8275e02de9353e960e319879a4ef137676b537a7240", + "0x959b7640821904ba10efe8561e442fbdf137ccb030aee7472d10095223e320ba", + "0xdba61c8785a64cb332342ab0510126c92a7d61f6a8178c5860d018d3dad571c6", + "0xc191fb6a92eb1f1fb9e7eb2bdecd7ec3b2380dd79c3198b3620ea00968f2bd74", + "0x16ef4e88e182dfc03e17dc9efaa4a9fbf4ff8cb143304a4a7a9c75d306729832", + "0x39080e4124ca577ff2718dfbcb3415a4220c5a7a4108729e0d87bd05adda5970", + "0xa29a740eef233956baff06e5b11c90ed7500d7947bada6da1c6b5d9336fc37b6", + "0x7fda7050e6be2675251d35376bacc895813620d245397ab57812391d503716ee", + "0x401e0bf36af9992deb87efb6a64aaf0a4bc9f5ad7b9241456b3d5cd650418337", + "0x814e70c57410e62593ebc351fdeb91522fe011db310fcf07e54ac3f6fefe6be5", + "0x03c1e52ecbef0d79a4682af142f012dc6b037a51f972a284fc7973b1b2c66dcf", + "0x57b22fb091447c279f8d47bdcc6a801a946ce78339e8cd2665423dfcdd58c671", + "0x53aeb39ab6d7d4375dc4880985233cba6a1be144289e13cf0bd04c203257d51b", + "0x795e5d1af4becbca66c8f1a2e751dcc8e15d7055b6fc09d0e053fa026f16f48f", + "0x1cd02dcd183103796f7961add835a7ad0ba636842f412643967c58fe9545bee4", + "0x55fc1550be9abf92cacb630acf58bad11bf734114ebe502978a261cc38a4dd70", + "0x6a044e0ea5c361d3fb2ca1ba795301e7eb63db4e8a0314638f42e358ea9cfc3e", + "0x57d9f15d4db199cbcb7cbd6524c52a1b799d52b0277b5a270d2985fcee1e2acb", + "0x66c78c412e586bd01febc3e4d909cc278134e74d51d6f60e0a55b35df6fb5b09", + "0x1076799e15a49d6b15c2486032f5e0b50f43c11bc076c401e0779d224e33f6fc", + "0x5f70e3a2714d8b4483cf3155865ba792197e957f5b3a6234e4c408bf2e55119d", + "0x9b105b0f89a05eb1ff7caed74cf9573dc55ac8bc4881529487b3700f5842de16", + "0x1753571b3cfadca4277c59aee89f607d1b1e3a6aa515d9051bafb2f0d8ce0daa", + "0x4014fff940b0950706926a19906a370ccbd652836dab678c82c539c00989201a", + "0x0423fa59ee58035a0beb9653841036101b2d5903ddeabddabf697dbc6f168e61", + "0x78f6781673d991f9138aa1f5142214232d6e3d6986acb6cc7fb000e1a055f425", + "0x21b8a1f6733b5762499bf2de90c9ef06af1c6c8b3ddb3a04cce949caad723197", + "0x83847957e909153312b5bd9a1a37db0bd6c72a417024a69df3e18512973a18b4", + "0x948addf423afd0c813647cfe32725bc55773167d5065539e6a3b50e6ebbdab38", + "0x0b0485d1bec07504a2e5e3a89addd6f25d497cd37a0c04bc38355f8bdb01cd48", + "0x31be8bda5143d39ea2655e9eca6a294791ca7854a829904d8574bedc5057ddc4", + "0x16a0d2d657fadce0d81264320e42e504f4d39b931dff9888f861f3cc78753f99", + "0xb43786061420c5231bf1ff638cb210f89bf4cd2d3e8bafbf34f497c9a298a13b", + "0x1f5986cbd7107d2a3cbc1826ec6908d976addbf9ae78f647c1d159cd5397e1bd", + "0xa883ccdbfd91fad436be7a4e2e74b7796c0aadfe03b7eea036d492eaf74a1a6f", + "0x5bc9eb77bbbf589db48bca436360d5fc1d74b9195237f11946349951f2a9f7f6", + "0xb6bc86de74a887a5dceb012d58c62399897141cbcc51bad9cb882f53991f499c", + "0xa6c3260e7c2dd13f26cf22bf4cd667688142ff7a3511ec895bc8f92ebfa694b6", + "0xb97da27e17d26608ef3607d83634d6e55736af10cc7e4744940a3e35d926c2ad", + "0x9df44067c2dc947c2f8e07ecc90ba54db11eac891569061a8a8821f8f9773694", + "0x865cc98e373800825e2b5ead6c21ac9112ff25a0dc2ab0ed61b16dc30a4a7cd7", + "0xe06a5b157570c5e010a52f332cacd4e131b7aed9555a5f4b5a1c9c4606caca75", + "0x824eccb5cf079b5943c4d17771d7f77555a964a106245607cedac33b7a14922e", + "0xe86f721d7a3b52524057862547fc72de58d88728868f395887057153bccaa566", + "0x3344e76d79f019459188344fb1744c93565c7a35799621d7f4505f5b6119ac82", + "0x401b3589bdd1b0407854565329e3f22251657912e27e1fb2d978bf41c435c3ac", + "0xb12fd0b2567eb14a562e710a6e46eef5e280187bf1411f5573bb86ecbe05e328", + "0xe6dc27bab027cbd9fbb5d80054a3f25b576bd0b4902527a0fc6d0de0e45a3f9f", + "0x1de222f0e731001c60518fc8d2be7d7a48cc84e0570f03516c70975fdf7dc882", + "0xb8ff6563e719fc182e15bbe678cf045696711244aacc7ce4833c72d2d108b1b9", + "0x53e28ac2df219bcbbc9b90272e623d3f6ca3221e57113023064426eff0e2f4f2", + "0x8a4e0776f03819e1f35b3325f20f793d026ccae9a769d6e0f987466e00bd1ce7", + "0x2f65f20089a31f79c2c0ce668991f4440b576ecf05776c1f6abea5e9b14b570f", + "0x448e124079a48f62d0d79b96d5ed1ffb86610561b10d5c4236280b01f8f1f406", + "0x419b34eca1440c847f7bff9e948c9913075d8e13c270e67f64380a3f31de9bb2", + "0x2f6e4fee667acaa81ba8e51172b8329ed936d57e9756fb31f635632dbc2709b7", + "0xdd5afc79e8540fcee6a896c43887bd59c9de5d61b3d1b86539faeb41a14b251d", + "0xc707bed926a46cc451a6b05e642b6098368dbdbf14528c4c28733d5d005af516", + "0x153e850b606eb8a05eacecc04db4b560d007305e664bbfe01595cb69d26b8597", + "0x1b91cc07570c812bb329d025e85ef520132981337d7ffc3d84003f81a90bf7a7", + "0x4ca32e77a12951a95356ca348639ebc451170280d979e91b13316844f65ed42a", + "0xe49ea1998e360bd68771bd69c3cd4cf406b41ccca4386378bec66ea210c40084", + "0x01aaffbde1a672d253e0e317603c2dc1d0f752100d9e853f840bca96e57f314c", + "0x170d0befcbbaafb317c8684213a4989368332f66e889824cc4becf148f808146", + "0x56f973308edf5732a60aa3e7899ae1162c7a2c7b528c3315237e20f9125b34e0", + "0x66c54fd5f6d480cab0640e9f3ec1a4eafbafc0501528f57bb0d5c78fd03068ef", + "0xaca6c83f665c64d76fbc4858da9f264ead3b6ecdc3d7437bb800ef7240abffb9", + "0xf1d4e02e7c85a92d634d16b12dc99e1d6ec9eae3d8dfbca77e7c609e226d0ce7", + "0x094352545250e843ced1d3c6c7957e78c7d8ff80c470974778930adbe9a4ed1a", + "0x76efa93070d78b73e12eb1efa7f36d49e7944ddcc3a043b916466ee83dca52ce", + "0x1772a2970588ddb584eadf02178cdb52a98ab6ea8a4036d29e59f179d7ba0543", + "0xe4bbf2d97d65331ac9f680f864208a9074d1def3c2433458c808427e0d1d3167", + "0x8ccfb5252b22c77ea631e03d491ea76eb9b74bc02072c3749f3e9d63323b44df", + "0x9e212a9bdf4e7ac0730a0cecd0f6cc49afc7e3eca7a15d0f5f5a68f72e45363b", + "0x52e548ea6445aae3f75509782a7ab1f4f02c2a85cdd0dc928370f8c76ae8802d", + "0xb62e7d73bf76c07e1a6f822a8544b78c96a6ba4f5c9b792546d94b56ca12c8b9", + "0x595cb0e985bae9c59af151bc748a50923921a195bbec226a02157f3b2e066f5b", + "0x1c7aa6b36f402cec990bafefbdbb845fc6c185c7e08b6114a71dd388fe236d32", + "0x01ee2ff1a1e88858934a420258e9478585b059c587024e5ec0a77944821f798c", + "0x420a963a139637bffa43cb007360b9f7d305ee46b6a694b0db91db09618fc2e5", + "0x5a8e2ad20f8da35f7c885e9af93e50009929357f1f4b38a6c3073e8f58fae49e", + "0x52a405fdd84c9dd01d1da5e9d1c4ba95cb261b53bf714c651767ffa2f9e9ad81", + "0xa1a334c901a6d5adc8bac20b7df025e906f7c4cfc0996bfe2c62144691c21990", + "0xb789a00252f0b34bded3cb14ae969effcf3eb29d97b05a578c3be8a9e479c213", + "0xb9dbf7e9ddb638a515da245845bea53d07becdf3f8d1ec17de11d495624c8eab", + "0xaf566b41f5ed0c026fa8bc709533d3fa7a5c5d69b03c39971f32e14ab523fa3d", + "0x8121e0b2d9b106bb2aefd364fd6a450d88b88ee1f5e4aad7c0fcd8508653a112", + "0x8581c1be74279216b93e0a0d7272f4d6385f6f68be3eef3758d5f68b62ee7b6c", + "0x85386f009278f9a1f828404fa1bbfa02dfb9d896554f0a52678eb6ec8feadc55", + "0xf483ed167d92a0035ac65a1cfdb7906e4952f74ae3a1d86324d21f241daffcb7", + "0x3872485e2a520a350884accd990a1860e789dd0d0664ad14f50186a92c7be7be", + "0xc6c1a3301933019105f5650cabcb22bfbf221965ffcfc1329315b24ea3d77fd4", + "0xcee901330a60d212a867805ce0c28f53c6cc718f52156c9e74390d18f5df6280", + "0xa67ae793b1cd1a828a607bae418755c84dbb61adf00833d4c61a94665363284f", + "0x80d8159873b517aa6815ccd7c8ed7cfb74f84298d703a6c5a2f9d7d4d984ddde", + "0x1de5a8b915f2d9b45c97a8e134871e2effb576d05f4922b577ade8e3cd747a79", + "0x6ea17c5ece9b97dddb8b2101b923941a91e4b35e33d536ab4ff15b647579e1f5", + "0xcb78631e09bc1d79908ce1d3e0b6768c54b272a1a5f8b3b52485f98d6bba9245", + "0xd7c38f9d3ffdc626fe996218c008f5c69498a8a899c7fd1d63fbb03e1d2a073f", + "0x72cdef54267088d466244a92e4e6f10742ae5e6f7f6a615eef0da049a82068f9", + "0x60b3c490ba8c502656f9c0ed37c47283e74fe1bc7f0e9f651cbc76552a0d88eb", + "0x56bd0c66987a6f3761d677097be9440ea192c1cb0f5ec38f42789abe347e0ea9", + "0x3caac3e480f62320028f6f938ee147b4c78e88a183c464a0c9fb0df937ae30c1", + "0x7a4d2f11bddda1281aba5a160df4b814d23aef07669affe421a861fac2b4ec0f", + "0x9bb4d11299922dc309a4523959298a666ebe4063a9ee3bad1b93988ed59fb933", + "0x957323fffbaf8f938354662452115ae5acba1290f0d3f7b2a671f0359c109292", + "0x877624e31497d32e83559e67057c7a605fb888ed8e31ba68e89e02220eac7096", + "0x8456546ae97470ff6ea98daf8ae632e59b309bd3ff8e9211f7d21728620ed1e5", + "0xbacb26f574a00f466ce354e846718ffe3f3a64897d14d5ffb01afcf22f95e72b", + "0x0228743a6e543004c6617bf2c9a7eba1f92ebd0072fb0383cb2700c3aed38ba0", + "0x04f093f0f93c594549436860058371fb44e8daf78d6e5f563ba63a46b61ddbf0", + "0x0ba17c1ec93429ceaff08eb81195c9844821b64f2b5363926c2a6662f83fb930", + "0xd71605d8446878c677f146837090797e888416cfc9dc4e79ab11776cc6639d3f", + "0x33dde958dc5a6796138c453224d4d6e7f2ae740cceef3b52a8b669eb4b9691a1", + "0x3c39838295d1495e90e61ce59f6fcc693b31c292d02d31759719df6fe3214559", + "0x8aecc66f38644296cf0e6693863d57a243a31a4929130e22ab44cb6157b1af41", + "0xdf7153a7eab9521f2b37124067166c72de8f342249ac0e0f5350bd32f1251053", + "0xa498840b58897cf3bed3981b94c86d85536dfebbc437d276031ebd9352e171eb", + "0xb1df15a081042ab665458223a0449ffc71a10f85f3d977beb20380958fd92262", + "0x15d3bdbdee2a61b01d7a6b72a5482f6714358eedf4bece7bb8458e100caf8fba", + "0x0c96b7a0ea09c3ef758424ffb93654ce1520571e32e1f83aecbeded2388c3a7a", + "0xb4a3a8023266d141ecd7c8a7ca5282a825410b263bc11c7d6cab0587c9b5446e", + "0xf38f535969d9592416d8329932b3a571c6eacf1763de10fb7b309d3078b9b8d4", + "0x5a1e7b1c3b3943158341ce6d7f9f74ae481975250d89ae4d69b2fcd4c092eb4e", + "0xdad31e707d352f6cca78840f402f2ac9292094b51f55048abf0d2badfeff5463", + "0x097e290170068e014ceda3dd47b28ede57ff7f916940294a13c9d4aa2dc98aad", + "0x22e2dcedb6bb7f8ace1e43facaa502daa7513e523be98daf82163d2a76a1e0be", + "0x7ef2b211ab710137e3e8c78b72744bf9de81c2adde007aef6e9ce92a05e7a2c5", + "0x49b427805fc5186f31fdd1df9d4c3f51962ab74e15229e813072ec481c18c717", + "0xe60f6caa09fa803d97613d58762e4ff7f22f47d5c30b9d0116cdc6a357de4464", + "0xab3507b37ee92f026c72cc1559331630bc1c7335b374e4418d0d02687df1a9dd", + "0x50825ae74319c9adebc8909ed7fc461702db8230c59975e8add09ad5e7a647ab", + "0x0ee8e9c1d8a527a42fb8c2c8e9e51faf727cffc23ee22b5a95828f2790e87a29", + "0x675c21c290ddb40bec0302f36fbcd2d1832717a4bc05d113c6118a62bc8f9aca", + "0x580bafab24f673317b533148d7226d485e211eaa3d6e2be2529a83ca842b58a7", + "0x540e474776cae597af24c147dc1ae0f70a6233e98cf5c3ce31f38b830b75c99a", + "0x36eaf9f286e0f356eaaf8d81f71cc52c81d9ebc838c3b4859009f8567a224d16", + "0x0e2cbbb40954be047d02b1450a3dbd2350506448425dc25fd5faf3a66ee8f5c4", + "0x7eb0390cfe4c4eb120bbe693e87adc8ecab51d5fd8ce8f911c8ff07fad8cbe20", + "0xbf77589f5c2ebb465b8d7936f6260a18a243f59bd87390ee22cf579f6f020285", + "0x695b96bb28693f6928777591ef64146466d27521280a295936a52ec60707c565", + "0x22a0d018cbd4274caa8b9e7fb132e0a7ed787874046ca683a7d81d1c7c8b8f15", + "0x84092b122bb35e5ad85407b4b55f33707b86e0238c7970a8583f3c44308ed1d9", + "0xea346067ca67255235f9cae949f06e4b6c93846a7abc7c8c8cd786e9c4b3e4bc", + "0xa6df0716b125dc696b5d0e520cb49c1c089397c754efc146792e95bc58cc7159", + "0x7377b5d3953029fc597fb10bb6479ee34133d38f08783fbb61c7d070f34ea66f", + "0x7d79b00ffb976a10cd24476a394c8ed22f93837c51a58a3ddc7418153a5a8ea1", + "0x01e55182e80dff26cc3e06bb736b4a63745bde8ae28c604fa7fb97d99de5f416", + "0x062a2d5a207f8d540764d09648afecbf5033b13aec239f722b9033a762acf18b", + "0x48be60a3221d98b4d62f0b89d3bef74c70878dd65c6f79b34c2c36d0ddaa1da0", + "0x41e11f33543cf045c1a99419379ea31523d153bdf664549286b16207b9648c85", + "0xeef4d30b4700813414763a199e7cc6ab0faec65ef8b514faa01c6aa520c76334", + "0xea7cfe990422663417715e7859fc935ca47f47c943a1254044b6bc5934c94bc8", + "0xbbd3c834e5403b98a0ca346c915a23310f3d58880786628bc6cfbe05ba29c3c5", + "0xe216379f385bc9995ae0f37f1409a78d475c56b8aeb4ee434326724ec20124f7", + "0xdd328a1eee19d09b6fef06e252f8ad0ae328fbf900ef745f5950896803a3899d", + "0xa16fde34b0d743919feb0781eca0c525a499d279119af823cb3a8817000335db", + "0x7a28d108c59b83b12c85cd9aabc1d1d994a9a0329ae7b64a32aadcd61ebe50e3", + "0xb28bc82fceae74312eb837a805f0a8a01c0f669b99bb03fde31c4d58bedff89b", + "0x1b0d8f37d349781e846900b51a90c828aa384afe9b8ee1f88aeb8dba4b3168f2", + "0xbfd0301ff964c286c3331a30e09e0916da6f484e9c9596dbf1cae3cc902dbf9e", + "0xbb8254cb9ef6b485b8fb6caeafe45f920affc30f6b9d671e9a454530536f4fef", + "0xcad2317cf63dfa7147ded5c7e15f5f72e78f42d635e638f1ece6bc722ca3638b", + "0xb6c6e856fd45117f54775142f2b38f31114539d8943bcbcf823f6c7650c001e4", + "0x869f1baa35684c8f67a5bc99b294187852e6c85243a2f36481d0891d8b043020", + "0x14c6ccf145ee40ff56e3810058d2fba9a943ffc7c7087c48a08b2451c13dc788", + "0x263c1bcb712890f155b7e256cefa4abf92fe4380f3ffc11c627d5e4e30864d18", + "0x69f4eaf655e31ad7f7a725cd415ce7e45dd4a8396ac416950d42ed33155c3487", + "0x47e8eec2c5e33c9a54fe1f9b09e7744b614fb16531c36b862aa899424be13b05", + "0x5c985de270e62c44f0b49157882e8e83641b906ce47959e337fe8423e125a2eb", + "0x4e13b11e13202439bb5de5eea3bb75d2d7bf90f91411163ade06161a9cf424db", + "0x583a8fa159bb74fa175d72f4e1705e9a3b8ffe26ec5ad6e720444b99288f1213", + "0x903d2a746a98dfe2ee2632606d57a9b0fa6d8ccd895bb18c2245fd91f8a43676", + "0xa35a51330316012d81ec7249e3f2b0c9d7fcbb99dd98c62fe880d0a152587f51", + "0x33818a7beb91730c7b359b5e23f68a27b429967ea646d1ea99c314353f644218", + "0x183650af1e0b67f0e7acb59f8c72cc0e60acc13896184db2a3e4613f65b70a8b", + "0x857ff2974bef960e520937481c2047938a718cea0b709282ed4c2b0dbe2ef8fa", + "0x95a367ecb9a401e98a4f66f964fb0ece783da86536410a2082c5dbb3fc865799", + "0x56c606a736ac8268aedadd330d2681e7c7919af0fe855f6c1c3d5c837aa92338", + "0x5c97f7abf30c6d0d4c23e762c026b94a6052a444df4ed942e91975419f68a3a4", + "0x0b571de27d2022158a3128ae44d23a8136e7dd2dee74421aa4d6ed15ee1090a0", + "0xa17f6bc934a2f3c33cea594fee8c96c1290feec934316ebbbd9efab4937bf9f9", + "0x9ff57d70f27aad7281841e76435285fd27f10dad256b3f5cabde4ddc51b70eff", + "0xafa3071a847215b3ccdf51954aa7cb3dd2e6e2a39800042fc42009da705508b2", + "0x5e3bea33e4ac6f7c50a077d19571b1796e403549b1ce7b15e09905a0cc5a4acf", + "0x0dc7ba994e632ab95f3ecb7848312798810cf761d1c776181882d17fd6dda075", + "0xb4f7158679dad9f7370a2f64fbe617a40092849d17453b4f50a93ca8c6885844", + "0x094564b00f53c6f27c121fd8adfe1685b258b259e585a67b57c85efb804c57b2", + "0x9cd21a4249ba3fccffad550cdb8409dc12d8b74a7192874b6bafe2363886f318", + "0xbb22e0dad55cb315c564c038686419d40ef7f13af2143a28455bf445f6e10393", + "0x2a71d5e00821178c2cd39e7501e07da5cca6680eb7cdbe996f52dccafadb3735", + "0x9619406093b121e044a5b403bb1713ae160aeb52ad441f82dc6c63e4b323b969", + "0x3b8bd1d82c6d67ae707e19b889f1cb1f7bba912f12ae4284298f3a70c3644c79", + "0xd7a70c50d47d48785b299dbea01bf03ef18b8495de3c35cb265bc8f3295c4e15", + "0x8802ecce8dd6b6190af8ac79aafda3479c29f548d65e5798c0ca51a529b19108", + "0x4b630e1df52ec5fd650f4a4e76b3eeddda39e1e9eab996f6d3f02eefdf690990", + "0x0bfbff60fcf7f411d469f7f6f0a58ca305fd84eb529ee3ac73c00174793d723e", + "0x535f78b5f3a99a1c498e2c19dc1acb0fbbaba8972ba1d7d66936c28ab3667ebe", + "0x06ba92d8129db98fec1b75f9489a394022854f22f2e9b9450b187a6fc0d94a86", + "0xb7ae275ba10f80fb618a2cf949d5ad2e3ae24eb2eb37dcf1ec8c8b148d3ba27f", + "0xb275579bcf2584d9794dd3fc7f999902b13d33a9095e1980d506678e9c263de1", + "0x843ccd52a81e33d03ad2702b4ef68f07ca0419d4495df848bff16d4965689e48", + "0xde8b779ca7250f0eb867d5abdffd1d28c72a5a884d794383fc93ca40e5bf6276", + "0x6b789a2befccb8788941c9b006e496b7f1b03dbb8e530ba339db0247a78a2850", + "0xfccd4dca80bc52f9418f26b0528690255e320055327a34b50caf088235d2f660", + "0x18479ebfbe86c1e94cd05c70cb6cace6443bd9fdac7e01e9c9535a9e85141f2f", + "0x5350c8f3296441db954a261238c88a3a0c51ab418a234d566985f2809e211148", + "0xa5636614135361d03a381ba9f6168e2fd0bd2c1105f9b4e347c414df8759dea3", + "0xe7bb69e600992e6bd41c88a714f50f450153f1a05d0ddb4213a3fc4ba1f48c3f", + "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", + "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", + ], + compressed_lamport_sk: + "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", + child_sk: + "7419543105316279183937430842449358701327973165530407166294956473095303972104" + } + } +} From ecf8efa9a985aac1e5b7e71cf03b28ae902b3b13 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 15:19:20 +1000 Subject: [PATCH 085/118] More progress --- eth2/utils/eth2_keystore/src/path.rs | 101 ++++++++++++++++++++------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 699278fb332..acd985059eb 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -19,22 +19,15 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { - let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); - - // TODO: don't do all this extra work with extraction. - let lamport_bytes = lamport - .iter_chunks() - .map(|a| a.to_vec()) - .flatten() - .collect::>(); - // TODO: justify 48. - mod_r(&lamport_bytes[0..48]) + let okm = &hkdf_expand_basic( + hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), + 48, + ); + mod_r(&okm) } fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(bytes.len(), HASH_SIZE); - let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); @@ -42,7 +35,7 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { debug_assert!(x.len() <= HASH_SIZE); let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output[HASH_SIZE - x.len()..].copy_from_slice(&x); output } @@ -115,6 +108,37 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } +// TODO: zeroize. +fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; HASH_SIZE]; + + let mut hasher = Sha256::new(); + hasher.input(prk); + hasher.input(&[]); // TODO: remove this? + hasher.input(&[1]); + hasher.result(&mut okm[..]); + + let mut i = 0; + while okm.len() < l { + i += 1; + + let mut hasher = Sha256::new(); + hasher.input(prk); + if i == 1 { + hasher.input(&[]); // TODO: remove this line? + } else { + hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? + } + hasher.input(&[i]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.extend_from_slice(&mut digest); + } + + okm[0..l].to_vec() +} + fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -131,29 +155,58 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec>, + lamport_1: Vec>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + #[test] + fn eip2333_intermediate_vector() { + let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); + assert_eq!( + &master_sk[..], + &vectors.master_sk[..], + "master_sk should match" + ); + } + struct RawTestVector { seed: &'static str, master_sk: &'static str, child_index: u32, lamport_0: Vec<&'static str>, lamport_1: Vec<&'static str>, - compressed_lamport_sk: &'static str, + compressed_lamport_pk: &'static str, child_sk: &'static str, } - struct TestVector { - seed: Vec, - master_sk: Vec, - child_index: u32, - lamport_0: Vec<[u8; HASH_SIZE]>, - lamport_1: Vec<[u8; HASH_SIZE]>, - compressed_lamport_pk: Vec, - child_sk: Vec, + fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") + } + + fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() } impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { - todo!() + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + lamport_0: raw.lamport_0.into_iter().map(hex_to_vec).collect(), + lamport_1: raw.lamport_1.into_iter().map(hex_to_vec).collect(), + compressed_lamport_pk: hex_to_vec(raw.compressed_lamport_pk), + child_sk: int_to_vec(raw.child_sk), + } } } @@ -677,7 +730,7 @@ mod test { "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", ], - compressed_lamport_sk: + compressed_lamport_pk: "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" From 8b8fbfc7ccea0ed076b30587ead4b26b99ad3ade Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:29:35 +1000 Subject: [PATCH 086/118] Passing intermediate test vectors --- .../eth2_keystore/src/lamport_secret_key.rs | 49 ++++++++ eth2/utils/eth2_keystore/src/path.rs | 106 ++++++++++-------- 2 files changed, 108 insertions(+), 47 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/lamport_secret_key.rs diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs new file mode 100644 index 00000000000..01329d7ede9 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs @@ -0,0 +1,49 @@ +use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use std::iter::Iterator; +use zeroize::Zeroize; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); + +impl LamportSecretKey { + /// Instantiates `Self` with all chunks set to zero. + pub fn zero() -> Self { + Self(vec![[0; HASH_SIZE]; LAMPORT_ARRAY_SIZE as usize]) + } + + /// Instantiates `Self` from a flat buffer of `HASH_SIZE * LAMPORT_ARRAY_SIZE` bytes. + /// + /// ## Panics + /// + /// If an incorrect number of bytes is supplied. + pub fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!( + bytes.len(), + HASH_SIZE * LAMPORT_ARRAY_SIZE as usize, + "incorrect byte length" + ); + + let mut this = Self::zero(); + + for i in 0..LAMPORT_ARRAY_SIZE { + let iu = i as usize; + this.get_mut_chunk(i) + .copy_from_slice(&bytes[iu * HASH_SIZE..(iu + 1) * HASH_SIZE]) + } + + this + } + + pub fn get_chunk(&self, i: u8) -> &[u8] { + &self.0[i as usize] + } + + pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { + &mut self.0[i as usize] + } + + pub fn iter_chunks(&self) -> impl Iterator { + self.0.iter() + } +} diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index acd985059eb..db3d2f9063d 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, sha2::Sha256}; +use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -9,6 +9,8 @@ pub const LAMPORT_ARRAY_SIZE: u8 = 255; pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; +pub const BLS_KEY_LEN: usize = 48; + fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } @@ -19,11 +21,9 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); // TODO: justify 48. - let okm = &hkdf_expand_basic( - hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), - 48, - ); + let okm = &hkdf_expand(&prk, BLS_KEY_LEN); mod_r(&okm) } @@ -44,8 +44,8 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let not_ikm = flip_bits(ikm); let lamports = [ - ikm_to_lamport_sk(ikm, &salt), - ikm_to_lamport_sk(¬_ikm, &salt), + ikm_to_lamport_sk(&salt, ikm), + ikm_to_lamport_sk(&salt, ¬_ikm), ]; let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; @@ -74,20 +74,24 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { - hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) + let prk = hkdf_extract(salt, ikm); + let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(&okm) } -fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { - let mut hasher = Sha256::new(); - hasher.input(salt); - hasher.input(ikm); - - let mut digest = vec![0; HASH_SIZE]; - hasher.result(&mut digest); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { + let mut prk = [0; HASH_SIZE]; + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); + prk +} - digest.into() +fn hkdf_expand(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; l]; + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); + okm } +/* fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { let mut okm = LamportSecretKey::zero(); @@ -107,37 +111,7 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } - -// TODO: zeroize. -fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; HASH_SIZE]; - - let mut hasher = Sha256::new(); - hasher.input(prk); - hasher.input(&[]); // TODO: remove this? - hasher.input(&[1]); - hasher.result(&mut okm[..]); - - let mut i = 0; - while okm.len() < l { - i += 1; - - let mut hasher = Sha256::new(); - hasher.input(prk); - if i == 1 { - hasher.input(&[]); // TODO: remove this line? - } else { - hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? - } - hasher.input(&[i]); - - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.extend_from_slice(&mut digest); - } - - okm[0..l].to_vec() -} +*/ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -168,12 +142,50 @@ mod test { #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); assert_eq!( &master_sk[..], &vectors.master_sk[..], "master_sk should match" ); + + let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + assert_eq!( + lamport_0 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_0, + "lamport_0 should match" + ); + + let lamport_1 = ikm_to_lamport_sk( + &vectors.child_index.to_be_bytes()[..], + &flip_bits(&master_sk), + ); + assert_eq!( + lamport_1 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_1, + "lamport_1 should match" + ); + + let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + assert_eq!( + &compressed_lamport_pk[..], + &vectors.compressed_lamport_pk[..], + "compressed_lamport_pk should match" + ); + + let child_sk = derive_child_sk(&master_sk, vectors.child_index); + assert_eq!( + &child_sk[..], + &vectors.child_sk[..], + "child_sk should match" + ); } struct RawTestVector { From 69b7143545f15da81f45bece017f63b7566ef8cc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:54:33 +1000 Subject: [PATCH 087/118] Tidy, add comments --- eth2/utils/eth2_keystore/src/path.rs | 83 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index db3d2f9063d..4060d78ff51 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -4,29 +4,53 @@ use num_bigint::BigUint; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; + /// The size of the lamport array. +/// +/// Indirectly defined in EIP-2333. pub const LAMPORT_ARRAY_SIZE: u8 = 255; +/// The order of the BLS 12-381 curve. +/// +/// Defined in EIP-2333. pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; -pub const BLS_KEY_LEN: usize = 48; +/// The `L` value used in the `hdkf_mod_r` function. +/// +/// In EIP-2333 this value is defined as: +/// +/// `ceil((1.5 * ceil(log2(r))) / 8)` +pub const MOD_R_L: usize = 48; +/// Derives the "master" BLS secret key from some `seed` bytes. +/// +/// Equivalent to `derive_master_SK` in EIP-2333. fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } +/// From the given `parent_sk`, derives a child key at index`. +/// +/// Equivalent to `derive_child_SK` in EIP-2333. fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); hkdf_mod_r(&compressed_lamport_pk) } +/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// BLS private key within the order of the BLS-381 curve. +/// +/// Equivalent to `HKDF_mod_r` in EIP-2333. fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - // TODO: justify 48. - let okm = &hkdf_expand(&prk, BLS_KEY_LEN); + let okm = &hkdf_expand(&prk, MOD_R_L); mod_r(&okm) } +/// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the +/// BLS-381 curve. +/// +/// This function is a part of the `HKDF_mod_r` function in EIP-2333. fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); @@ -39,6 +63,9 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { output } +/// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). +/// +/// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); @@ -73,48 +100,42 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { compressed_lamport_pk } +/// Generates a Lamport secret key from the `ikm` (initial key material). +/// +/// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); LamportSecretKey::from_bytes(&okm) } +/// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { let mut prk = [0; HASH_SIZE]; crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); prk } +/// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_expand(prk: &[u8], l: usize) -> Vec { let mut okm = vec![0; l]; crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); okm } -/* -fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { - let mut okm = LamportSecretKey::zero(); - - for i in 0..LAMPORT_ARRAY_SIZE { - let mut hasher = Sha256::new(); - - hasher.input(prk); - - if let Some(prev) = i.checked_sub(1) { - hasher.input(okm.get_chunk(prev)) - } - - hasher.input(&[i + 1]); - - hasher.result(okm.get_mut_chunk(i)); - } - - okm -} -*/ - +/// Flips each bit in the `input`. +/// +/// Equivalent to `flip_bits` in EIP-2333. +/// +/// ## Panics +/// +/// If `input` is not32-bytes. fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(input.len(), HASH_SIZE); + assert_eq!(input.len(), HASH_SIZE); let mut output = [0; HASH_SIZE]; @@ -129,6 +150,7 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + /// Contains the test vectors in a format that's easy for us to test against. struct TestVector { seed: Vec, master_sk: Vec, @@ -139,6 +161,9 @@ mod test { child_sk: Vec, } + /// "Test Vector with Intermediate values" from: + /// + /// https://eips.ethereum.org/EIPS/eip-2333 #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); @@ -188,6 +213,7 @@ mod test { ); } + /// Struct to deal with easy copy-paste from specification test vectors. struct RawTestVector { seed: &'static str, master_sk: &'static str, @@ -198,16 +224,20 @@ mod test { child_sk: &'static str, } + /// Converts 0x-prefixed hex to bytes. fn hex_to_vec(hex: &str) -> Vec { hex::decode(&hex[2..]).expect("should decode hex as vec") } + /// Converts an integer represented as a string to a big-endian byte array. fn int_to_vec(int_str: &str) -> Vec { BigUint::parse_bytes(int_str.as_bytes(), 10) .expect("must be able to parse int") .to_bytes_be() } + /// Converts from a format that's easy to copy-paste from the spec into a format that's easy to + /// test with. impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { TestVector { @@ -222,6 +252,7 @@ mod test { } } + /// Returns the copy-paste values from the spec. fn get_raw_vector() -> RawTestVector { RawTestVector { seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", From d689efaa195467d0731ab6140f1398c0eef26329 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:06:17 +1000 Subject: [PATCH 088/118] Add DerivedKey structs --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 51231797d6f..1032d4e597f 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -12,4 +12,5 @@ pub mod json_keystore; pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; +pub use path::MasterKey; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 4060d78ff51..08d077a87f0 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ -use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; +use crate::lamport_secret_key::LamportSecretKey; +use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -22,6 +22,27 @@ pub const R: &str = "52435875175126190479447740508185965837690552500527637822603 /// `ceil((1.5 * ceil(log2(r))) / 8)` pub const MOD_R_L: usize = 48; +/// A BLS secret key that is derived from some `seed`, or generated as a child from some other +/// `DerivedKey`. +pub struct DerivedKey([u8; HASH_SIZE]); + +impl DerivedKey { + /// Instantiates `Self` from some seed of any length. + pub fn from_seed(seed: &[u8]) -> Self { + Self(derive_master_sk(seed)) + } + + /// Derives a child key from the secret `Self` at some `index`. + pub fn derive_child(&self, index: u32) -> DerivedKey { + Self(derive_child_sk(&self.0, index)) + } + + /// Returns the secret BLS key in `self`. + pub fn secret(&self) -> &[u8] { + &self.0 + } +} + /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. From 48ba41f8c37ff2a0a262d4da50dadb55d7292f48 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:21:27 +1000 Subject: [PATCH 089/118] Move key derivation into own crate --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + eth2/utils/eth2_key_derivation/Cargo.toml | 15 +++++++++++++++ .../src/lamport_secret_key.rs | 4 ---- eth2/utils/eth2_key_derivation/src/lib.rs | 7 +++++++ .../src/path.rs | 0 eth2/utils/eth2_keystore/src/lib.rs | 3 --- 7 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 eth2/utils/eth2_key_derivation/Cargo.toml rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/lamport_secret_key.rs (93%) create mode 100644 eth2/utils/eth2_key_derivation/src/lib.rs rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b32c2057a7d..d19862eb5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,16 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth2_keystore" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 593bcd255a6..b2ee8e1120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/deposit_contract", "eth2/utils/eth2_config", "eth2/utils/eth2_interop_keypairs", + "eth2/utils/eth2_key_derivation", "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", "eth2/utils/logging", diff --git a/eth2/utils/eth2_key_derivation/Cargo.toml b/eth2/utils/eth2_key_derivation/Cargo.toml new file mode 100644 index 00000000000..61bfb5b8166 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "eth2_key_derivation" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "0.2.36" +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +num-bigint = "0.2.6" + +[dev-dependencies] +hex = "0.3" diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs similarity index 93% rename from eth2/utils/eth2_keystore/src/lamport_secret_key.rs rename to eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 01329d7ede9..e3a8a93990d 100644 --- a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -35,10 +35,6 @@ impl LamportSecretKey { this } - pub fn get_chunk(&self, i: u8) -> &[u8] { - &self.0[i as usize] - } - pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs new file mode 100644 index 00000000000..d25469000e9 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -0,0 +1,7 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + +mod lamport_secret_key; +mod path; + +pub use path::DerivedKey; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_key_derivation/src/path.rs similarity index 100% rename from eth2/utils/eth2_keystore/src/path.rs rename to eth2/utils/eth2_key_derivation/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 1032d4e597f..56174762158 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,14 +3,11 @@ mod derived_key; mod keystore; -mod lamport_secret_key; mod password; -mod path; mod plain_text; pub mod json_keystore; pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; -pub use path::MasterKey; pub use uuid::Uuid; From 1c6f5904606cbbb0ab1df17380abf5553fb07de7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 10:03:27 +1000 Subject: [PATCH 090/118] Add zeroize structs --- .../src/{path.rs => derived_key.rs} | 90 ++++++++++--------- .../src/lamport_secret_key.rs | 2 +- eth2/utils/eth2_key_derivation/src/lib.rs | 10 ++- .../eth2_key_derivation/src/secret_bytes.rs | 23 +++++ .../eth2_key_derivation/src/secret_hash.rs | 22 +++++ 5 files changed, 98 insertions(+), 49 deletions(-) rename eth2/utils/eth2_key_derivation/src/{path.rs => derived_key.rs} (95%) create mode 100644 eth2/utils/eth2_key_derivation/src/secret_bytes.rs create mode 100644 eth2/utils/eth2_key_derivation/src/secret_hash.rs diff --git a/eth2/utils/eth2_key_derivation/src/path.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs similarity index 95% rename from eth2/utils/eth2_key_derivation/src/path.rs rename to eth2/utils/eth2_key_derivation/src/derived_key.rs index 08d077a87f0..cd1a5b65e2c 100644 --- a/eth2/utils/eth2_key_derivation/src/path.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -1,4 +1,6 @@ -use crate::lamport_secret_key::LamportSecretKey; +use crate::{ + lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, secret_hash::SecretHash, +}; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; @@ -24,7 +26,7 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. -pub struct DerivedKey([u8; HASH_SIZE]); +pub struct DerivedKey(SecretHash); impl DerivedKey { /// Instantiates `Self` from some seed of any length. @@ -34,69 +36,70 @@ impl DerivedKey { /// Derives a child key from the secret `Self` at some `index`. pub fn derive_child(&self, index: u32) -> DerivedKey { - Self(derive_child_sk(&self.0, index)) + Self(derive_child_sk(self.0.as_bytes(), index)) } /// Returns the secret BLS key in `self`. pub fn secret(&self) -> &[u8] { - &self.0 + self.0.as_bytes() } } /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. -fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { +fn derive_master_sk(seed: &[u8]) -> SecretHash { hkdf_mod_r(seed) } /// From the given `parent_sk`, derives a child key at index`. /// /// Equivalent to `derive_child_SK` in EIP-2333. -fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); - hkdf_mod_r(&compressed_lamport_pk) + hkdf_mod_r(compressed_lamport_pk.as_bytes()) } -/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// From the `ikm` (initial key material), performs a HKDF-Extract and HKDF-Expand to generate a /// BLS private key within the order of the BLS-381 curve. /// /// Equivalent to `HKDF_mod_r` in EIP-2333. -fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { +fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - let okm = &hkdf_expand(&prk, MOD_R_L); - mod_r(&okm) + let okm = &hkdf_expand(prk.as_bytes(), MOD_R_L); + mod_r(okm.as_bytes()) } /// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the /// BLS-381 curve. /// /// This function is a part of the `HKDF_mod_r` function in EIP-2333. -fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { +fn mod_r(bytes: &[u8]) -> SecretHash { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); debug_assert!(x.len() <= HASH_SIZE); - let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - x.len()..].copy_from_slice(&x); + let mut output = SecretHash::zero(); + output.as_mut_bytes()[HASH_SIZE - x.len()..].copy_from_slice(&x); output } /// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). /// /// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. -fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); let lamports = [ ikm_to_lamport_sk(&salt, ikm), - ikm_to_lamport_sk(&salt, ¬_ikm), + ikm_to_lamport_sk(&salt, not_ikm.as_bytes()), ]; - let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + let mut lamport_pk = SecretBytes::zero(HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2); + let pk_bytes = lamport_pk.as_mut_bytes(); lamports .iter() @@ -104,19 +107,15 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { .flatten() .enumerate() .for_each(|(i, chunk)| { - let output_slice = lamport_pk - .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) - .expect("lamport_pk must have adequate capacity"); - let mut hasher = Sha256::new(); hasher.input(chunk); - hasher.result(output_slice); + hasher.result(&mut pk_bytes[i * HASH_SIZE..(i + 1) * HASH_SIZE]); }); - let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut compressed_lamport_pk = SecretHash::zero(); let mut hasher = Sha256::new(); - hasher.input(&lamport_pk); - hasher.result(&mut compressed_lamport_pk); + hasher.input(lamport_pk.as_bytes()); + hasher.result(compressed_lamport_pk.as_mut_bytes()); compressed_lamport_pk } @@ -126,25 +125,25 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { /// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); - let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); - LamportSecretKey::from_bytes(&okm) + let okm = hkdf_expand(prk.as_bytes(), HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(okm.as_bytes()) } /// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { - let mut prk = [0; HASH_SIZE]; - crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> SecretHash { + let mut prk = SecretHash::zero(); + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, prk.as_mut_bytes()); prk } /// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_expand(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; l]; - crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); +fn hkdf_expand(prk: &[u8], l: usize) -> SecretBytes { + let mut okm = SecretBytes::zero(l); + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], okm.as_mut_bytes()); okm } @@ -154,14 +153,15 @@ fn hkdf_expand(prk: &[u8], l: usize) -> Vec { /// /// ## Panics /// -/// If `input` is not32-bytes. -fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { +/// If `input` is not 32-bytes. +fn flip_bits(input: &[u8]) -> SecretHash { assert_eq!(input.len(), HASH_SIZE); - let mut output = [0; HASH_SIZE]; + let mut output = SecretHash::zero(); + let output_bytes = output.as_mut_bytes(); for (i, byte) in input.iter().enumerate() { - output[i] = !byte + output_bytes[i] = !byte } output @@ -191,12 +191,13 @@ mod test { let master_sk = derive_master_sk(&vectors.seed); assert_eq!( - &master_sk[..], + master_sk.as_bytes(), &vectors.master_sk[..], "master_sk should match" ); - let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + let lamport_0 = + ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], master_sk.as_bytes()); assert_eq!( lamport_0 .iter_chunks() @@ -208,7 +209,7 @@ mod test { let lamport_1 = ikm_to_lamport_sk( &vectors.child_index.to_be_bytes()[..], - &flip_bits(&master_sk), + flip_bits(master_sk.as_bytes()).as_bytes(), ); assert_eq!( lamport_1 @@ -219,16 +220,17 @@ mod test { "lamport_1 should match" ); - let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + let compressed_lamport_pk = + parent_sk_to_lamport_pk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &compressed_lamport_pk[..], + compressed_lamport_pk.as_bytes(), &vectors.compressed_lamport_pk[..], "compressed_lamport_pk should match" ); - let child_sk = derive_child_sk(&master_sk, vectors.child_index); + let child_sk = derive_child_sk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &child_sk[..], + child_sk.as_bytes(), &vectors.child_sk[..], "child_sk should match" ); diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index e3a8a93990d..2c64601e505 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -1,4 +1,4 @@ -use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs index d25469000e9..f6ed44c4df5 100644 --- a/eth2/utils/eth2_key_derivation/src/lib.rs +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -1,7 +1,9 @@ -//! Provides a JSON keystore for a BLS keypair, as specified by -//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +//! Provides path-based hierarchical BLS key derivation, as specified by +//! [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +mod derived_key; mod lamport_secret_key; -mod path; +mod secret_bytes; +mod secret_hash; -pub use path::DerivedKey; +pub use derived_key::DerivedKey; diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs new file mode 100644 index 00000000000..c4eee4b8f52 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -0,0 +1,23 @@ +use zeroize::Zeroize; + +/// Provides a wrapper around a `Vec` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + /// Instantiates `Self` with an all-zeros byte array of length `len`. + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs new file mode 100644 index 00000000000..9c1bbb27b8b --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -0,0 +1,22 @@ +use crate::derived_key::HASH_SIZE; +use zeroize::Zeroize; + +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretHash([u8; HASH_SIZE]); + +impl SecretHash { + /// Instantiates `Self` with all zeros. + pub fn zero() -> Self { + Self([0; HASH_SIZE]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} From 87ed9e3eda6c47efec73feeb73c908ccacccda73 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:15 +1000 Subject: [PATCH 091/118] Return error for empty seed --- .../eth2_key_derivation/src/derived_key.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index cd1a5b65e2c..ea1eb1fe0b1 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -29,13 +29,23 @@ pub const MOD_R_L: usize = 48; pub struct DerivedKey(SecretHash); impl DerivedKey { - /// Instantiates `Self` from some seed of any length. - pub fn from_seed(seed: &[u8]) -> Self { - Self(derive_master_sk(seed)) + /// Instantiates `Self` from some secret seed bytes. + /// + /// The key is generated deterministically; the same `seed` will always return the same `Self`. + /// + /// ## Errors + /// + /// Returns `Err(())` if `seed.is_empty()`, otherwise always returns `Ok(self)`. + pub fn from_seed(seed: &[u8]) -> Result { + if seed.is_empty() { + Err(()) + } else { + Ok(Self(derive_master_sk(seed))) + } } /// Derives a child key from the secret `Self` at some `index`. - pub fn derive_child(&self, index: u32) -> DerivedKey { + pub fn child(&self, index: u32) -> DerivedKey { Self(derive_child_sk(self.0.as_bytes(), index)) } From 872a93f1a734fa36efb0d6296e2c6ac8030f5bf2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:33 +1000 Subject: [PATCH 092/118] Add tests --- .../tests/eip2333_vectors.rs | 102 ++++++++++++++++++ eth2/utils/eth2_key_derivation/tests/tests.rs | 28 +++++ 2 files changed, 130 insertions(+) create mode 100644 eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs create mode 100644 eth2/utils/eth2_key_derivation/tests/tests.rs diff --git a/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs new file mode 100644 index 00000000000..42a3728b291 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs @@ -0,0 +1,102 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; +use num_bigint::BigUint; + +/// Contains the test vectors in a format that's easy for us to test against. +struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + child_sk: Vec, +} + +/// Struct to deal with easy copy-paste from specification test vectors. +struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + child_sk: &'static str, +} + +/// Converts from a format that's easy to copy-paste from the spec into a format that's easy to +/// test with. +impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + child_sk: int_to_vec(raw.child_sk), + } + } +} + +/// Converts 0x-prefixed hex to bytes. +fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") +} + +/// Converts an integer represented as a string to a big-endian byte array. +fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() +} + +/// Asserts that our code matches the given test vector. +fn assert_vector_passes(raw: RawTestVector) { + let vector: TestVector = raw.into(); + + let master = DerivedKey::from_seed(&vector.seed).unwrap(); + assert_eq!(master.secret(), &vector.master_sk[..], "master"); + + let child = master.child(vector.child_index); + assert_eq!(child.secret(), &vector.child_sk[..], "child"); +} + +/* + * The following test vectors are obtained from: + * + * https://eips.ethereum.org/EIPS/eip-2333 + */ + +#[test] +fn eip2333_test_case_0() { + assert_vector_passes(RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" + }) +} + +#[test] +fn eip2333_test_case_1() { + assert_vector_passes(RawTestVector { + seed: "0x3141592653589793238462643383279502884197169399375105820974944592", + master_sk: "46029459550803682895343812821003080589696405386150182061394330539196052371668", + child_index: 3141592653, + child_sk: "43469287647733616183478983885105537266268532274998688773496918571876759327260", + }) +} + +#[test] +fn eip2333_test_case_2() { + assert_vector_passes(RawTestVector { + seed: "0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00", + master_sk: "45379166311535261329029945990467475187325618028073620882733843918126031931161", + child_index: 4294967295, + child_sk: "46475244006136701976831062271444482037125148379128114617927607151318277762946", + }) +} + +#[test] +fn eip2333_test_case_3() { + assert_vector_passes(RawTestVector { + seed: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + master_sk: "31740500954810567003972734830331791822878290325762596213711963944729383643688", + child_index: 42, + child_sk: "51041472511529980987749393477251359993058329222191894694692317000136653813011", + }) +} diff --git a/eth2/utils/eth2_key_derivation/tests/tests.rs b/eth2/utils/eth2_key_derivation/tests/tests.rs new file mode 100644 index 00000000000..b18a7b0e267 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/tests.rs @@ -0,0 +1,28 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; + +#[test] +fn empty_seed() { + assert!( + DerivedKey::from_seed(&[]).is_err(), + "empty seed should fail" + ); +} + +#[test] +fn deterministic() { + assert_eq!( + DerivedKey::from_seed(&[42]).unwrap().secret(), + DerivedKey::from_seed(&[42]).unwrap().secret() + ); +} + +#[test] +fn children_deterministic() { + let master = DerivedKey::from_seed(&[42]).unwrap(); + assert_eq!( + master.child(u32::max_value()).secret(), + master.child(u32::max_value()).secret(), + ) +} From 3b3c5cf68f167b8e1a17f5506c30a3fdf199280a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:55:30 +1000 Subject: [PATCH 093/118] Tidy --- eth2/utils/eth2_key_derivation/src/derived_key.rs | 7 +++++++ .../eth2_key_derivation/src/lamport_secret_key.rs | 5 +++++ eth2/utils/eth2_key_derivation/src/secret_bytes.rs | 12 ++++++------ eth2/utils/eth2_key_derivation/src/secret_hash.rs | 4 +++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index ea1eb1fe0b1..b91f0bff800 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -3,6 +3,7 @@ use crate::{ }; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; +use zeroize::Zeroize; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; @@ -26,6 +27,12 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. +/// +/// Implements `Zeroize` on `Drop`. +// It's not strictly necessary that `DerivedKey` implements `Zeroize`, but it seems prudent to be a +// little over-cautious here; we don't require high-speed key generation at this stage. +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DerivedKey(SecretHash); impl DerivedKey { diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 2c64601e505..aa6dbb39323 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -2,6 +2,9 @@ use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; +/// A Lamport secret key as specified in [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +/// +/// Implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); @@ -35,10 +38,12 @@ impl LamportSecretKey { this } + /// Returns a reference to the `i`th `HASH_SIZE` chunk of `self`. pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } + /// Returns an iterator over `LAMPORT_ARRAY_SIZE` chunks of `HASH_SIZE` bytes. pub fn iter_chunks(&self) -> impl Iterator { self.0.iter() } diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs index c4eee4b8f52..5cedd05b1d3 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -1,6 +1,6 @@ use zeroize::Zeroize; -/// Provides a wrapper around a `Vec` that implements `Zeroize`. +/// Provides a wrapper around a `Vec` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretBytes(Vec); @@ -11,13 +11,13 @@ impl SecretBytes { Self(vec![0; len]) } - /// Returns a mutable reference to the underlying bytes. - pub fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } - /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } } diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs index 9c1bbb27b8b..0d9cfac1100 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_hash.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -1,7 +1,7 @@ use crate::derived_key::HASH_SIZE; use zeroize::Zeroize; -/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretHash([u8; HASH_SIZE]); @@ -12,10 +12,12 @@ impl SecretHash { Self([0; HASH_SIZE]) } + /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + /// Returns a mutable reference to the underlying bytes. pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 } From 973f1efe4b62321ce093dccfadca20a8f64690cc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:12:15 +1000 Subject: [PATCH 094/118] Add progress --- eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 15 +++++++++------ eth2/utils/eth2_wallet/src/lib.rs | 5 ++++- eth2/utils/eth2_wallet/src/wallet.rs | 19 ++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs index f7eb083bea7..18646b2b5ba 100644 --- a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -1,17 +1,20 @@ -use eth2_keystore::json_keystore::Crypto; use serde::{Deserialize, Serialize}; use serde_repr::*; +pub use eth2_keystore::json_keystore::{ + ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, Kdf, KdfModule, + Sha256Checksum, +}; pub use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct JsonWallet { - crypto: Crypto, - name: String, - nextaccount: u32, - uuid: Uuid, - version: Version, + pub crypto: Crypto, + pub name: String, + pub nextaccount: u32, + pub uuid: Uuid, + pub version: Version, } /// Version for `JsonWallet`. diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index 9adf2d1fd0f..98a2f5d04ad 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -1,2 +1,5 @@ -mod json_wallet; mod wallet; + +pub mod json_wallet; + +pub use wallet::{Error, Password, Wallet}; diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index 734b92081b5..b7bbe0742a8 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -1,21 +1,20 @@ -use crate::json_wallet::JsonWallet; -use eth2_keystore::{ - encrypt, - json_keystore::{Cipher, Kdf}, - Password, +use crate::json_wallet::{ + ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, Kdf, + KdfModule, Sha256Checksum, Version, }; +use eth2_keystore::encrypt; use uuid::Uuid; -pub use eth2_keystore::Error; +pub use eth2_keystore::{Error, Password, PlainText}; pub struct Wallet { json: JsonWallet, } impl Wallet { - pub fn encrypt( + fn encrypt( seed: &[u8], - password: Password, + password: &[u8], kdf: Kdf, cipher: Cipher, uuid: Uuid, @@ -45,8 +44,10 @@ impl Wallet { uuid, nextaccount: 0, version: Version::one(), - title, + name, }, }) } + + pub fn decrypt_seed(&self, password: &[u8]) -> Result<> } From f1698e3c40f3d117f214ca2c8160f77ad1ae54ae Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:12:37 +1000 Subject: [PATCH 095/118] Expose PlainText struct --- eth2/utils/eth2_keystore/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 56174762158..3ecd38e0d4b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -10,4 +10,5 @@ pub mod json_keystore; pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; pub use password::Password; +pub use plain_text::PlainText; pub use uuid::Uuid; From 9a7fbcc6fdd608a9ee275c3d2fe17adcddf64047 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 May 2020 19:55:58 +1000 Subject: [PATCH 096/118] First commits on path derivation --- eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 eth2/utils/eth2_keystore/src/path.rs diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 3ecd38e0d4b..99bdf60ce33 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -4,6 +4,7 @@ mod derived_key; mod keystore; mod password; +mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs new file mode 100644 index 00000000000..dcfa26c8966 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -0,0 +1,50 @@ +use crate::plain_text::PlainText; +use crypto::{digest::Digest, sha2::Sha256}; + +/// The byte size of a SHA256 hash. +const HASH_SIZE: usize = 32; +/// The digest size (in octets) of the hash function (SHA256) +const K: usize = HASH_SIZE; +/// The size of the lamport array. +const LAMPORT_ARRAY_SIZE: usize = 255; +/// The HKDF output size (in octets) +const L: usize = K * LAMPORT_ARRAY_SIZE; + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { + hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) +} + +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { + let mut hasher = Sha256::new(); + hasher.input(salt); + hasher.input(ikm); + + let mut digest = vec![0; HASH_SIZE]; + hasher.result(&mut digest); + + digest.into() +} + +fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { + let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); + + debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); + + for i in 0..LAMPORT_ARRAY_SIZE { + let mut hasher = Sha256::new(); + + hasher.input(prk); + + if let Some(prev) = okm.last() { + hasher.input(&prev[..]); + } + + hasher.input(&[i as u8]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.push(digest); + } + + okm +} From ed3ef29b0322c9a515cfbaa0754c4add09014524 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 13:33:11 +1000 Subject: [PATCH 097/118] Progress with implementation --- Cargo.lock | 1 + eth2/utils/eth2_keystore/Cargo.toml | 1 + eth2/utils/eth2_keystore/src/lib.rs | 1 + eth2/utils/eth2_keystore/src/path.rs | 674 ++++++++++++++++++++++++++- 4 files changed, 658 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d184550f7fe..b32c2057a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "bls 0.2.0", "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_keystore/Cargo.toml b/eth2/utils/eth2_keystore/Cargo.toml index 917a3f63ba5..8e83f2e759d 100644 --- a/eth2/utils/eth2_keystore/Cargo.toml +++ b/eth2/utils/eth2_keystore/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.3" bls = { path = "../bls" } eth2_ssz = { path = "../ssz" } serde_json = "1.0.41" +num-bigint = "0.2.6" [dev-dependencies] tempfile = "3.1.0" diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 99bdf60ce33..b6c20c646b3 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,6 +3,7 @@ mod derived_key; mod keystore; +mod lamport_secret_key; mod password; mod path; mod plain_text; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index dcfa26c8966..699278fb332 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,20 +1,90 @@ -use crate::plain_text::PlainText; +use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; use crypto::{digest::Digest, sha2::Sha256}; +use num_bigint::BigUint; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; -/// The digest size (in octets) of the hash function (SHA256) -const K: usize = HASH_SIZE; +pub const HASH_SIZE: usize = 32; /// The size of the lamport array. -const LAMPORT_ARRAY_SIZE: usize = 255; -/// The HKDF output size (in octets) -const L: usize = K * LAMPORT_ARRAY_SIZE; +pub const LAMPORT_ARRAY_SIZE: u8 = 255; -fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> Vec<[u8; HASH_SIZE]> { +pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + +fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { + hkdf_mod_r(seed) +} + +fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { + let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); + hkdf_mod_r(&compressed_lamport_pk) +} + +fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); + + // TODO: don't do all this extra work with extraction. + let lamport_bytes = lamport + .iter_chunks() + .map(|a| a.to_vec()) + .flatten() + .collect::>(); + + // TODO: justify 48. + mod_r(&lamport_bytes[0..48]) +} + +fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(bytes.len(), HASH_SIZE); + + let n = BigUint::from_bytes_be(bytes); + let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); + let x = (n % r).to_bytes_be(); + + debug_assert!(x.len() <= HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output +} + +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { + let salt = index.to_be_bytes(); + let not_ikm = flip_bits(ikm); + + let lamports = [ + ikm_to_lamport_sk(ikm, &salt), + ikm_to_lamport_sk(¬_ikm, &salt), + ]; + + let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + + lamports + .iter() + .map(LamportSecretKey::iter_chunks) + .flatten() + .enumerate() + .for_each(|(i, chunk)| { + let output_slice = lamport_pk + .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) + .expect("lamport_pk must have adequate capacity"); + + let mut hasher = Sha256::new(); + hasher.input(chunk); + hasher.result(output_slice); + }); + + let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut hasher = Sha256::new(); + hasher.input(&lamport_pk); + hasher.result(&mut compressed_lamport_pk); + + compressed_lamport_pk +} + +fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) } -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { +fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { let mut hasher = Sha256::new(); hasher.input(salt); hasher.input(ikm); @@ -25,26 +95,592 @@ fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> PlainText { digest.into() } -fn hkdf_expand(prk: &[u8]) -> Vec<[u8; HASH_SIZE]> { - let mut okm: Vec<[u8; HASH_SIZE]> = Vec::with_capacity(LAMPORT_ARRAY_SIZE); - - debug_assert!(LAMPORT_ARRAY_SIZE <= u8::max_value() as usize); +fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { + let mut okm = LamportSecretKey::zero(); for i in 0..LAMPORT_ARRAY_SIZE { let mut hasher = Sha256::new(); hasher.input(prk); - if let Some(prev) = okm.last() { - hasher.input(&prev[..]); + if let Some(prev) = i.checked_sub(1) { + hasher.input(okm.get_chunk(prev)) } - hasher.input(&[i as u8]); + hasher.input(&[i + 1]); - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.push(digest); + hasher.result(okm.get_mut_chunk(i)); } okm } + +fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { + debug_assert_eq!(input.len(), HASH_SIZE); + + let mut output = [0; HASH_SIZE]; + + for (i, byte) in input.iter().enumerate() { + output[i] = !byte + } + + output +} + +#[cfg(test)] +mod test { + use super::*; + + struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + lamport_0: Vec<&'static str>, + lamport_1: Vec<&'static str>, + compressed_lamport_sk: &'static str, + child_sk: &'static str, + } + + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec<[u8; HASH_SIZE]>, + lamport_1: Vec<[u8; HASH_SIZE]>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + todo!() + } + } + + fn get_raw_vector() -> RawTestVector { + RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: + "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + lamport_0: vec![ + "0x7b4a587eac94d7f56843e718a04965d4832ef826419b4001a3ad0ba77eb44a3b", + "0x90f45a712112122429412921ece5c30eb2a6daf739dc9034fc79424daeb5eff6", + "0xd061c2799de00b2be90eb1cc295f4c31e22d4b45c59a9b9b2554379bea7783cb", + "0x3ad17e4cda2913b5180557fbe7db04b5ba440ce8bb035ae27878d66fbfa50d2c", + "0xf5b954490933ad47f8bf612d4a4f329b3aa8914b1b83d59e15e271e2a087e002", + "0x95d68d505bf4ff3e5149bc5499cf4b2f00686c674a29a8d903f70e569557d867", + "0x1b59c76d9bb2170b220a87833582ede5970d4a336d91c99a812825afe963e056", + "0x4310ff73cfbbf7b81c39ecbf1412da33e9388c1a95d71a75e51fe12256551ceb", + "0xee696343f823e5716e16747f3bbae2fc6de233fe10eea8e45b4579018da0874f", + "0xae12a437aaa7ae59f7d8328944b6a2b973a43565c55d5807dc2faf223a33aa73", + "0x2a3ae0b47f145bab629452661ff7741f111272e33ec571030d0eb222e1ed1390", + "0x1a3ea396e8cbd1d97733ef4753d6840b42c0795d2d693f18e6f0e7b3fff2beb2", + "0x472429d0643c888bfdfe6e6ccfdeee6d345d60c6710859ac29fc289fd3656347", + "0xa32d4d955949b8bed0eb20f586d8fd516d6ddec84fbbc36998d692633c349822", + "0xe5ac8ac5ee1d40e53a7abf36e8269d5d5fce450a87feae8e59f432a44bcc7666", + "0xddf9e497ed78032fbd72d9b8abd5204d81c3475f29afa44cdf1ded8ea72dd1dc", + "0x945c62e88fb1e5f3c15ff57cd5eb1586ee93ec5ec80154c5a9c50241c5adae0a", + "0xc8868b50fc8423c96b7efa1ede4d3203a6b835dbeb6b2ababc58397e6b31d9dd", + "0x66de9bd86b50e2b6a755310520af655759c1753bff34b79a5cd63d6811fc8c65", + "0x5b13786c6068df7735343e5591393bea8aee92ac5826d6132bf4f5ebf1098776", + "0xa2038fc7d8e3cb2eda2bd303cfa76a9e5d8b88293918bec8b2fc03be75684f14", + "0x47a13f6b2308a50eded830fdee7c504bf49d1fe6a95e337b0825d0d77a520129", + "0xb534cdddcf1aa1c6b4cbba46d1db31b766d958e0a0306450bc031d1e3ed79d97", + "0x54aa051b754c31658377f7bff00b7deaa861e74cb12e1eb84216666e19b23d69", + "0x0220d57f63435948818eb376367b113c188e37451c216380f65d1ad55f73f527", + "0xf9dd2e391565534a4db84980433bf5a56250f45fe294fce2679bcf115522c081", + "0x1166591ee2ca59b9f4e525900f085141be8879c66ef18529968babeb87c44814", + "0xf4fa2e8de39bdbeb29b64d8b440d3a6c9a6ca5bdce543877eaee93c11bd70ab8", + "0x07f466d73b93db283b3f7bfaf9c39ae296adc376ab307ef12312631d0926790e", + "0xb2ecff93acb4fa44c1dbf8464b81734a863b6d7142b02f5c008907ea4dc9aaa1", + "0xa1d9c342f6c293ac6ef8b5013cba82c4bad6ed7024d782948cb23cd490039ba1", + "0xc7d04a639ba00517ece4dbc5ef4aaf20e0ccde6e4a24c28936fabe93dec594db", + "0xe3cbb9810472d9dd1cdb5eed2f74b67ea60e973d2d2e897bd64728c9b1aa0679", + "0xe36884703413958ff2aba7a1f138a26d0ac0a371270f0169219beb00a5add5f0", + "0xe5ea300a09895b3f98de5232d92a36d5611cbcf9aaf9e7bb20cf6d1696ad1cb4", + "0xc136cda884e18175ab45148ed4f9d0d1a3c5e11ad0275058e61ae48eb151a81f", + "0x3ee1101e944c040021187e93b6e0beb1048c75fb74f3fdd67756b1c8517a311f", + "0x016964fd6fc32b9ad07a630949596715dee84d78230640368ff0929a280cf3a2", + "0xe33865fc03120b94333bb754fd097dc0f90e69ff6fd221d6aae59fcf2d762d76", + "0xe80bb3515a09ac6ecb4ec59de22701cdf954b1ae8a677fd85508c5b041f28058", + "0x3889af7cd325141ec288021ede136652a0411d20364005b9d3ca9102cb368f57", + "0x18dad0bc975cf8800addd54c7867389d3f7fe1b97d348bd8412a6cbfb75c520a", + "0x09035218686061ee91bd2ad57dc6fb6da7243b8177a153484524b2b228da5314", + "0x688fd7a97551c64eae33f91abb073a46eafbbacd5595c6bac2e57dd536acdfe2", + "0x1fc164dce565a1d0da59cc8048b334cc5eb84bf04de2399ddb847c22a7e32ab7", + "0xa2a340ba05c8a30dd1cab886a926b761758eba0e41b5c4c5dfd4a42f249655c1", + "0xc43dffe01479db836a6a1a74564b297fad0d69c6b06cf593f6db9f26b4f307d5", + "0x73cef7f3ff724a30a79e1dca74cef74954afeefa2e476c4dec65afe50c16c5c4", + "0xa54002253ab7b95cc5b664b3f08976400475cc56f170b939f6792e730ff5170b", + "0x9ade43053d41afebc002f09476dffd1b13ecbf67f810791540b92ca56d5e63e4", + "0x234e7cbfbe45b22a871db26738fa05de09213a925439d7f3e5108132e521b280", + "0x066b712417332c7cfca871fb1bb5839f0341acf9266229603a3eddbc8a93b59f", + "0xb5857acdcf636330da2cfcc99c81d9fdbd20c506a3c0e4f4f6a139d2a64f051c", + "0xe119908a150a49704b6bbba2c470cd619a0ae10dd9736e8d491890e3c8509fff", + "0xb8a5c5dbb51e6cb73cca95b4ad63ea3c7399cd16b05ab6261535495b3af2ca51", + "0x05624a1d4d2d2a31160bc48a6314bbf13eaddf56cddb0f0aa4ed3fb87f8b479f", + "0x483daceff1c3baa0ed0f3be7e534eebf5f4aed424ecd804edfbf5c56b3476b50", + "0x424d04694e7ae673707c77eb1c6d0996d250cfab6832ee3506a12e0384a3c5c9", + "0xa11fed0ed8057966bfe7136a15a814d06a516fbc9d44aeef87c509137a26190e", + "0x3694d22d1bc64658f3adbe2cc9f1716aee889066e0950e0b7a2fd576ed36bb76", + "0x49a13000a87f39f93d0ae9c3a4cfccbf440c0a75cce4c9d70dac627b6d6958b3", + "0xb3ff0cdd878d5ac1cb12e7d0b300d649fdd008800d498ae4f9fbf9510c74249a", + "0xe52a867cfb87d2fe7102d23d8d64925f7b75ca3f7d6bb763f7337352c255e0be", + "0x6513b372e4e557cca59979e48ec27620e9d7cdb238fcf4a9f19c3ba502963be0", + "0x9f69d82d4d51736902a987c8b5c30c2b25a895f2af5d2c846667ff6768bcc774", + "0x049a220dbe3340749f94643a429cb3cba3c92b561dc756a733d652d838728ab3", + "0x4fa2cd877aa115b476082b11053309f3537fa03d9158085f5f3f4bab6083e6da", + "0xed12db4069eb9f347735816afcee3fe43d4a6999fef8240b91bf4b05447d734f", + "0x3ecbe5eda469278f68548c450836a05cc500864664c7dda9b7526f084a891032", + "0x690d8f928fc61949c22e18cceaa2a446f8e1b65bd2e7af9e0a8e8284134ab3d2", + "0x99e09167a09f8261e7e8571d19148b7d7a75990d0702d9d582a2e4a96ac34f8e", + "0x6d33931693ed7c2e1d080b6a37da52c279a06cec5f534305819f7adf7db0afe3", + "0xc4b735462a9a656e28a52b1d4992ea9dea826b858971d698453a4be534d6bb70", + "0xedf92b10302dc41f8d362b360f4c2ef551d50e2ded012312c964002d2afc46d7", + "0x58f6691cca081ae5c3661dd171b87cc49c90359bb03cc0e57e503f7fcf14aefc", + "0x5d29b8b4ee295a73c4a8618927b3d14b76c7da049133a2257192b10be8c17a6a", + "0x646802fa42801e0ae24011fb4f62e87219ef1da01f7fc14bf8d6bd2d9e7c21f1", + "0x23abf45eee65cc4c1e95ccab42ad280a00bb3b14d243e2021a684075f900141e", + "0x2b1ae95c975bf9c387eae506fdb5e58afd2d198f00a21cd3fddb5855e8021e4d", + "0x0ef9f6e1c0583493d343e75f9c0c557fa6da0dc12b17a96c5757292916b72ee3", + "0x04c7fc76195c64a3285af14161077c045ff6ddbb67c0ff91b080f98eb6781e5c", + "0xba12679b97027d0e7076e6d19086c07792eaa7f78350842fbef8ddf5bcd3ecc0", + "0xcead458e6799df4d2f6cbf7f13cb3afec3441a354816e3071856ed49cbdbb1a7", + "0xbe6c56256556bb5c6727a1d9cb641d969677f56bb5ad7f8f7a7c9cfd128427b4", + "0xc80f11963ff40cb1888054b83c0463d32f737f2e7d42098e639023db0dfc84d4", + "0xac80006c1296bcfde86697efebb87fb0fddfb70dd34dd2ee4c152482af4687eb", + "0xbb7d13ce184249df4576fc3d13351e1683500e48726cd4198423f14f9094068b", + "0x1b2d9c40c55bd7362664fa46c1268e094d56c8193e3d991c08dc7a6e4ca14fa1", + "0x9bd236254d0565f5b2d24552d4b4d732de43b0adaa64ecd8be3efc6508577591", + "0x38078cefccc04e8312d79e0636e0e3157434c50a2ad4e3e87cc6584c41eec8b5", + "0xb5d15a8527ff3fa254ba61ffceb02d2570b53361894f351a9e839c0bb716857d", + "0x6763dad684bf2e914f40ae0a7ee0cdf12c97f41fc05a485d5991b4daad21a3f8", + "0xc80363c20df589333ecbe05bd5f2c19942ebc2593626dc50d00835c40fb8d005", + "0x48502b56ae93acd2794f847cbe825525d5d5f59f0f75c67aff84e5338776b3af", + "0xfd8e033493ba8af264a855a78ab07f37d936351d2879b95928909ed8df1b4f91", + "0x11f75bee9eac7356e65ebc7f004ccdc1da80807380d69143293d1421f50b1c97", + "0x903a88a3ebe84ca1c52a752b1faffa9ca1daedac9cbf1aa70942efc9beb44b79", + "0x2c0dcd68837f32a69da651045ad836b8cd6b48f2c8c5d73a3bd3bba6148d345a", + "0x0aa0f49b3476f3fdb6393f2ab601e0009586090b72ee54a525734f51598960d5", + "0xf7a789f013f702731656c562caa15b04cb7c9957376c4d80b8839167bb7fa626", + "0x4e0be1b19e305d82db3fd8affd67b0d2559da3edbfb08d19632a5cc46a90ed07", + "0x3caaccfc546d84d543eaf4f4c50c9c8fd831c12a8de56fdb9dfd04cc082882fe", + "0x894f6a01fd34f0642077e22981752011678548eb70eb55e8072c1caffc16fe02", + "0xae7eb54adaa68679348ea3537a49be669d1d61001fbab9fac259ba727dbc9a1a", + "0x291a1cbdceff957b5a65440ab67fb8672de881230fe3108a15ca487c2662c2c7", + "0x891d43b867137bf8beb9df4da2d951b5984a266a8cd74ec1593801d005f83f08", + "0xc558407f6491b37a10835e0ad7ce74f4e368aa49157a28873f7229310cb2d7fd", + "0x9ce061b0a072e1fe645f3479dac089b5bfb78cfa6cfbe5fd603bcdb504711315", + "0xa8e30d07b09275115dd96472ecf9bc316581caf307735176ca226d4cd9022925", + "0x918ee6d2efba7757266577691203f973cf4f4cac10f7d5f86acd2a797ff66583", + "0xfa31ba95e15d1635d087522f3d0da9cf7acac4ed6d0ac672654032a3c39244a6", + "0xf2952b58f015d6733af06938cd1f82fbddb3b796823bee7a3dbffa04efc117c2", + "0x46f8f742d3683de010ede528128d1181e8819f4252474f51371a177bfa518fa4", + "0x4ca1cc80094f2910cf83a9e65ad70e234690ffb9142793911ec7cf71663545b3", + "0x381965037b5725c71bfa6989d4c432f6611de8e8ec387f3cfc0dcb1a15191b73", + "0x2562b88ed3b86ba188be056805a3b7a47cb1a3f630d0e2f39647b0792ec6b7d8", + "0x565f6d14e7f22724f06d40f54465ad40d265b6de072b34a09d6e37a97a118cd8", + "0xc2982c861ad3278063b4a5f584eaf866db684cc4e712d64230fc9ee33bb4253b", + "0xfd806c91927e549d8d400ab7aa68dbe60af988fbabf228483ab0c8de7dab7eee", + "0xafae6ff16c168a3a3b5c2f1742d3f89fa4777c4bd0108f174014debf8f4d629c", + "0xaf5a4be694de5e53632be9f1a49bd582bf76002259460719197079c8c4be7e66", + "0xa8df4a4b4c5bf7a4498a11186f8bb7679137395f28e5c2179589e1c1f26504b5", + "0xce8b77c64c646bb6023f3efaed21ca2e928e21517422b124362cf8f4d9667405", + "0x62e67a8c423bc6c6c73e6cd8939c5c1b110f1a38b2ab75566988823762087693", + "0x7e778f29937daaa272d06c62d6bf3c9c0112d45a3df1689c602d828b5a315a9f", + "0xe9b5abd46c2377e602ff329050afa08afe152f4b0861db8a887be910ff1570bf", + "0xa267b1b2ccd5d96ae8a916b0316f06fafb886b3bb41286b20763a656e3ca0052", + "0xb8ed85a67a64b3453888a10dedf4705bd27719664deff0996a51bb82bc07194f", + "0x57907c3c88848f9e27bc21dd8e7b9d61de48765f64d0e943e7a6bb94cc2021ab", + "0xd2f6f1141a3b76bf9bf581d49091142944c7f9f323578f5bdd5522ba32291243", + "0xc89f104200ed4c5d5f7046d99e68ae6f8ec31e2eeceb568eb05087e3aa546a74", + "0xc9f367fae45c39299693b134229bb6dd0da112fd1a7d19b7f4772c01e5cbe479", + "0x64e2d4ad51948764dd578d26357e29e8e4d076d65c05cffdf8211b624fefe9ac", + "0xf9a9b4e6d5be7fc051df8ecd9c389d16b1af86c749308e6a23f7ff4871f0ba9a", + "0x0d2b2a228b86ebf9499e1bf7674335087ced2eb35ce0eb90954a0f75751a2bf4", + "0xff8531b45420a960d6e48ca75d77758c25733abde83cd4a6160beae978aa735e", + "0xd6d412bd1cb96a2b568d30e7986b7e8994ca92fd65756a758295499e11ea52b6", + "0xad8533fccbecdd4a0b00d648bfe992360d265f7be70c41d9631cefad5d4fe2f6", + "0x31fbf2afb8d5cc896d517cfc5201ee24527e8d283f9c37ca10233bef01000a20", + "0x2fd67b7365efc258131eb410f46bf3b1cbd3e9c76fd6e9c3e86c9ff1054116ff", + "0xab6aa29f33d18244be26b23abadb39679a8aa56dafc0dd7b87b672df5f5f5db6", + "0xbad3b0f401ca0a53a3d465de5cecd57769ec9d4df2c04b78f8c342a7ed35bbee", + "0xbdc24d46e471835d83ce8c5b9ecbe675aab2fd8f7831c548e8efd268c2ee2232", + "0x87265fabd7397d08f0729f13a2f3a25bbc8c874b6b50f65715c92b62f665f925", + "0xa379fd268e7ff392c067c2dd823996f72714bf3f936d5eeded71298859f834cb", + "0xf3ab452c9599ebfbb234f72a86f3062aed12ae1f634abbe542ff60f5cefc1fcf", + "0x2b17ebb053a3034c07da36ed2ba42c25ad8e61dec87b5527f5e1c755eb55405a", + "0x305b40321bd67bf48bfd121ee4d5d347268578bd4b8344560046594771a11129", + "0xe7029c9bea020770d77fe06ca53b521b180ad6a9e747545aadc1c74beef7241c", + "0xabc357cec0f4351a5ada22483d3b103890392f8d8f9cb8073a61969ed1be4e08", + "0x97f88c301946508428044d05584dc41af2e6a0de946de7d7f5269c05468afe20", + "0xbdc08fe8d6f9a05ad8350626b622ad8eec80c52331d154a3860c98676719cfbd", + "0x161590fc9f7fcf4eaba2f950cf588e6da79e921f139d3c2d7ebe017003a4799e", + "0x91b658db75bc3d1954bfde2ef4bc12980ff1688e09d0537f170c9ab47c162320", + "0x76d995f121406a63ce26502e7ec2b653c221cda357694a8d53897a99e6ce731e", + "0x3d6b2009586aceb7232c01259bb9428523c02b0f42c2100ec0d392418260c403", + "0x14ca74ecbc8ec0c67444c6cb661a2bce907aa2a1453b11f16002b815b94a1c49", + "0x553b4dc88554ebe7b0a3bd0813104fd1165a1f950ceace11f5841aa74b756d85", + "0x4025bf4ad86751a156d447ce3cabafde9b688efcdafd8aa4be69e670f8a06d9e", + "0x74260cf266997d19225e9a0351a9acfa17471fccdf5edc9ccc3bb0d23ef551c5", + "0xf9dbca3e16d234e448cf03877746baeb62a8a25c261eff42498b1813565c752a", + "0x2652ec98e05c1b6920fb6ddc3b57e366d514ffa4b35d068f73b5603c47f68f2f", + "0x83f090efeb36db91eb3d4dfbb17335c733fce7c64317d0d3324d7caaaf880af5", + "0x1e86257f1151fb7022ed9ed00fb961a9a9989e58791fb72043bb63ed0811791c", + "0xd59e4dcc97cba88a48c2a9a2b29f79125099a39f74f4fb418547de8389cd5d15", + "0x875a19b152fe1eb3fe1de288fa9a84864a84a79bac30b1dbd70587b519a9770e", + "0x9c9dc2d3c8f2f6814cfc61b42ee0852bbaf3f523e0409dd5df3081b750a5b301", + "0xf6f7f81c51581c2e5861a00b66c476862424151dd750efeb20b7663d552a2e94", + "0x723fcb7ca43a42483b31443d4be9b756b34927176f91a391c71d0b774c73a299", + "0x2b02d8acf63bc8f528706ed4d5463a58e9428d5b71d577fd5daa13ba48ac56cf", + "0x2ff6911f574c0f0498fc6199da129446b40fca35ccbf362bc76534ba71c7ca22", + "0x1ef4b959b11bc87b11e4a5f84b4d757c6bdcfad874acec9a6c9eee23dc4bbe1b", + "0x68e2df9f512be9f64b7e3a2dee462149dac50780073d78b569a20256aea5f751", + "0xd1a3682e12b90ae1eab27fc5dc2aef3b8e4dbb813925e9a91e58d6c9832767b6", + "0x75778ccc102d98c5e0b4b83f7d4ef7fe8bc7263cc3317723001cb0b314d1e9e8", + "0xc7f44e2cead108dc167f0036ac8a278d3549cc3dd5cc067d074ccad9b1d9f8d4", + "0x4cba0223c5df2796b0ee9fbc084d69f10e6aedda8f0cf86171bebb156ede676c", + "0x628deda825661f586a5713e43c806fdd55e1a53fbe90a4ddb5f3786570740954", + "0xfc82a253bc7e0ac96252b238fbb411a54e0adf78d089f804a7fc83a4959b401e", + "0x72a6491f5daae0ceb85b61a5ed69009dd2a167c64cb35cabf38b846e27268e9d", + "0xee139a913d4fcf25ba54bb36fc8051b91f2ec73ba820cc193c46fb2f7c37a106", + "0x7f75021f2b1d0c78859478e27f6f40646b5776c060f1a5f6f0944c840a0121f8", + "0x5b60a1b78feca1d2602ac8110d263ad6b3663cbf49e6bdc1077b4b80af2feb6f", + "0xd61f15d80b1e88469b6a76ed6a6a2b94143b6acc3bd717357264818f9f2d5c6d", + "0xea85da1780b3879a4d81b685ba40b91c060866abd5080b30fbbb41730724a7dd", + "0xb9b9da9461e83153f3ae0af59fbd61febfde39eb6ac72db5ed014797495d4c26", + "0xf737762fe8665df8475ff341b3762aaeb90e52974fe5612f5efd0fc1c409d7f8", + "0xaaa25d934a1d5aa6b2a1863704d7a7f04794ed210883582c1f798be5ca046cf7", + "0x932f46d0b6444145221b647f9d3801b6cb8b1450a1a531a959abdaacf2b5656b", + "0xf4a8b0e52f843ad27635c4f5a467fbf98ba06ba9a2b93a8a97170b5c41bf4958", + "0x196ed380785ee2925307ec904161dc02a4596a55499e5b0a3897f95485b3e74a", + "0x772e829a405219e4f8cd93a1ef15c250be85c828c1e29ef6b3f7b46958a85b44", + "0xd66cfc9af9941515d788f9f5e3b56fddb92464173ddb67b83bf265e7ea502170", + "0xf5b040bfc246425278e2423b1953d8ad518de911cf04d16c67d8580a09f90e62", + "0xd2d18b2ae8a53dde14b4000e5e7e414505825f50401a3797dd8820cf510dc448", + "0xc01dcc064e644266739cd0ec7edf92fc2ef8e92e0beedf0e8aa30efcff1644fe", + "0x24720d325913ba137daf031924ad3bfaa1c8c00a53a2d048fe5667aef45efce3", + "0x70a24e1c89b3ea78d76ef458d498dcb5b8561d484853b2a8b2adcd61869857df", + "0x0ff3313997f14e1b1dcd80f1d62c58aaefb19efd7c0ea15dde21aa4e2a516e80", + "0x960c1f50062a4df851638f42c0259b6e0a0217300884f13a3c5c8d94adb34f21", + "0xb71ca7cc8578149da556131268f4625b51620dfc3a6e9fbd47f5df03afbd410e", + "0xa1a3eeec0addec7b9e15f416a07608a1b5d94f0b42d5c203b8ced03a07484f5b", + "0xa4bb8b059aa122ca4652115b83b17af80cfbea0d3e1e8979a396a667f94e85f3", + "0x31c4d2f252167fe2a4d41944224a80b2f1afaf76f8dd6a3d52d71751849e44bb", + "0x79642dd6a255f96c9efe569304d58c327a441448db0431aa81fe072d0d359b52", + "0x42a4b504714aba1b67defe9458fff0c8cb1f216dcab28263cef67a65693b2036", + "0xe3d2f6a9d882d0f026ef316940dfcbf131342060ea28944475fe1f56392c9ad2", + "0x986af9aeff236394a0afa83823e643e76f7624e9bfd47d5468f9b83758a86caa", + "0xafe2de6ede50ee351d63ed38d1f2ae5203174c731f41bbed95db467461ad5492", + "0x9ad40f0785fe1c8a5e4c3342b3c91987cd47a862ece6573674b52fa0456f697a", + "0xde4cde6d0fc6def3a89b79da0e01accdbec049f1c9471d13a5d59286bd679af1", + "0xecd0d1f70116d6b3ae21c57fb06ad90eed33d040e2c5c3d12714b3be934fa5ce", + "0x3c53c5bf2d1b1d4038e1f0e8a2e6d12e0d4613d5cd12562578b6909921224c10", + "0x36087382b37e9e306642cc6e867e0fb2971b6b2b28b6caf2f9c96b790e8db70a", + "0xa957496d6a4218a19998f90282d05bd93e6baabf55e55e8a5f74a933a4dec045", + "0x077d6f094e8467a21f02c67753565ec5755156015d4e86f1f82a22f9cf21c869", + "0x12dd3b1f29e1462ca392c12388a77c58044151154cf86f23873f92a99b6bb762", + "0x7fdbcdedcc02ecf16657792bd8ef4fa4adeee497f30207d4cc060eb0d528b26b", + "0x245554b12bf8edf9e9732d6e2fa50958376e355cb695515c94676e64c6e97009", + "0xccd3b1841b517f7853e35f85471710777e437a8665e352a0b61c7d7083c3babc", + "0xd970545a326dcd92e31310d1fdce3703dff8ef7c0f3411dfa74fab8b4b0763ac", + "0xd24163068918e2783f9e79c8f2dcc1c5ebac7796ce63070c364837aac91ee239", + "0x256a330055357e20691e53ca5be846507c2f02cfde09cafb5809106f0af9180e", + "0xfa446a5d1876c2051811af2a341a35dbcd3f7f8e2e4f816f501139d27dd7cd82", + "0xbafbc7a8f871d95736a41e5721605d37e7532e41eb1426897e33a72ed2f0bf1d", + "0x8055af9a105b6cf17cfeb3f5320e7dab1a6480500ff03a16c437dfec0724c290", + "0x1de6ee3e989497c1cc7ca1d16b7b01b2f336524aa2f75a823eaa1716c3a1a294", + "0x12bb9508d646dda515745d104199f71276d188b3e164083ad27dfdcdc68e290b", + "0x7ea9f9939ad4f3b44fe7b780e0587da4417c34459b2996b3a449bb5b3ff8c8cb", + "0xa88d2f8f35bc669aa6480ce82571df65fea366834670b4084910c7bb6a735dde", + "0x9486e045adb387a550b3c7a603c30e07ed8625d322d1158f4c424d30befe4a65", + "0xb283a70ba539fe1945be096cb90edb993fac77e8bf53616bde35cdcaa04ab732", + "0xab39a81558e9309831a2caf03e9df22e8233e20b1769f16e613debcdb8e2610f", + "0x1fc12540473fbbad97c08770c41f517ce19dc7106aa2be2e9b77867046627509", + "0xec33dbec9d655c4c581e07d1c40a587cf3217bc8168a81521b2d0021bd0ec133", + "0xc8699e3b41846bc291209bbb9c06f565f66c6ccecbf03ebc27593e798c21fe94", + "0x240d7eae209c19d453b666c669190db22db06279386aa30710b6edb885f6df94", + "0xb181c07071a750fc7638dd67e868dddbeeee8e8e0dcbc862539ee2084674a89e", + "0xb8792555c891b3cbfddda308749122a105938a80909c2013637289e115429625", + "0xfe3e9e5b4a5271d19a569fee6faee31814e55f156ba843b6e8f8dc439d60e67a", + "0x912e9ba3b996717f89d58f1e64243d9cca133614394e6ae776e2936cf1a9a859", + "0xa0671c91a21fdfd50e877afa9fe3974aa3913855a2a478ae2c242bcdb71c73d7", + "0x5b55d171b346db9ba27b67105b2b4800ca5ba06931ed6bd1bafb89d31e6472e6", + "0x68438458f1af7bd0103ef33f8bc5853fa857b8c1f84b843882d8c328c595940d", + "0x21fe319fe8c08c1d00f977d33d4a6f18aecaa1fc7855b157b653d2d3cbd8357f", + "0x23cce560bc31f68e699ece60f21dd7951c53c292b3f5522b9683eb2b3c85fc53", + "0x917fa32d172c352e5a77ac079df84401cdd960110c93aa9df51046d1525a9b49", + "0x3fc397180b65585305b88fe500f2ec17bc4dccb2ec254dbb72ffb40979f14641", + "0xf35fb569e7a78a1443b673251ac70384abea7f92432953ca9c0f31c356be9bd9", + "0x7955afa3cd34deb909cd031415e1079f44b76f3d6b0aaf772088445aaff77d08", + "0x45c0ca029356bf6ecfc845065054c06024977786b6fbfaea74b773d9b26f0e6c", + "0xe5c1dac2a6181f7c46ab77f2e99a719504cb1f3e3c89d720428d019cb142c156", + "0x677b0e575afcccf9ddefc9470e96a6cfff155e626600b660247b7121b17b030a", + "0xbeed763e9a38277efe57b834a946d05964844b1f51dba2c92a5f3b8d0b7c67d0", + "0x962b17ed1a9343d8ebfae3873162eef13734985f528ca06c90b0c1e68adfdd89", + ], + lamport_1: vec![ + "0xb3a3a79f061862f46825c00fec4005fb8c8c3462a1eb0416d0ebe9028436d3a9", + "0x6692676ce3b07f4c5ad4c67dc2cf1dfa784043a0e95dd6965e59dc00b9eaff2d", + "0xbf7b849feb312db230e6e2383681b9e35c064e2d037cbc3c9cc9cd49220e80c9", + "0xa54e391dd3b717ea818f5954eec17b4a393a12830e28fabd62cbcecf509c17dc", + "0x8d26d800ac3d4453c211ef35e9e5bb23d3b9ede74f26c1c417d6549c3110314d", + "0xbb8153e24a52398d92480553236850974576876c7da561651bc551498f184d10", + "0x0d30e0e203dc4197f01f0c1aba409321fbf94ec7216e47ab89a66fb45e295eff", + "0x01dc81417e36e527776bf37a3f9d74a4cf01a7fb8e1f407f6bd525743865791d", + "0xa6318e8a57bec438245a6834f44eb9b7fb77def1554d137ea12320fc572f42c9", + "0xd25db9df4575b595130b6159a2e8040d3879c1d877743d960bf9aa88363fbf9f", + "0x61bb8baeb2b92a4f47bb2c8569a1c68df31b3469e634d5e74221bc7065f07a96", + "0xb18962aee4db140c237c24fec7fd073b400b2e56b0d503f8bc74a9114bf183bf", + "0x205473cc0cdab4c8d0c6aeceda9262c225b9db2b7033babfe48b7e919751a2c6", + "0xc5aa7df7552e5bb17a08497b82d8b119f93463ccb67282960aee306e0787f228", + "0x36da99e7d38ce6d7eab90ea109ba26615ad75233f65b3ae5056fba79c0c6682a", + "0xd68b71bba6266b68aec0df39b7c2311e54d46a3eab35f07a9fe60d70f52eec58", + "0xbbe56f1274ada484277add5cb8c90ef687d0b69a4c95da29e32730d90a2d059f", + "0x0982d1d1c15a560339d9151dae5c05e995647624261022bbedce5dce8a220a31", + "0x8ef54ad546d2c6144fc26e1e2ef92919c676d7a76cfdfb5c6a64f09a54e82e71", + "0x1e3ac0133eef9cdbeb590f14685ce86180d02b0eea3ef600fd515c38992b1f26", + "0x642e6b1c4bec3d4ba0ff2f15fbd69dcb57e4ba8785582e1bc2b452f0c139b590", + "0xca713c8cf4afa9c5d0c2db4fc684a8a233b3b01c219b577f0a053548bedf8201", + "0xd0569ba4e1f6c02c69018b9877d6a409659cb5e0aa086df107c2cc57aaba62da", + "0x4ebe68755e14b74973e7f0fa374b87cee9c370439318f5783c734f00bb13e4b5", + "0x788b5292dc5295ae4d0ea0be345034af97a61eec206fda885bbc0f049678c574", + "0x0ebd88acd4ae195d1d3982038ced5af1b6f32a07349cf7fffbff3ce410c10df2", + "0xc7faf0a49234d149036c151381d38427b74bae9bd1601fc71663e603bc15a690", + "0xc5247bf09ebe9fa4e1013240a1f88c703f25a1437196c71ee02ca3033a61f946", + "0x719f8c68113d9f9118b4281e1f42c16060def3e3eeef15f0a10620e886dc988f", + "0x28da4f8d9051a8b4d6158503402bdb6c49ba2fb1174344f97b569c8f640504e6", + "0x96f6773576af69f7888b40b0a15bc18cc9ec8ca5e1bb88a5de58795c6ddf678e", + "0x8d80d188a4e7b85607deccf654a58616b6607a0299dd8c3f1165c453fd33d2e4", + "0x9c08dcc4f914486d33aa24d10b89fd0aabcc635aa2f1715dfb1a18bf4e66692a", + "0x0ff7045b5f6584cc22c140f064dec0692762aa7b9dfa1defc7535e9a76a83e35", + "0x8e2dae66fa93857b39929b8fc531a230a7cfdd2c449f9f52675ab5b5176461d5", + "0xf449017c5d429f9a671d9cc6983aafd0c70dd39b26a142a1d7f0773de091ac41", + "0xed3d4cab2d44fec0d5125a97b3e365a77620db671ecdda1b3c429048e2ebdae6", + "0x836a332a84ee2f4f5bf24697df79ed4680b4f3a9d87c50665f46edaeed309144", + "0x7a79278754a4788e5c1cf3b9145edb55a2ba0428ac1c867912b5406bb7c4ce96", + "0x51e6e2ba81958328b38fd0f052208178cec82a9c9abd403311234e93aff7fa70", + "0x217ec3ec7021599e4f34410d2c14a8552fff0bc8f6894ebb52ec79bf6ec80dc9", + "0x8a95bf197d8e359edabab1a77f5a6d04851263352aa46830f287d4e0564f0be0", + "0x60d0cbfb87340b7c92831872b48997ce715da91c576296df215070c6c20046d4", + "0x1739fbca476c540d081b3f699a97387b68af5d14be52a0768d5185bc9b26961b", + "0xac277974f945a02d89a0f8275e02de9353e960e319879a4ef137676b537a7240", + "0x959b7640821904ba10efe8561e442fbdf137ccb030aee7472d10095223e320ba", + "0xdba61c8785a64cb332342ab0510126c92a7d61f6a8178c5860d018d3dad571c6", + "0xc191fb6a92eb1f1fb9e7eb2bdecd7ec3b2380dd79c3198b3620ea00968f2bd74", + "0x16ef4e88e182dfc03e17dc9efaa4a9fbf4ff8cb143304a4a7a9c75d306729832", + "0x39080e4124ca577ff2718dfbcb3415a4220c5a7a4108729e0d87bd05adda5970", + "0xa29a740eef233956baff06e5b11c90ed7500d7947bada6da1c6b5d9336fc37b6", + "0x7fda7050e6be2675251d35376bacc895813620d245397ab57812391d503716ee", + "0x401e0bf36af9992deb87efb6a64aaf0a4bc9f5ad7b9241456b3d5cd650418337", + "0x814e70c57410e62593ebc351fdeb91522fe011db310fcf07e54ac3f6fefe6be5", + "0x03c1e52ecbef0d79a4682af142f012dc6b037a51f972a284fc7973b1b2c66dcf", + "0x57b22fb091447c279f8d47bdcc6a801a946ce78339e8cd2665423dfcdd58c671", + "0x53aeb39ab6d7d4375dc4880985233cba6a1be144289e13cf0bd04c203257d51b", + "0x795e5d1af4becbca66c8f1a2e751dcc8e15d7055b6fc09d0e053fa026f16f48f", + "0x1cd02dcd183103796f7961add835a7ad0ba636842f412643967c58fe9545bee4", + "0x55fc1550be9abf92cacb630acf58bad11bf734114ebe502978a261cc38a4dd70", + "0x6a044e0ea5c361d3fb2ca1ba795301e7eb63db4e8a0314638f42e358ea9cfc3e", + "0x57d9f15d4db199cbcb7cbd6524c52a1b799d52b0277b5a270d2985fcee1e2acb", + "0x66c78c412e586bd01febc3e4d909cc278134e74d51d6f60e0a55b35df6fb5b09", + "0x1076799e15a49d6b15c2486032f5e0b50f43c11bc076c401e0779d224e33f6fc", + "0x5f70e3a2714d8b4483cf3155865ba792197e957f5b3a6234e4c408bf2e55119d", + "0x9b105b0f89a05eb1ff7caed74cf9573dc55ac8bc4881529487b3700f5842de16", + "0x1753571b3cfadca4277c59aee89f607d1b1e3a6aa515d9051bafb2f0d8ce0daa", + "0x4014fff940b0950706926a19906a370ccbd652836dab678c82c539c00989201a", + "0x0423fa59ee58035a0beb9653841036101b2d5903ddeabddabf697dbc6f168e61", + "0x78f6781673d991f9138aa1f5142214232d6e3d6986acb6cc7fb000e1a055f425", + "0x21b8a1f6733b5762499bf2de90c9ef06af1c6c8b3ddb3a04cce949caad723197", + "0x83847957e909153312b5bd9a1a37db0bd6c72a417024a69df3e18512973a18b4", + "0x948addf423afd0c813647cfe32725bc55773167d5065539e6a3b50e6ebbdab38", + "0x0b0485d1bec07504a2e5e3a89addd6f25d497cd37a0c04bc38355f8bdb01cd48", + "0x31be8bda5143d39ea2655e9eca6a294791ca7854a829904d8574bedc5057ddc4", + "0x16a0d2d657fadce0d81264320e42e504f4d39b931dff9888f861f3cc78753f99", + "0xb43786061420c5231bf1ff638cb210f89bf4cd2d3e8bafbf34f497c9a298a13b", + "0x1f5986cbd7107d2a3cbc1826ec6908d976addbf9ae78f647c1d159cd5397e1bd", + "0xa883ccdbfd91fad436be7a4e2e74b7796c0aadfe03b7eea036d492eaf74a1a6f", + "0x5bc9eb77bbbf589db48bca436360d5fc1d74b9195237f11946349951f2a9f7f6", + "0xb6bc86de74a887a5dceb012d58c62399897141cbcc51bad9cb882f53991f499c", + "0xa6c3260e7c2dd13f26cf22bf4cd667688142ff7a3511ec895bc8f92ebfa694b6", + "0xb97da27e17d26608ef3607d83634d6e55736af10cc7e4744940a3e35d926c2ad", + "0x9df44067c2dc947c2f8e07ecc90ba54db11eac891569061a8a8821f8f9773694", + "0x865cc98e373800825e2b5ead6c21ac9112ff25a0dc2ab0ed61b16dc30a4a7cd7", + "0xe06a5b157570c5e010a52f332cacd4e131b7aed9555a5f4b5a1c9c4606caca75", + "0x824eccb5cf079b5943c4d17771d7f77555a964a106245607cedac33b7a14922e", + "0xe86f721d7a3b52524057862547fc72de58d88728868f395887057153bccaa566", + "0x3344e76d79f019459188344fb1744c93565c7a35799621d7f4505f5b6119ac82", + "0x401b3589bdd1b0407854565329e3f22251657912e27e1fb2d978bf41c435c3ac", + "0xb12fd0b2567eb14a562e710a6e46eef5e280187bf1411f5573bb86ecbe05e328", + "0xe6dc27bab027cbd9fbb5d80054a3f25b576bd0b4902527a0fc6d0de0e45a3f9f", + "0x1de222f0e731001c60518fc8d2be7d7a48cc84e0570f03516c70975fdf7dc882", + "0xb8ff6563e719fc182e15bbe678cf045696711244aacc7ce4833c72d2d108b1b9", + "0x53e28ac2df219bcbbc9b90272e623d3f6ca3221e57113023064426eff0e2f4f2", + "0x8a4e0776f03819e1f35b3325f20f793d026ccae9a769d6e0f987466e00bd1ce7", + "0x2f65f20089a31f79c2c0ce668991f4440b576ecf05776c1f6abea5e9b14b570f", + "0x448e124079a48f62d0d79b96d5ed1ffb86610561b10d5c4236280b01f8f1f406", + "0x419b34eca1440c847f7bff9e948c9913075d8e13c270e67f64380a3f31de9bb2", + "0x2f6e4fee667acaa81ba8e51172b8329ed936d57e9756fb31f635632dbc2709b7", + "0xdd5afc79e8540fcee6a896c43887bd59c9de5d61b3d1b86539faeb41a14b251d", + "0xc707bed926a46cc451a6b05e642b6098368dbdbf14528c4c28733d5d005af516", + "0x153e850b606eb8a05eacecc04db4b560d007305e664bbfe01595cb69d26b8597", + "0x1b91cc07570c812bb329d025e85ef520132981337d7ffc3d84003f81a90bf7a7", + "0x4ca32e77a12951a95356ca348639ebc451170280d979e91b13316844f65ed42a", + "0xe49ea1998e360bd68771bd69c3cd4cf406b41ccca4386378bec66ea210c40084", + "0x01aaffbde1a672d253e0e317603c2dc1d0f752100d9e853f840bca96e57f314c", + "0x170d0befcbbaafb317c8684213a4989368332f66e889824cc4becf148f808146", + "0x56f973308edf5732a60aa3e7899ae1162c7a2c7b528c3315237e20f9125b34e0", + "0x66c54fd5f6d480cab0640e9f3ec1a4eafbafc0501528f57bb0d5c78fd03068ef", + "0xaca6c83f665c64d76fbc4858da9f264ead3b6ecdc3d7437bb800ef7240abffb9", + "0xf1d4e02e7c85a92d634d16b12dc99e1d6ec9eae3d8dfbca77e7c609e226d0ce7", + "0x094352545250e843ced1d3c6c7957e78c7d8ff80c470974778930adbe9a4ed1a", + "0x76efa93070d78b73e12eb1efa7f36d49e7944ddcc3a043b916466ee83dca52ce", + "0x1772a2970588ddb584eadf02178cdb52a98ab6ea8a4036d29e59f179d7ba0543", + "0xe4bbf2d97d65331ac9f680f864208a9074d1def3c2433458c808427e0d1d3167", + "0x8ccfb5252b22c77ea631e03d491ea76eb9b74bc02072c3749f3e9d63323b44df", + "0x9e212a9bdf4e7ac0730a0cecd0f6cc49afc7e3eca7a15d0f5f5a68f72e45363b", + "0x52e548ea6445aae3f75509782a7ab1f4f02c2a85cdd0dc928370f8c76ae8802d", + "0xb62e7d73bf76c07e1a6f822a8544b78c96a6ba4f5c9b792546d94b56ca12c8b9", + "0x595cb0e985bae9c59af151bc748a50923921a195bbec226a02157f3b2e066f5b", + "0x1c7aa6b36f402cec990bafefbdbb845fc6c185c7e08b6114a71dd388fe236d32", + "0x01ee2ff1a1e88858934a420258e9478585b059c587024e5ec0a77944821f798c", + "0x420a963a139637bffa43cb007360b9f7d305ee46b6a694b0db91db09618fc2e5", + "0x5a8e2ad20f8da35f7c885e9af93e50009929357f1f4b38a6c3073e8f58fae49e", + "0x52a405fdd84c9dd01d1da5e9d1c4ba95cb261b53bf714c651767ffa2f9e9ad81", + "0xa1a334c901a6d5adc8bac20b7df025e906f7c4cfc0996bfe2c62144691c21990", + "0xb789a00252f0b34bded3cb14ae969effcf3eb29d97b05a578c3be8a9e479c213", + "0xb9dbf7e9ddb638a515da245845bea53d07becdf3f8d1ec17de11d495624c8eab", + "0xaf566b41f5ed0c026fa8bc709533d3fa7a5c5d69b03c39971f32e14ab523fa3d", + "0x8121e0b2d9b106bb2aefd364fd6a450d88b88ee1f5e4aad7c0fcd8508653a112", + "0x8581c1be74279216b93e0a0d7272f4d6385f6f68be3eef3758d5f68b62ee7b6c", + "0x85386f009278f9a1f828404fa1bbfa02dfb9d896554f0a52678eb6ec8feadc55", + "0xf483ed167d92a0035ac65a1cfdb7906e4952f74ae3a1d86324d21f241daffcb7", + "0x3872485e2a520a350884accd990a1860e789dd0d0664ad14f50186a92c7be7be", + "0xc6c1a3301933019105f5650cabcb22bfbf221965ffcfc1329315b24ea3d77fd4", + "0xcee901330a60d212a867805ce0c28f53c6cc718f52156c9e74390d18f5df6280", + "0xa67ae793b1cd1a828a607bae418755c84dbb61adf00833d4c61a94665363284f", + "0x80d8159873b517aa6815ccd7c8ed7cfb74f84298d703a6c5a2f9d7d4d984ddde", + "0x1de5a8b915f2d9b45c97a8e134871e2effb576d05f4922b577ade8e3cd747a79", + "0x6ea17c5ece9b97dddb8b2101b923941a91e4b35e33d536ab4ff15b647579e1f5", + "0xcb78631e09bc1d79908ce1d3e0b6768c54b272a1a5f8b3b52485f98d6bba9245", + "0xd7c38f9d3ffdc626fe996218c008f5c69498a8a899c7fd1d63fbb03e1d2a073f", + "0x72cdef54267088d466244a92e4e6f10742ae5e6f7f6a615eef0da049a82068f9", + "0x60b3c490ba8c502656f9c0ed37c47283e74fe1bc7f0e9f651cbc76552a0d88eb", + "0x56bd0c66987a6f3761d677097be9440ea192c1cb0f5ec38f42789abe347e0ea9", + "0x3caac3e480f62320028f6f938ee147b4c78e88a183c464a0c9fb0df937ae30c1", + "0x7a4d2f11bddda1281aba5a160df4b814d23aef07669affe421a861fac2b4ec0f", + "0x9bb4d11299922dc309a4523959298a666ebe4063a9ee3bad1b93988ed59fb933", + "0x957323fffbaf8f938354662452115ae5acba1290f0d3f7b2a671f0359c109292", + "0x877624e31497d32e83559e67057c7a605fb888ed8e31ba68e89e02220eac7096", + "0x8456546ae97470ff6ea98daf8ae632e59b309bd3ff8e9211f7d21728620ed1e5", + "0xbacb26f574a00f466ce354e846718ffe3f3a64897d14d5ffb01afcf22f95e72b", + "0x0228743a6e543004c6617bf2c9a7eba1f92ebd0072fb0383cb2700c3aed38ba0", + "0x04f093f0f93c594549436860058371fb44e8daf78d6e5f563ba63a46b61ddbf0", + "0x0ba17c1ec93429ceaff08eb81195c9844821b64f2b5363926c2a6662f83fb930", + "0xd71605d8446878c677f146837090797e888416cfc9dc4e79ab11776cc6639d3f", + "0x33dde958dc5a6796138c453224d4d6e7f2ae740cceef3b52a8b669eb4b9691a1", + "0x3c39838295d1495e90e61ce59f6fcc693b31c292d02d31759719df6fe3214559", + "0x8aecc66f38644296cf0e6693863d57a243a31a4929130e22ab44cb6157b1af41", + "0xdf7153a7eab9521f2b37124067166c72de8f342249ac0e0f5350bd32f1251053", + "0xa498840b58897cf3bed3981b94c86d85536dfebbc437d276031ebd9352e171eb", + "0xb1df15a081042ab665458223a0449ffc71a10f85f3d977beb20380958fd92262", + "0x15d3bdbdee2a61b01d7a6b72a5482f6714358eedf4bece7bb8458e100caf8fba", + "0x0c96b7a0ea09c3ef758424ffb93654ce1520571e32e1f83aecbeded2388c3a7a", + "0xb4a3a8023266d141ecd7c8a7ca5282a825410b263bc11c7d6cab0587c9b5446e", + "0xf38f535969d9592416d8329932b3a571c6eacf1763de10fb7b309d3078b9b8d4", + "0x5a1e7b1c3b3943158341ce6d7f9f74ae481975250d89ae4d69b2fcd4c092eb4e", + "0xdad31e707d352f6cca78840f402f2ac9292094b51f55048abf0d2badfeff5463", + "0x097e290170068e014ceda3dd47b28ede57ff7f916940294a13c9d4aa2dc98aad", + "0x22e2dcedb6bb7f8ace1e43facaa502daa7513e523be98daf82163d2a76a1e0be", + "0x7ef2b211ab710137e3e8c78b72744bf9de81c2adde007aef6e9ce92a05e7a2c5", + "0x49b427805fc5186f31fdd1df9d4c3f51962ab74e15229e813072ec481c18c717", + "0xe60f6caa09fa803d97613d58762e4ff7f22f47d5c30b9d0116cdc6a357de4464", + "0xab3507b37ee92f026c72cc1559331630bc1c7335b374e4418d0d02687df1a9dd", + "0x50825ae74319c9adebc8909ed7fc461702db8230c59975e8add09ad5e7a647ab", + "0x0ee8e9c1d8a527a42fb8c2c8e9e51faf727cffc23ee22b5a95828f2790e87a29", + "0x675c21c290ddb40bec0302f36fbcd2d1832717a4bc05d113c6118a62bc8f9aca", + "0x580bafab24f673317b533148d7226d485e211eaa3d6e2be2529a83ca842b58a7", + "0x540e474776cae597af24c147dc1ae0f70a6233e98cf5c3ce31f38b830b75c99a", + "0x36eaf9f286e0f356eaaf8d81f71cc52c81d9ebc838c3b4859009f8567a224d16", + "0x0e2cbbb40954be047d02b1450a3dbd2350506448425dc25fd5faf3a66ee8f5c4", + "0x7eb0390cfe4c4eb120bbe693e87adc8ecab51d5fd8ce8f911c8ff07fad8cbe20", + "0xbf77589f5c2ebb465b8d7936f6260a18a243f59bd87390ee22cf579f6f020285", + "0x695b96bb28693f6928777591ef64146466d27521280a295936a52ec60707c565", + "0x22a0d018cbd4274caa8b9e7fb132e0a7ed787874046ca683a7d81d1c7c8b8f15", + "0x84092b122bb35e5ad85407b4b55f33707b86e0238c7970a8583f3c44308ed1d9", + "0xea346067ca67255235f9cae949f06e4b6c93846a7abc7c8c8cd786e9c4b3e4bc", + "0xa6df0716b125dc696b5d0e520cb49c1c089397c754efc146792e95bc58cc7159", + "0x7377b5d3953029fc597fb10bb6479ee34133d38f08783fbb61c7d070f34ea66f", + "0x7d79b00ffb976a10cd24476a394c8ed22f93837c51a58a3ddc7418153a5a8ea1", + "0x01e55182e80dff26cc3e06bb736b4a63745bde8ae28c604fa7fb97d99de5f416", + "0x062a2d5a207f8d540764d09648afecbf5033b13aec239f722b9033a762acf18b", + "0x48be60a3221d98b4d62f0b89d3bef74c70878dd65c6f79b34c2c36d0ddaa1da0", + "0x41e11f33543cf045c1a99419379ea31523d153bdf664549286b16207b9648c85", + "0xeef4d30b4700813414763a199e7cc6ab0faec65ef8b514faa01c6aa520c76334", + "0xea7cfe990422663417715e7859fc935ca47f47c943a1254044b6bc5934c94bc8", + "0xbbd3c834e5403b98a0ca346c915a23310f3d58880786628bc6cfbe05ba29c3c5", + "0xe216379f385bc9995ae0f37f1409a78d475c56b8aeb4ee434326724ec20124f7", + "0xdd328a1eee19d09b6fef06e252f8ad0ae328fbf900ef745f5950896803a3899d", + "0xa16fde34b0d743919feb0781eca0c525a499d279119af823cb3a8817000335db", + "0x7a28d108c59b83b12c85cd9aabc1d1d994a9a0329ae7b64a32aadcd61ebe50e3", + "0xb28bc82fceae74312eb837a805f0a8a01c0f669b99bb03fde31c4d58bedff89b", + "0x1b0d8f37d349781e846900b51a90c828aa384afe9b8ee1f88aeb8dba4b3168f2", + "0xbfd0301ff964c286c3331a30e09e0916da6f484e9c9596dbf1cae3cc902dbf9e", + "0xbb8254cb9ef6b485b8fb6caeafe45f920affc30f6b9d671e9a454530536f4fef", + "0xcad2317cf63dfa7147ded5c7e15f5f72e78f42d635e638f1ece6bc722ca3638b", + "0xb6c6e856fd45117f54775142f2b38f31114539d8943bcbcf823f6c7650c001e4", + "0x869f1baa35684c8f67a5bc99b294187852e6c85243a2f36481d0891d8b043020", + "0x14c6ccf145ee40ff56e3810058d2fba9a943ffc7c7087c48a08b2451c13dc788", + "0x263c1bcb712890f155b7e256cefa4abf92fe4380f3ffc11c627d5e4e30864d18", + "0x69f4eaf655e31ad7f7a725cd415ce7e45dd4a8396ac416950d42ed33155c3487", + "0x47e8eec2c5e33c9a54fe1f9b09e7744b614fb16531c36b862aa899424be13b05", + "0x5c985de270e62c44f0b49157882e8e83641b906ce47959e337fe8423e125a2eb", + "0x4e13b11e13202439bb5de5eea3bb75d2d7bf90f91411163ade06161a9cf424db", + "0x583a8fa159bb74fa175d72f4e1705e9a3b8ffe26ec5ad6e720444b99288f1213", + "0x903d2a746a98dfe2ee2632606d57a9b0fa6d8ccd895bb18c2245fd91f8a43676", + "0xa35a51330316012d81ec7249e3f2b0c9d7fcbb99dd98c62fe880d0a152587f51", + "0x33818a7beb91730c7b359b5e23f68a27b429967ea646d1ea99c314353f644218", + "0x183650af1e0b67f0e7acb59f8c72cc0e60acc13896184db2a3e4613f65b70a8b", + "0x857ff2974bef960e520937481c2047938a718cea0b709282ed4c2b0dbe2ef8fa", + "0x95a367ecb9a401e98a4f66f964fb0ece783da86536410a2082c5dbb3fc865799", + "0x56c606a736ac8268aedadd330d2681e7c7919af0fe855f6c1c3d5c837aa92338", + "0x5c97f7abf30c6d0d4c23e762c026b94a6052a444df4ed942e91975419f68a3a4", + "0x0b571de27d2022158a3128ae44d23a8136e7dd2dee74421aa4d6ed15ee1090a0", + "0xa17f6bc934a2f3c33cea594fee8c96c1290feec934316ebbbd9efab4937bf9f9", + "0x9ff57d70f27aad7281841e76435285fd27f10dad256b3f5cabde4ddc51b70eff", + "0xafa3071a847215b3ccdf51954aa7cb3dd2e6e2a39800042fc42009da705508b2", + "0x5e3bea33e4ac6f7c50a077d19571b1796e403549b1ce7b15e09905a0cc5a4acf", + "0x0dc7ba994e632ab95f3ecb7848312798810cf761d1c776181882d17fd6dda075", + "0xb4f7158679dad9f7370a2f64fbe617a40092849d17453b4f50a93ca8c6885844", + "0x094564b00f53c6f27c121fd8adfe1685b258b259e585a67b57c85efb804c57b2", + "0x9cd21a4249ba3fccffad550cdb8409dc12d8b74a7192874b6bafe2363886f318", + "0xbb22e0dad55cb315c564c038686419d40ef7f13af2143a28455bf445f6e10393", + "0x2a71d5e00821178c2cd39e7501e07da5cca6680eb7cdbe996f52dccafadb3735", + "0x9619406093b121e044a5b403bb1713ae160aeb52ad441f82dc6c63e4b323b969", + "0x3b8bd1d82c6d67ae707e19b889f1cb1f7bba912f12ae4284298f3a70c3644c79", + "0xd7a70c50d47d48785b299dbea01bf03ef18b8495de3c35cb265bc8f3295c4e15", + "0x8802ecce8dd6b6190af8ac79aafda3479c29f548d65e5798c0ca51a529b19108", + "0x4b630e1df52ec5fd650f4a4e76b3eeddda39e1e9eab996f6d3f02eefdf690990", + "0x0bfbff60fcf7f411d469f7f6f0a58ca305fd84eb529ee3ac73c00174793d723e", + "0x535f78b5f3a99a1c498e2c19dc1acb0fbbaba8972ba1d7d66936c28ab3667ebe", + "0x06ba92d8129db98fec1b75f9489a394022854f22f2e9b9450b187a6fc0d94a86", + "0xb7ae275ba10f80fb618a2cf949d5ad2e3ae24eb2eb37dcf1ec8c8b148d3ba27f", + "0xb275579bcf2584d9794dd3fc7f999902b13d33a9095e1980d506678e9c263de1", + "0x843ccd52a81e33d03ad2702b4ef68f07ca0419d4495df848bff16d4965689e48", + "0xde8b779ca7250f0eb867d5abdffd1d28c72a5a884d794383fc93ca40e5bf6276", + "0x6b789a2befccb8788941c9b006e496b7f1b03dbb8e530ba339db0247a78a2850", + "0xfccd4dca80bc52f9418f26b0528690255e320055327a34b50caf088235d2f660", + "0x18479ebfbe86c1e94cd05c70cb6cace6443bd9fdac7e01e9c9535a9e85141f2f", + "0x5350c8f3296441db954a261238c88a3a0c51ab418a234d566985f2809e211148", + "0xa5636614135361d03a381ba9f6168e2fd0bd2c1105f9b4e347c414df8759dea3", + "0xe7bb69e600992e6bd41c88a714f50f450153f1a05d0ddb4213a3fc4ba1f48c3f", + "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", + "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", + ], + compressed_lamport_sk: + "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", + child_sk: + "7419543105316279183937430842449358701327973165530407166294956473095303972104" + } + } +} From 7629b00280c5aef6e7f7b3a82e1a33cc53d89078 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 15:19:20 +1000 Subject: [PATCH 098/118] More progress --- eth2/utils/eth2_keystore/src/path.rs | 101 ++++++++++++++++++++------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 699278fb332..acd985059eb 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -19,22 +19,15 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { - let lamport = &hkdf_expand(hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes()); - - // TODO: don't do all this extra work with extraction. - let lamport_bytes = lamport - .iter_chunks() - .map(|a| a.to_vec()) - .flatten() - .collect::>(); - // TODO: justify 48. - mod_r(&lamport_bytes[0..48]) + let okm = &hkdf_expand_basic( + hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), + 48, + ); + mod_r(&okm) } fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(bytes.len(), HASH_SIZE); - let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); @@ -42,7 +35,7 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { debug_assert!(x.len() <= HASH_SIZE); let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - bytes.len()..].copy_from_slice(&x); + output[HASH_SIZE - x.len()..].copy_from_slice(&x); output } @@ -115,6 +108,37 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } +// TODO: zeroize. +fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; HASH_SIZE]; + + let mut hasher = Sha256::new(); + hasher.input(prk); + hasher.input(&[]); // TODO: remove this? + hasher.input(&[1]); + hasher.result(&mut okm[..]); + + let mut i = 0; + while okm.len() < l { + i += 1; + + let mut hasher = Sha256::new(); + hasher.input(prk); + if i == 1 { + hasher.input(&[]); // TODO: remove this line? + } else { + hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? + } + hasher.input(&[i]); + + let mut digest = [0; HASH_SIZE]; + hasher.result(&mut digest); + okm.extend_from_slice(&mut digest); + } + + okm[0..l].to_vec() +} + fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -131,29 +155,58 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + lamport_0: Vec>, + lamport_1: Vec>, + compressed_lamport_pk: Vec, + child_sk: Vec, + } + + #[test] + fn eip2333_intermediate_vector() { + let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); + assert_eq!( + &master_sk[..], + &vectors.master_sk[..], + "master_sk should match" + ); + } + struct RawTestVector { seed: &'static str, master_sk: &'static str, child_index: u32, lamport_0: Vec<&'static str>, lamport_1: Vec<&'static str>, - compressed_lamport_sk: &'static str, + compressed_lamport_pk: &'static str, child_sk: &'static str, } - struct TestVector { - seed: Vec, - master_sk: Vec, - child_index: u32, - lamport_0: Vec<[u8; HASH_SIZE]>, - lamport_1: Vec<[u8; HASH_SIZE]>, - compressed_lamport_pk: Vec, - child_sk: Vec, + fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") + } + + fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() } impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { - todo!() + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + lamport_0: raw.lamport_0.into_iter().map(hex_to_vec).collect(), + lamport_1: raw.lamport_1.into_iter().map(hex_to_vec).collect(), + compressed_lamport_pk: hex_to_vec(raw.compressed_lamport_pk), + child_sk: int_to_vec(raw.child_sk), + } } } @@ -677,7 +730,7 @@ mod test { "0x17b42e81bae19591e22aa2510be06803bcb5c39946c928c977d78f346d3ca86b", "0x30a10c07dc9646b7cbb3e1ab722a94d2c53e04c0c19efaaea7dccba1b00f2a20", ], - compressed_lamport_sk: + compressed_lamport_pk: "0x672ba456d0257fe01910d3a799c068550e84881c8d441f8f5f833cbd6c1a9356", child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" From 3b905e38e5d97b2c21d1e303f6759464495aad65 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:29:35 +1000 Subject: [PATCH 099/118] Passing intermediate test vectors --- .../eth2_keystore/src/lamport_secret_key.rs | 49 ++++++++ eth2/utils/eth2_keystore/src/path.rs | 106 ++++++++++-------- 2 files changed, 108 insertions(+), 47 deletions(-) create mode 100644 eth2/utils/eth2_keystore/src/lamport_secret_key.rs diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs new file mode 100644 index 00000000000..01329d7ede9 --- /dev/null +++ b/eth2/utils/eth2_keystore/src/lamport_secret_key.rs @@ -0,0 +1,49 @@ +use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use std::iter::Iterator; +use zeroize::Zeroize; + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); + +impl LamportSecretKey { + /// Instantiates `Self` with all chunks set to zero. + pub fn zero() -> Self { + Self(vec![[0; HASH_SIZE]; LAMPORT_ARRAY_SIZE as usize]) + } + + /// Instantiates `Self` from a flat buffer of `HASH_SIZE * LAMPORT_ARRAY_SIZE` bytes. + /// + /// ## Panics + /// + /// If an incorrect number of bytes is supplied. + pub fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!( + bytes.len(), + HASH_SIZE * LAMPORT_ARRAY_SIZE as usize, + "incorrect byte length" + ); + + let mut this = Self::zero(); + + for i in 0..LAMPORT_ARRAY_SIZE { + let iu = i as usize; + this.get_mut_chunk(i) + .copy_from_slice(&bytes[iu * HASH_SIZE..(iu + 1) * HASH_SIZE]) + } + + this + } + + pub fn get_chunk(&self, i: u8) -> &[u8] { + &self.0[i as usize] + } + + pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { + &mut self.0[i as usize] + } + + pub fn iter_chunks(&self) -> impl Iterator { + self.0.iter() + } +} diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index acd985059eb..db3d2f9063d 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, sha2::Sha256}; +use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -9,6 +9,8 @@ pub const LAMPORT_ARRAY_SIZE: u8 = 255; pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; +pub const BLS_KEY_LEN: usize = 48; + fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } @@ -19,11 +21,9 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { + let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); // TODO: justify 48. - let okm = &hkdf_expand_basic( - hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm).as_bytes(), - 48, - ); + let okm = &hkdf_expand(&prk, BLS_KEY_LEN); mod_r(&okm) } @@ -44,8 +44,8 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let not_ikm = flip_bits(ikm); let lamports = [ - ikm_to_lamport_sk(ikm, &salt), - ikm_to_lamport_sk(¬_ikm, &salt), + ikm_to_lamport_sk(&salt, ikm), + ikm_to_lamport_sk(&salt, ¬_ikm), ]; let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; @@ -74,20 +74,24 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { } fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { - hkdf_expand(hkdf_extract(salt, ikm).as_bytes()) + let prk = hkdf_extract(salt, ikm); + let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(&okm) } -fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> PlainText { - let mut hasher = Sha256::new(); - hasher.input(salt); - hasher.input(ikm); - - let mut digest = vec![0; HASH_SIZE]; - hasher.result(&mut digest); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { + let mut prk = [0; HASH_SIZE]; + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); + prk +} - digest.into() +fn hkdf_expand(prk: &[u8], l: usize) -> Vec { + let mut okm = vec![0; l]; + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); + okm } +/* fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { let mut okm = LamportSecretKey::zero(); @@ -107,37 +111,7 @@ fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { okm } - -// TODO: zeroize. -fn hkdf_expand_basic(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; HASH_SIZE]; - - let mut hasher = Sha256::new(); - hasher.input(prk); - hasher.input(&[]); // TODO: remove this? - hasher.input(&[1]); - hasher.result(&mut okm[..]); - - let mut i = 0; - while okm.len() < l { - i += 1; - - let mut hasher = Sha256::new(); - hasher.input(prk); - if i == 1 { - hasher.input(&[]); // TODO: remove this line? - } else { - hasher.input(&okm[okm.len() - 32..]); // TODO: remove this line? - } - hasher.input(&[i]); - - let mut digest = [0; HASH_SIZE]; - hasher.result(&mut digest); - okm.extend_from_slice(&mut digest); - } - - okm[0..l].to_vec() -} +*/ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { debug_assert_eq!(input.len(), HASH_SIZE); @@ -168,12 +142,50 @@ mod test { #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); + let master_sk = derive_master_sk(&vectors.seed); assert_eq!( &master_sk[..], &vectors.master_sk[..], "master_sk should match" ); + + let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + assert_eq!( + lamport_0 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_0, + "lamport_0 should match" + ); + + let lamport_1 = ikm_to_lamport_sk( + &vectors.child_index.to_be_bytes()[..], + &flip_bits(&master_sk), + ); + assert_eq!( + lamport_1 + .iter_chunks() + .map(|c| c.to_vec()) + .collect::>(), + vectors.lamport_1, + "lamport_1 should match" + ); + + let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + assert_eq!( + &compressed_lamport_pk[..], + &vectors.compressed_lamport_pk[..], + "compressed_lamport_pk should match" + ); + + let child_sk = derive_child_sk(&master_sk, vectors.child_index); + assert_eq!( + &child_sk[..], + &vectors.child_sk[..], + "child_sk should match" + ); } struct RawTestVector { From 43a34d6004f215110b361428e2014df390b26212 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 17:54:33 +1000 Subject: [PATCH 100/118] Tidy, add comments --- eth2/utils/eth2_keystore/src/path.rs | 83 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index db3d2f9063d..4060d78ff51 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -4,29 +4,53 @@ use num_bigint::BigUint; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; + /// The size of the lamport array. +/// +/// Indirectly defined in EIP-2333. pub const LAMPORT_ARRAY_SIZE: u8 = 255; +/// The order of the BLS 12-381 curve. +/// +/// Defined in EIP-2333. pub const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; -pub const BLS_KEY_LEN: usize = 48; +/// The `L` value used in the `hdkf_mod_r` function. +/// +/// In EIP-2333 this value is defined as: +/// +/// `ceil((1.5 * ceil(log2(r))) / 8)` +pub const MOD_R_L: usize = 48; +/// Derives the "master" BLS secret key from some `seed` bytes. +/// +/// Equivalent to `derive_master_SK` in EIP-2333. fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { hkdf_mod_r(seed) } +/// From the given `parent_sk`, derives a child key at index`. +/// +/// Equivalent to `derive_child_SK` in EIP-2333. fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); hkdf_mod_r(&compressed_lamport_pk) } +/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// BLS private key within the order of the BLS-381 curve. +/// +/// Equivalent to `HKDF_mod_r` in EIP-2333. fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - // TODO: justify 48. - let okm = &hkdf_expand(&prk, BLS_KEY_LEN); + let okm = &hkdf_expand(&prk, MOD_R_L); mod_r(&okm) } +/// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the +/// BLS-381 curve. +/// +/// This function is a part of the `HKDF_mod_r` function in EIP-2333. fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); @@ -39,6 +63,9 @@ fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { output } +/// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). +/// +/// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); @@ -73,48 +100,42 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { compressed_lamport_pk } +/// Generates a Lamport secret key from the `ikm` (initial key material). +/// +/// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); LamportSecretKey::from_bytes(&okm) } +/// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { let mut prk = [0; HASH_SIZE]; crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); prk } +/// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. +/// +/// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). fn hkdf_expand(prk: &[u8], l: usize) -> Vec { let mut okm = vec![0; l]; crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); okm } -/* -fn hkdf_expand(prk: &[u8]) -> LamportSecretKey { - let mut okm = LamportSecretKey::zero(); - - for i in 0..LAMPORT_ARRAY_SIZE { - let mut hasher = Sha256::new(); - - hasher.input(prk); - - if let Some(prev) = i.checked_sub(1) { - hasher.input(okm.get_chunk(prev)) - } - - hasher.input(&[i + 1]); - - hasher.result(okm.get_mut_chunk(i)); - } - - okm -} -*/ - +/// Flips each bit in the `input`. +/// +/// Equivalent to `flip_bits` in EIP-2333. +/// +/// ## Panics +/// +/// If `input` is not32-bytes. fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { - debug_assert_eq!(input.len(), HASH_SIZE); + assert_eq!(input.len(), HASH_SIZE); let mut output = [0; HASH_SIZE]; @@ -129,6 +150,7 @@ fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { mod test { use super::*; + /// Contains the test vectors in a format that's easy for us to test against. struct TestVector { seed: Vec, master_sk: Vec, @@ -139,6 +161,9 @@ mod test { child_sk: Vec, } + /// "Test Vector with Intermediate values" from: + /// + /// https://eips.ethereum.org/EIPS/eip-2333 #[test] fn eip2333_intermediate_vector() { let vectors = TestVector::from(get_raw_vector()); @@ -188,6 +213,7 @@ mod test { ); } + /// Struct to deal with easy copy-paste from specification test vectors. struct RawTestVector { seed: &'static str, master_sk: &'static str, @@ -198,16 +224,20 @@ mod test { child_sk: &'static str, } + /// Converts 0x-prefixed hex to bytes. fn hex_to_vec(hex: &str) -> Vec { hex::decode(&hex[2..]).expect("should decode hex as vec") } + /// Converts an integer represented as a string to a big-endian byte array. fn int_to_vec(int_str: &str) -> Vec { BigUint::parse_bytes(int_str.as_bytes(), 10) .expect("must be able to parse int") .to_bytes_be() } + /// Converts from a format that's easy to copy-paste from the spec into a format that's easy to + /// test with. impl From for TestVector { fn from(raw: RawTestVector) -> TestVector { TestVector { @@ -222,6 +252,7 @@ mod test { } } + /// Returns the copy-paste values from the spec. fn get_raw_vector() -> RawTestVector { RawTestVector { seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", From bd8bb2bf1e805a31ab95a778670cbe9134c6fb65 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:06:17 +1000 Subject: [PATCH 101/118] Add DerivedKey structs --- eth2/utils/eth2_keystore/src/lib.rs | 2 -- eth2/utils/eth2_keystore/src/path.rs | 25 +++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index b6c20c646b3..3ecd38e0d4b 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,9 +3,7 @@ mod derived_key; mod keystore; -mod lamport_secret_key; mod password; -mod path; mod plain_text; pub mod json_keystore; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_keystore/src/path.rs index 4060d78ff51..08d077a87f0 100644 --- a/eth2/utils/eth2_keystore/src/path.rs +++ b/eth2/utils/eth2_keystore/src/path.rs @@ -1,5 +1,5 @@ -use crate::{lamport_secret_key::LamportSecretKey, plain_text::PlainText}; -use crypto::{digest::Digest, hmac::Hmac, mac::Mac, sha2::Sha256}; +use crate::lamport_secret_key::LamportSecretKey; +use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; /// The byte size of a SHA256 hash. @@ -22,6 +22,27 @@ pub const R: &str = "52435875175126190479447740508185965837690552500527637822603 /// `ceil((1.5 * ceil(log2(r))) / 8)` pub const MOD_R_L: usize = 48; +/// A BLS secret key that is derived from some `seed`, or generated as a child from some other +/// `DerivedKey`. +pub struct DerivedKey([u8; HASH_SIZE]); + +impl DerivedKey { + /// Instantiates `Self` from some seed of any length. + pub fn from_seed(seed: &[u8]) -> Self { + Self(derive_master_sk(seed)) + } + + /// Derives a child key from the secret `Self` at some `index`. + pub fn derive_child(&self, index: u32) -> DerivedKey { + Self(derive_child_sk(&self.0, index)) + } + + /// Returns the secret BLS key in `self`. + pub fn secret(&self) -> &[u8] { + &self.0 + } +} + /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. From 73ef2b3a32bd0896f3e676098c79b85b58fa04b1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 6 May 2020 18:21:27 +1000 Subject: [PATCH 102/118] Move key derivation into own crate --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + eth2/utils/eth2_key_derivation/Cargo.toml | 15 +++++++++++++++ .../src/lamport_secret_key.rs | 4 ---- eth2/utils/eth2_key_derivation/src/lib.rs | 7 +++++++ .../src/path.rs | 0 6 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 eth2/utils/eth2_key_derivation/Cargo.toml rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/lamport_secret_key.rs (93%) create mode 100644 eth2/utils/eth2_key_derivation/src/lib.rs rename eth2/utils/{eth2_keystore => eth2_key_derivation}/src/path.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b32c2057a7d..d19862eb5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,16 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth2_keystore" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 593bcd255a6..b2ee8e1120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/deposit_contract", "eth2/utils/eth2_config", "eth2/utils/eth2_interop_keypairs", + "eth2/utils/eth2_key_derivation", "eth2/utils/eth2_keystore", "eth2/utils/eth2_testnet_config", "eth2/utils/logging", diff --git a/eth2/utils/eth2_key_derivation/Cargo.toml b/eth2/utils/eth2_key_derivation/Cargo.toml new file mode 100644 index 00000000000..61bfb5b8166 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "eth2_key_derivation" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "0.2.36" +zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +num-bigint = "0.2.6" + +[dev-dependencies] +hex = "0.3" diff --git a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs similarity index 93% rename from eth2/utils/eth2_keystore/src/lamport_secret_key.rs rename to eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 01329d7ede9..e3a8a93990d 100644 --- a/eth2/utils/eth2_keystore/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -35,10 +35,6 @@ impl LamportSecretKey { this } - pub fn get_chunk(&self, i: u8) -> &[u8] { - &self.0[i as usize] - } - pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs new file mode 100644 index 00000000000..d25469000e9 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -0,0 +1,7 @@ +//! Provides a JSON keystore for a BLS keypair, as specified by +//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). + +mod lamport_secret_key; +mod path; + +pub use path::DerivedKey; diff --git a/eth2/utils/eth2_keystore/src/path.rs b/eth2/utils/eth2_key_derivation/src/path.rs similarity index 100% rename from eth2/utils/eth2_keystore/src/path.rs rename to eth2/utils/eth2_key_derivation/src/path.rs From 4736f8c35db650a3fae4488224b30e8a4a63157f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 10:03:27 +1000 Subject: [PATCH 103/118] Add zeroize structs --- .../src/{path.rs => derived_key.rs} | 90 ++++++++++--------- .../src/lamport_secret_key.rs | 2 +- eth2/utils/eth2_key_derivation/src/lib.rs | 10 ++- .../eth2_key_derivation/src/secret_bytes.rs | 23 +++++ .../eth2_key_derivation/src/secret_hash.rs | 22 +++++ 5 files changed, 98 insertions(+), 49 deletions(-) rename eth2/utils/eth2_key_derivation/src/{path.rs => derived_key.rs} (95%) create mode 100644 eth2/utils/eth2_key_derivation/src/secret_bytes.rs create mode 100644 eth2/utils/eth2_key_derivation/src/secret_hash.rs diff --git a/eth2/utils/eth2_key_derivation/src/path.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs similarity index 95% rename from eth2/utils/eth2_key_derivation/src/path.rs rename to eth2/utils/eth2_key_derivation/src/derived_key.rs index 08d077a87f0..cd1a5b65e2c 100644 --- a/eth2/utils/eth2_key_derivation/src/path.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -1,4 +1,6 @@ -use crate::lamport_secret_key::LamportSecretKey; +use crate::{ + lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, secret_hash::SecretHash, +}; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; @@ -24,7 +26,7 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. -pub struct DerivedKey([u8; HASH_SIZE]); +pub struct DerivedKey(SecretHash); impl DerivedKey { /// Instantiates `Self` from some seed of any length. @@ -34,69 +36,70 @@ impl DerivedKey { /// Derives a child key from the secret `Self` at some `index`. pub fn derive_child(&self, index: u32) -> DerivedKey { - Self(derive_child_sk(&self.0, index)) + Self(derive_child_sk(self.0.as_bytes(), index)) } /// Returns the secret BLS key in `self`. pub fn secret(&self) -> &[u8] { - &self.0 + self.0.as_bytes() } } /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. -fn derive_master_sk(seed: &[u8]) -> [u8; HASH_SIZE] { +fn derive_master_sk(seed: &[u8]) -> SecretHash { hkdf_mod_r(seed) } /// From the given `parent_sk`, derives a child key at index`. /// /// Equivalent to `derive_child_SK` in EIP-2333. -fn derive_child_sk(parent_sk: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); - hkdf_mod_r(&compressed_lamport_pk) + hkdf_mod_r(compressed_lamport_pk.as_bytes()) } -/// From the `ikm` (initial key material), performs a HKDF_Extract and HKDF_Expand and generates a +/// From the `ikm` (initial key material), performs a HKDF-Extract and HKDF-Expand to generate a /// BLS private key within the order of the BLS-381 curve. /// /// Equivalent to `HKDF_mod_r` in EIP-2333. -fn hkdf_mod_r(ikm: &[u8]) -> [u8; HASH_SIZE] { +fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { let prk = hkdf_extract("BLS-SIG-KEYGEN-SALT-".as_bytes(), ikm); - let okm = &hkdf_expand(&prk, MOD_R_L); - mod_r(&okm) + let okm = &hkdf_expand(prk.as_bytes(), MOD_R_L); + mod_r(okm.as_bytes()) } /// Interprets `bytes` as a big-endian integer and returns that integer modulo the order of the /// BLS-381 curve. /// /// This function is a part of the `HKDF_mod_r` function in EIP-2333. -fn mod_r(bytes: &[u8]) -> [u8; HASH_SIZE] { +fn mod_r(bytes: &[u8]) -> SecretHash { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = (n % r).to_bytes_be(); debug_assert!(x.len() <= HASH_SIZE); - let mut output = [0; HASH_SIZE]; - output[HASH_SIZE - x.len()..].copy_from_slice(&x); + let mut output = SecretHash::zero(); + output.as_mut_bytes()[HASH_SIZE - x.len()..].copy_from_slice(&x); output } /// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). /// /// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. -fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); let lamports = [ ikm_to_lamport_sk(&salt, ikm), - ikm_to_lamport_sk(&salt, ¬_ikm), + ikm_to_lamport_sk(&salt, not_ikm.as_bytes()), ]; - let mut lamport_pk = vec![0; HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2]; + let mut lamport_pk = SecretBytes::zero(HASH_SIZE * LAMPORT_ARRAY_SIZE as usize * 2); + let pk_bytes = lamport_pk.as_mut_bytes(); lamports .iter() @@ -104,19 +107,15 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { .flatten() .enumerate() .for_each(|(i, chunk)| { - let output_slice = lamport_pk - .get_mut(i * HASH_SIZE..(i + 1) * HASH_SIZE) - .expect("lamport_pk must have adequate capacity"); - let mut hasher = Sha256::new(); hasher.input(chunk); - hasher.result(output_slice); + hasher.result(&mut pk_bytes[i * HASH_SIZE..(i + 1) * HASH_SIZE]); }); - let mut compressed_lamport_pk = [0; HASH_SIZE]; + let mut compressed_lamport_pk = SecretHash::zero(); let mut hasher = Sha256::new(); - hasher.input(&lamport_pk); - hasher.result(&mut compressed_lamport_pk); + hasher.input(lamport_pk.as_bytes()); + hasher.result(compressed_lamport_pk.as_mut_bytes()); compressed_lamport_pk } @@ -126,25 +125,25 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> [u8; HASH_SIZE] { /// Equivalent to `IKM_to_lamport_SK` in EIP-2333. fn ikm_to_lamport_sk(salt: &[u8], ikm: &[u8]) -> LamportSecretKey { let prk = hkdf_extract(salt, ikm); - let okm = hkdf_expand(&prk, HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); - LamportSecretKey::from_bytes(&okm) + let okm = hkdf_expand(prk.as_bytes(), HASH_SIZE * LAMPORT_ARRAY_SIZE as usize); + LamportSecretKey::from_bytes(okm.as_bytes()) } /// Peforms a `HKDF-Extract` on the `ikm` (initial key material) based up on the `salt`. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; HASH_SIZE] { - let mut prk = [0; HASH_SIZE]; - crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, &mut prk); +fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> SecretHash { + let mut prk = SecretHash::zero(); + crypto::hkdf::hkdf_extract(Sha256::new(), salt, ikm, prk.as_mut_bytes()); prk } /// Peforms a `HKDF-Expand` on the `pkr` (pseudo-random key), returning `l` bytes. /// /// Defined in [RFC5869](https://tools.ietf.org/html/rfc5869). -fn hkdf_expand(prk: &[u8], l: usize) -> Vec { - let mut okm = vec![0; l]; - crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], &mut okm); +fn hkdf_expand(prk: &[u8], l: usize) -> SecretBytes { + let mut okm = SecretBytes::zero(l); + crypto::hkdf::hkdf_expand(Sha256::new(), prk, &[], okm.as_mut_bytes()); okm } @@ -154,14 +153,15 @@ fn hkdf_expand(prk: &[u8], l: usize) -> Vec { /// /// ## Panics /// -/// If `input` is not32-bytes. -fn flip_bits(input: &[u8]) -> [u8; HASH_SIZE] { +/// If `input` is not 32-bytes. +fn flip_bits(input: &[u8]) -> SecretHash { assert_eq!(input.len(), HASH_SIZE); - let mut output = [0; HASH_SIZE]; + let mut output = SecretHash::zero(); + let output_bytes = output.as_mut_bytes(); for (i, byte) in input.iter().enumerate() { - output[i] = !byte + output_bytes[i] = !byte } output @@ -191,12 +191,13 @@ mod test { let master_sk = derive_master_sk(&vectors.seed); assert_eq!( - &master_sk[..], + master_sk.as_bytes(), &vectors.master_sk[..], "master_sk should match" ); - let lamport_0 = ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], &master_sk); + let lamport_0 = + ikm_to_lamport_sk(&vectors.child_index.to_be_bytes()[..], master_sk.as_bytes()); assert_eq!( lamport_0 .iter_chunks() @@ -208,7 +209,7 @@ mod test { let lamport_1 = ikm_to_lamport_sk( &vectors.child_index.to_be_bytes()[..], - &flip_bits(&master_sk), + flip_bits(master_sk.as_bytes()).as_bytes(), ); assert_eq!( lamport_1 @@ -219,16 +220,17 @@ mod test { "lamport_1 should match" ); - let compressed_lamport_pk = parent_sk_to_lamport_pk(&master_sk, vectors.child_index); + let compressed_lamport_pk = + parent_sk_to_lamport_pk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &compressed_lamport_pk[..], + compressed_lamport_pk.as_bytes(), &vectors.compressed_lamport_pk[..], "compressed_lamport_pk should match" ); - let child_sk = derive_child_sk(&master_sk, vectors.child_index); + let child_sk = derive_child_sk(master_sk.as_bytes(), vectors.child_index); assert_eq!( - &child_sk[..], + child_sk.as_bytes(), &vectors.child_sk[..], "child_sk should match" ); diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index e3a8a93990d..2c64601e505 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -1,4 +1,4 @@ -use crate::path::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; +use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; diff --git a/eth2/utils/eth2_key_derivation/src/lib.rs b/eth2/utils/eth2_key_derivation/src/lib.rs index d25469000e9..f6ed44c4df5 100644 --- a/eth2/utils/eth2_key_derivation/src/lib.rs +++ b/eth2/utils/eth2_key_derivation/src/lib.rs @@ -1,7 +1,9 @@ -//! Provides a JSON keystore for a BLS keypair, as specified by -//! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). +//! Provides path-based hierarchical BLS key derivation, as specified by +//! [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +mod derived_key; mod lamport_secret_key; -mod path; +mod secret_bytes; +mod secret_hash; -pub use path::DerivedKey; +pub use derived_key::DerivedKey; diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs new file mode 100644 index 00000000000..c4eee4b8f52 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -0,0 +1,23 @@ +use zeroize::Zeroize; + +/// Provides a wrapper around a `Vec` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + /// Instantiates `Self` with an all-zeros byte array of length `len`. + pub fn zero(len: usize) -> Self { + Self(vec![0; len]) + } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs new file mode 100644 index 00000000000..9c1bbb27b8b --- /dev/null +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -0,0 +1,22 @@ +use crate::derived_key::HASH_SIZE; +use zeroize::Zeroize; + +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SecretHash([u8; HASH_SIZE]); + +impl SecretHash { + /// Instantiates `Self` with all zeros. + pub fn zero() -> Self { + Self([0; HASH_SIZE]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } +} From 11294180a782a6a432ca47ae5bd292c618667e82 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:15 +1000 Subject: [PATCH 104/118] Return error for empty seed --- .../eth2_key_derivation/src/derived_key.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index cd1a5b65e2c..ea1eb1fe0b1 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -29,13 +29,23 @@ pub const MOD_R_L: usize = 48; pub struct DerivedKey(SecretHash); impl DerivedKey { - /// Instantiates `Self` from some seed of any length. - pub fn from_seed(seed: &[u8]) -> Self { - Self(derive_master_sk(seed)) + /// Instantiates `Self` from some secret seed bytes. + /// + /// The key is generated deterministically; the same `seed` will always return the same `Self`. + /// + /// ## Errors + /// + /// Returns `Err(())` if `seed.is_empty()`, otherwise always returns `Ok(self)`. + pub fn from_seed(seed: &[u8]) -> Result { + if seed.is_empty() { + Err(()) + } else { + Ok(Self(derive_master_sk(seed))) + } } /// Derives a child key from the secret `Self` at some `index`. - pub fn derive_child(&self, index: u32) -> DerivedKey { + pub fn child(&self, index: u32) -> DerivedKey { Self(derive_child_sk(self.0.as_bytes(), index)) } From 7e68e5d6e8aa5f9b9ec7e23f640d938c03ab11f6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:47:33 +1000 Subject: [PATCH 105/118] Add tests --- .../tests/eip2333_vectors.rs | 102 ++++++++++++++++++ eth2/utils/eth2_key_derivation/tests/tests.rs | 28 +++++ 2 files changed, 130 insertions(+) create mode 100644 eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs create mode 100644 eth2/utils/eth2_key_derivation/tests/tests.rs diff --git a/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs new file mode 100644 index 00000000000..42a3728b291 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/eip2333_vectors.rs @@ -0,0 +1,102 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; +use num_bigint::BigUint; + +/// Contains the test vectors in a format that's easy for us to test against. +struct TestVector { + seed: Vec, + master_sk: Vec, + child_index: u32, + child_sk: Vec, +} + +/// Struct to deal with easy copy-paste from specification test vectors. +struct RawTestVector { + seed: &'static str, + master_sk: &'static str, + child_index: u32, + child_sk: &'static str, +} + +/// Converts from a format that's easy to copy-paste from the spec into a format that's easy to +/// test with. +impl From for TestVector { + fn from(raw: RawTestVector) -> TestVector { + TestVector { + seed: hex_to_vec(raw.seed), + master_sk: int_to_vec(raw.master_sk), + child_index: raw.child_index, + child_sk: int_to_vec(raw.child_sk), + } + } +} + +/// Converts 0x-prefixed hex to bytes. +fn hex_to_vec(hex: &str) -> Vec { + hex::decode(&hex[2..]).expect("should decode hex as vec") +} + +/// Converts an integer represented as a string to a big-endian byte array. +fn int_to_vec(int_str: &str) -> Vec { + BigUint::parse_bytes(int_str.as_bytes(), 10) + .expect("must be able to parse int") + .to_bytes_be() +} + +/// Asserts that our code matches the given test vector. +fn assert_vector_passes(raw: RawTestVector) { + let vector: TestVector = raw.into(); + + let master = DerivedKey::from_seed(&vector.seed).unwrap(); + assert_eq!(master.secret(), &vector.master_sk[..], "master"); + + let child = master.child(vector.child_index); + assert_eq!(child.secret(), &vector.child_sk[..], "child"); +} + +/* + * The following test vectors are obtained from: + * + * https://eips.ethereum.org/EIPS/eip-2333 + */ + +#[test] +fn eip2333_test_case_0() { + assert_vector_passes(RawTestVector { + seed: "0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + master_sk: "12513733877922233913083619867448865075222526338446857121953625441395088009793", + child_index: 0, + child_sk: "7419543105316279183937430842449358701327973165530407166294956473095303972104" + }) +} + +#[test] +fn eip2333_test_case_1() { + assert_vector_passes(RawTestVector { + seed: "0x3141592653589793238462643383279502884197169399375105820974944592", + master_sk: "46029459550803682895343812821003080589696405386150182061394330539196052371668", + child_index: 3141592653, + child_sk: "43469287647733616183478983885105537266268532274998688773496918571876759327260", + }) +} + +#[test] +fn eip2333_test_case_2() { + assert_vector_passes(RawTestVector { + seed: "0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00", + master_sk: "45379166311535261329029945990467475187325618028073620882733843918126031931161", + child_index: 4294967295, + child_sk: "46475244006136701976831062271444482037125148379128114617927607151318277762946", + }) +} + +#[test] +fn eip2333_test_case_3() { + assert_vector_passes(RawTestVector { + seed: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + master_sk: "31740500954810567003972734830331791822878290325762596213711963944729383643688", + child_index: 42, + child_sk: "51041472511529980987749393477251359993058329222191894694692317000136653813011", + }) +} diff --git a/eth2/utils/eth2_key_derivation/tests/tests.rs b/eth2/utils/eth2_key_derivation/tests/tests.rs new file mode 100644 index 00000000000..b18a7b0e267 --- /dev/null +++ b/eth2/utils/eth2_key_derivation/tests/tests.rs @@ -0,0 +1,28 @@ +#![cfg(test)] + +use eth2_key_derivation::DerivedKey; + +#[test] +fn empty_seed() { + assert!( + DerivedKey::from_seed(&[]).is_err(), + "empty seed should fail" + ); +} + +#[test] +fn deterministic() { + assert_eq!( + DerivedKey::from_seed(&[42]).unwrap().secret(), + DerivedKey::from_seed(&[42]).unwrap().secret() + ); +} + +#[test] +fn children_deterministic() { + let master = DerivedKey::from_seed(&[42]).unwrap(); + assert_eq!( + master.child(u32::max_value()).secret(), + master.child(u32::max_value()).secret(), + ) +} From cf2972e97f35677f7f45795e52690c1acfba5ae4 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 15:55:30 +1000 Subject: [PATCH 106/118] Tidy --- eth2/utils/eth2_key_derivation/src/derived_key.rs | 7 +++++++ .../eth2_key_derivation/src/lamport_secret_key.rs | 5 +++++ eth2/utils/eth2_key_derivation/src/secret_bytes.rs | 12 ++++++------ eth2/utils/eth2_key_derivation/src/secret_hash.rs | 4 +++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/eth2/utils/eth2_key_derivation/src/derived_key.rs b/eth2/utils/eth2_key_derivation/src/derived_key.rs index ea1eb1fe0b1..b91f0bff800 100644 --- a/eth2/utils/eth2_key_derivation/src/derived_key.rs +++ b/eth2/utils/eth2_key_derivation/src/derived_key.rs @@ -3,6 +3,7 @@ use crate::{ }; use crypto::{digest::Digest, sha2::Sha256}; use num_bigint::BigUint; +use zeroize::Zeroize; /// The byte size of a SHA256 hash. pub const HASH_SIZE: usize = 32; @@ -26,6 +27,12 @@ pub const MOD_R_L: usize = 48; /// A BLS secret key that is derived from some `seed`, or generated as a child from some other /// `DerivedKey`. +/// +/// Implements `Zeroize` on `Drop`. +// It's not strictly necessary that `DerivedKey` implements `Zeroize`, but it seems prudent to be a +// little over-cautious here; we don't require high-speed key generation at this stage. +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DerivedKey(SecretHash); impl DerivedKey { diff --git a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs index 2c64601e505..aa6dbb39323 100644 --- a/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs +++ b/eth2/utils/eth2_key_derivation/src/lamport_secret_key.rs @@ -2,6 +2,9 @@ use crate::derived_key::{HASH_SIZE, LAMPORT_ARRAY_SIZE}; use std::iter::Iterator; use zeroize::Zeroize; +/// A Lamport secret key as specified in [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333). +/// +/// Implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct LamportSecretKey(Vec<[u8; HASH_SIZE]>); @@ -35,10 +38,12 @@ impl LamportSecretKey { this } + /// Returns a reference to the `i`th `HASH_SIZE` chunk of `self`. pub fn get_mut_chunk(&mut self, i: u8) -> &mut [u8] { &mut self.0[i as usize] } + /// Returns an iterator over `LAMPORT_ARRAY_SIZE` chunks of `HASH_SIZE` bytes. pub fn iter_chunks(&self) -> impl Iterator { self.0.iter() } diff --git a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs index c4eee4b8f52..5cedd05b1d3 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_bytes.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_bytes.rs @@ -1,6 +1,6 @@ use zeroize::Zeroize; -/// Provides a wrapper around a `Vec` that implements `Zeroize`. +/// Provides a wrapper around a `Vec` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretBytes(Vec); @@ -11,13 +11,13 @@ impl SecretBytes { Self(vec![0; len]) } - /// Returns a mutable reference to the underlying bytes. - pub fn as_mut_bytes(&mut self) -> &mut [u8] { - &mut self.0 - } - /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + + /// Returns a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } } diff --git a/eth2/utils/eth2_key_derivation/src/secret_hash.rs b/eth2/utils/eth2_key_derivation/src/secret_hash.rs index 9c1bbb27b8b..0d9cfac1100 100644 --- a/eth2/utils/eth2_key_derivation/src/secret_hash.rs +++ b/eth2/utils/eth2_key_derivation/src/secret_hash.rs @@ -1,7 +1,7 @@ use crate::derived_key::HASH_SIZE; use zeroize::Zeroize; -/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize`. +/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] pub struct SecretHash([u8; HASH_SIZE]); @@ -12,10 +12,12 @@ impl SecretHash { Self([0; HASH_SIZE]) } + /// Returns a reference to the underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } + /// Returns a mutable reference to the underlying bytes. pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 } From 769fcbfbd4b846200249a300a259b98527ab69ec Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:26:49 +1000 Subject: [PATCH 107/118] Add builder --- Cargo.lock | 1 + eth2/utils/eth2_wallet/Cargo.toml | 1 + eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 4 +- eth2/utils/eth2_wallet/src/wallet.rs | 72 +++++++++++++++++-- 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe2c37cd8ff..fceecc55751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,6 +1280,7 @@ version = "0.1.0" dependencies = [ "eth2_key_derivation 0.1.0", "eth2_keystore 0.1.0", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_wallet/Cargo.toml b/eth2/utils/eth2_wallet/Cargo.toml index 6471aceb2dd..196825a1dec 100644 --- a/eth2/utils/eth2_wallet/Cargo.toml +++ b/eth2/utils/eth2_wallet/Cargo.toml @@ -11,5 +11,6 @@ serde = "1.0.102" serde_json = "1.0.41" serde_repr = "0.1" uuid = { version = "0.8", features = ["serde", "v4"] } +rand = "0.7.2" eth2_keystore = { path = "../eth2_keystore" } eth2_key_derivation = { path = "../eth2_key_derivation" } diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs index 18646b2b5ba..4eb4fc1a2eb 100644 --- a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; use serde_repr::*; pub use eth2_keystore::json_keystore::{ - ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, Kdf, KdfModule, - Sha256Checksum, + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, Kdf, KdfModule, + Scrypt, Sha256Checksum, }; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index b7bbe0742a8..f4626d9648d 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -1,12 +1,71 @@ use crate::json_wallet::{ - ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, Kdf, - KdfModule, Sha256Checksum, Version, + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, + Kdf, KdfModule, Scrypt, Sha256Checksum, Version, }; -use eth2_keystore::encrypt; +use eth2_keystore::{decrypt, encrypt}; +use rand::prelude::*; use uuid::Uuid; pub use eth2_keystore::{Error, Password, PlainText}; +/// Constructs a `Keystore`. +pub struct WalletBuilder<'a> { + seed: &'a [u8], + password: &'a [u8], + kdf: Kdf, + cipher: Cipher, + uuid: Uuid, + name: String, + nextaccount: u32, +} + +impl<'a> WalletBuilder<'a> { + /// Creates a new builder. + /// + /// Generates the KDF `salt` and AES `IV` using `rand::thread_rng()`. + /// + /// ## Errors + /// + /// Returns `Error::EmptyPassword` if `password == ""`. + pub fn from_seed(seed: &'a [u8], password: &[u8], name: String) -> Result { + if password.is_empty() { + Err(Error::EmptyPassword) + } else { + let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); + let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); + + Ok(Self { + seed, + password, + // Using scrypt as the default algorithm due to its memory hardness properties. + kdf: Kdf::Scrypt(Scrypt { + dklen: DKLEN, + n: 262144, + p: 1, + r: 8, + salt: salt.to_vec().into(), + }), + cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), + uuid: Uuid::new_v4(), + name, + }) + } + } + + /// Consumes `self`, returning a `Wallet`. + pub fn build(self) -> Result { + Wallet::encrypt( + self.seed, + self.password, + self.kdf, + self.cipher, + self.uuid, + self.name, + self.nextaccount, + ) + } +} + pub struct Wallet { json: JsonWallet, } @@ -19,6 +78,7 @@ impl Wallet { cipher: Cipher, uuid: Uuid, name: String, + next_account: u32, ) -> Result { let (cipher_text, checksum) = encrypt(&seed, &password, &kdf, &cipher)?; @@ -42,12 +102,14 @@ impl Wallet { }, }, uuid, - nextaccount: 0, + nextaccount, version: Version::one(), name, }, }) } - pub fn decrypt_seed(&self, password: &[u8]) -> Result<> + pub fn decrypt_seed(&self, password: &[u8]) -> Result { + decrypt(password, &self.json.crypto) + } } From e43eb83def8e9ac810e0f3ef0725b0840be887c9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:34:31 +1000 Subject: [PATCH 108/118] Expose consts, remove Password --- eth2/utils/eth2_keystore/src/keystore.rs | 41 +++++++++++-------- eth2/utils/eth2_keystore/src/lib.rs | 6 +-- eth2/utils/eth2_keystore/src/password.rs | 30 -------------- .../eth2_keystore/tests/eip2335_vectors.rs | 2 +- eth2/utils/eth2_keystore/tests/json.rs | 2 +- eth2/utils/eth2_keystore/tests/params.rs | 4 +- eth2/utils/eth2_keystore/tests/tests.rs | 31 ++++++-------- 7 files changed, 43 insertions(+), 73 deletions(-) delete mode 100644 eth2/utils/eth2_keystore/src/password.rs diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index ef7e07f2d75..fd6f0908dc8 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -7,7 +7,6 @@ use crate::json_keystore::{ Kdf, KdfModule, Scrypt, Sha256Checksum, Version, }; use crate::plain_text::PlainText; -use crate::Password; use crate::Uuid; use bls::{Keypair, PublicKey, SecretKey}; use crypto::{digest::Digest, sha2::Sha256}; @@ -24,7 +23,7 @@ const SECRET_KEY_LEN: usize = 32; /// [pbkdf2](https://www.ietf.org/rfc/rfc2898.txt) or [scrypt](https://tools.ietf.org/html/rfc7914) /// make a clear statement about what size it should be, however 32-bytes certainly seems /// reasonable and larger than the EITF examples. -const SALT_SIZE: usize = 32; +pub const SALT_SIZE: usize = 32; /// The length of the derived key. pub const DKLEN: u32 = 32; /// Size of the IV (initialization vector) used for aes-128-ctr encryption of private key material. @@ -42,9 +41,9 @@ pub const DKLEN: u32 = 32; /// As far as I know, AES-128-CTR is not defined by the IETF, but by NIST in SP800-38A. /// (https://csrc.nist.gov/publications/detail/sp/800-38a/final) The test vectors in this standard /// are 16 bytes. -const IV_SIZE: usize = 16; +pub const IV_SIZE: usize = 16; /// The byte size of a SHA256 hash. -const HASH_SIZE: usize = 32; +pub const HASH_SIZE: usize = 32; #[derive(Debug, PartialEq)] pub enum Error { @@ -65,7 +64,7 @@ pub enum Error { /// Constructs a `Keystore`. pub struct KeystoreBuilder<'a> { keypair: &'a Keypair, - password: Password, + password: &'a [u8], kdf: Kdf, cipher: Cipher, uuid: Uuid, @@ -80,8 +79,8 @@ impl<'a> KeystoreBuilder<'a> { /// ## Errors /// /// Returns `Error::EmptyPassword` if `password == ""`. - pub fn new(keypair: &'a Keypair, password: Password, path: String) -> Result { - if password.as_str() == "" { + pub fn new(keypair: &'a Keypair, password: &'a [u8], path: String) -> Result { + if password.is_empty() { Err(Error::EmptyPassword) } else { let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); @@ -90,14 +89,7 @@ impl<'a> KeystoreBuilder<'a> { Ok(Self { keypair, password, - // Using scrypt as the default algorithm due to its memory hardness properties. - kdf: Kdf::Scrypt(Scrypt { - dklen: DKLEN, - n: 262144, - p: 1, - r: 8, - salt: salt.to_vec().into(), - }), + kdf: default_kdf(salt.to_vec()), cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), uuid: Uuid::new_v4(), path, @@ -109,7 +101,7 @@ impl<'a> KeystoreBuilder<'a> { pub fn build(self) -> Result { Keystore::encrypt( self.keypair, - self.password.as_bytes(), + self.password, self.kdf, self.cipher, self.uuid, @@ -175,8 +167,8 @@ impl Keystore { /// /// - The provided password is incorrect. /// - The keystore is badly formed. - pub fn decrypt_keypair(&self, password: Password) -> Result { - let plain_text = decrypt(password.as_bytes(), &self.json.crypto)?; + pub fn decrypt_keypair(&self, password: &[u8]) -> Result { + let plain_text = decrypt(password, &self.json.crypto)?; // Verify that secret key material is correct length. if plain_text.len() != SECRET_KEY_LEN { @@ -234,6 +226,19 @@ impl Keystore { } } +/// Returns `Kdf` used by default when creating keystores. +/// +/// Currently this is set to scrypt due to its memory hardness properties. +pub fn default_kdf(salt: Vec) -> Kdf { + Kdf::Scrypt(Scrypt { + dklen: DKLEN, + n: 262144, + p: 1, + r: 8, + salt: salt.into(), + }) +} + /// Returns `(cipher_text, checksum)` for the given `plain_text` encrypted with `Cipher` using a /// key derived from `password` via the `Kdf` (key derivation function). /// diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index 3ecd38e0d4b..c3b6ba6e2a4 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -3,12 +3,12 @@ mod derived_key; mod keystore; -mod password; mod plain_text; pub mod json_keystore; -pub use keystore::{decrypt, encrypt, Error, Keystore, KeystoreBuilder}; -pub use password::Password; +pub use keystore::{ + decrypt, default_kdf, encrypt, Error, Keystore, KeystoreBuilder, DKLEN, HASH_SIZE, IV_SIZE, +}; pub use plain_text::PlainText; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_keystore/src/password.rs b/eth2/utils/eth2_keystore/src/password.rs deleted file mode 100644 index d616d5054b3..00000000000 --- a/eth2/utils/eth2_keystore/src/password.rs +++ /dev/null @@ -1,30 +0,0 @@ -use zeroize::Zeroize; - -/// Provides a wrapper around `String` that implements `Zeroize`. -#[derive(Zeroize, Clone, PartialEq)] -#[zeroize(drop)] -pub struct Password(String); - -impl Password { - /// Returns a reference to the underlying `String`. - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Returns a reference to the underlying `String`, as bytes. - pub fn as_bytes(&self) -> &[u8] { - self.0.as_str().as_bytes() - } -} - -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)) - } -} diff --git a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs index fc91e592105..fee3c37b72a 100644 --- a/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs +++ b/eth2/utils/eth2_keystore/tests/eip2335_vectors.rs @@ -12,7 +12,7 @@ const PASSWORD: &str = "testpassword"; pub fn decode_and_check_sk(json: &str) -> Keystore { let keystore = Keystore::from_json_str(json).expect("should decode keystore json"); let expected_sk = hex::decode(EXPECTED_SECRET).unwrap(); - let keypair = keystore.decrypt_keypair(PASSWORD.into()).unwrap(); + let keypair = keystore.decrypt_keypair(PASSWORD.as_bytes()).unwrap(); assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk); keystore } diff --git a/eth2/utils/eth2_keystore/tests/json.rs b/eth2/utils/eth2_keystore/tests/json.rs index 694e4de172e..1dec893afdd 100644 --- a/eth2/utils/eth2_keystore/tests/json.rs +++ b/eth2/utils/eth2_keystore/tests/json.rs @@ -381,7 +381,7 @@ fn json_bad_checksum() { assert_eq!( Keystore::from_json_str(&vector) .unwrap() - .decrypt_keypair("testpassword".into()) + .decrypt_keypair("testpassword".as_bytes()) .err() .unwrap(), Error::InvalidPassword diff --git a/eth2/utils/eth2_keystore/tests/params.rs b/eth2/utils/eth2_keystore/tests/params.rs index 8066e3ae377..9df536d05c7 100644 --- a/eth2/utils/eth2_keystore/tests/params.rs +++ b/eth2/utils/eth2_keystore/tests/params.rs @@ -7,7 +7,7 @@ const PASSWORD: &str = "testpassword"; fn decrypt_error(vector: &str) -> Error { Keystore::from_json_str(&vector) .unwrap() - .decrypt_keypair(PASSWORD.into()) + .decrypt_keypair(PASSWORD.as_bytes()) .err() .unwrap() } @@ -15,7 +15,7 @@ fn decrypt_error(vector: &str) -> Error { fn assert_decrypts(vector: &str) { Keystore::from_json_str(&vector) .unwrap() - .decrypt_keypair(PASSWORD.into()) + .decrypt_keypair(PASSWORD.as_bytes()) .unwrap(); } diff --git a/eth2/utils/eth2_keystore/tests/tests.rs b/eth2/utils/eth2_keystore/tests/tests.rs index 06e245be180..8c94a9f5e0b 100644 --- a/eth2/utils/eth2_keystore/tests/tests.rs +++ b/eth2/utils/eth2_keystore/tests/tests.rs @@ -1,22 +1,17 @@ #![cfg(test)] use bls::Keypair; -use eth2_keystore::{Error, Keystore, KeystoreBuilder, Password}; +use eth2_keystore::{Error, Keystore, KeystoreBuilder}; use std::fs::OpenOptions; use tempfile::tempdir; -fn good_password() -> Password { - "ilikecats".to_string().into() -} - -fn bad_password() -> Password { - "idontlikecats".to_string().into() -} +const GOOD_PASSWORD: &[u8] = &[42, 42, 42]; +const BAD_PASSWORD: &[u8] = &[43, 43, 43]; #[test] fn empty_password() { assert_eq!( - KeystoreBuilder::new(&Keypair::random(), "".into(), "".into()) + KeystoreBuilder::new(&Keypair::random(), "".as_bytes(), "".into()) .err() .unwrap(), Error::EmptyPassword @@ -27,7 +22,7 @@ fn empty_password() { fn string_round_trip() { let keypair = Keypair::random(); - let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) + let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into()) .unwrap() .build() .unwrap(); @@ -36,13 +31,13 @@ fn string_round_trip() { let decoded = Keystore::from_json_str(&json).unwrap(); assert_eq!( - decoded.decrypt_keypair(bad_password()).err().unwrap(), + decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(), Error::InvalidPassword, "should not decrypt with bad password" ); assert_eq!( - decoded.decrypt_keypair(good_password()).unwrap(), + decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(), keypair, "should decrypt with good password" ); @@ -63,7 +58,7 @@ fn file() { .expect("should create file") }; - let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) + let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into()) .unwrap() .build() .unwrap(); @@ -75,13 +70,13 @@ fn file() { let decoded = Keystore::from_json_reader(&mut get_file()).expect("should read from file"); assert_eq!( - decoded.decrypt_keypair(bad_password()).err().unwrap(), + decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(), Error::InvalidPassword, "should not decrypt with bad password" ); assert_eq!( - decoded.decrypt_keypair(good_password()).unwrap(), + decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(), keypair, "should decrypt with good password" ); @@ -91,7 +86,7 @@ fn file() { fn scrypt_params() { let keypair = Keypair::random(); - let keystore = KeystoreBuilder::new(&keypair, good_password(), "".into()) + let keystore = KeystoreBuilder::new(&keypair, GOOD_PASSWORD, "".into()) .unwrap() .build() .unwrap(); @@ -100,13 +95,13 @@ fn scrypt_params() { let decoded = Keystore::from_json_str(&json).unwrap(); assert_eq!( - decoded.decrypt_keypair(bad_password()).err().unwrap(), + decoded.decrypt_keypair(BAD_PASSWORD).err().unwrap(), Error::InvalidPassword, "should not decrypt with bad password" ); assert_eq!( - decoded.decrypt_keypair(good_password()).unwrap(), + decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(), keypair, "should decrypt with good password" ); From e01db15d5c5d82f9f8ed6319cc5d7bb1541ff06e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:39:12 +1000 Subject: [PATCH 109/118] Minor progress --- eth2/utils/eth2_wallet/src/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index f4626d9648d..d2c1691ab51 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -2,7 +2,7 @@ use crate::json_wallet::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, Kdf, KdfModule, Scrypt, Sha256Checksum, Version, }; -use eth2_keystore::{decrypt, encrypt}; +use eth2_keystore::{decrypt, encrypt, IV_SIZE, SALT_SIZE}; use rand::prelude::*; use uuid::Uuid; From 443e26f59a7c44f4d5dc718a91d989377dd2e211 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:39:46 +1000 Subject: [PATCH 110/118] Expose SALT_SIZE --- eth2/utils/eth2_keystore/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index c3b6ba6e2a4..ad536685310 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -9,6 +9,7 @@ pub mod json_keystore; pub use keystore::{ decrypt, default_kdf, encrypt, Error, Keystore, KeystoreBuilder, DKLEN, HASH_SIZE, IV_SIZE, + SALT_SIZE, }; pub use plain_text::PlainText; pub use uuid::Uuid; From 1e3eec939cb09eaea08b2d96abbb6db0b5f238df Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 18:42:33 +1000 Subject: [PATCH 111/118] First compiling version --- eth2/utils/eth2_wallet/src/lib.rs | 2 +- eth2/utils/eth2_wallet/src/wallet.rs | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index 98a2f5d04ad..e1b8b95ba58 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -2,4 +2,4 @@ mod wallet; pub mod json_wallet; -pub use wallet::{Error, Password, Wallet}; +pub use wallet::{Error, Wallet}; diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index d2c1691ab51..9562a6f43ce 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -1,12 +1,12 @@ use crate::json_wallet::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, - Kdf, KdfModule, Scrypt, Sha256Checksum, Version, + Kdf, KdfModule, Sha256Checksum, Version, }; -use eth2_keystore::{decrypt, encrypt, IV_SIZE, SALT_SIZE}; +use eth2_keystore::{decrypt, default_kdf, encrypt, IV_SIZE, SALT_SIZE}; use rand::prelude::*; use uuid::Uuid; -pub use eth2_keystore::{Error, Password, PlainText}; +pub use eth2_keystore::{Error, PlainText}; /// Constructs a `Keystore`. pub struct WalletBuilder<'a> { @@ -27,7 +27,7 @@ impl<'a> WalletBuilder<'a> { /// ## Errors /// /// Returns `Error::EmptyPassword` if `password == ""`. - pub fn from_seed(seed: &'a [u8], password: &[u8], name: String) -> Result { + pub fn from_seed(seed: &'a [u8], password: &'a [u8], name: String) -> Result { if password.is_empty() { Err(Error::EmptyPassword) } else { @@ -37,16 +37,10 @@ impl<'a> WalletBuilder<'a> { Ok(Self { seed, password, - // Using scrypt as the default algorithm due to its memory hardness properties. - kdf: Kdf::Scrypt(Scrypt { - dklen: DKLEN, - n: 262144, - p: 1, - r: 8, - salt: salt.to_vec().into(), - }), + kdf: default_kdf(salt.to_vec()), cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), uuid: Uuid::new_v4(), + nextaccount: 0, name, }) } @@ -78,7 +72,7 @@ impl Wallet { cipher: Cipher, uuid: Uuid, name: String, - next_account: u32, + nextaccount: u32, ) -> Result { let (cipher_text, checksum) = encrypt(&seed, &password, &kdf, &cipher)?; From e0a2ee5b2e671b75ef7b8dffe8e819db56b43bc5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 7 May 2020 19:12:11 +1000 Subject: [PATCH 112/118] Add test vectors --- Cargo.lock | 1 + eth2/utils/eth2_wallet/Cargo.toml | 3 + eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 29 +++++++++ eth2/utils/eth2_wallet/src/wallet.rs | 27 ++++++++- .../eth2_wallet/tests/eip2386_vectors.rs | 59 +++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 eth2/utils/eth2_wallet/tests/eip2386_vectors.rs diff --git a/Cargo.lock b/Cargo.lock index fceecc55751..c48be74476b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,6 +1280,7 @@ version = "0.1.0" dependencies = [ "eth2_key_derivation 0.1.0", "eth2_keystore 0.1.0", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/eth2/utils/eth2_wallet/Cargo.toml b/eth2/utils/eth2_wallet/Cargo.toml index 196825a1dec..c4405bdd7b5 100644 --- a/eth2/utils/eth2_wallet/Cargo.toml +++ b/eth2/utils/eth2_wallet/Cargo.toml @@ -14,3 +14,6 @@ uuid = { version = "0.8", features = ["serde", "v4"] } rand = "0.7.2" eth2_keystore = { path = "../eth2_keystore" } eth2_key_derivation = { path = "../eth2_key_derivation" } + +[dev-dependencies] +hex = "0.3" diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs index 4eb4fc1a2eb..834716fba2d 100644 --- a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use serde_repr::*; +use std::convert::TryFrom; pub use eth2_keystore::json_keystore::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, Kdf, KdfModule, @@ -15,6 +16,8 @@ pub struct JsonWallet { pub nextaccount: u32, pub uuid: Uuid, pub version: Version, + #[serde(rename = "type")] + pub type_field: TypeField, } /// Version for `JsonWallet`. @@ -29,3 +32,29 @@ impl Version { Version::V1 } } + +/// Used for ensuring that serde only decodes valid checksum functions. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum TypeField { + Hd, +} + +impl Into for TypeField { + fn into(self) -> String { + match self { + TypeField::Hd => "hierarchical deterministic".into(), + } + } +} + +impl TryFrom for TypeField { + type Error = String; + + fn try_from(s: String) -> Result { + match s.as_ref() { + "hierarchical deterministic" => Ok(TypeField::Hd), + other => Err(format!("Unsupported type function: {}", other)), + } + } +} diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index 9562a6f43ce..1c566af67b6 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -1,9 +1,11 @@ use crate::json_wallet::{ Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, - Kdf, KdfModule, Sha256Checksum, Version, + Kdf, KdfModule, Sha256Checksum, TypeField, Version, }; use eth2_keystore::{decrypt, default_kdf, encrypt, IV_SIZE, SALT_SIZE}; use rand::prelude::*; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Write}; use uuid::Uuid; pub use eth2_keystore::{Error, PlainText}; @@ -60,6 +62,8 @@ impl<'a> WalletBuilder<'a> { } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Wallet { json: JsonWallet, } @@ -98,6 +102,7 @@ impl Wallet { uuid, nextaccount, version: Version::one(), + type_field: TypeField::Hd, name, }, }) @@ -106,4 +111,24 @@ impl Wallet { pub fn decrypt_seed(&self, password: &[u8]) -> Result { decrypt(password, &self.json.crypto) } + + /// Encodes `self` as a JSON object. + pub fn to_json_string(&self) -> Result { + serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) + } + + /// Returns `self` from an encoded JSON object. + pub fn from_json_str(json_string: &str) -> Result { + serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) + } + + /// Encodes self as a JSON object to the given `writer`. + pub fn to_json_writer(&self, writer: W) -> Result<(), Error> { + serde_json::to_writer(writer, self).map_err(|e| Error::WriteError(format!("{}", e))) + } + + /// Instantiates `self` from a JSON `reader`. + pub fn from_json_reader(reader: R) -> Result { + serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) + } } diff --git a/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs new file mode 100644 index 00000000000..58896bdca59 --- /dev/null +++ b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs @@ -0,0 +1,59 @@ +use eth2_wallet::Wallet; + +const EXPECTED_SECRET: &str = "147addc7ec981eb2715a22603813271cce540e0b7f577126011eb06249d9227c"; +const PASSWORD: &str = "testpassword"; + +pub fn decode_and_check_seed(json: &str) -> Wallet { + let wallet = Wallet::from_json_str(json).expect("should decode keystore json"); + let expected_sk = hex::decode(EXPECTED_SECRET).unwrap(); + let seed = wallet.decrypt_seed(PASSWORD.as_bytes()).unwrap(); + assert_eq!(seed.as_bytes(), &expected_sk[..]); + wallet +} + +#[test] +fn eip2386_test_vector_scrypt() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "hierarchical deterministic", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 1 + } + "#; + + let wallet = decode_and_check_seed(&vector); + /* + assert_eq!( + *keystore.uuid(), + Uuid::parse_str("1d85ae20-35c5-4611-98e8-aa14a633906f").unwrap(), + "uuid" + ); + assert_eq!(keystore.path(), "", "path"); + */ +} From b7f722bfb57f88003bf5232049f9d189088c327f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 May 2020 08:40:02 +1000 Subject: [PATCH 113/118] Move dbg assert statement --- eth2/utils/eth2_keystore/src/keystore.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index fd6f0908dc8..de3577d9316 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -350,9 +350,6 @@ fn derive_key(password: &[u8], kdf: &Kdf) -> Result { ); } Kdf::Scrypt(params) => { - // Assert that `n` is power of 2. - debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); - // RFC7914 declares that all these parameters must be greater than 1: // // - `N`: costParameter. @@ -366,6 +363,9 @@ fn derive_key(password: &[u8], kdf: &Kdf) -> Result { return Err(Error::InvalidScryptParam); } + // Assert that `n` is power of 2. + debug_assert_eq!(params.n, 2u32.pow(log2_int(params.n))); + crypto::scrypt::scrypt( password, params.salt.as_bytes(), From babab0b0dfee98313caf8089cbe777826ff478be Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 May 2020 16:14:18 +1000 Subject: [PATCH 114/118] Add mnemonic, tidy --- Cargo.lock | 42 ++++++ eth2/utils/eth2_keystore/src/keystore.rs | 24 ++-- eth2/utils/eth2_keystore/src/lib.rs | 4 +- eth2/utils/eth2_wallet/Cargo.toml | 1 + eth2/utils/eth2_wallet/src/lib.rs | 4 +- eth2/utils/eth2_wallet/src/wallet.rs | 156 ++++++++++++++++++++--- 6 files changed, 201 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c48be74476b..b2580a90c6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,6 +1285,7 @@ dependencies = [ "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-bip39 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2703,6 +2704,14 @@ dependencies = [ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "oorandom" version = "11.1.0" @@ -2881,6 +2890,15 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -3447,6 +3465,11 @@ name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4141,6 +4164,21 @@ dependencies = [ "types 0.2.0", ] +[[package]] +name = "tiny-bip39" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tiny-keccak" version = "1.5.0" @@ -5317,6 +5355,7 @@ dependencies = [ "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" "checksum oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" @@ -5333,6 +5372,7 @@ dependencies = [ "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb" +"checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" @@ -5385,6 +5425,7 @@ dependencies = [ "checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" "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-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" "checksum rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" "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" @@ -5453,6 +5494,7 @@ dependencies = [ "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +"checksum tiny-bip39 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b0165e045cc2ae1660270ca65e1676dbaab60feb0f91b10f7d0665e9b47e31f2" "checksum tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2" "checksum tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" "checksum tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" diff --git a/eth2/utils/eth2_keystore/src/keystore.rs b/eth2/utils/eth2_keystore/src/keystore.rs index de3577d9316..38739b50772 100644 --- a/eth2/utils/eth2_keystore/src/keystore.rs +++ b/eth2/utils/eth2_keystore/src/keystore.rs @@ -178,19 +178,13 @@ impl Keystore { }); } - // Instantiate a `SecretKey`. - let sk = - SecretKey::from_bytes(plain_text.as_bytes()).map_err(Error::InvalidSecretKeyBytes)?; - - // Derive a `PublicKey` from `SecretKey`. - let pk = PublicKey::from_secret_key(&sk); - + let keypair = keypair_from_secret(plain_text.as_bytes())?; // Verify that the derived `PublicKey` matches `self`. - if pk.as_hex_string()[2..].to_string() != self.json.pubkey { + if keypair.pk.as_hex_string()[2..].to_string() != self.json.pubkey { return Err(Error::PublicKeyMismatch); } - Ok(Keypair { sk, pk }) + Ok(keypair) } /// Returns the UUID for the keystore. @@ -226,6 +220,18 @@ impl Keystore { } } +/// Instantiates a BLS keypair from the given `secret`. +/// +/// ## Errors +/// +/// - If `secret.len() != 32`. +/// - If `secret` does not represent a point in the BLS curve. +pub fn keypair_from_secret(secret: &[u8]) -> Result { + let sk = SecretKey::from_bytes(secret).map_err(Error::InvalidSecretKeyBytes)?; + let pk = PublicKey::from_secret_key(&sk); + Ok(Keypair { sk, pk }) +} + /// Returns `Kdf` used by default when creating keystores. /// /// Currently this is set to scrypt due to its memory hardness properties. diff --git a/eth2/utils/eth2_keystore/src/lib.rs b/eth2/utils/eth2_keystore/src/lib.rs index ad536685310..bb34e221f07 100644 --- a/eth2/utils/eth2_keystore/src/lib.rs +++ b/eth2/utils/eth2_keystore/src/lib.rs @@ -8,8 +8,8 @@ mod plain_text; pub mod json_keystore; pub use keystore::{ - decrypt, default_kdf, encrypt, Error, Keystore, KeystoreBuilder, DKLEN, HASH_SIZE, IV_SIZE, - SALT_SIZE, + decrypt, default_kdf, encrypt, keypair_from_secret, Error, Keystore, KeystoreBuilder, DKLEN, + HASH_SIZE, IV_SIZE, SALT_SIZE, }; pub use plain_text::PlainText; pub use uuid::Uuid; diff --git a/eth2/utils/eth2_wallet/Cargo.toml b/eth2/utils/eth2_wallet/Cargo.toml index c4405bdd7b5..99a4d9481f5 100644 --- a/eth2/utils/eth2_wallet/Cargo.toml +++ b/eth2/utils/eth2_wallet/Cargo.toml @@ -14,6 +14,7 @@ uuid = { version = "0.8", features = ["serde", "v4"] } rand = "0.7.2" eth2_keystore = { path = "../eth2_keystore" } eth2_key_derivation = { path = "../eth2_key_derivation" } +tiny-bip39 = "0.7.3" [dev-dependencies] hex = "0.3" diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index e1b8b95ba58..6563ab1f66f 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -1,5 +1,7 @@ +mod validator_path; mod wallet; pub mod json_wallet; -pub use wallet::{Error, Wallet}; +pub use validator_path::{KeyType, ValidatorPath, COIN_TYPE, PURPOSE}; +pub use wallet::{Error, Wallet, WalletBuilder}; diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index 1c566af67b6..dd98e77f953 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -1,18 +1,41 @@ -use crate::json_wallet::{ - Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, - Kdf, KdfModule, Sha256Checksum, TypeField, Version, +use crate::{ + json_wallet::{ + Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonWallet, + Kdf, KdfModule, Sha256Checksum, TypeField, Version, + }, + KeyType, ValidatorPath, +}; +use bip39::{Mnemonic, Seed as Bip39Seed}; +use eth2_key_derivation::DerivedKey; +use eth2_keystore::{ + decrypt, default_kdf, encrypt, keypair_from_secret, Error as KeystoreError, Keystore, + KeystoreBuilder, IV_SIZE, SALT_SIZE, }; -use eth2_keystore::{decrypt, default_kdf, encrypt, IV_SIZE, SALT_SIZE}; use rand::prelude::*; use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use uuid::Uuid; -pub use eth2_keystore::{Error, PlainText}; +pub use eth2_keystore::PlainText; + +#[derive(Debug, PartialEq)] +pub enum Error { + KeystoreError(KeystoreError), + PathExhausted, + EmptyPassword, +} + +impl From for Error { + fn from(e: KeystoreError) -> Error { + Error::KeystoreError(e) + } +} /// Constructs a `Keystore`. +/// +/// Generates the KDF `salt` and AES `IV` using `rand::thread_rng()`. pub struct WalletBuilder<'a> { - seed: &'a [u8], + seed: PlainText, password: &'a [u8], kdf: Kdf, cipher: Cipher, @@ -22,22 +45,36 @@ pub struct WalletBuilder<'a> { } impl<'a> WalletBuilder<'a> { - /// Creates a new builder. + /// Creates a new builder for a seed specified as a BIP-39 `Mnemonic` (where the nmemonic itself does + /// not have a passphrase). + /// + /// ## Errors /// - /// Generates the KDF `salt` and AES `IV` using `rand::thread_rng()`. + /// Returns `Error::EmptyPassword` if `password == ""`. + pub fn from_mnemonic( + mnemonic: &Mnemonic, + password: &'a [u8], + name: String, + ) -> Result { + let seed = Bip39Seed::new(mnemonic, ""); + + Self::from_seed_bytes(seed.as_bytes(), password, name) + } + + /// Creates a new builder from a `seed` specified as a byte slice. /// /// ## Errors /// /// Returns `Error::EmptyPassword` if `password == ""`. - pub fn from_seed(seed: &'a [u8], password: &'a [u8], name: String) -> Result { + pub fn from_seed_bytes(seed: &[u8], password: &'a [u8], name: String) -> Result { if password.is_empty() { - Err(Error::EmptyPassword) + Err(KeystoreError::EmptyPassword.into()) } else { let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); Ok(Self { - seed, + seed: seed.to_vec().into(), password, kdf: default_kdf(salt.to_vec()), cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), @@ -48,10 +85,22 @@ impl<'a> WalletBuilder<'a> { } } - /// Consumes `self`, returning a `Wallet`. + /// Specify a custom `kdf` (key derivation function) with which to encrypt the wallet. + pub fn kdf(mut self, kdf: Kdf) -> Self { + self.kdf = kdf; + self + } + + /// Specify a custom `cipher` with which to encrypt the wallet. + pub fn cipher(mut self, cipher: Cipher) -> Self { + self.cipher = cipher; + self + } + + /// Consumes `self`, returning an encrypted `Wallet`. pub fn build(self) -> Result { Wallet::encrypt( - self.seed, + self.seed.as_bytes(), self.password, self.kdf, self.cipher, @@ -69,6 +118,9 @@ pub struct Wallet { } impl Wallet { + /// Instantiates `Self`, encrypting the `seed` using `password` (via `kdf` and `cipher`). + /// + /// The `uuid`, `name` and `nextaccount` are carried through into the created wallet. fn encrypt( seed: &[u8], password: &[u8], @@ -108,27 +160,95 @@ impl Wallet { }) } + /// Produces a `Keystore` (encrypted with `keystore_password`) for the validator at + /// `self.nextaccount`, incrementing `self.nextaccount` if the keystore was successfully + /// generated. + /// + /// Uses the default encryption settings of `KeystoreBuilder`, not necessarily those that were + /// used to encrypt `self`. + /// + /// ## Errors + /// + /// - If `wallet_password` is unable to decrypt `self`. + /// - If `keystore_password.is_empty()`. + /// - If `self.nextaccount == u32::max_value() - 1`. + pub fn next_voting_keystore( + &mut self, + wallet_password: &[u8], + keystore_password: &[u8], + ) -> Result { + let (secret, path) = recover_validator_secret( + &self, + wallet_password, + self.json.nextaccount, + KeyType::Voting, + )?; + let keypair = keypair_from_secret(secret.as_bytes())?; + + let keystore = + KeystoreBuilder::new(&keypair, keystore_password, format!("{}", path))?.build()?; + + self.json.nextaccount = self + .json + .nextaccount + .checked_add(1) + .ok_or_else(|| Error::PathExhausted)?; + + Ok(keystore) + } + + /// Returns the master seed of this wallet. Care should be taken not to leak this seed. pub fn decrypt_seed(&self, password: &[u8]) -> Result { - decrypt(password, &self.json.crypto) + decrypt(password, &self.json.crypto).map_err(Into::into) } /// Encodes `self` as a JSON object. pub fn to_json_string(&self) -> Result { - serde_json::to_string(self).map_err(|e| Error::UnableToSerialize(format!("{}", e))) + serde_json::to_string(self) + .map_err(|e| KeystoreError::UnableToSerialize(format!("{}", e))) + .map_err(Into::into) } /// Returns `self` from an encoded JSON object. pub fn from_json_str(json_string: &str) -> Result { - serde_json::from_str(json_string).map_err(|e| Error::InvalidJson(format!("{}", e))) + serde_json::from_str(json_string) + .map_err(|e| KeystoreError::InvalidJson(format!("{}", e))) + .map_err(Into::into) } /// Encodes self as a JSON object to the given `writer`. pub fn to_json_writer(&self, writer: W) -> Result<(), Error> { - serde_json::to_writer(writer, self).map_err(|e| Error::WriteError(format!("{}", e))) + serde_json::to_writer(writer, self) + .map_err(|e| KeystoreError::WriteError(format!("{}", e))) + .map_err(Into::into) } /// Instantiates `self` from a JSON `reader`. pub fn from_json_reader(reader: R) -> Result { - serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) + serde_json::from_reader(reader) + .map_err(|e| KeystoreError::ReadError(format!("{}", e))) + .map_err(Into::into) } } + +/// Returns `(secret, path)` for the `key_type` for the validator at `index`. +/// +/// This function should only be used for recovering lost keys, not creating new ones because it +/// does not update `wallet.nextaccount`. Using this function to generate new keys can easily +/// result in the same key being unknowingly generated twice. +/// +/// To generate consecutive keys safely, use `Wallet::next_voting_keystore`. +pub fn recover_validator_secret( + wallet: &Wallet, + wallet_password: &[u8], + index: u32, + key_type: KeyType, +) -> Result<(PlainText, ValidatorPath), Error> { + let path = ValidatorPath::new(index, key_type); + let secret = wallet.decrypt_seed(wallet_password)?; + let master = DerivedKey::from_seed(secret.as_bytes()).map_err(|()| Error::EmptyPassword)?; + + let destination = path.iter_nodes().fold(master, |dk, i| dk.child(*i)); + + Ok((destination.secret().to_vec().into(), path)) +} From 1da5d2ec9db6bbc7407debb8daf34b0485c9f913 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 May 2020 00:18:59 +1000 Subject: [PATCH 115/118] Tidy --- eth2/utils/eth2_wallet/src/lib.rs | 2 +- eth2/utils/eth2_wallet/src/wallet.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index 6563ab1f66f..7bda5ccb69e 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -4,4 +4,4 @@ mod wallet; pub mod json_wallet; pub use validator_path::{KeyType, ValidatorPath, COIN_TYPE, PURPOSE}; -pub use wallet::{Error, Wallet, WalletBuilder}; +pub use wallet::{Error, KeystoreError, Wallet, WalletBuilder}; diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index dd98e77f953..72f20b0c278 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -8,15 +8,15 @@ use crate::{ use bip39::{Mnemonic, Seed as Bip39Seed}; use eth2_key_derivation::DerivedKey; use eth2_keystore::{ - decrypt, default_kdf, encrypt, keypair_from_secret, Error as KeystoreError, Keystore, - KeystoreBuilder, IV_SIZE, SALT_SIZE, + decrypt, default_kdf, encrypt, keypair_from_secret, Keystore, KeystoreBuilder, IV_SIZE, + SALT_SIZE, }; use rand::prelude::*; use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use uuid::Uuid; -pub use eth2_keystore::PlainText; +pub use eth2_keystore::{Error as KeystoreError, PlainText}; #[derive(Debug, PartialEq)] pub enum Error { From 0992d0fc9659788d637d098d786abf4f4c197764 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 May 2020 13:29:16 +1000 Subject: [PATCH 116/118] Add testing --- Cargo.lock | 2 + eth2/utils/eth2_wallet/Cargo.toml | 2 + eth2/utils/eth2_wallet/src/lib.rs | 6 +- eth2/utils/eth2_wallet/src/validator_path.rs | 41 +++ eth2/utils/eth2_wallet/src/wallet.rs | 91 ++++-- eth2/utils/eth2_wallet/tests/json.rs | 246 ++++++++++++++++ eth2/utils/eth2_wallet/tests/tests.rs | 280 +++++++++++++++++++ 7 files changed, 638 insertions(+), 30 deletions(-) create mode 100644 eth2/utils/eth2_wallet/src/validator_path.rs create mode 100644 eth2/utils/eth2_wallet/tests/json.rs create mode 100644 eth2/utils/eth2_wallet/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index b2580a90c6b..08330d4796b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,11 +1280,13 @@ version = "0.1.0" dependencies = [ "eth2_key_derivation 0.1.0", "eth2_keystore 0.1.0", + "eth2_ssz 0.1.2", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-bip39 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/eth2/utils/eth2_wallet/Cargo.toml b/eth2/utils/eth2_wallet/Cargo.toml index 99a4d9481f5..93f0cc8ffc7 100644 --- a/eth2/utils/eth2_wallet/Cargo.toml +++ b/eth2/utils/eth2_wallet/Cargo.toml @@ -18,3 +18,5 @@ tiny-bip39 = "0.7.3" [dev-dependencies] hex = "0.3" +eth2_ssz = { path = "../ssz" } +tempfile = "3.1.0" diff --git a/eth2/utils/eth2_wallet/src/lib.rs b/eth2/utils/eth2_wallet/src/lib.rs index 7bda5ccb69e..4a3ec97e6ab 100644 --- a/eth2/utils/eth2_wallet/src/lib.rs +++ b/eth2/utils/eth2_wallet/src/lib.rs @@ -3,5 +3,9 @@ mod wallet; pub mod json_wallet; +pub use bip39; pub use validator_path::{KeyType, ValidatorPath, COIN_TYPE, PURPOSE}; -pub use wallet::{Error, KeystoreError, Wallet, WalletBuilder}; +pub use wallet::{ + recover_validator_secret, DerivedKey, Error, KeystoreError, PlainText, ValidatorKeystores, + Wallet, WalletBuilder, +}; diff --git a/eth2/utils/eth2_wallet/src/validator_path.rs b/eth2/utils/eth2_wallet/src/validator_path.rs new file mode 100644 index 00000000000..3b4f7738dac --- /dev/null +++ b/eth2/utils/eth2_wallet/src/validator_path.rs @@ -0,0 +1,41 @@ +use std::fmt; +use std::iter::Iterator; + +pub const PURPOSE: u32 = 12381; +pub const COIN_TYPE: u32 = 3600; + +pub enum KeyType { + Voting, + Withdrawal, +} + +pub struct ValidatorPath(Vec); + +impl ValidatorPath { + pub fn new(index: u32, key_type: KeyType) -> Self { + let mut vec = vec![PURPOSE, COIN_TYPE, index, 0]; + + match key_type { + KeyType::Voting => vec.push(0), + KeyType::Withdrawal => {} + } + + Self(vec) + } + + pub fn iter_nodes(&self) -> impl Iterator { + self.0.iter() + } +} + +impl fmt::Display for ValidatorPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "m")?; + + for node in self.iter_nodes() { + write!(f, "/{}", node)?; + } + + Ok(()) + } +} diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index 72f20b0c278..5557edb1f0a 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -5,8 +5,6 @@ use crate::{ }, KeyType, ValidatorPath, }; -use bip39::{Mnemonic, Seed as Bip39Seed}; -use eth2_key_derivation::DerivedKey; use eth2_keystore::{ decrypt, default_kdf, encrypt, keypair_from_secret, Keystore, KeystoreBuilder, IV_SIZE, SALT_SIZE, @@ -16,6 +14,8 @@ use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use uuid::Uuid; +pub use bip39::{Mnemonic, Seed as Bip39Seed}; +pub use eth2_key_derivation::DerivedKey; pub use eth2_keystore::{Error as KeystoreError, PlainText}; #[derive(Debug, PartialEq)] @@ -23,6 +23,7 @@ pub enum Error { KeystoreError(KeystoreError), PathExhausted, EmptyPassword, + EmptySeed, } impl From for Error { @@ -31,6 +32,15 @@ impl From for Error { } } +/// Contains the two keystores required for an eth2 validator. +pub struct ValidatorKeystores { + /// Contains the secret key used for signing every-day consensus messages (blocks, + /// attestations, etc). + pub voting: Keystore, + /// Contains the secret key that should eventually be required for withdrawing stacked ETH. + pub withdrawal: Keystore, +} + /// Constructs a `Keystore`. /// /// Generates the KDF `salt` and AES `IV` using `rand::thread_rng()`. @@ -56,6 +66,7 @@ impl<'a> WalletBuilder<'a> { password: &'a [u8], name: String, ) -> Result { + // TODO: `bip39` does not use zeroize. Perhaps we should make a PR upstream? let seed = Bip39Seed::new(mnemonic, ""); Self::from_seed_bytes(seed.as_bytes(), password, name) @@ -68,7 +79,9 @@ impl<'a> WalletBuilder<'a> { /// Returns `Error::EmptyPassword` if `password == ""`. pub fn from_seed_bytes(seed: &[u8], password: &'a [u8], name: String) -> Result { if password.is_empty() { - Err(KeystoreError::EmptyPassword.into()) + Err(Error::EmptyPassword) + } else if seed.is_empty() { + Err(Error::EmptySeed) } else { let salt = rand::thread_rng().gen::<[u8; SALT_SIZE]>(); let iv = rand::thread_rng().gen::<[u8; IV_SIZE]>().to_vec().into(); @@ -85,18 +98,6 @@ impl<'a> WalletBuilder<'a> { } } - /// Specify a custom `kdf` (key derivation function) with which to encrypt the wallet. - pub fn kdf(mut self, kdf: Kdf) -> Self { - self.kdf = kdf; - self - } - - /// Specify a custom `cipher` with which to encrypt the wallet. - pub fn cipher(mut self, cipher: Cipher) -> Self { - self.cipher = cipher; - self - } - /// Consumes `self`, returning an encrypted `Wallet`. pub fn build(self) -> Result { Wallet::encrypt( @@ -172,21 +173,31 @@ impl Wallet { /// - If `wallet_password` is unable to decrypt `self`. /// - If `keystore_password.is_empty()`. /// - If `self.nextaccount == u32::max_value() - 1`. - pub fn next_voting_keystore( + pub fn next_validator( &mut self, wallet_password: &[u8], - keystore_password: &[u8], - ) -> Result { - let (secret, path) = recover_validator_secret( - &self, - wallet_password, - self.json.nextaccount, - KeyType::Voting, - )?; - let keypair = keypair_from_secret(secret.as_bytes())?; - - let keystore = - KeystoreBuilder::new(&keypair, keystore_password, format!("{}", path))?.build()?; + voting_keystore_password: &[u8], + withdrawal_keystore_password: &[u8], + ) -> Result { + // Helper closure to reduce code duplication when generating keys. + // + // It is not a function on `self` to help protect against generating keys without + // incrementing `nextaccount`. + let derive = |key_type: KeyType, password: &[u8]| -> Result { + let (secret, path) = + recover_validator_secret(&self, wallet_password, self.json.nextaccount, key_type)?; + + let keypair = keypair_from_secret(secret.as_bytes())?; + + KeystoreBuilder::new(&keypair, password, format!("{}", path))? + .build() + .map_err(Into::into) + }; + + let keystores = ValidatorKeystores { + voting: derive(KeyType::Voting, voting_keystore_password)?, + withdrawal: derive(KeyType::Withdrawal, withdrawal_keystore_password)?, + }; self.json.nextaccount = self .json @@ -194,7 +205,29 @@ impl Wallet { .checked_add(1) .ok_or_else(|| Error::PathExhausted)?; - Ok(keystore) + Ok(keystores) + } + + /// Returns the value of the JSON wallet `nextaccount` field. + /// + /// This will be the index of the next wallet generated with `Self::next_validator`. + pub fn nextaccount(&self) -> u32 { + self.json.nextaccount + } + + /// Returns the value of the JSON wallet `name` field. + pub fn name(&self) -> &str { + &self.json.name + } + + /// Returns the value of the JSON wallet `uuid` field. + pub fn uuid(&self) -> &Uuid { + &self.json.uuid + } + + /// Returns the value of the JSON wallet `type` field. + pub fn type_field(&self) -> String { + self.json.type_field.clone().into() } /// Returns the master seed of this wallet. Care should be taken not to leak this seed. diff --git a/eth2/utils/eth2_wallet/tests/json.rs b/eth2/utils/eth2_wallet/tests/json.rs new file mode 100644 index 00000000000..8653d3f8ce5 --- /dev/null +++ b/eth2/utils/eth2_wallet/tests/json.rs @@ -0,0 +1,246 @@ +use eth2_wallet::{Error, KeystoreError, Wallet}; + +fn assert_bad_json(json: &str) { + match Wallet::from_json_str(&json) { + Err(Error::KeystoreError(KeystoreError::InvalidJson(_))) => {} + _ => panic!("expected invalid json error"), + } +} + +/* + * Note: the `crypto` object is inherited from the `eth2_keystore` crate so we don't test it here. + */ + +#[test] +fn additional_top_level_param() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "hierarchical deterministic", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 1 + "cats": 42 + } + "#; + + assert_bad_json(&vector); +} + +#[test] +fn missing_top_level_param() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "hierarchical deterministic", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + } + "#; + + assert_bad_json(&vector); +} + +#[test] +fn bad_version() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "hierarchical deterministic", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 2 + } + "#; + + assert_bad_json(&vector); +} + +#[test] +fn bad_uuid() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "hierarchical deterministic", + "uuid": "!b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 1 + } + "#; + + assert_bad_json(&vector); +} + +#[test] +fn bad_type() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 0, + "type": "something else", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 1 + } + "#; + + assert_bad_json(&vector); +} + +#[test] +fn more_that_u32_nextaccount() { + let vector = r#" + { + "crypto": { + "checksum": { + "function": "sha256", + "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", + "params": { + "iv": "9476702ab99beff3e8012eff49ffb60d" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 16, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" + } + } + }, + "name": "Test wallet 2", + "nextaccount": 4294967297, + "type": "hierarchical deterministic", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "version": 1 + } + "#; + + assert_bad_json(&vector); +} diff --git a/eth2/utils/eth2_wallet/tests/tests.rs b/eth2/utils/eth2_wallet/tests/tests.rs new file mode 100644 index 00000000000..225064f6a49 --- /dev/null +++ b/eth2/utils/eth2_wallet/tests/tests.rs @@ -0,0 +1,280 @@ +use eth2_wallet::{ + bip39::{Language, Mnemonic, Seed}, + recover_validator_secret, DerivedKey, Error, KeyType, KeystoreError, Wallet, WalletBuilder, +}; +use ssz::Encode; +use std::fs::OpenOptions; +use tempfile::tempdir; + +const NAME: &str = "Wallet McWalletface"; +const SEED: &[u8] = &[42; 42]; +const WALLET_PASSWORD: &[u8] = &[43; 43]; +const VOTING_KEYSTORE_PASSWORD: &[u8] = &[44; 44]; +const WITHDRAWAL_KEYSTORE_PASSWORD: &[u8] = &[45; 45]; +const MNEMONIC: &str = + "enemy fog enlist laundry nurse hungry discover turkey holiday resemble glad discover"; + +fn wallet_from_seed() -> Wallet { + WalletBuilder::from_seed_bytes(SEED, WALLET_PASSWORD, NAME.into()) + .expect("should init builder") + .build() + .expect("should build wallet") +} + +fn recovered_voting_key(wallet: &Wallet, index: u32) -> Vec { + let (secret, path) = recover_validator_secret(wallet, WALLET_PASSWORD, index, KeyType::Voting) + .expect("should recover voting secret"); + + assert_eq!( + format!("{}", path), + format!("m/12381/3600/{}/0/0", index), + "path should be as expected" + ); + + secret.as_bytes().to_vec() +} + +fn recovered_withdrawal_key(wallet: &Wallet, index: u32) -> Vec { + let (secret, path) = + recover_validator_secret(wallet, WALLET_PASSWORD, index, KeyType::Withdrawal) + .expect("should recover withdrawal secret"); + + assert_eq!( + format!("{}", path), + format!("m/12381/3600/{}/0", index), + "path should be as expected" + ); + + secret.as_bytes().to_vec() +} + +fn manually_derived_voting_key(index: u32) -> Vec { + DerivedKey::from_seed(SEED) + .expect("should derive master key") + .child(12381) + .child(3600) + .child(index) + .child(0) + .child(0) + .secret() + .to_vec() +} + +fn manually_derived_withdrawal_key(index: u32) -> Vec { + DerivedKey::from_seed(SEED) + .expect("should derive master key") + .child(12381) + .child(3600) + .child(index) + .child(0) + .secret() + .to_vec() +} + +#[test] +fn mnemonic_equality() { + let m = Mnemonic::from_phrase(MNEMONIC, Language::English).unwrap(); + + let from_mnemonic = WalletBuilder::from_mnemonic(&m, WALLET_PASSWORD, NAME.into()) + .expect("should init builder") + .build() + .expect("should build wallet"); + + let seed = Seed::new(&m, ""); + + let from_seed = WalletBuilder::from_seed_bytes(seed.as_bytes(), WALLET_PASSWORD, NAME.into()) + .expect("should init builder") + .build() + .expect("should build wallet"); + + assert_eq!( + from_mnemonic + .decrypt_seed(WALLET_PASSWORD) + .unwrap() + .as_bytes(), + from_seed.decrypt_seed(WALLET_PASSWORD).unwrap().as_bytes(), + "wallet from mnemonic should match wallet from seed" + ); +} + +#[test] +fn metadata() { + let wallet = wallet_from_seed(); + assert_eq!(wallet.name(), NAME, "name"); + assert_eq!(&wallet.type_field(), "hierarchical deterministic", "name"); + assert_eq!(wallet.nextaccount(), 0, "name"); +} + +#[test] +fn string_round_trip() { + let wallet = wallet_from_seed(); + + let json = wallet.to_json_string().unwrap(); + let decoded = Wallet::from_json_str(&json).unwrap(); + + assert_eq!( + decoded.decrypt_seed(&[1, 2, 3]).err().unwrap(), + Error::KeystoreError(KeystoreError::InvalidPassword), + "should not decrypt with bad password" + ); + + assert_eq!( + wallet.decrypt_seed(WALLET_PASSWORD).unwrap().as_bytes(), + decoded.decrypt_seed(WALLET_PASSWORD).unwrap().as_bytes(), + "should decrypt with good password" + ); +} + +#[test] +fn file_round_trip() { + let wallet = wallet_from_seed(); + let dir = tempdir().unwrap(); + let path = dir.path().join("keystore.json"); + + let get_file = || { + OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(path.clone()) + .expect("should create file") + }; + + wallet + .to_json_writer(&mut get_file()) + .expect("should write to file"); + + let decoded = Wallet::from_json_reader(&mut get_file()).unwrap(); + + assert_eq!( + decoded.decrypt_seed(&[1, 2, 3]).err().unwrap(), + Error::KeystoreError(KeystoreError::InvalidPassword), + "should not decrypt with bad password" + ); + + assert_eq!( + wallet.decrypt_seed(WALLET_PASSWORD).unwrap().as_bytes(), + decoded.decrypt_seed(WALLET_PASSWORD).unwrap().as_bytes(), + "should decrypt with good password" + ); +} + +#[test] +fn empty_wallet_password() { + assert_eq!( + WalletBuilder::from_seed_bytes(SEED, &[], NAME.into()) + .err() + .expect("should error"), + Error::EmptyPassword + ) +} + +#[test] +fn empty_wallet_seed() { + assert_eq!( + WalletBuilder::from_seed_bytes(&[], WALLET_PASSWORD, NAME.into()) + .err() + .expect("should error"), + Error::EmptySeed + ) +} + +#[test] +fn empty_keystore_password() { + let mut wallet = wallet_from_seed(); + + assert_eq!(wallet.nextaccount(), 0, "initial nextaccount"); + + assert_eq!( + wallet + .next_validator(WALLET_PASSWORD, &[], WITHDRAWAL_KEYSTORE_PASSWORD,) + .err() + .expect("should error"), + Error::KeystoreError(KeystoreError::EmptyPassword), + "should fail with empty voting password" + ); + + assert_eq!(wallet.nextaccount(), 0, "next account should not update"); + + assert_eq!( + wallet + .next_validator(WALLET_PASSWORD, VOTING_KEYSTORE_PASSWORD, &[],) + .err() + .expect("should error"), + Error::KeystoreError(KeystoreError::EmptyPassword), + "should fail with empty withdrawal password" + ); + + assert_eq!(wallet.nextaccount(), 0, "next account should not update"); +} + +#[test] +fn key_derivation_from_seed() { + let mut wallet = wallet_from_seed(); + + for i in 0..4 { + assert_eq!(wallet.nextaccount(), i, "initial nextaccount"); + + let keystores = wallet + .next_validator( + WALLET_PASSWORD, + VOTING_KEYSTORE_PASSWORD, + WITHDRAWAL_KEYSTORE_PASSWORD, + ) + .expect("should generate keystores"); + + assert_eq!( + keystores.voting.path(), + format!("m/12381/3600/{}/0/0", i), + "voting path should match" + ); + + assert_eq!( + keystores.withdrawal.path(), + format!("m/12381/3600/{}/0", i), + "withdrawal path should match" + ); + + let voting_keypair = keystores + .voting + .decrypt_keypair(VOTING_KEYSTORE_PASSWORD) + .expect("should decrypt voting keypair"); + + assert_eq!( + voting_keypair.sk.as_ssz_bytes(), + manually_derived_voting_key(i), + "voting secret should match manually derived" + ); + + assert_eq!( + voting_keypair.sk.as_ssz_bytes(), + recovered_voting_key(&wallet, i), + "voting secret should match recovered" + ); + + let withdrawal_keypair = keystores + .withdrawal + .decrypt_keypair(WITHDRAWAL_KEYSTORE_PASSWORD) + .expect("should decrypt withdrawal keypair"); + + assert_eq!( + withdrawal_keypair.sk.as_ssz_bytes(), + manually_derived_withdrawal_key(i), + "withdrawal secret should match manually derived" + ); + + assert_eq!( + withdrawal_keypair.sk.as_ssz_bytes(), + recovered_withdrawal_key(&wallet, i), + "withdrawal secret should match recovered" + ); + + assert_ne!( + withdrawal_keypair.sk.as_ssz_bytes(), + voting_keypair.sk.as_ssz_bytes(), + "voting and withdrawal keypairs should be distinct" + ); + + assert_eq!(wallet.nextaccount(), i + 1, "updated nextaccount"); + } +} From 127c345e3748a912b226d75ea5bdfcce896f574b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 May 2020 18:57:18 +1000 Subject: [PATCH 117/118] Fix broken test --- eth2/utils/eth2_wallet/tests/eip2386_vectors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs index 7d9cde0fcb0..58896bdca59 100644 --- a/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs +++ b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs @@ -1,4 +1,4 @@ -use eth2_wallet::{Uuid, Wallet}; +use eth2_wallet::Wallet; const EXPECTED_SECRET: &str = "147addc7ec981eb2715a22603813271cce540e0b7f577126011eb06249d9227c"; const PASSWORD: &str = "testpassword"; From 57b7efb958c66600cbb6f4554bbefbf5ce65c0bf Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 May 2020 19:07:34 +1000 Subject: [PATCH 118/118] Address review comments --- eth2/utils/eth2_wallet/src/json_wallet/mod.rs | 5 +++++ eth2/utils/eth2_wallet/src/wallet.rs | 2 +- eth2/utils/eth2_wallet/tests/eip2386_vectors.rs | 11 ++++++----- eth2/utils/eth2_wallet/tests/json.rs | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs index 834716fba2d..6c430e50df0 100644 --- a/eth2/utils/eth2_wallet/src/json_wallet/mod.rs +++ b/eth2/utils/eth2_wallet/src/json_wallet/mod.rs @@ -13,6 +13,11 @@ pub use uuid::Uuid; pub struct JsonWallet { pub crypto: Crypto, pub name: String, + // TODO: confirm if this field is optional or not. + // + // Reference: + // + // https://github.com/sigp/lighthouse/pull/1117#discussion_r422892396 pub nextaccount: u32, pub uuid: Uuid, pub version: Version, diff --git a/eth2/utils/eth2_wallet/src/wallet.rs b/eth2/utils/eth2_wallet/src/wallet.rs index 5557edb1f0a..05ac7ad9167 100644 --- a/eth2/utils/eth2_wallet/src/wallet.rs +++ b/eth2/utils/eth2_wallet/src/wallet.rs @@ -172,7 +172,7 @@ impl Wallet { /// /// - If `wallet_password` is unable to decrypt `self`. /// - If `keystore_password.is_empty()`. - /// - If `self.nextaccount == u32::max_value() - 1`. + /// - If `self.nextaccount == u32::max_value()`. pub fn next_validator( &mut self, wallet_password: &[u8], diff --git a/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs index 58896bdca59..db98b2e9b16 100644 --- a/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs +++ b/eth2/utils/eth2_wallet/tests/eip2386_vectors.rs @@ -1,3 +1,4 @@ +use eth2_keystore::Uuid; use eth2_wallet::Wallet; const EXPECTED_SECRET: &str = "147addc7ec981eb2715a22603813271cce540e0b7f577126011eb06249d9227c"; @@ -48,12 +49,12 @@ fn eip2386_test_vector_scrypt() { "#; let wallet = decode_and_check_seed(&vector); - /* assert_eq!( - *keystore.uuid(), - Uuid::parse_str("1d85ae20-35c5-4611-98e8-aa14a633906f").unwrap(), + *wallet.uuid(), + Uuid::parse_str("b74559b8-ed56-4841-b25c-dba1b7c9d9d5").unwrap(), "uuid" ); - assert_eq!(keystore.path(), "", "path"); - */ + assert_eq!(wallet.name(), "Test wallet 2", "name"); + assert_eq!(wallet.nextaccount(), 0, "nextaccount"); + assert_eq!(wallet.type_field(), "hierarchical deterministic", "type"); } diff --git a/eth2/utils/eth2_wallet/tests/json.rs b/eth2/utils/eth2_wallet/tests/json.rs index 8653d3f8ce5..464f54cd757 100644 --- a/eth2/utils/eth2_wallet/tests/json.rs +++ b/eth2/utils/eth2_wallet/tests/json.rs @@ -43,7 +43,7 @@ fn additional_top_level_param() { "nextaccount": 0, "type": "hierarchical deterministic", "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", - "version": 1 + "version": 1, "cats": 42 } "#; @@ -82,7 +82,7 @@ fn missing_top_level_param() { "name": "Test wallet 2", "nextaccount": 0, "type": "hierarchical deterministic", - "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", + "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5" } "#;