Exemplo n.º 1
0
def main() -> None:
    config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME)
    # This is simulator
    local_test = config["testing"]
    if local_test is True:
        constants = test_constants
        current = config["database_path"]
        config["database_path"] = f"{current}_simulation"
        config["selected_network"] = "testnet0"
    else:
        constants = DEFAULT_CONSTANTS
    keychain = Keychain(testing=False)
    kwargs = service_kwargs_for_wallet(DEFAULT_ROOT_PATH, config, constants,
                                       keychain)
    return run_service(**kwargs)
Exemplo n.º 2
0
    def test_bip39_eip2333_test_vector(self):
        kc: Keychain = Keychain(testing=True)
        kc.delete_all_keys()

        mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
        passphrase = "TREZOR"
        print("entropy to seed:", mnemonic_to_seed(mnemonic, passphrase).hex())
        master_sk = kc.add_private_key(mnemonic, passphrase)
        tv_master_int = 5399117110774477986698372024995405256382522670366369834617409486544348441851
        tv_child_int = 11812940737387919040225825939013910852517748782307378293770044673328955938106
        assert master_sk == PrivateKey.from_bytes(
            tv_master_int.to_bytes(32, "big"))
        child_sk = AugSchemeMPL.derive_child_sk(master_sk, 0)
        assert child_sk == PrivateKey.from_bytes(
            tv_child_int.to_bytes(32, "big"))
Exemplo n.º 3
0
    async def remove_keyring_passphrase(self, request: Dict[str, Any]):
        success: bool = False
        error: Optional[str] = None
        current_passphrase: Optional[str] = None

        if not Keychain.has_master_passphrase():
            return {"success": False, "error": "passphrase not set"}

        current_passphrase = request.get("current_passphrase", None)
        if type(current_passphrase) is not str:
            return {"success": False, "error": "missing current_passphrase"}

        try:
            Keychain.remove_master_passphrase(current_passphrase)
        except KeyringCurrentPassphaseIsInvalid:
            error = "current passphrase is invalid"
        except Exception as e:
            tb = traceback.format_exc()
            self.log.error(f"Failed to remove keyring passphrase: {e} {tb}")
        else:
            success = True

        response: Dict[str, Any] = {"success": success, "error": error}
        return response
Exemplo n.º 4
0
def prompt_for_new_passphrase() -> str:
    min_length: int = Keychain.minimum_passphrase_length()
    if min_length > 0:
        n = min_length
        print(f"\nPassphrases must be {n} or more characters in length"
              )  # lgtm [py/clear-text-logging-sensitive-data]
    while True:
        passphrase = tidy_passphrase(getpass("New Passphrase: "))
        confirmation = tidy_passphrase(getpass("Confirm Passphrase: "))

        valid_passphrase, error_msg = verify_passphrase_meets_requirements(
            passphrase, confirmation)

        if valid_passphrase:
            return passphrase
        elif error_msg:
            print(f"{error_msg}\n"
                  )  # lgtm [py/clear-text-logging-sensitive-data]
Exemplo n.º 5
0
 def get_keychain_for_request(self, request: Dict[str, Any]):
     """
     Keychain instances can have user and service strings associated with them.
     The keychain backends ultimately point to the same data stores, but the user
     and service strings are used to partition those data stores. We attempt to
     maintain a mapping of user/service pairs to their corresponding Keychain.
     """
     keychain = None
     user = request.get("kc_user", self._default_keychain.user)
     service = request.get("kc_service", self._default_keychain.service)
     if user == self._default_keychain.user and service == self._default_keychain.service:
         keychain = self._default_keychain
     else:
         key = (user or "unnamed") + (service or "")
         if key in self._alt_keychains:
             keychain = self._alt_keychains[key]
         else:
             keychain = Keychain(user=user, service=service)
             self._alt_keychains[key] = keychain
     return keychain
Exemplo n.º 6
0
 def get_keychain_for_request(self, request: Dict[str, Any]):
     """
     Keychain instances can have a user and testing flag associated with them.
     The keychain backends ultimately point to the same data stores, but the user
     and testing flags are used to partition those data stores. We attempt to
     maintain a mapping of user/testing pairs to their corresponding Keychain.
     """
     keychain = None
     user = request.get("kc_user", self._default_keychain.user)
     testing = request.get("kc_testing", self._default_keychain.testing)
     if user == self._default_keychain.user and testing == self._default_keychain.testing:
         keychain = self._default_keychain
     else:
         key = (user or "unnamed") + ("test" if testing else "")
         if key in self._alt_keychains:
             keychain = self._alt_keychains[key]
         else:
             keychain = Keychain(user=user, testing=testing)
             self._alt_keychains[key] = keychain
     return keychain
Exemplo n.º 7
0
    async def get_sk(self, keychain_proxy: Optional[KeychainProxy] = None) -> Optional[Tuple[PrivateKey, bytes]]:
        sk: Optional[PrivateKey] = None
        if keychain_proxy:
            try:
                if self.alt_fingerprint is not None:
                    sk = await keychain_proxy.get_key_for_fingerprint(self.alt_fingerprint)
                else:
                    sk = await keychain_proxy.get_first_private_key()
            except Exception as e:
                log.error(f"Keychain proxy failed with error: {e}")
        else:
            sk_ent: Optional[Tuple[PrivateKey, bytes]] = None
            keychain: Keychain = Keychain()
            if self.alt_fingerprint is not None:
                sk_ent = keychain.get_private_key_by_fingerprint(self.alt_fingerprint)
            else:
                sk_ent = keychain.get_first_private_key()

            if sk_ent:
                sk = sk_ent[0]
        return sk
