key are only on descriptor level; improve tests

This commit is contained in:
scgbckbone 2025-06-27 18:53:21 +02:00
parent 32d28dea01
commit e9b04ff408
2 changed files with 189 additions and 388 deletions

View File

@ -8,7 +8,7 @@ from collections import OrderedDict
from binascii import hexlify as b2a_hex
from utils import xfp2str
from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR
from public_constants import AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, MAX_SIGNERS, MAX_TR_SIGNERS
from public_constants import AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, MAX_TR_SIGNERS
from desc_utils import parse_desc_str, append_checksum, descriptor_checksum, Key
from miniscript import Miniscript
from precomp_tag_hash import TAP_BRANCH_H
@ -17,7 +17,6 @@ from precomp_tag_hash import TAP_BRANCH_H
class Tapscript:
def __init__(self, tree):
self.tree = tree # miniscript or (tapscript, tapscript)
self._keys = None # de-duped cached keys
self._merkle_root = None
def iter_leaves(self):
@ -27,19 +26,6 @@ class Tapscript:
for ts in self.tree:
yield from ts.iter_leaves()
@property
def keys(self):
if self._keys:
return self._keys
keys = set()
for lv in self.iter_leaves():
for k in lv.keys:
keys.add(k)
self._keys = list(keys) # cache for later
return self._keys
@property
def merkle_root(self):
if not self._merkle_root:
@ -47,25 +33,15 @@ class Tapscript:
self._merkle_root = mr
return self._merkle_root
def derive(self, idx, change=False):
derived_keys = OrderedDict()
for k in self.keys:
dk = k.derive(idx, change=change)
dk.taproot=True
derived_keys[k] = dk
return type(self)(self._derive(self.tree, idx, derived_keys, change))
@staticmethod
def _derive(tree, idx, key_map, change=False):
if isinstance(tree, Miniscript):
tree = tree.derive(idx, key_map, change=change)
def derive(self, idx, key_map, change=False):
if isinstance(self.tree, Miniscript):
tree = self.tree.derive(idx, key_map, change=change)
else:
l, r = tree
tree = [Tapscript(l._derive(l.tree, idx, key_map, change=change)),
Tapscript(r._derive(r.tree, idx, key_map, change=change))]
l, r = self.tree
tree = [l.derive(idx, key_map, change=change),
r.derive(idx, key_map, change=change)]
return tree
return type(self)(tree)
def process_tree(self):
if isinstance(self.tree, Miniscript):
@ -123,7 +99,7 @@ class Tapscript:
class Descriptor:
def __init__(self, key=None, miniscript=None, tapscript=None, addr_fmt=None):
def __init__(self, key=None, miniscript=None, tapscript=None, addr_fmt=None, keys=None):
if addr_fmt in [AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH]:
assert miniscript
assert not key
@ -136,6 +112,8 @@ class Descriptor:
self.miniscript = miniscript
self.tapscript = tapscript
self.addr_fmt = addr_fmt
# cached keys
self._keys = keys
def validate(self):
# should only be run once while importing wallet
@ -172,7 +150,7 @@ class Descriptor:
assert c <= max_signers, "max signers"
assert has_mine != 0, 'My key %s missing in descriptor.' % xfp2str(my_xfp).upper()
assert has_mine > 0, 'My key %s missing in descriptor.' % xfp2str(my_xfp).upper()
def bip388_wallet_policy(self):
keys_info = OrderedDict()
@ -235,29 +213,53 @@ class Descriptor:
@property
def keys(self):
if self._keys:
return self._keys
if self.tapscript:
# internal is always first
# otherwise order of keys is not preserved after set operations
return [self.key] + self.tapscript.keys
# otherwise order of keys is not preserved (after set ops)
keys = set()
for lv in self.tapscript.iter_leaves():
for k in lv.keys:
keys.add(k)
self._keys = [self.key] + list(keys)
elif self.miniscript:
return self.miniscript.keys
self._keys = self.miniscript.keys
# single-sig
return [self.key]
else:
# single-sig
self._keys = [self.key]
return self._keys
def derive(self, idx=None, change=False):
# derive keys first
derived_keys = OrderedDict()
for i, k in enumerate(self.keys):
if not i and self.is_taproot:
# internal key is always at index 0 in self.keys
# ik is derived few lines later
continue
dk = k.derive(idx, change=change)
dk.taproot=self.is_taproot
derived_keys[k] = dk
if self.is_taproot:
return type(self)(
self.key.derive(idx, change=change),
tapscript=self.tapscript.derive(idx, change=change),
tapscript=self.tapscript.derive(idx, derived_keys, change=change),
addr_fmt=self.addr_fmt,
keys=list(derived_keys.values()),
)
if self.miniscript:
return type(self)(
None,
self.miniscript.derive(idx, change=change),
self.miniscript.derive(idx, derived_keys, change=change),
addr_fmt=self.addr_fmt,
keys=list(derived_keys.values())
)
# single-sig

