Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating presigned exit message from private key #56

Merged
merged 2 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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(())
}
Loading