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

Adding command to create deposit with validator keystore #113

Merged
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
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
39 changes: 31 additions & 8 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 @@ -200,7 +202,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 @@ -241,6 +243,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 @@ -302,7 +317,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 @@ -376,7 +392,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 @@ -492,7 +509,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,9 +570,13 @@ 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](#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 @@ -619,7 +641,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.

## Canonical Deposit Contract and Launchpad

Ethstaker confirms the canonical Ethereum staking deposit contract addresses and launchpad URLs.
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