Exemplo n.º 8
0
    def _patch_and_create_keychain(self, user: str, testing: bool,
                                   populate: bool,
                                   existing_keyring_path: Optional[str]):
        existing_keyring_dir = Path(
            existing_keyring_path).parent if existing_keyring_path else None
        temp_dir = existing_keyring_dir or tempfile.mkdtemp(
            prefix="test_keyring_wrapper")

        mock_supports_keyring_passphrase_patch = patch(
            "chia.util.keychain.supports_keyring_passphrase")
        mock_supports_keyring_passphrase = mock_supports_keyring_passphrase_patch.start(
        )

        # Patch supports_keyring_passphrase() to return True
        mock_supports_keyring_passphrase.return_value = True

        mock_configure_backend_patch = patch.object(KeyringWrapper,
                                                    "_configure_backend")
        mock_configure_backend = mock_configure_backend_patch.start()
        setup_mock_file_keyring(mock_configure_backend,
                                temp_dir,
                                populate=populate)

        mock_data_root_patch = patch.object(platform_, "data_root")
        mock_data_root = mock_data_root_patch.start()

        # Mock CryptFileKeyring's file_path indirectly by changing keyring.util.platform_.data_root
        # We don't want CryptFileKeyring finding the real legacy keyring
        mock_data_root.return_value = temp_dir

        keychain = Keychain(user=user, testing=testing)
        keychain.keyring_wrapper = KeyringWrapper(
            keys_root_path=Path(temp_dir))

        # Stash the temp_dir in the keychain instance
        keychain._temp_dir = temp_dir  # type: ignore

        # Stash the patches in the keychain instance
        keychain._mock_supports_keyring_passphrase_patch = mock_supports_keyring_passphrase_patch  # type: ignore
        keychain._mock_configure_backend_patch = mock_configure_backend_patch  # type: ignore
        keychain._mock_data_root_patch = mock_data_root_patch  # type: ignore

        return keychain
Exemplo n.º 9
0
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, 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")
Exemplo n.º 10
0
def show_all_keys(show_mnemonic: bool):
    """
    Prints all keys and mnemonics (if available).
    """
    root_path = DEFAULT_ROOT_PATH
    config = load_config(root_path, "config.yaml")
    private_keys = Keychain().get_all_private_keys()
    selected = config["selected_network"]
    prefix = config["network_overrides"]["config"][selected]["address_prefix"]
    if len(private_keys) == 0:
        print("There are no saved private keys")
        return None
    msg = "Showing all public keys derived from your master seed and private key:"
    if show_mnemonic:
        msg = "Showing all public and private keys"
    print(msg)
    for sk, seed in private_keys:
        print("")
        print("Fingerprint:", sk.get_g1().get_fingerprint())
        print("Master public key (m):", sk.get_g1())
        print(
            "Farmer public key (m/12381/8444/0/0):",
            master_sk_to_farmer_sk(sk).get_g1(),
        )
        print("Pool public key (m/12381/8444/1/0):", master_sk_to_pool_sk(sk).get_g1())
        print(
            "First wallet address:",
            encode_puzzle_hash(create_puzzlehash_for_pk(master_sk_to_wallet_sk(sk, uint32(0)).get_g1()), prefix),
        )
        assert seed is not None
        if show_mnemonic:
            print("Master private key (m):", bytes(sk).hex())
            print(
                "First wallet secret key (m/12381/8444/2/0):",
                master_sk_to_wallet_sk(sk, uint32(0)),
            )
            mnemonic = bytes_to_mnemonic(seed)
            print("  Mnemonic seed (24 secret words):")
            print(mnemonic)
Exemplo n.º 11
0
def prompt_for_new_passphrase() -> Tuple[str, bool]:
    min_length: int = Keychain.minimum_passphrase_length()
    if min_length > 0:
        n = min_length
        print(f"\nPassphrases must be {n} or more characters in length"
              )  # lgtm [py/clear-text-logging-sensitive-data]
    while True:
        passphrase: str = getpass("New Passphrase: ")
        confirmation: str = getpass("Confirm Passphrase: ")
        save_passphrase: bool = False

        valid_passphrase, error_msg = verify_passphrase_meets_requirements(
            passphrase, confirmation)

        if valid_passphrase:
            if supports_os_passphrase_storage():
                save_passphrase = prompt_to_save_passphrase()

            return passphrase, save_passphrase
        elif error_msg:
            print(f"{error_msg}\n"
                  )  # lgtm [py/clear-text-logging-sensitive-data]
Exemplo n.º 12
0
async def async_update_daemon_passphrase_cache_if_running(
        root_path: Path) -> None:
    """
    Attempt to connect to the daemon and update the cached passphrase
    """
    new_passphrase = Keychain.get_cached_master_passphrase()
    assert new_passphrase is not None

    try:
        async with acquire_connection_to_daemon(root_path,
                                                quiet=True) as daemon:
            if daemon is not None:
                response = await daemon.unlock_keyring(new_passphrase)
                if response is None:
                    raise Exception("daemon didn't respond")

                success: bool = response.get("data", {}).get("success", False)
                if success is False:
                    error = response.get("data",
                                         {}).get("error", "unknown error")
                    raise Exception(error)
    except Exception as e:
        print(f"Failed to notify daemon of updated keyring passphrase: {e}")
Exemplo n.º 13
0
def cli(
    ctx: click.Context,
    root_path: str,
    keys_root_path: Optional[str] = None,
    passphrase_file: Optional[TextIOWrapper] = None,
) -> None:
    from pathlib import Path

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

    # keys_root_path and passphrase_file will be None if the passphrase options have been
    # scrubbed from the CLI options
    if keys_root_path is not None:
        set_keys_root_path(Path(keys_root_path))

    if passphrase_file is not None:
        from chia.cmds.passphrase_funcs import cache_passphrase, read_passphrase_from_file
        from sys import exit

        try:
            passphrase = read_passphrase_from_file(passphrase_file)
            if Keychain.master_passphrase_is_valid(passphrase):
                cache_passphrase(passphrase)
            else:
                raise KeyringCurrentPassphraseIsInvalid("Invalid passphrase")
        except KeyringCurrentPassphraseIsInvalid:
            if Path(passphrase_file.name).is_file():
                print(f'Invalid passphrase found in "{passphrase_file.name}"')
            else:
                print("Invalid passphrase")
            exit(1)
        except Exception as e:
            print(f"Failed to read passphrase: {e}")

    check_ssl(Path(root_path))
