Improve bls::SecretKey privacy (#1164)
* Improve bls::SecretKey privacy * Add missed file * Remove more methods from bls::SecretKey * Add as_bytes() to SecretKey, remove as_raw * Remove as_raw * Add back as_raw * Address review comments
This commit is contained in:
parent
314fae41fe
commit
c93f9c351b
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -433,6 +433,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_hex",
|
||||
"tree_hash",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1334,7 +1335,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"eth2_key_derivation",
|
||||
"eth2_keystore",
|
||||
"eth2_ssz",
|
||||
"hex 0.3.2",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
@ -5294,8 +5294,6 @@ dependencies = [
|
||||
"bls",
|
||||
"deposit_contract",
|
||||
"eth2_keystore",
|
||||
"eth2_ssz",
|
||||
"eth2_ssz_derive",
|
||||
"eth2_wallet",
|
||||
"rand 0.7.3",
|
||||
"rayon",
|
||||
|
@ -479,7 +479,7 @@ mod release_tests {
|
||||
let num_validators =
|
||||
num_committees * E::slots_per_epoch() as usize * spec.target_committee_size;
|
||||
let mut state_builder =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(num_validators, &spec);
|
||||
let slot_offset = 1000 * E::slots_per_epoch() + E::slots_per_epoch() / 2;
|
||||
let slot = spec.genesis_slot + slot_offset;
|
||||
state_builder.teleport_to_slot(slot);
|
||||
@ -897,7 +897,7 @@ mod release_tests {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let num_validators = 32;
|
||||
let mut state_builder =
|
||||
TestingBeaconStateBuilder::<MainnetEthSpec>::from_default_keypairs_file_if_exists(
|
||||
TestingBeaconStateBuilder::<MainnetEthSpec>::from_deterministic_keypairs(
|
||||
num_validators,
|
||||
&spec,
|
||||
);
|
||||
|
@ -17,8 +17,6 @@ eth2_keystore = { path = "../../crypto/eth2_keystore" }
|
||||
types = { path = "../../consensus/types" }
|
||||
rand = "0.7.2"
|
||||
deposit_contract = { path = "../deposit_contract" }
|
||||
eth2_ssz = { path = "../../consensus/ssz" }
|
||||
eth2_ssz_derive = { path = "../../consensus/ssz_derive" }
|
||||
rayon = "1.3.0"
|
||||
tree_hash = { path = "../../consensus/tree_hash" }
|
||||
|
||||
|
@ -3,9 +3,8 @@
|
||||
//! we're confident that no-one is using these keypairs anymore (hopefully mid-June 2020).
|
||||
#![cfg(feature = "unencrypted_keys")]
|
||||
|
||||
use bls::{BLS_PUBLIC_KEY_BYTE_SIZE as PK_LEN, BLS_SECRET_KEY_BYTE_SIZE as SK_LEN};
|
||||
use eth2_keystore::PlainText;
|
||||
use ssz::Decode;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
@ -32,35 +31,18 @@ pub fn load_unencrypted_keypair<P: AsRef<Path>>(path: P) -> Result<Keypair, Stri
|
||||
|
||||
let bytes: PlainText = bytes.into();
|
||||
|
||||
SszEncodableKeypair::from_ssz_bytes(bytes.as_bytes())
|
||||
.map(Into::into)
|
||||
.map_err(|e| format!("Unable to decode keypair: {:?}", e))
|
||||
}
|
||||
|
||||
/// A helper struct to allow SSZ enc/dec for a `Keypair`.
|
||||
///
|
||||
/// This only exists as compatibility with the old scheme and should not be implemented on any new
|
||||
/// features.
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct SszEncodableKeypair {
|
||||
pk: PublicKey,
|
||||
sk: SecretKey,
|
||||
}
|
||||
|
||||
impl Into<Keypair> for SszEncodableKeypair {
|
||||
fn into(self) -> Keypair {
|
||||
Keypair {
|
||||
sk: self.sk,
|
||||
pk: self.pk,
|
||||
}
|
||||
if bytes.len() != PK_LEN + SK_LEN {
|
||||
return Err(format!("Invalid keypair byte length: {}", bytes.len()));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keypair> for SszEncodableKeypair {
|
||||
fn from(kp: Keypair) -> Self {
|
||||
Self {
|
||||
sk: kp.sk,
|
||||
pk: kp.pk,
|
||||
}
|
||||
}
|
||||
let pk_bytes = &bytes.as_bytes()[..PK_LEN];
|
||||
let sk_bytes = &bytes.as_bytes()[PK_LEN..];
|
||||
|
||||
let pk = PublicKey::from_bytes(pk_bytes)
|
||||
.map_err(|e| format!("Unable to decode public key: {:?}", e))?;
|
||||
|
||||
let sk = SecretKey::from_bytes(sk_bytes)
|
||||
.map_err(|e| format!("Unable to decode secret key: {:?}", e))?;
|
||||
|
||||
Ok(Keypair { pk, sk })
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ impl Harness {
|
||||
check_keystore(&validator.dir().join(VOTING_KEYSTORE_FILE), &password_dir);
|
||||
|
||||
if !config.random_voting_keystore {
|
||||
assert_eq!(voting_keypair, generate_deterministic_keypair(0))
|
||||
assert_eq!(voting_keypair.pk, generate_deterministic_keypair(0).pk)
|
||||
}
|
||||
|
||||
// Use OR here instead of AND so we *always* check for the withdrawal keystores if random
|
||||
@ -128,11 +128,11 @@ impl Harness {
|
||||
let withdrawal_keypair = check_keystore(&withdrawal_keystore_path, &password_dir);
|
||||
|
||||
if !config.random_withdrawal_keystore {
|
||||
assert_eq!(withdrawal_keypair, generate_deterministic_keypair(1))
|
||||
assert_eq!(withdrawal_keypair.pk, generate_deterministic_keypair(1).pk)
|
||||
}
|
||||
|
||||
// The withdrawal keys should be distinct from the voting keypairs.
|
||||
assert_ne!(withdrawal_keypair, voting_keypair);
|
||||
assert_ne!(withdrawal_keypair.pk, voting_keypair.pk);
|
||||
}
|
||||
|
||||
if !config.store_withdrawal_keystore && !config.random_withdrawal_keystore {
|
||||
|
@ -15,7 +15,7 @@ pub struct BlockProcessingBuilder<'a, T: EthSpec> {
|
||||
impl<'a, T: EthSpec> BlockProcessingBuilder<'a, T> {
|
||||
pub fn new(num_validators: usize, state_slot: Slot, spec: &'a ChainSpec) -> Self {
|
||||
let mut state_builder =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(num_validators, &spec);
|
||||
state_builder.teleport_to_slot(state_slot);
|
||||
let (state, keypairs) = state_builder.build();
|
||||
let block_builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
@ -22,7 +22,7 @@ pub struct BlockBuilder<T: EthSpec> {
|
||||
impl<T: EthSpec> BlockBuilder<T> {
|
||||
pub fn new(num_validators: usize, spec: &ChainSpec) -> Self {
|
||||
let state_builder =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(num_validators, &spec);
|
||||
let block_builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
||||
Self {
|
||||
|
@ -5,11 +5,9 @@ const TREE_HASH_LOOPS: usize = 1_000;
|
||||
const VALIDATOR_COUNT: usize = 1_000;
|
||||
|
||||
fn build_state<T: EthSpec>(validator_count: usize) -> BeaconState<T> {
|
||||
let (state, _keypairs) = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
|
||||
validator_count,
|
||||
&T::default_spec(),
|
||||
)
|
||||
.build();
|
||||
let (state, _keypairs) =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, &T::default_spec())
|
||||
.build();
|
||||
|
||||
assert_eq!(state.validators.len(), validator_count);
|
||||
assert_eq!(state.balances.len(), validator_count);
|
||||
|
@ -11,7 +11,7 @@ fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
// Build a state for testing.
|
||||
let build_state = |validator_count: usize| -> BeaconState<T> {
|
||||
let builder: TestingBeaconStateBuilder<T> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
state.build_committee_cache(relative_epoch, &spec).unwrap();
|
||||
|
||||
@ -114,7 +114,7 @@ fn cache_initialization() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
|
||||
state.slot =
|
||||
@ -176,7 +176,7 @@ fn clone_config() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
|
||||
state.build_all_caches(&spec).unwrap();
|
||||
@ -374,7 +374,7 @@ mod get_outstanding_deposit_len {
|
||||
fn state() -> BeaconState<MinimalEthSpec> {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(16, &spec);
|
||||
let (state, _keypairs) = builder.build();
|
||||
|
||||
state
|
||||
|
@ -1,10 +1,10 @@
|
||||
use super::super::{generate_deterministic_keypairs, KeypairsFile};
|
||||
use super::super::generate_deterministic_keypairs;
|
||||
use crate::test_utils::{AttestationTestTask, TestingPendingAttestationBuilder};
|
||||
use crate::*;
|
||||
use bls::get_withdrawal_credentials;
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs";
|
||||
|
||||
@ -29,44 +29,6 @@ pub struct TestingBeaconStateBuilder<T: EthSpec> {
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
||||
/// Attempts to load validators from a file in `$HOME/.lighthouse/keypairs.raw_keypairs`. If
|
||||
/// the file is unavailable, it generates the keys at runtime.
|
||||
///
|
||||
/// If the `$HOME` environment variable is not set, the local directory is used.
|
||||
///
|
||||
/// See the `Self::from_keypairs_file` method for more info.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the file does not contain enough keypairs or is invalid.
|
||||
pub fn from_default_keypairs_file_if_exists(validator_count: usize, spec: &ChainSpec) -> Self {
|
||||
let dir = dirs::home_dir()
|
||||
.map(|home| home.join(".lighthouse"))
|
||||
.unwrap_or_else(|| PathBuf::from(""));
|
||||
let file = dir.join(KEYPAIRS_FILE);
|
||||
|
||||
if file.exists() {
|
||||
TestingBeaconStateBuilder::from_keypairs_file(validator_count, &file, spec)
|
||||
} else {
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, spec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the initial validator keypairs from a file on disk.
|
||||
///
|
||||
/// Loading keypairs from file is ~10x faster than generating them. Use the `gen_keys` command
|
||||
/// on the `test_harness` binary to generate the keys. In the `test_harness` dir, run `cargo
|
||||
/// run -- gen_keys -h` for help.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the file does not exist, is invalid or does not contain enough keypairs.
|
||||
pub fn from_keypairs_file(validator_count: usize, path: &Path, spec: &ChainSpec) -> Self {
|
||||
debug!("Loading {} keypairs from file...", validator_count);
|
||||
let keypairs = Vec::from_raw_file(path, validator_count).unwrap();
|
||||
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
|
||||
}
|
||||
|
||||
/// Generates the validator keypairs deterministically.
|
||||
pub fn from_deterministic_keypairs(validator_count: usize, spec: &ChainSpec) -> Self {
|
||||
debug!("Generating {} deterministic keypairs...", validator_count);
|
||||
|
@ -1,128 +0,0 @@
|
||||
use crate::*;
|
||||
use rayon::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::io::{Error, ErrorKind, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
pub const PUBLIC_KEY_BYTES_LEN: usize = 96;
|
||||
pub const SECRET_KEY_BYTES_LEN: usize = 32;
|
||||
|
||||
pub const BATCH_SIZE: usize = 1_000; // ~15MB
|
||||
|
||||
pub const KEYPAIR_BYTES_LEN: usize = PUBLIC_KEY_BYTES_LEN + SECRET_KEY_BYTES_LEN;
|
||||
pub const BATCH_BYTE_LEN: usize = KEYPAIR_BYTES_LEN * BATCH_SIZE;
|
||||
|
||||
/// Defines a trait that allows reading/writing a vec of `Keypair` from/to a file.
|
||||
pub trait KeypairsFile {
|
||||
/// Write to file, without guaranteeing interoperability with other clients.
|
||||
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error>;
|
||||
/// Read from file, without guaranteeing interoperability with other clients.
|
||||
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error>;
|
||||
}
|
||||
|
||||
impl KeypairsFile for Vec<Keypair> {
|
||||
/// Write the keypairs to file, using the fastest possible method without guaranteeing
|
||||
/// interoperability with other clients.
|
||||
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error> {
|
||||
let mut keypairs_file = File::create(path)?;
|
||||
|
||||
for keypair_batch in keypairs.chunks(BATCH_SIZE) {
|
||||
let mut buf = Vec::with_capacity(BATCH_BYTE_LEN);
|
||||
|
||||
for keypair in keypair_batch {
|
||||
buf.append(&mut keypair.sk.as_raw().as_bytes());
|
||||
buf.append(&mut keypair.pk.clone().as_uncompressed_bytes());
|
||||
}
|
||||
|
||||
keypairs_file.write_all(&buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the keypairs from file, using the fastest possible method without guaranteeing
|
||||
/// interoperability with other clients.
|
||||
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error> {
|
||||
let mut keypairs_file = File::open(path)?;
|
||||
|
||||
let mut keypairs = Vec::with_capacity(count);
|
||||
|
||||
let indices: Vec<usize> = (0..count).collect();
|
||||
|
||||
for batch in indices.chunks(BATCH_SIZE) {
|
||||
let mut buf = vec![0; batch.len() * KEYPAIR_BYTES_LEN];
|
||||
keypairs_file.read_exact(&mut buf)?;
|
||||
|
||||
let mut keypair_batch = batch
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| {
|
||||
let sk_start = i * KEYPAIR_BYTES_LEN;
|
||||
let sk_end = sk_start + SECRET_KEY_BYTES_LEN;
|
||||
let sk = SecretKey::from_bytes(&buf[sk_start..sk_end])
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes"))
|
||||
.unwrap();
|
||||
|
||||
let pk_start = sk_end;
|
||||
let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN;
|
||||
let pk = PublicKey::from_uncompressed_bytes(&buf[pk_start..pk_end])
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes"))
|
||||
.unwrap();
|
||||
|
||||
Keypair { sk, pk }
|
||||
})
|
||||
.collect();
|
||||
|
||||
keypairs.append(&mut keypair_batch);
|
||||
}
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use std::fs::remove_file;
|
||||
|
||||
fn random_keypairs(n: usize) -> Vec<Keypair> {
|
||||
(0..n).into_par_iter().map(|_| Keypair::random()).collect()
|
||||
}
|
||||
|
||||
fn random_tmp_file() -> String {
|
||||
let rng = thread_rng();
|
||||
|
||||
rng.sample_iter(&Alphanumeric).take(7).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn read_write_consistency_small_batch() {
|
||||
let num_keypairs = 10;
|
||||
let keypairs = random_keypairs(num_keypairs);
|
||||
|
||||
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
|
||||
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
|
||||
|
||||
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
|
||||
remove_file(keypairs_path).unwrap();
|
||||
|
||||
assert_eq!(keypairs, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn read_write_consistency_big_batch() {
|
||||
let num_keypairs = BATCH_SIZE + 1;
|
||||
let keypairs = random_keypairs(num_keypairs);
|
||||
|
||||
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
|
||||
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
|
||||
|
||||
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
|
||||
remove_file(keypairs_path).unwrap();
|
||||
|
||||
assert_eq!(keypairs, decoded);
|
||||
}
|
||||
}
|
@ -4,14 +4,12 @@
|
||||
mod macros;
|
||||
mod builders;
|
||||
mod generate_deterministic_keypairs;
|
||||
mod keypairs_file;
|
||||
mod test_random;
|
||||
|
||||
pub use builders::*;
|
||||
pub use generate_deterministic_keypairs::generate_deterministic_keypair;
|
||||
pub use generate_deterministic_keypairs::generate_deterministic_keypairs;
|
||||
pub use generate_deterministic_keypairs::load_keypairs_from_yaml;
|
||||
pub use keypairs_file::KeypairsFile;
|
||||
pub use rand::{RngCore, SeedableRng};
|
||||
pub use rand_xorshift::XorShiftRng;
|
||||
pub use test_random::{test_random_instance, TestRandom};
|
||||
|
@ -16,6 +16,7 @@ eth2_ssz = "0.1.2"
|
||||
eth2_ssz_types = { path = "../../consensus/ssz_types" }
|
||||
tree_hash = "0.1.0"
|
||||
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
|
||||
zeroize = { version = "1.0.0", features = ["zeroize_derive"] }
|
||||
|
||||
[features]
|
||||
fake_crypto = []
|
||||
|
@ -1,9 +1,8 @@
|
||||
use super::{PublicKey, SecretKey};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone)]
|
||||
pub struct Keypair {
|
||||
pub sk: SecretKey,
|
||||
pub pk: PublicKey,
|
||||
|
@ -4,6 +4,7 @@ extern crate ssz;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod keypair;
|
||||
mod plain_text;
|
||||
mod public_key_bytes;
|
||||
mod secret_key;
|
||||
mod signature_bytes;
|
||||
@ -14,6 +15,7 @@ pub use crate::public_key_bytes::PublicKeyBytes;
|
||||
pub use crate::secret_key::SecretKey;
|
||||
pub use crate::signature_bytes::SignatureBytes;
|
||||
pub use milagro_bls::{compress_g2, hash_to_curve_g2};
|
||||
pub use plain_text::PlainText;
|
||||
pub use signature_set::{verify_signature_sets, SignatureSet};
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
|
@ -32,3 +32,9 @@ impl From<Vec<u8>> for PlainText {
|
||||
Self(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PlainText {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
extern crate rand;
|
||||
|
||||
use super::BLS_SECRET_KEY_BYTE_SIZE;
|
||||
use hex::encode as hex_encode;
|
||||
use crate::PlainText;
|
||||
use milagro_bls::SecretKey as RawSecretKey;
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use serde_hex::PrefixedHexVisitor;
|
||||
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
||||
use ssz::DecodeError;
|
||||
|
||||
/// A single BLS signature.
|
||||
///
|
||||
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
|
||||
/// serialization).
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
#[derive(Clone)]
|
||||
pub struct SecretKey(RawSecretKey);
|
||||
|
||||
impl SecretKey {
|
||||
/// Generate a new `Self` using `rand::thread_rng`.
|
||||
pub fn random() -> Self {
|
||||
SecretKey(RawSecretKey::random(&mut rand::thread_rng()))
|
||||
}
|
||||
@ -24,9 +21,13 @@ impl SecretKey {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
/// Returns the underlying point as compressed bytes.
|
||||
fn as_bytes(&self) -> Vec<u8> {
|
||||
self.as_raw().as_bytes()
|
||||
/// Returns the secret key as a byte array (wrapped in `PlainText` wrapper so it is zeroized on
|
||||
/// `Drop`).
|
||||
///
|
||||
/// Extreme care should be taken not to leak these bytes as they are the unencrypted secret
|
||||
/// key.
|
||||
pub fn as_bytes(&self) -> PlainText {
|
||||
self.as_raw().as_bytes().into()
|
||||
}
|
||||
|
||||
/// Instantiate a SecretKey from existing bytes.
|
||||
@ -42,40 +43,14 @@ impl SecretKey {
|
||||
}
|
||||
|
||||
/// Returns the underlying secret key.
|
||||
pub fn as_raw(&self) -> &RawSecretKey {
|
||||
pub(crate) fn as_raw(&self) -> &RawSecretKey {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl_ssz!(SecretKey, BLS_SECRET_KEY_BYTE_SIZE, "SecretKey");
|
||||
|
||||
impl_tree_hash!(SecretKey, BLS_SECRET_KEY_BYTE_SIZE);
|
||||
|
||||
impl Serialize for SecretKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&hex_encode(ssz_encode(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SecretKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?;
|
||||
let secret_key = SecretKey::from_ssz_bytes(&bytes[..])
|
||||
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
|
||||
Ok(secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ssz::ssz_encode;
|
||||
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
@ -85,9 +60,9 @@ mod tests {
|
||||
];
|
||||
let original = SecretKey::from_bytes(&byte_key).unwrap();
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
let decoded = SecretKey::from_ssz_bytes(&bytes).unwrap();
|
||||
let bytes = original.as_bytes();
|
||||
let decoded = SecretKey::from_bytes(bytes.as_ref()).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
assert!(original.as_bytes() == decoded.as_bytes());
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use crate::json_keystore::{
|
||||
Aes128Ctr, ChecksumModule, Cipher, CipherModule, Crypto, EmptyMap, EmptyString, JsonKeystore,
|
||||
Kdf, KdfModule, Scrypt, Sha256Checksum, Version,
|
||||
};
|
||||
use crate::plain_text::PlainText;
|
||||
use crate::PlainText;
|
||||
use crate::Uuid;
|
||||
use bls::{Keypair, PublicKey, SecretKey};
|
||||
use crypto::{digest::Digest, sha2::Sha256};
|
||||
@ -130,7 +130,7 @@ impl Keystore {
|
||||
uuid: Uuid,
|
||||
path: String,
|
||||
) -> Result<Self, Error> {
|
||||
let secret = PlainText::from(keypair.sk.as_raw().as_bytes());
|
||||
let secret: PlainText = keypair.sk.as_bytes();
|
||||
|
||||
let (cipher_text, checksum) = encrypt(secret.as_bytes(), password, &kdf, &cipher)?;
|
||||
|
||||
|
@ -3,13 +3,12 @@
|
||||
|
||||
mod derived_key;
|
||||
mod keystore;
|
||||
mod plain_text;
|
||||
|
||||
pub mod json_keystore;
|
||||
|
||||
pub use bls::PlainText;
|
||||
pub use keystore::{
|
||||
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;
|
||||
|
@ -14,7 +14,7 @@ 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);
|
||||
assert_eq!(keypair.sk.as_bytes().as_ref(), &expected_sk[..]);
|
||||
keystore
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,8 @@ fn string_round_trip() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(),
|
||||
keypair,
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
||||
keypair.pk,
|
||||
"should decrypt with good password"
|
||||
);
|
||||
}
|
||||
@ -77,8 +77,8 @@ fn file() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(),
|
||||
keypair,
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
||||
keypair.pk,
|
||||
"should decrypt with good password"
|
||||
);
|
||||
}
|
||||
@ -102,8 +102,8 @@ fn scrypt_params() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap(),
|
||||
keypair,
|
||||
decoded.decrypt_keypair(GOOD_PASSWORD).unwrap().pk,
|
||||
keypair.pk,
|
||||
"should decrypt with good password"
|
||||
);
|
||||
}
|
||||
|
@ -18,5 +18,4 @@ tiny-bip39 = "0.7.3"
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.3"
|
||||
eth2_ssz = { path = "../../consensus/ssz" }
|
||||
tempfile = "3.1.0"
|
||||
|
@ -4,7 +4,6 @@ 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;
|
||||
|
||||
@ -243,14 +242,14 @@ fn key_derivation_from_seed() {
|
||||
.expect("should decrypt voting keypair");
|
||||
|
||||
assert_eq!(
|
||||
voting_keypair.sk.as_ssz_bytes(),
|
||||
manually_derived_voting_key(i),
|
||||
voting_keypair.sk.as_bytes().as_ref(),
|
||||
&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_keypair.sk.as_bytes().as_ref(),
|
||||
&recovered_voting_key(&wallet, i)[..],
|
||||
"voting secret should match recovered"
|
||||
);
|
||||
|
||||
@ -260,20 +259,20 @@ fn key_derivation_from_seed() {
|
||||
.expect("should decrypt withdrawal keypair");
|
||||
|
||||
assert_eq!(
|
||||
withdrawal_keypair.sk.as_ssz_bytes(),
|
||||
manually_derived_withdrawal_key(i),
|
||||
withdrawal_keypair.sk.as_bytes().as_ref(),
|
||||
&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_keypair.sk.as_bytes().as_ref(),
|
||||
&recovered_withdrawal_key(&wallet, i)[..],
|
||||
"withdrawal secret should match recovered"
|
||||
);
|
||||
|
||||
assert_ne!(
|
||||
withdrawal_keypair.sk.as_ssz_bytes(),
|
||||
voting_keypair.sk.as_ssz_bytes(),
|
||||
withdrawal_keypair.sk.as_bytes().as_ref(),
|
||||
voting_keypair.sk.as_bytes().as_bytes(),
|
||||
"voting and withdrawal keypairs should be distinct"
|
||||
);
|
||||
|
||||
|
@ -553,7 +553,7 @@ fn genesis_yaml<T: EthSpec>(validator_count: usize, genesis_time: u64, output: P
|
||||
let spec = &T::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<T> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec);
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, spec);
|
||||
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
state.genesis_time = genesis_time;
|
||||
|
@ -296,7 +296,7 @@ impl TestValidator {
|
||||
let withdrawal_keypair = withdrawal_result.unwrap();
|
||||
assert_ne!(voting_keypair.pk, withdrawal_keypair.pk);
|
||||
} else {
|
||||
withdrawal_result.unwrap_err();
|
||||
withdrawal_result.err().unwrap();
|
||||
}
|
||||
|
||||
// Deposit tx file should not exist yet.
|
||||
@ -370,7 +370,7 @@ fn write_legacy_keypair<P: AsRef<Path>>(name: &str, dir: P) -> Keypair {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let mut keypair_bytes = keypair.pk.as_bytes();
|
||||
keypair_bytes.extend_from_slice(&keypair.sk.as_raw().as_bytes());
|
||||
keypair_bytes.extend_from_slice(keypair.sk.as_bytes().as_ref());
|
||||
|
||||
fs::write(dir.as_ref().join(name), &keypair_bytes).unwrap();
|
||||
|
||||
@ -409,12 +409,12 @@ fn upgrade_legacy_keypairs() {
|
||||
let dir = ValidatorDir::open(&validator_dir).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
voting_keypair,
|
||||
dir.voting_keypair(secrets_dir.path()).unwrap()
|
||||
voting_keypair.pk,
|
||||
dir.voting_keypair(secrets_dir.path()).unwrap().pk
|
||||
);
|
||||
assert_eq!(
|
||||
withdrawal_keypair,
|
||||
dir.withdrawal_keypair(secrets_dir.path()).unwrap()
|
||||
withdrawal_keypair.pk,
|
||||
dir.withdrawal_keypair(secrets_dir.path()).unwrap().pk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,12 +15,31 @@ use types::{
|
||||
};
|
||||
use validator_dir::{Manager as ValidatorManager, ValidatorDir};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct LocalValidator {
|
||||
validator_dir: ValidatorDir,
|
||||
voting_keypair: Keypair,
|
||||
}
|
||||
|
||||
/// We derive our own `PartialEq` to avoid doing equality checks between secret keys.
|
||||
///
|
||||
/// It's nice to avoid secret key comparisons from a security perspective, but it's also a little
|
||||
/// risky when it comes to `HashMap` integrity (that's why we need `PartialEq`).
|
||||
///
|
||||
/// Currently, we obtain keypairs from keystores where we derive the `PublicKey` from a `SecretKey`
|
||||
/// via a hash function. In order to have two equal `PublicKey` with different `SecretKey` we would
|
||||
/// need to have either:
|
||||
///
|
||||
/// - A serious upstream integrity error.
|
||||
/// - A hash collision.
|
||||
///
|
||||
/// It seems reasonable to make these two assumptions in order to avoid the equality checks.
|
||||
impl PartialEq for LocalValidator {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.validator_dir == other.validator_dir
|
||||
&& self.voting_keypair.pk == other.voting_keypair.pk
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorStore<T, E: EthSpec> {
|
||||
validators: Arc<RwLock<HashMap<PublicKey, LocalValidator>>>,
|
||||
|
Loading…
Reference in New Issue
Block a user