Skip to content

Commit

Permalink
Merge pull request #196 from ethereum/dev
Browse files Browse the repository at this point in the history
dev -> master: v1.2.0
  • Loading branch information
CarlBeek authored Apr 2, 2021
2 parents 7bac110 + 17b7363 commit 256ea21
Show file tree
Hide file tree
Showing 14 changed files with 2,439 additions and 27 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ You can use `new-mnemonic --help` to see all arguments. Note that if there are m
| Argument | Type | Description |
| -------- | -------- | -------- |
| `--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. |
| `--mnemonic_language` | String. Options: `czech`, `chinese_traditional`, `chinese_simplified`, `english`, `spanish`, `italian`, `korean`. Default to `english` | The mnemonic language |
| `--mnemonic_language` | String. Options: `chinese_simplified`, `chinese_traditional`, `czech`, `english`, `italian`, `korean`, `portuguese`, `spanish`. Default to `english` | The mnemonic language |
| `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) |
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
| `--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). |

###### `existing-mnemonic` Arguments

Expand All @@ -132,6 +133,7 @@ You can use `existing-mnemonic --help` to see all arguments. Note that if there
| `--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. |
| `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) |
| `--chain` | String. `mainnet` by default | The chain setting for the signing domain. |
| `--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). |

###### Successful message

Expand Down
29 changes: 27 additions & 2 deletions eth2deposit/cli/generate_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
Callable,
)

from eth_typing import HexAddress
from eth_utils import is_hex_address, to_normalized_address

from eth2deposit.credentials import (
CredentialList,
)
Expand Down Expand Up @@ -57,6 +60,18 @@ def validate_password(cts: click.Context, param: Any, password: str) -> str:
return password


def validate_eth1_withdrawal_address(cts: click.Context, param: Any, address: str) -> HexAddress:
if address is None:
return None
if not is_hex_address(address):
raise ValueError("The given Eth1 address is not in hexadecimal encoded form.")

normalized_address = to_normalized_address(address)
click.echo(f'\n**[Warning] you are setting Eth1 address {normalized_address} as your withdrawal address. '
'Please ensure that you have control over this address.**\n')
return normalized_address


