def creates_add_input(bitcoind, tx): """Creates and add an input to a CMutableTransaction, SIGHASH_ALL. :returns: The txid of the first stage fee bumping tx (for convenience) """ # First we get some coins privkey = CKey(os.urandom(32)) scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) # Let's say we want to increase the fees by 5000 sats amount = 5000 # Bitcoind is nice and will create the first stage transaction first_txid = bitcoind.rpc.sendtoaddress(str(address), amount / COIN) vout_index = get_output_index( bitcoind.rpc.getrawtransaction(first_txid, 1), amount) # === We don't generate a block yet ! === tx.vin.append( CTxIn(COutPoint(lx(first_txid), vout_index), nSequence=0xfffffffe)) # Sign the new input with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub]))) return first_txid
def add_input_output(bitcoind, tx): """Add an input and an output to a CMutableTransaction, SIGHASH_ALL.""" # First we get some coins privkey = CKey(os.urandom(32)) scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) amount = Decimal("50") * Decimal(COIN) - Decimal("500") # This creates a one-output transaction txid = bitcoind.pay_to(str(address), amount / Decimal(COIN)) # We bump the fees by 5000 tx.vout.append(CTxOut(amount - Decimal("5000"), scriptPubKey)) tx.vin.append(CTxIn(COutPoint(lx(txid), 0))) # Sign the new output with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, int(amount), SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub])))
def add_input_output(bitcoind, tx, coin, fees): """Add another input to the transaction to bump the feerate.""" coin_amount = Decimal(coin["amount"]) * Decimal(COIN) # First get the private key from bitcoind's wallet. privkey = CKey(wif_decode(bitcoind.dumpprivkey(coin["address"]))) # Add the fetched coin as a new input. tx.vin.append(CTxIn(COutPoint(lx(coin["txid"]), coin["vout"]))) # And likely add an output, otherwise all goes to the fees. scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) if coin_amount > fees + 294: # For simplicity, pay to the same script tx.vout.append(CTxOut(coin_amount - Decimal(fees), scriptPubKey)) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) # We only do this once, sign it with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, int(coin_amount), SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append( CTxInWitness(CScriptWitness([sig, privkey.pub])) ) return tx
def add_input(bitcoind, tx, fees): """Add another input to the transaction to bump the feerate.""" # Don't be dust! if fees < 294: fees = 294 # Create the first stage transaction new_prevout_addr = P2WPKHBitcoinAddress(bitcoind.getnewaddress()) txid = bitcoind.sendtoaddress(str(new_prevout_addr), fees / COIN) out_index = get_output_index(bitcoind.getrawtransaction(txid, decode=True), fees) # Then gather the private key to unlock its output privkey = CKey(wif_decode(bitcoind.dumpprivkey(str(new_prevout_addr)))) # Add the fetched coin as a new input. tx.vin.append(CTxIn(COutPoint(lx(txid), out_index))) # We only do this once, sign it with ALL tx_hash = SignatureHash(new_prevout_addr.to_redeemScript(), tx, 1, SIGHASH_ALL, fees, SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub]))) return tx
def test_unvault_txout(bitcoind): """Test that unvault_txout() produces a valid and conform txo. Note that we use python-bitcoinlib for this one, as signrawtransactionwithkey is (apparently?) not happy dealing with exotic scripts. Note also that bitcoinlib's API uses sats, while bitcoind's one uses BTC.. """ amount = 50 * COIN - 500 # The stakeholders stk_privkeys = [CKey(os.urandom(32)) for i in range(4)] stk_pubkeys = [k.pub for k in stk_privkeys] # The cosigning server serv_privkey = CKey(os.urandom(32)) # First, pay to the unvault tx script txo = unvault_txout(stk_pubkeys, serv_privkey.pub, amount) txo_addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) amount_for_bitcoind = float(Decimal(amount) / Decimal(COIN)) txid = bitcoind.pay_to(txo_addr, amount_for_bitcoind) # We can spend it immediately if all stakeholders sign (emergency or cancel # tx) txin = CTxIn(COutPoint(lx(txid), 0)) amount_min_fees = amount - 500 addr = bitcoind.getnewaddress() new_txo = CTxOut(amount_min_fees, CBitcoinAddress(addr).to_scriptPubKey()) tx = CMutableTransaction([txin], [new_txo], nVersion=2) # We can't test the signing against bitcoind, but we can at least test the # transaction format bitcoind_tx = bitcoind.rpc.createrawtransaction([ {"txid": txid, "vout": 0} ], [ {addr: float(Decimal(amount_min_fees) / Decimal(COIN))} ]) assert b2x(tx.serialize()) == bitcoind_tx tx_hash = SignatureHash(unvault_script(*stk_pubkeys, serv_privkey.pub), tx, 0, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) sigs = [key.sign(tx_hash) + bytes([SIGHASH_ALL]) for key in stk_privkeys[::-1]] # Note the reverse here witness_script = [*sigs, unvault_script(*stk_pubkeys, serv_privkey.pub)] witness = CTxInWitness(CScriptWitness(witness_script)) tx.wit = CTxWitness([witness]) bitcoind.send_tx(b2x(tx.serialize())) assert bitcoind.has_utxo(addr) # If two out of three stakeholders sign, we need the signature from the # cosicosigning server and we can't spend it before 6 blocks (csv). # Pay back to the unvault tx script txo = unvault_txout(stk_pubkeys, serv_privkey.pub, amount) txo_addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) txid = bitcoind.pay_to(txo_addr, amount_for_bitcoind) # Reconstruct the transaction but with only two stakeholders signatures txin = CTxIn(COutPoint(lx(txid), 0), nSequence=6) amount_min_fees = amount - 500 addr = bitcoind.getnewaddress() new_txo = CTxOut(amount_min_fees, CBitcoinAddress(addr).to_scriptPubKey()) tx = CMutableTransaction([txin], [new_txo], nVersion=2) # We can't test the signing against bitcoind, but we can at least test the # transaction format bitcoind_tx = bitcoind.rpc.createrawtransaction([ {"txid": txid, "vout": 0, "sequence": 6} ], [ {addr: float(Decimal(amount_min_fees) / Decimal(COIN))} ]) assert b2x(tx.serialize()) == bitcoind_tx tx_hash = SignatureHash(unvault_script(*stk_pubkeys, serv_privkey.pub), tx, 0, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) # The cosigning server sigs = [serv_privkey.sign(tx_hash) + bytes([SIGHASH_ALL])] # We fail the third CHECKSIG !! sigs += [empty_signature()] sigs += [key.sign(tx_hash) + bytes([SIGHASH_ALL]) for key in stk_privkeys[::-1][2:]] # Just the first two witness_script = [*sigs, unvault_script(*stk_pubkeys, serv_privkey.pub)] witness = CTxInWitness(CScriptWitness(witness_script)) tx.wit = CTxWitness([witness]) # Relative locktime ! for i in range(5): with pytest.raises(VerifyRejectedError, match="non-BIP68-final"): bitcoind.send_tx(b2x(tx.serialize())) bitcoind.generate_block(1) # It's been 6 blocks now bitcoind.send_tx(b2x(tx.serialize())) assert bitcoind.has_utxo(addr)