diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a144cd8d7798b956d66bc0e3b6726b0975ae0cf4..2e5db9c0b26786e1c94c716eb2169bad4ccf4e8b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,8 @@ Changelog 0.24.20 ------- * New hive node has been added (https://hived.emre.sh) +* Add option to use a derived WIF from coldcard hardware wallet to derive a new account password +* beempy keygen and beempy importaccount have now a new option import-coldcard 0.24.19 ------- diff --git a/beem/cli.py b/beem/cli.py index a9bcd1a2f2fbe2eb5f69813919ba50d33428e63c..c838fda781bf63fc82a39fbb304c8a5dde28df00 100644 --- a/beem/cli.py +++ b/beem/cli.py @@ -812,20 +812,40 @@ def delkey(confirm, pub): @click.option('--sequence', '-s', help='Sequence key number, when using BIP39 (default is 0)', default=0) @click.option('--account', '-a', help='account name for password based key generation or sequence number for BIP39 key, default = 0') @click.option('--import-password', '-i', help='Imports a password and derives all four account keys', is_flag=True, default=False) +@click.option('--import-coldcard', '-o', help='Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used to derives all four account keys') @click.option('--create-password', '-c', help='Creates a new password and derives four account keys from it', is_flag=True, default=False) @click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).', default=0) @click.option('--export-pub', '-u', help='Exports the public account keys to a json file for account creation or keychange') @click.option('--export', '-e', help='The results are stored in a text file and will not be shown') -def keygen(import_word_list, strength, passphrase, path, network, role, account_keys, sequence, account, import_password, create_password, wif, export_pub, export): +def keygen(import_word_list, strength, passphrase, path, network, role, account_keys, sequence, account, import_password, import_coldcard, create_password, wif, export_pub, export): """ Creates a new random BIP39 key or password based key and prints its derived private key and public key. The generated key is not stored. Can also be used to create new keys for an account. Can also be used to derive account keys from a password or BIP39 wordlist """ stm = shared_blockchain_instance() - if not account and import_password or create_password: + if not account and (import_password or create_password or import_coldcard): account = stm.config["default_account"] if import_password: import_password = click.prompt("Enter password", confirmation_prompt=False, hide_input=True) + elif import_coldcard is not None: + next_var = "" + import_password = "" + path = "" + with open(import_coldcard) as fp: + for line in fp: + if line.strip() == "": + continue + if line.strip() == "WIF (privkey):": + next_var = "wif" + continue + elif "Path Used" in line.strip(): + next_var = "path" + continue + if next_var == "wif": + import_password = line.strip() + elif next_var == "path": + path = line + next_var = "" elif create_password: alphabet = string.ascii_letters + string.digits while True: @@ -840,7 +860,7 @@ def keygen(import_word_list, strength, passphrase, path, network, role, account_ else: roles = ['owner', 'active', 'posting', 'memo'] - if import_password or create_password: + if import_password or create_password or import_coldcard: if wif > 0: password = import_password for _ in range(wif): @@ -853,6 +873,8 @@ def keygen(import_word_list, strength, passphrase, path, network, role, account_ t_pub = PrettyTable(["Key", "Value"]) t.add_row(["Username", account]) t_pub.add_row(["Username", account]) + if import_coldcard: + t_pub.add_row(["cold card path", path]) t.align = "l" t_pub.align = "l" for r in roles: @@ -980,6 +1002,7 @@ def keygen(import_word_list, strength, passphrase, path, network, role, account_ if export and export != "": with open(export, 'w') as fp: fp.write(str(t)) + fp.write("\n") fp.write(str(t_pub)) print("%s was sucessfully saved." % export) else: @@ -2145,8 +2168,10 @@ def delprofile(variable, account, export): @cli.command() @click.argument('account', nargs=1, required=True) -@click.option('--roles', help='Import specified keys (owner, active, posting, memo).', default=["active", "posting", "memo"]) -def importaccount(account, roles): +@click.option('--roles', '-r', help='Import specified keys (owner, active, posting, memo).', default=["active", "posting", "memo"]) +@click.option('--import-coldcard', '-i', help='Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used as passphrase') +@click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).', default=0) +def importaccount(account, roles, import_coldcard, wif): """Import an account using a passphrase""" from beemgraphenebase.account import PasswordKey stm = shared_blockchain_instance() @@ -2156,10 +2181,34 @@ def importaccount(account, roles): return account = Account(account, blockchain_instance=stm) imported = False - password = click.prompt("Account Passphrase", confirmation_prompt=False, hide_input=True) - if not password: - print("You cannot chose an empty Passphrase") - return + if import_coldcard is None: + password = click.prompt("Account Passphrase", confirmation_prompt=False, hide_input=True) + if not password: + print("You cannot chose an empty Passphrase") + return + else: + next_var = "" + password = "" + with open(import_coldcard) as fp: + for line in fp: + if line.strip() == "": + continue + if line.strip() == "WIF (privkey):": + next_var = "wif" + continue + elif "Path Used" in line.strip(): + next_var = "path" + continue + if next_var == "wif": + password = line.strip() + next_var = "" + + if wif > 0: + for _ in range(wif): + pk = PasswordKey("", password, role="") + password = str(pk.get_private()) + password = 'P' + password + if "owner" in roles: owner_key = PasswordKey(account["name"], password, role="owner") owner_pubkey = format(owner_key.get_public_key(), stm.prefix)