Example #1
0
def test_sign_example(set_master_key, sim_execfile, start_sign, end_sign):
    # Use the private key given in BIP 174 and do similar signing
    # as the examples.

    # PROBLEM: revised BIP174 has p2sh multisig cases which we don't support yet.
    raise pytest.skip('not ready for multisig')

    # expect xfp=0x4f6a0cd9
    exk = 'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF'
    set_master_key(exk)

    psbt = a2b_hex(open('data/worked-unsigned.psbt', 'rb').read())

    start_sign(psbt)
    signed = end_sign(True)

    open('debug/ex-signed.psbt', 'wb').write(signed)

    b4 = BasicPSBT().parse(psbt)
    aft = BasicPSBT().parse(signed)
    assert b4 != aft, "signing didn't change anything?"

    open('debug/example-signed.psbt', 'wb').write(signed)
    expect = BasicPSBT().parse(
        a2b_hex(open('data/worked-combined.psbt', 'rb').read()))

    assert aft == expect
Example #2
0
def test_change_fraud_addr(start_sign, end_sign, check_against_bitcoind, cap_story):
    # fraud: BIP-32 path of output doesn't match TXO address
    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)

    # 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()

    with BytesIO() as fd:
        b4.serialize(fd)
        mod_psbt = fd.getvalue()

    open('debug/mod-addr.psbt', 'wb').write(mod_psbt)

    start_sign(mod_psbt)
    with pytest.raises(CCProtoError) as ee:
        signed = end_sign(True)
    assert 'Change output is fraud' in str(ee)
Example #3
0
    def doit(accept=True, in_psbt=None, finalize=False):

        if accept != None:
            need_keypress('y' if accept else 'x')

        if accept == False:
            with pytest.raises(CCUserRefused):
                done = None
                while done == None:
                    time.sleep(0.050)
                    done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)
            return
        else:
            done = None
            while done == None:
                time.sleep(0.050)
                done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)

        assert len(done) == 2

        resp_len, chk = done
        psbt_out = dev.download_file(resp_len, chk)

        if not finalize:
            if in_psbt:
                assert BasicPSBT().parse(in_psbt) == BasicPSBT().parse(psbt_out)
        else:
            from pycoin.tx.Tx import Tx
            # parse it
            res = psbt_out
            assert res[0:4] != b'psbt'
            t = Tx.from_bin(res)
            assert t.version in [1, 2]

        return psbt_out
Example #4
0
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)
Example #5
0
def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
    # unit test: parsing by the psbt proxy object

    sim_exec('import main; main.FILENAME = %r; ' % ('../../testing/'+fn))
    rv = sim_execfile('devtest/unit_psbt.py')
    assert not rv, rv

    rb = '../unix/work/readback.psbt'

    oo = BasicPSBT().parse(open(fn, 'rb').read())
    rb = BasicPSBT().parse(open(rb, 'rb').read())
    assert oo == rb
Example #6
0
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'])
Example #7
0
    def doit(num_ins, num_outs, fat=0):
        psbt = BasicPSBT()
        txn = Tx(2, [], [])

        for i in range(num_ins):
            h = TxIn(pack('4Q', 0, 0, 0, i), i)
            txn.txs_in.append(h)

        for i in range(num_outs):
            # random P2PKH
            scr = bytes([0x76, 0xa9, 0x14]) + pack(
                'I', i + 1) + bytes(16) + bytes([0x88, 0xac])
            h = TxOut((1E6 * i) if i else 1E8, scr)
            txn.txs_out.append(h)

        with BytesIO() as b:
            txn.stream(b)
            psbt.txn = b.getvalue()

        psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)]
        psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)]

        if fat:
            for i in range(num_ins):
                psbt.inputs[i].utxo = os.urandom(fat)

        rv = BytesIO()
        psbt.serialize(rv)
        assert rv.tell() <= MAX_TXN_LEN, 'too fat'

        return rv.getvalue()
Example #8
0
    def doit(num_ins, num_outs, master_xpub, subpath="0/%d", fee=10000):
        psbt = BasicPSBT()
        txn = Tx(2, [], [])

        # we have a key; use it to provide "plausible" value inputs
        from pycoin.key.BIP32Node import BIP32Node
        mk = BIP32Node.from_wallet_key(master_xpub)
        xfp = mk.fingerprint()

        psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)]
        psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)]

        for i in range(num_ins):
            # make a fake txn to supply each of the inputs
            # - each input is 1BTC

            # addr where the fake money will be stored.
            subkey = mk.subkey_for_path(subpath % i)
            sec = subkey.sec()
            assert len(sec) == 33, "expect compressed"
            assert subpath[0:2] == '0/'

            psbt.inputs[i].bip32_paths[sec] = xfp + pack('<II', 0, i)

            # UTXO that provides the funding for to-be-signed txn
            supply = Tx(2, [TxIn(pack('4Q', 0xdead, 0xbeef, 0, 0), 73)], [])

            scr = bytes([0x76, 0xa9, 0x14]) + subkey.hash160() + bytes(
                [0x88, 0xac])
            supply.txs_out.append(TxOut(1E8, scr))

            with BytesIO() as fd:
                supply.stream(fd)
                psbt.inputs[i].utxo = fd.getvalue()

            if 0:
                with BytesIO() as fd:
                    supply.stream(fd, include_witness_data=True)
                    psbt.inputs[i].witness_utxo = fd.getvalue()

            spendable = TxIn(supply.hash(), 0)
            txn.txs_in.append(spendable)

        for i in range(num_outs):
            # random P2PKH
            scr = bytes([0x76, 0xa9, 0x14]) + pack(
                'I', i + 1) + bytes(16) + bytes([0x88, 0xac])
            h = TxOut(round(((1E8 * num_ins) - fee) / num_outs, 4), scr)
            txn.txs_out.append(h)

        with BytesIO() as b:
            txn.stream(b)
            psbt.txn = b.getvalue()

        rv = BytesIO()
        psbt.serialize(rv)
        assert rv.tell() <= MAX_TXN_LEN, 'too fat'

        return rv.getvalue()
