Exemplo n.º 1
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()
Exemplo n.º 2
0
# is the redeemScript, not the scriptPubKey. That's because when the CHECKSIG
# operation happens EvalScript() will be evaluating the redeemScript, so the
# corresponding SignatureHash() function will use that same script when it
# replaces the scriptSig in the transaction being hashed with the script being
# executed.
sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL)
print("hash:", b2x(sighash))
print(b2x(bytes([SIGHASH_ALL])))

# 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])

print("sig:", b2x(sig))
# Set the scriptSig of our transaction input appropriately.
txin.scriptSig = CScript([sig, txin_redeemScript])

# Verify the signature worked. This calls EvalScript() and actually executes
# the opcodes in the scripts to see if everything worked out. If it doesn't an
# exception will be raised.
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH, ))

# Done! Print the transaction to standard output with the bytes-to-hex
# function.
txn = tx.serialize()
print("raw size:", len(txn))
print("txid:", b2lx(tx.GetTxid()))
txn = b2x(txn)
# txn = get_actual_txn()
print(txn)
Exemplo n.º 3
0
    def htlc_tx(self, commit_tx: CMutableTransaction, outnum: int, side: Side,
                amount_sat: int, locktime: int) -> CMutableTransaction:
        # BOLT #3:
        # ## HTLC-Timeout and HTLC-Success Transactions
        #
        # These HTLC transactions are almost identical, except the
        # HTLC-timeout transaction is timelocked. Both
        # HTLC-timeout/HTLC-success transactions can be spent by a valid
        # penalty transaction.

        # BOLT #3:
        # ## HTLC-Timeout and HTLC-Success Transactions
        # ...
        # * txin count: 1
        # * `txin[0]` outpoint: `txid` of the commitment transaction and
        #    `output_index` of the matching HTLC output for the HTLC transaction
        # * `txin[0]` sequence: `0`
        # * `txin[0]` script bytes: `0`
        txin = CTxIn(COutPoint(commit_tx.GetTxid(), outnum), nSequence=0x0)

        # BOLT #3:
        # ## HTLC-Timeout and HTLC-Success Transactions
        # ...
        # * txout count: 1
        # * `txout[0]` amount: the HTLC amount minus fees (see [Fee
        #    Calculation](#fee-calculation))
        # * `txout[0]` script: version-0 P2WSH with witness script as shown below
        #
        # The witness script for the output is:
        # OP_IF
        #     # Penalty transaction
        #     <revocationpubkey>
        # OP_ELSE
        #     `to_self_delay`
        #     OP_CHECKSEQUENCEVERIFY
        #     OP_DROP
        #     <local_delayedpubkey>
        # OP_ENDIF
        # OP_CHECKSIG
        redeemscript = script.CScript([
            script.OP_IF,
            self.revocation_pubkey(side).format(), script.OP_ELSE,
            self.self_delay[side], script.OP_CHECKSEQUENCEVERIFY,
            script.OP_DROP,
            self.delayed_pubkey(side).format(), script.OP_ENDIF,
            script.OP_CHECKSIG
        ])
        print("htlc redeemscript = {}".format(redeemscript.hex()))
        txout = CTxOut(amount_sat,
                       CScript([script.OP_0,
                                sha256(redeemscript).digest()]))

        # BOLT #3:
        # ## HTLC-Timeout and HTLC-Success Transactions
        # ...
        # * version: 2
        # * locktime: `0` for HTLC-success, `cltv_expiry` for HTLC-timeout
        return CMutableTransaction(vin=[txin],
                                   vout=[txout],
                                   nVersion=2,
                                   nLockTime=locktime)
Exemplo n.º 4
0
import hashlib
from bitcoin.core import b2x, lx, COIN, CMutableTxOut, CMutableTxIn, CMutableTransaction, CBlock
from bitcoin.wallet import P2PKHBitcoinAddress

receiver_pkh = bytes.fromhex('346753e81b93e3f1567a16f3009c7c65c768d865')
print('receiver_pkh: "%s"' % receiver_pkh.hex())
receiver = P2PKHBitcoinAddress.from_bytes(receiver_pkh)
tx = CMutableTransaction()

