bugfix: single key miniscript wallets

This commit is contained in:
scgbckbone 2024-11-29 10:10:34 +01:00 committed by doc-hex
parent b07fb8a6b3
commit 053c9165bb
3 changed files with 134 additions and 2 deletions

View File

@ -826,7 +826,6 @@ class HSMPolicy:
return False
if miniscript:
print("self.share_addrs", self.share_addrs)
return ('msas' in self.share_addrs)
if is_p2sh:

View File

@ -504,7 +504,7 @@ class psbtOutputProxy(psbtProxy):
# - must match expected address for this output, coming from unsigned txn
addr_type, addr_or_pubkey, is_segwit = txo.get_address()
if self.subpaths and len(self.subpaths) == 1:
if self.subpaths and len(self.subpaths) == 1 and not active_miniscript: # miniscript can have one key only
# p2pk, p2pkh, p2wpkh cases
expect_pubkey, = self.subpaths.keys()
elif self.taproot_subpaths and len(self.taproot_subpaths) == 1:

View File

@ -2876,3 +2876,136 @@ def test_big_boy(use_regtest, clear_miniscript, bitcoin_core_signer, get_cc_key,
assert len(unspent) == 11
@pytest.mark.parametrize("af", ["bech32", "bech32m"])
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):
sequence = 10
goto_home()
clear_miniscript()
settings_set("chain", "XRT")
policy = "and_v(v:pk(@0/<0;1>/*),older(10))"
if af == "bech32m":
tmplt = f"tr(tpubD6NzVbkrYhZ4XgXS51CV3bhoP5dJeQqPhEyhKPDXBgEs64VdSyAfku99gtDXQzY6HEXY5Dqdw8Qud1fYiyewDmYjKe9gGJeDx7x936ur4Ju/<0;1>/*,{policy})"
else:
tmplt = f"wsh({policy})"
cc_key = get_cc_key("m/99h/0h/0h").replace('/<0;1>/*', '')
tmplt = tmplt.replace("@0", cc_key)
wname = "single_key_mini"
fname = f"{wname}.txt"
fpath = microsd_path(fname)
with open(fpath, "w") as f:
f.write(tmplt)
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())
unspent = wo.listunspent()
assert len(unspent) == 1
inp = {"txid": unspent[0]["txid"], "vout": unspent[0]["vout"], "sequence": sequence}
# split to 10 utxos
dest_addrs = [wo.getnewaddress(f"a{i}", af) for i in range(10)]
psbt_resp = wo.walletcreatefundedpsbt(
[inp],
[{a: 4} for a in dest_addrs] + [{bitcoind.supply_wallet.getnewaddress(): 5}],
0,
{"fee_rate": 3, "change_type": af, "subtractFeeFromOutputs": [0]},
)
psbt = psbt_resp.get("psbt")
start_sign(base64.b64decode(psbt))
time.sleep(.1)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "Consolidating" not in story
final_psbt = end_sign(True)
final_psbt = base64.b64encode(final_psbt).decode()
res = wo.finalizepsbt(final_psbt)
assert res["complete"]
tx_hex = res["hex"]
res = wo.testmempoolaccept([tx_hex])
# timelocked
assert not res[0]["allowed"]
assert res[0]["reject-reason"] == 'non-BIP68-final'
# mines some blocks to release the lock
bitcoind.supply_wallet.generatetoaddress(sequence, bitcoind.supply_wallet.getnewaddress())
res = wo.testmempoolaccept([tx_hex])
assert res[0]["allowed"]
res = wo.sendrawtransaction(tx_hex)
assert len(res) == 64 # tx id
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress()) # mine above
unspent = wo.listunspent()
assert len(unspent) == 11
# now consolidate to one output
psbt_resp = wo.walletcreatefundedpsbt(
[{"txid": o["txid"], "vout": o["vout"], "sequence": sequence} for o in unspent],
[{wo.getnewaddress("", af): wo.getbalance()}],
0,
{"fee_rate": 3, "change_type": af, "subtractFeeFromOutputs": [0]},
)
psbt = psbt_resp.get("psbt")
start_sign(base64.b64decode(psbt))
time.sleep(.1)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "Consolidating" in story
final_psbt = end_sign(True)
final_psbt = base64.b64encode(final_psbt).decode()
res = wo.finalizepsbt(final_psbt)
assert res["complete"]
tx_hex = res["hex"]
res = wo.testmempoolaccept([tx_hex])
# timelocked
assert not res[0]["allowed"]
assert res[0]["reject-reason"] == 'non-BIP68-final'
# mines some blocks to release the lock
bitcoind.supply_wallet.generatetoaddress(sequence, bitcoind.supply_wallet.getnewaddress())
res = wo.testmempoolaccept([tx_hex])
assert res[0]["allowed"]
res = wo.sendrawtransaction(tx_hex)
assert len(res) == 64 # tx id
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress()) # mine above
unspent = wo.listunspent()
assert len(unspent) == 1