Beispiel #1
0
    async def write_prev_tx_footer(self, w: writers.Writer,
                                   tx: TransactionType,
                                   prev_hash: bytes) -> None:
        await super().write_prev_tx_footer(w, tx, prev_hash)

        if self.coin.extra_data:
            ofs = 0
            while ofs < tx.extra_data_len:
                size = min(1024, tx.extra_data_len - ofs)
                data = await helpers.request_tx_extra_data(
                    self.tx_req, ofs, size, prev_hash)
                writers.write_bytes_unchecked(w, data)
                ofs += len(data)
Beispiel #2
0
def append_pubkey(w: Writer, pubkey: bytes) -> None:
    write_op_push(w, len(pubkey))
    write_bytes_unchecked(w, pubkey)
Beispiel #3
0
def append_signature(w: Writer, signature: bytes, sighash: int) -> None:
    write_op_push(w, len(signature) + 1)
    write_bytes_unchecked(w, signature)
    w.append(sighash)
Beispiel #4
0
async def get_prevtx_output_value(
    coin: coininfo.CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int
) -> int:
    total_out = 0  # sum of output amounts

    # STAGE_REQUEST_2_PREV_META
    tx = await helpers.request_tx_meta(tx_req, coin, prev_hash)

    if tx.outputs_cnt <= prev_index:
        raise SigningError(
            FailureType.ProcessError, "Not enough outputs in previous transaction."
        )

    if not utils.BITCOIN_ONLY and coin.decred:
        txh = utils.HashWriter(blake256())
    else:
        txh = utils.HashWriter(sha256())

    if not utils.BITCOIN_ONLY and coin.overwintered:
        writers.write_uint32(
            txh, tx.version | zcash.OVERWINTERED
        )  # nVersion | fOverwintered
        writers.write_uint32(txh, tx.version_group_id)  # nVersionGroupId
    elif not utils.BITCOIN_ONLY and coin.decred:
        writers.write_uint32(txh, tx.version | decred.DECRED_SERIALIZE_NO_WITNESS)
    else:
        writers.write_uint32(txh, tx.version)  # nVersion
        if not utils.BITCOIN_ONLY and coin.timestamp:
            writers.write_uint32(txh, tx.timestamp)

    writers.write_varint(txh, tx.inputs_cnt)

    for i in range(tx.inputs_cnt):
        # STAGE_REQUEST_2_PREV_INPUT
        txi = await helpers.request_tx_input(tx_req, i, coin, prev_hash)
        if not utils.BITCOIN_ONLY and coin.decred:
            writers.write_tx_input_decred(txh, txi)
        else:
            writers.write_tx_input(txh, txi)

    writers.write_varint(txh, tx.outputs_cnt)

    for o in range(tx.outputs_cnt):
        # STAGE_REQUEST_2_PREV_OUTPUT
        txo_bin = await helpers.request_tx_output(tx_req, o, coin, prev_hash)
        writers.write_tx_output(txh, txo_bin)
        if o == prev_index:
            total_out += txo_bin.amount
            if (
                not utils.BITCOIN_ONLY
                and coin.decred
                and txo_bin.decred_script_version is not None
                and txo_bin.decred_script_version != 0
            ):
                raise SigningError(
                    FailureType.ProcessError,
                    "Cannot use utxo that has script_version != 0",
                )

    writers.write_uint32(txh, tx.lock_time)

    if not utils.BITCOIN_ONLY and (coin.overwintered or coin.decred):
        writers.write_uint32(txh, tx.expiry)

    if not utils.BITCOIN_ONLY and coin.extra_data:
        ofs = 0
        while ofs < tx.extra_data_len:
            size = min(1024, tx.extra_data_len - ofs)
            data = await helpers.request_tx_extra_data(tx_req, ofs, size, prev_hash)
            writers.write_bytes_unchecked(txh, data)
            ofs += len(data)

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

    return total_out
