Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit 256ea21

Browse files
authored
Merge pull request #196 from ethereum/dev
dev -> master: v1.2.0
2 parents 7bac110 + 17b7363 commit 256ea21

File tree

14 files changed

+2439
-27
lines changed

14 files changed

+2439
-27
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ You can use `new-mnemonic --help` to see all arguments. Note that if there are m
118118
| Argument | Type | Description |
119119
| -------- | -------- | -------- |
120120
| `--num_validators` | Non-negative integer | The number of signing keys you want to generate. Note that the child key(s) are generated via the same master key. |
121-
| `--mnemonic_language` | String. Options: `czech`, `chinese_traditional`, `chinese_simplified`, `english`, `spanish`, `italian`, `korean`. Default to `english` | The mnemonic language |
121+
| `--mnemonic_language` | String. Options: `chinese_simplified`, `chinese_traditional`, `czech`, `english`, `italian`, `korean`, `portuguese`, `spanish`. Default to `english` | The mnemonic language |
122122
| `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) |
123123
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
124+
| `--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 [EIP-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). |
124125

125126
###### `existing-mnemonic` Arguments
126127

@@ -132,6 +133,7 @@ You can use `existing-mnemonic --help` to see all arguments. Note that if there
132133
| `--num_validators` | Non-negative integer | The number of signing keys you want to generate. Note that the child key(s) are generated via the same master key. |
133134
| `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) |
134135
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
136+
| `--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 [EIP-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). |
135137

136138
###### Successful message
137139

eth2deposit/cli/generate_keys.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
Callable,
66
)
77

8+
from eth_typing import HexAddress
9+
from eth_utils import is_hex_address, to_normalized_address
10+
811
from eth2deposit.credentials import (
912
CredentialList,
1013
)
@@ -57,6 +60,18 @@ def validate_password(cts: click.Context, param: Any, password: str) -> str:
5760
return password
5861

5962

