Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion anchor/database/src/keysplit_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ impl NetworkDatabase {
// Get the public key for each operator id
pub fn get_keys_for_operators(
&self,
operators: Vec<u64>,
operators: &[u64],
) -> Result<Vec<Rsa<Public>>, DatabaseError> {
let conn = self.connection()?;
let mut stmt = conn.prepare(sql_operations::GET_OPERATOR_KEY)?;
Expand Down
1 change: 1 addition & 0 deletions anchor/keysplit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ hex = { workspace = true }
openssl = { workspace = true }
operator_key = { workspace = true }
pbkdf2 = { workspace = true }
rayon = "1.10.0"
scrypt = "0.11.0"
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
5 changes: 3 additions & 2 deletions anchor/keysplit/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ pub struct Manual {
pub struct SharedKeygenOptions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something I thought of is that we should probably adjust our cli names here to match their docs https://docs.ssv.network/stakers/tools/ssv-keys-cli/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of our flags function differently, e.g. keystore_paths takes multiple paths instead of a folder if the user wants to split multiple and output_path points to the folder to place the input into (instead of being the path for the file to be written). We could change that of course, but personally I prefer our current behavior. wdyt?

My point is that matching the names (IMO) only has value if we also match the behavior.

#[clap(
long,
help = "Path to the validator keystore file",
help = "Path(s) to the validator keystore file",
num_args = 1..,
value_name = "PATH"
)]
pub keystore_path: String,
pub keystore_paths: Vec<String>,

#[clap(
long,
Expand Down
21 changes: 14 additions & 7 deletions anchor/keysplit/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
EncryptedKeyShare, KeyShare, KeysplitError, ValidatorKeys,
cli::SharedKeygenOptions,
keystore::{KdfparamsType, Keystore},
split::Split,
};

struct Aes128Ctr {
Expand Down Expand Up @@ -91,9 +92,9 @@ pub fn extract_key(keystore: &Keystore, password: &str) -> Result<ValidatorKeys,
}

