def sign_our_inputs(self) -> None: assert self.tx is not None for idx, _in in enumerate(self.inputs): privkey = _in['privkey'] if privkey and 'sig' not in _in: print('signing our input for tx', self.tx.serialize().hex()) inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # Really horrid hack to produce a signature for the # multisig utxo in tests/helpers.py if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': redeemscript = bytes.fromhex('51210253cdf835e328346a4f19de099cf3d42d4a7041e073cd4057a1c4fd7cdbb1228f2103ae903722f21f85e651b8f9b18fc854084fb90eeb76452bdcfd0cb43a16a382a221036c264d68a9727afdc75949f7d7fa71910ae9ae8001a1fbffa6f7ce000976597c21036429fa8a4ef0b2b1d5cb553e34eeb90a32ab19fae1f0024f332ab4f74283a7282103d4232f19ea85051e7b76bf5f01d03e17eea8751463dee36d71413a739de1a92755ae') else: address = P2WPKHBitcoinAddress.from_scriptPubKey(CScript([script.OP_0, Hash160(inkey_pub.format())])) redeemscript = address.to_redeemScript() sighash = script.SignatureHash(redeemscript, self.tx, idx, script.SIGHASH_ALL, amount=_in['sats'], sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': _in['sig'] = CTxInWitness(CScriptWitness([bytes([]), sig, redeemscript])) else: _in['sig'] = CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))
def add_witnesses(self, witness_stack) -> str: wits = [] for idx, _in in enumerate(self.inputs): privkey = _in['privkey'] serial_id = _in['serial_id'] if privkey: inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) address = P2WPKHBitcoinAddress.from_scriptPubKey(CScript([script.OP_0, Hash160(inkey_pub.format())])) sighash = script.SignatureHash(address.to_redeemScript(), self.tx, idx, script.SIGHASH_ALL, amount=_in['sats'], sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) wits.append(CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))) continue # Every input from the witness stack will be the accepter's # which is always an odd serial assert(serial_id % 2 == 1) elems = witness_stack.pop(0)['witness_element'] stack = [] for elem in elems: stack.append(bytes.fromhex(elem['witness'])) wits.append(CTxInWitness(CScriptWitness(stack))) self.tx.wit = CTxWitness(wits) return self.tx.serialize().hex()
def close_tx(self, fee: int, privkey_dest: str) -> str: """Create a (mutual) close tx""" txin = CTxIn(COutPoint(bytes.fromhex(self.txid), self.output_index)) out_privkey = privkey_expand(privkey_dest) txout = CTxOut(self.amount - fee, CScript([script.OP_0, Hash160(coincurve.PublicKey.from_secret(out_privkey.secret).format())])) tx = CMutableTransaction(vin=[txin], vout=[txout]) sighash = script.SignatureHash(self.redeemscript(), tx, inIdx=0, hashtype=script.SIGHASH_ALL, amount=self.amount, sigversion=script.SIGVERSION_WITNESS_V0) sigs = [key.sign(sighash, hasher=None) for key in self.funding_privkeys_for_tx()] # BOLT #3: # ## Closing Transaction # ... # * `txin[0]` witness: `0 <signature_for_pubkey1> <signature_for_pubkey2>` witness = CScriptWitness([bytes(), sigs[0] + bytes([script.SIGHASH_ALL]), sigs[1] + bytes([script.SIGHASH_ALL]), self.redeemscript()]) tx.wit = CTxWitness([CTxInWitness(witness)]) return tx.serialize().hex()
def from_utxo(txid_in: str, tx_index_in: int, sats: int, privkey: str, fee: int, local_node_privkey: str, local_funding_privkey: str, remote_node_privkey: str, remote_funding_privkey: str, chain_hash: str = regtest_hash) -> Tuple['Funding', str]: """Make a funding transaction by spending this utxo using privkey: return Funding, tx.""" # Create dummy one to start: we will fill in txid at the end. funding = Funding('', 0, sats - fee, local_node_privkey, local_funding_privkey, remote_node_privkey, remote_funding_privkey, chain_hash) # input private key. inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # use RBF'able input (requirement for dual-funded things) txin = CTxIn(COutPoint(bytes.fromhex(txid_in), tx_index_in), nSequence=0xFFFFFFFD) txout = CTxOut( sats - fee, CScript([script.OP_0, sha256(funding.redeemscript()).digest()])) tx = CMutableTransaction([txin], [txout], nVersion=2, nLockTime=funding.locktime) # now fill in funding txid. funding.txid = tx.GetTxid().hex() funding.tx = tx # while we're here, sign the transaction. address = P2WPKHBitcoinAddress.from_scriptPubKey( CScript([script.OP_0, Hash160(inkey_pub.format())])) sighash = script.SignatureHash(address.to_redeemScript(), tx, 0, script.SIGHASH_ALL, amount=sats, sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) tx.wit = CTxWitness( [CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))]) return funding, tx.serialize().hex()
def add_witnesses(self, witness_stack: List[Dict[str, Any]]) -> str: assert self.tx is not None wits = [] for idx, _in in enumerate(self.inputs): if 'sig' in _in: wits.append(_in['sig']) continue if not len(witness_stack): continue elems = witness_stack.pop(0)['witness_element'] stack = [] for elem in elems: stack.append(bytes.fromhex(elem['witness'])) wits.append(CTxInWitness(CScriptWitness(stack))) self.tx.wit = CTxWitness(wits) return self.tx.serialize().hex()
def signed_tx(self, unsigned_tx: CMutableTransaction) -> CMutableTransaction: # BOLT #3: # * `txin[0]` witness: `0 <signature_for_pubkey1> <signature_for_pubkey2>` tx = unsigned_tx.copy() sighash = script.SignatureHash(self.funding.redeemscript(), tx, inIdx=0, hashtype=script.SIGHASH_ALL, amount=self.funding.amount, sigversion=script.SIGVERSION_WITNESS_V0) sigs = [ key.sign(sighash, hasher=None) for key in self.funding.funding_privkeys_for_tx() ] tx.wit = CTxWitness([ CScriptWitness([ bytes(), sigs[0] + bytes([script.SIGHASH_ALL]), sigs[1] + bytes([script.SIGHASH_ALL]), self.funding.redeemscript() ]) ]) return tx
def sign_planned_transaction(planned_transaction, parameters=None): """ Sign a planned transaction by parameterizing each of the witnesses based on the script templates from their predecesor coins. """ for planned_input in planned_transaction.inputs: logger.info("parent transaction name: {}".format( planned_input.utxo.transaction.name)) # Sanity test: all parent transactions should already be finalized assert planned_input.utxo.transaction.is_finalized == True planned_utxo = planned_input.utxo witness_template_selection = planned_input.witness_template_selection # sanity check if witness_template_selection not in planned_utxo.script_template.witness_templates.keys( ): raise VaultException( "UTXO {} is missing witness template \"{}\"".format( planned_utxo.internal_id, witness_template_selection)) witness_template = planned_utxo.script_template.witness_templates[ witness_template_selection] # Would use transaction.bitcoin_transaction.get_txid() but for the # very first utxo, the txid is going to be mocked for testing # purposes. So it's better to just use the txid property... txid = planned_utxo.transaction.txid vout = planned_utxo.vout relative_timelock = planned_input.relative_timelock if relative_timelock != None: # Note that it's not enough to just have the relative timelock # in the script; you also have to set it on the CTxIn object. planned_input.bitcoin_input = CTxIn(COutPoint(txid, vout), nSequence=relative_timelock) else: planned_input.bitcoin_input = CTxIn(COutPoint(txid, vout)) # TODO: is_finalized is misnamed here.. since the signature isn't # there yet. planned_input.is_finalized = True # Can't sign the input yet because the other inputs aren't finalized. # sanity check finalized = planned_transaction.check_inputs_outputs_are_finalized() assert finalized == True bitcoin_inputs = [ planned_input.bitcoin_input for planned_input in planned_transaction.inputs ] bitcoin_outputs = [ planned_output.bitcoin_output for planned_output in planned_transaction.output_utxos ] witnesses = [] planned_transaction.bitcoin_inputs = bitcoin_inputs planned_transaction.bitcoin_outputs = bitcoin_outputs # Must be a mutable transaction because the witnesses are added later. planned_transaction.bitcoin_transaction = CMutableTransaction( bitcoin_inputs, bitcoin_outputs, nLockTime=0, nVersion=2, witness=None) # python-bitcoin-utils had a bug where the witnesses weren't # initialized blank. #planned_transaction.bitcoin_transaction.witnesses = [] if len( bitcoin_inputs ) == 0 and planned_transaction.name != "initial transaction (from user)": raise VaultException("Can't have a transaction with zero inputs") # Now that the inputs are finalized, it should be possible to sign each # input on this transaction and add to the list of witnesses. witnesses = [] for planned_input in planned_transaction.inputs: # sign! # Make a signature. Use some code defined in the PlannedInput model. witness = parameterize_witness_template_by_signing( planned_input, parameters) witnesses.append(witness) # Now take the list of CScript objects and do the needful. ctxinwitnesses = [ CTxInWitness(CScriptWitness(list(witness))) for witness in witnesses ] witness = CTxWitness(ctxinwitnesses) planned_transaction.bitcoin_transaction.wit = witness planned_transaction.is_finalized = True if planned_transaction.name == "initial transaction (from user)": # serialization function fails, so just skip return serialized_transaction = planned_transaction.serialize() logger.info("tx len: {}".format(len(serialized_transaction))) logger.info("txid: {}".format( b2lx(planned_transaction.bitcoin_transaction.GetTxid()))) logger.info("Serialized transaction: {}".format( b2x(serialized_transaction)))
# Calculate the signature hash for the transaction. This is then signed by the # private key that controls the UTXO being spent here at this txin_index. sighash = SignatureHash(redeem_script, tx, txin_index, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) signature = seckey.sign(sighash) + bytes([SIGHASH_ALL]) # Construct a witness for this transaction input. The public key is given in # the witness so that the appropriate redeem_script can be calculated by # anyone. The original scriptPubKey had only the Hash160 hash of the public # key, not the public key itself, and the redeem script can be entirely # re-constructed (from implicit template) if given just the public key. So the # public key is added to the witness. This is P2WPKH in bip141. witness = [signature, public_key] # Aggregate all of the witnesses together, and then assign them to the # transaction object. ctxinwitnesses = [CTxInWitness(CScriptWitness(witness))] tx.wit = CTxWitness(ctxinwitnesses) # Broadcast the transaction to the regtest network. spend_txid = connection.sendrawtransaction(tx) # Done! Print the transaction to standard output. Show the transaction # serialization in hex (instead of bytes), and render the txid. print("serialized transaction: {}".format(b2x(tx.serialize()))) print("txid: {}".format(b2lx(spend_txid)))
def bake_ctv_transaction(some_transaction, skip_inputs=False, parameters=None): """ Create a OP_CHECKTEMPLATEVERIFY version transaction for the planned transaction tree. This version uses a hash-based covenant opcode instead of using pre-signed transactions with trusted key deletion. This function does two passes over the planned transaction tree, consisting of (1) crawling the whole tree and generating standard template hashes (starting with the deepest elements in the tree and working backwards towards the root of the tree), and then (2) crawling the whole tree and assigning txids to the inputs. This is possible because OP_CHECKTEMPLATEVERIFY does not include the hash of the inputs in the standard template hash, otherwise there would be a recursive hash commitment dependency loop error. See the docstring for bake_ctv_output too. """ if hasattr(some_transaction, "ctv_baked") and some_transaction.ctv_baked == True: return some_transaction.ctv_bitcoin_transaction # Bake each UTXO. Recurse down the tree and compute StandardTemplateHash # values (to be placed in scriptpubkeys) for OP_CHECKTEMPLATEVERIFY. These # standard template hashes can only be computed once the descendant tree is # computed, so it must be done recursively. for utxo in some_transaction.output_utxos: bake_ctv_output(utxo, parameters=parameters) # Construct python-bitcoinlib bitcoin transactions and attach them to the # PlannedTransaction objects, once all the UTXOs are ready. logger.info("Baking a transaction with name {}".format( some_transaction.name)) bitcoin_inputs = [] if not skip_inputs: for some_input in some_transaction.inputs: # When computing the standard template hash for a child transaction, # the child transaction needs to be only "partially" baked. It doesn't # need to have the inputs yet. if some_input.utxo.transaction.__class__ == InitialTransaction or some_input.transaction.name == "Burn some UTXO": txid = some_input.utxo.transaction.txid else: logger.info("The parent transaction name is: {}".format( some_input.utxo.transaction.name)) logger.info("Name of the UTXO being spent: {}".format( some_input.utxo.name)) logger.info("Current transaction name: {}".format( some_input.transaction.name)) # This shouldn't happen... We should be able to bake transactions # in a certain order and be done with this. #if not hasattr(some_input.utxo.transaction, "ctv_bitcoin_transaction"): # bake_ctv_transaction(some_input.utxo.transaction, parameters=parameters) # TODO: this creates an infinite loop.... txid = some_input.utxo.transaction.ctv_bitcoin_transaction.GetTxid( ) vout = some_input.utxo.transaction.output_utxos.index( some_input.utxo) relative_timelock = None if some_input.utxo.script_template.__class__ in [ ColdStorageScriptTemplate, ShardScriptTemplate ]: # TODO: This should be controlled by the template or whole # program parameters. relative_timelock = 144 if relative_timelock: bitcoin_input = CTxIn(COutPoint(txid, vout), nSequence=relative_timelock) else: bitcoin_input = CTxIn(COutPoint(txid, vout)) bitcoin_inputs.append(bitcoin_input) bitcoin_outputs = [] for some_output in some_transaction.output_utxos: amount = some_output.amount # For certain UTXOs, just use the previous UTXO script templates, # instead of the CTV version. (utxo.ctv_bypass == True) if hasattr(some_output, "ctv_bypass"): scriptpubkey = some_output.scriptpubkey else: scriptpubkey = some_output.ctv_scriptpubkey bitcoin_output = CTxOut(amount, scriptpubkey) bitcoin_outputs.append(bitcoin_output) bitcoin_transaction = CMutableTransaction(bitcoin_inputs, bitcoin_outputs, nLockTime=0, nVersion=2, witness=None) if not skip_inputs: witnesses = [] for some_input in some_transaction.inputs: logger.info("Transaction name: {}".format(some_transaction.name)) logger.info("Spending UTXO with name: {}".format( some_input.utxo.name)) logger.info("Parent transaction name: {}".format( some_input.utxo.transaction.name)) if some_transaction.name in [ "Burn some UTXO", "Funding commitment transaction" ]: witness = some_input.witness else: witness = some_input.ctv_witness #logger.info("Appending witness: {}".format(list(witness))) witnesses.append(witness) ctxinwitnesses = [ CTxInWitness(CScriptWitness(list(witness))) for witness in witnesses ] witness = CTxWitness(ctxinwitnesses) bitcoin_transaction.wit = witness else: bitcoin_transaction.wit = CTxWitness() some_transaction.ctv_bitcoin_transaction = bitcoin_transaction if not skip_inputs: some_transaction.ctv_baked = True return bitcoin_transaction