Skip to content

Commit

Permalink
Add support for generating presigned exit message from private key
Browse files Browse the repository at this point in the history
  • Loading branch information
mksh committed Aug 28, 2024
1 parent ae15e9b commit 73df6cf
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 11 deletions.
40 changes: 30 additions & 10 deletions src/cli/presigned_exit_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub struct PresignedExitMessageSubcommandOpts {
/// argument, and wait for the CLI to ask you
/// for your mnemonic as otherwise it will
/// appear in your shell history.
#[arg(long)]
pub mnemonic: String,
#[arg(long, required_unless_present = "private_key")]
pub mnemonic: Option<String>,

/// The name of Ethereum PoS chain you are targeting.
///
Expand All @@ -28,8 +28,16 @@ pub struct PresignedExitMessageSubcommandOpts {
/// and you want to generate for the 2nd validator,
/// the validator_start_index would be 1.
/// If no index specified, it will be set to 0.
#[arg(long, visible_alias = "validator_seed_index")]
pub validator_seed_index: u32,
#[arg(
long,
visible_alias = "validator_seed_index",
required_unless_present = "private_key"
)]
pub validator_seed_index: Option<u32>,

/// Validator private key bytes in hex form
#[arg(long, required_unless_present_all = ["mnemonic", "validator_seed_index"])]
pub private_key: Option<String>,

/// On-chain beacon index of the validator.
#[arg(long, visible_alias = "validator_beacon_index")]
Expand Down Expand Up @@ -82,12 +90,24 @@ impl PresignedExitMessageSubcommandOpts {
},
);

let (voluntary_exit, key_material) = voluntary_exit::voluntary_exit_message_from_mnemonic(
self.mnemonic.as_bytes(),
self.validator_seed_index as u64,
self.validator_beacon_index as u64,
self.epoch,
);
let (voluntary_exit, key_material) = if self.mnemonic.is_some() {
voluntary_exit::voluntary_exit_message_from_mnemonic(
self.mnemonic.clone().unwrap().as_bytes(),
self.validator_seed_index.unwrap() as u64,
self.validator_beacon_index as u64,
self.epoch,
)
} else {
let secret_key_str = self.private_key.clone().unwrap();
let secret_key_bytes =
hex::decode(secret_key_str.strip_prefix("0x").unwrap_or(&secret_key_str))
.expect("Invalid custom genesis validators root");
voluntary_exit::voluntary_exit_message_from_secret_key(
secret_key_bytes.as_slice(),
self.validator_beacon_index as u64,
self.epoch,
)
};

let signed_voluntary_exit =
voluntary_exit.sign(&key_material.keypair.sk, genesis_validators_root, &spec);
Expand Down
18 changes: 17 additions & 1 deletion src/key_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use eth2_keystore::{
json_keystore::Kdf, keypair_from_secret, Keystore, KeystoreBuilder, PlainText,
};
use eth2_wallet::{KeyType, ValidatorPath};
use types::Keypair;
use types::{Keypair, SecretKey};

use crate::utils::{pbkdf2, scrypt};

Expand All @@ -17,6 +17,22 @@ pub struct VotingKeyMaterial {
pub withdrawal_keypair: Option<Keypair>,
}

impl VotingKeyMaterial {
pub fn from_voting_secret_bytes(voting_secret_bytes: &[u8]) -> Self {
let sk = SecretKey::deserialize(voting_secret_bytes).expect("Invalid private key passed");
let pk = sk.public_key();
let keypair = Keypair::from_components(pk, sk);
let voting_secret = voting_secret_bytes.to_vec().into();

Self {
keypair,
voting_secret,
keystore: None,
withdrawal_keypair: None,
}
}
}

/// Key derivation function for the keystore
#[derive(clap::ValueEnum, Clone)]
pub enum KdfVariant {
Expand Down
15 changes: 15 additions & 0 deletions src/voluntary_exit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,20 @@ pub fn voluntary_exit_message_from_mnemonic(
(voluntary_exit, key_material.clone())
}

pub fn voluntary_exit_message_from_secret_key(
secret_key_bytes: &[u8],
validator_beacon_index: u64,
epoch: u64,
) -> (VoluntaryExit, VotingKeyMaterial) {
let key_material = VotingKeyMaterial::from_voting_secret_bytes(secret_key_bytes);

let voluntary_exit = VoluntaryExit {
epoch: Epoch::from(epoch),
validator_index: validator_beacon_index,
};

(voluntary_exit, key_material)
}

#[cfg(test)]
mod test;
34 changes: 34 additions & 0 deletions tests/e2e/presigned_exit_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,37 @@ fn test_presigned_exit_message_send_beacon_node() -> Result<(), Box<dyn std::err

Ok(())
}

#[test]
fn test_presigned_exit_message_private_key() -> Result<(), Box<dyn std::error::Error>> {
let chain = "mainnet";
let private_key = "0x6d446ca271eb229044b9039354ecdfa6244d1a11615ec1a46fc82a800367de5d";
let validator_index = "100";
let epoch = "305658";

// run eth-staking-smith
let mut cmd = Command::cargo_bin("eth-staking-smith")?;

cmd.arg("presigned-exit-message");
cmd.arg("--chain");
cmd.arg(chain);
cmd.arg("--validator_beacon_index");
cmd.arg(validator_index);
cmd.arg("--private-key");
cmd.arg(private_key);
cmd.arg("--epoch");
cmd.arg(epoch);

cmd.assert().success();

let output = &cmd.output()?.stdout;
let command_output = std::str::from_utf8(output)?;

let signed_voluntary_exit: SignedVoluntaryExit = serde_json::from_str(command_output)?;
assert_eq!(
signed_voluntary_exit.signature.to_string(),
"0xa74f22d26da9934c2a9c783799fb9e7bef49b3d7c3759a0683b52ee5d71516c0ecdbcc47703f11959c5e701a6c47194410bed800217bd4dd0dab1e0587b14551771accd04ff1c78302f9605f44c3894976c5b3537b70cb7ac9dcb5398dc22079"
);

Ok(())
}

0 comments on commit 73df6cf

Please sign in to comment.