Example #9
0
def spend_outputs(funding_psbt, finalized_txn, tweaker=None):
    # take details from PSBT that created a finalized txn (also provided)
    # and build a new PSBT that spends those change outputs.
    from pycoin.tx.Tx import Tx
    from pycoin.tx.TxOut import TxOut
    from pycoin.tx.TxIn import TxIn
    funding = Tx.from_bin(finalized_txn)
    b4 = BasicPSBT().parse(funding_psbt)

    # segwit change outputs only
    spendables = [(n, i) for n, i in enumerate(funding.tx_outs_as_spendable())
                  if i.script[0:2] == b'\x00\x14' and b4.outputs[n].bip32_paths
                  ]

    #spendables = list(reversed(spendables))
    random.shuffle(spendables)

    if tweaker:
        tweaker(spendables)

    nn = BasicPSBT()
    nn.inputs = [BasicPSBTInput(idx=i) for i in range(len(spendables))]
    nn.outputs = [BasicPSBTOutput(idx=0)]

    # copy input values from funding PSBT's output side
    for p_in, (f_out, sp) in zip(nn.inputs,
                                 [(b4.outputs[x], s) for x, s in spendables]):
        p_in.bip32_paths = f_out.bip32_paths
        p_in.witness_script = f_out.redeem_script
        with BytesIO() as fd:
            sp.stream(fd)
            p_in.witness_utxo = fd.getvalue()

    # build new txn: single output, no change, no miner fee
    act_scr = fake_dest_addr('p2wpkh')
    dest_out = TxOut(sum(s.coin_value for n, s in spendables), act_scr)

    txn = Tx(2, [s.tx_in() for _, s in spendables], [dest_out])

    # put unsigned TXN into PSBT
    with BytesIO() as b:
        txn.stream(b)
        nn.txn = b.getvalue()

    with BytesIO() as rv:
        nn.serialize(rv)
        raw = rv.getvalue()

    open('debug/spend_outs.psbt', 'wb').write(raw)

    return nn, raw
Example #10
0
def test_sign_example(set_master_key, sim_execfile, start_sign, end_sign):
    # use the private key given in BIP 174 and do similar signing
    # as the examples.

    # TODO fix this
    # - doesn't work anymore, because we won't sign a multisig we don't know the wallet details for
    raise pytest.skip('needs rework')
    
    exk = 'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF'
    set_master_key(exk)

    mk = BIP32Node.from_wallet_key(exk)

    psbt = a2b_hex(open('data/worked-unsigned.psbt', 'rb').read())

    start_sign(psbt)
    signed = end_sign(True)

    aft = BasicPSBT().parse(signed)
    expect = BasicPSBT().parse(open('data/worked-combined.psbt', 'rb').read())

    assert aft == expect
Example #11
0
    def doit(accept=True,
             in_psbt=None,
             finalize=False,
             accept_ms_import=False,
             expect_txn=True):

        if accept_ms_import:
            # XXX would be better to do cap_story here, but that would limit test to simulator
            need_keypress('y')
            time.sleep(0.050)

        if accept != None:
            need_keypress('y' if accept else 'x', timeout=None)

        if accept == False:
            with pytest.raises(CCUserRefused):
                done = None
                while done == None:
                    time.sleep(0.050)
                    done = dev.send_recv(CCProtocolPacker.get_signed_txn(),
                                         timeout=None)
            return
        else:
            done = None
            while done == None:
                time.sleep(0.00)
                done = dev.send_recv(CCProtocolPacker.get_signed_txn(),
                                     timeout=None)

        assert len(done) == 2

        resp_len, chk = done
        psbt_out = dev.download_file(resp_len, chk)

        if not expect_txn:
            # skip checks; it's text
            return psbt_out

        if not finalize:
            if in_psbt:
                from psbt import BasicPSBT
                assert BasicPSBT().parse(in_psbt) != None
        else:
            from pycoin.tx.Tx import Tx
            # parse it
            res = psbt_out
            assert res[0:4] != b'psbt', 'still a PSBT, but asked for finalize'
            t = Tx.from_bin(res)
            assert t.version in [1, 2]

        return psbt_out
Example #12
0
def test_ms_sign_myself(M, make_myself_wallet, segwit, num_ins, dev, clear_ms,
        fake_ms_txn, try_sign, bitcoind_finalizer, incl_xpubs, bitcoind_analyze, bitcoind_decode):

    # IMPORTANT: wont work if you start simulator with -m flag. Use no args

    all_out_styles = list(unmap_addr_fmt.keys())
    num_outs = len(all_out_styles)

    clear_ms()

    # create a wallet, with 3 bip39 pw's
    keys, select_wallet = make_myself_wallet(M, do_import=(not incl_xpubs))
    N = len(keys)

    psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs, 
                        outstyles=all_out_styles, change_outputs=list(range(1,num_outs)))

    open(f'debug/myself-before.psbt', 'wb').write(psbt)
    for idx in range(M):
        select_wallet(idx)
        _, updated = try_sign(psbt, accept_ms_import=(incl_xpubs and (idx==0)))
        open(f'debug/myself-after.psbt', 'wb').write(updated)
        assert updated != psbt

        aft = BasicPSBT().parse(updated)

        # check all inputs gained a signature
        assert all(len(i.part_sigs)==(idx+1) for i in aft.inputs)

        psbt = updated

    # should be fully signed now.
    anal = bitcoind_analyze(aft.as_bytes())

    try:
        assert not any(inp.get('missing') for inp in anal['inputs']), "missing sigs: %r" % anal
        assert all(inp['next'] in {'finalizer','updater'} for inp in anal['inputs']), "other issue: %r" % anal
    except:
        # XXX seems to be a bug in analyzepsbt function ... not fully studied
        pprint(anal, stream=open('debug/analyzed.txt', 'wt'))
        decode = bitcoind_decode(aft.as_bytes())
        pprint(decode, stream=open('debug/decoded.txt', 'wt'))
    
        if M==N or segwit:
            # as observed, bug not trigged, so raise if it *does* happen
            raise
        else:
            print("ignoring bug in bitcoind")

    if 0:
        # why doesn't this work?
        extracted_psbt, txn, is_complete = bitcoind_finalizer(aft.as_bytes(), extract=True)

        ex = BasicPSBT().parse(extracted_psbt)
        assert is_complete
        assert ex != aft