Exemplo n.º 14
0
async def async_update_daemon_passphrase_cache_if_running(
        root_path: Path) -> None:
    from chia.daemon.client import connect_to_daemon_and_validate

    new_passphrase = Keychain.get_cached_master_passphrase()
    assert new_passphrase is not None

    daemon = None
    try:
        daemon = await connect_to_daemon_and_validate(root_path, quiet=True)
        if daemon:
            response = await daemon.unlock_keyring(new_passphrase)

            if not response:
                raise Exception("daemon didn't respond")

            if response["data"].get("success", False) is False:
                error = response["data"].get("error", "unknown error")
                raise Exception(error)
    except Exception as e:
        print(f"Failed to notify daemon of updated keyring passphrase: {e}")

    if daemon:
        await daemon.close()
Exemplo n.º 15
0
def check_plots(root_path, num, challenge_start, grep_string, list_duplicates,
                debug_show_memo):
    config = load_config(root_path, "config.yaml")
    if num is not None:
        if num == 0:
            log.warning("Not opening plot files")
        else:
            if num < 5:
                log.warning(
                    f"{num} challenges is too low, setting it to the minimum of 5"
                )
                num = 5
            if num < 30:
                log.warning(
                    "Use 30 challenges (our default) for balance of speed and accurate results"
                )
    else:
        num = 30

    if challenge_start is not None:
        num_start = challenge_start
        num_end = num_start + num
    else:
        num_start = 0
        num_end = num
    challenges = num_end - num_start

    if grep_string is not None:
        match_str = grep_string
    else:
        match_str = None
    if list_duplicates:
        log.warning("Checking for duplicate Plot IDs")
        log.info("Plot filenames expected to end with -[64 char plot ID].plot")

    show_memo: bool = debug_show_memo

    if list_duplicates:
        plot_filenames: Dict[Path, List[Path]] = get_plot_filenames(
            config["harvester"])
        all_filenames: List[Path] = []
        for paths in plot_filenames.values():
            all_filenames += paths
        find_duplicate_plot_IDs(all_filenames)

    if num == 0:
        return None

    v = Verifier()
    log.info("Loading plots in config.yaml using plot_tools loading code\n")
    kc: Keychain = Keychain()
    pks = [
        master_sk_to_farmer_sk(sk).get_g1()
        for sk, _ in kc.get_all_private_keys()
    ]
    pool_public_keys = [
        G1Element.from_bytes(bytes.fromhex(pk))
        for pk in config["farmer"]["pool_public_keys"]
    ]
    _, provers, failed_to_open_filenames, no_key_filenames = load_plots(
        {},
        {},
        pks,
        pool_public_keys,
        match_str,
        show_memo,
        root_path,
        open_no_key_filenames=True,
    )
    if len(provers) > 0:
        log.info("")
        log.info("")
        log.info(f"Starting to test each plot with {num} challenges each\n")
    total_good_plots: Counter = Counter()
    total_bad_plots = 0
    total_size = 0
    bad_plots_list: List[Path] = []

    for plot_path, plot_info in provers.items():
        pr = plot_info.prover
        log.info(f"Testing plot {plot_path} k={pr.get_size()}")
        log.info(f"\tPool public key: {plot_info.pool_public_key}")

        # Look up local_sk from plot to save locked memory
        (
            pool_public_key_or_puzzle_hash,
            farmer_public_key,
            local_master_sk,
        ) = parse_plot_info(pr.get_memo())
        local_sk = master_sk_to_local_sk(local_master_sk)
        log.info(f"\tFarmer public key: {farmer_public_key}")
        log.info(f"\tLocal sk: {local_sk}")
        total_proofs = 0
        caught_exception: bool = False
        for i in range(num_start, num_end):
            challenge = std_hash(i.to_bytes(32, "big"))
            # Some plot errors cause get_qualities_for_challenge to throw a RuntimeError
            try:
                for index, quality_str in enumerate(
                        pr.get_qualities_for_challenge(challenge)):
                    # Other plot errors cause get_full_proof or validate_proof to throw an AssertionError
                    try:
                        proof = pr.get_full_proof(challenge, index)
                        total_proofs += 1
                        ver_quality_str = v.validate_proof(
                            pr.get_id(), pr.get_size(), challenge, proof)
                        assert quality_str == ver_quality_str
                    except AssertionError as e:
                        log.error(
                            f"{type(e)}: {e} error in proving/verifying for plot {plot_path}"
                        )
                        caught_exception = True
            except KeyboardInterrupt:
                log.warning("Interrupted, closing")
                return None
            except SystemExit:
                log.warning("System is shutting down.")
                return None
            except Exception as e:
                log.error(
                    f"{type(e)}: {e} error in getting challenge qualities for plot {plot_path}"
                )
                caught_exception = True
            if caught_exception is True:
                break
        if total_proofs > 0 and caught_exception is False:
            log.info(
                f"\tProofs {total_proofs} / {challenges}, {round(total_proofs/float(challenges), 4)}"
            )
            total_good_plots[pr.get_size()] += 1
            total_size += plot_path.stat().st_size
        else:
            total_bad_plots += 1
            log.error(
                f"\tProofs {total_proofs} / {challenges}, {round(total_proofs/float(challenges), 4)}"
            )
            bad_plots_list.append(plot_path)
    log.info("")
    log.info("")
    log.info("Summary")
    total_plots: int = sum(list(total_good_plots.values()))
    log.info(
        f"Found {total_plots} valid plots, total size {total_size / (1024 * 1024 * 1024 * 1024):.5f} TiB"
    )
    for (k, count) in sorted(dict(total_good_plots).items()):
        log.info(f"{count} plots of size {k}")
    grand_total_bad = total_bad_plots + len(failed_to_open_filenames)
    if grand_total_bad > 0:
        log.warning(f"{grand_total_bad} invalid plots found:")
        for bad_plot_path in bad_plots_list:
            log.warning(f"{bad_plot_path}")
    if len(no_key_filenames) > 0:
        log.warning(
            f"There are {len(no_key_filenames)} plots with a farmer or pool public key that "
            f"is not on this machine. The farmer private key must be in the keychain in order to "
            f"farm them, use 'chia keys' to transfer keys. The pool public keys must be in the config.yaml"
        )
