EIP-2335: Keystore (#1071)
* Add test to understand flow of key storage * First commit * Committing to save trait stuff * Working naive design * Add keystore struct * Move keystore files into their own module * Add serde (de)serialize_with magic * Add keystore test * Fix tests * Add comments and minor fixes * Pass optional params to `to_keystore` function * Add `path` field to keystore * Add function to read Keystore from file * Add test vectors and fix Version serialization * Checksum params is empty object * Add public key to Keystore * Add function for saving keystore into file * Deleted account_manager main.rs * Move keystore module to validator_client * Add save_keystore method to validator_directory * Add load_keystore function. Minor refactorings * Fixed dependencies * Address some review comments * Add Password newtype; derive Zeroize * Fix test * Move keystore into own crate * Remove padding * Add error enum, zeroize more things * Fix comment * Add keystore builder * Remove keystore stuff from val client * Add more tests, comments * Add more comments, test vectors * Progress on improving JSON validation * More JSON verification * Start moving JSON into own mod * Remove old code * Add more tests, reader/writers * Tidy * Move keystore into own file * Move more logic into keystore file * Tidy * Tidy * Allow for odd-character hex * Add more json missing field checks * Use scrypt by default * Tidy, address comments * Test path and uuid in vectors * Fix comment * Add checks for kdf params * Enforce empty kdf message * Expose json_keystore mod * Split out encrypt/decrypt * Replace some password usage with slice * Expose PlainText struct * Expose consts, remove Password * Expose SALT_SIZE * Move dbg assert statement * Fix dodgy json test * Protect against n == 1 * Return error if n is not power of 2 * Add dklen checks * Add note about panics Co-authored-by: pawan <pawandhananjay@gmail.com>
This commit is contained in:
		
							parent
							
								
									294d007f64
								
							
						
					
					
						commit
						f30271ee9e
					
				
							
								
								
									
										71
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										71
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1296,6 +1296,23 @@ dependencies = [ | ||||
|  "serde_yaml", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "eth2_keystore" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "bls", | ||||
|  "eth2_ssz", | ||||
|  "hex 0.3.2", | ||||
|  "rand 0.7.3", | ||||
|  "rust-crypto", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "serde_repr", | ||||
|  "tempfile", | ||||
|  "uuid 0.8.1", | ||||
|  "zeroize 1.1.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "eth2_ssz" | ||||
| version = "0.1.2" | ||||
| @ -3550,7 +3567,7 @@ dependencies = [ | ||||
|  "tokio-threadpool", | ||||
|  "tokio-timer 0.2.13", | ||||
|  "url 1.7.2", | ||||
|  "uuid", | ||||
|  "uuid 0.7.4", | ||||
|  "winreg", | ||||
| ] | ||||
| 
 | ||||
| @ -3650,6 +3667,19 @@ dependencies = [ | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rust-crypto" | ||||
| version = "0.2.36" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" | ||||
| dependencies = [ | ||||
|  "gcc", | ||||
|  "libc", | ||||
|  "rand 0.3.23", | ||||
|  "rustc-serialize", | ||||
|  "time", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc-demangle" | ||||
| version = "0.1.16" | ||||
| @ -3662,6 +3692,12 @@ version = "2.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc-serialize" | ||||
| version = "0.3.24" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc_version" | ||||
| version = "0.2.3" | ||||
| @ -4199,9 +4235,9 @@ dependencies = [ | ||||
| name = "state_transition_vectors" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "eth2_ssz 0.1.2", | ||||
|  "state_processing 0.2.0", | ||||
|  "types 0.2.0", | ||||
|  "eth2_ssz", | ||||
|  "state_processing", | ||||
|  "types", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -4983,6 +5019,16 @@ dependencies = [ | ||||
|  "rand 0.6.5", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "uuid" | ||||
| version = "0.8.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" | ||||
| dependencies = [ | ||||
|  "rand 0.7.3", | ||||
|  "serde", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "validator_client" | ||||
| version = "0.2.0" | ||||
| @ -5424,7 +5470,7 @@ version = "0.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e68403b858b6af538b11614e62dfe9ab2facba9f13a0cafb974855cfb495ec95" | ||||
| dependencies = [ | ||||
|  "zeroize_derive", | ||||
|  "zeroize_derive 0.1.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -5432,6 +5478,9 @@ name = "zeroize" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" | ||||
| dependencies = [ | ||||
|  "zeroize_derive 1.0.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zeroize_derive" | ||||
| @ -5443,3 +5492,15 @@ dependencies = [ | ||||
|  "quote 0.6.13", | ||||
|  "syn 0.15.44", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zeroize_derive" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" | ||||
| dependencies = [ | ||||
|  "proc-macro2 1.0.12", | ||||
|  "quote 1.0.4", | ||||
|  "syn 1.0.19", | ||||
|  "synstructure", | ||||
| ] | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
							
								
								
									
										22
									
								
								eth2/utils/eth2_keystore/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								eth2/utils/eth2_keystore/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| [package] | ||||
| name = "eth2_keystore" | ||||
| version = "0.1.0" | ||||
| authors = ["Pawan Dhananjay <pawan@sigmaprime.io", "Paul Hauner <paul@paulhauner.com>"] | ||||
| 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"] } | ||||
| zeroize = { version = "1.0.0", features = ["zeroize_derive"] } | ||||
| serde = "1.0.102" | ||||
| serde_repr = "0.1" | ||||
| hex = "0.3" | ||||
| bls = { path = "../bls" } | ||||
| eth2_ssz = { path = "../ssz" } | ||||
| serde_json = "1.0.41" | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| tempfile = "3.1.0" | ||||
							
								
								
									
										24
									
								
								eth2/utils/eth2_keystore/src/derived_key.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								eth2/utils/eth2_keystore/src/derived_key.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| 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]); | ||||
| 
 | ||||
| impl DerivedKey { | ||||
|     /// Instantiates `Self` with an 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 a reference to the underlying byte array.
 | ||||
|     pub fn as_bytes(&self) -> &[u8] { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| //! 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<String> for ChecksumFunction { | ||||
|     fn into(self) -> String { | ||||
|         match self { | ||||
|             ChecksumFunction::Sha256 => "sha256".into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<String> for ChecksumFunction { | ||||
|     type Error = String; | ||||
| 
 | ||||
|     fn try_from(s: String) -> Result<Self, Self::Error> { | ||||
|         match s.as_ref() { | ||||
|             "sha256" => Ok(ChecksumFunction::Sha256), | ||||
|             other => Err(format!("Unsupported checksum function: {}", other)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Used for ensuring serde only decodes an empty map.
 | ||||
| #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] | ||||
| #[serde(try_from = "Value", into = "Value")] | ||||
| pub struct EmptyMap; | ||||
| 
 | ||||
| impl Into<Value> for EmptyMap { | ||||
|     fn into(self) -> Value { | ||||
|         Value::Object(Map::default()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<Value> for EmptyMap { | ||||
|     type Error = &'static str; | ||||
| 
 | ||||
|     fn try_from(v: Value) -> Result<Self, Self::Error> { | ||||
|         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)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| 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 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								eth2/utils/eth2_keystore/src/json_keystore/cipher_module.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| //! 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<String> for CipherFunction { | ||||
|     fn into(self) -> String { | ||||
|         match self { | ||||
|             CipherFunction::Aes128Ctr => "aes-128-ctr".into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<String> for CipherFunction { | ||||
|     type Error = String; | ||||
| 
 | ||||
|     fn try_from(s: String) -> Result<Self, Self::Error> { | ||||
|         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)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| 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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								eth2/utils/eth2_keystore/src/json_keystore/hex_bytes.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::convert::TryFrom; | ||||
| 
 | ||||
| /// 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<u8>); | ||||
| 
 | ||||
| impl HexBytes { | ||||
|     pub fn as_bytes(&self) -> &[u8] { | ||||
|         &self.0 | ||||
|     } | ||||
| 
 | ||||
|     pub fn len(&self) -> usize { | ||||
|         self.0.len() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Vec<u8>> for HexBytes { | ||||
|     fn from(vec: Vec<u8>) -> Self { | ||||
|         Self(vec) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Into<String> for HexBytes { | ||||
|     fn into(self) -> String { | ||||
|         hex::encode(self.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<String> for HexBytes { | ||||
|     type Error = String; | ||||
| 
 | ||||
|     fn try_from(s: String) -> Result<Self, Self::Error> { | ||||
|         // 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<u8> { | ||||
|         serde_json::from_str::<HexBytes>(&format!("\"{}\"", json)) | ||||
|             .expect("should decode json") | ||||
|             .as_bytes() | ||||
|             .to_vec() | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn odd_hex_bytes() { | ||||
|         let empty: Vec<u8> = 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"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								eth2/utils/eth2_keystore/src/json_keystore/kdf_module.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| //! 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)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| pub struct KdfModule { | ||||
|     pub function: KdfFunction, | ||||
|     pub params: Kdf, | ||||
|     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<String> for EmptyString { | ||||
|     fn into(self) -> String { | ||||
|         "".into() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<String> for EmptyString { | ||||
|     type Error = &'static str; | ||||
| 
 | ||||
|     fn try_from(s: String) -> Result<Self, Self::Error> { | ||||
|         match s.as_ref() { | ||||
|             "" => Ok(Self), | ||||
|             _ => Err("kdf message must be empty"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[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<String> for KdfFunction { | ||||
|     fn into(self) -> String { | ||||
|         match self { | ||||
|             KdfFunction::Scrypt => "scrypt".into(), | ||||
|             KdfFunction::Pbkdf2 => "pbkdf2".into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<String> for KdfFunction { | ||||
|     type Error = String; | ||||
| 
 | ||||
|     fn try_from(s: String) -> Result<Self, Self::Error> { | ||||
|         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, | ||||
| } | ||||
							
								
								
									
										52
									
								
								eth2/utils/eth2_keystore/src/json_keystore/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								eth2/utils/eth2_keystore/src/json_keystore/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| //! 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; | ||||
| 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::{EmptyString, Kdf, KdfModule, Pbkdf2, Prf, Scrypt}; | ||||
| 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)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| 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)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| pub struct Crypto { | ||||
|     pub kdf: KdfModule, | ||||
|     pub checksum: ChecksumModule, | ||||
|     pub cipher: CipherModule, | ||||
| } | ||||
							
								
								
									
										399
									
								
								eth2/utils/eth2_keystore/src/keystore.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								eth2/utils/eth2_keystore/src/keystore.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,399 @@ | ||||
| //! 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, EmptyString, JsonKeystore, | ||||
|     Kdf, KdfModule, Scrypt, Sha256Checksum, Version, | ||||
| }; | ||||
| use crate::plain_text::PlainText; | ||||
| 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 the EITF examples.
 | ||||
| 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.
 | ||||
| ///
 | ||||
| /// 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
 | ||||
| ///
 | ||||
| /// 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.
 | ||||
| pub const IV_SIZE: usize = 16; | ||||
| /// The byte size of a SHA256 hash.
 | ||||
| pub 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), | ||||
|     InvalidPbkdf2Param, | ||||
|     InvalidScryptParam, | ||||
|     IncorrectIvSize { expected: usize, len: usize }, | ||||
| } | ||||
| 
 | ||||
| /// Constructs a `Keystore`.
 | ||||
| pub struct KeystoreBuilder<'a> { | ||||
|     keypair: &'a Keypair, | ||||
|     password: &'a [u8], | ||||
|     kdf: Kdf, | ||||
|     cipher: Cipher, | ||||
|     uuid: Uuid, | ||||
|     path: String, | ||||
| } | ||||
| 
 | ||||
| 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 == ""`.
 | ||||
|     pub fn new(keypair: &'a Keypair, password: &'a [u8], path: String) -> Result<Self, Error> { | ||||
|         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 { | ||||
|                 keypair, | ||||
|                 password, | ||||
|                 kdf: default_kdf(salt.to_vec()), | ||||
|                 cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }), | ||||
|                 uuid: Uuid::new_v4(), | ||||
|                 path, | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Consumes `self`, returning a `Keystore`.
 | ||||
|     pub fn build(self) -> Result<Keystore, Error> { | ||||
|         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: &[u8], | ||||
|         kdf: Kdf, | ||||
|         cipher: Cipher, | ||||
|         uuid: Uuid, | ||||
|         path: String, | ||||
|     ) -> Result<Self, Error> { | ||||
|         let secret = PlainText::from(keypair.sk.as_raw().as_bytes()); | ||||
| 
 | ||||
|         let (cipher_text, checksum) = encrypt(secret.as_bytes(), password, &kdf, &cipher)?; | ||||
| 
 | ||||
|         Ok(Keystore { | ||||
|             json: JsonKeystore { | ||||
|                 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, | ||||
|                 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.
 | ||||
|     ///
 | ||||
|     /// ## Panics
 | ||||
|     ///
 | ||||
|     /// May panic if provided unreasonable crypto parameters.
 | ||||
|     pub fn decrypt_keypair(&self, password: &[u8]) -> Result<Keypair, Error> { | ||||
|         let plain_text = decrypt(password, &self.json.crypto)?; | ||||
| 
 | ||||
|         // 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 | ||||
|     } | ||||
| 
 | ||||
|     /// 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<String, Error> { | ||||
|         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<Self, Error> { | ||||
|         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<W: Write>(&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<R: Read>(reader: R) -> Result<Self, Error> { | ||||
|         serde_json::from_reader(reader).map_err(|e| Error::ReadError(format!("{}", e))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// 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<u8>) -> 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).
 | ||||
| ///
 | ||||
| /// ## Errors
 | ||||
| ///
 | ||||
| /// - The `kdf` is badly formed (e.g., has some values set to zero).
 | ||||
| pub fn encrypt( | ||||
|     plain_text: &[u8], | ||||
|     password: &[u8], | ||||
|     kdf: &Kdf, | ||||
|     cipher: &Cipher, | ||||
| ) -> Result<(Vec<u8>, [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, &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: &[u8], crypto: &Crypto) -> Result<PlainText, Error> { | ||||
|     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]) -> [u8; HASH_SIZE] { | ||||
|     let mut hasher = Sha256::new(); | ||||
|     hasher.input(&derived_key.as_bytes()[16..32]); | ||||
|     hasher.input(cipher_message); | ||||
| 
 | ||||
|     let mut digest = [0; HASH_SIZE]; | ||||
|     hasher.result(&mut digest); | ||||
|     digest | ||||
| } | ||||
| 
 | ||||
| /// Derive a private key from the given `password` using the given `kdf` (key derivation function).
 | ||||
| fn derive_key(password: &[u8], kdf: &Kdf) -> Result<DerivedKey, Error> { | ||||
|     let mut dk = DerivedKey::zero(); | ||||
| 
 | ||||
|     match &kdf { | ||||
|         Kdf::Pbkdf2(params) => { | ||||
|             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`.
 | ||||
|             //
 | ||||
|             // Both of these seem fairly convincing that it shouldn't be 0.
 | ||||
|             //
 | ||||
|             // Reference:
 | ||||
|             //
 | ||||
|             // https://www.ietf.org/rfc/rfc2898.txt
 | ||||
|             //
 | ||||
|             // Additionally, we always compute a derived key of 32 bytes so reject anything that
 | ||||
|             // says otherwise.
 | ||||
|             if params.c == 0 || params.dklen != DKLEN { | ||||
|                 return Err(Error::InvalidPbkdf2Param); | ||||
|             } | ||||
| 
 | ||||
|             crypto::pbkdf2::pbkdf2( | ||||
|                 &mut mac, | ||||
|                 params.salt.as_bytes(), | ||||
|                 params.c, | ||||
|                 dk.as_mut_bytes(), | ||||
|             ); | ||||
|         } | ||||
|         Kdf::Scrypt(params) => { | ||||
|             // RFC7914 declares that all these parameters must be greater than 1:
 | ||||
|             //
 | ||||
|             // - `N`: costParameter.
 | ||||
|             // - `r`: blockSize.
 | ||||
|             // - `p`: parallelizationParameter
 | ||||
|             //
 | ||||
|             // Reference:
 | ||||
|             //
 | ||||
|             // https://tools.ietf.org/html/rfc7914
 | ||||
|             //
 | ||||
|             // Additionally, we always compute a derived key of 32 bytes so reject anything that
 | ||||
|             // says otherwise.
 | ||||
|             if params.n <= 1 || params.r == 0 || params.p == 0 || params.dklen != DKLEN { | ||||
|                 return Err(Error::InvalidScryptParam); | ||||
|             } | ||||
| 
 | ||||
|             // Ensure that `n` is power of 2.
 | ||||
|             if params.n != 2u32.pow(log2_int(params.n)) { | ||||
|                 return Err(Error::InvalidScryptParam); | ||||
|             } | ||||
| 
 | ||||
|             crypto::scrypt::scrypt( | ||||
|                 password, | ||||
|                 params.salt.as_bytes(), | ||||
|                 &crypto::scrypt::ScryptParams::new(log2_int(params.n) as u8, params.r, params.p), | ||||
|                 dk.as_mut_bytes(), | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Ok(dk) | ||||
| } | ||||
| 
 | ||||
| /// Compute floor of log2 of a u32.
 | ||||
| fn log2_int(x: u32) -> u32 { | ||||
|     if x == 0 { | ||||
|         return 0; | ||||
|     } | ||||
|     31 - x.leading_zeros() | ||||
| } | ||||
							
								
								
									
										15
									
								
								eth2/utils/eth2_keystore/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								eth2/utils/eth2_keystore/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| //! Provides a JSON keystore for a BLS keypair, as specified by
 | ||||
| //! [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335).
 | ||||
| 
 | ||||
| mod derived_key; | ||||
| mod keystore; | ||||
| mod plain_text; | ||||
| 
 | ||||
| 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; | ||||
							
								
								
									
										34
									
								
								eth2/utils/eth2_keystore/src/plain_text.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								eth2/utils/eth2_keystore/src/plain_text.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| use zeroize::Zeroize; | ||||
| 
 | ||||
| /// Provides wrapper around `Vec<u8>` that implements `Zeroize`.
 | ||||
| #[derive(Zeroize, Clone, PartialEq)] | ||||
| #[zeroize(drop)] | ||||
| pub struct PlainText(Vec<u8>); | ||||
| 
 | ||||
| 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 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<Vec<u8>> for PlainText { | ||||
|     fn from(vec: Vec<u8>) -> Self { | ||||
|         Self(vec) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										107
									
								
								eth2/utils/eth2_keystore/tests/eip2335_vectors.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								eth2/utils/eth2_keystore/tests/eip2335_vectors.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| //! Test cases taken from:
 | ||||
| //!
 | ||||
| //! https://eips.ethereum.org/EIPS/eip-2335
 | ||||
| 
 | ||||
| #![cfg(test)] | ||||
| 
 | ||||
| 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.as_bytes()).unwrap(); | ||||
|     assert_eq!(keypair.sk.as_raw().as_bytes(), expected_sk); | ||||
|     keystore | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn eip2335_test_vector_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 | ||||
|         } | ||||
|         "#;
 | ||||
| 
 | ||||
|     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": { | ||||
|                     "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 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"); | ||||
| } | ||||
							
								
								
									
										1011
									
								
								eth2/utils/eth2_keystore/tests/json.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1011
									
								
								eth2/utils/eth2_keystore/tests/json.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										322
									
								
								eth2/utils/eth2_keystore/tests/params.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								eth2/utils/eth2_keystore/tests/params.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,322 @@ | ||||
| #![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.as_bytes()) | ||||
|         .err() | ||||
|         .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_dklen_not_32() { | ||||
|     let vector = r#" | ||||
|             { | ||||
|             "crypto": { | ||||
|                 "kdf": { | ||||
|                     "function": "scrypt", | ||||
|                     "params": { | ||||
|                         "dklen": 33, | ||||
|                         "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_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_eq!(decrypt_error(vector), Error::InvalidScryptParam); | ||||
| } | ||||
| 
 | ||||
| #[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_eq!(decrypt_error(vector), Error::InvalidPbkdf2Param); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn pbkdf2_dklen_not_32() { | ||||
|     let vector = r#" | ||||
|             { | ||||
|             "crypto": { | ||||
|                 "kdf": { | ||||
|                     "function": "pbkdf2", | ||||
|                     "params": { | ||||
|                         "dklen": 33, | ||||
|                         "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_eq!(decrypt_error(vector), Error::InvalidPbkdf2Param); | ||||
| } | ||||
							
								
								
									
										108
									
								
								eth2/utils/eth2_keystore/tests/tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								eth2/utils/eth2_keystore/tests/tests.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| #![cfg(test)] | ||||
| 
 | ||||
| use bls::Keypair; | ||||
| use eth2_keystore::{Error, Keystore, KeystoreBuilder}; | ||||
| use std::fs::OpenOptions; | ||||
| use tempfile::tempdir; | ||||
| 
 | ||||
| const GOOD_PASSWORD: &[u8] = &[42, 42, 42]; | ||||
| const BAD_PASSWORD: &[u8] = &[43, 43, 43]; | ||||
| 
 | ||||
| #[test] | ||||
| fn empty_password() { | ||||
|     assert_eq!( | ||||
|         KeystoreBuilder::new(&Keypair::random(), "".as_bytes(), "".into()) | ||||
|             .err() | ||||
|             .unwrap(), | ||||
|         Error::EmptyPassword | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn string_round_trip() { | ||||
|     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" | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[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" | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[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" | ||||
|     ); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user