Example #13
0
def KEEP_test_random_psbt(try_sign, sim_exec, fname="data/   .psbt"):
    # allow almost any PSBT to run on simulator, at least up until wrong pubkeys detected
    # - detects expected XFP and changes to match
    # - good for debug of random psbt
    oo = BasicPSBT().parse(open(fname, 'rb').read())
    paths = []
    for i in oo.inputs:
         paths.extend(i.bip32_paths.values())

    used = set(i[0:4] for i in paths)
    assert len(used) == 1, "multiple key fingerprints in inputs, can only handle 1"
    need_xfp, = struct.unpack("<I", used.pop())

    sim_exec('from main import settings; settings.set("xfp", 0x%x);' % need_xfp)


    with pytest.raises(CCProtoError) as ee:
        orig, result = try_sign(fname, accept=True)

    msg = ee.value.args[0]
    assert 'Signing failed late' in msg
    assert 'led to wrong pubkey for input' in msg
Example #14
0
def test_change_case(start_sign, end_sign, check_against_bitcoind, cap_story):
    # is change shown/hidden at right times. no fraud checks

    # NOTE: out#1 is change:
    chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'

    psbt = open('data/example-change.psbt', 'rb').read()

    start_sign(psbt)

    time.sleep(.1)
    _, story = cap_story()
    assert chg_addr in story

    b4 = BasicPSBT().parse(psbt)
    check_against_bitcoind(B2A(b4.txn),
                           Decimal('0.00000294'),
                           change_outs=[
                               1,
                           ])

    signed = end_sign(True)
    open('debug/chg-signed.psbt', 'wb').write(signed)

    # modify it: remove bip32 path
    b4.outputs[1].bip32_paths = {}
    with BytesIO() as fd:
        b4.serialize(fd)
        mod_psbt = fd.getvalue()

    start_sign(mod_psbt)

    time.sleep(.1)
    _, story = cap_story()

    # no change expected (they are outputs)
    assert 'Change back' not in story

    check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[])

    signed2 = end_sign(True)
    open('debug/chg-signed2.psbt', 'wb').write(signed)
    aft = BasicPSBT().parse(signed)
    aft2 = BasicPSBT().parse(signed2)
    assert aft.txn == aft2.txn
Example #15
0
def test_vs_bitcoind(match_key, check_against_bitcoind, bitcoind, start_sign,
                     end_sign, we_finalize, num_dests):

    bal = bitcoind.getbalance()
    assert bal > 0, "need some play money; drink from a faucet"

    amt = round((bal / 8) / num_dests, 6)

    args = {}

    for no in range(num_dests):
        dest = bitcoind.getrawchangeaddress()
        assert dest[0] in '2mn' or dest.startswith('tb1'), dest

        args[dest] = amt

    if 0:
        # old approach: fundraw + convert to psbt

        # working with hex strings here
        txn = bitcoind.createrawtransaction([], args)
        assert txn[0:2] == '02'
        #print(txn)

        resp = bitcoind.fundrawtransaction(txn)
        txn2 = resp['hex']
        fee = resp['fee']
        chg_pos = resp['changepos']
        #print(txn2)

        print("Sending %.8f XTN to %s (Change back in position: %d)" %
              (amt, dest, chg_pos))

        psbt = b64decode(bitcoind.converttopsbt(txn2, True))
    else:
        # use walletcreatefundedpsbt
        # - updated/validated against 0.17.1
        resp = bitcoind.walletcreatefundedpsbt([], args, 0, {}, True)

        psbt = b64decode(resp['psbt'])
        fee = resp['fee']
        chg_pos = resp['changepos']

        # pull out included txn
        txn2 = B2A(BasicPSBT().parse(psbt).txn)

    open('debug/vs.psbt', 'wb').write(psbt)

    start_sign(psbt, finalize=we_finalize)

    # verify against how bitcoind reads it
    check_against_bitcoind(txn2, fee)

    signed = end_sign(accept=True)
    #signed = end_sign(None)
    open('debug/vs-signed.psbt', 'wb').write(signed)

    if not we_finalize:
        b4 = BasicPSBT().parse(psbt)
        aft = BasicPSBT().parse(signed)
        assert b4 != aft, "signing didn't change anything?"

        open('debug/signed.psbt', 'wb').write(signed)
        resp = bitcoind.finalizepsbt(str(b64encode(signed), 'ascii'), True)

        #combined_psbt = b64decode(resp['psbt'])
        #open('debug/combined.psbt', 'wb').write(combined_psbt)

        assert resp['complete'] == True, "bitcoind wasn't able to finalize it"

        network = a2b_hex(resp['hex'])

        # assert resp['complete']
        #print("Final txn: %r" % network)

        # try to send it
        txed = bitcoind.sendrawtransaction(B2A(network))
        print("Final txn hash: %r" % txed)

    else:
        assert signed[0:4] != b'psbt', "expecting raw bitcoin txn"
        #print("Final txn: %s" % B2A(signed))
        open('debug/finalized.psbt', 'wb').write(signed)

        txed = bitcoind.sendrawtransaction(B2A(signed))
        print("Final txn hash: %r" % txed)