Exemplo n.º 16
0
class KeychainProxy(DaemonProxy):
    """
    KeychainProxy can act on behalf of a local or remote keychain. In the case of
    wrapping a local keychain, the proxy object simply forwards-along the calls to
    the underlying local keychain. In the remote case, calls are made to the daemon
    over the RPC interface, allowing the daemon to act as the keychain authority.
    """

    def __init__(
        self,
        log: logging.Logger,
        uri: str = None,
        ssl_context: Optional[ssl.SSLContext] = None,
        local_keychain: Optional[Keychain] = None,
        user: str = None,
        testing: bool = False,
    ):
        self.log = log
        if local_keychain:
            self.keychain = local_keychain
        elif not supports_keyring_passphrase():
            self.keychain = Keychain()  # Proxy locally, don't use RPC
        else:
            self.keychain = None  # type: ignore
        self.keychain_user = user
        self.keychain_testing = testing
        super().__init__(uri or "", ssl_context)

    def use_local_keychain(self) -> bool:
        """
        Indicates whether the proxy forwards calls to a local keychain
        """
        return self.keychain is not None

    def format_request(self, command: str, data: Dict[str, Any]) -> WsRpcMessage:
        """
        Overrides DaemonProxy.format_request() to add keychain-specific RPC params
        """
        if data is None:
            data = {}

        if self.keychain_user or self.keychain_testing:
            data["kc_user"] = self.keychain_user
            data["kc_testing"] = self.keychain_testing

        return super().format_request(command, data)

    async def get_response_for_request(self, request_name: str, data: Dict[str, Any]) -> Tuple[WsRpcMessage, bool]:
        request = self.format_request(request_name, data)
        response = await self._get(request)
        success = response["data"].get("success", False)
        return response, success

    def handle_error(self, response: WsRpcMessage):
        """
        Common error handling for RPC responses
        """
        error = response["data"].get("error", None)
        if error:
            error_details = response["data"].get("error_details", {})
            if error == KEYCHAIN_ERR_LOCKED:
                raise KeyringIsLocked()
            elif error == KEYCHAIN_ERR_NO_KEYS:
                raise KeyringIsEmpty()
            elif error == KEYCHAIN_ERR_MALFORMED_REQUEST:
                message = error_details.get("message", "")
                raise MalformedKeychainRequest(message)
            else:
                err = f"{response['data'].get('command')} failed with error: {error}"
                self.log.error(f"{err}")
                raise Exception(f"{err}")

    async def add_private_key(self, mnemonic: str, passphrase: str) -> PrivateKey:
        """
        Forwards to Keychain.add_private_key()
        """
        key: PrivateKey
        if self.use_local_keychain():
            key = self.keychain.add_private_key(mnemonic, passphrase)
        else:
            response, success = await self.get_response_for_request(
                "add_private_key", {"mnemonic": mnemonic, "passphrase": passphrase}
            )
            if success:
                seed = mnemonic_to_seed(mnemonic, passphrase)
                key = AugSchemeMPL.key_gen(seed)
            else:
                error = response["data"].get("error", None)
                if error == KEYCHAIN_ERR_KEYERROR:
                    error_details = response["data"].get("error_details", {})
                    word = error_details.get("word", "")
                    raise KeyError(word)
                else:
                    self.handle_error(response)

        return key

    async def check_keys(self, root_path):
        """
        Forwards to init_funcs.check_keys()
        """
        if self.use_local_keychain():
            check_keys(root_path, self.keychain)
        else:
            response, success = await self.get_response_for_request("check_keys", {"root_path": str(root_path)})
            if not success:
                self.handle_error(response)

    async def delete_all_keys(self):
        """
        Forwards to Keychain.delete_all_keys()
        """
        if self.use_local_keychain():
            self.keychain.delete_all_keys()
        else:
            response, success = await self.get_response_for_request("delete_all_keys", {})
            if not success:
                self.handle_error(response)

    async def delete_key_by_fingerprint(self, fingerprint: int):
        """
        Forwards to Keychain.delete_key_by_fingerprint()
        """
        if self.use_local_keychain():
            self.keychain.delete_key_by_fingerprint(fingerprint)
        else:
            response, success = await self.get_response_for_request(
                "delete_key_by_fingerprint", {"fingerprint": fingerprint}
            )
            if not success:
                self.handle_error(response)

    async def get_all_private_keys(self) -> List[Tuple[PrivateKey, bytes]]:
        """
        Forwards to Keychain.get_all_private_keys()
        """
        keys: List[Tuple[PrivateKey, bytes]] = []
        if self.use_local_keychain():
            keys = self.keychain.get_all_private_keys()
        else:
            response, success = await self.get_response_for_request("get_all_private_keys", {})
            if success:
                private_keys = response["data"].get("private_keys", None)
                if private_keys is None:
                    err = f"Missing private_keys in {response.get('command')} response"
                    self.log.error(f"{err}")
                    raise MalformedKeychainResponse(f"{err}")
                else:
                    for key_dict in private_keys:
                        pk = key_dict.get("pk", None)
                        ent_str = key_dict.get("entropy", None)
                        if pk is None or ent_str is None:
                            err = f"Missing pk and/or ent in {response.get('command')} response"
                            self.log.error(f"{err}")
                            continue  # We'll skip the incomplete key entry
                        ent = bytes.fromhex(ent_str)
                        mnemonic = bytes_to_mnemonic(ent)
                        seed = mnemonic_to_seed(mnemonic, passphrase="")
                        key = AugSchemeMPL.key_gen(seed)
                        if bytes(key.get_g1()).hex() == pk:
                            keys.append((key, ent))
                        else:
                            err = "G1Elements don't match"
                            self.log.error(f"{err}")
            else:
                self.handle_error(response)

        return keys

    async def get_first_private_key(self) -> Optional[PrivateKey]:
        """
        Forwards to Keychain.get_first_private_key()
        """
        key: Optional[PrivateKey] = None
        if self.use_local_keychain():
            sk_ent = self.keychain.get_first_private_key()
            if sk_ent:
                key = sk_ent[0]
        else:
            response, success = await self.get_response_for_request("get_first_private_key", {})
            if success:
                private_key = response["data"].get("private_key", None)
                if private_key is None:
                    err = f"Missing private_key in {response.get('command')} response"
                    self.log.error(f"{err}")
                    raise MalformedKeychainResponse(f"{err}")
                else:
                    pk = private_key.get("pk", None)
                    ent_str = private_key.get("entropy", None)
                    if pk is None or ent_str is None:
                        err = f"Missing pk and/or ent in {response.get('command')} response"
                        self.log.error(f"{err}")
                        raise MalformedKeychainResponse(f"{err}")
                    ent = bytes.fromhex(ent_str)
                    mnemonic = bytes_to_mnemonic(ent)
                    seed = mnemonic_to_seed(mnemonic, passphrase="")
                    sk = AugSchemeMPL.key_gen(seed)
                    if bytes(sk.get_g1()).hex() == pk:
                        key = sk
                    else:
                        err = "G1Elements don't match"
                        self.log.error(f"{err}")
            else:
                self.handle_error(response)

        return key

    async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[PrivateKey]:
        """
        Locates and returns a private key matching the provided fingerprint
        """
        key: Optional[PrivateKey] = None
        if self.use_local_keychain():
            private_keys = self.keychain.get_all_private_keys()
            if len(private_keys) == 0:
                raise KeyringIsEmpty()
            else:
                if fingerprint is not None:
                    for sk, _ in private_keys:
                        if sk.get_g1().get_fingerprint() == fingerprint:
                            key = sk
                            break
                else:
                    key = private_keys[0][0]
        else:
            response, success = await self.get_response_for_request(
                "get_key_for_fingerprint", {"fingerprint": fingerprint}
            )
            if success:
                pk = response["data"].get("pk", None)
                ent = response["data"].get("entropy", None)
                if pk is None or ent is None:
                    err = f"Missing pk and/or ent in {response.get('command')} response"
                    self.log.error(f"{err}")
                    raise MalformedKeychainResponse(f"{err}")
                else:
                    mnemonic = bytes_to_mnemonic(bytes.fromhex(ent))
                    seed = mnemonic_to_seed(mnemonic, passphrase="")
                    private_key = AugSchemeMPL.key_gen(seed)
                    if bytes(private_key.get_g1()).hex() == pk:
                        key = private_key
                    else:
                        err = "G1Elements don't match"
                        self.log.error(f"{err}")
            else:
                self.handle_error(response)

        return key
