Beispiel #1
0
def write_witness_multisig(
    w: Writer,
    multisig: MultisigRedeemScriptType,
    signature: bytes,
    signature_index: int,
    sighash_type: SigHashType,
) -> None:
    # get other signatures, stretch with empty bytes to the number of the pubkeys
    signatures = multisig.signatures + [b""] * (
        multisig_get_pubkey_count(multisig) - len(multisig.signatures))

    # fill in our signature
    if signatures[signature_index]:
        raise wire.DataError("Invalid multisig parameters")
    signatures[signature_index] = signature

    # witness program + signatures + redeem script
    num_of_witness_items = 1 + sum(1 for s in signatures if s) + 1
    write_compact_size(w, num_of_witness_items)

    # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
    # consumes one additional item on the stack:
    # https://bitcoin.org/en/developer-guide#standard-transactions
    write_compact_size(w, 0)

    for s in signatures:
        if s:
            write_signature_prefixed(
                w, s, sighash_type)  # size of the witness included

    # redeem script
    pubkeys = multisig_get_pubkeys(multisig)
    write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True)
Beispiel #2
0
def write_signature_prefixed(w: Writer, signature: bytes,
                             sighash_type: SigHashType) -> None:
    length = len(signature)
    if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT:
        length += 1

    write_compact_size(w, length)
    write_bytes_unchecked(w, signature)
    if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT:
        w.append(sighash_type)
Beispiel #3
0
 def write_tx_header(
     self,
     w: writers.Writer,
     tx: SignTx | PrevTx,
     witness_marker: bool,
 ) -> None:
     writers.write_uint32(w, tx.version)  # nVersion
     if witness_marker:
         write_compact_size(w, 0x00)  # segwit witness marker
         write_compact_size(w, 0x01)  # segwit witness flag
Beispiel #4
0
    async def step2_approve_outputs(self) -> None:
        write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count)
        write_compact_size(self.h_prefix, self.tx_info.tx.outputs_count)

        if self.tx_info.tx.decred_staking_ticket:
            await self.approve_staking_ticket()
        else:
            await super().step2_approve_outputs()

        self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
        self.write_tx_footer(self.h_prefix, self.tx_info.tx)
Beispiel #5
0
def write_output_script_p2pkh(w: Writer,
                              pubkeyhash: bytes,
                              prefixed: bool = False) -> None:
    if prefixed:
        write_compact_size(w, 25)
    w.append(0x76)  # OP_DUP
    w.append(0xA9)  # OP_HASH160
    w.append(0x14)  # OP_DATA_20
    write_bytes_fixed(w, pubkeyhash, 20)
    w.append(0x88)  # OP_EQUALVERIFY
    w.append(0xAC)  # OP_CHECKSIG
Beispiel #6
0
 async def step4_serialize_inputs(self) -> None:
     self.write_tx_header(self.serialized_tx, self.tx_info.tx, bool(self.segwit))
     write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
     for i in range(self.tx_info.tx.inputs_count):
         progress.advance()
         if i in self.external:
             await self.serialize_external_input(i)
         elif i in self.segwit:
             await self.serialize_segwit_input(i)
         else:
             await self.sign_nonsegwit_input(i)
Beispiel #7
0
def write_input_script_p2wsh_in_p2sh(w: Writer,
                                     script_hash: bytes,
                                     prefixed: bool = False) -> None:
    # 22 00 20 <redeem script hash>
    # Signature is moved to the witness.
    if prefixed:
        write_compact_size(w, 35)

    w.append(0x22)  # length of the data
    w.append(0x00)  # witness version byte
    w.append(0x20)  # P2WSH witness program (redeem script hash length)
    write_bytes_fixed(w, script_hash, 32)
Beispiel #8
0
def write_input_script_p2wpkh_in_p2sh(w: Writer,
                                      pubkeyhash: bytes,
                                      prefixed: bool = False) -> None:
    # 16 00 14 <pubkeyhash>
    # Signature is moved to the witness.
    if prefixed:
        write_compact_size(w, 23)

    w.append(0x16)  # length of the data
    w.append(0x00)  # witness version byte
    w.append(0x14)  # P2WPKH witness program (pub key hash length)
    write_bytes_fixed(w, pubkeyhash, 20)  # pub key hash