Example #16
0
    def doit(f_or_data,
             accept=True,
             finalize=False,
             accept_ms_import=False,
             complete=False,
             encoding='binary',
             del_after=0):

        if f_or_data[0:5] == b'psbt\xff':
            ip = f_or_data
            filename = 'memory'
        else:
            filename = f_or_data
            ip = open(f_or_data, 'rb').read()
            if ip[0:10] == b'70736274ff':
                ip = a2b_hex(ip.strip())
            assert ip[0:5] == b'psbt\xff'

        psbtname = 'ftrysign'

        # population control
        from glob import glob
        import os
        pat = microsd_path(psbtname + '*.psbt')
        for f in glob(pat):
            assert 'psbt' in f
            os.unlink(f)

        if encoding == 'hex':
            ip = b2a_hex(ip)
        elif encoding == 'base64':
            from base64 import b64encode, b64decode
            ip = b64encode(ip)
        else:
            assert encoding == 'binary'

        with open_microsd(psbtname + '.psbt', 'wb') as sd:
            sd.write(ip)

        goto_home()
        pick_menu_item('Ready To Sign')

        time.sleep(.1)
        _, story = cap_story()
        if 'Choose PSBT file' in story:
            need_keypress('y')
            time.sleep(.1)

        pick_menu_item(psbtname + '.psbt')

        time.sleep(.1)

        if accept_ms_import:
            # XXX would be better to do cap_story here, but that would limit test to simulator
            need_keypress('y')
            time.sleep(0.050)

        title, story = cap_story()
        assert title == 'OK TO SEND?'

        if accept != None:
            need_keypress('y' if accept else 'x')

        if accept == False:
            time.sleep(0.050)

            # look for "Aborting..." ??
            return ip, None, None

        # wait for it to finish
        for r in range(10):
            time.sleep(0.1)
            title, story = cap_story()
            if title == 'PSBT Signed': break
        else:
            assert False, 'timed out'

        txid = None
        lines = story.split('\n')
        if 'Final TXID:' in lines:
            txid = lines[-1]
            result_fname = lines[-4]
        else:
            result_fname = lines[-1]

        result = open_microsd(result_fname, 'rb').read()

        if encoding == 'hex' or finalize:
            result = a2b_hex(result.strip())
        elif encoding == 'base64':
            result = b64decode(result)
        else:
            assert encoding == 'binary'

        in_file = microsd_path(psbtname + '.psbt')

        # read back final product
        if finalize:

            if del_after:
                if not txid:
                    txid = re.findall('[0-9a-f]{64}', result_fname)[0]
                assert result_fname == txid + '.txn'
                assert not os.path.exists(in_file)
            else:
                assert 'final' in result_fname
                assert os.path.exists(in_file)

            from pycoin.tx.Tx import Tx
            # parse it a little
            assert result[
                0:4] != b'psbt', 'still a PSBT, but asked for finalize'
            t = Tx.from_bin(result)
            assert t.version in [1, 2]
            assert t.id() == txid

        else:
            assert result[0:5] == b'psbt\xff'

            if complete:
                assert '-signed' in result_fname
            else:
                assert '-part' in result_fname

            if del_after:
                assert not os.path.exists(in_file)

            from psbt import BasicPSBT
            was = BasicPSBT().parse(ip)
            now = BasicPSBT().parse(result)
            assert was.txn == now.txn
            assert was != now

        return ip, result, txid
Example #17
0
def test_bitcoind_cosigning(dev, bitcoind, start_sign, end_sign, import_ms_wallet, clear_ms, explora, try_sign, need_keypress, addr_style):
    # Make a P2SH wallet with local bitcoind as a co-signer (and simulator)
    # - send an receive various
    # - following text of <https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md>
    # - the constructed multisig walelt will only work for a single pubkey on core side
    # - before starting this test, have some funds already deposited to bitcoind testnet wallet
    from pycoin.encoding import sec_to_public_pair
    from binascii import a2b_hex
    import re

    if addr_style == 'legacy':
        addr_fmt = AF_P2SH
    elif addr_style == 'p2sh-segwit':
        addr_fmt = AF_P2WSH_P2SH
    elif addr_style == 'bech32':
        addr_fmt = AF_P2WSH
    
    try:
        addr, = bitcoind.getaddressesbylabel("sim-cosign").keys()
    except:
        addr = bitcoind.getnewaddress("sim-cosign")

    info = bitcoind.getaddressinfo(addr)
    #pprint(info)

    assert info['address'] == addr
    bc_xfp = swab32(int(info['hdmasterfingerprint'], 16))
    bc_deriv = info['hdkeypath']        # example: "m/0'/0'/3'"
    bc_pubkey = info['pubkey']          # 02f75ae81199559c4aa...

    pp = sec_to_public_pair(a2b_hex(bc_pubkey))

    # No means to export XPUB from bitcoind! Still. In 2019.
    # - this fake will only work for for one pubkey value, the first/topmost
    node = BIP32Node('XTN', b'\x23'*32, depth=len(bc_deriv.split('/'))-1,
                        parent_fingerprint=a2b_hex('%08x' % bc_xfp), public_pair=pp)

    keys = [
        (bc_xfp, None, node),
        (1130956047, None, BIP32Node.from_hwif('tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n')),     # simulator: m/45'
    ]

    M,N=2,2

    clear_ms()
    import_ms_wallet(M, N, keys=keys, accept=1, name="core-cosign")

    cc_deriv = "m/45'/55"
    cc_pubkey = B2A(BIP32Node.from_hwif(simulator_fixed_xprv).subkey_for_path(cc_deriv[2:]).sec())

    

    # NOTE: bitcoind doesn't seem to implement pubkey sorting. We have to do it.
    resp = bitcoind.addmultisigaddress(M, list(sorted([cc_pubkey, bc_pubkey])),
                                                'shared-addr-'+addr_style, addr_style)
    ms_addr = resp['address']
    bc_redeem = a2b_hex(resp['redeemScript'])

    assert bc_redeem[0] == 0x52

    def mapper(cosigner_idx):
        return list(str2ipath(cc_deriv if cosigner_idx else bc_deriv))

    scr, pubkeys, xfp_paths = make_redeem(M, keys, mapper)

    assert scr == bc_redeem

    # check Coldcard calcs right address to match
    got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
                                M, xfp_paths, scr, addr_fmt=addr_fmt), timeout=None)
    assert got_addr == ms_addr
    time.sleep(.1)
    need_keypress('x')      # clear screen / start over

    print(f"Will be signing an input from {ms_addr}")

    if xfp2str(bc_xfp) in ('5380D0ED', 'EDD08053'):
        # my own expected values
        assert ms_addr in ( '2NDT3ymKZc8iMfbWqsNd1kmZckcuhixT5U4',
                            '2N1hZJ5mazTX524GQTPKkCT4UFZn5Fqwdz6',
                            'tb1qpcv2rkc003p5v8lrglrr6lhz2jg8g4qa9vgtrgkt0p5rteae5xtqn6njw9')

    # Need some UTXO to sign
    #
    # - but bitcoind can't give me that (using listunspent) because it's only a watched addr??
    #
    did_fund = False
    while 1:
        rr = explora('address', ms_addr, 'utxo')
        pprint(rr)

        avail = []
        amt = 0
        for i in rr:
            txn = i['txid']
            vout = i['vout']
            avail.append( (txn, vout) )
            amt += i['value']

            # just use first UTXO available; save other for later tests
            break

        else:
            # doesn't need to confirm, but does need to reach public testnet/blockstream
            assert not amt and not avail

            if not did_fund:
                print(f"Sending some XTN to {ms_addr}  (wait)")
                bitcoind.sendtoaddress(ms_addr, 0.0001, 'fund testing')
                did_fund = True
            else:
                print(f"Still waiting ...")

            time.sleep(2)

        if amt: break

    ret_addr = bitcoind.getrawchangeaddress()

    ''' If you get insufficent funds, even tho we provide the UTXO (!!), do this:

            bitcoin-cli importaddress "2NDT3ymKZc8iMfbWqsNd1kmZckcuhixT5U4" true true

        Better method: always fund addresses for testing here from same wallet (ie.
        got from non-multisig to multisig on same bitcoin-qt instance).
        -> Now doing that, automated, above.
    '''
    resp = bitcoind.walletcreatefundedpsbt([dict(txid=t, vout=o) for t,o in avail],
               [{ret_addr: amt/1E8}], 0,
                {'subtractFeeFromOutputs': [0], 'includeWatching': True}, True)

    assert resp['changepos'] == -1
    psbt = b64decode(resp['psbt'])

    open('debug/funded.psbt', 'wb').write(psbt)

    # patch up the PSBT a little ... bitcoind doesn't know the path for the CC's key
    ex = BasicPSBT().parse(psbt)
    cxpk = a2b_hex(cc_pubkey)
    for i in ex.inputs:
        assert cxpk in i.bip32_paths, 'input not to be signed by CC?'
        i.bip32_paths[cxpk] = pack('<3I', keys[1][0], *str2ipath(cc_deriv))

    psbt = ex.as_bytes()

    open('debug/patched.psbt', 'wb').write(psbt)

    _, updated = try_sign(psbt, finalize=False)

    open('debug/cc-updated.psbt', 'wb').write(updated)

    # have bitcoind do the rest of the signing
    rr = bitcoind.walletprocesspsbt(b64encode(updated).decode('ascii'))
    pprint(rr)

    open('debug/bc-processed.psbt', 'wt').write(rr['psbt'])
    assert rr['complete']

    # finalize and send
    rr = bitcoind.finalizepsbt(rr['psbt'], True)
    open('debug/bc-final-txn.txn', 'wt').write(rr['hex'])
    assert rr['complete']

    txn_id = bitcoind.sendrawtransaction(rr['hex'])
    print(txn_id)
