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 sign_tx(tx, utxo_list, is_test): secret_exponents = [ utxo.address_rec.rawPrivKey for utxo in utxo_list if utxo.address_rec ] solver = SecretExponentSolver(secret_exponents) txins = tx.txs_in[:] hash_type = SIGHASH_ALL for txin_idx in xrange(len(txins)): blank_txin = txins[txin_idx] utxo = None for utxo_candidate in utxo_list: if utxo_candidate.get_txhash() == blank_txin.previous_hash \ and utxo_candidate.outindex == blank_txin.previous_index: utxo = utxo_candidate break if not (utxo and utxo.address_rec): continue txout_script = utxo.script.decode('hex') signature_hash = tx.signature_hash(txout_script, txin_idx, hash_type=hash_type) txin_script = solver(txout_script, signature_hash, hash_type) txins[txin_idx] = TxIn(blank_txin.previous_hash, blank_txin.previous_index, txin_script, blank_txin.sequence) if not verify_script( txin_script, txout_script, signature_hash, hash_type=hash_type): raise Exception("invalid script") tx.txs_in = txins
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 test_multisig_one_at_a_time(self): M = 3 N = 3 keys = [ Key(secret_exponent=i, generator=secp256k1_generator) for i in range(1, N + 2) ] tx_in = TxIn.coinbase_tx_in(script=b'') script = script_for_multisig(m=M, sec_keys=[key.sec() for key in keys[:N]]) tx_out = TxOut(1000000, script) tx1 = Tx(version=1, txs_in=[tx_in], txs_out=[tx_out]) tx2 = tx_utils.create_tx(tx1.tx_outs_as_spendable(), [keys[-1].address()]) ids = [ "403e5bfc59e097bb197bf77a692d158dd3a4f7affb4a1fa41072dafe7bec7058", "5931d9995e83721243dca24772d7012afcd4378996a8b953c458175f15a544db", "9bb4421088190bbbb5b42a9eaa9baed7ec7574a407c25f71992ba56ca43d9c44", "03a1dc2a63f93a5cf5a7cb668658eb3fc2eda88c06dc287b85ba3e6aff751771" ] for i in range(1, N + 1): self.assertEqual(tx2.bad_signature_count(), 1) self.assertEqual(tx2.id(), ids[i - 1]) hash160_lookup = build_hash160_lookup( [keys[i - 1].secret_exponent()], [secp256k1_generator]) tx2.sign(hash160_lookup=hash160_lookup) self.assertEqual(tx2.id(), ids[i]) t1 = sorted(who_signed_tx(tx2, 0, UI)) t2 = sorted(((key.address(), SIGHASH_ALL) for key in keys[:i])) self.assertEqual(t1, t2) self.assertEqual(tx2.bad_signature_count(), 0)
def construct_standard_tx(composed_tx_spec, is_test): txouts = [] STANDARD_SCRIPT_OUT = "OP_DUP OP_HASH160 %s OP_EQUALVERIFY OP_CHECKSIG" for txout in composed_tx_spec.get_txouts(): hash160 = bitcoin_address_to_hash160_sec(txout.target_addr, is_test) script_text = STANDARD_SCRIPT_OUT % b2h(hash160) script_bin = tools.compile(script_text) txouts.append(TxOut(txout.value, script_bin)) txins = [] for cts_txin in composed_tx_spec.get_txins(): txin = TxIn(cts_txin.get_txhash(), cts_txin.prevout.n) if cts_txin.nSequence: print cts_txin.nSequence txin.sequence = cts_txin.nSequence txins.append(txin) version = 1 lock_time = 0 return Tx(version, txins, txouts, lock_time)
def construct_standard_tx(composed_tx_spec, is_test): txouts = [] STANDARD_SCRIPT_OUT = "OP_DUP OP_HASH160 %s OP_EQUALVERIFY OP_CHECKSIG" for txout in composed_tx_spec.get_txouts(): hash160 = bitcoin_address_to_hash160_sec(txout.target_addr, is_test) script_text = STANDARD_SCRIPT_OUT % b2h(hash160) script_bin = tools.compile(script_text) txouts.append(TxOut(txout.value, script_bin)) txins = [] for cts_txin in composed_tx_spec.get_txins(): txin = TxIn(cts_txin.get_txhash(), cts_txin.prevout.n) if cts_txin.nSequence: print cts_txin.nSequence txin.sequence = cts_txin.nSequence txins.append(txin) version = 1 lock_time = 0 return Tx(version, txins, txouts, lock_time)
def find_txins(service, addresses, amount): spendables = retrieve_utxos(service, addresses) txins = [] total = 0 for spendable in spendables: total += spendable.coin_value txins.append(TxIn(spendable.tx_hash, spendable.tx_out_index)) if total >= amount: return txins, total return txins, total
def spend_sh_fund(tx_ins, wif_keys, tx_outs): """ spend script hash fund the key point of an input comes from multisig address is that, its sign script is combined with several individual signs :param tx_ins: list with tuple(tx_id, idx, balance, address, redeem_script) :param wif_keys: private keys in wif format, technical should be the same order with the pubkey in redeem script, but pycoin has inner control, so here order is not mandatory :param tx_outs: balance, receiver_address :return: raw hex and tx id """ _txs_in = [] _un_spent = [] for tx_id, idx, balance, address, _ in tx_ins: # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append( Spendable( balance, script_obj_from_address(address, netcodes=[NET_CODE]).script(), tx_id_b, idx)) _txs_out = [] for balance, receiver_address in tx_outs: _txs_out.append( TxOut( balance, script_obj_from_address(receiver_address, netcodes=[NET_CODE]).script())) version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) # construct hash160_lookup[hash160] = (secret_exponent, public_pair, compressed) for each individual key hash160_lookup = build_hash160_lookup( [Key.from_text(wif_key).secret_exponent() for wif_key in wif_keys]) for i in range(0, len(tx_ins)): # you can add some conditions that if the input script is not p2sh type, not provide p2sh_lookup, # so that all kinds of inputs can work together p2sh_lookup = build_p2sh_lookup([binascii.unhexlify(tx_ins[i][-1])]) tx.sign_tx_in(hash160_lookup, i, tx.unspents[i].script, hash_type=SIGHASH_ALL, p2sh_lookup=p2sh_lookup) return tx.as_hex(), tx.id()
def multisig_M_of_N(self, M, N, unsigned_id, signed_id): keys = [Key(secret_exponent=i) for i in range(1, N+2)] tx_in = TxIn.coinbase_tx_in(script=b'') script = ScriptMultisig(m=M, sec_keys=[key.sec() for key in keys[:N]]).script() tx_out = TxOut(1000000, script) tx1 = Tx(version=1, txs_in=[tx_in], txs_out=[tx_out]) tx2 = tx_utils.create_tx(tx1.tx_outs_as_spendable(), [keys[-1].address()]) self.assertEqual(tx2.id(), unsigned_id) self.assertEqual(tx2.bad_signature_count(), 1) hash160_lookup = build_hash160_lookup(key.secret_exponent() for key in keys) tx2.sign(hash160_lookup=hash160_lookup) self.assertEqual(tx2.id(), signed_id) self.assertEqual(tx2.bad_signature_count(), 0) self.assertEqual(sorted(who_signed.who_signed_tx(tx2, 0)), sorted(((key.address(), SIGHASH_ALL) for key in keys[:M])))
def standard_tx(coins_from, coins_to): txs_in = [] unspents = [] for h, idx, tx_out in coins_from: txs_in.append(TxIn(h, idx)) unspents.append(tx_out) txs_out = [] for coin_value, address in coins_to: txs_out.append(TxOut(coin_value, script_for_address(address))) version, lock_time = 1, 0 tx = Tx(version, txs_in, txs_out, lock_time) tx.set_unspents(unspents) return tx
def test_sign_pay_to_script_multisig(self): M, N = 3, 3 keys = [Key(secret_exponent=i) for i in range(1, N+2)] tx_in = TxIn.coinbase_tx_in(script=b'') underlying_script = ScriptMultisig(m=M, sec_keys=[key.sec() for key in keys[:N]]).script() address = address_for_pay_to_script(underlying_script) self.assertEqual(address, "39qEwuwyb2cAX38MFtrNzvq3KV9hSNov3q") script = standard_tx_out_script(address) tx_out = TxOut(1000000, script) tx1 = Tx(version=1, txs_in=[tx_in], txs_out=[tx_out]) tx2 = tx_utils.create_tx(tx1.tx_outs_as_spendable(), [address]) hash160_lookup = build_hash160_lookup(key.secret_exponent() for key in keys[:N]) p2sh_lookup = build_p2sh_lookup([underlying_script]) tx2.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) self.assertEqual(tx2.bad_signature_count(), 0) self.assertRaises(who_signed.NoAddressesForScriptTypeError, who_signed.who_signed_tx, tx2, 0)
def tether_tx(tx_ins, private_key, send_amount, receiver): """ simple usdt transaction here assume utxo comes from the sender and is used for mine fee bitcoin change will be sent back to sender address of course different address's utxo can be used for mine fee, but should be aware sender is determined by the first input in the tx bitcoin change can also be sent back to different address, but should be aware receiver is indicated by the last output address that is not the sender for full customization, use btc sample p2pkh_tx :param tx_ins: utxo from the sender :param private_key: private key of the same sender :param send_amount: (display amount) * (10 ** 8) :param receiver: address to receive usdt """ _txs_in = [] _un_spent = [] total_bal = 0 for tx_id, idx, balance, address in tx_ins: total_bal += balance # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append(Spendable(balance, standard_tx_out_script(address), tx_id_b, idx)) txn_fee = estimate_p2pkh_tx_bytes(len(tx_ins), 3) * recommend_satoshi_per_byte() _txs_out = [TxOut(total_bal - txn_fee - 546, standard_tx_out_script(tx_ins[0][3])), TxOut(0, binascii.unhexlify(omni_tether_script(send_amount))), TxOut(546, standard_tx_out_script(receiver))] version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) solver = build_hash160_lookup([int(private_key, 16)] * len(tx_ins)) signed_tx = tx.sign(solver, hash_type=SIGHASH_ALL) return signed_tx.as_hex(), signed_tx.id()
def get_raw_unsigned(self, fee_satoshi): """Return raw transaction ready for signing May return a transaction with amount=0 if the input amount is not enough to cover fees """ txout = self.tx.txs_out[self.vout] amount_satoshi = txout.coin_value if fee_satoshi >= amount_satoshi: logging.warning('Insufficient funds to cover fee') logging.warning('txout has value of {}, fee = {}'.format( amount_satoshi, fee_satoshi)) # Calculate adjusted amount = input amount - fee adjusted_amount_satoshi = max(0, amount_satoshi - fee_satoshi) logging.debug('tx amount = amount - fee = {} - {} = {}'.format( amount_satoshi, fee_satoshi, adjusted_amount_satoshi)) assert adjusted_amount_satoshi >= 0 adjusted_amount_btc = decimal.Decimal( adjusted_amount_satoshi) / satoshi_per_btc logging.debug("Create tx: {} sat -> {}".format(adjusted_amount_satoshi, self.dest_address)) logging.info("Input tx id = {}, vout={}".format( self.tx.id().encode("ascii"), self.vout)) txin = TxIn(self.tx.hash(), self.vout, sequence=MAX_BIP125_RBF_SEQUENCE) scriptPubKey = pycoin.ui.script_obj_from_address(self.dest_address) txout = TxOut(adjusted_amount_satoshi, scriptPubKey.script()) # Set nlocktime to the current blockheight to discourage 'fee sniping', as per the core # wallet implementation nlocktime = util.get_current_blockcount() or 0 version = 1 tx = Tx(version, [ txin, ], [ txout, ], nlocktime) return tx.as_hex()
def test_multisig_one_at_a_time(self): M = 3 N = 3 keys = [Key(secret_exponent=i) for i in range(1, N+2)] tx_in = TxIn.coinbase_tx_in(script=b'') script = ScriptMultisig(m=M, sec_keys=[key.sec() for key in keys[:N]]).script() tx_out = TxOut(1000000, script) tx1 = Tx(version=1, txs_in=[tx_in], txs_out=[tx_out]) tx2 = tx_utils.create_tx(tx1.tx_outs_as_spendable(), [keys[-1].address()]) ids = ["403e5bfc59e097bb197bf77a692d158dd3a4f7affb4a1fa41072dafe7bec7058", "5931d9995e83721243dca24772d7012afcd4378996a8b953c458175f15a544db", "9bb4421088190bbbb5b42a9eaa9baed7ec7574a407c25f71992ba56ca43d9c44", "03a1dc2a63f93a5cf5a7cb668658eb3fc2eda88c06dc287b85ba3e6aff751771"] for i in range(1, N+1): self.assertEqual(tx2.bad_signature_count(), 1) self.assertEqual(tx2.id(), ids[i-1]) hash160_lookup = build_hash160_lookup(key.secret_exponent() for key in keys[i-1:i]) tx2.sign(hash160_lookup=hash160_lookup) self.assertEqual(tx2.id(), ids[i]) self.assertEqual(sorted(who_signed.who_signed_tx(tx2, 0)), sorted(((key.address(), SIGHASH_ALL) for key in keys[:i]))) self.assertEqual(tx2.bad_signature_count(), 0)
def test_sign_pay_to_script_multisig(self): M, N = 3, 3 keys = [ Key(secret_exponent=i, generator=secp256k1_generator) for i in range(1, N + 2) ] tx_in = TxIn.coinbase_tx_in(script=b'') underlying_script = script_for_multisig( m=M, sec_keys=[key.sec() for key in keys[:N]]) address = address_for_p2s(underlying_script) self.assertEqual(address, "39qEwuwyb2cAX38MFtrNzvq3KV9hSNov3q") script = script_for_address(address) tx_out = TxOut(1000000, script) tx1 = Tx(version=1, txs_in=[tx_in], txs_out=[tx_out]) tx2 = tx_utils.create_tx(tx1.tx_outs_as_spendable(), [address]) hash160_lookup = build_hash160_lookup( (key.secret_exponent() for key in keys[:N]), [secp256k1_generator]) p2sh_lookup = build_p2sh_lookup([underlying_script]) tx2.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) self.assertEqual(tx2.bad_signature_count(), 0) self.assertEqual( sorted(who_signed_tx(tx2, 0, UI)), sorted(((key.address(), SIGHASH_ALL) for key in keys[:M])))
def spend_pkh_fund(tx_ins, in_keys, tx_outs): """ p2pkh address send to p2pkh p2sh transaction :param tx_ins: list with tuple(tx_id, idx, balance, address) :param in_keys: list of private keys in hex format corresponding to each input :param tx_outs: balance, receiver_address :return: raw hex and tx id """ _txs_in = [] _un_spent = [] for tx_id, idx, balance, address in tx_ins: # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append( Spendable( balance, script_obj_from_address(address, netcodes=[NET_CODE]).script(), tx_id_b, idx)) _txs_out = [] for balance, receiver_address in tx_outs: _txs_out.append( TxOut( balance, script_obj_from_address(receiver_address, netcodes=[NET_CODE]).script())) version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) solver = build_hash160_lookup([int(pri_hex, 16) for pri_hex in in_keys]) tx.sign(solver, hash_type=SIGHASH_ALL) return tx.as_hex(), tx.id()
def fake_txn(num_ins, num_outs, master_xpub=None, subpath="0/%d", fee=10000, outvals=None, segwit_in=False, outstyles=['p2pkh'], is_testnet=False, change_style='p2pkh', partial=False, change_outputs=[]): # make various size txn's ... completely fake and pointless values # - but has UTXO's to match needs # - input total = num_inputs * 1BTC from pycoin.tx.Tx import Tx from pycoin.tx.TxIn import TxIn from pycoin.tx.TxOut import TxOut from pycoin.serialize import h2b_rev from struct import pack 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)] outputs = [] 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/' if partial and (i == 0): psbt.inputs[i].bip32_paths[sec] = b'Nope' + pack('<II', 0, i) else: 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): is_change = False # 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, change_style) psbt.outputs[i].bip32_paths[pubkey] = sp is_change = True else: scr = act_scr = fake_dest_addr(style) isw = ('w' in style) 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) outputs.append( (Decimal(h.coin_value) / Decimal(1E8), act_scr, is_change)) txn.txs_out.append(h) with BytesIO() as b: txn.stream(b) psbt.txn = b.getvalue() rv = BytesIO() psbt.serialize(rv) return rv.getvalue(), [(n, render_address(s, is_testnet), ic) for n, s, ic in outputs]
def txin(txhash, index): txhash = txid(txhash) index = positive_integer(index) return TxIn(txhash, index)
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()