Skip to content

Commit

Permalink
Adding command to create deposits with validator keystore
Browse files Browse the repository at this point in the history
  • Loading branch information
valefar-on-discord committed Aug 28, 2024
1 parent 2827254 commit 2c77dfb
Show file tree
Hide file tree
Showing 19 changed files with 613 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bls_to_execution_changes
exit_transactions
partial_deposits
validator_keys

# Python testing & linting:
Expand Down
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [`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)
- [`partial-deposit` Arguments](#partial-deposit-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 @@ -154,6 +155,7 @@ The CLI offers different commands depending on what you want to do with the tool
| `generate-bls-to-execution-change` | This command is used to generate BLS to execution address change message. This is used to add a withdrawal address to a validator that does not currently have one. |
| `exit-transaction-keystore` | This command is used to create an exit transaction using a keystore file. |
| `exit-transaction-mnemonic` | This command is used to create an exit transaction using a mnemonic phrase. |
| `partial-deposit` | This command is used to create a deposit file using a keystore file. |

###### `new-mnemonic` Arguments

Expand Down Expand Up @@ -198,7 +200,7 @@ Your keys can be found at: <YOUR_FOLDER_PATH>

###### `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.
You can use `generate-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.

| Argument | Type | Description |
| -------- | -------- | -------- |
Expand Down Expand Up @@ -239,6 +241,19 @@ You can use `exit-transaction-mnemonic --help` to see all arguments. Note that i
| `--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 |

###### `partial-deposit` Arguments

You can use `partial-deposit --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 deposit to. |
| `--keystore_password` | String | The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. |
| `--amount` | Float. `32` by default | The amount you wish to deposit. Must be in ether, at least 1 ether, and can not have higher precision than 1 gwei. |
| `--withdrawal_address` | String. Ethereum execution address in hexadecimal encoded form | The withdrawal address of the existing validator or the desired withdrawal address. |
| `--output_folder` | String. Pointing to `./partial_deposit` by default | The folder path for the `deposit-*` JSON file |

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

##### Step 0. Python version checking
Expand Down Expand Up @@ -300,7 +315,8 @@ 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
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments\
See [here](#partial-deposit-arguments) for `partial-deposit` arguments

###### Successful message
See [here](#successful-message)
Expand Down Expand Up @@ -374,7 +390,8 @@ 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
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments\
See [here](#partial-deposit-arguments) for `partial-deposit` arguments

#### Option 4. Use published docker image

Expand Down Expand Up @@ -490,7 +507,8 @@ 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
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments\
See [here](#partial-deposit-arguments) for `partial-deposit` arguments

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

Expand Down Expand Up @@ -552,7 +570,10 @@ See [here](#commands)

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](#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\
See [here](#partial-deposit-arguments) for `partial-deposit` arguments

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

Expand Down Expand Up @@ -617,7 +638,8 @@ 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
See [here](#exit-transaction-mnemonic-arguments) for `exit-transaction-mnemonic` arguments\
See [here](#partial-deposit-arguments) for `partial-deposit` arguments

## Development

Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
- [Generate BLS to Execution Change](generate_bls_to_execution_change.md)
- [Exit Transaction Keystore](exit_transaction_keystore.md)
- [Exit Transaction Mnemonic](exit_transaction_mnemonic.md)
- [Partial Deposit](partial_deposit.md)
- [Local Development](local_development.md)
2 changes: 2 additions & 0 deletions docs/src/landing.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ If there is a specific command you would like to understand more, please choose

- **[exit-transaction-mnemonic](exit_transaction_mnemonic.md)**: Generate an exit message using the mnemonic of your validators.

- **[partial-deposit](partial_deposit.md)**: Generate a deposit file with an existing validator key. Can be used to initiate a validator or deposit to an existing validator.

## Contributing

This project is open-source and welcomes contributions from the community. If you would like to contribute to the `ethstaker-deposit-cli`, please read the [Local Development Instructions](local_development.md), fork the project, and create a pull request with a description of the changes you have made and why.
Expand Down
28 changes: 28 additions & 0 deletions docs/src/partial_deposit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# partial-deposit

{{#include ./snippet/warning_message.md}}

## Description
Creates a deposit file with an existing validator key. Can be used to initiate a validator or deposit to an existing validator.
If you wish to create a validator with 0x00 credentials, you must use the **[new-mnemonic](new_mnemonic.md)** or the **[existing-mnemonic](existing_mnemonic.md)** command.

## Optional Arguments

- **`--chain`**: The chain to use for generating the deposit data. Options are: 'mainnet', 'holesky', etc.

- **`--keystore`**: The keystore file associating with the validator you wish to deposit to.

- **`--keystore_password`**: The password that is used to encrypt the provided keystore. Note: It's not your mnemonic password. <span class="warning"></span>

- **`--amount`**: The amount you wish to deposit in ether. Must be at least 1 and can not have precision beyond 1 gwei. Defaults to 32 ether.

- **`--withdrawal_address`**: The withdrawal address of the existing validator or the desired withdrawal address.

- **`--output_folder`**: The folder path for the `deposit-*` JSON file.


## Example Usage

```sh
./deposit partial-deposit --keystore /path/to/keystore.json
```
2 changes: 2 additions & 0 deletions docs/src/quick_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Determine which command best suites what you would like to accomplish:

- **[exit-transaction-mnemonic](exit_transaction_mnemonic.md)**: Generate an exit message using the mnemonic of your validators.

- **[partial-deposit](partial_deposit.md)**: Generate a partial deposit using a validator keystore.

---

If you encounter any issues, please check the [issues page](https://github.com/eth-educators/ethstaker-deposit-cli/issues) for help or to report a problem. You may also contact us on the [Ethstaker discord](https://dsc.gg/ethstaker).
2 changes: 1 addition & 1 deletion ethstaker_deposit/cli/exit_transaction_keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time

from typing import Any
from ethstaker_deposit.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.utils.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.settings import (
MAINNET,
Expand Down
180 changes: 180 additions & 0 deletions ethstaker_deposit/cli/partial_deposit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import json
import click
import os
import time

from eth_typing import HexAddress
from eth_utils import to_canonical_address
from py_ecc.bls import G2ProofOfPossession as bls
from typing import Any

from ethstaker_deposit.key_handling.keystore import Keystore
from ethstaker_deposit.settings import (
DEPOSIT_CLI_VERSION,
MAINNET,
ALL_CHAIN_KEYS,
get_chain_setting,
)
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
choice_prompt_func,
jit_option,
)
from ethstaker_deposit.utils.constants import DEFAULT_PARTIAL_DEPOSIT_FOLDER_NAME, EXECUTION_ADDRESS_WITHDRAWAL_PREFIX
from ethstaker_deposit.utils.deposit import export_deposit_data_json
from ethstaker_deposit.utils.intl import (
closest_match,
load_text,
)
from ethstaker_deposit.utils.ssz import (
DepositData,
DepositMessage,
compute_deposit_domain,
compute_signing_root,
)
from ethstaker_deposit.utils.validation import (
validate_deposit,
validate_keystore_file,
validate_partial_deposit_amount,
validate_withdrawal_address,
)


FUNC_NAME = 'partial_deposit'


@click.command(
help=load_text(['arg_partial_deposit', 'help'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda x: closest_match(x, ALL_CHAIN_KEYS),
choice_prompt_func(
lambda: load_text(['arg_partial_deposit_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
),
default=MAINNET,
help=lambda: load_text(['arg_partial_deposit_chain', 'help'], func=FUNC_NAME),
param_decls='--chain',
prompt=choice_prompt_func(
lambda: load_text(['arg_partial_deposit_chain', 'prompt'], func=FUNC_NAME),
ALL_CHAIN_KEYS
),
)
@jit_option(
callback=captive_prompt_callback(
lambda file: validate_keystore_file(file),
lambda: load_text(['arg_partial_deposit_keystore', 'prompt'], func=FUNC_NAME),
),
help=lambda: load_text(['arg_partial_deposit_keystore', 'help'], func=FUNC_NAME),
param_decls='--keystore',
prompt=lambda: load_text(['arg_partial_deposit_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_partial_deposit_keystore_password', 'prompt'], func=FUNC_NAME),
None,
lambda: load_text(['arg_partial_deposit_keystore_password', 'invalid'], func=FUNC_NAME),
True,
),
help=lambda: load_text(['arg_partial_deposit_keystore_password', 'help'], func=FUNC_NAME),
hide_input=True,
param_decls='--keystore_password',
prompt=lambda: load_text(['arg_partial_deposit_keystore_password', 'prompt'], func=FUNC_NAME),
)
@jit_option(
callback=captive_prompt_callback(
lambda amount: validate_partial_deposit_amount(amount),
lambda: load_text(['arg_partial_deposit_amount', 'prompt'], func=FUNC_NAME),
default="32",
prompt_if_none=True,
),
default="32",
help=lambda: load_text(['arg_partial_deposit_amount', 'help'], func=FUNC_NAME),
param_decls='--amount',
prompt=False, # the callback handles the prompt, to avoid second callback with gwei
)
@jit_option(
callback=captive_prompt_callback(
lambda address: validate_withdrawal_address(None, None, address, True),
lambda: load_text(['arg_withdrawal_address', 'prompt'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'confirm'], func=FUNC_NAME),
lambda: load_text(['arg_withdrawal_address', 'mismatch'], func=FUNC_NAME),
prompt_if_none=True,
),
help=lambda: load_text(['arg_withdrawal_address', 'help'], func=FUNC_NAME),
param_decls=['--withdrawal_address', '--execution_address', '--eth1_withdrawal_credentials'],
prompt=False, # the callback handles the prompt
)
@jit_option(
default=os.getcwd(),
help=lambda: load_text(['arg_partial_deposit_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 partial_deposit(
ctx: click.Context,
chain: str,
keystore: Keystore,
keystore_password: str,
amount: int,
withdrawal_address: HexAddress,
output_folder: str,
**kwargs: Any) -> None:
try:
secret_bytes = keystore.decrypt(keystore_password)
except ValueError:
click.echo(load_text(['arg_partial_deposit_keystore_password', 'mismatch']))
exit(1)

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

withdrawal_credentials = EXECUTION_ADDRESS_WITHDRAWAL_PREFIX
withdrawal_credentials += b'\x00' * 11
withdrawal_credentials += to_canonical_address(withdrawal_address)

deposit_message = DepositMessage( # type: ignore[no-untyped-call]
pubkey=bls.SkToPk(signing_key),
withdrawal_credentials=withdrawal_credentials,
amount=amount
)

domain = compute_deposit_domain(fork_version=chain_settings.GENESIS_FORK_VERSION)

signing_root = compute_signing_root(deposit_message, domain)
signature = bls.Sign(signing_key, signing_root)

signed_deposit = DepositData( # type: ignore[no-untyped-call]
**deposit_message.as_dict(), # type: ignore[no-untyped-call]
signature=signature
)

folder = os.path.join(output_folder, DEFAULT_PARTIAL_DEPOSIT_FOLDER_NAME)
if not os.path.exists(folder):
os.mkdir(folder)

click.echo(load_text(['msg_partial_deposit_creation']))
deposit_data = signed_deposit.as_dict() # type: ignore[no-untyped-call]
deposit_data.update({'deposit_message_root': deposit_message.hash_tree_root})
deposit_data.update({'deposit_data_root': signed_deposit.hash_tree_root})
deposit_data.update({'fork_version': chain_settings.GENESIS_FORK_VERSION})
deposit_data.update({'network_name': chain_settings.NETWORK_NAME})
deposit_data.update({'deposit_cli_version': DEPOSIT_CLI_VERSION})
saved_folder = export_deposit_data_json(folder, time.time(), [deposit_data])

click.echo(load_text(['msg_verify_partial_deposit']))
deposit_json = []
with open(saved_folder, 'r', encoding='utf-8') as f:
deposit_json = json.load(f)

if (not validate_deposit(deposit_json[0])):
click.echo(load_text(['err_verify_partial_deposit']))
return

click.echo(load_text(['msg_creation_success']) + saved_folder)
click.pause(load_text(['msg_pause']))
10 changes: 3 additions & 7 deletions ethstaker_deposit/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from py_ecc.bls import G2ProofOfPossession as bls

from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.utils.exit_transaction import exit_transaction_generation, export_exit_transaction_json
from ethstaker_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key
from ethstaker_deposit.key_handling.keystore import (
Keystore,
Expand All @@ -27,6 +27,7 @@
MIN_DEPOSIT_AMOUNT,
)
from ethstaker_deposit.utils.crypto import SHA256
from ethstaker_deposit.utils.deposit import export_deposit_data_json as export_deposit_data_json_util
from ethstaker_deposit.utils.intl import load_text
from ethstaker_deposit.utils.ssz import (
compute_deposit_domain,
Expand Down Expand Up @@ -326,12 +327,7 @@ def export_deposit_data_json(self, folder: str, timestamp: float) -> str:
deposit_data.append(datum_dict)
bar.update(1)

filefolder = os.path.join(folder, 'deposit_data-%i.json' % timestamp)
with open(filefolder, 'w') as f:
json.dump(deposit_data, f, default=lambda x: x.hex())
if os.name == 'posix':
os.chmod(filefolder, int('440', 8)) # Read for owner & group
return filefolder
return export_deposit_data_json_util(folder, timestamp, deposit_data)

def verify_keystores(self, keystore_filefolders: List[str], password: str) -> bool:
all_valid_keystores = True
Expand Down
2 changes: 2 additions & 0 deletions ethstaker_deposit/deposit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ethstaker_deposit.cli.exit_transaction_mnemonic import exit_transaction_mnemonic
from ethstaker_deposit.cli.generate_bls_to_execution_change import generate_bls_to_execution_change
from ethstaker_deposit.cli.new_mnemonic import new_mnemonic
from ethstaker_deposit.cli.partial_deposit import partial_deposit
from ethstaker_deposit.exceptions import ValidationError
from ethstaker_deposit.utils.click import (
captive_prompt_callback,
Expand Down Expand Up @@ -52,6 +53,7 @@ def check_connectivity() -> None:
generate_bls_to_execution_change,
exit_transaction_keystore,
exit_transaction_mnemonic,
partial_deposit,
]


Expand Down
Loading

0 comments on commit 2c77dfb

Please sign in to comment.