From 30e9ebbb2183cf275112f9a9009681caaf645a24 Mon Sep 17 00:00:00 2001 From: Maksym Kulish Date: Wed, 3 Jul 2024 16:56:28 +0300 Subject: [PATCH] Add script for creating keystores folder --- README.md | 35 +++++++++++++++++ scripts/generate_keys_folder.py | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 scripts/generate_keys_folder.py diff --git a/README.md b/README.md index 616c10a..9020803 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,41 @@ You can use `eth-staking-smith` as follows to convert your address: Note that --validator-index and --validator-start-index are two distinct parameter, the former being index of validator on Beacon chain, and the latter is the index of validator private key derived from the seed +## Exporting CLI standard output into common keystores folder format + +Most validator clients recognize the keystore folder format, +produced by upstream Python deposit CLI. While `eth-staking-smith` outputs +all validator data into standard output, allowing for better security in +enterprise setups, for small and individual stakers this is not convenient, +as they need to be able to import validator keys directly into validator client. + +To address such needs, `eth-staking-smith` provides convenience Python3 script +to export JSON validator output into common keystore folder format. It should +work on any box with Python 3.10+ installed. + +``` +mkdir validator_keys/ +./target/debug/eth-staking-smith new-mnemonic --chain holesky --num_validators 2 \ + --keystore_password test > validator_secrets.json +cat validator_secrets.json | python3 scripts/generate_keys_folder.py +cat validator_secrets.json | jq .mnemonic.seed > mnemonic.txt +rm validator_secrets.json +echo "MAKE SURE TO BACK UP mnemonic.text IN THE SAFE PLACE" + +ls validator_keys/ +deposit_data-1720014619.json keystore-m_12381_3600_0_0_0-1720014619.json keystore-m_12381_3600_1_0_0-1720014619.json +``` + +The contents of `validator_keys/` folder might be imported into most +validator clients, for example Lighthouse import command will look like that: + +``` +echo "test" > ./password.txt +lighthouse account validator import \ + --network holesky --reuse-password + --directory validator_keys/ --password-file ./password.txt +``` + ### Command to send SignedBLSToExecutionChange request to Beacon node ``` diff --git a/scripts/generate_keys_folder.py b/scripts/generate_keys_folder.py new file mode 100644 index 0000000..a1eda0b --- /dev/null +++ b/scripts/generate_keys_folder.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +# +# This script takes JSON output of `eth-staking-smith` into stdin, +# and produces folder with deposit data and keystore files in the given +# location. +# +# Caveat: keystore_password should be specified before generating. +# + +import argparse +import json +import logging +import pathlib +import sys +import time + + +logger = logging.getLogger(__name__) +parser = argparse.ArgumentParser("Generate eth validator keys folder") +parser.add_argument( + "output_folder", + default=pathlib.Path("validator_keys/"), + nargs="?", + help="Folder where to store deposit data", +) + + +def main(): + args = parser.parse_args() + base_path = args.output_folder + if type(base_path) == str: + base_path = pathlib.Path(base_path) + assert base_path.exists(), "Output folder must exist" + inp = sys.stdin.read() + assert inp, "Should have value at standard input" + try: + validators = json.loads(inp) + except (KeyError, ValueError) as exc: + logger.exception("Got invalid json input") + else: + assert validators, "Should have non-empty JSON value passed" + assert ( + "keystores" in validators + ), "Should have non-empty keystores in eth-staking-smith output" + deposit_datas = validators["deposit_data"] + num_validators = len(deposit_datas) + print(f"Exporting {num_validators} keystores to {base_path}") + # Set path as EIP-2334 format + # https://eips.ethereum.org/EIPS/eip-2334 + for idx, keystore in enumerate(validators["keystores"]): + ts = int(time.time()) + purpose = "12381" + coin_type = "3600" + account = str(idx) + withdrawal_key_path = f"m/{purpose}/{coin_type}/{account}/0" + signing_key_path = f"{withdrawal_key_path}/0" + file_path = signing_key_path.replace("/", "_") + filename = f"keystore-{file_path}-{ts}.json" + with open(base_path / filename, "w") as fl: + fl.write(json.dumps(keystore)) + + with open(base_path / f"deposit_data-{ts}.json", "w") as fl: + fl.write(json.dumps(deposit_datas)) + + +if __name__ == "__main__": + logging.basicConfig(stream=sys.stdout, level=logging.WARN) + main()