tx.vin.append(CMutableTxIn())
tx.vout.append(CMutableTxOut(10, receiver.to_scriptPubKey()))
print('tx: "%s"' % tx.serialize().hex())

print('txid: "%s"' % tx.GetTxid().hex())

blk = CBlock(vtx=[tx])
blk_mtr = blk.calc_merkle_root()
print('block_mtr: "%s"' % blk_mtr.hex())

mmr = hashlib.sha256(b'\x00' + blk_mtr).digest()
print('mmr: "%s"' % mmr.hex())
Exemplo n.º 5
0
class Funding(object):
    def __init__(self,
                 funding_txid: str,
                 funding_output_index: int,
                 funding_amount: int,
                 local_node_privkey: str,
                 local_funding_privkey: str,
                 remote_node_privkey: str,
                 remote_funding_privkey: str,
                 chain_hash: str = regtest_hash,
                 locktime: int = 0):
        self.chain_hash = chain_hash
        self.txid = funding_txid
        self.output_index = funding_output_index
        self.amount = funding_amount
        self.bitcoin_privkeys = [privkey_expand(local_funding_privkey),
                                 privkey_expand(remote_funding_privkey)]
        self.node_privkeys = [privkey_expand(local_node_privkey),
                              privkey_expand(remote_node_privkey)]
        self.tx = None
        self.locktime = locktime
        self.outputs: List[Dict[str, Any]] = []
        self.inputs: List[Dict[str, Any]] = []

    def tx_hex(self) -> str:
        if not self.tx:
            return ''
        return self.tx.serialize().hex()

    @staticmethod
    def sort_by_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey,
                     val_one: Any, val_two: Any) -> Tuple[Any, Any]:
        """In many places we have to sort elements into key or nodeid order"""
        # BOLT #3:
        # ## Funding Transaction Output
        #
        # * The funding output script is a P2WSH to: `2 <pubkey1> <pubkey2> 2
        #  OP_CHECKMULTISIG`
        # * Where `pubkey1` is the lexicographically lesser of the two
        #   `funding_pubkey` in compressed format, and where `pubkey2` is the
        #   lexicographically greater of the two.
        if key_one.format() < key_two.format():
            return val_one, val_two
        else:
            return val_two, val_one

    def node_id_sort(self, local: Any, remote: Any) -> Tuple[Any, Any]:
        """Sorts these two items into lexicographical node id order"""
        # BOLT #7:
        # - MUST set `node_id_1` and `node_id_2` to the public keys of the two
        #   nodes operating the channel, such that `node_id_1` is the
        #   lexicographically-lesser of the two compressed keys sorted in
        #   ascending lexicographic order.
        return self.sort_by_keys(self.node_id(Side.local),
                                 self.node_id(Side.remote),
                                 local, remote)

    @staticmethod
    def redeemscript_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey) -> CScript:
        return CScript([script.OP_2]
                       + [k.format() for k in Funding.sort_by_keys(key_one, key_two, key_one, key_two)]
                       + [script.OP_2,
                          script.OP_CHECKMULTISIG])

    def redeemscript(self) -> CScript:
        key_a, key_b = self.funding_pubkeys_for_tx()
        return self.redeemscript_keys(key_a, key_b)

    @staticmethod
    def locking_script_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey) -> CScript:
        return CScript([script.OP_0, sha256(Funding.redeemscript_keys(key_one, key_two)).digest()])

    def locking_script(self) -> CScript:
        a, b = self.funding_pubkeys_for_tx()
        return self.locking_script_keys(a, b)

    @staticmethod
    def start(local_node_privkey: str,
              local_funding_privkey: str,
              remote_node_privkey: str,
              remote_funding_privkey: str,
              funding_sats: int,
              locktime: int,
              chain_hash: str = regtest_hash) -> 'Funding':

        # Create dummy one to start: we will fill in txid at the end
        return Funding('', 0, funding_sats,
                       local_node_privkey,
                       local_funding_privkey,
                       remote_node_privkey,
                       remote_funding_privkey,
                       chain_hash, locktime)

    def add_input(self,
                  serial_id: int,
                  prevtx: str,
                  prevtx_vout: int,
                  script_sig: str,
                  sequence: int,
                  privkey: str = None) -> None:
        # the dummy runner sends empty info, skip
        if len(prevtx) == 0:
            return

        # Find the txid of the transaction
        prev_tx = CTransaction.deserialize(bytes.fromhex(prevtx))
        txin = CTxIn(COutPoint(prev_tx.GetTxid(), prevtx_vout),
                     nSequence=sequence)

        # Get the previous output for its outscript + value
        prev_vout = prev_tx.vout[prevtx_vout]

        self.inputs.append({'input': txin,
                            'serial_id': serial_id,
                            'sats': prev_vout.nValue,
                            'prev_outscript': prev_vout.scriptPubKey.hex(),
                            'redeemscript': script_sig,
                            'privkey': privkey,
                            })

    def add_output(self,
                   serial_id: int,
                   script: str,
                   sats: int) -> None:
        txout = CTxOut(sats, CScript(bytes.fromhex(script)))
        self.outputs.append({'output': txout,
                             'serial_id': serial_id})

    def our_witnesses(self) -> str:
        """ Extract expected witness data for our node """
        witnesses = []
        # these were sorted in `build_tx`
        for _in in self.inputs:
            if not _in['privkey']:
                continue

            wit = _in['sig']
            print('witness is ... ', wit)
            elems = []
            for e in wit.scriptWitness.stack:
                elems.append('{{witness={0}}}'.format(e.hex()))
            witnesses.append('{{witness_element=[{0}]}}'.format(','.join(elems)))
        val = '[{}]'.format(','.join(witnesses))
        print('witnesses are', val)
        return val

    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: 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 build_tx(self) -> str:
        # Sort inputs/outputs by serial number
        self.inputs = sorted(self.inputs, key=lambda k: k['serial_id'])
        self.outputs = sorted(self.outputs, key=lambda k: k['serial_id'])

        self.tx = CMutableTransaction([i['input'] for i in self.inputs],
                                      [o['output'] for o in self.outputs],
                                      nVersion=2, nLockTime=self.locktime)
        assert self.tx is not None
        self.txid = self.tx.GetTxid().hex()

        # Set the output index for the funding output
        locking_script = self.locking_script()
        for i, out in enumerate([o['output'] for o in self.outputs]):
            if out.scriptPubKey == locking_script:
                self.output_index = i
                self.amount = out.nValue

        return self.tx.serialize().hex()

    @staticmethod
    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 channel_id(self) -> str:
        # BOLT #2: This message introduces the `channel_id` to identify the
        # channel. It's derived from the funding transaction by combining the
        # `funding_txid` and the `funding_output_index`, using big-endian
        # exclusive-OR (i.e. `funding_output_index` alters the last 2 bytes).
        chanid = bytearray.fromhex(self.txid)
        chanid[-1] ^= self.output_index % 256
        chanid[-2] ^= self.output_index // 256
        return chanid.hex()

    @staticmethod
    def funding_pubkey_key(privkey: coincurve.PrivateKey) -> coincurve.PublicKey:
        return coincurve.PublicKey.from_secret(privkey.secret)

    def funding_pubkey(self, side: Side) -> coincurve.PublicKey:
        return self.funding_pubkey_key(self.bitcoin_privkeys[side])

    def funding_pubkeys_for_tx(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding pubkeys, in tx order"""
        # BOLT #3:
        # ## Funding Transaction Output
        #
        # * The funding output script is a P2WSH to: `2 <pubkey1> <pubkey2> 2
        #  OP_CHECKMULTISIG`
        # * Where `pubkey1` is the lexicographically lesser of the two
        #   `funding_pubkey` in compressed format, and where `pubkey2` is the
        #   lexicographically greater of the two.
        return self.sort_by_keys(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote),
                                 self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote))

    def funding_privkeys_for_tx(self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]:
        """Returns funding private keys, in tx order"""
        return self.sort_by_keys(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote),
                                 self.bitcoin_privkeys[Side.local],
                                 self.bitcoin_privkeys[Side.remote])

    def node_id(self, side: Side) -> coincurve.PublicKey:
        return coincurve.PublicKey.from_secret(self.node_privkeys[side].secret)

    def node_ids(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns node pubkeys, in order"""
        return self.node_id_sort(self.node_id(Side.local),
                                 self.node_id(Side.remote))

    def node_id_privkeys(self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]:
        """Returns node private keys, in order"""
        return self.node_id_sort(self.node_privkeys[Side.local],
                                 self.node_privkeys[Side.remote])

    def funding_pubkeys_for_gossip(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding public keys, in gossip order"""
        return self.node_id_sort(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote))

    def funding_privkeys_for_gossip(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding private keys, in gossip order"""
        return self.node_id_sort(self.bitcoin_privkeys[Side.local],
                                 self.bitcoin_privkeys[Side.remote])

    def _unsigned_channel_announcment(self,
                                      features: str,
                                      short_channel_id: str) -> Message:
        """Produce a channel_announcement message with dummy sigs"""
        node_ids = self.node_ids()
        bitcoin_keys = self.funding_pubkeys_for_gossip()
        return Message(namespace().get_msgtype('channel_announcement'),
                       node_signature_1=Sig(bytes(64)),
                       node_signature_2=Sig(bytes(64)),
                       bitcoin_signature_1=Sig(bytes(64)),
                       bitcoin_signature_2=Sig(bytes(64)),
                       features=features,
                       chain_hash=self.chain_hash,
                       short_channel_id=short_channel_id,
                       node_id_1=node_ids[0].format(),
                       node_id_2=node_ids[1].format(),
                       bitcoin_key_1=bitcoin_keys[0].format(),
                       bitcoin_key_2=bitcoin_keys[1].format())

    def channel_announcement(self,
                             short_channel_id: str,
                             features: str) -> Message:
        """Produce a (signed) channel_announcement message"""
        ann = self._unsigned_channel_announcment(features, short_channel_id)
        # BOLT #7:
        # - MUST compute the double-SHA256 hash `h` of the message, beginning
        #   at offset 256, up to the end of the message.
        #   - Note: the hash skips the 4 signatures but hashes the rest of the
        #     message, including any future fields appended to the end.
        buf = io.BytesIO()
        ann.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 256:]).digest()).digest()

        # BOLT #7:
        # - MUST set `node_signature_1` and `node_signature_2` to valid
        #   signatures of the hash `h` (using `node_id_1` and `node_id_2`'s
        #   respective secrets).
        node_privkeys = self.node_id_privkeys()
        ann.set_field('node_signature_1', Sig(node_privkeys[0].secret.hex(), h.hex()))
        ann.set_field('node_signature_2', Sig(node_privkeys[1].secret.hex(), h.hex()))

        bitcoin_privkeys = self.funding_privkeys_for_gossip()
        # - MUST set `bitcoin_signature_1` and `bitcoin_signature_2` to valid
        #   signatures of the hash `h` (using `bitcoin_key_1` and
        #   `bitcoin_key_2`'s respective secrets).
        ann.set_field('bitcoin_signature_1', Sig(bitcoin_privkeys[0].secret.hex(), h.hex()))
        ann.set_field('bitcoin_signature_2', Sig(bitcoin_privkeys[1].secret.hex(), h.hex()))

        return ann

    def channel_update(self,
                       short_channel_id: str,
                       side: Side,
                       disable: bool,
                       cltv_expiry_delta: int,
                       htlc_minimum_msat: int,
                       fee_base_msat: int,
                       fee_proportional_millionths: int,
                       timestamp: int,
                       htlc_maximum_msat: Optional[int]) -> Message:
        # BOLT #7: The `channel_flags` bitfield is used to indicate the
        # direction of the channel: it identifies the node that this update
        # originated from and signals various options concerning the
        # channel. The following table specifies the meaning of its individual
        # bits:
        #
        # | Bit Position  | Name        | Meaning                          |
        # | ------------- | ----------- | -------------------------------- |
        # | 0             | `direction` | Direction this update refers to. |
        # | 1             | `disable`   | Disable the channel.             |

        # BOLT #7:
        #   - if the origin node is `node_id_1` in the message:
        #     - MUST set the `direction` bit of `channel_flags` to 0.
        #   - otherwise:
        #     - MUST set the `direction` bit of `channel_flags` to 1.
        if self.funding_pubkey(side) == self.funding_pubkeys_for_gossip()[0]:
            channel_flags = 0
        else:
            channel_flags = 1

        if disable:
            channel_flags |= 2

        # BOLT #7: The `message_flags` bitfield is used to indicate the
        # presence of optional fields in the `channel_update` message:
        #
        # | Bit Position  | Name                      | Field                            |
        # | ------------- | ------------------------- | -------------------------------- |
        # | 0             | `option_channel_htlc_max` | `htlc_maximum_msat`              |
        message_flags = 0
        if htlc_maximum_msat:
            message_flags |= 1

        # Begin with a fake signature.
        update = Message(namespace().get_msgtype('channel_update'),
                         short_channel_id=short_channel_id,
                         signature=Sig(bytes(64)),
                         chain_hash=self.chain_hash,
                         timestamp=timestamp,
                         message_flags=message_flags,
                         channel_flags=channel_flags,
                         cltv_expiry_delta=cltv_expiry_delta,
                         htlc_minimum_msat=htlc_minimum_msat,
                         fee_base_msat=fee_base_msat,
                         fee_proportional_millionths=fee_proportional_millionths)
        if htlc_maximum_msat:
            update.set_field('htlc_maximum_msat', htlc_maximum_msat)

        # BOLT #7:
        # - MUST set `signature` to the signature of the double-SHA256 of the
        #   entire remaining packet after `signature`, using its own `node_id`.
        buf = io.BytesIO()
        update.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest()

        update.set_field('signature', Sig(self.node_privkeys[side].secret.hex(), h.hex()))

        return update

    def node_announcement(self,
                          side: Side,
                          features: str,
                          rgb_color: Tuple[int, int, int],
                          alias: str,
                          addresses: bytes,
                          timestamp: int) -> Message:
        # Begin with a fake signature.
        ann = Message(namespace().get_msgtype('node_announcement'),
                      signature=Sig(bytes(64)),
                      features=features,
                      timestamp=timestamp,
                      node_id=self.node_id(side).format().hex(),
                      rgb_color=bytes(rgb_color).hex(),
                      alias=bytes(alias, encoding='utf-8').zfill(32),
                      addresses=addresses)

        # BOLT #7:
        #  - MUST set `signature` to the signature of the double-SHA256 of the entire
        #  remaining packet after `signature` (using the key given by `node_id`).
        buf = io.BytesIO()
        ann.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest()

        ann.set_field('signature', Sig(self.node_privkeys[side].secret.hex(), h.hex()))
        return ann

    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()
