Files
chia-blockchain/tests/core/cmds/test_keys.py
T
JeffandGitHub 99275e6e37 Support searching derived addresses on testnet. (#11449)
* Support searching derived addresses on testnet.

* Added tests
2022-05-16 13:29:33 -07:00

1166 lines
40 KiB
Python

import os
import pkg_resources
import pytest
import re
from blspy import PrivateKey
from chia.cmds.chia import cli
from chia.cmds.keys import delete_all_cmd, generate_and_print_cmd, show_cmd, sign_cmd, verify_cmd
from chia.util.config import load_config
from chia.util.file_keyring import FileKeyring
from chia.util.keychain import DEFAULT_USER, DEFAULT_SERVICE, Keychain, generate_mnemonic
from chia.util.keyring_wrapper import DEFAULT_KEYS_ROOT_PATH, KeyringWrapper, LegacyKeyring
from click.testing import CliRunner, Result
from keyring.backend import KeyringBackend
from pathlib import Path
from tests.util.keyring import TempKeyring
from typing import Dict, List, Optional, Tuple
TEST_MNEMONIC_SEED = (
"grief lock ketchup video day owner torch young work "
"another venue evidence spread season bright private "
"tomato remind jaguar original blur embody project can"
)
TEST_FINGERPRINT = 2877570395
class DummyLegacyKeyring(KeyringBackend):
# Fingerprint 2474840988
KEY_0 = (
"89e29e5f9c3105b2a853475cab2392468cbfb1d65c3faabea8ebc78fe903fd279e56a8d93f6325fc6c3d833a2ae74832"
"b8feaa3d6ee49998f43ce303b66dcc5abb633e5c1d80efe85c40766135e4a44c"
)
# Fingerprint 4149609062
KEY_1 = (
"8b0d72288727af6238fcd9b0a663cd7d4728738fca597d0046cbb42b6432e0a5ae8026683fc5f9c73df26fb3e1cec2c8"
"ad1b4f601107d96a99f6fa9b9d2382918fb1e107fb6655c7bdd8c77c1d9c201f"
)
# Fingerprint 3618811800
KEY_2 = (
"8b2a26ba319f83bd3da5b1b147a817ecc4ca557f037c9db1cfedc59b16ee6880971b7d292f023358710a292c8db0eb82"
"35808f914754ae24e493fad9bc7f654b0f523fb406973af5235256a39bed1283"
)
def __init__(self, populate: bool = True):
self.service_dict = {}
if populate:
self.service_dict[DEFAULT_SERVICE] = {
f"wallet-{DEFAULT_USER}-0": DummyLegacyKeyring.KEY_0,
f"wallet-{DEFAULT_USER}-1": DummyLegacyKeyring.KEY_1,
f"wallet-{DEFAULT_USER}-2": DummyLegacyKeyring.KEY_2,
}
def get_password(self, service, username, password=None):
return self.service_dict.get(service, {}).get(username)
def set_password(self, service, username, password):
self.service_dict.setdefault(service, {})[username] = password
def delete_password(self, service, username):
del self.service_dict[service][username]
@pytest.fixture(scope="function")
def empty_keyring():
with TempKeyring(user="user-chia-1.8", service="chia-user-chia-1.8") as keychain:
yield keychain
KeyringWrapper.cleanup_shared_instance()
@pytest.fixture(scope="function")
def keyring_with_one_key(empty_keyring):
keychain = empty_keyring
keychain.add_private_key(TEST_MNEMONIC_SEED, "")
return keychain
@pytest.fixture(scope="function")
def mnemonic_seed_file(tmp_path):
seed_file = Path(tmp_path) / "seed.txt"
with open(seed_file, "w") as f:
f.write(TEST_MNEMONIC_SEED)
return seed_file
@pytest.fixture(scope="function")
def setup_keyringwrapper(tmp_path):
KeyringWrapper.cleanup_shared_instance()
KeyringWrapper.set_keys_root_path(tmp_path)
_ = KeyringWrapper.get_shared_instance()
yield
KeyringWrapper.cleanup_shared_instance()
KeyringWrapper.set_keys_root_path(DEFAULT_KEYS_ROOT_PATH)
@pytest.fixture(scope="function")
def setup_legacy_keyringwrapper(tmp_path, monkeypatch):
def mock_setup_keyring_file_watcher(_):
pass
# Silence errors in the watchdog module during testing
monkeypatch.setattr(FileKeyring, "setup_keyring_file_watcher", mock_setup_keyring_file_watcher)
KeyringWrapper.cleanup_shared_instance()
KeyringWrapper.set_keys_root_path(tmp_path)
KeyringWrapper.get_shared_instance().legacy_keyring = DummyLegacyKeyring()
yield
KeyringWrapper.cleanup_shared_instance()
KeyringWrapper.set_keys_root_path(DEFAULT_KEYS_ROOT_PATH)
class TestKeysCommands:
def test_generate_with_new_config(self, tmp_path, empty_keyring):
"""
Generate a new config and a new key. Verify that the config has
the correct xch_target_address entries.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
# Generate the new config
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
# Generate a new key
runner = CliRunner()
result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "keys", "generate"]
)
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
# Verify that the config has the correct xch_target_address entries
address_matches = re.findall(r"xch1[^\n]+", result.output)
assert len(address_matches) > 1
address = address_matches[0]
config: Dict = load_config(tmp_path, "config.yaml")
assert config["farmer"]["xch_target_address"] == address
assert config["pool"]["xch_target_address"] == address
def test_generate_with_existing_config(self, tmp_path, empty_keyring):
"""
Generate a new key using an existing config. Verify that the config has
the original xch_target_address entries.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
# Generate the new config
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
# Generate the first key
runner = CliRunner()
generate_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "keys", "generate"]
)
assert generate_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
# Verify that the config has the correct xch_target_address entries
address_matches = re.findall(r"xch1[^\n]+", generate_result.output)
assert len(address_matches) > 1
address = address_matches[0]
existing_config: Dict = load_config(tmp_path, "config.yaml")
assert existing_config["farmer"]["xch_target_address"] == address
assert existing_config["pool"]["xch_target_address"] == address
# Generate the second key
runner = CliRunner()
result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "keys", "generate"]
)
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 2
# Verify that the config's xch_target_address entries have not changed
config: Dict = load_config(tmp_path, "config.yaml")
assert config["farmer"]["xch_target_address"] == existing_config["farmer"]["xch_target_address"]
assert config["pool"]["xch_target_address"] == existing_config["pool"]["xch_target_address"]
def test_show(self, keyring_with_one_key):
"""
Test that the `chia keys show` command shows the correct key.
"""
keychain = keyring_with_one_key
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(show_cmd, [])
# assert result.exit_code == 0
assert result.output.find(f"Fingerprint: {TEST_FINGERPRINT}") != 0
def test_show_mnemonic(self, keyring_with_one_key):
"""
Test that the `chia keys show --show-mnemonic-seed` command shows the key's mnemonic seed.
"""
keychain = keyring_with_one_key
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(show_cmd, ["--show-mnemonic-seed"])
# assert result.exit_code == 0
assert result.output.find(f"Fingerprint: {TEST_FINGERPRINT}") != 0
assert result.output.find("Mnemonic: seed (24 secret words):") != 0
assert result.output.find(TEST_MNEMONIC_SEED) != 0
def test_add_interactive(self, tmp_path, empty_keyring):
"""
Test adding a key from mnemonic seed using the interactive prompt.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
runner = CliRunner()
result: Result = runner.invoke(
cli,
["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "keys", "add"],
input=f"{TEST_MNEMONIC_SEED}\n",
)
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
def test_add_from_mnemonic_seed(self, tmp_path, empty_keyring, mnemonic_seed_file):
"""
Test adding a key from a mnemonic seed file using the `--filename` flag.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"add",
"--filename",
os.fspath(mnemonic_seed_file),
],
)
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
def test_delete(self, tmp_path, empty_keyring, mnemonic_seed_file):
"""
Test deleting a key using the `--fingerprint` option.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
runner = CliRunner()
add_result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"add",
"--filename",
os.fspath(mnemonic_seed_file),
],
)
assert add_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"delete",
"--fingerprint",
TEST_FINGERPRINT,
],
)
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
def test_delete_all(self, empty_keyring):
"""
Test deleting all keys from the keyring
"""
keychain = empty_keyring
assert len(keychain.get_all_private_keys()) == 0
for i in range(5):
mnemonic: str = generate_mnemonic()
keychain.add_private_key(mnemonic, "")
assert len(keychain.get_all_private_keys()) == 5
runner = CliRunner()
result: Result = runner.invoke(delete_all_cmd, [])
assert result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
def test_generate_and_print(self):
"""
Test the `chia keys generate_and_print` command.
"""
runner = CliRunner()
result: Result = runner.invoke(generate_and_print_cmd, [])
assert result.exit_code == 0
assert result.output.find("Mnemonic (24 secret words):") != 0
def test_sign(self, keyring_with_one_key):
"""
Test the `chia keys sign` command.
"""
message: str = "hello world"
hd_path: str = "m/12381/8444/0/1"
runner = CliRunner()
result: Result = runner.invoke(
sign_cmd, ["--message", message, "--fingerprint", str(TEST_FINGERPRINT), "--hd_path", hd_path]
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Public key: 92f15caed8a5495faa7ec25a8af3f223438ef73c974b0aa81e788057b1154870f149739b2c2d0e"
"736234baf9386f7f83"
)
)
!= -1
)
assert (
result.output.find(
(
"Signature: a82e7d1b87d8c25a6ccac603194011d73f71fc76c17c1ce4ee53484f81874f116b1cb9dd991bcf9"
"aa41c10beaab54a830fc6f7e5e25a9144f73e38a6fb852a87e36d80f575a6f84359144e6e9499ba9208912de55"
"a1f7514cd8cfa166ae48e64"
)
)
!= -1
)
def test_sign_non_observer(self, keyring_with_one_key):
"""
Test the `chia keys sign` command with a non-observer key.
"""
message: str = "hello world"
hd_path: str = "m/12381n/8444n/0n/1n"
runner = CliRunner()
result: Result = runner.invoke(
sign_cmd, ["--message", message, "--fingerprint", str(TEST_FINGERPRINT), "--hd_path", hd_path]
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Public key: b5e383b8192dacff662455bdb3bbfc433f678f0d7ff7f118149e0d2ad39aa6d59ac4cb3662acf8"
"e8307e66069d3a13cc"
)
)
) != -1
assert (
result.output.find(
(
"Signature: b5b3bc1417f67498748018a7ad2c95acfc5ae2dcd0d9dd0f3abfc7e3f047f2e6cf6c3e775b6caff"
"a3e0baaadc2fe705a100cd4c961d6ff3c575c5c33683eb7b1e2dbbcaf37318227ae40ef8ccf57879a7818fad8f"
"dc573d55c908be2611b8077"
)
)
) != -1
def test_sign_mnemonic_seed_file(self, empty_keyring, mnemonic_seed_file):
"""
Test signing a message using a key imported from a mnemonic seed file.
"""
message: str = "hello world"
hd_path: str = "m/12381/8444/0/1"
runner = CliRunner()
result: Result = runner.invoke(
sign_cmd,
[
"--message",
message,
"--hd_path",
hd_path,
"--mnemonic-seed-filename",
mnemonic_seed_file,
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Public key: "
"92f15caed8a5495faa7ec25a8af3f223438ef73c974b0aa81e788057b1154870f149739b2c2d0e736234baf9386f7f83"
)
)
!= -1
)
assert (
result.output.find(
(
"Signature: a82e7d1b87d8c25a6ccac603194011d73f71fc76c17c1ce4ee53484f81874f116b1cb9dd991bcf"
"9aa41c10beaab54a830fc6f7e5e25a9144f73e38a6fb852a87e36d80f575a6f84359144e6e9499ba9208912de"
"55a1f7514cd8cfa166ae48e64"
)
)
!= -1
)
def test_verify(self):
"""
Test the `chia keys verify` command.
"""
message: str = "hello world"
signature: str = (
"a82e7d1b87d8c25a6ccac603194011d73f71fc76c17c1ce4ee53484f81874f116b1cb9dd991bcf9aa41c10beaab54a83"
"0fc6f7e5e25a9144f73e38a6fb852a87e36d80f575a6f84359144e6e9499ba9208912de55a1f7514cd8cfa166ae48e64"
)
public_key: str = (
"92f15caed8a5495faa7ec25a8af3f223438ef73c974b0aa81e788057b1154870f149739b2c2d0e736234baf9386f7f83"
)
runner = CliRunner()
result: Result = runner.invoke(
verify_cmd, ["--message", message, "--public_key", public_key, "--signature", signature]
)
assert result.exit_code == 0
assert result.output.find("True") == 0
def test_derive_search(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive search` command, searching a public and private key
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"search",
"--limit",
"10",
"--search-type",
"all",
"a4601f992f24047097a30854ef656382911575694439108723698972941e402d737c13df76fdf43597f7b3c2fa9ed27a",
"028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Found public key: a4601f992f24047097a30854ef656382911575694439108723698"
"972941e402d737c13df76fdf43597f7b3c2fa9ed27a (HD path: m/12381/8444/2/9)"
)
)
!= -1
)
assert (
result.output.find(
(
"Found private key: "
"028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36 (HD path: m/12381/8444/2/9)"
)
)
!= -1
)
def test_derive_search_wallet_address(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive search` command, searching for a wallet address
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"search",
"--limit",
"40",
"--search-type",
"address",
"xch1mnr0ygu7lvmk3nfgzmncfk39fwu0dv933yrcv97nd6pmrt7fzmhs8taffd",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Found wallet address: "
"xch1mnr0ygu7lvmk3nfgzmncfk39fwu0dv933yrcv97nd6pmrt7fzmhs8taffd (HD path: m/12381/8444/2/30)"
)
)
!= -1
)
def test_derive_search_wallet_testnet_address(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive search` command, searching for a testnet wallet address
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"search",
"--limit",
"40",
"--search-type",
"address",
"txch1mnr0ygu7lvmk3nfgzmncfk39fwu0dv933yrcv97nd6pmrt7fzmhs2v6lg7",
"--prefix",
"txch",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Found wallet address: "
"txch1mnr0ygu7lvmk3nfgzmncfk39fwu0dv933yrcv97nd6pmrt7fzmhs2v6lg7 (HD path: m/12381/8444/2/30)"
)
)
!= -1
)
def test_derive_search_failure(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive search` command with a failing search.
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"search",
"--limit",
"10",
"--search-type",
"all",
"something_that_doesnt_exist",
],
)
assert result.exit_code != 0
def test_derive_search_hd_path(self, tmp_path, empty_keyring, mnemonic_seed_file):
"""
Test the `chia keys derive search` command, searching under a provided HD path.
"""
keychain = empty_keyring
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 0
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--mnemonic-seed-filename",
os.fspath(mnemonic_seed_file),
"search",
"--limit",
"50",
"--search-type",
"all",
"--derive-from-hd-path",
"m/12381n/8444n/2/",
"80dc3a2ea450eb09e24debe22e1b5934911ba530792ef0be361badebb168780bd328ff8d4655e5dd573d5bef4a340344",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Found public key: 80dc3a2ea450eb09e24debe22e1b5934911ba530792ef0be361bad"
"ebb168780bd328ff8d4655e5dd573d5bef4a340344 (HD path: m/12381n/8444n/2/35)"
)
)
!= -1
)
def test_derive_wallet_address(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive wallet-address` command, generating a couple of wallet addresses.
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"wallet-address",
"--index",
"50",
"--count",
"2",
"--non-observer-derivation",
"--show-hd-path",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Wallet address 50 (m/12381n/8444n/2n/50n): "
"xch1jp2u7an0mn9hdlw2x05nmje49gwgzmqyvh0qmh6008yksetuvkfs6wrfdq"
)
)
!= -1
)
assert (
result.output.find(
(
"Wallet address 51 (m/12381n/8444n/2n/51n): "
"xch1006n6l3x5e8exar8mlj004znjl5pq0tq73h76kz0yergswnjzn8sumvfmt"
)
)
!= -1
)
def test_derive_wallet_testnet_address(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive wallet-address` command, generating a couple of testnet wallet addresses.
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"wallet-address",
"--index",
"50",
"--count",
"2",
"--non-observer-derivation",
"--show-hd-path",
"--prefix",
"txch",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Wallet address 50 (m/12381n/8444n/2n/50n): "
"txch1jp2u7an0mn9hdlw2x05nmje49gwgzmqyvh0qmh6008yksetuvkfshfylvn"
)
)
!= -1
)
assert (
result.output.find(
(
"Wallet address 51 (m/12381n/8444n/2n/51n): "
"txch1006n6l3x5e8exar8mlj004znjl5pq0tq73h76kz0yergswnjzn8s3utl6c"
)
)
!= -1
)
def test_derive_child_keys(self, tmp_path, keyring_with_one_key):
"""
Test the `chia keys derive child-keys` command, generating a couple of derived keys.
"""
keychain = keyring_with_one_key
keys_root_path = keychain.keyring_wrapper.keys_root_path
runner = CliRunner()
init_result: Result = runner.invoke(
cli, ["--root-path", os.fspath(tmp_path), "--keys-root-path", os.fspath(keys_root_path), "init"]
)
assert init_result.exit_code == 0
assert len(keychain.get_all_private_keys()) == 1
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"--keys-root-path",
os.fspath(keys_root_path),
"keys",
"derive",
"--fingerprint",
str(TEST_FINGERPRINT),
"child-key",
"--derive-from-hd-path",
"m/12381n/8444n/2/3/4/",
"--index",
"30",
"--count",
"2",
"--show-private-keys",
"--show-hd-path",
],
)
assert result.exit_code == 0
assert (
result.output.find(
(
"Observer public key 30 (m/12381n/8444n/2/3/4/30): "
"979a1fa0bfc140488d4a9edcfbf244a398fe922618a981cc0fffe5445d811f2237ff8234c0520b28b3096c8269f2731e"
)
)
!= -1
)
assert (
result.output.find(
(
"Observer private key 30 (m/12381n/8444n/2/3/4/30): "
"5dd22db24fe28805b101104c543f5bec3808328ad67de3d3dcd9efd6faab13aa"
)
)
!= -1
)
assert (
result.output.find(
(
"Observer public key 31 (m/12381n/8444n/2/3/4/31): "
"ab5885df340a27b5eb3f1c4b8c32889f529ad5ecc4c9718247e36756de2e143c604af9956941a72239124e6fb352782e"
)
)
!= -1
)
assert (
result.output.find(
(
"Observer private key 31 (m/12381n/8444n/2/3/4/31): "
"113610b39c2151fd68d7f795d5dd596b94889a3cf7825a56da5c6d2c7e5141a1"
)
)
!= -1
)
def test_migration_not_needed(self, tmp_path, setup_keyringwrapper, monkeypatch):
"""
Test the `chia keys migrate` command when no migration is necessary
"""
def mock_keychain_needs_migration() -> bool:
return False
monkeypatch.setattr(Keychain, "needs_migration", mock_keychain_needs_migration)
def mock_keychain_get_keys_needing_migration() -> Tuple[List[Tuple[PrivateKey, bytes]], Optional[Keychain]]:
return [], None
monkeypatch.setattr(Keychain, "get_keys_needing_migration", mock_keychain_get_keys_needing_migration)
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"keys",
"migrate",
],
)
assert result.exit_code == 0
assert result.output.find("No keys need migration") != -1
def test_migration_full(self, tmp_path, setup_legacy_keyringwrapper):
"""
Test the `chia keys migrate` command when a full migration is needed
"""
legacy_keyring = KeyringWrapper.get_shared_instance().legacy_keyring
assert legacy_keyring is not None
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 3
runner = CliRunner()
init_result: Result = runner.invoke(
cli,
["--root-path", os.fspath(tmp_path), "init"],
)
assert init_result.exit_code == 0
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"keys",
"migrate",
],
input="n\ny\ny\n", # Prompts: 'n' = don't set a passphrase, 'y' = begin migration, 'y' = remove legacy keys
)
assert result.exit_code == 0
assert KeyringWrapper.get_shared_instance().using_legacy_keyring() is False # legacy keyring unset
assert type(KeyringWrapper.get_shared_instance().keyring) is FileKeyring # new keyring set
assert len(Keychain().get_all_public_keys()) == 3 # new keyring has 3 keys
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 0 # legacy keys removed
current_version_str = pkg_resources.get_distribution("chia-blockchain").version
last_migration_version_str = (
KeyringWrapper.get_shared_instance().keys_root_path / ".last_legacy_migration"
).read_text()
assert last_migration_version_str == current_version_str # last migration version set
def test_migration_incremental(self, tmp_path, keyring_with_one_key, monkeypatch):
KeyringWrapper.set_keys_root_path(tmp_path)
KeyringWrapper.cleanup_shared_instance()
keychain = keyring_with_one_key
legacy_keyring = DummyLegacyKeyring()
def mock_get_legacy_keyring_instance() -> Optional[LegacyKeyring]:
nonlocal legacy_keyring
return legacy_keyring
from chia.util import keyring_wrapper
monkeypatch.setattr(keyring_wrapper, "get_legacy_keyring_instance", mock_get_legacy_keyring_instance)
assert len(keychain.get_all_private_keys()) == 1
assert keychain.keyring_wrapper.legacy_keyring is None
assert legacy_keyring is not None
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 3
runner = CliRunner()
init_result: Result = runner.invoke(
cli,
["--root-path", os.fspath(tmp_path), "init"],
)
assert init_result.exit_code == 0
runner = CliRunner()
result: Result = runner.invoke(
cli,
[
"--root-path",
os.fspath(tmp_path),
"keys",
"migrate",
],
input="y\ny\n", # Prompts: 'y' = migrate keys, 'y' = remove legacy keys
)
assert result.exit_code == 0
assert KeyringWrapper.get_shared_instance().using_legacy_keyring() is False # legacy keyring is not set
assert type(KeyringWrapper.get_shared_instance().keyring) is FileKeyring # new keyring set
assert len(Keychain().get_all_public_keys()) == 4 # new keyring has 4 keys
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 0 # legacy keys removed
current_version_str = pkg_resources.get_distribution("chia-blockchain").version
last_migration_version_str = (
KeyringWrapper.get_shared_instance().keys_root_path / ".last_legacy_migration"
).read_text()
assert last_migration_version_str == current_version_str # last migration version set
def test_migration_silent(self, tmp_path, keyring_with_one_key, monkeypatch):
KeyringWrapper.set_keys_root_path(tmp_path)
KeyringWrapper.cleanup_shared_instance()
keychain = keyring_with_one_key
legacy_keyring = DummyLegacyKeyring()
def mock_get_legacy_keyring_instance() -> Optional[LegacyKeyring]:
nonlocal legacy_keyring
return legacy_keyring
from chia.util import keyring_wrapper
monkeypatch.setattr(keyring_wrapper, "get_legacy_keyring_instance", mock_get_legacy_keyring_instance)
assert len(keychain.get_all_private_keys()) == 1
assert len(Keychain().get_all_private_keys()) == 1
assert keychain.keyring_wrapper.legacy_keyring is None
assert legacy_keyring is not None
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 3
keys_needing_migration, legacy_migration_keychain = Keychain.get_keys_needing_migration()
assert len(keys_needing_migration) == 3
assert legacy_migration_keychain is not None
Keychain.migrate_legacy_keys_silently()
assert type(KeyringWrapper.get_shared_instance().keyring) is FileKeyring # new keyring set
assert len(Keychain().get_all_public_keys()) == 4 # new keyring has 4 keys
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 3 # legacy keys still intact
def test_migration_silent_keys_already_present(self, tmp_path, keyring_with_one_key, monkeypatch):
KeyringWrapper.set_keys_root_path(tmp_path)
KeyringWrapper.cleanup_shared_instance()
keychain = keyring_with_one_key
pkent_str = keychain.keyring_wrapper.get_passphrase(DEFAULT_SERVICE, f"wallet-{DEFAULT_USER}-0")
legacy_keyring = DummyLegacyKeyring(populate=False)
legacy_keyring.set_password(DEFAULT_SERVICE, f"wallet-{DEFAULT_USER}-0", pkent_str)
def mock_get_legacy_keyring_instance() -> Optional[LegacyKeyring]:
nonlocal legacy_keyring
return legacy_keyring
from chia.util import keyring_wrapper
monkeypatch.setattr(keyring_wrapper, "get_legacy_keyring_instance", mock_get_legacy_keyring_instance)
assert len(keychain.get_all_private_keys()) == 1
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 1
keys_needing_migration, legacy_migration_keychain = Keychain.get_keys_needing_migration()
assert len(keys_needing_migration) == 0
assert legacy_migration_keychain is not None
Keychain.migrate_legacy_keys_silently()
assert type(KeyringWrapper.get_shared_instance().keyring) is FileKeyring # new keyring set
assert len(Keychain().get_all_public_keys()) == 1 # keyring has 1 key
assert len(legacy_keyring.service_dict[DEFAULT_SERVICE]) == 1 # legacy keys still intact
def test_migration_checked(self, tmp_path, monkeypatch):
KeyringWrapper.set_keys_root_path(tmp_path)
KeyringWrapper.cleanup_shared_instance()
assert Keychain.migration_checked_for_current_version() is False
dist_version = ""
class DummyDistribution:
def __init__(self, version):
self.version = version
def mock_get_distribution_version(_) -> DummyDistribution:
nonlocal dist_version
return DummyDistribution(dist_version)
monkeypatch.setattr(pkg_resources, "get_distribution", mock_get_distribution_version)
dist_version = "1.2.11.dev123"
assert pkg_resources.get_distribution("chia-blockchain").version == "1.2.11.dev123"
Keychain.mark_migration_checked_for_current_version()
last_migration_version_str = (
KeyringWrapper.get_shared_instance().keys_root_path / ".last_legacy_migration"
).read_text()
assert last_migration_version_str == "1.2.11.dev123" # last migration version set
assert Keychain.migration_checked_for_current_version() is True
dist_version = "1.2.11.dev345"
assert Keychain.migration_checked_for_current_version() is True # We don't check the build number
dist_version = "1.2.10.dev111"
assert Keychain.migration_checked_for_current_version() is True # Checked version > current version
dist_version = "1.3.0.dev100"
assert Keychain.migration_checked_for_current_version() is False # Checked version < current version