Beispiel #9
0
 def write_tx_header(
     self,
     w: writers.Writer,
     tx: SignTx | PrevTx,
     witness_marker: bool,
 ) -> None:
     writers.write_uint32(w, tx.version)  # nVersion
     if self.coin.timestamp:
         assert tx.timestamp is not None  # checked in sanitize_*
         writers.write_uint32(w, tx.timestamp)
     if witness_marker:
         write_compact_size(w, 0x00)  # segwit witness marker
         write_compact_size(w, 0x01)  # segwit witness flag
Beispiel #10
0
    async def get_prevtx_output(
        self, prev_hash: bytes, prev_index: int
    ) -> tuple[int, bytes]:
        amount_out = 0  # output amount

        # STAGE_REQUEST_3_PREV_META in legacy
        tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)

        if tx.outputs_count <= prev_index:
            raise wire.ProcessError("Not enough outputs in previous transaction.")

        txh = self.create_hash_writer()

        # witnesses are not included in txid hash
        self.write_tx_header(txh, tx, witness_marker=False)
        write_compact_size(txh, tx.inputs_count)

        for i in range(tx.inputs_count):
            # STAGE_REQUEST_3_PREV_INPUT in legacy
            txi = await helpers.request_tx_prev_input(
                self.tx_req, i, self.coin, prev_hash
            )
            self.write_tx_input(txh, txi, txi.script_sig)

        write_compact_size(txh, tx.outputs_count)

        script_pubkey: bytes | None = None
        for i in range(tx.outputs_count):
            # STAGE_REQUEST_3_PREV_OUTPUT in legacy
            txo_bin = await helpers.request_tx_prev_output(
                self.tx_req, i, self.coin, prev_hash
            )
            self.write_tx_output(txh, txo_bin, txo_bin.script_pubkey)
            if i == prev_index:
                amount_out = txo_bin.amount
                script_pubkey = txo_bin.script_pubkey
                self.check_prevtx_output(txo_bin)

        assert script_pubkey is not None  # prev_index < tx.outputs_count

        await self.write_prev_tx_footer(txh, tx, prev_hash)

        if (
            writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True)
            != prev_hash
        ):
            raise wire.ProcessError("Encountered invalid prev_hash")

        return amount_out, script_pubkey
Beispiel #11
0
def message_digest(coin: CoinInfo, message: bytes) -> bytes:
    if not utils.BITCOIN_ONLY and coin.decred:
        h = utils.HashWriter(blake256())
    else:
        h = utils.HashWriter(sha256())
    if not coin.signed_message_header:
        raise wire.DataError("Empty message header not allowed.")
    write_compact_size(h, len(coin.signed_message_header))
    h.extend(coin.signed_message_header.encode())
    write_compact_size(h, len(message))
    h.extend(message)
    ret = h.get_digest()
    if coin.sign_hash_double:
        ret = sha256(ret).digest()
    return ret
Beispiel #12
0
def write_output_script_multisig(
    w: Writer,
    pubkeys: Sequence[bytes | memoryview],
    m: int,
    prefixed: bool = False,
) -> None:
    n = len(pubkeys)
    if n < 1 or n > 15 or m < 1 or m > 15 or m > n:
        raise wire.DataError("Invalid multisig parameters")
    for pubkey in pubkeys:
        if len(pubkey) != 33:
            raise wire.DataError("Invalid multisig parameters")

    if prefixed:
        write_compact_size(w, output_script_multisig_length(pubkeys, m))

    w.append(0x50 + m)  # numbers 1 to 16 are pushed as 0x50 + value
    for p in pubkeys:
        append_pubkey(w, p)
    w.append(0x50 + n)
    w.append(0xAE)  # OP_CHECKMULTISIG
Beispiel #13
0
    def __init__(
        self,
        tx: SignTx,
        keychain: Keychain,
        coin: CoinInfo,
        approver: approvers.Approver | None,
    ) -> None:
        ensure(coin.decred)
        self.h_prefix = HashWriter(blake256())

        ensure(approver is None)
        approver = DecredApprover(tx, coin)
        super().__init__(tx, keychain, coin, approver)

        self.write_tx_header(self.serialized_tx, self.tx_info.tx, witness_marker=True)
        write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)

        writers.write_uint32(
            self.h_prefix, self.tx_info.tx.version | DECRED_SERIALIZE_NO_WITNESS
        )
        write_compact_size(self.h_prefix, self.tx_info.tx.inputs_count)
