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