diff --git a/src/cli/presigned_exit_message.rs b/src/cli/presigned_exit_message.rs index 25ce787..dfd09ab 100644 --- a/src/cli/presigned_exit_message.rs +++ b/src/cli/presigned_exit_message.rs @@ -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, /// The name of Ethereum PoS chain you are targeting. /// @@ -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, + + /// Validator private key bytes in hex form + #[arg(long, required_unless_present_all = ["mnemonic", "validator_seed_index"])] + pub private_key: Option, /// On-chain beacon index of the validator. #[arg(long, visible_alias = "validator_beacon_index")] @@ -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); diff --git a/src/key_material.rs b/src/key_material.rs index 0ddba35..b26cca5 100644 --- a/src/key_material.rs +++ b/src/key_material.rs @@ -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}; @@ -17,6 +17,22 @@ pub struct VotingKeyMaterial { pub withdrawal_keypair: Option, } +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 { diff --git a/src/voluntary_exit/mod.rs b/src/voluntary_exit/mod.rs index 88a8b27..7b69bf5 100644 --- a/src/voluntary_exit/mod.rs +++ b/src/voluntary_exit/mod.rs @@ -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; diff --git a/tests/e2e/presigned_exit_message.rs b/tests/e2e/presigned_exit_message.rs index e8eb68b..a804687 100644 --- a/tests/e2e/presigned_exit_message.rs +++ b/tests/e2e/presigned_exit_message.rs @@ -149,3 +149,37 @@ fn test_presigned_exit_message_send_beacon_node() -> Result<(), Box Result<(), Box> { + 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(()) +}