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.
This commit is contained in:
Hsiao-Wei Wang 2020-05-24 00:19:41 +08:00
parent 2359a9d033
commit 08eeed26ed
No known key found for this signature in database
GPG Key ID: 95B070122902DEA4
7 changed files with 88 additions and 23 deletions

View File

@ -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

View File

@ -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())

View File

@ -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).')

19
eth2deposit/settings.py Normal file
View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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,
)