Skip to content

Commit

Permalink
util: Remove legacy keyring support (Chia-Network#13398)
Browse files Browse the repository at this point in the history
  • Loading branch information
xdustinface authored Nov 18, 2022
1 parent 22a1d1b commit 2e2c297
Show file tree
Hide file tree
Showing 18 changed files with 26 additions and 964 deletions.
1 change: 0 additions & 1 deletion build_scripts/check_dependency_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import tempfile

excepted_packages = {
"keyrings.cryptfile", # pure python
"dnslib", # pure python
}

Expand Down
7 changes: 0 additions & 7 deletions chia/cmds/chia.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,17 @@
"--keys-root-path", default=DEFAULT_KEYS_ROOT_PATH, help="Keyring file root", type=click.Path(), show_default=True
)
@click.option("--passphrase-file", type=click.File("r"), help="File or descriptor to read the keyring passphrase from")
@click.option(
"--force-legacy-keyring-migration/--no-force-legacy-keyring-migration",
default=True,
help="Force legacy keyring migration. Legacy keyring support will be removed in an upcoming version!",
)
@click.pass_context
def cli(
ctx: click.Context,
root_path: str,
keys_root_path: Optional[str] = None,
passphrase_file: Optional[TextIOWrapper] = None,
force_legacy_keyring_migration: bool = True,
) -> None:
from pathlib import Path

ctx.ensure_object(dict)
ctx.obj["root_path"] = Path(root_path)
ctx.obj["force_legacy_keyring_migration"] = force_legacy_keyring_migration

# keys_root_path and passphrase_file will be None if the passphrase options have been
# scrubbed from the CLI options
Expand Down
15 changes: 0 additions & 15 deletions chia/cmds/keys.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import asyncio
import sys
from typing import Optional, Tuple

import click
Expand All @@ -13,15 +11,10 @@ def keys_cmd(ctx: click.Context):
"""Create, delete, view and use your key pairs"""
from pathlib import Path

from .keys_funcs import migrate_keys

root_path: Path = ctx.obj["root_path"]
if not root_path.is_dir():
raise RuntimeError("Please initialize (or migrate) your config directory with chia init")

if ctx.obj["force_legacy_keyring_migration"] and not asyncio.run(migrate_keys(root_path, True)):
sys.exit(1)


