From f271d96ad20a50de0d06d8e3fbe50f092d667b52 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Fri, 18 Dec 2020 16:53:54 +0100 Subject: [PATCH 1/4] chmod keystores and deposit_data.json to `440` --- eth2deposit/credentials.py | 2 ++ eth2deposit/key_handling/keystore.py | 7 +++++-- tests/test_cli/helpers.py | 4 ++++ tests/test_cli/test_existing_menmonic.py | 11 ++++++++++- tests/test_cli/test_new_mnemonic.py | 12 +++++++++++- tests/test_cli/test_regeneration.py | 12 +++++++++++- 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/eth2deposit/credentials.py b/eth2deposit/credentials.py index 2d14ca7a..a7d864af 100644 --- a/eth2deposit/credentials.py +++ b/eth2deposit/credentials.py @@ -153,6 +153,8 @@ def export_deposit_data_json(self, folder: str) -> str: 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()) + if os.name == 'posix': + os.chmod(filefolder, int('440', 8)) # Read for owner & group return filefolder def verify_keystores(self, keystore_filefolders: List[str], password: str) -> bool: diff --git a/eth2deposit/key_handling/keystore.py b/eth2deposit/key_handling/keystore.py index 8075bbaf..6e9c122d 100644 --- a/eth2deposit/key_handling/keystore.py +++ b/eth2deposit/key_handling/keystore.py @@ -5,6 +5,7 @@ field as dataclass_field ) import json +import os from py_ecc.bls import G2ProofOfPossession as bls from secrets import randbits from typing import Any, Dict, Union @@ -90,12 +91,14 @@ class Keystore(BytesDataclass): def kdf(self, **kwargs: Any) -> bytes: return scrypt(**kwargs) if 'scrypt' in self.crypto.kdf.function else PBKDF2(**kwargs) - def save(self, file: str) -> None: + def save(self, filefolder: str) -> None: """ Save self as a JSON keystore. """ - with open(file, 'w') as f: + with open(filefolder, 'w') as f: f.write(self.as_json()) + if os.name == 'posix': + os.chmod(filefolder, int('440', 8)) # Read for owner & group @classmethod def from_json(cls, json_dict: Dict[Any, Any]) -> 'Keystore': diff --git a/tests/test_cli/helpers.py b/tests/test_cli/helpers.py index 01f9204a..5d43d959 100644 --- a/tests/test_cli/helpers.py +++ b/tests/test_cli/helpers.py @@ -19,3 +19,7 @@ def clean_key_folder(my_folder_path: str) -> None: def get_uuid(key_file: str) -> str: keystore = Keystore.from_file(key_file) return keystore.uuid + + +def get_permissions(path: str, file_name: str) -> str: + return oct(os.stat(os.path.join(path, file_name)).st_mode & 0o777) diff --git a/tests/test_cli/test_existing_menmonic.py b/tests/test_cli/test_existing_menmonic.py index 145bd55a..5c3623d1 100644 --- a/tests/test_cli/test_existing_menmonic.py +++ b/tests/test_cli/test_existing_menmonic.py @@ -7,7 +7,7 @@ from eth2deposit.deposit import cli from eth2deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME -from.helpers import clean_key_folder, get_uuid +from.helpers import clean_key_folder, get_permissions, get_uuid def test_existing_mnemonic() -> None: @@ -38,6 +38,10 @@ def test_existing_mnemonic() -> None: ] assert len(set(all_uuid)) == 5 + # Verify file permissions + if os.name == 'posix': + for file_name in key_files: + assert get_permissions(validator_keys_folder_path, file_name) == '0o440' # Clean up clean_key_folder(my_folder_path) @@ -85,5 +89,10 @@ async def test_script() -> None: validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) _, _, key_files = next(os.walk(validator_keys_folder_path)) + # Verify file permissions + if os.name == 'posix': + for file_name in key_files: + assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + # Clean up clean_key_folder(my_folder_path) diff --git a/tests/test_cli/test_new_mnemonic.py b/tests/test_cli/test_new_mnemonic.py index aa9b8847..d588db28 100644 --- a/tests/test_cli/test_new_mnemonic.py +++ b/tests/test_cli/test_new_mnemonic.py @@ -7,7 +7,7 @@ from eth2deposit.cli import new_mnemonic from eth2deposit.deposit import cli from eth2deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME -from .helpers import clean_key_folder, get_uuid +from .helpers import clean_key_folder, get_permissions, get_uuid def test_new_mnemonic(monkeypatch) -> None: @@ -40,6 +40,11 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: ] assert len(set(all_uuid)) == 1 + # Verify file permissions + if os.name == 'posix': + for file_name in key_files: + assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + # Clean up clean_key_folder(my_folder_path) @@ -103,5 +108,10 @@ async def test_script() -> None: ] assert len(set(all_uuid)) == 5 + # Verify file permissions + if os.name == 'posix': + for file_name in key_files: + assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + # Clean up clean_key_folder(my_folder_path) diff --git a/tests/test_cli/test_regeneration.py b/tests/test_cli/test_regeneration.py index fe1da9a3..8f5dac73 100644 --- a/tests/test_cli/test_regeneration.py +++ b/tests/test_cli/test_regeneration.py @@ -6,7 +6,7 @@ from eth2deposit.cli import new_mnemonic from eth2deposit.deposit import cli from eth2deposit.utils.constants import DEFAULT_VALIDATOR_KEYS_FOLDER_NAME -from .helpers import clean_key_folder, get_uuid +from .helpers import clean_key_folder, get_permissions, get_uuid def test_regeneration(monkeypatch) -> None: @@ -47,6 +47,11 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: for key_file in part_1_key_files] assert len(set(all_uuid)) == 2 + # Verify file permissions + if os.name == 'posix': + for file_name in part_1_key_files: + assert get_permissions(validator_keys_folder_path_1, file_name) == '0o440' + # Part 2: existing-mnemonic runner = CliRunner() # Create index 1 and 2 @@ -78,6 +83,11 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: assert keystore_1_1['pubkey'] == keystore_2_0['pubkey'] assert keystore_1_1['path'] == keystore_2_0['path'] + # Verify file permissions + if os.name == 'posix': + for file_name in part_2_key_files: + assert get_permissions(validator_keys_folder_path_2, file_name) == '0o440' + # Clean up clean_key_folder(folder_path_1) clean_key_folder(folder_path_2) From 62e13e82613101e1808b250c2a777e30304ce08f Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 27 Jan 2021 14:53:06 +0100 Subject: [PATCH 2/4] Add note about 440 file permissions to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7aea4c5e..e24679da 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ You can find the audit report by Trail of Bits [here](https://github.com/trailof ### For Linux or MacOS users +#### File Permissions + +On Unix-based systems, keystores and the `deposit_data*.json` have `440`/`-r--r-----` file permissions (user & group read only). This improves security by limiting which users and processes that have access to these files. If you are getting `permission denied` errors when handling your keystores, consider changing which user/group owns the file (with `chown`) or, if need be, change the file permissions with `chmod`. + #### Option 1. Download binary executable file ##### Step 1. Installation From fc466d3b3231f9e4f6f8651f25afb948babe3ea0 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Fri, 19 Mar 2021 13:08:59 +0100 Subject: [PATCH 3/4] Adds Prater testnet support --- eth2deposit/settings.py | 6 +++++- setup.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/eth2deposit/settings.py b/eth2deposit/settings.py index 35bde8aa..adc36d95 100644 --- a/eth2deposit/settings.py +++ b/eth2deposit/settings.py @@ -1,7 +1,7 @@ from typing import Dict, NamedTuple -DEPOSIT_CLI_VERSION = '1.1.0' +DEPOSIT_CLI_VERSION = '1.1.1' class BaseChainSetting(NamedTuple): @@ -16,6 +16,7 @@ class BaseChainSetting(NamedTuple): SPADINA = 'spadina' ZINKEN = 'zinken' PYRMONT = 'pyrmont' +PRATER = 'prater' # Eth2 Mainnet setting @@ -32,6 +33,8 @@ class BaseChainSetting(NamedTuple): ZinkenSetting = BaseChainSetting(ETH2_NETWORK_NAME=ZINKEN, GENESIS_FORK_VERSION=bytes.fromhex('00000003')) # Eth2 pre-launch testnet (spec v1.0.0) PyrmontSetting = BaseChainSetting(ETH2_NETWORK_NAME=PYRMONT, GENESIS_FORK_VERSION=bytes.fromhex('00002009')) +# Eth2 testnet (spec v1.0.1) +PraterSetting = BaseChainSetting(ETH2_NETWORK_NAME=PRATER, GENESIS_FORK_VERSION=bytes.fromhex('00001020')) ALL_CHAINS: Dict[str, BaseChainSetting] = { @@ -42,6 +45,7 @@ class BaseChainSetting(NamedTuple): SPADINA: SpadinaSetting, ZINKEN: ZinkenSetting, PYRMONT: PyrmontSetting, + PRATER: PraterSetting, } diff --git a/setup.py b/setup.py index efe66cc1..e148d351 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="eth2deposit", - version='1.1.0', + version='1.1.1', py_modules=["eth2deposit"], packages=find_packages(exclude=('tests', 'docs')), python_requires=">=3.7,<4", From 388191dc5539238fa41e7d02a289ba53a66e10da Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Mon, 22 Mar 2021 15:31:55 +0100 Subject: [PATCH 4/4] Depricate support for old testnets (Witti, Altona, Medalla, Spadina, Zinken) --- README.md | 4 ++-- eth2deposit/settings.py | 20 -------------------- tests/test_credentials.py | 4 ++-- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e24679da..f7a5df4b 100644 --- a/README.md +++ b/README.md @@ -293,10 +293,10 @@ You can also run the tool with optional arguments: docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ethereum/eth2.0-deposit-cli new-mnemonic --num_validators= --mnemonic_language=english --folder= ``` -Example for 1 validator on the [Medalla testnet](https://medalla.launchpad.ethereum.org/) using english: +Example for 1 validator on the [Prater testnet](https://prater.launchpad.ethereum.org/) using english: ```sh -docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ethereum/eth2.0-deposit-cli new-mnemonic --num_validators=1 --mnemonic_language=english --chain=medalla +docker run -it --rm -v $(pwd)/validator_keys:/app/validator_keys ethereum/eth2.0-deposit-cli new-mnemonic --num_validators=1 --mnemonic_language=english --chain=prater ``` ###### Arguments diff --git a/eth2deposit/settings.py b/eth2deposit/settings.py index adc36d95..8558afe4 100644 --- a/eth2deposit/settings.py +++ b/eth2deposit/settings.py @@ -10,27 +10,12 @@ class BaseChainSetting(NamedTuple): MAINNET = 'mainnet' -WITTI = 'witti' -ALTONA = 'altona' -MEDALLA = 'medalla' -SPADINA = 'spadina' -ZINKEN = 'zinken' PYRMONT = 'pyrmont' PRATER = 'prater' # Eth2 Mainnet setting MainnetSetting = BaseChainSetting(ETH2_NETWORK_NAME=MAINNET, GENESIS_FORK_VERSION=bytes.fromhex('00000000')) -# Eth2 spec v0.11.3 testnet -WittiSetting = BaseChainSetting(ETH2_NETWORK_NAME=WITTI, GENESIS_FORK_VERSION=bytes.fromhex('00000113')) -# Eth2 spec v0.12.1 testnet -AltonaSetting = BaseChainSetting(ETH2_NETWORK_NAME=ALTONA, GENESIS_FORK_VERSION=bytes.fromhex('00000121')) -# Eth2 "official" public testnet (spec v0.12.2) -MedallaSetting = BaseChainSetting(ETH2_NETWORK_NAME=MEDALLA, GENESIS_FORK_VERSION=bytes.fromhex('00000001')) -# Eth2 "dress rehearsal" testnet (spec v0.12.3) -SpadinaSetting = BaseChainSetting(ETH2_NETWORK_NAME=SPADINA, GENESIS_FORK_VERSION=bytes.fromhex('00000002')) -# Eth2 "dress rehearsal" testnet (spec v0.12.3) -ZinkenSetting = BaseChainSetting(ETH2_NETWORK_NAME=ZINKEN, GENESIS_FORK_VERSION=bytes.fromhex('00000003')) # Eth2 pre-launch testnet (spec v1.0.0) PyrmontSetting = BaseChainSetting(ETH2_NETWORK_NAME=PYRMONT, GENESIS_FORK_VERSION=bytes.fromhex('00002009')) # Eth2 testnet (spec v1.0.1) @@ -39,11 +24,6 @@ class BaseChainSetting(NamedTuple): ALL_CHAINS: Dict[str, BaseChainSetting] = { MAINNET: MainnetSetting, - WITTI: WittiSetting, - ALTONA: AltonaSetting, - MEDALLA: MedallaSetting, - SPADINA: SpadinaSetting, - ZINKEN: ZinkenSetting, PYRMONT: PyrmontSetting, PRATER: PraterSetting, } diff --git a/tests/test_credentials.py b/tests/test_credentials.py index 219735a4..fa14b6be 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -1,7 +1,7 @@ import pytest from eth2deposit.credentials import CredentialList -from eth2deposit.settings import MedallaSetting +from eth2deposit.settings import MainnetSetting def test_from_mnemonic() -> None: @@ -11,6 +11,6 @@ def test_from_mnemonic() -> None: mnemonic_password="", num_keys=1, amounts=[32, 32], - chain_setting=MedallaSetting, + chain_setting=MainnetSetting, start_index=1, )