Exemplo n.º 17
0
def check_plots(root_path, num, challenge_start, grep_string, list_duplicates, debug_show_memo):
    config = load_config(root_path, "config.yaml")
    plot_refresh_parameter: PlotsRefreshParameter = PlotsRefreshParameter(100, 100, 1)
    plot_manager: PlotManager = PlotManager(
        root_path,
        match_str=grep_string,
        show_memo=debug_show_memo,
        open_no_key_filenames=True,
        refresh_parameter=plot_refresh_parameter,
        refresh_callback=plot_refresh_callback,
    )
    if num is not None:
        if num == 0:
            log.warning("Not opening plot files")
        else:
            if num < 5:
                log.warning(f"{num} challenges is too low, setting it to the minimum of 5")
                num = 5
            if num < 30:
                log.warning("Use 30 challenges (our default) for balance of speed and accurate results")
    else:
        num = 30

    if challenge_start is not None:
        num_start = challenge_start
        num_end = num_start + num
    else:
        num_start = 0
        num_end = num
    challenges = num_end - num_start

    if list_duplicates:
        log.warning("Checking for duplicate Plot IDs")
        log.info("Plot filenames expected to end with -[64 char plot ID].plot")

    if list_duplicates:
        all_filenames: List[Path] = []
        for paths in get_plot_filenames(root_path).values():
            all_filenames += paths
        find_duplicate_plot_IDs(all_filenames)

    if num == 0:
        return None

    parallel_read: bool = config["harvester"].get("parallel_read", True)

    v = Verifier()
    log.info(f"Loading plots in config.yaml using plot_manager loading code (parallel read: {parallel_read})\n")
    # Prompts interactively if the keyring is protected by a master passphrase. To use the daemon
    # for keychain access, KeychainProxy/connect_to_keychain should be used instead of Keychain.
    kc: Keychain = Keychain()
    plot_manager.set_public_keys(
        [master_sk_to_farmer_sk(sk).get_g1() for sk, _ in kc.get_all_private_keys()],
        [G1Element.from_bytes(bytes.fromhex(pk)) for pk in config["farmer"]["pool_public_keys"]],
    )
    plot_manager.start_refreshing()

    while plot_manager.needs_refresh():
        sleep(1)

    plot_manager.stop_refreshing()

    if plot_manager.plot_count() > 0:
        log.info("")
        log.info("")
        log.info(f"Starting to test each plot with {num} challenges each\n")
    total_good_plots: Counter = Counter()
    total_bad_plots = 0
    total_size = 0
    bad_plots_list: List[Path] = []

    with plot_manager:
        for plot_path, plot_info in plot_manager.plots.items():
            pr = plot_info.prover
            log.info(f"Testing plot {plot_path} k={pr.get_size()}")
            log.info(f"\tPool public key: {plot_info.pool_public_key}")

            # Look up local_sk from plot to save locked memory
            (
                pool_public_key_or_puzzle_hash,
                farmer_public_key,
                local_master_sk,
            ) = parse_plot_info(pr.get_memo())
            local_sk = master_sk_to_local_sk(local_master_sk)
            log.info(f"\tFarmer public key: {farmer_public_key}")
            log.info(f"\tLocal sk: {local_sk}")
            total_proofs = 0
            caught_exception: bool = False
            for i in range(num_start, num_end):
                challenge = std_hash(i.to_bytes(32, "big"))
                # Some plot errors cause get_qualities_for_challenge to throw a RuntimeError
                try:
                    quality_start_time = int(round(time() * 1000))
                    for index, quality_str in enumerate(pr.get_qualities_for_challenge(challenge)):
                        quality_spent_time = int(round(time() * 1000)) - quality_start_time
                        if quality_spent_time > 5000:
                            log.warning(
                                f"\tLooking up qualities took: {quality_spent_time} ms. This should be below 5 seconds "
                                f"to minimize risk of losing rewards."
                            )
                        else:
                            log.info(f"\tLooking up qualities took: {quality_spent_time} ms.")

                        # Other plot errors cause get_full_proof or validate_proof to throw an AssertionError
                        try:
                            proof_start_time = int(round(time() * 1000))
                            proof = pr.get_full_proof(challenge, index, parallel_read)
                            proof_spent_time = int(round(time() * 1000)) - proof_start_time
                            if proof_spent_time > 15000:
                                log.warning(
                                    f"\tFinding proof took: {proof_spent_time} ms. This should be below 15 seconds "
                                    f"to minimize risk of losing rewards."
                                )
                            else:
                                log.info(f"\tFinding proof took: {proof_spent_time} ms")
                            total_proofs += 1
                            ver_quality_str = v.validate_proof(pr.get_id(), pr.get_size(), challenge, proof)
                            assert quality_str == ver_quality_str
                        except AssertionError as e:
                            log.error(f"{type(e)}: {e} error in proving/verifying for plot {plot_path}")
                            caught_exception = True
                        quality_start_time = int(round(time() * 1000))
                except KeyboardInterrupt:
                    log.warning("Interrupted, closing")
                    return None
                except SystemExit:
                    log.warning("System is shutting down.")
                    return None
                except Exception as e:
                    log.error(f"{type(e)}: {e} error in getting challenge qualities for plot {plot_path}")
                    caught_exception = True
                if caught_exception is True:
                    break
            if total_proofs > 0 and caught_exception is False:
                log.info(f"\tProofs {total_proofs} / {challenges}, {round(total_proofs/float(challenges), 4)}")
                total_good_plots[pr.get_size()] += 1
                total_size += plot_path.stat().st_size
            else:
                total_bad_plots += 1
                log.error(f"\tProofs {total_proofs} / {challenges}, {round(total_proofs/float(challenges), 4)}")
                bad_plots_list.append(plot_path)
    log.info("")
    log.info("")
    log.info("Summary")
    total_plots: int = sum(list(total_good_plots.values()))
    log.info(f"Found {total_plots} valid plots, total size {total_size / (1024 * 1024 * 1024 * 1024):.5f} TiB")
    for (k, count) in sorted(dict(total_good_plots).items()):
        log.info(f"{count} plots of size {k}")
    grand_total_bad = total_bad_plots + len(plot_manager.failed_to_open_filenames)
    if grand_total_bad > 0:
        log.warning(f"{grand_total_bad} invalid plots found:")
        for bad_plot_path in bad_plots_list:
            log.warning(f"{bad_plot_path}")
    if len(plot_manager.no_key_filenames) > 0:
        log.warning(
            f"There are {len(plot_manager.no_key_filenames)} plots with a farmer or pool public key that "
            f"is not on this machine. The farmer private key must be in the keychain in order to "
            f"farm them, use 'chia keys' to transfer keys. The pool public keys must be in the config.yaml"
        )
