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 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 main(): parser = create_parser() args = parser.parse_args() (txs, spendables, payables, key_iters, p2sh_lookup, tx_db, warning_tx_cache, warning_tx_for_tx_hash, warning_spendables) = parse_context(args, parser) txs_in = [] txs_out = [] unspents = [] # we use a clever trick here to keep each tx_in corresponding with its tx_out for tx in txs: smaller = min(len(tx.txs_in), len(tx.txs_out)) txs_in.extend(tx.txs_in[:smaller]) txs_out.extend(tx.txs_out[:smaller]) unspents.extend(tx.unspents[:smaller]) for tx in txs: smaller = min(len(tx.txs_in), len(tx.txs_out)) txs_in.extend(tx.txs_in[smaller:]) txs_out.extend(tx.txs_out[smaller:]) unspents.extend(tx.unspents[smaller:]) for spendable in spendables: txs_in.append(spendable.tx_in()) unspents.append(spendable) for address, coin_value in payables: script = standard_tx_out_script(address) txs_out.append(TxOut(coin_value, script)) lock_time = args.lock_time version = args.transaction_version # if no lock_time is explicitly set, inherit from the first tx or use default if lock_time is None: if txs: lock_time = txs[0].lock_time else: lock_time = DEFAULT_LOCK_TIME # if no version is explicitly set, inherit from the first tx or use default if version is None: if txs: version = txs[0].version else: version = DEFAULT_VERSION if args.remove_tx_in: s = set(args.remove_tx_in) txs_in = [tx_in for idx, tx_in in enumerate(txs_in) if idx not in s] if args.remove_tx_out: s = set(args.remove_tx_out) txs_out = [ tx_out for idx, tx_out in enumerate(txs_out) if idx not in s ] tx = Tx(txs_in=txs_in, txs_out=txs_out, lock_time=lock_time, version=version, unspents=unspents) fee = args.fee try: if len(payables) > 0: distribute_from_split_pool(tx, fee) except ValueError as ex: print("warning: %s" % ex.args[0], file=sys.stderr) unsigned_before = tx.bad_signature_count() unsigned_after = unsigned_before if unsigned_before > 0 and key_iters: def wif_iter(iters): while len(iters) > 0: for idx, iter in enumerate(iters): try: wif = next(iter) yield wif except StopIteration: iters = iters[:idx] + iters[idx + 1:] break print("signing...", file=sys.stderr) sign_tx(tx, wif_iter(key_iters), p2sh_lookup=p2sh_lookup) unsigned_after = tx.bad_signature_count() if unsigned_after > 0: print("warning: %d TxIn items still unsigned" % unsigned_after, file=sys.stderr) if len(tx.txs_in) == 0: print("warning: transaction has no inputs", file=sys.stderr) if len(tx.txs_out) == 0: print("warning: transaction has no outputs", file=sys.stderr) include_unspents = (unsigned_after > 0) tx_as_hex = tx.as_hex(include_unspents=include_unspents) if args.output_file: f = args.output_file if f.name.endswith(".hex"): f.write(tx_as_hex.encode("utf8")) else: tx.stream(f) if include_unspents: tx.stream_unspents(f) f.close() elif args.show_unspents: for spendable in tx.tx_outs_as_spendable(): print(spendable.as_text()) else: if not tx.missing_unspents(): check_fees(tx) dump_tx(tx, args.network, args.verbose_signature, args.disassemble, args.trace, args.pdb) if include_unspents: print( "including unspents in hex dump since transaction not fully signed" ) print(tx_as_hex) if args.cache: if tx_db is None: warning_tx_cache = message_about_tx_cache_env() warning_tx_for_tx_hash = message_about_tx_for_tx_hash_env( args.network) tx_db = get_tx_db(args.network) tx_db.put(tx) if args.bitcoind_url: if tx_db is None: warning_tx_cache = message_about_tx_cache_env() warning_tx_for_tx_hash = message_about_tx_for_tx_hash_env( args.network) tx_db = get_tx_db(args.network) validate_bitcoind(tx, tx_db, args.bitcoind_url) if tx.missing_unspents(): print("\n** can't validate transaction as source transactions missing", file=sys.stderr) else: try: if tx_db is None: warning_tx_cache = message_about_tx_cache_env() warning_tx_for_tx_hash = message_about_tx_for_tx_hash_env( args.network) tx_db = get_tx_db(args.network) tx.validate_unspents(tx_db) print('all incoming transaction values validated') except BadSpendableError as ex: print("\n**** ERROR: FEES INCORRECTLY STATED: %s" % ex.args[0], file=sys.stderr) except Exception as ex: print( "\n*** can't validate source transactions as untampered: %s" % ex.args[0], file=sys.stderr) # print warnings for m in [warning_tx_cache, warning_tx_for_tx_hash, warning_spendables]: if m: print("warning: %s" % m, file=sys.stderr)
def doit(num_ins, num_outs, master_xpub=None, subpath="0/%d", fee=10000, outvals=None, segwit_in=False, outstyles=['p2pkh'], psbt_hacker=None, change_outputs=[], capture_scripts=None, add_xpub=None): psbt = BasicPSBT() txn = Tx(2, [], []) master_xpub = master_xpub or simulator_fixed_xprv # 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) 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) if capture_scripts is not None: capture_scripts.append(act_scr) txn.txs_out.append(h) with BytesIO() as b: txn.stream(b) psbt.txn = b.getvalue() if add_xpub: # some people want extra xpub data in their PSBTs from pycoin.encoding import a2b_base58 psbt.xpubs = [(a2b_base58(master_xpub), xfp)] # last minute chance to mod PSBT object if psbt_hacker: psbt_hacker(psbt) rv = BytesIO() psbt.serialize(rv) assert rv.tell() <= MAX_TXN_LEN, 'too fat' return rv.getvalue()
def _test_sighash_single(self, network): Key = network.ui._key_class k0 = Key(secret_exponent=PRIV_KEYS[0], generator=secp256k1_generator, is_compressed=True) k1 = Key(secret_exponent=PRIV_KEYS[1], generator=secp256k1_generator, is_compressed=True) k2 = Key(secret_exponent=PRIV_KEYS[2], generator=secp256k1_generator, is_compressed=True) k3 = Key(secret_exponent=PRIV_KEYS[3], generator=secp256k1_generator, is_compressed=True) k4 = Key(secret_exponent=PRIV_KEYS[4], generator=secp256k1_generator, is_compressed=True) k5 = Key(secret_exponent=PRIV_KEYS[5], generator=secp256k1_generator, is_compressed=True) # Fake a coinbase transaction coinbase_tx = Tx.coinbase_tx(k0.sec(), 500000000) coinbase_tx.txs_out.append( TxOut(1000000000, BitcoinScriptTools.compile('%s OP_CHECKSIG' % b2h(k1.sec())))) coinbase_tx.txs_out.append( TxOut(1000000000, BitcoinScriptTools.compile('%s OP_CHECKSIG' % b2h(k2.sec())))) self.assertEqual( '2acbe1006f7168bad538b477f7844e53de3a31ffddfcfc4c6625276dd714155a', b2h_rev(coinbase_tx.hash())) script_for_address = network.ui.script_for_address # Make the test transaction txs_in = [ TxIn(coinbase_tx.hash(), 0), TxIn(coinbase_tx.hash(), 1), TxIn(coinbase_tx.hash(), 2), ] txs_out = [ TxOut(900000000, script_for_address(k3.address())), TxOut(800000000, script_for_address(k4.address())), TxOut(800000000, script_for_address(k5.address())), ] tx = Tx(1, txs_in, txs_out) tx.set_unspents(coinbase_tx.txs_out) self.assertEqual( '791b98ef0a3ac87584fe273bc65abd89821569fd7c83538ac0625a8ca85ba587', b2h_rev(tx.hash())) sig_type = SIGHASH_SINGLE solution_checker = BitcoinSolutionChecker(tx) sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[0].script, 0, sig_type) self.assertEqual( 0xcc52d785a3b4133504d1af9e60cd71ca422609cb41df3a08bbb466b2a98a885e, sig_hash) sig = sigmake(k0, sig_hash, sig_type) self.assertTrue(sigcheck(k0, sig_hash, sig[:-1])) tx.txs_in[0].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(0)) sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[1].script, 1, sig_type) self.assertEqual( 0x93bb883d70fccfba9b8aa2028567aca8357937c65af7f6f5ccc6993fd7735fb7, sig_hash) sig = sigmake(k1, sig_hash, sig_type) self.assertTrue(sigcheck(k1, sig_hash, sig[:-1])) tx.txs_in[1].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(1)) sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[2].script, 2, sig_type) self.assertEqual( 0x53ef7f67c3541bffcf4e0d06c003c6014e2aa1fb38ff33240b3e1c1f3f8e2a35, sig_hash) sig = sigmake(k2, sig_hash, sig_type) self.assertTrue(sigcheck(k2, sig_hash, sig[:-1])) tx.txs_in[2].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(2)) sig_type = SIGHASH_SINGLE | SIGHASH_ANYONECANPAY sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[0].script, 0, sig_type) self.assertEqual( 0x2003393d246a7f136692ce7ab819c6eadc54ffea38eb4377ac75d7d461144e75, sig_hash) sig = sigmake(k0, sig_hash, sig_type) self.assertTrue(sigcheck(k0, sig_hash, sig[:-1])) tx.txs_in[0].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(0)) sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[1].script, 1, sig_type) self.assertEqual( 0xe3f469ac88e9f35e8eff0bd8ad4ad3bf899c80eb7645947d60860de4a08a35df, sig_hash) sig = sigmake(k1, sig_hash, sig_type) self.assertTrue(sigcheck(k1, sig_hash, sig[:-1])) tx.txs_in[1].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(1)) sig_hash = solution_checker._signature_hash( coinbase_tx.txs_out[2].script, 2, sig_type) self.assertEqual( 0xbacd7c3ab79cad71807312677c1788ad9565bf3c00ab9a153d206494fb8b7e6a, sig_hash) sig = sigmake(k2, sig_hash, sig_type) self.assertTrue(sigcheck(k2, sig_hash, sig[:-1])) tx.txs_in[2].script = BitcoinScriptTools.compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(2))
async def build_tx(self, priv: str, addrs: List[Tuple[str, D]], split_fee=True): """ We distribute fee equally on every recipient by reducing the amount of money they will receive. The rest will go back to the sender as a change. :param priv: WIF private key of sender -> str :param addrs: distribution -> [(addr1, amount1), (addr2, amount2),...] :return: transaction id -> str """ addr = Key.from_text(priv).address() spendables = await self.get_spendable_list_for_addr(addr) addrs.append((addr, D(0))) txs_out = [] for payable in addrs: bitcoin_address, coin_value = payable coin_value *= COIN script = standard_tx_out_script(bitcoin_address) txs_out.append(TxOut(coin_value, script)) txs_in = [spendable.tx_in() for spendable in spendables] tx = Tx(version=1, txs_in=txs_in, txs_out=txs_out, lock_time=0) tx.set_unspents(spendables) fee = await self.calc_fee(tx) total_coin_value = sum(spendable.coin_value for spendable in tx.unspents) coins_allocated = sum(tx_out.coin_value for tx_out in tx.txs_out) if split_fee: fee_per_tx_out, extra_count = divmod(fee, len(tx.txs_out) - 1) if coins_allocated > total_coin_value: raise NotEnoughAmountError( 'Coins allocated exceeds total spendable: ' f'allocated: {coins_allocated}, ' f'spendable: {total_coin_value}') for tx_out in tx.txs_out: if tx_out.address(netcode=self.NETCODE) == addr: tx_out.coin_value = total_coin_value - coins_allocated else: tx_out.coin_value -= fee_per_tx_out if extra_count > 0: tx_out.coin_value -= 1 extra_count -= 1 if tx_out.coin_value < 1: raise NotEnoughAmountError( 'Not enough in each output to spread fee evenly: ' f'{tx_out.address} allocated too little') else: if (coins_allocated + fee) > total_coin_value: raise NotEnoughAmountError( 'Coins allocated exceeds total spendable: ' f'allocated: {coins_allocated}, ' f'fee: {fee}, ' f'spendable: {total_coin_value}') for tx_out in tx.txs_out: if tx_out.address(netcode=self.NETCODE) == addr: tx_out.coin_value = (total_coin_value - coins_allocated - fee) break return tx