diff --git a/f469-disco b/f469-disco index 4cd201a4..db3ce3e9 160000 --- a/f469-disco +++ b/f469-disco @@ -1 +1 @@ -Subproject commit 4cd201a46485c77149c3ca433d3237284785c867 +Subproject commit db3ce3e918cf0fd36f076ecd86ef05240d7c3cef diff --git a/src/apps/wallets/manager.py b/src/apps/wallets/manager.py index 582a8d2f..30bf8f6e 100644 --- a/src/apps/wallets/manager.py +++ b/src/apps/wallets/manager.py @@ -13,7 +13,7 @@ from .wallet import WalletError, Wallet from .commands import DELETE, EDIT from io import BytesIO -from bcur import bcur_decode_stream, bcur_encode_stream +from bcur import bcur_decode_stream from helpers import a2b_base64_stream, b2a_base64_stream import gc import json @@ -439,7 +439,7 @@ async def confirm_new_wallet(self, w, show_screen): "None of the keys belong to the device.\n\n" "Are you sure you still want to add the wallet?")): return False - return await show_screen(ConfirmWalletScreen(w.name, w.full_policy, keys, w.is_miniscript)) + return await show_screen(ConfirmWalletScreen(w.name, w.full_policy, keys, w.is_complex)) async def showaddr( self, paths: list, script_type: str, redeem_script=None, show_screen=None @@ -792,6 +792,7 @@ def sign_psbtview(self, psbtv, out_stream, wallets, sighash): with open(self.tempdir+"/sigs", "rb") as sig_stream: psbtv.write_to(out_stream, compress=CompressMode.PARTIAL, extra_input_streams=[sig_stream]) + def wipe(self): """Deletes all wallets info""" self.wallets = [] diff --git a/src/apps/wallets/screens.py b/src/apps/wallets/screens.py index 8f61bd01..03da61c7 100644 --- a/src/apps/wallets/screens.py +++ b/src/apps/wallets/screens.py @@ -1,5 +1,5 @@ import lvgl as lv -from gui.common import add_label, add_button, HOR_RES, format_addr, PADDING +from gui.common import add_label, add_button, HOR_RES, format_addr from gui.decorators import on_release from gui.screens import QRAlert, Prompt, Alert from .commands import DELETE, EDIT, MENU @@ -123,10 +123,10 @@ def update_address(self): class ConfirmWalletScreen(Prompt): - def __init__(self, name, policy, keys, is_miniscript=True): + def __init__(self, name, policy, keys, is_complex=True): super().__init__('Add wallet "%s"?' % name, "") self.policy = add_label("Policy: " + policy, y=75, scr=self) - self.is_miniscript = is_miniscript + self.is_complex = is_complex lbl = lv.label(self) lbl.set_text("Canonical xpub SLIP-132 ") @@ -145,10 +145,12 @@ def fill_message(self): msg = "" arg = "slip132" if self.slip_switch.get_state() else "canonical" for i, k in enumerate(self.keys): - alias = "" if not self.is_miniscript else " (%s)" % chr(65+i) + alias = "" if not self.is_complex else " (%s)" % chr(65+i) kstr = str(k[arg]).replace("]","]\n") if k["mine"]: msg += "#7ED321 My key%s: #\n%s\n\n" % (alias, kstr) + elif k["is_nums"]: + msg += "#00CAF1 NUMS key%s: #\nNobody knows private key\n\n" % alias elif k["is_private"]: msg += "#F51E2D Private key%s: #\n%s\n\n" % (alias, kstr) else: @@ -157,10 +159,10 @@ def fill_message(self): # TODO: refactor to remove duplication class WalletInfoScreen(Alert): - def __init__(self, name, policy, keys, is_miniscript=True): + def __init__(self, name, policy, keys, is_complex=True): super().__init__(name, "") self.policy = add_label("Policy: " + policy, y=75, scr=self) - self.is_miniscript = is_miniscript + self.is_complex = is_complex lbl = lv.label(self) lbl.set_text("Canonical xpub SLIP-132 ") @@ -179,10 +181,12 @@ def fill_message(self): msg = "" arg = "slip132" if self.slip_switch.get_state() else "canonical" for i, k in enumerate(self.keys): - alias = "" if not self.is_miniscript else " (%s)" % chr(65+i) + alias = "" if not self.is_complex else " (%s)" % chr(65+i) kstr = str(k[arg]).replace("]","]\n") if k["mine"]: msg += "#7ED321 My key%s: #\n%s\n\n" % (alias, kstr) + elif k["is_nums"]: + msg += "#AAAAAA NUMS key%s: #\nNobody knows private key\n\n" % alias elif k["is_private"]: msg += "#F51E2D Private key%s: #\n%s\n\n" % (alias, kstr) else: diff --git a/src/apps/wallets/wallet.py b/src/apps/wallets/wallet.py index 5b739fbd..7b6b4ad6 100644 --- a/src/apps/wallets/wallet.py +++ b/src/apps/wallets/wallet.py @@ -2,14 +2,13 @@ import platform from platform import maybe_mkdir, delete_recursively import json -from embit import ec, hashes, script +from embit import ec, hashes from embit.networks import NETWORKS from embit.psbt import DerivationPath from embit.descriptor import Descriptor from embit.descriptor.checksum import add_checksum from embit.descriptor.arguments import AllowedDerivation from embit.transaction import SIGHASH -import hashlib from .screens import WalletScreen, WalletInfoScreen from .commands import DELETE, EDIT, MENU, INFO, EXPORT from gui.screens import Menu, QRAlert, Alert @@ -57,7 +56,7 @@ async def show(self, network, show_screen): keys = self.get_key_dicts(network) for k in keys: k["mine"] = True if self.keystore and self.keystore.owns(k["key"]) else False - await show_screen(WalletInfoScreen(self.name, self.full_policy, keys, self.is_miniscript)) + await show_screen(WalletInfoScreen(self.name, self.full_policy, keys, self.is_complex)) continue elif cmd == EXPORT: await self.export_menu(show_screen) @@ -252,6 +251,7 @@ def get_key_dicts(self, network): k["slip132"] = k["key"].to_string(self.Networks[network][ver]) ver = canonical_ver.replace("pub", "prv") if k["is_private"] else canonical_ver k["canonical"] = k["key"].to_string(self.Networks[network][ver]) + k["is_nums"] = (k["key"].sec() == ec.NUMS_PUBKEY.sec()) # nothing up my sleeve return keys def sign_psbt(self, psbt, sighash=SIGHASH.ALL): @@ -348,16 +348,25 @@ def full_policy(self): else: p = "Legacy\n" pp = self.descriptor.full_policy - if not self.is_miniscript: + if not self.is_complex: p += pp else: - p += "Miniscript:\n"+pp.replace(",",", ") + prefix = "Miniscript:\n" if self.is_miniscript else "\n" + p += prefix+pp.replace(",",", ") return p @property def is_miniscript(self): return not (self.descriptor.is_basic_multisig or self.descriptor.is_pkh or self.descriptor.is_taproot) + @property + def is_taptree(self): + return bool(self.descriptor.taptree) + + @property + def is_complex(self): + return self.is_miniscript or self.is_taptree + def __str__(self): return "%s&%s" % (self.name, self.descriptor) diff --git a/test/integration/tests/test_with_rpc.py b/test/integration/tests/test_with_rpc.py index 14c8ea1f..d56dda27 100644 --- a/test/integration/tests/test_with_rpc.py +++ b/test/integration/tests/test_with_rpc.py @@ -1,13 +1,11 @@ -from unittest import TestCase, skip +from unittest import TestCase from util.controller import sim from util.rpc import prepare_rpc import random -import time from embit.descriptor import Descriptor -from embit.bip32 import HDKey from embit.networks import NETWORKS from embit.psbt import PSBT, DerivationPath -from embit.transaction import Transaction, TransactionInput, TransactionOutput, SIGHASH +from embit.transaction import Transaction, SIGHASH from embit import ec, bip32 from embit.script import Witness diff --git a/test/integration/util/__init__.py b/test/integration/util/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/integration/util/misc.py b/test/integration/util/misc.py new file mode 100644 index 00000000..a82905e0 --- /dev/null +++ b/test/integration/util/misc.py @@ -0,0 +1,54 @@ +from embit.descriptor import Descriptor +from embit.networks import NETWORKS +from .rpc import prepare_rpc + +def create_wallet(wname, d1: str, d2: str, rpc=None): + if rpc is None: + rpc = prepare_rpc() + wdefault = rpc.wallet("") + # to derive addresses + desc1 = Descriptor.from_string(d1) + + # recv addr + addr = desc1.derive(0).address(NETWORKS['regtest']) + + # to add checksums + d1 = rpc.getdescriptorinfo(d1)["descriptor"] + d2 = rpc.getdescriptorinfo(d2)["descriptor"] + rpc.createwallet(wname, True, True) + w = rpc.wallet(wname) + info = w.getwalletinfo() + # bitcoin core uses descriptor wallets by default so importmulti may fail + use_descriptors = info.get("descriptors", False) + if not use_descriptors: + res = w.importmulti([{ + "desc": d1, + "internal": False, + "timestamp": "now", + "watchonly": True, + "range": 10, + },{ + "desc": d2, + "internal": True, + "timestamp": "now", + "watchonly": True, + "range": 10, + }],{"rescan": False}) + else: + res = w.importdescriptors([{ + "desc": d1, + "internal": False, + "timestamp": "now", + "watchonly": True, + "active": True, + },{ + "desc": d2, + "internal": True, + "timestamp": "now", + "watchonly": True, + "active": True, + }]) + assert all([k["success"] for k in res]) + wdefault.sendtoaddress(addr, 0.1) + rpc.mine() + return w diff --git a/test/tests/test_compatibility.py b/test/tests/test_compatibility.py index a90a4c24..e479da14 100644 --- a/test/tests/test_compatibility.py +++ b/test/tests/test_compatibility.py @@ -1,5 +1,5 @@ from unittest import TestCase -from apps.compatibility import * +from apps.compatibility import parse_software_wallet_json, parse_cc_wallet_txt import json from io import BytesIO diff --git a/test/tests/test_keystore.py b/test/tests/test_keystore.py index b40be672..b609d897 100644 --- a/test/tests/test_keystore.py +++ b/test/tests/test_keystore.py @@ -1,6 +1,6 @@ from unittest import TestCase from keystore import FlashKeyStore -import os, json +import os import platform TEST_DIR = "testdir" diff --git a/test/tests/test_wallets.py b/test/tests/test_wallets.py index fe00e829..6d672869 100644 --- a/test/tests/test_wallets.py +++ b/test/tests/test_wallets.py @@ -1,8 +1,6 @@ from unittest import TestCase from apps.wallets.wallet import Wallet from embit.descriptor import Key -import os, json -import platform TEST_DIR = "testdir" @@ -60,3 +58,7 @@ def test_invalid_keys(self): Key.parse(k) print(k) + def test_taptree(self): + d = "tr([73c5da0a/2/2/2]tpubDCPwGho2toLmdSELZ3o8v1D6RUUK7Y5keCjMyrSfE75aX2Mcx4MNEM6MnXDZR87GQ1ot4YNn2GGtiN5SvM12c6cvYMrt6avwtYNcRab2HFv/<0;1>/*,or_b(pk([73c5da0a/1/2/3]tpubDCpEkdSHkygNaquCRtW8Fuo3TchAXFSWUuYB9aryim58T4CWM9vLgt26uUV5wdtuvbSk7rWmQQCpcYhGjbHiBzWCYXeyRMJ98zSBWekaJJm/<0;1>/*),s:pk([73c5da0a/3/2/1]tpubDDrLDbxjL1d5FK8djVqUjD3xL1gkhaTXTL1rHzEavwA2ss4YpF8Qm82cKN89PEBRYk6JVTZULA872LuFGENTGdNYASDCrXKKZkU86A8HLqA/<0;1>/*)))" + w = Wallet.parse(d) + print(w) diff --git a/test/tests/util.py b/test/tests/util.py index ffab0eeb..e239df54 100644 --- a/test/tests/util.py +++ b/test/tests/util.py @@ -1,7 +1,6 @@ from keystore.ram import RAMKeyStore from app import BaseApp from apps.wallets import App as WalletsApp -import os, json import platform TEST_DIR = "testdir" @@ -48,4 +47,4 @@ def get_wallets_app(keystore, network): BaseApp.tempdir = TEST_DIR+"/tmp" wapp = WalletsApp(TEST_DIR+"/wallets") wapp.init(keystore, network, show_loader, communicate) - return wapp \ No newline at end of file + return wapp