Beispiel #5
0
async def sign_tx(tx: SignTx, keychain: seed.Keychain):
    coin_name = tx.coin_name if tx.coin_name is not None else "Bitcoin"
    coin = coins.by_name(coin_name)
    tx = helpers.sanitize_sign_tx(tx, coin)

    progress.init(tx.inputs_count, tx.outputs_count)

    # Phase 1

    (
        h_first,
        hash143,
        segwit,
        authorized_bip143_in,
        wallet_path,
        multisig_fp,
    ) = await check_tx_fee(tx, keychain, coin)

    # Phase 2
    # - sign inputs
    # - check that nothing changed

    any_segwit = True in segwit.values()
    tx_ser = TxRequestSerializedType()

    txo_bin = TxOutputBinType()
    tx_req = TxRequest()
    tx_req.details = TxRequestDetailsType()
    tx_req.serialized = None

    if not utils.BITCOIN_ONLY and coin.decred:
        prefix_hash = hash143.prefix_hash()

    for i_sign in range(tx.inputs_count):
        progress.advance()
        txi_sign = None
        key_sign = None
        key_sign_pub = None

        if segwit[i_sign]:
            # STAGE_REQUEST_SEGWIT_INPUT
            txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)

            if not input_is_segwit(txi_sign):
                raise SigningError(
                    FailureType.ProcessError, "Transaction has changed during signing"
                )
            input_check_wallet_path(txi_sign, wallet_path)
            # NOTE: No need to check the multisig fingerprint, because we won't be signing
            # the script here. Signatures are produced in STAGE_REQUEST_SEGWIT_WITNESS.

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub)

            w_txi = writers.empty_bytearray(
                7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
            )
            if i_sign == 0:  # serializing first input => prepend headers
                write_tx_header(w_txi, coin, tx, True)
            writers.write_tx_input(w_txi, txi_sign)
            tx_ser.serialized_tx = w_txi
            tx_ser.signature_index = None
            tx_ser.signature = None
            tx_req.serialized = tx_ser

        elif not utils.BITCOIN_ONLY and (coin.force_bip143 or coin.overwintered):
            # STAGE_REQUEST_SEGWIT_INPUT
            txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)
            input_check_wallet_path(txi_sign, wallet_path)
            input_check_multisig_fingerprint(txi_sign, multisig_fp)

            is_bip143 = (
                txi_sign.script_type == InputScriptType.SPENDADDRESS
                or txi_sign.script_type == InputScriptType.SPENDMULTISIG
            )
            if not is_bip143 or txi_sign.amount > authorized_bip143_in:
                raise SigningError(
                    FailureType.ProcessError, "Transaction has changed during signing"
                )
            authorized_bip143_in -= txi_sign.amount

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            hash143_hash = hash143.preimage_hash(
                coin,
                tx,
                txi_sign,
                addresses.ecdsa_hash_pubkey(key_sign_pub, coin),
                get_hash_type(coin),
            )

            # if multisig, check if signing with a key that is included in multisig
            if txi_sign.multisig:
                multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)

            signature = ecdsa_sign(key_sign, hash143_hash)
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            gc.collect()
            txi_sign.script_sig = input_derive_script(
                coin, txi_sign, key_sign_pub, signature
            )
            w_txi_sign = writers.empty_bytearray(
                5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
            )
            if i_sign == 0:  # serializing first input => prepend headers
                write_tx_header(w_txi_sign, coin, tx, any_segwit)
            writers.write_tx_input(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign

            tx_req.serialized = tx_ser

        elif not utils.BITCOIN_ONLY and coin.decred:
            txi_sign = await helpers.request_tx_input(tx_req, i_sign, coin)

            input_check_wallet_path(txi_sign, wallet_path)
            input_check_multisig_fingerprint(txi_sign, multisig_fp)

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()

            if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
                prev_pkscript = scripts.output_script_multisig(
                    multisig.multisig_get_pubkeys(txi_sign.multisig),
                    txi_sign.multisig.m,
                )
            elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
                prev_pkscript = scripts.output_script_p2pkh(
                    addresses.ecdsa_hash_pubkey(key_sign_pub, coin)
                )
            else:
                raise SigningError("Unsupported input script type")

            h_witness = utils.HashWriter(blake256())
            writers.write_uint32(
                h_witness, tx.version | decred.DECRED_SERIALIZE_WITNESS_SIGNING
            )
            writers.write_varint(h_witness, tx.inputs_count)

            for ii in range(tx.inputs_count):
                if ii == i_sign:
                    writers.write_bytes_prefixed(h_witness, prev_pkscript)
                else:
                    writers.write_varint(h_witness, 0)

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

            h_sign = utils.HashWriter(blake256())
            writers.write_uint32(h_sign, decred.DECRED_SIGHASHALL)
            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=coin.sign_hash_double)
            signature = ecdsa_sign(key_sign, sig_hash)
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            gc.collect()
            txi_sign.script_sig = input_derive_script(
                coin, txi_sign, key_sign_pub, signature
            )
            w_txi_sign = writers.empty_bytearray(
                8 + 4 + len(hash143.get_last_output_bytes())
                if i_sign == 0
                else 0 + 16 + 4 + len(txi_sign.script_sig)
            )

            if i_sign == 0:
                writers.write_bytes_unchecked(
                    w_txi_sign, hash143.get_last_output_bytes()
                )
                writers.write_uint32(w_txi_sign, tx.lock_time)
                writers.write_uint32(w_txi_sign, tx.expiry)
                writers.write_varint(w_txi_sign, tx.inputs_count)

            writers.write_tx_input_decred_witness(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign
            tx_req.serialized = tx_ser

        else:
            # hash of what we are signing with this input
            h_sign = utils.HashWriter(sha256())
            # same as h_first, checked before signing the digest
            h_second = utils.HashWriter(sha256())

            writers.write_uint32(h_sign, tx.version)  # nVersion
            if not utils.BITCOIN_ONLY and coin.timestamp:
                writers.write_uint32(h_sign, tx.timestamp)

            writers.write_varint(h_sign, tx.inputs_count)

            for i in range(tx.inputs_count):
                # STAGE_REQUEST_4_INPUT
                txi = await helpers.request_tx_input(tx_req, i, coin)
                input_check_wallet_path(txi, wallet_path)
                writers.write_tx_input_check(h_second, txi)
                if i == i_sign:
                    txi_sign = txi
                    input_check_multisig_fingerprint(txi_sign, multisig_fp)
                    key_sign = keychain.derive(txi.address_n, coin.curve_name)
                    key_sign_pub = key_sign.public_key()
                    # for the signing process the script_sig is equal
                    # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH)
                    if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
                        txi_sign.script_sig = scripts.output_script_multisig(
                            multisig.multisig_get_pubkeys(txi_sign.multisig),
                            txi_sign.multisig.m,
                        )
                    elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
                        txi_sign.script_sig = scripts.output_script_p2pkh(
                            addresses.ecdsa_hash_pubkey(key_sign_pub, coin)
                        )
                    else:
                        raise SigningError(
                            FailureType.ProcessError, "Unknown transaction type"
                        )
                else:
                    txi.script_sig = bytes()
                writers.write_tx_input(h_sign, txi)

            writers.write_varint(h_sign, tx.outputs_count)

            for o in range(tx.outputs_count):
                # STAGE_REQUEST_4_OUTPUT
                txo = await helpers.request_tx_output(tx_req, o, coin)
                txo_bin.amount = txo.amount
                txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
                writers.write_tx_output(h_second, txo_bin)
                writers.write_tx_output(h_sign, txo_bin)

            writers.write_uint32(h_sign, tx.lock_time)
            writers.write_uint32(h_sign, get_hash_type(coin))

            # check the control digests
            if writers.get_tx_hash(h_first, False) != writers.get_tx_hash(h_second):
                raise SigningError(
                    FailureType.ProcessError, "Transaction has changed during signing"
                )

            # if multisig, check if signing with a key that is included in multisig
            if txi_sign.multisig:
                multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)

            # compute the signature from the tx digest
            signature = ecdsa_sign(
                key_sign, writers.get_tx_hash(h_sign, double=coin.sign_hash_double)
            )
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            gc.collect()
            txi_sign.script_sig = input_derive_script(
                coin, txi_sign, key_sign_pub, signature
            )
            w_txi_sign = writers.empty_bytearray(
                5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
            )
            if i_sign == 0:  # serializing first input => prepend headers
                write_tx_header(w_txi_sign, coin, tx, any_segwit)
            writers.write_tx_input(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign

            tx_req.serialized = tx_ser

    if not utils.BITCOIN_ONLY and coin.decred:
        return await helpers.request_tx_finish(tx_req)

    for o in range(tx.outputs_count):
        progress.advance()
        # STAGE_REQUEST_5_OUTPUT
        txo = await helpers.request_tx_output(tx_req, o, coin)
        txo_bin.amount = txo.amount
        txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)

        # serialize output
        w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
        if o == 0:  # serializing first output => prepend outputs count
            writers.write_varint(w_txo_bin, tx.outputs_count)
        writers.write_tx_output(w_txo_bin, txo_bin)

        tx_ser.signature_index = None
        tx_ser.signature = None
        tx_ser.serialized_tx = w_txo_bin

        tx_req.serialized = tx_ser

    for i in range(tx.inputs_count):
        progress.advance()
        if segwit[i]:
            # STAGE_REQUEST_SEGWIT_WITNESS
            txi = await helpers.request_tx_input(tx_req, i, coin)
            input_check_wallet_path(txi, wallet_path)
            input_check_multisig_fingerprint(txi, multisig_fp)

            if not input_is_segwit(txi) or txi.amount > authorized_bip143_in:
                raise SigningError(
                    FailureType.ProcessError, "Transaction has changed during signing"
                )
            authorized_bip143_in -= txi.amount

            key_sign = keychain.derive(txi.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            hash143_hash = hash143.preimage_hash(
                coin,
                tx,
                txi,
                addresses.ecdsa_hash_pubkey(key_sign_pub, coin),
                get_hash_type(coin),
            )

            signature = ecdsa_sign(key_sign, hash143_hash)
            if txi.multisig:
                # find out place of our signature based on the pubkey
                signature_index = multisig.multisig_pubkey_index(
                    txi.multisig, key_sign_pub
                )
                witness = scripts.witness_p2wsh(
                    txi.multisig, signature, signature_index, get_hash_type(coin)
                )
            else:
                witness = scripts.witness_p2wpkh(
                    signature, key_sign_pub, get_hash_type(coin)
                )

            tx_ser.serialized_tx = witness
            tx_ser.signature_index = i
            tx_ser.signature = signature
        elif any_segwit:
            tx_ser.serialized_tx += bytearray(1)  # empty witness for non-segwit inputs
            tx_ser.signature_index = None
            tx_ser.signature = None

        tx_req.serialized = tx_ser

    writers.write_uint32(tx_ser.serialized_tx, tx.lock_time)

    if not utils.BITCOIN_ONLY and coin.overwintered:
        if tx.version == 3:
            writers.write_uint32(tx_ser.serialized_tx, tx.expiry)  # expiryHeight
            writers.write_varint(tx_ser.serialized_tx, 0)  # nJoinSplit
        elif tx.version == 4:
            writers.write_uint32(tx_ser.serialized_tx, tx.expiry)  # expiryHeight
            writers.write_uint64(tx_ser.serialized_tx, 0)  # valueBalance
            writers.write_varint(tx_ser.serialized_tx, 0)  # nShieldedSpend
            writers.write_varint(tx_ser.serialized_tx, 0)  # nShieldedOutput
            writers.write_varint(tx_ser.serialized_tx, 0)  # nJoinSplit
        else:
            raise SigningError(
                FailureType.DataError,
                "Unsupported version for overwintered transaction",
            )

    await helpers.request_tx_finish(tx_req)
Beispiel #6
0
def append_pubkey(w: bytearray, pubkey: bytes) -> bytearray:
    write_op_push(w, len(pubkey))
    write_bytes_unchecked(w, pubkey)
    return w
Beispiel #7
0
def append_signature(w: bytearray, signature: bytes,
                     sighash: int) -> bytearray:
    write_op_push(w, len(signature) + 1)
    write_bytes_unchecked(w, signature)
    w.append(sighash)
    return w