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