optimize PSBT class

This commit is contained in:
scgbckbone 2025-08-15 13:20:12 +02:00
parent 5a27fcc45a
commit 4439f3d7fe
14 changed files with 831 additions and 764 deletions

View File

@ -356,31 +356,20 @@ class ApproveTransaction(UserAuthorizedAction):
# Do some analysis/ validation
try:
await self.psbt.validate() # might do UX: accept multisig import
dis.progress_sofar(10, 100)
# consider_keys only needs num_our_keys to be set
# it set during psbt.validate()
self.psbt.consider_keys()
dis.progress_sofar(20, 100)
ccc_c_xfp = CCCFeature.get_xfp() # can be None
self.psbt.consider_inputs(cosign_xfp=ccc_c_xfp)
dis.progress_sofar(50, 100)
self.psbt.consider_outputs()
dis.progress_sofar(75, 100)
self.psbt.consider_dangerous_sighash()
dis.progress_sofar(90, 100)
self.psbt.consider_outputs(cosign_xfp=ccc_c_xfp)
except FraudulentChangeOutput as exc:
# sys.print_exception(exc)
#print('FraudulentChangeOutput: ' + exc.args[0])
return await self.failure(exc.args[0], title='Change Fraud')
except FatalPSBTIssue as exc:
#print('FatalPSBTIssue: ' + exc.args[0])
return await self.failure(exc.args[0])
except BaseException as exc:
sys.print_exception(exc)
# sys.print_exception(exc)
del self.psbt
gc.collect()
@ -932,7 +921,7 @@ async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_
del_after = settings.get('del', 0)
def _chunk_write(file_d, ofs, chunk=4096):
def _chunk_write(file_d, ofs, chunk=1024):
written = 0
while written < data_len:
if (written + chunk) > data_len:

View File

