mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
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:
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user