63+
def validate_eth1_withdrawal_address(cts: click.Context, param: Any, address: str) -> HexAddress:
64+
if address is None:
65+
return None
66+
if not is_hex_address(address):
67+
raise ValueError("The given Eth1 address is not in hexadecimal encoded form.")
68+
69+
normalized_address = to_normalized_address(address)
70+
click.echo(f'\n**[Warning] you are setting Eth1 address {normalized_address} as your withdrawal address. '
71+
'Please ensure that you have control over this address.**\n')
72+
return normalized_address
73+
74+
6075
def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]:
6176
'''
6277
This is a decorator that, when applied to a parent-command, implements the
@@ -91,6 +106,14 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[
91106
'to ask you for your mnemonic as otherwise it will appear in your shell history.)'),
92107
prompt='Type the password that secures your validator keystore(s)',
93108
),
109+
click.option(
110+
'--eth1_withdrawal_address',
111+
default=None,
112+
callback=validate_eth1_withdrawal_address,
113+
help=('If this field is set and valid, the given Eth1 address will be used to create the '
114+
'withdrawal credentials. Otherwise, it will generate withdrawal credentials with the '
115+
'mnemonic-derived withdrawal public key.'),
116+
),
94117
]
95118
for decorator in reversed(decorators):
96119
function = decorator(function)
@@ -100,7 +123,8 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[
100123
@click.command()
101124
@click.pass_context
102125
def generate_keys(ctx: click.Context, validator_start_index: int,
103-
num_validators: int, folder: str, chain: str, keystore_password: str, **kwargs: Any) -> None:
126+
num_validators: int, folder: str, chain: str, keystore_password: str,
127+
eth1_withdrawal_address: HexAddress, **kwargs: Any) -> None:
104128
mnemonic = ctx.obj['mnemonic']
105129
mnemonic_password = ctx.obj['mnemonic_password']
106130
amounts = [MAX_DEPOSIT_AMOUNT] * num_validators
@@ -118,12 +142,13 @@ def generate_keys(ctx: click.Context, validator_start_index: int,
118142
amounts=amounts,
119143
chain_setting=chain_setting,
120144
start_index=validator_start_index,
145+
hex_eth1_withdrawal_address=eth1_withdrawal_address,
121146
)
122147
keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder)
123148
deposits_file = credentials.export_deposit_data_json(folder=folder)
124149
if not credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=keystore_password):
125150
raise ValidationError("Failed to verify the keystores.")
126-
if not verify_deposit_data_json(deposits_file):
151+
if not verify_deposit_data_json(deposits_file, credentials.credentials):
127152
raise ValidationError("Failed to verify the deposit data JSON files.")
128153
click.echo('\nSuccess!\nYour keys can be found at: %s' % folder)
129154
click.pause('\n\nPress any key.')

eth2deposit/credentials.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import os
22
import click
3+
from enum import Enum
34
import time
45
import json
5-
from typing import Dict, List
6+
from typing import Dict, List, Optional
7+
8+
from eth_typing import Address, HexAddress
9+
from eth_utils import to_canonical_address
610
from py_ecc.bls import G2ProofOfPossession as bls
711

812
from eth2deposit.exceptions import ValidationError
@@ -14,6 +18,7 @@
1418
from eth2deposit.settings import DEPOSIT_CLI_VERSION, BaseChainSetting
1519
from eth2deposit.utils.constants import (
1620
BLS_WITHDRAWAL_PREFIX,
21+
ETH1_ADDRESS_WITHDRAWAL_PREFIX,
1722
ETH2GWEI,
1823
MAX_DEPOSIT_AMOUNT,
1924
MIN_DEPOSIT_AMOUNT,
@@ -27,13 +32,19 @@
2732
)
2833

2934

35+
class WithdrawalType(Enum):
36+
BLS_WITHDRAWAL = 0
37+
ETH1_ADDRESS_WITHDRAWAL = 1
38+
39+
3040
class Credential:
3141
"""
3242
A Credential object contains all of the information for a single validator and the corresponding functionality.
3343
Once created, it is the only object that should be required to perform any processing for a validator.
3444
"""
3545
def __init__(self, *, mnemonic: str, mnemonic_password: str,
36-
index: int, amount: int, chain_setting: BaseChainSetting):
46+
index: int, amount: int, chain_setting: BaseChainSetting,
47+
hex_eth1_withdrawal_address: Optional[HexAddress]):
3748
# Set path as EIP-2334 format
3849
# https://eips.ethereum.org/EIPS/eip-2334
3950
purpose = '12381'
@@ -48,6 +59,7 @@ def __init__(self, *, mnemonic: str, mnemonic_password: str,
4859
mnemonic=mnemonic, path=self.signing_key_path, password=mnemonic_password)
4960
self.amount = amount
5061
self.chain_setting = chain_setting
62+
self.hex_eth1_withdrawal_address = hex_eth1_withdrawal_address
5163

5264
@property
5365
def signing_pk(self) -> bytes:
@@ -57,10 +69,42 @@ def signing_pk(self) -> bytes:
5769
def withdrawal_pk(self) -> bytes:
5870
return bls.SkToPk(self.withdrawal_sk)
5971

72+
@property
73+
def eth1_withdrawal_address(self) -> Optional[Address]:
74+
if self.hex_eth1_withdrawal_address is None:
75+
return None
76+
return to_canonical_address(self.hex_eth1_withdrawal_address)
77+
78+
@property
79+
def withdrawal_prefix(self) -> bytes:
80+
if self.eth1_withdrawal_address is not None:
81+
return ETH1_ADDRESS_WITHDRAWAL_PREFIX
82+
else:
83+
return BLS_WITHDRAWAL_PREFIX
84+
85+
@property
86+
def withdrawal_type(self) -> WithdrawalType:
87+
if self.withdrawal_prefix == BLS_WITHDRAWAL_PREFIX:
88+
return WithdrawalType.BLS_WITHDRAWAL
89+
elif self.withdrawal_prefix == ETH1_ADDRESS_WITHDRAWAL_PREFIX:
90+
return WithdrawalType.ETH1_ADDRESS_WITHDRAWAL
91+
else:
92+
raise ValueError(f"Invalid withdrawal_prefix {self.withdrawal_prefix.hex()}")
93+
6094
@property
6195
def withdrawal_credentials(self) -> bytes:
62-
withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
63-
withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
96+
if self.withdrawal_type == WithdrawalType.BLS_WITHDRAWAL:
97+
withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
98+
withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
99+
elif (
100+
self.withdrawal_type == WithdrawalType.ETH1_ADDRESS_WITHDRAWAL
101+
and self.eth1_withdrawal_address is not None
102+
):
103+
withdrawal_credentials = ETH1_ADDRESS_WITHDRAWAL_PREFIX
104+
withdrawal_credentials += b'\x00' * 11
105+
withdrawal_credentials += self.eth1_withdrawal_address
106+
else:
107+
raise ValueError(f"Invalid withdrawal_type {self.withdrawal_type}")
64108
return withdrawal_credentials
65109

66110
@property
@@ -129,7 +173,8 @@ def from_mnemonic(cls,
129173
num_keys: int,
130174
amounts: List[int],
131175
chain_setting: BaseChainSetting,
132-
start_index: int) -> 'CredentialList':
176+
start_index: int,
177+
hex_eth1_withdrawal_address: Optional[HexAddress]) -> 'CredentialList':
133178
if len(amounts) != num_keys:
134179
raise ValueError(
135180
f"The number of keys ({num_keys}) doesn't equal to the corresponding deposit amounts ({len(amounts)})."
@@ -138,7 +183,8 @@ def from_mnemonic(cls,
138183
with click.progressbar(key_indices, label='Creating your keys:\t\t',
139184
show_percent=False, show_pos=True) as indices:
140185
return cls([Credential(mnemonic=mnemonic, mnemonic_password=mnemonic_password,
141-
index=index, amount=amounts[index - start_index], chain_setting=chain_setting)
186+
index=index, amount=amounts[index - start_index], chain_setting=chain_setting,
187+
hex_eth1_withdrawal_address=hex_eth1_withdrawal_address)
142188
for index in indices])
143189

144190
def export_keystores(self, password: str, folder: str) -> List[str]:

0 commit comments

Comments
 (0)