From 60739f8c3108662e56de7e96d0fe8cb1ed2e3b9a Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holgernahrstaedt@gmx.de>
Date: Tue, 24 Nov 2020 00:25:37 +0100
Subject: [PATCH] Add option to import text file with a bip-85 WIF from a
 coldcard

---
 CHANGELOG.rst |  2 ++
 beem/cli.py   | 67 ++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a144cd8d..2e5db9c0 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 a9bcd1a2..c838fda7 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)
-- 
GitLab