diff --git a/src/deposit.py b/src/deposit.py index 3f15e94..d54b090 100644 --- a/src/deposit.py +++ b/src/deposit.py @@ -5,12 +5,16 @@ from key_handling.key_derivation.mnemonic import ( get_languages, get_mnemonic, ) +from utils.eth2_deposit_check import verify_deposit_data_json from utils.credentials import ( mnemonic_to_credentials, export_keystores, export_deposit_data_json, ) -from utils.constants import WORD_LISTS_PATH +from utils.constants import ( + WORD_LISTS_PATH, + MAX_DEPOSIT_AMOUNT, +) words_path = os.path.join(os.getcwd(), WORD_LISTS_PATH) languages = get_languages(words_path) @@ -53,17 +57,19 @@ def generate_mnemonic(language: str, words_path: str) -> str: ) def main(num_validators: int, mnemonic_language: str, password: str, folder: str): mnemonic = generate_mnemonic(mnemonic_language, words_path) - amounts = [32 * 10 ** 9] * num_validators + amounts = [MAX_DEPOSIT_AMOUNT] * num_validators folder = os.path.join(folder, 'validator_keys') if not os.path.exists(folder): os.mkdir(folder) click.clear() click.echo('Creating your keys.') credentials = mnemonic_to_credentials(mnemonic=mnemonic, num_keys=num_validators, amounts=amounts) - click.echo('Saving your keystores.') + click.echo('Saving your keystore(s).') export_keystores(credentials=credentials, password=password, folder=folder) - click.echo('Creating your deposits.') - export_deposit_data_json(credentials=credentials, folder=folder) + click.echo('Creating your deposit(s).') + deposits_file = export_deposit_data_json(credentials=credentials, folder=folder) + click.echo('Verifying your deposit(s).') + assert verify_deposit_data_json(deposits_file) click.echo('\nSuccess!\nYour keys can be found at: %s' % folder) click.pause('\n\nPress any key.') diff --git a/src/utils/constants.py b/src/utils/constants.py index 0afb0d9..2a875ba 100644 --- a/src/utils/constants.py +++ b/src/utils/constants.py @@ -1,4 +1,7 @@ DOMAIN_DEPOSIT = bytes.fromhex('03000000') GENESIS_FORK_VERSION = bytes.fromhex('00000000') +MIN_DEPOSIT_AMOUNT = 2 ** 0 * 10 ** 9 +MAX_DEPOSIT_AMOUNT = 2 ** 5 * 10 ** 9 + WORD_LISTS_PATH = 'src/key_handling/key_derivation/word_lists/' diff --git a/src/utils/credentials.py b/src/utils/credentials.py index 7db2f4f..1091121 100644 --- a/src/utils/credentials.py +++ b/src/utils/credentials.py @@ -10,8 +10,8 @@ from utils.crypto import SHA256 from utils.ssz import ( compute_domain, compute_signing_root, - DepositData, - SignedDepositData, + DepositMessage, + Deposit, ) @@ -54,14 +54,14 @@ def export_keystores(*, credentials: List[ValidatorCredentials], password: str, credential.save_signing_keystore(password=password, folder=folder) -def sign_deposit_data(deposit_data: DepositData, sk: int) -> SignedDepositData: +def sign_deposit_data(deposit_data: DepositMessage, sk: int) -> Deposit: ''' - Given a DepositData, it signs its root and returns a SignedDepositData + Given a DepositMessage, it signs its root and returns a Deposit ''' assert bls.PrivToPub(sk) == deposit_data.pubkey domain = compute_domain() signing_root = compute_signing_root(deposit_data, domain) - signed_deposit_data = SignedDepositData( + signed_deposit_data = Deposit( **deposit_data.as_dict(), signature=bls.Sign(sk, signing_root) ) @@ -70,9 +70,8 @@ def sign_deposit_data(deposit_data: DepositData, sk: int) -> SignedDepositData: def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder: str): deposit_data: List[dict] = [] - domain = compute_domain() for credential in credentials: - deposit_datum = DepositData( + deposit_datum = DepositMessage( pubkey=credential.signing_pk, withdrawal_credentials=SHA256(credential.withdrawal_pk), amount=credential.amount, @@ -85,3 +84,4 @@ def export_deposit_data_json(*, credentials: List[ValidatorCredentials], folder: filefolder = os.path.join(folder, 'deposit_data-%i.json' % time.time()) with open(filefolder, 'w') as f: json.dump(deposit_data, f, default=lambda x: x.hex()) + return filefolder diff --git a/src/utils/eth2_deposit_check.py b/src/utils/eth2_deposit_check.py new file mode 100644 index 0000000..8958692 --- /dev/null +++ b/src/utils/eth2_deposit_check.py @@ -0,0 +1,48 @@ +import json +from py_ecc.bls import G2ProofOfPossession as bls + +from utils.ssz import ( + compute_domain, + compute_signing_root, + Deposit, + DepositMessage, +) +from utils.constants import ( + DOMAIN_DEPOSIT, + MAX_DEPOSIT_AMOUNT, + MIN_DEPOSIT_AMOUNT, +) + + +def verify_deposit_data_json(filefolder: str) -> bool: + with open(filefolder, 'r') as f: + deposit_json = json.load(f) + return all([verify_deposit(deposit) for deposit in deposit_json]) + return False + + +def verify_deposit(deposit_data_dict: dict) -> bool: + ''' + Checks whether a deposit is valid based on the eth2 rules. + https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#deposits + ''' + pubkey = bytes.fromhex(deposit_data_dict['pubkey']) + withdrawal_credentials = bytes.fromhex(deposit_data_dict['withdrawal_credentials']) + amount = deposit_data_dict['amount'] + signature = bytes.fromhex(deposit_data_dict['signature']) + deposit_data_root = bytes.fromhex(deposit_data_dict['deposit_data_root']) + + # Verify deposit amount + if not MIN_DEPOSIT_AMOUNT < amount <= MAX_DEPOSIT_AMOUNT: + return False + + # Verify deposit signature && pubkey + deposit_message = DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) + domain = compute_domain(domain_type=DOMAIN_DEPOSIT) + signing_root = compute_signing_root(deposit_message, domain) + if not bls.Verify(pubkey, signing_root, signature): + return False + + # Verify Deposit Root + deposit = Deposit(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, signature=signature) + return deposit.hash_tree_root == deposit_data_root diff --git a/src/utils/ssz.py b/src/utils/ssz.py index 1617d74..badedd4 100644 --- a/src/utils/ssz.py +++ b/src/utils/ssz.py @@ -14,6 +14,7 @@ from utils.constants import ( bytes8 = ByteVector(8) + # Crypto Domain SSZ class SigningRoot(Serializable): @@ -41,9 +42,9 @@ def compute_signing_root(ssz_object: Serializable, domain: bytes) -> bytes: return domain_wrapped_object.hash_tree_root -# DepositData SSZ +# DepositMessage SSZ -class DepositData(Serializable): +class DepositMessage(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32), @@ -51,7 +52,7 @@ class DepositData(Serializable): ] -class SignedDepositData(Serializable): +class Deposit(Serializable): fields = [ ('pubkey', bytes48), ('withdrawal_credentials', bytes32),