def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[..., Any]:
'''
This is a decorator that, when applied to a parent-command, implements the
Expand Down Expand Up @@ -91,6 +106,14 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[
'to ask you for your mnemonic as otherwise it will appear in your shell history.)'),
prompt='Type the password that secures your validator keystore(s)',
),
click.option(
'--eth1_withdrawal_address',
default=None,
callback=validate_eth1_withdrawal_address,
help=('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.'),
),
]
for decorator in reversed(decorators):
function = decorator(function)
Expand All @@ -100,7 +123,8 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[
@click.command()
@click.pass_context
def generate_keys(ctx: click.Context, validator_start_index: int,
num_validators: int, folder: str, chain: str, keystore_password: str, **kwargs: Any) -> None:
num_validators: int, folder: str, chain: str, keystore_password: str,
eth1_withdrawal_address: HexAddress, **kwargs: Any) -> None:
mnemonic = ctx.obj['mnemonic']
mnemonic_password = ctx.obj['mnemonic_password']
amounts = [MAX_DEPOSIT_AMOUNT] * num_validators
Expand All @@ -118,12 +142,13 @@ def generate_keys(ctx: click.Context, validator_start_index: int,
amounts=amounts,
chain_setting=chain_setting,
start_index=validator_start_index,
hex_eth1_withdrawal_address=eth1_withdrawal_address,
)
keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder)
deposits_file = credentials.export_deposit_data_json(folder=folder)
if not credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=keystore_password):
raise ValidationError("Failed to verify the keystores.")
if not verify_deposit_data_json(deposits_file):
if not verify_deposit_data_json(deposits_file, credentials.credentials):
raise ValidationError("Failed to verify the deposit data JSON files.")
click.echo('\nSuccess!\nYour keys can be found at: %s' % folder)
click.pause('\n\nPress any key.')
58 changes: 52 additions & 6 deletions eth2deposit/credentials.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import os
import click
from enum import Enum
import time
import json
from typing import Dict, List
from typing import Dict, List, Optional

from eth_typing import Address, HexAddress
from eth_utils import to_canonical_address
from py_ecc.bls import G2ProofOfPossession as bls

from eth2deposit.exceptions import ValidationError
Expand All @@ -14,6 +18,7 @@
from eth2deposit.settings import DEPOSIT_CLI_VERSION, BaseChainSetting
from eth2deposit.utils.constants import (
BLS_WITHDRAWAL_PREFIX,
ETH1_ADDRESS_WITHDRAWAL_PREFIX,
ETH2GWEI,
MAX_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
Expand All @@ -27,13 +32,19 @@
)


class WithdrawalType(Enum):
BLS_WITHDRAWAL = 0
ETH1_ADDRESS_WITHDRAWAL = 1


class Credential:
"""
A Credential object contains all of the information for a single validator and the corresponding functionality.
Once created, it is the only object that should be required to perform any processing for a validator.
"""
def __init__(self, *, mnemonic: str, mnemonic_password: str,
index: int, amount: int, chain_setting: BaseChainSetting):
index: int, amount: int, chain_setting: BaseChainSetting,
hex_eth1_withdrawal_address: Optional[HexAddress]):
# Set path as EIP-2334 format
# https://eips.ethereum.org/EIPS/eip-2334
purpose = '12381'
Expand All @@ -48,6 +59,7 @@ def __init__(self, *, mnemonic: str, mnemonic_password: str,
mnemonic=mnemonic, path=self.signing_key_path, password=mnemonic_password)
self.amount = amount
self.chain_setting = chain_setting
self.hex_eth1_withdrawal_address = hex_eth1_withdrawal_address

@property
def signing_pk(self) -> bytes:
Expand All @@ -57,10 +69,42 @@ def signing_pk(self) -> bytes:
def withdrawal_pk(self) -> bytes:
return bls.SkToPk(self.withdrawal_sk)

@property
def eth1_withdrawal_address(self) -> Optional[Address]:
if self.hex_eth1_withdrawal_address is None:
return None
return to_canonical_address(self.hex_eth1_withdrawal_address)

@property
def withdrawal_prefix(self) -> bytes:
if self.eth1_withdrawal_address is not None:
return ETH1_ADDRESS_WITHDRAWAL_PREFIX
else:
return BLS_WITHDRAWAL_PREFIX

@property
def withdrawal_type(self) -> WithdrawalType:
if self.withdrawal_prefix == BLS_WITHDRAWAL_PREFIX:
return WithdrawalType.BLS_WITHDRAWAL
elif self.withdrawal_prefix == ETH1_ADDRESS_WITHDRAWAL_PREFIX:
return WithdrawalType.ETH1_ADDRESS_WITHDRAWAL
else:
raise ValueError(f"Invalid withdrawal_prefix {self.withdrawal_prefix.hex()}")

@property
def withdrawal_credentials(self) -> bytes:
withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
if self.withdrawal_type == WithdrawalType.BLS_WITHDRAWAL:
withdrawal_credentials = BLS_WITHDRAWAL_PREFIX
withdrawal_credentials += SHA256(self.withdrawal_pk)[1:]
elif (
self.withdrawal_type == WithdrawalType.ETH1_ADDRESS_WITHDRAWAL
and self.eth1_withdrawal_address is not None
):
withdrawal_credentials = ETH1_ADDRESS_WITHDRAWAL_PREFIX
withdrawal_credentials += b'\x00' * 11
withdrawal_credentials += self.eth1_withdrawal_address
else:
raise ValueError(f"Invalid withdrawal_type {self.withdrawal_type}")
return withdrawal_credentials

@property
Expand Down Expand Up @@ -129,7 +173,8 @@ def from_mnemonic(cls,
num_keys: int,
amounts: List[int],
chain_setting: BaseChainSetting,
start_index: int) -> 'CredentialList':
start_index: int,
hex_eth1_withdrawal_address: Optional[HexAddress]) -> 'CredentialList':
if len(amounts) != num_keys:
raise ValueError(
f"The number of keys ({num_keys}) doesn't equal to the corresponding deposit amounts ({len(amounts)})."
Expand All @@ -138,7 +183,8 @@ def from_mnemonic(cls,
with click.progressbar(key_indices, label='Creating your keys:\t\t',
show_percent=False, show_pos=True) as indices:
return cls([Credential(mnemonic=mnemonic, mnemonic_password=mnemonic_password,
index=index, amount=amounts[index - start_index], chain_setting=chain_setting)
index=index, amount=amounts[index - start_index], chain_setting=chain_setting,
hex_eth1_withdrawal_address=hex_eth1_withdrawal_address)
for index in indices])

def export_keystores(self, password: str, folder: str) -> List[str]:
Expand Down
Loading

0 comments on commit 256ea21

Please sign in to comment.