xch-blockchain/tools/legacy_keyring.py

154 lines
4.6 KiB
Python

"""
Provides a helper to access the legacy keyring which was supported up to version 1.6.1 of chia-blockchain. To use this
helper it's required to install the `legacy_keyring` extra dependency which can be done via the install-option `-l`.
"""
from __future__ import annotations
import sys
from typing import Callable, List, Union, cast
import click
from blspy import G1Element
from keyring.backends.macOS import Keyring as MacKeyring
from keyring.backends.Windows import WinVaultKeyring as WinKeyring
try:
from keyrings.cryptfile.cryptfile import CryptFileKeyring
except ImportError:
if sys.platform == "linux":
sys.exit("Use `install.sh -l` to install the legacy_keyring dependency.")
CryptFileKeyring = None
from chia.util.errors import KeychainUserNotFound
from chia.util.keychain import KeyData, KeyDataSecrets, get_private_key_user
from chia.util.misc import prompt_yes_no
LegacyKeyring = Union[MacKeyring, WinKeyring, CryptFileKeyring]
CURRENT_KEY_VERSION = "1.8"
DEFAULT_USER = f"user-chia-{CURRENT_KEY_VERSION}" # e.g. user-chia-1.8
DEFAULT_SERVICE = f"chia-{DEFAULT_USER}" # e.g. chia-user-chia-1.8
MAX_KEYS = 100
# casting to compensate for a combination of mypy and keyring issues
# https://github.com/python/mypy/issues/9025
# https://github.com/jaraco/keyring/issues/437
def create_legacy_keyring() -> LegacyKeyring:
if sys.platform == "darwin":
return cast(Callable[[], LegacyKeyring], MacKeyring)()
elif sys.platform == "win32" or sys.platform == "cygwin":
return cast(Callable[[], LegacyKeyring], WinKeyring)()
elif sys.platform == "linux":
keyring: CryptFileKeyring = CryptFileKeyring()
keyring.keyring_key = "your keyring password"
return keyring
raise click.ClickException(f"platform '{sys.platform}' not supported.")
def generate_and_add(keyring: LegacyKeyring) -> KeyData:
key = KeyData.generate()
index = 0
while True:
try:
get_key_data(keyring, index)
index += 1
except KeychainUserNotFound:
keyring.set_password(
DEFAULT_SERVICE,
get_private_key_user(DEFAULT_USER, index),
bytes(key.public_key).hex() + key.entropy.hex(),
)
return key
def get_key_data(keyring: LegacyKeyring, index: int) -> KeyData:
user = get_private_key_user(DEFAULT_USER, index)
read_str = keyring.get_password(DEFAULT_SERVICE, user)
if read_str is None or len(read_str) == 0:
raise KeychainUserNotFound(DEFAULT_SERVICE, user)
str_bytes = bytes.fromhex(read_str)
public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE])
fingerprint = public_key.get_fingerprint()
entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32]
return KeyData(
fingerprint=fingerprint,
public_key=public_key,
label=None,
secrets=KeyDataSecrets.from_entropy(entropy),
)
def get_keys(keyring: LegacyKeyring) -> List[KeyData]:
keys: List[KeyData] = []
for index in range(MAX_KEYS + 1):
try:
keys.append(get_key_data(keyring, index))
except KeychainUserNotFound:
pass
return keys
def print_key(key: KeyData) -> None:
print(f"fingerprint: {key.fingerprint}, mnemonic: {key.mnemonic_str()}")
def print_keys(keyring: LegacyKeyring) -> None:
keys = get_keys(keyring)
if len(keys) == 0:
raise click.ClickException("No keys found in the legacy keyring.")
for key in keys:
print_key(key)
def remove_keys(keyring: LegacyKeyring) -> None:
removed = 0
for index in range(MAX_KEYS + 1):
try:
keyring.delete_password(DEFAULT_SERVICE, get_private_key_user(DEFAULT_USER, index))
removed += 1
except Exception:
pass
print(f"{removed} key{'s' if removed != 1 else ''} removed.")
@click.group(help="Manage the keys in the legacy keyring.")
def legacy_keyring() -> None:
pass
@legacy_keyring.command(help="Generate and add a random key (for testing)", hidden=True)
def generate() -> None:
keyring = create_legacy_keyring()
key = generate_and_add(keyring)
print_key(key)
@legacy_keyring.command(help="Show all available keys")
def show() -> None:
print_keys(create_legacy_keyring())
@legacy_keyring.command(help="Remove all keys")
def clear() -> None:
keyring = create_legacy_keyring()
print_keys(keyring)
if not prompt_yes_no("\nDo you really want to remove all the keys from the legacy keyring? This can't be undone."):
raise click.ClickException("Aborted!")
remove_keys(keyring)
if __name__ == "__main__":
legacy_keyring()