Skip to content

Commit

Permalink
Add support for generating presigned exit message from private key (#56)
Browse files Browse the repository at this point in the history
* Add support for generating presigned exit message from private key

* Fix strings
  • Loading branch information
mksh authored Aug 29, 2024
1 parent ae15e9b commit be5b231
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 11 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ Use `eth-staking-smith` via command line like:

Note that --validator-beacon-index and --validator-seed-index are two distinct parameter, the former being index of validator on Beacon chain, and the latter is the index of validator private key derived from the seed

It is also possible to directly pass private key with `--private-key` parameter instead,
then `--mnemonic` and `--validator-seed-index` may be omitted like follows

```
./target/debug/eth-staking-smith presigned-exit-message --chain mainnet --private-key "0x3f3e0a69a6a66aeaec606a2ccb47c703afb2e8ae64f70a1650c03343b06e8f0c" --validator_beacon_index 100 --epoch 300000
```



### Command to send VoluntaryExitMessage request to Beacon node

Expand Down
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 private key hex input");
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 be5b231

Please sign in to comment.