@keys_cmd.command("generate", short_help="Generates and adds a key to keychain")
@click.option(
Expand Down Expand Up @@ -226,14 +219,6 @@ def verify_cmd(message: str, public_key: str, signature: str):
verify(message, public_key, signature)


@keys_cmd.command("migrate", short_help="Attempt to migrate keys to the Chia keyring")
@click.pass_context
def migrate_cmd(ctx: click.Context):
from .keys_funcs import migrate_keys

asyncio.run(migrate_keys(ctx.obj["root_path"]))


@keys_cmd.group("derive", short_help="Derive child keys or wallet addresses")
@click.option(
"--fingerprint",
Expand Down
91 changes: 1 addition & 90 deletions chia/cmds/keys_funcs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import json
import logging
import os
import sys
from enum import Enum
Expand All @@ -12,11 +11,9 @@

from chia.cmds.passphrase_funcs import obtain_current_passphrase
from chia.consensus.coinbase import create_puzzlehash_for_pk
from chia.daemon.client import connect_to_daemon_and_validate
from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate, wrap_local_keychain
from chia.util.bech32m import encode_puzzle_hash
from chia.util.config import load_config
from chia.util.errors import KeychainException, KeychainNotSet
from chia.util.errors import KeychainException
from chia.util.file_keyring import MAX_LABEL_LENGTH
from chia.util.ints import uint32
from chia.util.keychain import Keychain, bytes_to_mnemonic, generate_mnemonic, mnemonic_to_seed
Expand Down Expand Up @@ -268,92 +265,6 @@ def verify(message: str, public_key: str, signature: str):
print(AugSchemeMPL.verify(public_key, messageBytes, signature))


async def migrate_keys(root_path: Path, forced: bool = False) -> bool:
from chia.util.keyring_wrapper import KeyringWrapper
from chia.util.misc import prompt_yes_no

deprecation_message = (
"\nLegacy keyring support is deprecated and will be removed in an upcoming version. "
"You need to migrate your keyring to continue using Chia.\n"
)

# Check if the keyring needs a full migration (i.e. if it's using the old keyring)
if Keychain.needs_migration():
print(deprecation_message)
return await KeyringWrapper.get_shared_instance().migrate_legacy_keyring_interactive()
else:
already_checked_marker = KeyringWrapper.get_shared_instance().keys_root_path / ".checked_legacy_migration"
if forced and already_checked_marker.exists():
return True

log = logging.getLogger("migrate_keys")
config = load_config(root_path, "config.yaml")
# Connect to the daemon here first to see if ts running since `connect_to_keychain_and_validate` just tries to
# connect forever if it's not up.
keychain_proxy: Optional[KeychainProxy] = None
daemon = await connect_to_daemon_and_validate(root_path, config, quiet=True)
if daemon is not None:
await daemon.close()
keychain_proxy = await connect_to_keychain_and_validate(root_path, log)
if keychain_proxy is None:
keychain_proxy = wrap_local_keychain(Keychain(), log=log)

try:
legacy_keyring = Keychain(force_legacy=True)
all_sks = await keychain_proxy.get_all_private_keys()
all_legacy_sks = legacy_keyring.get_all_private_keys()
set_legacy_sks = {str(x[0]) for x in all_legacy_sks}
set_sks = {str(x[0]) for x in all_sks}
missing_legacy_keys = set_legacy_sks - set_sks
keys_to_migrate = [x for x in all_legacy_sks if str(x[0]) in missing_legacy_keys]
except KeychainNotSet:
keys_to_migrate = []

if len(keys_to_migrate) > 0:
print(deprecation_message)
print(f"Found {len(keys_to_migrate)} key(s) that need migration:")
for key, _ in keys_to_migrate:
print(f"Fingerprint: {key.get_g1().get_fingerprint()}")

print()
if not prompt_yes_no("Migrate these keys?"):
await keychain_proxy.close()
print("Migration aborted, can't run any chia commands.")
return False

for sk, seed_bytes in keys_to_migrate:
mnemonic = bytes_to_mnemonic(seed_bytes)
await keychain_proxy.add_private_key(mnemonic)
fingerprint = sk.get_g1().get_fingerprint()
print(f"Added private key with public key fingerprint {fingerprint}")

print(f"Migrated {len(keys_to_migrate)} key(s)")

print("Verifying migration results...", end="")
all_sks = await keychain_proxy.get_all_private_keys()
await keychain_proxy.close()
set_sks = {str(x[0]) for x in all_sks}
keys_present = set_sks.issuperset(set(map(lambda x: str(x[0]), keys_to_migrate)))
if keys_present:
print(" Verified")
print()
response = prompt_yes_no("Remove key(s) from old keyring (recommended)?")
if response:
legacy_keyring.delete_keys(keys_to_migrate)
print(f"Removed {len(keys_to_migrate)} key(s) from old keyring")
print("Migration complete")
else:
print(" Failed")
return False
return True
elif not forced:
print("No keys need migration")
if already_checked_marker.parent.exists():
already_checked_marker.touch()
await keychain_proxy.close()
return True


def _clear_line_part(n: int):
# Move backward, overwrite with spaces, then move backward again
sys.stdout.write("\b" * n)
Expand Down
8 changes: 2 additions & 6 deletions chia/cmds/passphrase.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@


@click.group("passphrase", short_help="Manage your keyring passphrase")
@click.pass_context
def passphrase_cmd(ctx: click.Context):
from .keys_funcs import migrate_keys

if ctx.obj["force_legacy_keyring_migration"] and not asyncio.run(migrate_keys(ctx.obj["root_path"], True)):
sys.exit(1)
def passphrase_cmd():
pass


@passphrase_cmd.command(
Expand Down
26 changes: 0 additions & 26 deletions chia/cmds/passphrase_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

import click
import colorama

from chia.daemon.client import acquire_connection_to_daemon
from chia.util.config import load_config
from chia.util.errors import KeychainMaxUnlockAttempts
from chia.util.keychain import Keychain, supports_os_passphrase_storage
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, KeyringWrapper
from chia.util.misc import prompt_yes_no
from chia.util.ws_message import WsRpcMessage

DEFAULT_PASSPHRASE_PROMPT = (
colorama.Fore.YELLOW + colorama.Style.BRIGHT + "(Unlock Keyring)" + colorama.Style.RESET_ALL + " Passphrase: "
Expand Down Expand Up @@ -348,26 +345,3 @@ async def async_update_daemon_passphrase_cache_if_running(root_path: Path, confi
raise Exception(error)
except Exception as e:
print(f"Failed to notify daemon of updated keyring passphrase: {e}")


async def async_update_daemon_migration_completed_if_running() -> None:
"""
Attempt to connect to the daemon to notify that keyring migration has completed.
This allows the daemon to refresh its keyring so that it can stop using the
legacy keyring.
"""
ctx: click.Context = click.get_current_context()
root_path: Path = ctx.obj["root_path"]

if root_path is None:
print("Missing root_path in context. Unable to notify daemon")
return None

async with acquire_connection_to_daemon(root_path, load_config(root_path, "config.yaml"), quiet=True) as daemon:
if daemon is not None:
passphrase: str = Keychain.get_cached_master_passphrase()

print("Updating daemon... ", end="")
response: WsRpcMessage = await daemon.notify_keyring_migration_completed(passphrase)
success: bool = response.get("data", {}).get("success", False)
print("succeeded" if success is True else "failed")
2 changes: 1 addition & 1 deletion chia/cmds/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ def start_cmd(ctx: click.Context, restart: bool, group: str) -> None:
root_path = ctx.obj["root_path"]
config = load_config(root_path, "config.yaml")
warn_if_beta_enabled(config)
asyncio.run(async_start(root_path, config, group, restart, ctx.obj["force_legacy_keyring_migration"]))
asyncio.run(async_start(root_path, config, group, restart))
10 changes: 1 addition & 9 deletions chia/cmds/start_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from pathlib import Path
from typing import Any, Dict, Optional

from chia.cmds.keys_funcs import migrate_keys
from chia.cmds.passphrase_funcs import get_current_passphrase
from chia.daemon.client import DaemonProxy, connect_to_daemon_and_validate
from chia.util.errors import KeychainMaxUnlockAttempts
Expand Down Expand Up @@ -51,9 +50,7 @@ async def create_start_daemon_connection(root_path: Path, config: Dict[str, Any]
return None


async def async_start(
root_path: Path, config: Dict[str, Any], group: str, restart: bool, force_keyring_migration: bool
) -> None:
async def async_start(root_path: Path, config: Dict[str, Any], group: str, restart: bool) -> None:
try:
daemon = await create_start_daemon_connection(root_path, config)
except KeychainMaxUnlockAttempts:
Expand All @@ -64,11 +61,6 @@ async def async_start(
print("Failed to create the chia daemon")
return None

if force_keyring_migration:
if not await migrate_keys(root_path, True):
await daemon.close()
sys.exit(1)

for service in services_for_groups(group):
if await daemon.is_running(service_name=service):
print(f"{service}: ", end="", flush=True)
Expand Down
6 changes: 0 additions & 6 deletions chia/daemon/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,6 @@ async def unlock_keyring(self, passphrase: str) -> WsRpcMessage:
response = await self._get(request)
return response

async def notify_keyring_migration_completed(self, passphrase: Optional[str]) -> WsRpcMessage:
data: Dict[str, Any] = {"key": passphrase}
request: WsRpcMessage = self.format_request("notify_keyring_migration_completed", data)
response: WsRpcMessage = await self._get(request)
return response

async def ping(self) -> WsRpcMessage:
request = self.format_request("ping", {})
response = await self._get(request)
Expand Down
Loading

0 comments on commit 2e2c297

Please sign in to comment.