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