@ -393,8 +393,8 @@ class Key:
return cls.from_cc_data(vals["xfp"], vals["%s_deriv" % af_str], ek)
@classmethod
def from_psbt_xpub(cls, pth, ek_bytes):
xfp, *path = ustruct.unpack_from('<%dI' % (len(pth)//4), pth, 0)
def from_psbt_xpub(cls, ek_bytes, xfp_path):
xfp, *path = xfp_path
koi = KeyOriginInfo(a2b_hex(xfp2str(xfp)), path)
# TODO this should be done by C code, no need to base58 encode/decode
# byte-serialized key should be decodable

View File

@ -349,7 +349,7 @@ class ApprovalRule:
# we are verifying the whole consensus-encoded txout
txo_bytes = CTxOut(txo.nValue, txo.scriptPubKey).serialize()
digest = chain.hash_message(txo_bytes)
addr_fmt, pubkey = chains.verify_recover_pubkey(o.attestation, digest)
addr_fmt, pubkey = chains.verify_recover_pubkey(psbt.get(o.attestation), digest)
# we have extracted a valid pubkey from the sig, but is it
# a whitelisted pubkey or something else?
ver_addr = chain.pubkey_to_address(pubkey, addr_fmt)
@ -372,11 +372,11 @@ class ApprovalRule:
# check the self-transfer percentage
if self.min_pct_self_transfer:
own_in_value = sum([i.amount for i in psbt.inputs if i.num_our_keys])
own_in_value = sum([i.amount for i in psbt.inputs if i.sp_idxs])
own_out_value = 0
for idx, txo in psbt.output_iter():
o = psbt.outputs[idx]
if o.num_our_keys:
if o.sp_idxs:
own_out_value += txo.nValue
percentage = (float(own_out_value) / own_in_value) * 100.0
assert percentage >= self.min_pct_self_transfer, 'does not meet self transfer threshold, expected: %.2f, actual: %.2f' % (self.min_pct_self_transfer, percentage)
@ -387,8 +387,8 @@ class ApprovalRule:
assert len(psbt.inputs) == len(psbt.outputs), 'unequal number of inputs and outputs'
if "EQ_NUM_OWN_INS_OUTS" in self.patterns:
own_ins = sum([1 for i in psbt.inputs if i.num_our_keys])
own_outs = sum([1 for o in psbt.outputs if o.num_our_keys])
own_ins = sum([1 for i in psbt.inputs if i.sp_idxs])
own_outs = sum([1 for o in psbt.outputs if o.sp_idxs])
assert own_ins == own_outs, 'unequal number of own inputs and outputs'
if "EQ_OUT_AMOUNTS" in self.patterns:
@ -488,7 +488,7 @@ class HSMPolicy:
# a list of paths we can accept for signing
self.msg_paths = pop_deriv_list(j, 'msg_paths', ['any'])
self.share_xpubs = pop_deriv_list(j, 'share_xpubs', ['any'])
self.share_addrs = pop_deriv_list(j, 'share_addrs', ['p2sh', 'any', 'msas'])
self.share_addrs = pop_deriv_list(j, 'share_addrs', ['any', 'msas'])
# free text shown at top
self.notes = pop_string(j, 'notes', 1, 80)
@ -573,7 +573,7 @@ class HSMPolicy:
fd.write('\n')
def plist(pl):
remap = {'any': '(any path)', 'p2sh': '(any P2SH)' }
remap = {'any': '(any path)', 'msas': '(any miniscript)' }
return ' OR '.join(remap.get(i, i) for i in pl)
fd.write('\nMessage signing:\n')
@ -603,7 +603,7 @@ class HSMPolicy:
fd.write('- XPUB values will be shared, if path matches: m OR %s.\n'
% plist(self.share_xpubs))
if self.share_addrs:
fd.write('- Address values values will be shared, if path matches: %s.\n'
fd.write('- Address values will be shared, if path matches: %s.\n'
% plist(self.share_addrs))
if self.priv_over_ux:
fd.write('- Status responses optimized for privacy.\n')

File diff suppressed because it is too large Load Diff

View File

@ -754,8 +754,6 @@ async def kt_send_file_psbt(*a):
psbt.consider_inputs()
dis.progress_sofar(3, 4)
psbt.consider_keys()
except Exception as exc:
# not going to do full reporting here, use our other code for that!
await ux_show_story("Cannot validate PSBT?\n\n"+str(exc), "PSBT Load Failed")

View File

@ -53,7 +53,7 @@ HSM_WHITELIST = frozenset({
'blkc', 'hsts', # report status values
'stok', 'smok', # completion check: sign txn or msg
'xpub', 'msck', # quick status checks
'p2sh', 'show', 'msas', # limited by HSM policy
'show', 'msas', # limited by HSM policy
'user', # auth HSM user, other user cmds not allowed
'gslr', # read storage locker; hsm mode only, limited usage
})

View File

@ -510,8 +510,8 @@ class MiniScriptWallet(WalletABC):
has_mine = 0
keys = []
for k, v in xpubs_list:
k = Key.from_psbt_xpub(k, v)
for ek, xfp_pth in xpubs_list:
k = Key.from_psbt_xpub(ek, xfp_pth)
has_mine += k.validate(my_xfp, cls.disable_checks)
keys.append(k)
@ -526,8 +526,8 @@ class MiniScriptWallet(WalletABC):
def validate_psbt_xpubs(self, psbt_xpubs):
keys = set()
for k, v in psbt_xpubs:
key = Key.from_psbt_xpub(k, v)
for ek, xfp_pth in psbt_xpubs:
key = Key.from_psbt_xpub(ek, xfp_pth)
key.validate(settings.get('xfp', 0), self.disable_checks)
keys.add(key)

View File

@ -4,32 +4,40 @@
from h import a2b_hex, b2a_hex
from serializations import CTxOut
from uio import BytesIO
from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2SH
cases = [
# TxOut, type, is_segwit, hash160/pubkey,
( 'c4f33d0000000000160014ad46a001d55bd55d157e716bf17c02f8964b5a19',
'p2wpkh',
'ad46a001d55bd55d157e716bf17c02f8964b5a19' ),
( '202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac',
'p2pkh',
'8280b37df378db99f66f85c95a783a76ac7a6d59' ),
(
'c4f33d0000000000160014ad46a001d55bd55d157e716bf17c02f8964b5a19',
AF_P2WPKH,
'ad46a001d55bd55d157e716bf17c02f8964b5a19'
),
(
'202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac',
AF_CLASSIC,
'8280b37df378db99f66f85c95a783a76ac7a6d59'
),
# from legendary txid: 40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8
( '301b0f000000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a87',
'p2sh',
'e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a'),
(
'301b0f000000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a87',
AF_P2SH,
'e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a'
),
# from testnet: a4c89e0ffb84d06a1e62f0f9f0f5974db250878caa1f71f9992a1f865b8ff2fa
# via <https://github.com/bitcoinjs/bitcoinjs-lib/issues/856>
( 'b88201000000000017a914f0ca58dc8e539421a3cb4a9c22c059973075287c87',
'p2sh',
'f0ca58dc8e539421a3cb4a9c22c059973075287c'),
(
'b88201000000000017a914f0ca58dc8e539421a3cb4a9c22c059973075287c87',
AF_P2SH,
'f0ca58dc8e539421a3cb4a9c22c059973075287c'
),
# XXX missing: P2SH segwit, 1of1 and N of M
( 'd0f13d0000000000160014f2369bac6d24ed11313fa65adda1971d10e17bff',
'p2wpkh',
'f2369bac6d24ed11313fa65adda1971d10e17bff')
(
'd0f13d0000000000160014f2369bac6d24ed11313fa65adda1971d10e17bff',
AF_P2WPKH,
'f2369bac6d24ed11313fa65adda1971d10e17bff'
)
]
for raw_txo, expect_type, expect_hash in cases:

View File

@ -14,7 +14,7 @@ from pysecp256k1 import ec_seckey_verify, ec_pubkey_parse, ec_pubkey_serialize,
from mnemonic import Mnemonic
from bip32 import BIP32Node
from constants import AF_P2WSH
from charcodes import KEY_QR
from charcodes import KEY_QR, KEY_DELETE
from bbqr import split_qrs
from psbt import BasicPSBT
@ -1026,7 +1026,8 @@ def test_ccc_xpub_export(chain, c_num_words, acct, settings_set, load_export, se
def test_multiple_multisig_wallets(settings_set, setup_ccc, enter_enabled_ccc, ccc_ms_setup,
bitcoind_create_watch_only_wallet, cap_story, bitcoind,
policy_sign, settings_get, cap_menu, pick_menu_item,
press_select, load_export, offer_minsc_import, goto_home):
press_select, load_export, offer_minsc_import, goto_home,
need_keypress, is_q1, enter_text):
# - 'build 2-of-N' path
goto_home()
settings_set("ccc", None)
@ -1090,7 +1091,8 @@ def test_multiple_multisig_wallets(settings_set, setup_ccc, enter_enabled_ccc, c
assert mi not in m
# export one of the wallets
w_mn, w_name = ami.rsplit(" ", 1)
w_mn, w_name = ami.split(":", 1)
w_name = w_name.strip()
new_name = "new"
pick_menu_item(ami) # just another ms wallet
pick_menu_item("Descriptors")
@ -1100,17 +1102,21 @@ def test_multiple_multisig_wallets(settings_set, setup_ccc, enter_enabled_ccc, c
# try importing duplicate does not work
_, story = offer_minsc_import(ms_conf)
assert "Duplicate wallet" in story
press_select() # not importable - dupe
# try rename
ms_conf = ms_conf.replace(w_name, new_name)
_, story = offer_minsc_import(ms_conf)
assert "Update NAME only of existing multisig wallet?" in story
press_select()
time.sleep(.1)
pick_menu_item("Settings")
pick_menu_item("Miniscript")
pick_menu_item(w_name)
pick_menu_item("Rename")
for i in range(len(w_name)):
need_keypress(KEY_DELETE if is_q1 else "x")
enter_text(new_name)
time.sleep(.1)
enter_enabled_ccc(words)
m = cap_menu()
assert f"{w_mn} {new_name}" in m
assert f"{w_mn}: {new_name}" in m
def test_remove_ccc(settings_set, setup_ccc, ccc_ms_setup, settings_get, policy_sign,

View File

@ -71,7 +71,7 @@ def compute_policy_hash(policy):
if type_ == Deriv:
rv = []
for orig in value or []:
rv.append(orig if orig in ["any", "p2sh"] else orig.replace('p', "h").replace("'", 'h'))
rv.append(orig if orig in ["any", "msas"] else orig.replace('p', "h").replace("'", 'h'))
elif type_ == WhitelistOpts:
rv = OrderedDict()
rv["mode"] = value.get("mode", "BASIC")
@ -170,10 +170,10 @@ def hsm_reset(dev, sim_exec):
(DICT(msg_paths=["any"]), "(any path)"),
# data sharing
(DICT(share_addrs=["m/1'/2p/3H"]), ['Address values values will be shared', "m/1h/2h/3h"]),
(DICT(share_addrs=["m/1", "m/2"]), ['Address values values will be shared', "m/1 OR m/2"]),
(DICT(share_addrs=["any"]), ['Address values values will be shared', "(any path)"]),
(DICT(share_addrs=["p2sh", "any"]), ['Address values values will be shared', "(any P2SH)", "(any path"]),
(DICT(share_addrs=["m/1'/2p/3H"]), ['Address values will be shared', "m/1h/2h/3h"]),
(DICT(share_addrs=["m/1", "m/2"]), ['Address values will be shared', "m/1 OR m/2"]),
(DICT(share_addrs=["any"]), ['Address values will be shared', "(any path)"]),
(DICT(share_addrs=["msas", "any"]), ['Address values will be shared', "(any miniscript)", "(any path"]),
(DICT(share_xpubs=["m/1'/2p/3H"]), ['XPUB values will be shared', "m/1h/2h/3h"]),
(DICT(share_xpubs=["m/1", "m/2"]), ['XPUB values will be shared', "m/1 OR m/2"]),
@ -564,7 +564,7 @@ def test_named_wallets(dev, start_hsm, tweak_rule, make_myself_wallet, hsm_statu
policy = DICT(rules=[dict(wallet=wname)])
stat = start_hsm(policy)
assert 'Any amount from multisig wallet' in stat.summary
assert 'Any amount from miniscript wallet' in stat.summary
assert wname in stat.summary
assert 'wallets' not in stat
@ -581,7 +581,7 @@ def test_named_wallets(dev, start_hsm, tweak_rule, make_myself_wallet, hsm_statu
# check ms txn not accepted when rule spec's a single signer
tweak_rule(0, dict(wallet='1'))
attempt_psbt(psbt, 'wrong multisig wallet')
attempt_psbt(psbt, 'wrong miniscript wallet')
@pytest.mark.bitcoind
def test_named_wallets_miniscript(dev, start_hsm, tweak_rule, make_myself_wallet,
@ -612,7 +612,7 @@ def test_named_wallets_miniscript(dev, start_hsm, tweak_rule, make_myself_wallet
pick_menu_item(name)
pick_menu_item("Descriptors")
pick_menu_item("Bitcoin Core")
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False, sig_check=False)
text = load_export("sd", label="Bitcoin Core miniscript", is_json=False)
text = text.replace("importdescriptors ", "").strip()
# remove junk
r1 = text.find("[")
@ -1239,37 +1239,6 @@ def test_show_addr(dev, quick_start_hsm, change_hsm):
path = path.replace('*', '73')
addr = doit(path, addr_fmt)
def test_show_p2sh_addr(dev, hsm_reset, start_hsm, change_hsm, make_myself_wallet, addr_vs_path):
# MULTISIG addrs
from test_multisig import HARD, make_redeem
M = 4
pm = lambda i: [HARD(45), i, 0,0]
# can't amke ms wallets inside HSM mode
hsm_reset()
keys, _ = make_myself_wallet(M) # slow AF
permit = ['p2sh', 'm/73']
start_hsm(DICT(share_addrs=permit))
scr, pubkeys, xfp_paths = make_redeem(M, keys, path_mapper=pm)
assert len(scr) <= 520, "script too long for standard!"
got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
M, xfp_paths, scr, addr_fmt=AF_P2WSH))
addr_vs_path(got_addr, addr_fmt=AF_P2WSH, script=scr)
# turn it off; p2sh must be explicitly allowed
for allow in ['m', 'any']:
change_hsm(DICT(share_addrs=[allow]))
dev.send_recv(CCProtocolPacker.show_address('m', AF_CLASSIC))
with pytest.raises(CCProtoError) as ee:
got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
M, xfp_paths, scr, addr_fmt=AF_P2WSH))
assert 'Not allowed in HSM mode' in str(ee)
def test_show_miniscript_addr(dev, offer_minsc_import, start_hsm,
change_hsm, need_keypress, clear_miniscript):
clear_miniscript()
@ -1282,7 +1251,7 @@ def test_show_miniscript_addr(dev, offer_minsc_import, start_hsm,
need_keypress("y")
time.sleep(.2)
policy = DICT(share_addrs=["any", "p2sh"], rules=[dict(wallet=name)])
policy = DICT(share_addrs=["any"], rules=[dict(wallet=name)])
start_hsm(policy)
with pytest.raises(CCProtoError) as ee:
@ -1290,7 +1259,7 @@ def test_show_miniscript_addr(dev, offer_minsc_import, start_hsm,
assert "Not allowed in HSM mode" in ee.value.args[0]
# change policy to allow miniscript address show
policy = DICT(share_addrs=["any", "p2sh", "msas"], rules=[dict(wallet=name)])
policy = DICT(share_addrs=["any", "msas"], rules=[dict(wallet=name)])
change_hsm(policy)
addr = dev.send_recv(CCProtocolPacker.miniscript_address(name, False, 0))
assert addr[2:4] == "1q"
@ -1574,7 +1543,7 @@ def worst_case_policy():
addrs = [render_address(b'\x00\x14' + prandom(20)) for i in range(5)]
p = DICT(period=30, share_xpubs=paths, share_addrs=paths+['p2sh'], msg_paths=paths,
p = DICT(period=30, share_xpubs=paths, share_addrs=paths+['msas'], msg_paths=paths,
warnings_ok=False, must_log=True)
p.rules = [dict(
local_conf=True,

View File

@ -1693,6 +1693,7 @@ def test_minitapscript(leaf2_mine, recovery, minisc, clear_miniscript, goto_home
time.sleep(0.1)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "warning" not in story
assert "1 input" in story
assert "20 outputs" in story
assert "Consolidating" in story
@ -1755,6 +1756,7 @@ def test_minitapscript(leaf2_mine, recovery, minisc, clear_miniscript, goto_home
time.sleep(.1)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "warning" not in story
assert "Consolidating" not in story
assert "20 inputs" in story
assert "2 outputs" in story

View File

@ -1468,7 +1468,7 @@ def test_wrong_xfp(fake_txn, try_sign, addr_fmt):
orig, result = try_sign(psbt, accept=True)
assert 'None of the keys' in str(ee)
assert 'found 12345678' in str(ee)
assert 'need 0F056943' in str(ee)
@pytest.mark.parametrize('addr_fmt', ["p2wpkh", "p2tr"])
def test_wrong_xfp_multi(fake_txn, try_sign, addr_fmt, sim_root_dir):
@ -1502,8 +1502,7 @@ def test_wrong_xfp_multi(fake_txn, try_sign, addr_fmt, sim_root_dir):
pass
else:
assert 'None of the keys' in str(ee)
# WEAK: device keeps them in order, but that's chance/impl defined...
assert 'found '+', '.join(sorted(wrongs)) in str(ee)
assert "need 0F056943" in str(ee)
@pytest.mark.parametrize('out_style', ADDR_STYLES_SINGLE)
@ -1731,7 +1730,7 @@ def test_zero_xfp(dev, start_sign, end_sign, fake_txn, cap_story):
assert 'Zero XFP' in story
# and then signing should work.
signed = end_sign(True, finalize=True)
end_sign(True, finalize=True)
@pytest.mark.parametrize("addr_fmt", ["p2pkh", "p2wpkh"])
@ -3349,8 +3348,8 @@ def test_finalize_with_foreign_inputs(bitcoind, bitcoind_d_sim_watch, start_sign
# EOF
@pytest.mark.bitcoind
def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign, microsd_path, cap_story, goto_home,
press_select, pick_menu_item, bitcoind):
def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign, microsd_path,
cap_story, goto_home, press_select, pick_menu_item, bitcoind, sim_root_dir):
use_regtest()
sim = bitcoind_d_sim_watch
sim.keypoolrefill(10)
@ -3361,7 +3360,8 @@ def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sig
psbt_resp = sim.walletcreatefundedpsbt([], [{dest_addr: 1.0}], 0, {"fee_rate": 20})
psbt = psbt_resp.get("psbt")
psbt_fname = "tr.psbt"
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w')as f:
f.write(psbt)
with open(microsd_path(psbt_fname), "w") as f:
f.write(psbt)
goto_home()
@ -3391,7 +3391,8 @@ def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sig
signed_psbt_fname = split_story[1]
with open(microsd_path(signed_psbt_fname), "r") as f:
signed_psbt = f.read().strip()
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
f.write(psbt)
signed_txn_fname = split_story[3]
with open(microsd_path(signed_txn_fname), "r") as f:
signed_txn = f.read().strip()
@ -3422,7 +3423,8 @@ def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sig
0, {'subtractFeeFromOutputs': [0], "fee_rate": 20})
psbt = psbt_resp.get("psbt")
psbt_fname = "tr-all.psbt"
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
f.write(psbt)
with open(microsd_path(psbt_fname), "w") as f:
f.write(psbt)
goto_home()
@ -3448,7 +3450,8 @@ def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sig
signed_psbt_fname = split_story[1]
with open(microsd_path(signed_psbt_fname), "r") as f:
signed_psbt = f.read().strip()
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
f.write(psbt)
signed_txn_fname = split_story[3]
with open(microsd_path(signed_txn_fname), "r") as f:
signed_txn = f.read().strip()
@ -3497,7 +3500,8 @@ def test_taproot_keyspend(use_regtest, bitcoind_d_sim_watch, start_sign, end_sig
signed_psbt_fname = split_story[1]
with open(microsd_path(signed_psbt_fname), "r") as f:
signed_psbt = f.read().strip()
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
f.write(psbt)
signed_txn_fname = split_story[3]
with open(microsd_path(signed_txn_fname), "r") as f:
signed_txn = f.read().strip()

View File

@ -617,7 +617,7 @@ def test_teleport_big_ms(make_myself_wallet, clear_miniscript, fake_ms_txn, try_
@pytest.mark.manual
def test_teleport_real_ms(dev, fake_ms_txn):
def test_teleport_real_ms(dev, fake_ms_txn, sim_root_dir):
#
# Do a 2-of-2 w/ USB-attached REAL Q and simulator
# - build ms wallet beforehand, both devices (QR); default air-gap settings
@ -648,11 +648,12 @@ def test_teleport_real_ms(dev, fake_ms_txn):
# match the default paths created by CC in airgapped MS wallet creation.
return str_to_path(deriv)
psbt = fake_ms_txn(3, 2, M, keys, fee=10000, outvals=None, inp_addr_fmt="p2sh",
psbt = fake_ms_txn(3, 2, M, keys, fee=10000, outvals=None, inp_addr_fmt="p2wsh",
outstyles=['p2pkh'], change_outputs=[],
hack_change_out=False, input_amount=1E8, path_mapper=p2wsh_mapper)
open('debug/teleport_real_ms.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/teleport_real_ms.psbt', 'wb') as f:
f.write(psbt)
ll, sha = dev.upload_file(psbt)
dev.send_recv(CCProtocolPacker.sign_transaction(ll, sha))

View File

@ -137,10 +137,6 @@ def test_slip132(unit_test):
# slip132 ?pub stuff
unit_test('devtest/unit_slip132.py')
def test_multisig(unit_test):
# scripts/multisig unit tests
unit_test('devtest/unit_multisig.py')
def test_decoding(unit_test):
# utils.py Hex/Base64 streaming decoders
unit_test('devtest/unit_decoding.py')