Exemplo n.º 6
0
class Funding(object):
    def __init__(self,
                 funding_txid: str,
                 funding_output_index: int,
                 funding_amount: int,
                 local_node_privkey: str,
                 local_funding_privkey: str,
                 remote_node_privkey: str,
                 remote_funding_privkey: str,
                 chain_hash: str = regtest_hash,
                 locktime: int = 0):
        self.chain_hash = chain_hash
        self.txid = funding_txid
        self.output_index = funding_output_index
        self.amount = funding_amount
        self.bitcoin_privkeys = [
            privkey_expand(local_funding_privkey),
            privkey_expand(remote_funding_privkey)
        ]
        self.node_privkeys = [
            privkey_expand(local_node_privkey),
            privkey_expand(remote_node_privkey)
        ]
        self.tx = None
        self.locktime = locktime
        self.outputs = []
        self.inputs = []

    def tx_hex(self) -> str:
        if not self.tx:
            return ''
        return self.tx.serialize().hex()

    @staticmethod
    def sort_by_keys(key_one: coincurve.PublicKey,
                     key_two: coincurve.PublicKey, val_one: Any,
                     val_two: Any) -> Tuple[Any, Any]:
        """In many places we have to sort elements into key or nodeid order"""
        # BOLT #3:
        # ## Funding Transaction Output
        #
        # * The funding output script is a P2WSH to: `2 <pubkey1> <pubkey2> 2
        #  OP_CHECKMULTISIG`
        # * Where `pubkey1` is the lexicographically lesser of the two
        #   `funding_pubkey` in compressed format, and where `pubkey2` is the
        #   lexicographically greater of the two.
        if key_one.format() < key_two.format():
            return val_one, val_two
        else:
            return val_two, val_one

    def node_id_sort(self, local: Any, remote: Any) -> Tuple[Any, Any]:
        """Sorts these two items into lexicographical node id order"""
        # BOLT #7:
        # - MUST set `node_id_1` and `node_id_2` to the public keys of the two
        #   nodes operating the channel, such that `node_id_1` is the
        #   lexicographically-lesser of the two compressed keys sorted in
        #   ascending lexicographic order.
        return self.sort_by_keys(self.node_id(Side.local),
                                 self.node_id(Side.remote), local, remote)

    @staticmethod
    def redeemscript_keys(key_one: coincurve.PublicKey,
                          key_two: coincurve.PublicKey) -> CScript:
        return CScript([script.OP_2] + [
            k.format()
            for k in Funding.sort_by_keys(key_one, key_two, key_one, key_two)
        ] + [script.OP_2, script.OP_CHECKMULTISIG])

    def redeemscript(self) -> CScript:
        key_a, key_b = self.funding_pubkeys_for_tx()
        return self.redeemscript_keys(key_a, key_b)

    @staticmethod
    def locking_script_keys(key_one: coincurve.PublicKey,
                            key_two: coincurve.PublicKey) -> CScript:
        return CScript([
            script.OP_0,
            sha256(Funding.redeemscript_keys(key_one, key_two)).digest()
        ])

    def locking_script(self) -> CScript:
        a, b = self.funding_pubkeys_for_tx()
        return self.locking_script_keys(a, b)

    @staticmethod
    def start(local_node_privkey: str,
              local_funding_privkey: str,
              remote_node_privkey: str,
              remote_funding_privkey: str,
              funding_sats: int,
              locktime: int,
              chain_hash: str = regtest_hash) -> 'Funding':

        # Create dummy one to start: we will fill in txid at the end
        return Funding('', 0, funding_sats, local_node_privkey,
                       local_funding_privkey, remote_node_privkey,
                       remote_funding_privkey, chain_hash, locktime)

    def add_input(self,
                  serial_id: int,
                  prevtx: str,
                  prevtx_vout: int,
                  max_witness_len: int,
                  script: str,
                  sequence: int,
                  privkey: str = None) -> None:
        # Find the txid of the transaction
        prev_tx = CTransaction.deserialize(bytes.fromhex(prevtx))
        txin = CTxIn(COutPoint(prev_tx.GetTxid(), prevtx_vout),
                     nSequence=sequence)

        # Get the previous output for its outscript + value
        prev_vout = prev_tx.vout[prevtx_vout]

        self.inputs.append({
            'input': txin,
            'serial_id': serial_id,
            'sats': prev_vout.nValue,
            'prev_outscript': prev_vout.scriptPubKey.hex(),
            'redeemscript': script,
            'max_witness_len': max_witness_len,
            'privkey': privkey,
        })

    def add_output(self, serial_id: int, script: str, sats: int) -> None:
        txout = CTxOut(sats, CScript(bytes.fromhex(script)))
        self.outputs.append({'output': txout, 'serial_id': serial_id})

    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 build_tx(self) -> str:
        # Sort inputs by serial number
        ins = [
            x['input']
            for x in sorted(self.inputs, key=lambda k: k['serial_id'])
        ]

        # Sort outputs by serial number
        outs = [
            x['output']
            for x in sorted(self.outputs, key=lambda k: k['serial_id'])
        ]

        self.tx = CMutableTransaction(ins,
                                      outs,
                                      nVersion=2,
                                      nLockTime=self.locktime)
        self.txid = self.tx.GetTxid().hex()

        return self.tx.serialize().hex()

    @staticmethod
    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 channel_id(self) -> str:
        # BOLT #2: This message introduces the `channel_id` to identify the
        # channel. It's derived from the funding transaction by combining the
        # `funding_txid` and the `funding_output_index`, using big-endian
        # exclusive-OR (i.e. `funding_output_index` alters the last 2 bytes).
        chanid = bytearray.fromhex(self.txid)
        chanid[-1] ^= self.output_index % 256
        chanid[-2] ^= self.output_index // 256
        return chanid.hex()

    @staticmethod
    def funding_pubkey_key(
            privkey: coincurve.PrivateKey) -> coincurve.PublicKey:
        return coincurve.PublicKey.from_secret(privkey.secret)

    def funding_pubkey(self, side: Side) -> coincurve.PublicKey:
        return self.funding_pubkey_key(self.bitcoin_privkeys[side])

    def funding_pubkeys_for_tx(
            self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding pubkeys, in tx order"""
        # BOLT #3:
        # ## Funding Transaction Output
        #
        # * The funding output script is a P2WSH to: `2 <pubkey1> <pubkey2> 2
        #  OP_CHECKMULTISIG`
        # * Where `pubkey1` is the lexicographically lesser of the two
        #   `funding_pubkey` in compressed format, and where `pubkey2` is the
        #   lexicographically greater of the two.
        return self.sort_by_keys(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote),
                                 self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote))

    def funding_privkeys_for_tx(
            self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]:
        """Returns funding private keys, in tx order"""
        return self.sort_by_keys(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote),
                                 self.bitcoin_privkeys[Side.local],
                                 self.bitcoin_privkeys[Side.remote])

    def node_id(self, side: Side) -> coincurve.PublicKey:
        return coincurve.PublicKey.from_secret(self.node_privkeys[side].secret)

    def node_ids(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns node pubkeys, in order"""
        return self.node_id_sort(self.node_id(Side.local),
                                 self.node_id(Side.remote))

    def node_id_privkeys(
            self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]:
        """Returns node private keys, in order"""
        return self.node_id_sort(self.node_privkeys[Side.local],
                                 self.node_privkeys[Side.remote])

    def funding_pubkeys_for_gossip(
            self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding public keys, in gossip order"""
        return self.node_id_sort(self.funding_pubkey(Side.local),
                                 self.funding_pubkey(Side.remote))

    def funding_privkeys_for_gossip(
            self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]:
        """Returns funding private keys, in gossip order"""
        return self.node_id_sort(self.bitcoin_privkeys[Side.local],
                                 self.bitcoin_privkeys[Side.remote])

    def _unsigned_channel_announcment(self, features: str,
                                      short_channel_id: str) -> Message:
        """Produce a channel_announcement message with dummy sigs"""
        node_ids = self.node_ids()
        bitcoin_keys = self.funding_pubkeys_for_gossip()
        return Message(event_namespace.get_msgtype('channel_announcement'),
                       node_signature_1=Sig(bytes(64)),
                       node_signature_2=Sig(bytes(64)),
                       bitcoin_signature_1=Sig(bytes(64)),
                       bitcoin_signature_2=Sig(bytes(64)),
                       features=features,
                       chain_hash=self.chain_hash,
                       short_channel_id=short_channel_id,
                       node_id_1=node_ids[0].format(),
                       node_id_2=node_ids[1].format(),
                       bitcoin_key_1=bitcoin_keys[0].format(),
                       bitcoin_key_2=bitcoin_keys[1].format())

    def channel_announcement(self, short_channel_id: str,
                             features: str) -> Message:
        """Produce a (signed) channel_announcement message"""
        ann = self._unsigned_channel_announcment(features, short_channel_id)
        # BOLT #7:
        # - MUST compute the double-SHA256 hash `h` of the message, beginning
        #   at offset 256, up to the end of the message.
        #   - Note: the hash skips the 4 signatures but hashes the rest of the
        #     message, including any future fields appended to the end.
        buf = io.BytesIO()
        ann.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 256:]).digest()).digest()

        # BOLT #7:
        # - MUST set `node_signature_1` and `node_signature_2` to valid
        #   signatures of the hash `h` (using `node_id_1` and `node_id_2`'s
        #   respective secrets).
        node_privkeys = self.node_id_privkeys()
        ann.set_field('node_signature_1',
                      Sig(node_privkeys[0].secret.hex(), h.hex()))
        ann.set_field('node_signature_2',
                      Sig(node_privkeys[1].secret.hex(), h.hex()))

        bitcoin_privkeys = self.funding_privkeys_for_gossip()
        # - MUST set `bitcoin_signature_1` and `bitcoin_signature_2` to valid
        #   signatures of the hash `h` (using `bitcoin_key_1` and
        #   `bitcoin_key_2`'s respective secrets).
        ann.set_field('bitcoin_signature_1',
                      Sig(bitcoin_privkeys[0].secret.hex(), h.hex()))
        ann.set_field('bitcoin_signature_2',
                      Sig(bitcoin_privkeys[1].secret.hex(), h.hex()))

        return ann

    def channel_update(self, short_channel_id: str, side: Side, disable: bool,
                       cltv_expiry_delta: int, htlc_minimum_msat: int,
                       fee_base_msat: int, fee_proportional_millionths: int,
                       timestamp: int,
                       htlc_maximum_msat: Optional[int]) -> Message:
        # BOLT #7: The `channel_flags` bitfield is used to indicate the
        # direction of the channel: it identifies the node that this update
        # originated from and signals various options concerning the
        # channel. The following table specifies the meaning of its individual
        # bits:
        #
        # | Bit Position  | Name        | Meaning                          |
        # | ------------- | ----------- | -------------------------------- |
        # | 0             | `direction` | Direction this update refers to. |
        # | 1             | `disable`   | Disable the channel.             |

        # BOLT #7:
        #   - if the origin node is `node_id_1` in the message:
        #     - MUST set the `direction` bit of `channel_flags` to 0.
        #   - otherwise:
        #     - MUST set the `direction` bit of `channel_flags` to 1.
        if self.funding_pubkey(side) == self.funding_pubkeys_for_gossip()[0]:
            channel_flags = 0
        else:
            channel_flags = 1

        if disable:
            channel_flags |= 2

        # BOLT #7: The `message_flags` bitfield is used to indicate the
        # presence of optional fields in the `channel_update` message:
        #
        # | Bit Position  | Name                      | Field                            |
        # | ------------- | ------------------------- | -------------------------------- |
        # | 0             | `option_channel_htlc_max` | `htlc_maximum_msat`              |
        message_flags = 0
        if htlc_maximum_msat:
            message_flags |= 1

        # Begin with a fake signature.
        update = Message(
            event_namespace.get_msgtype('channel_update'),
            short_channel_id=short_channel_id,
            signature=Sig(bytes(64)),
            chain_hash=self.chain_hash,
            timestamp=timestamp,
            message_flags=message_flags,
            channel_flags=channel_flags,
            cltv_expiry_delta=cltv_expiry_delta,
            htlc_minimum_msat=htlc_minimum_msat,
            fee_base_msat=fee_base_msat,
            fee_proportional_millionths=fee_proportional_millionths)
        if htlc_maximum_msat:
            update.set_field('htlc_maximum_msat', htlc_maximum_msat)

        # BOLT #7:
        # - MUST set `signature` to the signature of the double-SHA256 of the
        #   entire remaining packet after `signature`, using its own `node_id`.
        buf = io.BytesIO()
        update.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest()

        update.set_field('signature',
                         Sig(self.node_privkeys[side].secret.hex(), h.hex()))

        return update

    def node_announcement(self, side: Side, features: str,
                          rgb_color: Tuple[int, int, int], alias: str,
                          addresses: bytes, timestamp: int) -> Message:
        # Begin with a fake signature.
        ann = Message(event_namespace.get_msgtype('node_announcement'),
                      signature=Sig(bytes(64)),
                      features=features,
                      timestamp=timestamp,
                      node_id=self.node_id(side).format().hex(),
                      rgb_color=bytes(rgb_color).hex(),
                      alias=bytes(alias, encoding='utf-8').zfill(32),
                      addresses=addresses)

        # BOLT #7:
        #  - MUST set `signature` to the signature of the double-SHA256 of the entire
        #  remaining packet after `signature` (using the key given by `node_id`).
        buf = io.BytesIO()
        ann.write(buf)
        # Note the first two 'type' bytes!
        h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest()

        ann.set_field('signature',
                      Sig(self.node_privkeys[side].secret.hex(), h.hex()))
        return ann

    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()