diff --git a/shared/address_explorer.py b/shared/address_explorer.py index ee87cf0d..c1884fb1 100644 --- a/shared/address_explorer.py +++ b/shared/address_explorer.py @@ -7,7 +7,7 @@ import chains, stash, version from ux import ux_show_story, the_ux, ux_enter_bip32_index from ux import export_prompt_builder, import_export_prompt_decode -from menu import MenuSystem, MenuItem +from menu import MenuSystem, MenuItem, ToggleMenuItem from public_constants import AFC_BECH32, AFC_BECH32M, AF_P2WPKH, AF_P2TR, AF_CLASSIC from wallet import MiniScriptWallet from uasyncio import sleep_ms @@ -199,8 +199,14 @@ class AddressListMenu(MenuSystem): items.append(MenuItem("Custom Path", menu=self.make_custom)) # if they have miniscript wallets, add those next - for msc in MiniScriptWallet.iter_wallets(): - items.append(MenuItem(msc.name, f=self.pick_miniscript, arg=msc)) + if MiniScriptWallet.exists(): + items.append(ToggleMenuItem('MS Scripts/Derivs', 'aemscsv', + ['Default Off', 'Enable'], story=( + "Enable this option to add script(s) and derivations to the CSV export" + " of Miniscript wallets. Default is to only export addresses."))) + + for msc in MiniScriptWallet.iter_wallets(): + items.append(MenuItem(msc.name, f=self.pick_miniscript, arg=msc)) else: items.append(MenuItem("Account: %d" % self.account_num, f=self.change_account)) diff --git a/shared/descriptor.py b/shared/descriptor.py index 0683e6ff..dc959ec1 100644 --- a/shared/descriptor.py +++ b/shared/descriptor.py @@ -18,6 +18,7 @@ class Tapscript: def __init__(self, tree): self.tree = tree # miniscript or (tapscript, tapscript) self._merkle_root = None + self._processed_tree = None def iter_leaves(self): if isinstance(self.tree, Miniscript): @@ -29,8 +30,7 @@ class Tapscript: @property def merkle_root(self): if not self._merkle_root: - _, mr = self.process_tree() - self._merkle_root = mr + self._processed_tree, self._merkle_root = self.process_tree() return self._merkle_root def derive(self, idx, key_map, change=False): @@ -60,6 +60,14 @@ class Tapscript: h = ngu.hash.sha256t(TAP_BRANCH_H, left_h + right_h, True) return left + right, h + # UNUSED - using above proces tree cached result to dump scripts to CSV + # def script_tree(self): + # if isinstance(self.tree, Miniscript): + # return b2a_hex(chains.tapscript_serialize(self.tree.compile())).decode() + # + # l, r = self.tree + # return "{" + l.script_tree() + "," +r.script_tree() + "}" + @classmethod def read_from(cls, s): c = s.read(1) @@ -82,13 +90,6 @@ class Tapscript: ms = Miniscript.read_from(s, taproot=True) return cls(ms) - def script_tree(self): - if isinstance(self.tree, Miniscript): - return b2a_hex(chains.tapscript_serialize(self.tree.compile())).decode() - - l, r = self.tree - return "{" + l.script_tree() + "," +r.script_tree() + "}" - def to_string(self, external=True, internal=True): if isinstance(self.tree, Miniscript): return self.tree.to_string(external, internal) diff --git a/shared/nvstore.py b/shared/nvstore.py index 0de03cd0..bc14425a 100644 --- a/shared/nvstore.py +++ b/shared/nvstore.py @@ -66,6 +66,7 @@ from utils import call_later_ms # hmx = (bool) Force display of current XFP in home menu, even w/o tmp seed active # ccc = (complex) If present, CCC feature is enabled and key details stored here. # ktrx = (privkey) Key teleport Rx has been started, this will be our keypair +# aemscsv = (bool) opt-in enable more verbose CSV output for miniscript wallets with Derivations and Scripts # Stored w/ key=00 for access before login # _skip_pin = hard code a PIN value (dangerous, only for debug) diff --git a/shared/wallet.py b/shared/wallet.py index 6fba4e4a..420fa09a 100644 --- a/shared/wallet.py +++ b/shared/wallet.py @@ -136,7 +136,6 @@ class MasterSingleSigWallet(WalletABC): return self._path + '/%d/%d' % (change_idx, idx) def to_descriptor(self): - from glob import settings from descriptor import Descriptor, Key xfp = settings.get('xfp') xpub = settings.get('xpub') @@ -597,10 +596,15 @@ class MiniScriptWallet(WalletABC): addr = ch.render_address(d.script_pubkey(compiled_scr=scr)) ders = script = None if scripts: - # maybe key.origin.to_string() ?? - ders = ["[%s]" % str(k.origin) for k in d.keys] + ders = "" + for k in d.keys: + ders += "[%s]; " % str(k.origin) + if d.tapscript: - script = d.tapscript.script_tree() + # DFS ordered list of scripts + script = "" + for leaf_ver, scr, _ in d.tapscript._processed_tree: + script += b2a_hex(chains.tapscript_serialize(scr, leaf_ver)).decode() + "; " else: script = b2a_hex(ser_string(scr)).decode() @@ -614,7 +618,7 @@ class MiniScriptWallet(WalletABC): addrs = [] - for idx, addr, *_ in self.yield_addresses(start, n, change, scripts=False): + for idx, addr, *_ in self.yield_addresses(start, n, change): msg += '.../%d =>\n' % idx # just idx, if derivations or scripts needed - export csv addrs.append(addr) msg += show_single_address(addr) + '\n\n' @@ -623,15 +627,17 @@ class MiniScriptWallet(WalletABC): return msg, addrs def generate_address_csv(self, start, n, change): - yield '"' + '","'.join( - ['Index', 'Payment Address'] - ) + '"\n' - for idx, addr, ders, script in self.yield_addresses(start, n, change): + scripts = settings.get("aemscsv", False) + header = ['Index', 'Payment Address'] + if scripts: + header += ['Script', 'Derivations'] + + yield '"' + '","'.join(header) + '"\n' + for idx, addr, ders, script in self.yield_addresses(start, n, change, scripts=scripts): ln = '%d,"%s"' % (idx, addr) - if ders: - ln += ',"%s","' % script - ln += '","'.join(ders) - ln += '"' + if scripts: + ln += ',"%s"' % script + ln += ',"%s"' % ders ln += '\n' yield ln