Example #18
0
def test_finalization_vs_bitcoind(match_key, check_against_bitcoind, bitcoind, start_sign, end_sign, num_dests):
    # Compare how we finalize vs bitcoind ... should be exactly the same txn

    wallet_xfp = match_key()

    bal = bitcoind.getbalance()
    assert bal > 0, "need some play money; drink from a faucet"

    amt = round((bal/4)/num_dests, 6)

    args = {}

    for no in range(num_dests):
        dest = bitcoind.getrawchangeaddress()
        assert dest[0] in '2mn' or dest.startswith('tb1'), dest

        args[dest] = amt

    # use walletcreatefundedpsbt
    # - updated/validated against 0.17.1
    resp = bitcoind.walletcreatefundedpsbt([], args, 0, {
                'subtractFeeFromOutputs': list(range(num_dests)),
                'feeRate': 0.00001500}, True)

    psbt = b64decode(resp['psbt'])
    fee = resp['fee']
    chg_pos = resp['changepos']

    open('debug/vs.psbt', 'wb').write(psbt)

    # check some basics
    mine = BasicPSBT().parse(psbt)
    for i in mine.inputs:
        got_xfp, = struct.unpack_from('I', list(i.bip32_paths.values())[0])
        #assert hex(got_xfp) == hex(wallet_xfp), "wrong HD master key fingerprint"

        # see <https://github.com/bitcoin/bitcoin/issues/15884>
        if hex(got_xfp) != hex(wallet_xfp):
            raise pytest.xfail("wrong HD master key fingerprint")

    # pull out included txn
    txn2 = B2A(mine.txn)

    start_sign(psbt, finalize=True)

    # verify against how bitcoind reads it
    check_against_bitcoind(txn2, fee)

    signed_final = end_sign(accept=True)
    assert signed_final[0:4] != b'psbt', "expecting raw bitcoin txn"
    open('debug/finalized-by-ckcc.txn', 'wt').write(B2A(signed_final))

    # Sign again, but don't finalize it.
    start_sign(psbt, finalize=False)
    signed = end_sign(accept=True)

    open('debug/vs-signed-unfin.psbt', 'wb').write(signed)

    # Use bitcoind to finalize it this time.
    resp = bitcoind.finalizepsbt(str(b64encode(signed), 'ascii'), True)
    assert resp['complete'] == True, "bitcoind wasn't able to finalize it"

    network = a2b_hex(resp['hex'])

    # assert resp['complete']
    #print("Final txn: %r" % network)
    open('debug/finalized-by-btcd.txn', 'wt').write(B2A(network))

    assert network == signed_final, "Finalized differently"

    # try to send it
    txed = bitcoind.sendrawtransaction(B2A(network))
    print("Final txn hash: %r" % txed)