// Given a secret key, split it into parts
pub fn split_keys(
pub fn split_key(
shared: &SharedKeygenOptions,
sk: SecretKey,
sk: &SecretKey,
) -> Result<Vec<(KeyId, SecretKey)>, KeysplitError> {
let num_operators = shared.operators.0.len();
let threshold = num_operators - ((num_operators - 1) / 3);
Expand All @@ -105,15 +106,16 @@ pub fn split_keys(
.iter()
.map(|id| KeyId::try_from(*id).unwrap());

split(&sk, threshold as u64, key_ids)
split(sk, threshold as u64, key_ids)
.map_err(|e| KeysplitError::SplitFailure(format!("Failed to split key: {e:?}")))
}

// Encrypt the keyshare with the operators rsa public key
pub fn encrypt_keyshares(
key_shares: Vec<KeyShare>,
) -> Result<Vec<EncryptedKeyShare>, KeysplitError> {
key_shares
split: Split<KeyShare>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, it's not clear to me what the difference between Vec<KeyShare> and Split<KeyShare> is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a comment

) -> Result<Split<EncryptedKeyShare>, KeysplitError> {
let key_shares = split
.key_shares
.into_iter()
.map(|share| {
let pkey = PKey::from_rsa(share.public_key.clone())
Expand Down Expand Up @@ -144,5 +146,10 @@ pub fn encrypt_keyshares(
share_public_key: share.keyshare.public_key(),
})
})
.collect()
.collect::<Result<Vec<_>, _>>()?;

Ok(Split {
nonce: split.nonce,
key_shares,
})
}
62 changes: 38 additions & 24 deletions anchor/keysplit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ use crypto::extract_key;
use error::KeysplitError;
use global_config::GlobalConfig;
use openssl::{pkey::Public, rsa::Rsa};
use rayon::prelude::*;
use tracing::info;
use types::{PublicKey, SecretKey};

use crate::{
crypto::{encrypt_keyshares, split_keys},
crypto::{encrypt_keyshares, split_key},
output::OutputData,
split::{manual_split, onchain_split},
};
Expand All @@ -18,27 +19,27 @@ mod cli;
mod crypto;
mod error;
mod keystore;
mod output;
pub mod output;
mod split;
mod util;

// A specific operators keyshare
pub(crate) struct KeyShare {
pub struct KeyShare {
id: u64,
public_key: Rsa<Public>,
keyshare: SecretKey,
}

// A keyshare where the secretkey has been encrypted with the operators public key
pub(crate) struct EncryptedKeyShare {
pub struct EncryptedKeyShare {
id: u64,
public_key: Rsa<Public>,
share_public_key: PublicKey,
encrypted_keyshare: Vec<u8>,
}

// PK and SK from keystore file
struct ValidatorKeys {
pub struct ValidatorKeys {
public_key: PublicKey,
secret_key: SecretKey,
}
Expand All @@ -47,48 +48,61 @@ pub fn run_keysplitter(
keysplit: Keysplit,
global_config: GlobalConfig,
) -> Result<(), KeysplitError> {
let shared = keysplit.get_shared().clone();
let mut shared = keysplit.get_shared();
shared.operators.0.sort_unstable();
info!("----- Anchor Keysplitter -----");

// 1) Read in the keystore file and parse it into a usable format
info!(
"Reading in validator keystore file from {}...",
shared.keystore_path
);
let keystore_file = File::open(shared.keystore_path.clone())
.map_err(|e| KeysplitError::Keystore(format!("Failed to open keystore file: {e}")))?;
let keystore = keystore::parse_keystore(keystore_file)?;
info!("Successfully read in validator keystore file");
// 1) Read in the keystore files and parse them into a usable format
let keystores = shared
.keystore_paths
.iter()
.map(|path| {
info!("Reading in validator keystore file from {path}...",);
let keystore_file = File::open(path).map_err(|e| {
KeysplitError::Keystore(format!("Failed to open keystore file: {e}"))
})?;
keystore::parse_keystore(keystore_file)
})
.collect::<Result<Vec<_>, _>>()?;
info!("Successfully read in validator keystore file(s)");

// 2) Extract the validator keys from the keystore file
info!("Extracting keys from keystore file...");
let keys = extract_key(&keystore, &shared.password)?;
info!("Successfully extracted keys from keystore file");
info!("Extracting keys from keystore file(s)...");
let keys = keystores
.into_par_iter()
.map(|keystore| extract_key(&keystore, &shared.password))
.collect::<Result<Vec<_>, _>>()?;
info!("Successfully extracted keys from keystore file(s)");

// 3) Split the key into keyshares and group together relevant information
info!(
"Splitting validator key into {} shares...",
"Splitting validator key(s) into {} shares...",
shared.operators.0.len()
);
let (keyshares, nonce) = match keysplit.subcommand {
KeygenSubcommands::Manual(manual) => manual_split(manual, keys.secret_key.clone()),
let splits = match keysplit.subcommand {
KeygenSubcommands::Manual(manual) => {
manual_split(manual, keys.iter().map(|k| &k.secret_key))
}
KeygenSubcommands::Onchain(onchain) => {
onchain_split(onchain, global_config, keys.secret_key.clone())
onchain_split(onchain, global_config, keys.iter().map(|k| &k.secret_key))
}
}?;
info!("Successfully split validator key into shares");

// 4) Encrypt the keyshares with the operators public keys
info!("Encrypting keyshares...");
let encrypted_keyshares = encrypt_keyshares(keyshares)?;
let encrypted_keyshares = splits
.into_iter()
.map(encrypt_keyshares)
.collect::<Result<Vec<_>, _>>()?;
info!("Encrypted all keyshares!");

// 5) Construct the payload and turn data into proper output format.
info!(
"Constructing output and writing to file {}...",
shared.output_path
);
let output = OutputData::new(encrypted_keyshares, shared.clone(), keys, nonce);
let output = OutputData::new(encrypted_keyshares, &shared, keys);

// 6) Write output data to file
let json_data = serde_json::to_string_pretty(&output).map_err(|e| {
Expand Down
46 changes: 28 additions & 18 deletions anchor/keysplit/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use openssl::{pkey::Public, rsa::Rsa};
use serde::Serialize;
use types::{Address, PublicKey};

use crate::{EncryptedKeyShare, ValidatorKeys, cli::SharedKeygenOptions, util::serialize_rsa};
use crate::{
EncryptedKeyShare, ValidatorKeys, cli::SharedKeygenOptions, split::Split, util::serialize_rsa,
};

const VERSION: &str = "v1.2.1";

Expand Down Expand Up @@ -61,28 +63,36 @@ impl From<EncryptedKeyShare> for Operator {

impl OutputData {
pub fn new(
encrypted_keys: Vec<EncryptedKeyShare>,
shared: SharedKeygenOptions,
keys: ValidatorKeys,
nonce: u64,
encrypted_keys: Vec<Split<EncryptedKeyShare>>,
shared: &SharedKeygenOptions,
keys: Vec<ValidatorKeys>,
) -> Self {
let payload = Payload::new(&encrypted_keys, &keys, nonce, shared.owner);
let operators: Vec<Operator> = encrypted_keys.into_iter().map(Operator::from).collect();

let output_key_data = OutputKeyData {
owner_nonce: nonce,
owner_address: shared.owner,
public_key: keys.public_key,
operators,
};
let shares = encrypted_keys
.into_iter()
.zip(keys)
.map(|(share, key)| {
let payload = Payload::new(&share.key_shares, &key, share.nonce, shared.owner);
let operators: Vec<Operator> =
share.key_shares.into_iter().map(Operator::from).collect();

let output_key_data = OutputKeyData {
owner_nonce: share.nonce,
owner_address: shared.owner,
public_key: key.public_key,
operators,
};

OutputKeyShare {
data: output_key_data,
payload,
}
})
.collect();

Self {
version: VERSION.to_string(),
created_at: Utc::now(),
shares: vec![OutputKeyShare {
data: output_key_data,
payload,
}],
shares,
}
}
}
Expand Down
Loading
Loading