View File

@ -22,7 +22,10 @@ TREE = {
7: '{{%s,{%s,%s}},{%s,{%s,{%s,%s}}}}',
8: '{{{%s,%s},{%s,%s}},{{%s,%s},{%s,%s}}}',
# more than MAX (4) for test purposes
9: '{{{%s,{%s,%s}},{%s,%s}},{{%s,%s},{%s,%s}}}'
9: '{{{%s,{%s,%s}},{%s,%s}},{{%s,%s},{%s,%s}}}',
10: '{{{{%s,%s},{%s,%s}},{%s,%s}},{{%s,%s},{%s,%s}}}',
11: '{{{{%s,%s},{%s,%s}},{%s,%s}},{{%s,%s},{%s,{%s,%s}}}}',
12: '{{{{%s,%s},{%s,%s}},{%s,%s}},{{%s,%s},{{%s,%s},{%s,%s}}}}',
}
@ -405,12 +408,62 @@ def address_explorer_check(goto_home, pick_menu_item, need_keypress, cap_menu,
return doit
@pytest.fixture
def create_core_wallet(goto_home, pick_menu_item, load_export, bitcoind):
def doit(name, addr_type, way="sd", funded=True):
try:
pick_menu_item(name) # pick imported descriptor multisig wallet
except:
# probably not in Miniscript
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
pick_menu_item(name)
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export(way, label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# watch only wallet where miniscript descriptor will be imported
ms = bitcoind.create_wallet(
wallet_name=name, disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
# import descriptors to watch only wallet
res = ms.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
if funded:
addr = ms.getnewaddress("", addr_type)
if addr_type == "bech32":
sw = "bcrt1q"
elif addr_type == "bech32m":
sw = "bcrt1p"
else:
sw = "2"
assert addr.startswith(sw)
# get some coins and fund above multisig address
bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress()) # mine above
return ms
return doit
@pytest.fixture
def bitcoind_miniscript(bitcoind, need_keypress, cap_story, load_export,
pick_menu_item, goto_home, cap_menu, microsd_path,
use_regtest, get_cc_key, import_miniscript,
bitcoin_core_signer, import_duplicate, press_select,
virtdisk_path, garbage_collector):
virtdisk_path, garbage_collector, create_core_wallet):
def doit(M, N, script_type, internal_key=None, cc_account=0, funded=True,
tapscript_threshold=False, add_own_pk=False, same_account=False, way="sd"):
@ -423,11 +476,6 @@ def bitcoind_miniscript(bitcoind, need_keypress, cap_story, load_export,
bitcoind_signers.append(s)
bitcoind_signers_xpubs.append(core_key)
# watch only wallet where multisig descriptor will be imported
ms = bitcoind.create_wallet(
wallet_name=f"watch_only_{script_type}_{M}of{N}", disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
me_pth = f"m/48h/1h/{cc_account}h/3h"
me = get_cc_key(me_pth)
ik = internal_key or ranged_unspendable_internal_key()
@ -499,56 +547,21 @@ def bitcoind_miniscript(bitcoind, need_keypress, cap_story, load_export,
assert name in story
assert "Press (1) to see extended public keys" in story
if script_type == "p2wsh":
af = "bech32"
assert "P2WSH" in story
elif script_type == "p2sh":
af = "legacy"
assert "P2SH" in story
elif script_type == "p2tr":
af = "bech32m"
assert "P2TR" in story
else:
af = "p2sh-segwit"
assert "P2SH-P2WSH" in story
# assert "Derivation:\n Varies (2)" in story
press_select() # approve multisig import
import_duplicate(fname, way=way, data=data)
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
menu = cap_menu()
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export(way, label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# import descriptors to watch only wallet
res = ms.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
if funded:
if script_type == "p2wsh":
addr_type = "bech32"
elif script_type == "p2tr":
addr_type = "bech32m"
elif script_type == "p2sh":
addr_type = "legacy"
else:
addr_type = "p2sh-segwit"
addr = ms.getnewaddress("", addr_type)
if script_type == "p2wsh":
sw = "bcrt1q"
elif script_type == "p2tr":
sw = "bcrt1p"
else:
sw = "2"
assert addr.startswith(sw)
# get some coins and fund above multisig address
bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress()) # mine above
ms = create_core_wallet(name, af, way, funded)
return ms, bitcoind_signers
@ -569,12 +582,13 @@ def bitcoind_miniscript(bitcoind, need_keypress, cap_story, load_export,
"or_d(pk(@A),and_v(v:multi(2,@B,@C),locktime(N)))",
])
def test_liana_miniscripts_simple(addr_fmt, recovery, lt_type, minisc, clear_miniscript, goto_home,
pick_menu_item, cap_menu, cap_story, microsd_path, way,
use_regtest, bitcoind, microsd_wipe, load_export, dev,
def test_liana_miniscripts_simple(addr_fmt, recovery, lt_type, minisc, clear_miniscript,
pick_menu_item, cap_story, microsd_path, way, dev,
use_regtest, bitcoind, microsd_wipe, load_export,
address_explorer_check, get_cc_key, import_miniscript,
bitcoin_core_signer, import_duplicate, press_select,
virtdisk_path, skip_if_useless_way, garbage_collector):
virtdisk_path, skip_if_useless_way, garbage_collector,
create_core_wallet, goto_home):
skip_if_useless_way(way)
normal_cosign_core = False
recovery_cosign_core = False
@ -633,41 +647,18 @@ def test_liana_miniscripts_simple(addr_fmt, recovery, lt_type, minisc, clear_min
with open(fpath, "w") as f:
f.write(desc)
wo = bitcoind.create_wallet(wallet_name=name, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname, way=way, data=data)
try:
assert "Create new miniscript wallet?" in story
except:
time.sleep(.2)
_, story = cap_story()
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
time.sleep(.2)
assert "Create new miniscript wallet?" in story
press_select()
import_duplicate(fname, way=way, data=data)
menu = cap_menu()
assert menu[0] == name
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export(way, label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", addr_fmt)
addr_dest = wo.getnewaddress("", addr_fmt) # self-spend
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(name, addr_fmt, way, True)
all_of_it = wo.getbalance()
unspent = wo.listunspent()
assert len(unspent) == 1
addr_dest = wo.getnewaddress("", addr_fmt) # self-spend
inp = {"txid": unspent[0]["txid"], "vout": unspent[0]["vout"]}
if recovery and sequence:
inp["sequence"] = sequence
@ -745,7 +736,7 @@ def test_liana_miniscripts_complex(addr_fmt, minsc, bitcoind, use_regtest, clear
load_export, goto_home, address_explorer_check, cap_menu,
get_cc_key, import_miniscript, bitcoin_core_signer,
import_duplicate, press_select, way, skip_if_useless_way,
garbage_collector):
garbage_collector, create_core_wallet):
skip_if_useless_way(way)
use_regtest()
clear_miniscript()
@ -803,33 +794,16 @@ def test_liana_miniscripts_complex(addr_fmt, minsc, bitcoind, use_regtest, clear
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=name, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname, way=way, data=data)
time.sleep(.2)
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
import_duplicate(fname, way=way, data=data)
menu = cap_menu()
assert menu[0] == name
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export(way, label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", addr_fmt)
wo = create_core_wallet(name, addr_fmt, way, True)
addr_dest = wo.getnewaddress("", addr_fmt) # self-spend
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
unspent = wo.listunspent()
assert len(unspent) == 1
inp = {"txid": unspent[0]["txid"], "vout": unspent[0]["vout"]}
@ -1076,7 +1050,7 @@ def test_tapscript_pk(num_leafs, use_regtest, clear_miniscript, microsd_wipe, bi
internal_key_spendable, dev, microsd_path, get_cc_key,
pick_menu_item, cap_story, goto_home, cap_menu, load_export,
import_miniscript, bitcoin_core_signer, import_duplicate,
press_select, garbage_collector):
press_select, garbage_collector, create_core_wallet):
use_regtest()
clear_miniscript()
microsd_wipe()
@ -1101,11 +1075,6 @@ def test_tapscript_pk(num_leafs, use_regtest, clear_miniscript, microsd_wipe, bi
random.shuffle(leafs)
desc = f"tr({internal_key},{tmplt % (*leafs,)})"
ts = bitcoind.create_wallet(
wallet_name=f"watch_only_pk_ts", disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
fname = "ts_pk.txt"
fpath = microsd_path(fname)
with open(fpath, "w") as f:
@ -1120,28 +1089,13 @@ def test_tapscript_pk(num_leafs, use_regtest, clear_miniscript, microsd_wipe, bi
press_select()
import_duplicate(fname)
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
menu = cap_menu()
pick_menu_item(menu[0])
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# import descriptors to watch only wallet
res = ts.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = ts.getnewaddress("", "bech32m")
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
ts = create_core_wallet(menu[0], "bech32m", "sd", True)
dest_addr = ts.getnewaddress("", "bech32m") # selfspend
psbt = ts.walletcreatefundedpsbt([], [{dest_addr: 1.0}], 0, {"fee_rate": 2})["psbt"]
@ -1220,14 +1174,15 @@ def test_tapscript_import_export(clear_miniscript, pick_menu_item, cap_story,
def test_duplicate_tapscript_leaves(use_regtest, clear_miniscript, microsd_wipe, bitcoind, dev,
goto_home, pick_menu_item, microsd_path, import_miniscript,
cap_story, load_export, get_cc_key, garbage_collector,
bitcoin_core_signer, import_duplicate, press_select):
bitcoin_core_signer, import_duplicate, press_select,
create_core_wallet):
# works in core - but some discussions are ongoing
# https://github.com/bitcoin/bitcoin/issues/27104
# CC also allows this for now... (experimental branch)
use_regtest()
clear_miniscript()
microsd_wipe()
ss, core_key = bitcoin_core_signer(f"dup_leafs")
ss, core_key = bitcoin_core_signer(f"s1_dup_leafs")
cc_key = get_cc_key("86h/0h/100h")
cc_leaf = f"pk({cc_key})"
@ -1250,32 +1205,8 @@ def test_duplicate_tapscript_leaves(use_regtest, clear_miniscript, microsd_wipe,
press_select()
import_duplicate(fname)
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
pick_menu_item(fname.split(".")[0])
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# wo wallet
ts = bitcoind.create_wallet(
wallet_name=f"dup_leafs_wo", disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
# import descriptors to watch only wallet
res = ts.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = ts.getnewaddress("", "bech32m")
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
ts = create_core_wallet(fname.split(".")[0], "bech32m", "sd", True)
dest_addr = ts.getnewaddress("", "bech32m") # selfspend
psbt = ts.walletcreatefundedpsbt([], [{dest_addr: 1.0}], 0, {"fee_rate": 2})["psbt"]
@ -1322,7 +1253,7 @@ def test_duplicate_tapscript_leaves(use_regtest, clear_miniscript, microsd_wipe,
def test_same_key_account_based_minisc(goto_home, pick_menu_item, cap_story,
clear_miniscript, microsd_path, load_export, bitcoind,
import_miniscript, use_regtest, import_duplicate,
press_select, garbage_collector):
press_select, garbage_collector, create_core_wallet):
clear_miniscript()
use_regtest()
@ -1346,32 +1277,8 @@ def test_same_key_account_based_minisc(goto_home, pick_menu_item, cap_story,
press_select()
import_duplicate(fname)
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
pick_menu_item(fname.split(".")[0])
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# wo wallet
wo = bitcoind.create_wallet(
wallet_name=f"multi-account", disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
# import descriptors to watch only wallet
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", "bech32")
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(fname.split(".")[0], "bech32", "sd", True)
dest_addr = wo.getnewaddress("", "bech32") # selfspend
psbt = wo.walletcreatefundedpsbt([], [{dest_addr: 1.0}], 0, {"fee_rate": 2})["psbt"]
@ -1463,7 +1370,7 @@ CHANGE_BASED_DESCS = [
def test_same_key_change_based_minisc(goto_home, pick_menu_item, cap_story,
clear_miniscript, microsd_path, load_export, bitcoind,
import_miniscript, address_explorer_check, use_regtest,
desc, press_select, garbage_collector):
desc, press_select, garbage_collector, create_core_wallet):
clear_miniscript()
use_regtest()
if desc.startswith("tr("):
@ -1482,34 +1389,9 @@ def test_same_key_change_based_minisc(goto_home, pick_menu_item, cap_story,
assert "Create new miniscript wallet?" in story
assert fname.split(".")[0] in story
assert "Press (1) to see extended public keys" in story
press_select()
goto_home()
pick_menu_item('Settings')
pick_menu_item('Miniscript')
pick_menu_item(fname.split(".")[0])
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
# wo wallet
wo = bitcoind.create_wallet(
wallet_name=f"minsc-change", disable_private_keys=True,
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
)
# import descriptors to watch only wallet
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", af)
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(name, af, "sd", True)
dest_addr = wo.getnewaddress("", af) # selfspend
psbt = wo.walletcreatefundedpsbt([], [{dest_addr: 1.0}], 0, {"fee_rate": 2})["psbt"]
@ -1645,7 +1527,6 @@ def test_tapscript_depth(get_cc_key, pick_menu_item, cap_story,
@pytest.mark.parametrize("same_acct", [True, False])
@pytest.mark.parametrize("recovery", [True, False])
@pytest.mark.parametrize("leaf2_mine", [True, False])
# @pytest.mark.parametrize("internal_type", ["unspend(", "xpub"])
@pytest.mark.parametrize("minisc", [
"or_d(pk(@A),and_v(v:pkh(@B),locktime(N)))",
@ -1660,7 +1541,7 @@ def test_minitapscript(leaf2_mine, recovery, minisc, clear_miniscript, goto_home
use_regtest, bitcoind, microsd_wipe, load_export, dev,
address_explorer_check, get_cc_key, import_miniscript,
bitcoin_core_signer, same_acct, import_duplicate, press_select,
garbage_collector, start_sign, end_sign):
garbage_collector, start_sign, end_sign, create_core_wallet):
lt_type = "older"
# needs bitcoind 26.0
normal_cosign_core = False
@ -1726,33 +1607,14 @@ def test_minitapscript(leaf2_mine, recovery, minisc, clear_miniscript, goto_home
f.write(desc)
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=name, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname)
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
import_duplicate(fname)
menu = cap_menu()
assert menu[0] == name
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", "bech32m")
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(name, "bech32m", "sd", True)
all_of_it = wo.getbalance()
unspent = wo.listunspent()
assert len(unspent) == 1
@ -1914,7 +1776,7 @@ def test_timelock_mixin():
def test_d_wrapper(addr_fmt, bitcoind, get_cc_key, goto_home, pick_menu_item, cap_story, cap_menu,
load_export, microsd_path, use_regtest, clear_miniscript, cc_first,
address_explorer_check, import_miniscript, bitcoin_core_signer, press_select,
garbage_collector):
garbage_collector, create_core_wallet):
# check D wrapper u property for segwit v0 and v1
# https://github.com/bitcoin/bitcoin/pull/24906/files
@ -1946,9 +1808,6 @@ def test_d_wrapper(addr_fmt, bitcoind, get_cc_key, goto_home, pick_menu_item, ca
f.write(desc)
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=name, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
clear_miniscript()
use_regtest()
_, story = import_miniscript(fname)
@ -1960,26 +1819,10 @@ def test_d_wrapper(addr_fmt, bitcoind, get_cc_key, goto_home, pick_menu_item, ca
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
menu = cap_menu()
assert menu[0] == name
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
addr = wo.getnewaddress("", addr_fmt) # self-spend
wo = create_core_wallet(name, addr_fmt, "sd", True)
addr_dest = wo.getnewaddress("", addr_fmt) # self-spend
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
all_of_it = wo.getbalance()
unspent = wo.listunspent()
assert len(unspent) == 1
@ -2528,7 +2371,8 @@ aA+bduIqWCMpBW2K5F0FuxfY4ofjfGWLKDMAAAgAEAAIAAAACAAgAAgAEAAAADAAAAAA=="""
def test_expanding_multisig(tmplt, clear_miniscript, goto_home, pick_menu_item, garbage_collector,
cap_menu, cap_story, microsd_path, use_regtest, bitcoind, microsd_wipe,
load_export, dev, address_explorer_check, get_cc_key, import_miniscript,
bitcoin_core_signer, import_duplicate, press_select, start_sign, end_sign):
bitcoin_core_signer, import_duplicate, press_select, start_sign, end_sign,
create_core_wallet):
use_regtest()
clear_miniscript()
sequence = 10
@ -2566,33 +2410,12 @@ def test_expanding_multisig(tmplt, clear_miniscript, goto_home, pick_menu_item,
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=wname, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname)
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
menu = cap_menu()
assert menu[0] == wname
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
# fund wallet
addr = wo.getnewaddress("", af)
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(wname, af, "sd", True)
# use non-recovery path to split into 5 utxos + 1 going back to supply (not a conso)
unspent = wo.listunspent()
@ -2727,7 +2550,8 @@ def test_expanding_multisig(tmplt, clear_miniscript, goto_home, pick_menu_item,
@pytest.mark.parametrize("blinded", [True, False])
def test_big_boy(use_regtest, clear_miniscript, bitcoin_core_signer, get_cc_key, microsd_path,
garbage_collector, pick_menu_item, bitcoind, import_miniscript, press_select,
cap_story, cap_menu, load_export, start_sign, end_sign, blinded):
cap_story, cap_menu, load_export, start_sign, end_sign, blinded,
create_core_wallet):
# keys (@0,@4,@5) are more important (primary) than keys (@1,@2,@3) (secondary)
# currently requires to tweak MAX_TR_SIGNERS = 33
# with blinded=True, all co-signer keys are blinded (have no key origin info)
@ -2770,33 +2594,12 @@ def test_big_boy(use_regtest, clear_miniscript, bitcoin_core_signer, get_cc_key,
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=wname, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname)
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
menu = cap_menu()
assert menu[0] == wname
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
# fund wallet
addr = wo.getnewaddress("", af)
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(wname, af, "sd", True)
unspent = wo.listunspent()
assert len(unspent) == 1
@ -2841,7 +2644,7 @@ def test_big_boy(use_regtest, clear_miniscript, bitcoin_core_signer, get_cc_key,
def test_single_key_miniscript(af, settings_set, clear_miniscript, goto_home, get_cc_key,
garbage_collector, microsd_path, bitcoind, import_miniscript,
press_select, cap_menu, pick_menu_item, load_export, cap_story,
start_sign, end_sign):
start_sign, end_sign, create_core_wallet):
sequence = 10
goto_home()
clear_miniscript()
@ -2864,33 +2667,12 @@ def test_single_key_miniscript(af, settings_set, clear_miniscript, goto_home, ge
garbage_collector.append(fpath)
wo = bitcoind.create_wallet(wallet_name=wname, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
_, story = import_miniscript(fname)
assert "Create new miniscript wallet?" in story
# do some checks on policy --> helper function to replace keys with letters
press_select()
menu = cap_menu()
assert menu[0] == wname
pick_menu_item(menu[0]) # pick imported descriptor multisig wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
# fund wallet
addr = wo.getnewaddress("", af)
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(wname, af, "sd", True)
unspent = wo.listunspent()
assert len(unspent) == 1
@ -2981,7 +2763,7 @@ def test_single_key_miniscript(af, settings_set, clear_miniscript, goto_home, ge
def test_originless_keys(tmplt, offer_minsc_import, get_cc_key, bitcoin_core_signer, bitcoind,
pick_menu_item, load_export, goto_home, cap_menu, clear_miniscript,
use_regtest, press_select, start_sign, end_sign, cap_story, cc_sign,
has_orig, address_explorer_check):
has_orig, address_explorer_check, create_core_wallet):
# can be both:
# a.) just ranged xpub without origin info -> xpub1/<0;1>/*
# b.) ranged xpub with its fp -> [xpub1_fp]xpub1/<0;1>/*
@ -3006,32 +2788,7 @@ def test_originless_keys(tmplt, offer_minsc_import, get_cc_key, bitcoin_core_sig
offer_minsc_import(json.dumps(to_import))
press_select()
wo = bitcoind.create_wallet(wallet_name=name, disable_private_keys=True, blank=True,
passphrase=None, avoid_reuse=False, descriptors=True)
goto_home()
pick_menu_item("Settings")
pick_menu_item("Miniscript")
menu = cap_menu()
assert menu[0] == name
pick_menu_item(menu[0]) # pick imported descriptor miniscript wallet
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
r2 = text.find("]", -1, 0)
text = text[r1: r2]
core_desc_object = json.loads(text)
res = wo.importdescriptors(core_desc_object)
for obj in res:
assert obj["success"]
# fund wallet
addr = wo.getnewaddress("", af)
assert bitcoind.supply_wallet.sendtoaddress(addr, 49)
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
wo = create_core_wallet(name, af, "sd", True)
unspent = wo.listunspent()
assert len(unspent) == 1
@ -3104,4 +2861,46 @@ def test_static_internal_key(internal_key, clear_miniscript, microsd_path, pick_
title, story = import_miniscript(fname)
assert "Failed to import" in story
assert "only extended keys allowed" in story
assert "only extended keys allowed" in story
@pytest.mark.bitcoind
def test_csa_tapscript(clear_miniscript, bitcoin_core_signer, get_cc_key,
use_regtest, address_explorer_check, bitcoind,
offer_minsc_import, create_core_wallet, press_select):
use_regtest()
clear_miniscript()
M, N = 11, 12
bitcoind_signers = []
bitcoind_signers_xpubs = []
for i in range(N - 1):
s, core_key = bitcoin_core_signer(f"bitcoind--signer{i}")
s.keypoolrefill(10)
bitcoind_signers.append(s)
bitcoind_signers_xpubs.append(core_key)
me = get_cc_key(f"m/48h/1h/0h/3h")
ik = ranged_unspendable_internal_key()
signers_xp = [me] + bitcoind_signers_xpubs
assert len(signers_xp) == N
desc = f"tr({ik},%s)"
scripts = []
for c in itertools.combinations(signers_xp, M):
tmplt = f"multi_a({M},{','.join(c)})"
scripts.append(tmplt)
assert len(scripts) == 12
temp = TREE[len(scripts)]
temp = temp % tuple(scripts)
desc = desc % temp
title, story = offer_minsc_import(desc)
name = story.split("\n")[3].strip()
assert "Create new miniscript wallet?" in story
press_select()
ms_wo = create_core_wallet(name, "bech32m", "sd", False)
address_explorer_check("sd", "bech32m", ms_wo, "minisc")