def _create_bip69_tx(spendables: List[Spendable], payables: List[Tuple[str, int]], rbf: bool, version: int = 1) -> Tx: spendables.sort(key=lambda utxo: (utxo.as_dict()["tx_hash_hex"], utxo.as_dict()["tx_out_index"])) # Create input list from utxos # Set sequence numbers to zero if using RBF. txs_in = [spendable.tx_in() for spendable in spendables] # type: List[TxIn] if rbf: logging.info("Spending with opt-in Replace by Fee! (RBF)") for txin in txs_in: txin.sequence = 0 # Create output list from payables txs_out = list() # type: List[TxOut] for payable in payables: bitcoin_address, coin_value = payable script = standard_tx_out_script(bitcoin_address) # type: bytes txs_out.append(TxOut(coin_value, script)) txs_out.sort(key=lambda txo: (txo.coin_value, b2h(txo.script))) tx = Tx(version=version, txs_in=txs_in, txs_out=txs_out) # type: Tx tx.set_unspents(spendables) return tx
def test_sign_p2sh(self): tx_out_script = h2b( "76a91491b24bf9f5288532960ac687abb035127b1d28a588ac") script = script_for_address("1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm") self.assertEqual(tx_out_script, script) tx_out = TxOut(100, tx_out_script) tx = Tx(1, [TxIn(b'\1' * 32, 1)], [TxOut(100, tx_out_script)]) tx.set_unspents([tx_out]) hl = build_hash160_lookup([1], [secp256k1_generator]) self.assertEqual(tx.bad_signature_count(), 1) tx.sign(hash160_lookup=hl) self.assertEqual(tx.bad_signature_count(), 0)
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 make_test_tx(self, input_script): previous_hash = b'\1' * 32 txs_in = [TxIn(previous_hash, 0)] txs_out = [ TxOut( 1000, script_for_address( Key(1, generator=secp256k1_generator).address())) ] version, lock_time = 1, 0 tx = Tx(version, txs_in, txs_out, lock_time) unspents = [TxOut(1000, input_script)] tx.set_unspents(unspents) return tx
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 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, bitcoin_address in coins_to: txs_out.append(TxOut(coin_value, standard_tx_out_script(bitcoin_address))) version, lock_time = 1, 0 tx = Tx(version, txs_in, txs_out, lock_time) tx.set_unspents(unspents) return tx
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 _create_replacement_tx(self, hist_obj: History, version: int = 1) -> Tuple[Tx, Set[str], int]: """ Builds a replacement Bitcoin transaction based on a given History object in order to implement opt in Replace-By-Fee. :param hist_obj: a History object from our tx history data :param version: an int representing the Tx version :returns: A not-fully-formed and unsigned replacement Tx object :raise: Raises a ValueError if tx not a spend or is already confirmed """ if hist_obj.height == 0 and hist_obj.is_spend: old_tx = hist_obj.tx_obj # type: Tx spendables = old_tx.unspents # type: List[Spendable] chg_vout = None # type: int in_addrs = set() # type: Set[str] for utxo in spendables: in_addrs.add(utxo.address(self.chain.netcode)) txs_out = list() # type: List[TxOut] for i, txout in enumerate(old_tx.txs_out): value = None # type: int if txout.coin_value / Wallet.COIN == hist_obj.value: value = txout.coin_value else: value = 0 chg_vout = i txs_out.append(TxOut(value, txout.script)) new_tx = Tx(version=version, txs_in=old_tx.txs_in, txs_out=txs_out) # type: Tx new_tx.set_unspents(spendables) return new_tx, in_addrs, chg_vout else: raise ValueError("This transaction is not replaceable")
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 _create_bip69_tx(spendables: List[Spendable], payables: List[Tuple[str, int]], rbf: bool, version: int = 1) -> Tx: """ Create tx inputs and outputs from spendables and payables. Sort lexicographically and return unsigned Tx object. :param spendables: A list of Spendable objects :param payables: A list of payable tuples :param rbf: Replace by fee flag :param version: Tx format version :returns: Fully formed but unsigned Tx object """ spendables.sort(key=lambda utxo: (utxo.as_dict()["tx_hash_hex"], utxo.as_dict()["tx_out_index"])) # Create input list from utxos # Set sequence numbers to zero if using RBF. txs_in = [spendable.tx_in() for spendable in spendables] # type: List[TxIn] if rbf: logging.info("Spending with opt-in Replace by Fee! (RBF)") for txin in txs_in: txin.sequence = 0 # Create output list from payables txs_out = [] # type: List[TxOut] for payable in payables: bitcoin_address, coin_value = payable script = standard_tx_out_script(bitcoin_address) # type: bytes txs_out.append(TxOut(coin_value, script)) txs_out.sort(key=lambda txo: (txo.coin_value, b2h(txo.script))) tx = Tx(version=version, txs_in=txs_in, txs_out=txs_out) # type: Tx tx.set_unspents(spendables) return tx
def _test_sighash_single(self, netcode): k0 = Key(secret_exponent=PRIV_KEYS[0], is_compressed=True, netcode=netcode) k1 = Key(secret_exponent=PRIV_KEYS[1], is_compressed=True, netcode=netcode) k2 = Key(secret_exponent=PRIV_KEYS[2], is_compressed=True, netcode=netcode) k3 = Key(secret_exponent=PRIV_KEYS[3], is_compressed=True, netcode=netcode) k4 = Key(secret_exponent=PRIV_KEYS[4], is_compressed=True, netcode=netcode) k5 = Key(secret_exponent=PRIV_KEYS[5], is_compressed=True, netcode=netcode) # Fake a coinbase transaction coinbase_tx = Tx.coinbase_tx(k0.sec(), 500000000) coinbase_tx.txs_out.append( TxOut(1000000000, pycoin_compile('%s OP_CHECKSIG' % b2h(k1.sec())))) coinbase_tx.txs_out.append( TxOut(1000000000, pycoin_compile('%s OP_CHECKSIG' % b2h(k2.sec())))) self.assertEqual( '2acbe1006f7168bad538b477f7844e53de3a31ffddfcfc4c6625276dd714155a', b2h_rev(coinbase_tx.hash())) # 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, standard_tx_out_script(k3.address())), TxOut(800000000, standard_tx_out_script(k4.address())), TxOut(800000000, standard_tx_out_script(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 sig_hash = tx.signature_hash(coinbase_tx.txs_out[0].script, 0, sig_type) self.assertEqual( 'cc52d785a3b4133504d1af9e60cd71ca422609cb41df3a08bbb466b2a98a885e', b2h(to_bytes_32(sig_hash))) sig = sigmake(k0, sig_hash, sig_type) self.assertTrue(sigcheck(k0, sig_hash, sig[:-1])) tx.txs_in[0].script = pycoin_compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(0)) sig_hash = tx.signature_hash(coinbase_tx.txs_out[1].script, 1, sig_type) self.assertEqual( '93bb883d70fccfba9b8aa2028567aca8357937c65af7f6f5ccc6993fd7735fb7', b2h(to_bytes_32(sig_hash))) sig = sigmake(k1, sig_hash, sig_type) self.assertTrue(sigcheck(k1, sig_hash, sig[:-1])) tx.txs_in[1].script = pycoin_compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(1)) sig_hash = tx.signature_hash(coinbase_tx.txs_out[2].script, 2, sig_type) self.assertEqual( '53ef7f67c3541bffcf4e0d06c003c6014e2aa1fb38ff33240b3e1c1f3f8e2a35', b2h(to_bytes_32(sig_hash))) sig = sigmake(k2, sig_hash, sig_type) self.assertTrue(sigcheck(k2, sig_hash, sig[:-1])) tx.txs_in[2].script = pycoin_compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(2)) sig_type = SIGHASH_SINGLE | SIGHASH_ANYONECANPAY sig_hash = tx.signature_hash(coinbase_tx.txs_out[0].script, 0, sig_type) self.assertEqual( '2003393d246a7f136692ce7ab819c6eadc54ffea38eb4377ac75d7d461144e75', b2h(to_bytes_32(sig_hash))) sig = sigmake(k0, sig_hash, sig_type) self.assertTrue(sigcheck(k0, sig_hash, sig[:-1])) tx.txs_in[0].script = pycoin_compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(0)) sig_hash = tx.signature_hash(coinbase_tx.txs_out[1].script, 1, sig_type) self.assertEqual( 'e3f469ac88e9f35e8eff0bd8ad4ad3bf899c80eb7645947d60860de4a08a35df', b2h(to_bytes_32(sig_hash))) sig = sigmake(k1, sig_hash, sig_type) self.assertTrue(sigcheck(k1, sig_hash, sig[:-1])) tx.txs_in[1].script = pycoin_compile(b2h(sig)) self.assertTrue(tx.is_signature_ok(1)) sig_hash = tx.signature_hash(coinbase_tx.txs_out[2].script, 2, sig_type) self.assertEqual( 'bacd7c3ab79cad71807312677c1788ad9565bf3c00ab9a153d206494fb8b7e6a', b2h(to_bytes_32(sig_hash))) sig = sigmake(k2, sig_hash, sig_type) self.assertTrue(sigcheck(k2, sig_hash, sig[:-1])) tx.txs_in[2].script = pycoin_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