From 08eeed26edb1d110227a75c6c404d9b9fb5bd42e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sun, 24 May 2020 00:19:41 +0800 Subject: [PATCH] Add fork_version 1. Use v0.12.0 signing format 2. `GENESIS_FORK_VERSION` may be various for mainnet and testnets. Add option for it. --- README.md | 1 + eth2deposit/credentials.py | 24 +++++++++++------ eth2deposit/deposit.py | 22 ++++++++++++--- eth2deposit/settings.py | 19 +++++++++++++ eth2deposit/utils/constants.py | 3 ++- eth2deposit/utils/eth2_deposit_check.py | 6 ++--- eth2deposit/utils/ssz.py | 36 +++++++++++++++++++------ 7 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 eth2deposit/settings.py diff --git a/README.md b/README.md index 6709c33..85f627d 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ You can also run the tool with optional arguments: | `--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 | | `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) | +| `--chain` | String. `mainnet` by defualt | The chain setting for the signing domain. | ### For Windows users diff --git a/eth2deposit/credentials.py b/eth2deposit/credentials.py index e25d3c5..9938c55 100644 --- a/eth2deposit/credentials.py +++ b/eth2deposit/credentials.py @@ -12,7 +12,7 @@ from eth2deposit.key_handling.keystore import ( from eth2deposit.utils.constants import BLS_WITHDRAWAL_PREFIX from eth2deposit.utils.crypto import SHA256 from eth2deposit.utils.ssz import ( - compute_domain, + compute_deposit_domain, compute_signing_root, DepositMessage, Deposit, @@ -20,11 +20,12 @@ from eth2deposit.utils.ssz import ( class ValidatorCredentials: - def __init__(self, *, mnemonic: str, index: int, amount: int): + def __init__(self, *, mnemonic: str, index: int, amount: int, fork_version: bytes): self.signing_key_path = 'm/12381/3600/%s/0' % index self.signing_sk = mnemonic_and_path_to_key(mnemonic=mnemonic, path=self.signing_key_path) self.withdrawal_sk = mnemonic_and_path_to_key(mnemonic=mnemonic, path=self.signing_key_path + '/0') self.amount = amount + self.fork_version = fork_version @property def signing_pk(self) -> bytes: @@ -57,11 +58,17 @@ class ValidatorCredentials: def mnemonic_to_credentials(*, mnemonic: str, num_keys: int, - amounts: List[int], start_index: int=0,) -> List[ValidatorCredentials]: + amounts: List[int], fork_version: bytes, start_index: int=0,) -> List[ValidatorCredentials]: assert len(amounts) == num_keys key_indices = range(start_index, start_index + num_keys) - credentials = [ValidatorCredentials(mnemonic=mnemonic, index=index, amount=amounts[index]) - for index in key_indices] + credentials = [ + ValidatorCredentials( + mnemonic=mnemonic, + index=index, + amount=amounts[index], + fork_version=fork_version, + ) for index in key_indices + ] return credentials @@ -69,12 +76,12 @@ def export_keystores(*, credentials: List[ValidatorCredentials], password: str, return [credential.save_signing_keystore(password=password, folder=folder) for credential in credentials] -def sign_deposit_data(deposit_data: DepositMessage, sk: int) -> Deposit: +def sign_deposit_data(deposit_data: DepositMessage, sk: int, fork_version: bytes) -> Deposit: ''' Given a DepositMessage, it signs its root and returns a Deposit ''' assert bls.PrivToPub(sk) == deposit_data.pubkey - domain = compute_domain() + domain = compute_deposit_domain(fork_version) signing_root = compute_signing_root(deposit_data, domain) signed_deposit_data = Deposit( **deposit_data.as_dict(), @@ -91,10 +98,11 @@ def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder: withdrawal_credentials=credential.withdrawal_credentials, amount=credential.amount, ) - signed_deposit_datum = sign_deposit_data(deposit_datum, credential.signing_sk) + signed_deposit_datum = sign_deposit_data(deposit_datum, credential.signing_sk, credential.fork_version) datum_dict = signed_deposit_datum.as_dict() datum_dict.update({'deposit_data_root': deposit_datum.hash_tree_root}) datum_dict.update({'signed_deposit_data_root': signed_deposit_datum.hash_tree_root}) + datum_dict.update({'fork_version': credential.fork_version}) deposit_data.append(datum_dict) filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time()) diff --git a/eth2deposit/deposit.py b/eth2deposit/deposit.py index 796969a..968ae65 100644 --- a/eth2deposit/deposit.py +++ b/eth2deposit/deposit.py @@ -19,6 +19,11 @@ from eth2deposit.utils.constants import ( DEFAULT_VALIDATOR_KEYS_FOLDER_NAME, ) from eth2deposit.utils.ascii_art import RHINO_0 +from eth2deposit.settings import ( + ALL_CHAINS, + MAINNET, + get_setting, +) words_path = os.path.join(os.getcwd(), WORD_LISTS_PATH) languages = get_languages(words_path) @@ -54,7 +59,7 @@ def check_python_version() -> None: '--num_validators', prompt='Please choose how many validators you wish to run', required=True, - type=int, # type: ignore + type=int, ) @click.option( '--mnemonic_language', @@ -67,18 +72,29 @@ def check_python_version() -> None: type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd() ) +@click.option( + '--chain', + type=click.Choice(ALL_CHAINS.keys(), case_sensitive=False), + default=MAINNET, +) @click.password_option(prompt='Type the password that secures your validator keystore(s)') -def main(num_validators: int, mnemonic_language: str, folder: str, password: str): +def main(num_validators: int, mnemonic_language: str, folder: str, chain: str, password: str) -> None: check_python_version() mnemonic = generate_mnemonic(mnemonic_language, words_path) amounts = [MAX_DEPOSIT_AMOUNT] * num_validators folder = os.path.join(folder, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + setting = get_setting(chain) if not os.path.exists(folder): os.mkdir(folder) click.clear() click.echo(RHINO_0) click.echo('Creating your keys.') - credentials = mnemonic_to_credentials(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts) + credentials = mnemonic_to_credentials( + mnemonic=mnemonic, + num_keys=num_validators, + amounts=amounts, + fork_version=setting.GENESIS_FORK_VERSION, + ) click.echo('Saving your keystore(s).') keystore_filefolders = export_keystores(credentials=credentials, password=password, folder=folder) click.echo('Creating your deposit(s).') diff --git a/eth2deposit/settings.py b/eth2deposit/settings.py new file mode 100644 index 0000000..1842ece --- /dev/null +++ b/eth2deposit/settings.py @@ -0,0 +1,19 @@ +from typing import Dict, NamedTuple + + +class BaseChainSetting(NamedTuple): + GENESIS_FORK_VERSION: bytes + + +MainnetSetting = BaseChainSetting( + GENESIS_FORK_VERSION=bytes.fromhex('00000000'), +) + +MAINNET = 'mainnet' +ALL_CHAINS: Dict[str, BaseChainSetting] = { + MAINNET: MainnetSetting, +} + + +def get_setting(chain_name: str = MAINNET) -> BaseChainSetting: + return ALL_CHAINS[chain_name] diff --git a/eth2deposit/utils/constants.py b/eth2deposit/utils/constants.py index 52a25e6..05afe71 100644 --- a/eth2deposit/utils/constants.py +++ b/eth2deposit/utils/constants.py @@ -1,9 +1,10 @@ import os +ZERO_BYTES32 = b'\x00' * 32 + # Spec constants DOMAIN_DEPOSIT = bytes.fromhex('03000000') -GENESIS_FORK_VERSION = bytes.fromhex('00000000') BLS_WITHDRAWAL_PREFIX = bytes.fromhex('00') MIN_DEPOSIT_AMOUNT = 2 ** 0 * 10 ** 9 diff --git a/eth2deposit/utils/eth2_deposit_check.py b/eth2deposit/utils/eth2_deposit_check.py index fa07709..d277ee9 100644 --- a/eth2deposit/utils/eth2_deposit_check.py +++ b/eth2deposit/utils/eth2_deposit_check.py @@ -8,13 +8,12 @@ from typing import Any, Dict from py_ecc.bls import G2ProofOfPossession as bls from eth2deposit.utils.ssz import ( - compute_domain, + compute_deposit_domain, compute_signing_root, Deposit, DepositMessage, ) from eth2deposit.utils.constants import ( - DOMAIN_DEPOSIT, MAX_DEPOSIT_AMOUNT, MIN_DEPOSIT_AMOUNT, ) @@ -37,6 +36,7 @@ def verify_deposit(deposit_data_dict: Dict[str, Any]) -> bool: amount = deposit_data_dict['amount'] signature = BLSSignature(bytes.fromhex(deposit_data_dict['signature'])) deposit_data_root = bytes.fromhex(deposit_data_dict['signed_deposit_data_root']) + fork_version = bytes.fromhex(deposit_data_dict['fork_version']) # Verify deposit amount if not MIN_DEPOSIT_AMOUNT < amount <= MAX_DEPOSIT_AMOUNT: @@ -44,7 +44,7 @@ def verify_deposit(deposit_data_dict: Dict[str, Any]) -> bool: # Verify deposit signature && pubkey deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) - domain = compute_domain(domain_type=DOMAIN_DEPOSIT) + domain = compute_deposit_domain(fork_version) signing_root = compute_signing_root(deposit_message, domain) if not bls.Verify(pubkey, signing_root, signature): return False diff --git a/eth2deposit/utils/ssz.py b/eth2deposit/utils/ssz.py index f03f8a8..0b5eb2a 100644 --- a/eth2deposit/utils/ssz.py +++ b/eth2deposit/utils/ssz.py @@ -2,14 +2,14 @@ from ssz import ( ByteVector, Serializable, uint64, + bytes4, bytes32, bytes48, bytes96 ) - from eth2deposit.utils.constants import ( DOMAIN_DEPOSIT, - GENESIS_FORK_VERSION, + ZERO_BYTES32, ) bytes8 = ByteVector(8) @@ -17,25 +17,45 @@ bytes8 = ByteVector(8) # Crypto Domain SSZ -class SigningRoot(Serializable): +class SigningData(Serializable): fields = [ ('object_root', bytes32), - ('domain', bytes8) + ('domain', bytes32) ] -def compute_domain(domain_type: bytes=DOMAIN_DEPOSIT, fork_version: bytes=GENESIS_FORK_VERSION) -> bytes: +class ForkData(Serializable): + fields = [ + ('current_version', bytes4), + ('genesis_validators_root', bytes32), + ] + + +def compute_deposit_domain(fork_version: bytes) -> bytes: """ - Return the domain for the ``domain_type`` and ``fork_version``. + Deposit-only `compute_domain` """ - return domain_type + fork_version + assert len(fork_version) == 4 + domain_type = DOMAIN_DEPOSIT + fork_data_root = compute_deposit_fork_data_root(fork_version) + return domain_type + fork_data_root[:28] + + +def compute_deposit_fork_data_root(current_version: bytes) -> bytes: + genesis_validators_root = ZERO_BYTES32 # For deposit, it's fixed value + assert len(current_version) == 4 + return ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + ).hash_tree_root def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes: """ Return the signing root of an object by calculating the root of the object-domain tree. """ - domain_wrapped_object = SigningRoot( + assert len(domain) == 32 + domain_wrapped_object = SigningData( object_root=ssz_object.hash_tree_root, domain=domain, )