Example #1
0
    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()
Example #2
0
    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()]))
Example #3
0
    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()
Example #4
0
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
Example #5
0
def test_emergency_txout(bitcoind):
    """Test mostly the emergency tx locktime"""
    amount = Decimal("50") - Decimal("500") / Decimal(COIN)
    privkeys = [CKey(os.urandom(32)) for _ in range(4)]
    pubkeys = [k.pub for k in privkeys]
    txo = emergency_txout(pubkeys, COIN * amount)
    addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey))
    # This makes a transaction with only one vout
    txid = bitcoind.pay_to(addr, amount)
    new_amount = amount - Decimal("500") / Decimal(COIN)
    addr = bitcoind.getnewaddress()
    txin = CTxIn(COutPoint(lx(txid), 0), nSequence=4464)
    txout = CTxOut(new_amount * COIN, CBitcoinAddress(addr).to_scriptPubKey())
    tx = CMutableTransaction([txin], [txout], nVersion=2)
    tx_hash = SignatureHash(emergency_script(pubkeys), tx, 0, SIGHASH_ALL,
                            int(amount * COIN), SIGVERSION_WITNESS_V0)
    sigs = [k.sign(tx_hash) + bytes([SIGHASH_ALL]) for k in privkeys]
    witness_script = [bytes(0), *sigs, emergency_script(pubkeys)]
    tx.wit = CTxWitness([CTxInWitness(CScriptWitness(witness_script))])
    # 1 month of locktime
    bitcoind.generate_block(4464 - 2)
    with pytest.raises(VerifyRejectedError, match="non-BIP68-final"):
        bitcoind.send_tx(tx.serialize().hex())
    bitcoind.generate_block(1)
    bitcoind.send_tx(tx.serialize().hex())
    assert bitcoind.has_utxo(addr)
Example #6
0
    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()
Example #7
0
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])))
Example #8
0
def form_unvault_spend(tx, sigs, pubkeys, pub_server):
    """Forms the transaction spending from an unvault using four signatures.

    :param tx: The unsigned transaction, a CMutableTransaction.
    :param sigs: The list of the four signatures in the same order as the
                 following pubkeys.
    :param pubkeys: The pubkeys of the stakeholders, to form the script.
    :param pub_server: The pubkey of the cosigning server, to form the script.

    :return: The immutable signed transaction, a CTransaction.
    """
    # Note that we use 4 sigs, but no CHECKMULTISIG, so no empty byte array at
    # the begining of this one!
    witness_script = [*sigs[::-1], unvault_script(*pubkeys, pub_server)]
    witness = CTxInWitness(CScriptWitness(witness_script))
    tx.wit = CTxWitness([witness])
    # Make it immutable
    return CTransaction.from_tx(tx)
Example #9
0
def form_spend_vault_txout(tx, pubkeys, sigs):
    """Forms the final transaction spending a vault txout.

    :param tx: The unsigned transaction, as a CMutableTransaction.
    :param pubkeys: A list containing the pubkey of each of the four
                    stakeholders.
    :param sigs: A list of the four signatures for the four pubkeys, in the
                 same order.

    :return: The immutable CTransaction.
    """
    # Spending a P2WSH, so the witness is <unlocking_script> <actual_script>.
    # Here, unlocking_script is the four signatures. Moreover note the empty
    # byte array for the CHECKMULTISIG bug.
    witness_script = [bytes(0), *sigs, vault_script(pubkeys)]
    witness = CTxInWitness(CScriptWitness(witness_script))
    tx.wit = CTxWitness([witness])
    # Make it immutable
    return CTransaction.from_tx(tx)
Example #10
0
    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()
Example #11
0
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
Example #12
0
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
Example #13
0
def form_spend_tx(tx, pubkeys, serv_pubkey, sigs):
    """Forms the tx spending the unvault_tx after the relative locktime,
    from three signatures.

    :param tx: The unsigned transaction, a CMutableTransaction.
    :param pubkeys: An *ordered* list of the four pubkeys of the stakeholders.
    :param serv_pubkey: The cosigning server pubkey.
    :param sigs: An *ordered* list of *four* bytearrays. Any of the first three
                 can be empty (2of3). The first one is the first trader's
                 signature, the second one the second trader signature, the
                 third one the signature of the stakeholder's pubkey used in
                 the unvaulting script, and the last one the cosigning server's
                 signature.

    :return: The spending transaction, a CTransaction.
    """
    # The sigs are reversed as we request them to be in the same order as the
    # pubkeys to keep the API simple.
    witness_script = [*sigs[::-1], unvault_script(*pubkeys, serv_pubkey)]
    witness = CTxInWitness(CScriptWitness(witness_script))
    tx.wit = CTxWitness([witness])
    # Make it immutable
    return CTransaction.from_tx(tx)
Example #14
0
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
Example #15
0
# 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)))
Example #16
0
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)
Example #17
0
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)))
Example #18
0
# Specify a destination address and create the txout.
destination_address = CBitcoinAddress(
    'tb1q5rn69avl3ganw3cmhz5ldcxpash2kusq7sncfl').to_scriptPubKey()
txout = CMutableTxOut(amount_less_fee, destination_address)

# Create the unsigned transaction.
# Set the nLockTime so that OP_HODL will work
tx = CMutableTransaction([txin], [txout])
tx.nLockTime = nLockTime

# Calculate the signature hash for that transaction. Note how the script we use
# is the witnessScript, not the redeemScript.
sighash = SignatureHash(script=txin_witnessScript,
                        txTo=tx,
                        inIdx=0,
                        hashtype=SIGHASH_ALL,
                        amount=amount,
                        sigversion=SIGVERSION_WITNESS_V0)

# Now sign it. We have to append the type of signature we want to the end, in
# this case the usual SIGHASH_ALL.
sig = seckey.sign(sighash) + bytes([SIGHASH_ALL])

# Construct a witness for this P2WSH transaction and add to tx.
witness = CScriptWitness([sig, txin_witnessScript])
tx.wit = CTxWitness([CTxInWitness(witness)])

# Done! Print the transaction to standard output with the bytes-to-hex
# function.
print(b2x(tx.serialize()))