Example #19
0
def test_sign_p2sh_example(set_master_key, sim_execfile, start_sign, end_sign, decode_psbt_with_bitcoind, offer_ms_import, need_keypress, clear_ms):
    # Use the private key given in BIP 174 and do similar signing
    # as the examples.

    # PROBLEM: we can't handle this, since we don't allow same cosigner key to be used
    # more than once and that check happens after we decide we can sign an input, and yet
    # no way to provide the right set of keys needed since 4 in total, etc, etc.
    # - code below nearly works tho
    raise pytest.skip('difficult example')
    
    # expect xfp=4F6A0CD9
    exk = 'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF'
    set_master_key(exk)

    # Peeked at PSBT to know the full, deep hardened path we'll need.
    # in1: 0'/0'/0' and 0'/0'/1'
    # in2: 0'/0'/3' and 0'/0'/2'

    config = "name: p2sh-example\npolicy: 2 of 2\n\n"
    n1 = BIP32Node.from_hwif(exk).subkey_for_path("0'/0'").hwif()
    n2 = BIP32Node.from_hwif(exk).subkey_for_path("0'/0'").hwif()
    xfp = '4F6A0CD9'
    config += f'{xfp}: {n1}\n{xfp}: {n2}\n'

    clear_ms()
    offer_ms_import(config)
    time.sleep(.1)
    need_keypress('y')

    psbt = a2b_hex(open('data/worked-unsigned.psbt', 'rb').read())

    # PROBLEM: revised BIP-174 has p2sh multisig cases which we don't support yet.
    # - it has two signatures from same key on same input
    # - that's a rare case and not worth supporting in the firmware
    # - but we can do it in two passes
    # - the MS wallet is also hard, since dup xfp (same actual key) ... altho can
    #   provide different subkeys

    start_sign(psbt)
    part_signed = end_sign(True)

    open('debug/ex-signed-part.psbt', 'wb').write(part_signed)

    b4 = BasicPSBT().parse(psbt)
    aft = BasicPSBT().parse(part_signed)
    assert b4 != aft, "(partial) signing didn't change anything?"

    # NOTE: cannot handle combining multisig txn yet, so cannot finalize on-device
    start_sign(part_signed, finalize=False)
    signed = end_sign(True, finalize=False)

    open('debug/ex-signed.psbt', 'wb').write(signed)
    aft2 = BasicPSBT().parse(signed)

    decode = decode_psbt_with_bitcoind(signed)
    pprint(decode)

    mx_expect = BasicPSBT().parse(a2b_hex(open('data/worked-combined.psbt', 'rb').read()))
    assert aft2 == mx_expect

    expect = a2b_hex(open('data/worked-combined.psbt', 'rb').read())
    decode_ex = decode_psbt_with_bitcoind(expect)

    # NOTE: because we are using RFC6979, the exact bytes of the signatures should match

    for i in range(2):
        assert decode['inputs'][i]['partial_signatures'] == \
                    decode_ex['inputs'][i]['partial_signatures']

    if 0:
        import json, decimal
        def EncodeDecimal(o):
            if isinstance(o, decimal.Decimal):
                return float(round(o, 8))
            raise TypeError
        json.dump(decode, open('debug/core-decode.json', 'wt'), indent=2, default=EncodeDecimal)
Example #20
0
    def doit(num_ins, num_outs, M, keys, fee=10000,
                outvals=None, segwit_in=False, outstyles=['p2pkh'], change_outputs=[],
                incl_xpubs=False):
        psbt = BasicPSBT()
        txn = Tx(2,[],[])

        if incl_xpubs:
            # add global header with XPUB's
            # - assumes BIP45
            for xfp, m, sk in keys:
                kk = pack('<II', xfp, 45|0x80000000)
                psbt.xpubs.append( (sk.serialize(as_private=False), kk) )

        psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)]
        psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)]

        for i in range(num_ins):
            # make a fake txn to supply each of the inputs
            # - each input is 1BTC

            # addr where the fake money will be stored.
            addr, scriptPubKey, script, details = make_ms_address(M, keys, idx=i)

            # lots of supporting details needed for p2sh inputs
            if segwit_in:
                psbt.inputs[i].witness_script = script
            else:
                psbt.inputs[i].redeem_script = script

            for pubkey, xfp_path in details:
                psbt.inputs[i].bip32_paths[pubkey] = b''.join(pack('<I', j) for j in xfp_path)

            # UTXO that provides the funding for to-be-signed txn
            supply = Tx(2,[TxIn(pack('4Q', 0xdead, 0xbeef, 0, 0), 73)],[])

            supply.txs_out.append(TxOut(1E8, scriptPubKey))

            with BytesIO() as fd:
                if not segwit_in:
                    supply.stream(fd)
                    psbt.inputs[i].utxo = fd.getvalue()
                else:
                    supply.txs_out[-1].stream(fd)
                    psbt.inputs[i].witness_utxo = fd.getvalue()

            spendable = TxIn(supply.hash(), 0)
            txn.txs_in.append(spendable)


        for i in range(num_outs):
            # random P2PKH
            if not outstyles:
                style = ADDR_STYLES[i % len(ADDR_STYLES)]
            else:
                style = outstyles[i % len(outstyles)]

            if i in change_outputs:
                addr, scriptPubKey, scr, details = \
                    make_ms_address(M, keys, idx=i, addr_fmt=unmap_addr_fmt[style])

                for pubkey, xfp_path in details:
                    psbt.outputs[i].bip32_paths[pubkey] = b''.join(pack('<I', j) for j in xfp_path)

                if 'w' in style:
                    psbt.outputs[i].witness_script = scr
                    if style.endswith('p2sh'):
                        psbt.outputs[i].redeem_script = b'\0\x20' + sha256(scr).digest()
                elif style.endswith('sh'):
                    psbt.outputs[i].redeem_script = scr
            else:
                scr = fake_dest_addr(style)

            assert scr

            if not outvals:
                h = TxOut(round(((1E8*num_ins)-fee) / num_outs, 4), scriptPubKey)
            else:
                h = TxOut(outvals[i], scriptPubKey)

            txn.txs_out.append(h)

        with BytesIO() as b:
            txn.stream(b)
            psbt.txn = b.getvalue()

        rv = BytesIO()
        psbt.serialize(rv)
        assert rv.tell() <= MAX_TXN_LEN, 'too fat'

        return rv.getvalue()
