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:
Paul Hauner 2020-05-19 11:23:08 +10:00 committed by GitHub
parent 314fae41fe
commit c93f9c351b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 102 additions and 295 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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,
);

View File

@ -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" }

View File

@ -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 })
}

View File

@ -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 {

View File

@ -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);

View File

@ -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 {

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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};

View File

@ -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 = []

View File

@ -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,

View File

@ -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")]

View File

@ -32,3 +32,9 @@ impl From<Vec<u8>> for PlainText {
Self(vec)
}
}
impl AsRef<[u8]> for PlainText {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

View File

@ -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());
}
}

View File

@ -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)?;

View File

@ -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;

View File

@ -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
}

View File

@ -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"
);
}

View File

@ -18,5 +18,4 @@ tiny-bip39 = "0.7.3"
[dev-dependencies]
hex = "0.3"
eth2_ssz = { path = "../../consensus/ssz" }
tempfile = "3.1.0"

View File

@ -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"
);

View File

@ -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;

View File

@ -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
);
}
}

View File

@ -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>>>,