mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-10-05 23:57:46 +03:00
154 lines
4.6 KiB
Python
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()
|