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

[STACKED] Command to generate presigned exit message #55

Merged
merged 3 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
42 changes: 30 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,36 @@ You can use `eth-staking-smith` as follows to convert your address:

Note that --validator-index and --validator-start-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


### Command to send SignedBLSToExecutionChange request to Beacon node

```
curl -H "Content-Type: application/json" -d '{
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we showing curl command instead of cli cmd?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The command right now does not support sending output to beacon node; it should be easy to add though given openssl-sys is already a dependency. I will do it in #54 as this PR only touches presigned exit message, but bls-to-execution-change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sending to beacon node was implemented in 64ef067

"message": {
"validator_index": 100,
"from_bls_pubkey": "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d",
"to_execution_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
},
"signature": "0x9220e5badefdfe8abc36cae01af29b981edeb940ff88c438f72c8af876fbd6416138c85f5348c5ace92a081fa15291aa0ffb856141b871dc807f3ec2fe9c8415cac3d76579c61455ab3938bc162e139d060c8aa13fcd670febe46bf0bb579c5a"
}' http://localhost:3500/eth/v1/beacon/pool/bls_to_execution_change
```

## Generating pre-signed exit message

It is possible to create pre-signed voluntary exit message for every validator that
is generated from some known mnemonic, given the minimum epoch for exit to trigger.

Use `eth-staking-smith` via command line like:

### Command to generate presigned exit message

```
./target/debug/eth-staking-smith presigned-exit-message --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --epoch 300000
```

Note that --validator-index and --validator-start-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
Copy link
Contributor

Choose a reason for hiding this comment

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

hm can we not just rename them to make it more explicit? --validator-beacon-index or --validator_seed_index

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, renamed in 176cfba, will rename in bls-to-execution-change in nested PR



## Exporting CLI standard output into common keystores folder format