Beispiel #14
0
def write_input_script_multisig_prefixed(
    w: Writer,
    multisig: MultisigRedeemScriptType,
    signature: bytes,
    signature_index: int,
    sighash_type: SigHashType,
    coin: CoinInfo,
) -> None:
    signatures = multisig.signatures  # other signatures
    if len(signatures[signature_index]) > 0:
        raise wire.DataError("Invalid multisig parameters")
    signatures[signature_index] = signature  # our signature

    # length of the redeem script
    pubkeys = multisig_get_pubkeys(multisig)
    redeem_script_length = output_script_multisig_length(pubkeys, multisig.m)

    # length of the result
    total_length = 1  # OP_FALSE
    for s in signatures:
        if s:
            total_length += 1 + len(s) + 1  # length, signature, sighash_type
    total_length += op_push_length(redeem_script_length) + redeem_script_length
    write_compact_size(w, total_length)

    # Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
    # consumes one additional item on the stack:
    # https://bitcoin.org/en/developer-guide#standard-transactions
    w.append(0x00)

    for s in signatures:
        if s:
            append_signature(w, s, sighash_type)

    # redeem script
    write_op_push(w, redeem_script_length)
    write_output_script_multisig(w, pubkeys, multisig.m)
Beispiel #15
0
    async def step7_finish(self) -> None:
        self.write_tx_footer(self.serialized_tx, self.tx_info.tx)

        write_uint64(self.serialized_tx, 0)  # valueBalance
        write_compact_size(self.serialized_tx, 0)  # nShieldedSpend
        write_compact_size(self.serialized_tx, 0)  # nShieldedOutput
        write_compact_size(self.serialized_tx, 0)  # nJoinSplit

        await helpers.request_tx_finish(self.tx_req)
Beispiel #16
0
def write_witness_p2wpkh(w: Writer, signature: bytes, pubkey: bytes,
                         sighash_type: SigHashType) -> None:
    write_compact_size(w, 0x02)  # num of segwit items, in P2WPKH it's always 2
    write_signature_prefixed(w, signature, sighash_type)
    write_bytes_prefixed(w, pubkey)
Beispiel #17
0
def write_witness_p2tr(w: Writer, signature: bytes,
                       sighash_type: SigHashType) -> None:
    # Taproot key path spending without annex.
    write_compact_size(w, 0x01)  # num of segwit items
    write_signature_prefixed(w, signature, sighash_type)
Beispiel #18
0
    async def get_legacy_tx_digest(
        self,
        index: int,
        tx_info: TxInfo | OriginalTxInfo,
        script_pubkey: bytes | None = None,
    ) -> tuple[bytes, TxInput, bip32.HDNode | None]:
        tx_hash = tx_info.orig_hash if isinstance(tx_info, OriginalTxInfo) else None

        # the transaction digest which gets signed for this input
        h_sign = self.create_hash_writer()
        # should come out the same as h_tx_check, checked before signing the digest
        h_check = HashWriter(sha256())

        self.write_tx_header(h_sign, tx_info.tx, witness_marker=False)
        write_compact_size(h_sign, tx_info.tx.inputs_count)

        txi_sign = None
        node = None
        for i in range(tx_info.tx.inputs_count):
            # STAGE_REQUEST_4_INPUT in legacy
            txi = await helpers.request_tx_input(self.tx_req, i, self.coin, tx_hash)
            writers.write_tx_input_check(h_check, txi)
            # Only the previous UTXO's scriptPubKey is included in h_sign.
            if i == index:
                txi_sign = txi
                if not script_pubkey:
                    self.tx_info.check_input(txi)
                    node = self.keychain.derive(txi.address_n)
                    key_sign_pub = node.public_key()
                    if txi.multisig:
                        # Sanity check to ensure we are signing with a key that is included in the multisig.
                        multisig.multisig_pubkey_index(txi.multisig, key_sign_pub)

                    if txi.script_type == InputScriptType.SPENDMULTISIG:
                        assert txi.multisig is not None  # checked in sanitize_tx_input
                        script_pubkey = scripts.output_script_multisig(
                            multisig.multisig_get_pubkeys(txi.multisig),
                            txi.multisig.m,
                        )
                    elif txi.script_type == InputScriptType.SPENDADDRESS:
                        script_pubkey = scripts.output_script_p2pkh(
                            addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin)
                        )
                    else:
                        raise wire.ProcessError("Unknown transaction type")
                self.write_tx_input(h_sign, txi, script_pubkey)
            else:
                self.write_tx_input(h_sign, txi, bytes())

        if txi_sign is None:
            raise RuntimeError  # index >= tx_info.tx.inputs_count

        write_compact_size(h_sign, tx_info.tx.outputs_count)

        for i in range(tx_info.tx.outputs_count):
            # STAGE_REQUEST_4_OUTPUT in legacy
            txo = await helpers.request_tx_output(self.tx_req, i, self.coin, tx_hash)
            script_pubkey = self.output_derive_script(txo)
            self.write_tx_output(h_check, txo, script_pubkey)
            self.write_tx_output(h_sign, txo, script_pubkey)

        writers.write_uint32(h_sign, tx_info.tx.lock_time)
        writers.write_uint32(h_sign, self.get_hash_type(txi_sign))

        # check that the inputs were the same as those streamed for approval
        if tx_info.get_tx_check_digest() != h_check.get_digest():
            raise wire.ProcessError("Transaction has changed during signing")

        tx_digest = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
        return tx_digest, txi_sign, node