Exemplo n.º 18
0
bram_message = None

status = None
while True:
    status_input = input(
        "What is the status of this alert? (ready/not ready)").lower()
    if status_input == "ready":
        status = True
        break
    elif status_input == "not ready":
        status = False
        break
    else:
        print("Unknown input")

keychain: Keychain = Keychain()
print("\n___________ SELECT KEY ____________")

private_keys = keychain.get_all_private_keys()
if len(private_keys) == 0:
    print("There are no saved private keys.")
    quit()
print("Showing all private keys:")
for sk, seed in private_keys:
    print("\nFingerprint:", sk.get_g1().get_fingerprint())

selected_key = None
while True:
    user_input = input(
        "\nEnter fingerprint of the key you want to use, or enter Q to quit: "
    ).lower()
Exemplo n.º 19
0
 def __init__(self):
     self._default_keychain = Keychain()
     self._alt_keychains = {}
Exemplo n.º 20
0
def main(argv) -> int:
    from chia.util.default_root import DEFAULT_ROOT_PATH
    from chia.util.keychain import Keychain

    wait_for_unlock = "--wait-for-unlock" in argv and Keychain.is_keyring_locked()
    return run_daemon(DEFAULT_ROOT_PATH, wait_for_unlock)
Exemplo n.º 21
0
from typing import List

from blspy import AugSchemeMPL, G1Element, G2Element

from chia.consensus.coinbase import create_puzzlehash_for_pk
from chia.util.bech32m import encode_puzzle_hash
from chia.util.config import load_config
from chia.util.default_root import DEFAULT_ROOT_PATH
from chia.util.ints import uint32
from chia.util.keychain import Keychain, bytes_to_mnemonic, generate_mnemonic
from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk

keychain: Keychain = Keychain()


def generate_and_print():
    """
    Generates a seed for a private key, and prints the mnemonic to the terminal.
    """

    mnemonic = generate_mnemonic()
    print("Generating private key. Mnemonic (24 secret words):")
    print(mnemonic)
    print(
        'Note that this key has not been added to the keychain. Run chia keys add_seed -m "[MNEMONICS]" to add'
    )
    return mnemonic


