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 ability to generate signed exit messages with mnemonic or validator key #1

Closed
wants to merge 2 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
validator_keys
bls_to_execution_changes
exit_transactions
validator_keys

# Python testing & linting:
build/
Expand Down
75 changes: 57 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- [`existing-mnemonic` Arguments](#existing-mnemonic-arguments)
- [Successful message](#successful-message)
- [`generate-bls-to-execution-change` Arguments](#generate-bls-to-execution-change-arguments)
- [`exit-transaction-keystore` Arguments](#exit-transaction-keystore-arguments)
- [`exit-transaction-mnemonic` Arguments](#exit-transaction-mnemonic-arguments)
- [Option 2. Build `deposit-cli` with native Python](#option-2-build-deposit-cli-with-native-python)
- [Step 0. Python version checking](#step-0-python-version-checking)
- [Step 1. Installation](#step-1-installation-1)
Expand Down Expand Up @@ -72,7 +74,7 @@

## Introduction

`deposit-cli` is a tool for creating [EIP-2335 format](https://eips.ethereum.org/EIPS/eip-2335) BLS12-381 keystores and a corresponding `deposit_data*.json` file for [Ethereum Staking Launchpad](https://github.com/ethereum/staking-launchpad).
`deposit-cli` is a tool for creating [EIP-2335 format](https://eips.ethereum.org/EIPS/eip-2335) BLS12-381 keystores and a corresponding `deposit_data*.json` file for [Ethereum Staking Launchpad](https://github.com/ethereum/staking-launchpad). One can also provide a keystore file to generate a `signed_exit_transaction*.json` file to be broadcast at a later date to exit a validator.

- **Warning: Please generate your keystores on your own safe, completely offline device.**
- **Warning: Please backup your mnemonic, keystores, and password securely.**
Expand Down Expand Up @@ -178,7 +180,7 @@ Success!
Your keys can be found at: <YOUR_FOLDER_PATH>
```

###### `generate-bls-to-execution-change` Arguments
###### `generate-bls-to-execution-change` Arguments

You can use `bls-to-execution-change --help` to see all arguments. Note that if there are missing arguments that the CLI needs, it will ask you for them.

Expand All @@ -192,7 +194,34 @@ You can use `bls-to-execution-change --help` to see all arguments. Note that if
| `--validator_indices` | String of integer(s) | A list of the chosen validator index number(s) as identified on the beacon chain. Split multiple items with whitespaces or commas. |
| `--bls_withdrawal_credentials_list` | String of hexstring(s). | A list of the old BLS withdrawal credentials of the given validator(s). It is for confirming you are using the correct keys. Split multiple items with whitespaces or commas. |
| `--execution_address` (or `--eth1_withdrawal_address`) | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). |
| `--devnet_chain_setting` | String. JSON string `'{"network_name": "<NETWORK_NAME>", "genesis_fork_version": "<GENESIS_FORK_VERSION>", "genesis_validator_root": "<GENESIS_VALIDATOR_ROOT>"}'` | The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. |
| `--devnet_chain_setting` | String. JSON string `'{"network_name": "<NETWORK_NAME>", "genesis_fork_version": "<GENESIS_FORK_VERSION>", "exit_fork_version": "<EXIT_FORK_VERSION>", "genesis_validator_root": "<GENESIS_VALIDATOR_ROOT>"}'` | The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. |

###### `exit-transaction-keystore` Arguments

You can use `exit-transaction-keystore --help` to see all arguments. Note that if there are missing arguments that the CLI needs, it will ask you for them.

| Argument | Type | Description |
| -------- | -------- | -------- |
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
| `--keystore` | File | The keystore file associating with the validator you wish to exit. |
| `--keystore_password` | String | The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. |
| `--validator_index` | Integer | The validator index corresponding to the provided keystore. |
| `--epoch` | Optional integer. 0 by default | The epoch of when the exit transaction will be valid. The transaction will always be valid by default. |
| `--output_folder` | String. Pointing to `./exit_transaction` by default | The folder path for the `signed_exit_transaction-*` JSON file |

###### `exit-transaction-mnemonic` Arguments

You can use `exit-transaction-mnemonic --help` to see all arguments. Note that if there are missing arguments that the CLI needs, it will ask you for them.

| Argument | Type | Description |
| -------- | -------- | -------- |
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
| `--mnemonic` | String. mnemonic split by space. | The mnemonic you used during key generation. |
| `--mnemonic_password` | Optional string. Empty by default. | The mnemonic password you used in your key generation. Note: It's not the keystore password. |
| `--validator_start_index` | Non-negative integer | The index position for the keys to start generating keystores in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). |
| `--validator_indices` | String of integer(s) | A list of the chosen validator index number(s) as identified on the beacon chain. Split multiple items with whitespaces or commas. |
| `--epoch` | Optional integer. 0 by default | The epoch of when the exit transaction will be valid. The transaction will always be valid by default. |
| `--output_folder` | String. Pointing to `./exit_transaction` by default | The folder path for the `signed_exit_transaction-*` JSON file |

#### Option 2. Build `deposit-cli` with native Python

Expand Down Expand Up @@ -252,9 +281,11 @@ See [here](#commands)

###### Arguments

See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments
See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments\
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments\
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments\
See [here](#exit-transaction-keystore-arguments) for `exit-transaction-keystore` arguments\
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments

###### Successful message
See [here](#successful-message)
Expand Down Expand Up @@ -320,9 +351,11 @@ See [here](#commands)

###### Arguments

See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments
See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments\
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments\
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments\
See [here](#exit-transaction-keystore-arguments) for `exit-transaction-keystore` arguments\
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments

#### Option 4. Use Docker image

Expand Down Expand Up @@ -404,9 +437,11 @@ See [here](#commands)

###### Arguments

See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments
See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments\
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments\
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments\
See [here](#exit-transaction-keystore-arguments) for `exit-transaction-keystore` arguments\
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments

#### Option 2. Build `deposit-cli` with native Python

Expand Down Expand Up @@ -467,9 +502,11 @@ See [here](#commands)

###### Arguments

See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments
See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments\
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments\
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments\
See [here](#exit-transaction-keystore-arguments) for `exit-transaction-keystore` arguments\
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments

#### Option 3. Build `deposit-cli` with `virtualenv`

Expand Down Expand Up @@ -532,9 +569,11 @@ See [here](#commands)

###### Arguments

See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments
See [here](#new-mnemonic-arguments) for `new-mnemonic` arguments\
See [here](#existing-mnemonic-arguments) for `existing-mnemonic` arguments\
See [here](#generate-bls-to-execution-change-arguments) for `generate-bls-to-execution-change` arguments\
See [here](#exit-transaction-keystore-arguments) for `exit-transaction-keystore` arguments\
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments

## Development

Expand Down
2 changes: 1 addition & 1 deletion staking_deposit/cli/existing_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def load_mnemonic_arguments_decorator(function: Callable[..., Any]) -> Callable[
default='',
help=lambda: load_text(['arg_mnemonic_password', 'help'], func='existing_mnemonic'),
hidden=True,
param_decls='--mnemonic-password',
param_decls='--mnemonic_password',
prompt=False,
),
]
Expand Down
117 changes: 117 additions & 0 deletions staking_deposit/cli/exit_transaction_keystore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import click
import os

from typing import Any
from staking_deposit.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from staking_deposit.key_handling.keystore import Keystore
from staking_deposit.settings import ALL_CHAINS, MAINNET, PRATER, get_chain_setting
from staking_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
)
from staking_deposit.utils.intl import (
closest_match,
load_text,
)
from staking_deposit.utils.validation import validate_int_range


FUNC_NAME = 'exit_transaction_keystore'


@click.command(
help=load_text(['arg_exit_transaction_keystore', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, list(ALL_CHAINS.keys())),
choice_prompt_func(
lambda: load_text(['arg_exit_transaction_keystore_chain', 'prompt'], func=FUNC_NAME),
list(ALL_CHAINS.keys())
),
),
default=MAINNET,
help=lambda: load_text(['arg_exit_transaction_keystore_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=choice_prompt_func(
lambda: load_text(['arg_exit_transaction_keystore_chain', 'prompt'], func=FUNC_NAME),
# Since `prater` is alias of `goerli`, do not show `prater` in the prompt message.
list(key for key in ALL_CHAINS.keys() if key != PRATER)
),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_exit_transaction_keystore_keystore', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_exit_transaction_keystore_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=lambda: load_text(['arg_exit_transaction_keystore_keystore', 'prompt'], func=FUNC_NAME),
type=click.Path(exists=True, file_okay=True, dir_okay=False),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: x,
lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_exit_transaction_keystore_keystore_password', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_validator_index', 'help'], func=FUNC_NAME),
param_decls='--validator_index',
prompt=lambda: load_text(['arg_validator_index', 'prompt'], func=FUNC_NAME),
)
@jit_option(
default=0,
help=lambda: load_text(['arg_exit_transaction_keystore_epoch', 'help'], func=FUNC_NAME),
param_decls='--epoch',
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_exit_transaction_keystore_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@click.pass_context
def exit_transaction_keystore(
ctx: click.Context,
chain: str,
keystore: str,
keystore_password: str,
validator_index: int,
epoch: int,
output_folder: str,
**kwargs: Any) -> None:
saved_keystore = Keystore.from_file(keystore)

try:
secret_bytes = saved_keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_exit_transaction_keystore_keystore_password', 'mismatch']))
exit(1)

signing_key = int.from_bytes(secret_bytes, 'big')
chain_settings = get_chain_setting(chain)

signed_exit = exit_transaction_generation(
chain_settings=chain_settings,
signing_key=signing_key,
validator_index=validator_index,
epoch=epoch,
)

saved_folder = export_exit_transaction_json(folder=output_folder, signed_exit=signed_exit)

click.echo(load_text(['msg_creation_success']) + saved_folder)
click.pause(load_text(['msg_pause']))
115 changes: 115 additions & 0 deletions staking_deposit/cli/exit_transaction_mnemonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import click
import os

from typing import Any, Sequence
from staking_deposit.cli.existing_mnemonic import load_mnemonic_arguments_decorator
from staking_deposit.credentials import Credential
from staking_deposit.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from staking_deposit.settings import ALL_CHAINS, MAINNET, PRATER, get_chain_setting
from staking_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
)
from staking_deposit.utils.intl import (
closest_match,
load_text,
)
from staking_deposit.utils.validation import validate_int_range, validate_validator_indices


FUNC_NAME = 'exit_transaction_mnemonic'


@click.command(
help=load_text(['arg_exit_transaction_mnemonic', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, list(ALL_CHAINS.keys())),
choice_prompt_func(
lambda: load_text(['arg_exit_transaction_mnemonic_chain', 'prompt'], func=FUNC_NAME),
list(ALL_CHAINS.keys())
),
),
default=MAINNET,
help=lambda: load_text(['arg_exit_transaction_mnemonic_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=choice_prompt_func(
lambda: load_text(['arg_exit_transaction_mnemonic_chain', 'prompt'], func=FUNC_NAME),
# Since `prater` is alias of `goerli`, do not show `prater` in the prompt message.
list(key for key in ALL_CHAINS.keys() if key != PRATER)
),
)
@load_mnemonic_arguments_decorator
@jit_option(
callback=captive_prompt_callback(
lambda num: validate_int_range(num, 0, 2**32),
lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'prompt'], func=FUNC_NAME),
),
default=0,
help=lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'help'], func=FUNC_NAME),
param_decls="--validator_start_index",
prompt=lambda: load_text(['arg_exit_transaction_mnemonic_start_index', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda validator_indices: validate_validator_indices(validator_indices),
lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'help'], func=FUNC_NAME),
param_decls='--validator_indices',
prompt=lambda: load_text(['arg_exit_transaction_mnemonic_indices', 'prompt'], func=FUNC_NAME),
)
@jit_option(
default=0,
help=lambda: load_text(['arg_exit_transaction_mnemonic_epoch', 'help'], func=FUNC_NAME),
param_decls='--epoch',
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_exit_transaction_mnemonic_output_folder', 'help'], func=FUNC_NAME),
param_decls='--output_folder',
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@click.pass_context
def exit_transaction_mnemonic(
ctx: click.Context,
chain: str,
mnemonic: str,
mnemonic_password: str,
validator_start_index: int,
validator_indices: Sequence[int],
epoch: int,
output_folder: str,
**kwargs: Any) -> None:

chain_settings = get_chain_setting(chain)
num_keys = len(validator_indices)
key_indices = range(validator_start_index, validator_start_index + num_keys)

click.echo(load_text(['msg_creation_start']))
# We assume that the list of validator indices are in order and increment the start index
for key_index, validator_index in zip(key_indices, validator_indices):
credential = Credential(
mnemonic=mnemonic,
mnemonic_password=mnemonic_password,
index=key_index,
amount=0, # Unneeded for this purpose
chain_setting=chain_settings,
hex_eth1_withdrawal_address=None
)

signing_key = credential.signing_sk

signed_voluntary_exit = exit_transaction_generation(
chain_settings=chain_settings,
signing_key=signing_key,
validator_index=validator_index,
epoch=epoch
)

saved_folder = export_exit_transaction_json(folder=output_folder, signed_exit=signed_voluntary_exit)
click.echo(load_text(['msg_creation_success']) + saved_folder)

click.pause(load_text(['msg_pause']))
1 change: 1 addition & 0 deletions staking_deposit/cli/generate_bls_to_execution_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def generate_bls_to_execution_change(
chain_setting = get_devnet_chain_setting(
network_name=devnet_chain_setting_dict['network_name'],
genesis_fork_version=devnet_chain_setting_dict['genesis_fork_version'],
exit_fork_version=devnet_chain_setting_dict['exit_fork_version'],
genesis_validator_root=devnet_chain_setting_dict['genesis_validator_root'],
)

Expand Down
Loading