Beispiel #19
0
 async def step5_serialize_outputs(self) -> None:
     write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count)
     for i in range(self.tx_info.tx.outputs_count):
         progress.advance()
         await self.serialize_output(i)
Beispiel #20
0
 def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None:
     # serialize Sapling bundle
     write_compact_size(w, 0)  # nSpendsSapling
     write_compact_size(w, 0)  # nOutputsSapling
     # serialize Orchard bundle
     write_compact_size(w, 0)  # nActionsOrchard
Beispiel #21
0
def write_bytes_prefixed(w: Writer, b: bytes) -> None:
    write_compact_size(w, len(b))
    write_bytes_unchecked(w, b)
Beispiel #22
0
    async def step4_serialize_inputs(self) -> None:
        write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)

        prefix_hash = self.h_prefix.get_digest()

        for i_sign in range(self.tx_info.tx.inputs_count):
            progress.advance()

            txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin)

            self.tx_info.check_input(txi_sign)

            key_sign = self.keychain.derive(txi_sign.address_n)
            key_sign_pub = key_sign.public_key()

            h_witness = self.create_hash_writer()
            writers.write_uint32(
                h_witness, self.tx_info.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING
            )
            write_compact_size(h_witness, self.tx_info.tx.inputs_count)

            for ii in range(self.tx_info.tx.inputs_count):
                if ii == i_sign:
                    if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX:
                        scripts_decred.write_output_script_ssrtx_prefixed(
                            h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)
                        )
                    elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen:
                        scripts_decred.write_output_script_ssgen_prefixed(
                            h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)
                        )
                    elif txi_sign.script_type == InputScriptType.SPENDMULTISIG:
                        assert txi_sign.multisig is not None
                        scripts_decred.write_output_script_multisig(
                            h_witness,
                            multisig.multisig_get_pubkeys(txi_sign.multisig),
                            txi_sign.multisig.m,
                            prefixed=True,
                        )
                    elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
                        scripts_decred.write_output_script_p2pkh(
                            h_witness,
                            ecdsa_hash_pubkey(key_sign_pub, self.coin),
                            prefixed=True,
                        )
                    else:
                        raise wire.DataError("Unsupported input script type")
                else:
                    write_compact_size(h_witness, 0)

            witness_hash = writers.get_tx_hash(
                h_witness, double=self.coin.sign_hash_double, reverse=False
            )

            h_sign = self.create_hash_writer()
            writers.write_uint32(h_sign, SigHashType.SIGHASH_ALL)
            writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE)
            writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE)

            sig_hash = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
            signature = ecdsa_sign(key_sign, sig_hash)

            # serialize input with correct signature
            self.write_tx_input_witness(
                self.serialized_tx, txi_sign, key_sign_pub, signature
            )
            self.set_serialized_signature(i_sign, signature)
Beispiel #23
0
def write_input_script_p2pkh_or_p2sh_prefixed(
        w: Writer, pubkey: bytes, signature: bytes,
        sighash_type: SigHashType) -> None:
    write_compact_size(w, 1 + len(signature) + 1 + 1 + len(pubkey))
    append_signature(w, signature, sighash_type)
    append_pubkey(w, pubkey)