def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, cap_story, case): # not fraud: output address encoded in various equiv forms from pycoin.tx.Tx import Tx from pycoin.tx.TxOut import TxOut # NOTE: out#1 is change: #chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo' psbt = open('data/example-change.psbt', 'rb').read() b4 = BasicPSBT().parse(psbt) t = Tx.parse(BytesIO(b4.txn)) pkh = t.txs_out[1].hash160() if case == 'p2wpkh': t.txs_out[1].script = bytes([0, 20]) + bytes(pkh) from bech32 import encode expect_addr = encode('tb', 0, pkh) elif case == 'p2sh': spk = bytes([0xa9, 0x14]) + pkh + bytes([0x87]) b4.outputs[1].redeem_script = bytes([0, 20]) + bytes(pkh) t.txs_out[1].script = spk expect_addr = t.txs_out[1].address('XTN') b4.txn = t.as_bin() with BytesIO() as fd: b4.serialize(fd) mod_psbt = fd.getvalue() open('debug/mod-%s.psbt' % case, 'wb').write(mod_psbt) start_sign(mod_psbt) time.sleep(.1) _, story = cap_story() check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[ 1, ], dests=[(1, expect_addr)]) #print(story) assert expect_addr in story assert parse_change_back(story) == (Decimal('1.09997082'), [expect_addr]) signed = end_sign(True)
def test_change_outs(fake_txn, start_sign, end_sign, cap_story, dev, num_outs, act_outs, segwit, out_style, num_ins=3): # create a TXN which has change outputs, which shouldn't be shown to user, and also not fail. xp = dev.master_xpub couts = num_outs if act_outs == -1 else num_ins - act_outs psbt = fake_txn(num_ins, num_outs, xp, segwit_in=segwit, outstyles=[out_style], change_outputs=range(couts)) open('debug/change.psbt', 'wb').write(psbt) # should be able to sign, but get warning start_sign(psbt, False) time.sleep(.1) title, story = cap_story() print(repr(story)) assert title == "OK TO SEND?" assert 'Network fee' in story if couts < num_outs: assert '- to address -' in story else: assert 'Consolidating' in story if couts == 1: assert "- to address -" in story else: assert "- to addresses -" in story val, addrs = parse_change_back(story) assert val > 0 # hard to calc here assert len(addrs) == couts if out_style == 'p2pkh': assert all((i[0] in 'mn') for i in addrs) elif out_style == 'p2wpkh': assert set(i[0:4] for i in addrs) == {'tb1q'} elif out_style == 'p2wpkh-p2sh': assert set(i[0] for i in addrs) == {'2'}
def test_change_troublesome(start_sign, cap_story, try_path, expect): from struct import pack # NOTE: out#1 is change: # addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo' # path = (m=4369050F)/44'/1'/0'/1/5 # pubkey = 03c80814536f8e801859fc7c2e5129895b261153f519d4f3418ffb322884a7d7e1 psbt = open('data/example-change.psbt', 'rb').read() b4 = BasicPSBT().parse(psbt) if 0: #from pycoin.tx.Tx import Tx #from pycoin.tx.TxOut import TxOut # tweak output addr to garbage t = Tx.parse(BytesIO(b4.txn)) chg = t.txs_out[1] # pycoin.tx.TxOut.TxOut b = bytearray(chg.script) b[-5] ^= 0x55 chg.script = bytes(b) b4.txn = t.as_bin() pubkey = a2b_hex( '03c80814536f8e801859fc7c2e5129895b261153f519d4f3418ffb322884a7d7e1') path = [ int(p) if ("'" not in p) else 0x80000000 + int(p[:-1]) for p in try_path.split('/') ] bin_path = b4.outputs[1].bip32_paths[pubkey][0:4] \ + b''.join(pack('<I', i) for i in path) b4.outputs[1].bip32_paths[pubkey] = bin_path with BytesIO() as fd: b4.serialize(fd) mod_psbt = fd.getvalue() open('debug/troublesome.psbt', 'wb').write(mod_psbt) start_sign(mod_psbt) time.sleep(0.1) title, story = cap_story() assert 'OK TO SEND' in title assert '(1 warning below)' in story, "no warning shown" assert expect in story, story assert parse_change_back(story) == (Decimal('1.09997082'), ['mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'])
def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign, dev): # check how we render the value of outputs # - works on simulator and connected USB real-device xp = dev.master_xpub oi = int(Decimal(outval) * int(1E8)) psbt = fake_txn(1, 2, dev.master_xpub, segwit_in=segwit, outvals=[oi, 1E8 - oi], outstyles=[out_style], change_outputs=[1]) open('debug/render.psbt', 'wb').write(psbt) # should be able to sign, but get warning # use new feature to have Coldcard return the 'visualization' of transaction start_sign(psbt, False, stxn_flags=STXN_VISUALIZE) story = end_sign(accept=None, expect_txn=False) story = story.decode('ascii') assert 'Network fee' in story assert '- to address -' in story # check rendered right lines = story.split('\n') amt = Decimal(lines[lines.index(' - to address -') - 1].split(' ')[0]) assert amt == Decimal(outval) val, addrs = parse_change_back(story) assert val == (1 - Decimal(outval)) assert len(addrs) == 1 if out_style == 'p2pkh': assert all((i[0] in 'mn1') for i in addrs) elif out_style == 'p2wpkh': pr = set(i[0:4] for i in addrs) assert len(pr) == 1 assert pr.pop() in {'tb1q', 'bc1q'} elif out_style == 'p2wpkh-p2sh': assert len(set(i[0] for i in addrs)) == 1 assert addrs[0][0] in {'2', '3'}
def test_change_outs(fake_txn, start_sign, end_sign, cap_story, dev, num_outs, master_xpub, act_outs, segwit, out_style, visualized, add_xpub, num_ins=3): # create a TXN which has change outputs, which shouldn't be shown to user, and also not fail. xp = dev.master_xpub couts = num_outs if act_outs == -1 else num_ins-act_outs psbt = fake_txn(num_ins, num_outs, xp, segwit_in=segwit, outstyles=[out_style], change_outputs=range(couts), add_xpub=add_xpub) open('debug/change.psbt', 'wb').write(psbt) # should be able to sign, but get warning if not visualized: start_sign(psbt, False) time.sleep(.1) title, story = cap_story() print(repr(story)) assert title == "OK TO SEND?" else: # use new feature to have Coldcard return the 'visualization' of transaction start_sign(psbt, False, stxn_flags=visualized) story = end_sign(accept=None, expect_txn=False) story = story.decode('ascii') if (visualized & STXN_SIGNED): # last line should be signature, using 'm' over the rest from pycoin.contrib.msg_signing import verify_message from pycoin.key.BIP32Node import BIP32Node #def verify_message(key_or_address, signature, message=None, msg_hash=None, netcode=None): assert story[-1] == '\n' last_nl = story[:-1].rindex('\n') msg, sig = story[0:last_nl+1], story[last_nl:] wallet = BIP32Node.from_wallet_key(master_xpub) assert verify_message(wallet, sig, message=msg) == True story = msg assert 'Network fee' in story if couts < num_outs: assert '- to address -' in story else: assert 'Consolidating' in story if couts == 1: assert "- to address -" in story else: assert "- to addresses -" in story val, addrs = parse_change_back(story) assert val > 0 # hard to calc here assert len(addrs) == couts if out_style == 'p2pkh': assert all((i[0] in 'mn') for i in addrs) elif out_style == 'p2wpkh': assert set(i[0:4] for i in addrs) == {'tb1q'} elif out_style == 'p2wpkh-p2sh': assert set(i[0] for i in addrs) == {'2'}