Example #21
0
    def doit(num_ins,
             num_outs,
             master_xpub,
             subpath="0/%d",
             fee=10000,
             outvals=None,
             segwit_in=False,
             outstyles=['p2pkh'],
             change_outputs=[]):
        psbt = BasicPSBT()
        txn = Tx(2, [], [])

        # we have a key; use it to provide "plausible" value inputs
        mk = BIP32Node.from_wallet_key(master_xpub)
        xfp = mk.fingerprint()

        psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)]
        psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)]

        for i in range(num_ins):
            # make a fake txn to supply each of the inputs
            # - each input is 1BTC

            # addr where the fake money will be stored.
            subkey = mk.subkey_for_path(subpath % i)
            sec = subkey.sec()
            assert len(sec) == 33, "expect compressed"
            assert subpath[0:2] == '0/'

            psbt.inputs[i].bip32_paths[sec] = xfp + pack('<II', 0, i)

            # UTXO that provides the funding for to-be-signed txn
            supply = Tx(2, [TxIn(pack('4Q', 0xdead, 0xbeef, 0, 0), 73)], [])

            scr = bytes([0x76, 0xa9, 0x14]) + subkey.hash160() + bytes(
                [0x88, 0xac])

            supply.txs_out.append(TxOut(1E8, scr))

            with BytesIO() as fd:
                if not segwit_in:
                    supply.stream(fd)
                    psbt.inputs[i].utxo = fd.getvalue()
                else:
                    supply.txs_out[-1].stream(fd)
                    psbt.inputs[i].witness_utxo = fd.getvalue()

            spendable = TxIn(supply.hash(), 0)
            txn.txs_in.append(spendable)

        for i in range(num_outs):
            # random P2PKH
            if not outstyles:
                style = ADDR_STYLES[i % len(ADDR_STYLES)]
            else:
                style = outstyles[i % len(outstyles)]

            if i in change_outputs:
                scr, act_scr, isw, pubkey, sp = make_change_addr(mk, style)
                psbt.outputs[i].bip32_paths[pubkey] = sp
            else:
                scr = act_scr = fake_dest_addr(style)
                isw = ('w' in style)
                #if style.endswith('sh'):

            assert scr
            act_scr = act_scr or scr

            if isw:
                psbt.outputs[i].witness_script = scr
            elif style.endswith('sh'):
                psbt.outputs[i].redeem_script = scr

            if not outvals:
                h = TxOut(round(((1E8 * num_ins) - fee) / num_outs, 4),
                          act_scr)
            else:
                h = TxOut(outvals[i], act_scr)

            txn.txs_out.append(h)

        with BytesIO() as b:
            txn.stream(b)
            psbt.txn = b.getvalue()

        rv = BytesIO()
        psbt.serialize(rv)
        assert rv.tell() <= MAX_TXN_LEN, 'too fat'

        return rv.getvalue()
Example #22
0
def test_vs_bitcoind(match_key, check_against_bitcoind, bitcoind, start_sign, end_sign, we_finalize, num_dests):

    wallet_xfp = match_key()

    bal = bitcoind.getbalance()
    assert bal > 0, "need some play money; drink from a faucet"

    amt = round((bal/4)/num_dests, 6)

    args = {}

    for no in range(num_dests):
        dest = bitcoind.getrawchangeaddress()
        assert dest[0] in '2mn' or dest.startswith('tb1'), dest

        args[dest] = amt

    if 0:
        # old approach: fundraw + convert to psbt

        # working with hex strings here
        txn = bitcoind.createrawtransaction([], args)
        assert txn[0:2] == '02'
        #print(txn)

        resp = bitcoind.fundrawtransaction(txn)
        txn2 = resp['hex']
        fee = resp['fee']
        chg_pos = resp['changepos']
        #print(txn2)

        print("Sending %.8f XTN to %s (Change back in position: %d)" % (amt, dest, chg_pos))

        psbt = b64decode(bitcoind.converttopsbt(txn2, True))

    # use walletcreatefundedpsbt
    # - updated/validated against 0.17.1
    resp = bitcoind.walletcreatefundedpsbt([], args, 0, {
                'subtractFeeFromOutputs': list(range(num_dests)),
                'feeRate': 0.00001500}, True)

    if 0:
        # OMFG all this to reconstruct the rpc command!
        import json, decimal
        def EncodeDecimal(o):
            if isinstance(o, decimal.Decimal):
                return float(round(o, 8))
            raise TypeError

        print('walletcreatefundedpsbt "[]" "[%s]" 0 {} true' % json.dumps(args,
                    default=EncodeDecimal).replace('"', '\\"'))

    psbt = b64decode(resp['psbt'])
    fee = resp['fee']
    chg_pos = resp['changepos']

    open('debug/vs.psbt', 'wb').write(psbt)

    # check some basics
    mine = BasicPSBT().parse(psbt)
    for i in mine.inputs:
        got_xfp, = struct.unpack_from('I', list(i.bip32_paths.values())[0])
        #assert hex(got_xfp) == hex(wallet_xfp), "wrong HD master key fingerprint"

        # see <https://github.com/bitcoin/bitcoin/issues/15884>
        if hex(got_xfp) != hex(wallet_xfp):
            raise pytest.xfail("wrong HD master key fingerprint")

    # pull out included txn
    txn2 = B2A(mine.txn)

    start_sign(psbt, finalize=we_finalize)

    # verify against how bitcoind reads it
    check_against_bitcoind(txn2, fee)

    signed = end_sign(accept=True)
    open('debug/vs-signed.psbt', 'wb').write(signed)

    if not we_finalize:
        b4 = BasicPSBT().parse(psbt)
        aft = BasicPSBT().parse(signed)
        assert b4 != aft, "signing didn't change anything?"

        open('debug/signed.psbt', 'wb').write(signed)
        resp = bitcoind.finalizepsbt(str(b64encode(signed), 'ascii'), True)

        #combined_psbt = b64decode(resp['psbt'])
        #open('debug/combined.psbt', 'wb').write(combined_psbt)

        assert resp['complete'] == True, "bitcoind wasn't able to finalize it"

        network = a2b_hex(resp['hex'])

        # assert resp['complete']
        print("Final txn: %r" % network)
        open('debug/finalized-by-btcd.txn', 'wb').write(network)

        # try to send it
        txed = bitcoind.sendrawtransaction(B2A(network))
        print("Final txn hash: %r" % txed)

    else:
        assert signed[0:4] != b'psbt', "expecting raw bitcoin txn"
        #print("Final txn: %s" % B2A(signed))
        open('debug/finalized-by-cc.txn', 'wb').write(signed)

        txed = bitcoind.sendrawtransaction(B2A(signed))
        print("Final txn hash: %r" % txed)
