Skip to content

Commit

Permalink
Merge pull request #32 from ethereum/hwwhww/fork_version
Browse files Browse the repository at this point in the history
Add fork_version
  • Loading branch information
hwwhww authored Jun 2, 2020
2 parents 1f6f63b + dbac6d2 commit 87a1e15
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 30 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 18 additions & 11 deletions eth2deposit/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@
)
from eth2deposit.utils.constants import (
BLS_WITHDRAWAL_PREFIX,
DOMAIN_DEPOSIT,
)
from eth2deposit.utils.crypto import SHA256
from eth2deposit.utils.ssz import (
compute_domain,
compute_deposit_domain,
compute_signing_root,
DepositData,
DepositMessage,
)


class Credential:
def __init__(self, *, mnemonic: str, index: int, amount: int):
def __init__(self, *, mnemonic: str, index: int, amount: int, fork_version: bytes):
# Set path as EIP-2334 format
# https://eips.ethereum.org/EIPS/eip-2334
purpose = '12381'
Expand All @@ -36,6 +35,7 @@ def __init__(self, *, mnemonic: str, index: int, amount: int):
self.withdrawal_sk = mnemonic_and_path_to_key(mnemonic=mnemonic, path=withdrawal_key_path, password='')
self.signing_sk = mnemonic_and_path_to_key(mnemonic=mnemonic, path=self.signing_key_path, password='')
self.amount = amount
self.fork_version = fork_version

@property
def signing_pk(self) -> bytes:
Expand Down Expand Up @@ -66,18 +66,18 @@ def verify_keystore(self, keystore_filefolder: str, password: str) -> bool:
secret_bytes = saved_keystore.decrypt(password)
return self.signing_sk == int.from_bytes(secret_bytes, 'big')

def unsigned_deposit(self) -> DepositMessage:
def deposit_message(self) -> DepositMessage:
return DepositMessage(
pubkey=self.signing_pk,
withdrawal_credentials=self.withdrawal_credentials,
amount=self.amount,
)

def signed_deposit(self) -> DepositData:
domain = compute_domain(domain_type=DOMAIN_DEPOSIT)
signing_root = compute_signing_root(self.unsigned_deposit(), domain)
domain = compute_deposit_domain(fork_version=self.fork_version)
signing_root = compute_signing_root(self.deposit_message(), domain)
signed_deposit = DepositData(
**self.unsigned_deposit().as_dict(),
**self.deposit_message().as_dict(),
signature=bls.Sign(self.signing_sk, signing_root)
)
return signed_deposit
Expand All @@ -88,10 +88,16 @@ def __init__(self, credentials: List[Credential]):
self.credentials = credentials

@classmethod
def from_mnemonic(cls, *, mnemonic: str, num_keys: int, amounts: List[int], start_index: int=0) -> 'CredentialList':
def from_mnemonic(cls,
*,
mnemonic: str,
num_keys: int,
amounts: List[int],
fork_version: bytes,
start_index: int=0) -> 'CredentialList':
assert len(amounts) == num_keys
key_indices = range(start_index, start_index + num_keys)
return cls([Credential(mnemonic=mnemonic, index=index, amount=amounts[index])
return cls([Credential(mnemonic=mnemonic, index=index, amount=amounts[index], fork_version=fork_version)
for index in key_indices])

def export_keystores(self, password: str, folder: str) -> List[str]:
Expand All @@ -102,8 +108,9 @@ def export_deposit_data_json(self, folder: str) -> str:
for credential in self.credentials:
signed_deposit_datum = credential.signed_deposit()
datum_dict = signed_deposit_datum.as_dict()
datum_dict.update({'deposit_data_root': credential.unsigned_deposit().hash_tree_root})
datum_dict.update({'signed_deposit_data_root': signed_deposit_datum.hash_tree_root})
datum_dict.update({'deposit_message_root': credential.deposit_message().hash_tree_root})
datum_dict.update({'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())
Expand Down
22 changes: 19 additions & 3 deletions eth2deposit/deposit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
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)
Expand Down Expand Up @@ -51,7 +56,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',
Expand All @@ -64,18 +69,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 = CredentialList.from_mnemonic(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts)
credentials = CredentialList.from_mnemonic(
mnemonic=mnemonic,
num_keys=num_validators,
amounts=amounts,
fork_version=setting.GENESIS_FORK_VERSION,
)
click.echo('Saving your keystore(s).')
keystore_filefolders = credentials.export_keystores(password=password, folder=folder)
click.echo('Creating your deposit(s).')
Expand Down
28 changes: 28 additions & 0 deletions eth2deposit/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Dict, NamedTuple


class BaseChainSetting(NamedTuple):
GENESIS_FORK_VERSION: bytes


MainnetSetting = BaseChainSetting(
GENESIS_FORK_VERSION=bytes.fromhex('00000000'),
)


# Eth2 spec v0.11.3 testnet
WittiSetting = BaseChainSetting(
GENESIS_FORK_VERSION=bytes.fromhex('00000113'),
)


MAINNET = 'mainnet'
WITTI = 'witti'
ALL_CHAINS: Dict[str, BaseChainSetting] = {
MAINNET: MainnetSetting,
WITTI: WittiSetting,
}


def get_setting(chain_name: str = MAINNET) -> BaseChainSetting:
return ALL_CHAINS[chain_name]
3 changes: 2 additions & 1 deletion eth2deposit/utils/constants.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
36 changes: 28 additions & 8 deletions eth2deposit/utils/ssz.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,60 @@
ByteVector,
Serializable,
uint64,
bytes4,
bytes32,
bytes48,
bytes96
)

from eth2deposit.utils.constants import (
DOMAIN_DEPOSIT,
GENESIS_FORK_VERSION,
ZERO_BYTES32,
)

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,
)
Expand Down
14 changes: 7 additions & 7 deletions eth2deposit/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
from py_ecc.bls import G2ProofOfPossession as bls

from eth2deposit.utils.ssz import (
compute_domain,
compute_deposit_domain,
compute_signing_root,
DepositData,
DepositMessage,
)
from eth2deposit.utils.constants import (
DOMAIN_DEPOSIT,
MAX_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
)
Expand All @@ -36,16 +35,17 @@ def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool:
withdrawal_credentials = bytes.fromhex(deposit_data_dict['withdrawal_credentials'])
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'])
deposit_message_root = bytes.fromhex(deposit_data_dict['deposit_data_root'])
fork_version = bytes.fromhex(deposit_data_dict['fork_version'])

# Verify deposit amount
if not MIN_DEPOSIT_AMOUNT < amount <= MAX_DEPOSIT_AMOUNT:
return False

# Verify deposit signature && pubkey
unsigned_deposit = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = compute_domain(domain_type=DOMAIN_DEPOSIT)
signing_root = compute_signing_root(unsigned_deposit, domain)
deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = compute_deposit_domain(fork_version)
signing_root = compute_signing_root(deposit_message, domain)
if not bls.Verify(pubkey, signing_root, signature):
return False

Expand All @@ -56,4 +56,4 @@ def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool:
amount=amount,
signature=signature,
)
return signed_deposit.hash_tree_root == deposit_data_root
return signed_deposit.hash_tree_root == deposit_message_root

0 comments on commit 87a1e15

Please sign in to comment.