Most validator clients recognize the keystore folder format,
Expand Down Expand Up @@ -117,18 +147,6 @@ lighthouse account validator import \
--directory validator_keys/ --password-file ./password.txt
```

### Command to send SignedBLSToExecutionChange request to Beacon node

```
curl -H "Content-Type: application/json" -d '{
"message": {
"validator_index": 100,
"from_bls_pubkey": "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d",
"to_execution_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
},
"signature": "0x9220e5badefdfe8abc36cae01af29b981edeb940ff88c438f72c8af876fbd6416138c85f5348c5ace92a081fa15291aa0ffb856141b871dc807f3ec2fe9c8415cac3d76579c61455ab3938bc162e139d060c8aa13fcd670febe46bf0bb579c5a"
}' http://localhost:3500/eth/v1/beacon/pool/bls_to_execution_change
```

# Implementation Details
To avoid heavy lifting, we're interfacing [Lighthouse account manager](https://github.com/sigp/lighthouse/blob/stable/account_manager), but optimizing it in a way so all operations are done in memory and key material is never written to filesystem during the generation to cater for our use case.
Expand Down
47 changes: 0 additions & 47 deletions src/bls_to_execution_change/constants.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/bls_to_execution_change/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod constants;
pub(crate) mod operator;

use regex::Regex;
Expand Down
4 changes: 3 additions & 1 deletion src/bls_to_execution_change/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ impl SignedBlsToExecutionChangeOperator for SignedBlsToExecutionChange {
to_execution_address: self.message.to_execution_address,
};
let signing_root = bls_to_execution_change.signing_root(domain);
self.signature.verify(&withdrawal_pubkey, signing_root);
if !self.signature.verify(&withdrawal_pubkey, signing_root) {
panic!("Invalid bls to execution change signature")
}
}
}
2 changes: 1 addition & 1 deletion src/bls_to_execution_change/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::str::FromStr;

use types::{Hash256, PublicKey};

use crate::{bls_to_execution_change::constants::validators_root_for, chain_spec, utils};
use crate::{chain_spec, networks::validators_root_for, utils};

const EXECUTION_WITHDRAWAL_ADDRESS: &str = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F";
const PHRASE: &str = "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup";
Expand Down
29 changes: 28 additions & 1 deletion src/chain_spec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::Path;

use eth2_network_config::Eth2NetworkConfig;
use types::{ChainSpec, Config, MainnetEthSpec, MinimalEthSpec};
use types::{ChainSpec, Config, Hash256, MainnetEthSpec, MinimalEthSpec};

use crate::{networks::SupportedNetworks, DepositError};

Expand Down Expand Up @@ -40,3 +40,30 @@ pub fn chain_spec_from_file(chain_spec_file: String) -> Result<ChainSpec, Deposi
}
}
}

pub fn validators_root_and_spec(
chain: Option<SupportedNetworks>,
testnet_properties: Option<(String, String)>,
) -> (Hash256, ChainSpec) {
if chain.is_some() {
let well_known_chain = chain.unwrap();
(
crate::networks::validators_root_for(&well_known_chain),
chain_spec_for_network(&well_known_chain).expect("Invalid chain spec"),
)
} else {
let (genesis_validators_root_str, testnet_config_path) = testnet_properties.expect(
"If custom testnet config is passed, genesis validators root value must be included",
);
let genesis_validators_root_bytes = hex::decode(
genesis_validators_root_str
.strip_prefix("0x")
.unwrap_or(&genesis_validators_root_str),
)
.expect("Invalid custom genesis validators root");
(
Hash256::from_slice(genesis_validators_root_bytes.as_slice()),
chain_spec_from_file(testnet_config_path).expect("Invalid chain spec in file"),
)
}
}
43 changes: 19 additions & 24 deletions src/cli/bls_to_execution_change.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::bls_to_execution_change;
use crate::bls_to_execution_change::operator::SignedBlsToExecutionChangeOperator;
use crate::{bls_to_execution_change, chain_spec};
use crate::chain_spec::validators_root_and_spec;
use clap::{arg, Parser};
use types::Hash256;

#[derive(Clone, Parser)]
pub struct BlsToExecutionChangeSubcommandOpts {
Expand Down Expand Up @@ -64,26 +64,21 @@ impl BlsToExecutionChangeSubcommandOpts {
self.chain.clone()
};

let (genesis_validator_root, spec) = if chain.is_some() {
let well_known_chain = chain.unwrap();
(
bls_to_execution_change::constants::validators_root_for(&well_known_chain),
chain_spec::chain_spec_for_network(&well_known_chain).expect("Invalid chain spec"),
)
} else {
let genesis_validators_root_str = self.genesis_validators_root.clone().expect("If custom testnet config is passed, genesis validators root value must be included");
let genesis_validators_root_bytes = hex::decode(
genesis_validators_root_str
.strip_prefix("0x")
.unwrap_or(&genesis_validators_root_str),
)
.expect("Invalid custom genesis validators root");
(
Hash256::from_slice(genesis_validators_root_bytes.as_slice()),
chain_spec::chain_spec_from_file(self.testnet_config.clone().unwrap())
.expect("Invalid chain spec in file"),
)
};
let (genesis_validators_root, spec) = validators_root_and_spec(
chain.clone(),
if chain.is_some() {
None
} else {
Some((
self.genesis_validators_root
.clone()
.expect("Genesis validators root parameter must be set"),
self.testnet_config
.clone()
.expect("Testnet config must be set"),
))
},
);

let (bls_to_execution_change, keypair) =
bls_to_execution_change::bls_execution_change_from_mnemonic(
Expand All @@ -95,15 +90,15 @@ impl BlsToExecutionChangeSubcommandOpts {

let signed_bls_to_execution_change = bls_to_execution_change.sign(
&keypair.withdrawal_keypair.unwrap().sk,
genesis_validator_root,
genesis_validators_root,
&spec,
);

signed_bls_to_execution_change.clone().validate(
self.bls_withdrawal_credentials.as_str(),
self.execution_address.as_str(),
&spec,
&genesis_validator_root,
&genesis_validators_root,
);

let export = signed_bls_to_execution_change.export();
Expand Down
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod bls_to_execution_change;
pub mod existing_mnemonic;
pub mod new_mnemonic;
pub mod presigned_exit_message;
100 changes: 100 additions & 0 deletions src/cli/presigned_exit_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use clap::{arg, Parser};

use crate::voluntary_exit::operator::SignedVoluntaryExitOperator;
use crate::{chain_spec::validators_root_and_spec, voluntary_exit};

#[derive(Clone, Parser)]
pub struct PresignedExitMessageSubcommandOpts {
/// The mnemonic that you used to generate your
/// keys.
///
/// It is recommended not to use this
/// 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,

/// The name of Ethereum PoS chain you are targeting.
///
/// Use "mainnet" if you are
/// depositing ETH
#[arg(value_enum, long)]
pub chain: Option<crate::networks::SupportedNetworks>,

/// The index of the first validator's keys you wish to generate the address for
/// e.g. if you generated 3 keys before (index #0, index #1, index #2)
/// 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_start_index")]
pub validator_start_index: u32,

/// On-chain beacon index of the validator.
#[arg(long, visible_alias = "validator_index")]
pub validator_index: u32,

/// Epoch number which must be included in the presigned exit message.
#[arg(long, visible_alias = "execution_address")]
pub epoch: u64,

/// Path to a custom Eth PoS chain config
#[arg(long, visible_alias = "testnet_config")]
pub testnet_config: Option<String>,

/// Custom genesis validators root for the custom testnet, passed as hex string.
/// See https://eth2book.info/capella/part3/containers/state/ for value
/// description
#[arg(long, visible_alias = "genesis_validators_root")]
pub genesis_validators_root: Option<String>,
}

impl PresignedExitMessageSubcommandOpts {
pub fn run(&self) {
let chain = if self.chain.is_some() && self.testnet_config.is_some() {
panic!("should only pass one of testnet_config or chain")
} else if self.testnet_config.is_some() {
// Signalizes custom testnet config will be used
None
} else {
self.chain.clone()
};

let (genesis_validators_root, spec) = validators_root_and_spec(
chain.clone(),
if chain.is_some() {
None
} else {
Some((
self.genesis_validators_root
.clone()
.expect("Genesis validators root parameter must be set"),
self.testnet_config
.clone()
.expect("Testnet config must be set"),
))
},
);

let (voluntary_exit, key_material) = voluntary_exit::voluntary_exit_message_from_mnemonic(
self.mnemonic.as_bytes(),
self.validator_start_index as u64,
self.validator_index as u64,
self.epoch,
);

let signed_voluntary_exit =
voluntary_exit.sign(&key_material.keypair.sk, genesis_validators_root, &spec);

signed_voluntary_exit.clone().validate(
&key_material.keypair.pk,
&spec,
&genesis_validators_root,
);
let export = signed_voluntary_exit.export();

let presigned_exit_message_json =
serde_json::to_string_pretty(&export).expect("could not parse validator export");
println!("{}", presigned_exit_message_json);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod networks;
pub(crate) mod seed;
pub mod utils;
pub mod validators;
pub mod voluntary_exit;

pub use deposit::DepositError;
pub use validators::*;
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![forbid(unsafe_code)]
use clap::{Parser, Subcommand};
use eth_staking_smith::cli::{bls_to_execution_change, existing_mnemonic, new_mnemonic};
use eth_staking_smith::cli::{
bls_to_execution_change, existing_mnemonic, new_mnemonic, presigned_exit_message,
};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
Expand All @@ -18,6 +20,9 @@ enum SubCommands {
ExistingMnemonic(existing_mnemonic::ExistingMnemonicSubcommandOpts),
/// Generate new keys with new mnemonic.
NewMnemonic(new_mnemonic::NewMnemonicSubcommandOpts),
/// Generate presigned exit message which can be sent
/// to the Beacon Node to start voluntary exit process for the validator
PresignedExitMessage(presigned_exit_message::PresignedExitMessageSubcommandOpts),
}

impl SubCommands {
Expand All @@ -26,6 +31,7 @@ impl SubCommands {
Self::BlsToExecutionChange(sub) => sub.run(),
Self::ExistingMnemonic(sub) => sub.run(),
Self::NewMnemonic(sub) => sub.run(),
Self::PresignedExitMessage(sub) => sub.run(),
}
}
}
Expand Down
Loading
Loading