Example #23
0
def build_psbt(ctx, xfp, addrs, pubkey=None, xpubs=None, redeem=None):
    locals().update(ctx.obj)
    payout_address = ctx.obj['payout_address']
    out_psbt = ctx.obj['output_psbt']

    if pubkey:
        assert len(addrs) == 1  # can only be single addr in that case
        assert len(pubkey) == 33

    spending = []
    total = 0
    psbt = BasicPSBT()

    for path, addr in addrs:
        print(f"addr: {path} => {addr} ... ", end='')

        rr = explora('address', addr, 'utxo')

        if not rr:
            print('nada')
            continue

        here = 0
        for u in rr:
            here += u['value']

            tt = TxIn(h2b_rev(u['txid']), u['vout'])
            spending.append(tt)
            #print(rr)

            pin = BasicPSBTInput(idx=len(psbt.inputs))
            psbt.inputs.append(pin)

            pubkey = pubkey or calc_pubkey(xpubs, path)

            pin.bip32_paths[pubkey] = str2path(xfp, path)

            # fetch the UTXO for witness signging
            td = explora('tx', u['txid'], 'hex', is_json=False)

            #print(f"txis {u['txid']}:\b{td!r}")
            outpt = Tx.from_hex(td.decode('ascii')).txs_out[u['vout']]

            with BytesIO() as b:
                outpt.stream(b)
                pin.witness_utxo = b.getvalue()

            if redeem:
                pin.redeem_script = redeem

        print('%.8f BTC' % (here / 1E8))
        total += here

        if len(spending) > 15:
            print("Reached practical limit on # of inputs. "
                  "You'll need to repeat this process again later.")
            break

    assert total, "Sorry! Didn't find any UTXO"

    print("Found total: %.8f BTC" % (total / 1E8))

    if payout_address:
        print("Planning to send to: %s" % payout_address)
        dest_scr = BTC.contract.for_address(payout_address)

        txn = Tx(2, spending, [TxOut(total, dest_scr)])
    else:
        print("Output section of PSBT will be empty. Change downstream")
        txn = Tx(2, spending, [])

    fee = tx_fee.recommended_fee_for_tx(txn)

    # placeholder, single output that isn't change
    pout = BasicPSBTOutput(idx=0)
    psbt.outputs.append(pout)

    print("Guestimate fee: %.8f BTC" % (fee / 1E8))

    if txn.txs_out:
        txn.txs_out[0].coin_value -= fee

    # write txn into PSBT
    with BytesIO() as b:
        txn.stream(b)
        psbt.txn = b.getvalue()

    out_psbt.write(psbt.as_bytes())

    print("PSBT to be signed:\n\n\t" + out_psbt.name, end='\n\n')
Example #24
0
    signed2 = end_sign(True)
    open('debug/chg-signed2.psbt', 'wb').write(signed)
    aft = BasicPSBT().parse(signed)
    aft2 = BasicPSBT().parse(signed2)
    assert aft.txn == aft2.txn

@pytest.mark.parametrize('case', [ 1, 2])
@pytest.mark.bitcoind
def test_change_fraud_path(start_sign, end_sign, case, check_against_bitcoind, cap_story):
    # fraud: BIP-32 path of output doesn't lead to pubkey indicated

    # NOTE: out#1 is change:
    chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'

    psbt = open('data/example-change.psbt', 'rb').read()
    b4 = BasicPSBT().parse(psbt)

    (pubkey, path), = b4.outputs[1].bip32_paths.items()
    skp = bytearray(b4.outputs[1].bip32_paths[pubkey])
    if case == 1:
        # change subkey
        skp[-2] ^= 0x01
    elif case == 2:
        # change xfp
        skp[0] ^= 0x01

    b4.outputs[1].bip32_paths[pubkey] = bytes(skp)

    with BytesIO() as fd:
        b4.serialize(fd)
        mod_psbt = fd.getvalue()
Example #25
0
    def doit(f_or_data,
             accept=True,
             finalize=False,
             accept_ms_import=False,
             complete=False):

        if f_or_data[0:5] == b'psbt\xff':
            ip = f_or_data
            filename = 'memory'
        else:
            filename = f_or_data
            ip = open(f_or_data, 'rb').read()
            if ip[0:10] == b'70736274ff':
                ip = a2b_hex(ip.strip())
            assert ip[0:5] == b'psbt\xff'

        psbtname = 'ftrysign'

        # population control
        from glob import glob
        import os
        pat = microsd_path(psbtname + '*.psbt')
        for f in glob(pat):
            assert 'psbt' in f
            os.unlink(f)

        with open_microsd(psbtname + '.psbt', 'wb') as sd:
            sd.write(ip)

        goto_home()
        pick_menu_item('Ready To Sign')

        time.sleep(.1)
        _, story = cap_story()
        if 'Choose PSBT file' in story:
            need_keypress('y')
            time.sleep(.1)

        pick_menu_item(psbtname + '.psbt')

        time.sleep(.1)

        if accept_ms_import:
            # XXX would be better to do cap_story here, but that would limit test to simulator
            need_keypress('y')
            time.sleep(0.050)

        title, story = cap_story()
        assert title == 'OK TO SEND?'

        if accept != None:
            need_keypress('y' if accept else 'x')

        if accept == False:
            time.sleep(0.050)

            # look for "Aborting..." ??
            return ip, None

        # wait for it to finish
        for r in range(10):
            time.sleep(0.1)
            title, story = cap_story()
            if title == 'PSBT Signed': break
        else:
            assert False, 'timed out'

        result_fname = story.split('\n')[-1]

        result = open_microsd(result_fname, 'rb').read()

        # read back final product
        if finalize:
            assert 'final' in result_fname

            from pycoin.tx.Tx import Tx
            # parse it a little
            assert result[
                0:4] != b'psbt', 'still a PSBT, but asked for finalize'
            t = Tx.from_bin(result)
            assert t.version in [1, 2]

        else:
            if complete:
                assert '-signed' in result_fname
            else:
                assert '-part' in result_fname

            from psbt import BasicPSBT
            was = BasicPSBT().parse(ip)
            now = BasicPSBT().parse(result)
            assert was.txn == now.txn
            assert was != now

        return ip, result