Exemple #1
0
    def doit(M, keys, addr_fmt=AF_P2SH, bip45=True, **make_redeem_args):
        # test we are showing addresses correctly
        # - verifies against bitcoind as well
        addr_fmt = unmap_addr_fmt.get(addr_fmt, addr_fmt)

        # make a redeem script, using provided keys/pubkeys
        if bip45:
            make_redeem_args['path_mapper'] = lambda i: [HARD(45), i, 0,0]

        scr, pubkeys, xfp_paths = make_redeem(M, keys, **make_redeem_args)
        assert len(scr) <= 520, "script too long for standard!"

        got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
                                    M, xfp_paths, scr, addr_fmt=addr_fmt),
                                    timeout=None)

        title, story = cap_story()

        #print(story)

        assert got_addr in story
        assert all((xfp2str(xfp) in story) for xfp,_,_ in keys)
        if bip45:
            for i in range(len(keys)):
                assert ('/_/%d/0/0' % i) in story

        need_keypress('y')
        # check expected addr was generated based on my math
        addr_vs_path(got_addr, addr_fmt=addr_fmt, script=scr)

        # also check against bitcoind
        core_addr, core_scr = bitcoind_p2sh(M, pubkeys, addr_fmt)
        assert B2A(scr) == core_scr
        assert core_addr == got_addr
Exemple #2
0
def show_address(path_prefix,
                 script,
                 fingerprints,
                 min_signers=0,
                 quiet=False,
                 segwit=False,
                 wrap=False):
    '''Show a multisig payment address on-screen

    Append subkey path to fingerprint value (4369050F/1/0/0 for example) or omit for 0/0/0

    This is provided as a demo or debug feature: you'll need the full redeem script (hex),
    and the fingerprints and paths used to generate each public key inside that.
    Order of fingerprint/path must match order of pubkeys in script.
    '''

    dev = ColdcardDevice(sn=force_serial)

    addr_fmt = AF_P2SH
    if segwit:
        addr_fmt = AF_P2WSH
    if wrap:
        addr_fmt = AF_P2WSH_P2SH

    script = a2b_hex(script)
    N = len(fingerprints)

    if N <= 16:
        if not min_signers:
            assert N <= 15
            min_signers = script[0] - 80
        else:
            assert min_signers == script[0], "M conficts with script"

        assert script[-1] == 0xAE, "expect script to end with OP_CHECKMULTISIG"
        assert script[-2] == 80 + N, "second last byte should encode N"

    xfp_paths = []
    for idx, xfp in enumerate(fingerprints):
        if '/' not in xfp:
            # this isn't BIP45 compliant but we don't know the cosigner's index
            # values, since they would have been suffled when the redeem script is sorted
            p = '0/0/0'
        else:
            # better if all paths provided
            xfp, p = xfp.split('/', 1)
            p = path_prefix + p

        xfp_paths.append(str_to_int_path(xfp, p))

    addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(min_signers,
                                                            xfp_paths,
                                                            script,
                                                            addr_fmt=addr_fmt),
                         timeout=None)

    if quiet:
        click.echo(addr)
    else:
        click.echo('Displaying address:\n\n%s\n' % addr)
Exemple #3
0
def show_address(script, fingerprints, quiet=False, segwit=False, wrap=False):
    '''Show a multisig payment address on-screen.

    Needs a redeem script and list of fingerprint/path (4369050F/1/0/0 for example).

    This is provided as a demo or debug feature. You'll need need some way to
    generate the full redeem script (hex), and the fingerprints and paths used to
    generate each public key inside that. The order of fingerprint/paths must
    match order of pubkeys in the script.
    '''

    dev = ColdcardDevice(sn=force_serial)

    addr_fmt = AF_P2SH
    if segwit:
        addr_fmt = AF_P2WSH
    if wrap:
        addr_fmt = AF_P2WSH_P2SH

    script = a2b_hex(script)
    N = len(fingerprints)

    assert 1 <= N <= 15, "bad N"

    min_signers = script[0] - 80
    assert 1 <= min_signers <= N, "bad M"

    assert script[-1] == 0xAE, "expect script to end with OP_CHECKMULTISIG"
    assert script[-2] == 80 + N, "second last byte should encode N"

    xfp_paths = []
    for idx, xfp in enumerate(fingerprints):
        assert '/' in xfp, 'Needs a XFP/path: ' + xfp
        xfp, p = xfp.split('/', 1)

        xfp_paths.append(str_to_int_path(xfp, p))

    addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(min_signers,
                                                            xfp_paths,
                                                            script,
                                                            addr_fmt=addr_fmt),
                         timeout=None)

    if quiet:
        click.echo(addr)
    else:
        click.echo('Displaying address:\n\n%s\n' % addr)
Exemple #4
0
 def show_p2sh_address(self, *args, **kws):
     # prompt user w/ p2sh address, also returns it immediately.
     return self.dev.send_recv(CCProtocolPacker.show_p2sh_address(
         *args, **kws),
                               timeout=None)
Exemple #5
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)