def generate_and_add():
    """
Exemplo n.º 22
0
def cache_passphrase(passphrase: str) -> None:
    Keychain.set_cached_master_passphrase(passphrase)
Exemplo n.º 23
0
    def test_basic_add_delete(self):
        kc: Keychain = Keychain(user="******",
                                service="chia-testing-1.8.0")
        kc.delete_all_keys()

        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 0
        assert kc.get_first_private_key() is None
        assert kc.get_first_public_key() is None

        mnemonic = generate_mnemonic()
        entropy = bytes_from_mnemonic(mnemonic)
        assert bytes_to_mnemonic(entropy) == mnemonic
        mnemonic_2 = generate_mnemonic()

        # misspelled words in the mnemonic
        bad_mnemonic = mnemonic.split(" ")
        bad_mnemonic[6] = "ZZZZZZ"
        self.assertRaisesRegex(
            ValueError,
            "'ZZZZZZ' is not in the mnemonic dictionary; may be misspelled",
            bytes_from_mnemonic,
            " ".join(bad_mnemonic),
        )

        kc.add_private_key(mnemonic, "")
        assert kc._get_free_private_key_index() == 1
        assert len(kc.get_all_private_keys()) == 1

        kc.add_private_key(mnemonic_2, "")
        kc.add_private_key(mnemonic_2, "")  # checks to not add duplicates
        assert kc._get_free_private_key_index() == 2
        assert len(kc.get_all_private_keys()) == 2

        assert kc._get_free_private_key_index() == 2
        assert len(kc.get_all_private_keys()) == 2
        assert len(kc.get_all_public_keys()) == 2
        assert kc.get_all_private_keys()[0] == kc.get_first_private_key()
        assert kc.get_all_public_keys()[0] == kc.get_first_public_key()

        assert len(kc.get_all_private_keys()) == 2

        seed_2 = mnemonic_to_seed(mnemonic, "")
        seed_key_2 = AugSchemeMPL.key_gen(seed_2)
        kc.delete_key_by_fingerprint(seed_key_2.get_g1().get_fingerprint())
        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 1

        kc.delete_all_keys()
        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 0

        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "my passphrase")
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "")
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)),
                           "third passphrase")

        assert len(kc.get_all_public_keys()) == 3
        assert len(kc.get_all_private_keys()) == 1
        assert len(kc.get_all_private_keys(["my passphrase", ""])) == 2
        assert len(
            kc.get_all_private_keys(
                ["my passphrase", "", "third passphrase", "another"])) == 3
        assert len(kc.get_all_private_keys(["my passhrase wrong"])) == 0

        assert kc.get_first_private_key() is not None
        assert kc.get_first_private_key(["bad passphrase"]) is None
        assert kc.get_first_public_key() is not None

        kc.delete_all_keys()
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "my passphrase")
        assert kc.get_first_public_key() is not None
Exemplo n.º 24
0
def main() -> None:
    config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME)
    config_pool = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", "pool")
    keychain = Keychain()
    kwargs = service_kwargs_for_farmer(DEFAULT_ROOT_PATH, config, config_pool, keychain, DEFAULT_CONSTANTS)
    return run_service(**kwargs)
Exemplo n.º 25
0
    def migrate_legacy_keyring(self):
        """
        Handle importing keys from the legacy keyring into the new keyring.

        Prior to beginning, we'll ensure that we at least suggest setting a master passphrase
        and backing up mnemonic seeds. After importing keys from the legacy keyring, we'll
        perform a before/after comparison of the keyring contents, and on success we'll prompt
        to cleanup the legacy keyring.
        """

        from chia.util.keychain import Keychain, MAX_KEYS

        # Make sure the user is ready to begin migration. We want to ensure that
        response = self.confirm_migration()
        if not response:
            print("Skipping migration. Unable to proceed")
            exit(0)

        print("Migrating contents from legacy keyring")

        keychain = Keychain()
        # Obtain contents from the legacy keyring. When using the Keychain interface
        # to read, the legacy keyring will be preferred over the new keyring.
        original_private_keys = keychain.get_all_private_keys()
        service = keychain._get_service()
        user_passphrase_pairs = []
        index = 0
        user = keychain._get_private_key_user(index)
        while index <= MAX_KEYS:
            # Build up a list of user/passphrase tuples from the legacy keyring contents
            if user is not None:
                passphrase = self.get_passphrase(service, user)

            if passphrase is not None:
                user_passphrase_pairs.append((user, passphrase))

            index += 1
            user = keychain._get_private_key_user(index)

        # Write the keys directly to the new keyring (self.keyring)
        for (user, passphrase) in user_passphrase_pairs:
            self.keyring.set_password(service, user, passphrase)

        # Stop using the legacy keyring. This will direct subsequent reads to the new keyring.
        old_keyring = self.legacy_keyring
        self.legacy_keyring = None

        print("Verifying migration results...", end="")

        # Compare the original keyring contents with the new
        try:
            post_migration_private_keys = keychain.get_all_private_keys()

            if post_migration_private_keys == original_private_keys:
                print(" Verified")
        except Exception as e:
            print(f"\nMigration failed: {e}")
            print("Leaving legacy keyring intact")
            exit(1)

        print(f"Keyring migration completed successfully ({str(self.keyring.keyring_path)})\n")

        # Ask if we should clean up the legacy keyring
        self.confirm_legacy_keyring_cleanup(old_keyring, service, [user for (user, _) in user_passphrase_pairs])
Exemplo n.º 26
0
 async def is_keyring_locked(self) -> Dict[str, Any]:
     locked: bool = Keychain.is_keyring_locked()
     response: Dict[str, Any] = {"success": True, "is_keyring_locked": locked}
     return response
Exemplo n.º 27
0
def check_keys(new_root: Path) -> None:
    keychain: Keychain = Keychain()
    all_sks = keychain.get_all_private_keys()
    if len(all_sks) == 0:
        print(
            "No keys are present in the keychain. Generate them with 'chia keys generate'"
        )
        return

    config: Dict = load_config(new_root, "config.yaml")
    pool_child_pubkeys = [
        master_sk_to_pool_sk(sk).get_g1() for sk, _ in all_sks
    ]
    all_targets = []
    stop_searching_for_farmer = "xch_target_address" not in config["farmer"]
    stop_searching_for_pool = "xch_target_address" not in config["pool"]
    number_of_ph_to_search = 500
    selected = config["selected_network"]
    prefix = config["network_overrides"]["config"][selected]["address_prefix"]
    for i in range(number_of_ph_to_search):
        if stop_searching_for_farmer and stop_searching_for_pool and i > 0:
            break
        for sk, _ in all_sks:
            all_targets.append(
                encode_puzzle_hash(
                    create_puzzlehash_for_pk(
                        master_sk_to_wallet_sk(sk, uint32(i)).get_g1()),
                    prefix))
            if all_targets[-1] == config["farmer"].get("xch_target_address"):
                stop_searching_for_farmer = True
            if all_targets[-1] == config["pool"].get("xch_target_address"):
                stop_searching_for_pool = True

    # Set the destinations
    if "xch_target_address" not in config["farmer"]:
        print(
            f"Setting the xch destination address for coinbase fees reward to {all_targets[0]}"
        )
        config["farmer"]["xch_target_address"] = all_targets[0]
    elif config["farmer"]["xch_target_address"] not in all_targets:
        print(
            f"WARNING: using a farmer address which we don't have the private"
            f" keys for. We searched the first {number_of_ph_to_search} addresses. Consider overriding "
            f"{config['farmer']['xch_target_address']} with {all_targets[0]}")

    if "pool" not in config:
        config["pool"] = {}
    if "xch_target_address" not in config["pool"]:
        print(
            f"Setting the xch destination address for coinbase reward to {all_targets[0]}"
        )
        config["pool"]["xch_target_address"] = all_targets[0]
    elif config["pool"]["xch_target_address"] not in all_targets:
        print(
            f"WARNING: using a pool address which we don't have the private"
            f" keys for. We searched the first {number_of_ph_to_search} addresses. Consider overriding "
            f"{config['pool']['xch_target_address']} with {all_targets[0]}")

    # Set the pool pks in the farmer
    pool_pubkeys_hex = set(bytes(pk).hex() for pk in pool_child_pubkeys)
    if "pool_public_keys" in config["farmer"]:
        for pk_hex in config["farmer"]["pool_public_keys"]:
            # Add original ones in config
            pool_pubkeys_hex.add(pk_hex)

    config["farmer"]["pool_public_keys"] = pool_pubkeys_hex
    save_config(new_root, "config.yaml", config)
Exemplo n.º 28
0
def using_default_passphrase() -> bool:
    if not Keychain.has_master_passphrase():
        return False

    return Keychain.master_passphrase_is_valid(default_passphrase())
Exemplo n.º 29
0
async def setup_wallet_node(
    port,
    consensus_constants: ConsensusConstants,
    local_bt,
    full_node_port=None,
    introducer_port=None,
    key_seed=None,
    starting_height=None,
):
    config = bt.config["wallet"]
    config["port"] = port
    config["rpc_port"] = port + 1000
    if starting_height is not None:
        config["starting_height"] = starting_height
    config["initial_num_public_keys"] = 5

    entropy = token_bytes(32)
    keychain = Keychain(entropy.hex(), True)
    if key_seed is None:
        key_seed = entropy
    keychain.add_private_key(bytes_to_mnemonic(key_seed), "")
    first_pk = keychain.get_first_public_key()
    assert first_pk is not None
    db_path_key_suffix = str(first_pk.get_fingerprint())
    db_name = f"test-wallet-db-{port}-KEY.sqlite"
    db_path_replaced: str = db_name.replace("KEY", db_path_key_suffix)
    db_path = bt.root_path / db_path_replaced

    if db_path.exists():
        db_path.unlink()
    config["database_path"] = str(db_name)
    config["testing"] = True

    config["introducer_peer"]["host"] = self_hostname
    if introducer_port is not None:
        config["introducer_peer"]["port"] = introducer_port
        config["peer_connect_interval"] = 10
    else:
        config["introducer_peer"] = None

    if full_node_port is not None:
        config["full_node_peer"] = {}
        config["full_node_peer"]["host"] = self_hostname
        config["full_node_peer"]["port"] = full_node_port
    else:
        del config["full_node_peer"]

    kwargs = service_kwargs_for_wallet(local_bt.root_path, config,
                                       consensus_constants, keychain)
    kwargs.update(
        parse_cli_args=False,
        connect_to_daemon=False,
    )

    service = Service(**kwargs)

    await service.start(new_wallet=True)

    yield service._node, service._node.server

    service.stop()
    await service.wait_closed()
    if db_path.exists():
        db_path.unlink()
    keychain.delete_all_keys()
Exemplo n.º 30
0
    def test_basic_add_delete(self):
        kc: Keychain = Keychain(testing=True)
        kc.delete_all_keys()

        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 0
        assert kc.get_first_private_key() is None
        assert kc.get_first_public_key() is None

        mnemonic = generate_mnemonic()
        entropy = bytes_from_mnemonic(mnemonic)
        assert bytes_to_mnemonic(entropy) == mnemonic
        mnemonic_2 = generate_mnemonic()

        kc.add_private_key(mnemonic, "")
        assert kc._get_free_private_key_index() == 1
        assert len(kc.get_all_private_keys()) == 1

        kc.add_private_key(mnemonic_2, "")
        kc.add_private_key(mnemonic_2, "")  # checks to not add duplicates
        assert kc._get_free_private_key_index() == 2
        assert len(kc.get_all_private_keys()) == 2

        assert kc._get_free_private_key_index() == 2
        assert len(kc.get_all_private_keys()) == 2
        assert len(kc.get_all_public_keys()) == 2
        assert kc.get_all_private_keys()[0] == kc.get_first_private_key()
        assert kc.get_all_public_keys()[0] == kc.get_first_public_key()

        assert len(kc.get_all_private_keys()) == 2

        seed_2 = mnemonic_to_seed(mnemonic, "")
        seed_key_2 = AugSchemeMPL.key_gen(seed_2)
        kc.delete_key_by_fingerprint(seed_key_2.get_g1().get_fingerprint())
        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 1

        kc.delete_all_keys()
        assert kc._get_free_private_key_index() == 0
        assert len(kc.get_all_private_keys()) == 0

        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "my passphrase")
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "")
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)),
                           "third passphrase")

        assert len(kc.get_all_public_keys()) == 3
        assert len(kc.get_all_private_keys()) == 1
        assert len(kc.get_all_private_keys(["my passphrase", ""])) == 2
        assert len(
            kc.get_all_private_keys(
                ["my passphrase", "", "third passphrase", "another"])) == 3
        assert len(kc.get_all_private_keys(["my passhrase wrong"])) == 0

        assert kc.get_first_private_key() is not None
        assert kc.get_first_private_key(["bad passphrase"]) is None
        assert kc.get_first_public_key() is not None

        kc.delete_all_keys()
        kc.add_private_key(bytes_to_mnemonic(token_bytes(32)), "my passphrase")
        assert kc.get_first_public_key() is not None