Beispiel #1
0
    async def step4_serialize_inputs(self) -> None:
        writers.write_varint(self.serialized_tx, self.tx.inputs_count)

        prefix_hash = self.h_prefix.get_digest()

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

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

            self.wallet_path.check_input(txi_sign)
            self.multisig_fingerprint.check_input(txi_sign)

            key_sign = self.keychain.derive(txi_sign.address_n)
            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, self.coin)
                )
            else:
                raise SigningError("Unsupported input script type")

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

            for ii in range(self.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=self.coin.sign_hash_double, reverse=False
            )

            h_sign = self.create_hash_writer()
            writers.write_uint32(h_sign, DECRED_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
            gc.collect()
            script_sig = self.input_derive_script(txi_sign, key_sign_pub, signature)
            writers.write_tx_input_decred_witness(
                self.serialized_tx, txi_sign, script_sig
            )
            self.set_serialized_signature(i_sign, signature)
Beispiel #2
0
 async def confirm_output(self, txo: TxOutputType, script_pubkey: bytes) -> None:
     if txo.decred_script_version != 0:
         raise SigningError(
             FailureType.ActionCancelled,
             "Cannot send to output with script version != 0",
         )
     await super().confirm_output(txo, script_pubkey)
     self.write_tx_output(self.serialized_tx, txo, script_pubkey)
Beispiel #3
0
    async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
        amount_out = 0  # output amount

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

        if tx.outputs_cnt <= prev_index:
            raise SigningError(
                FailureType.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)
        writers.write_varint(txh, tx.inputs_cnt)

        for i in range(tx.inputs_cnt):
            # STAGE_REQUEST_2_PREV_INPUT in legacy
            txi = await helpers.request_tx_input(self.tx_req, i, self.coin, prev_hash)
            self.write_tx_input(txh, txi, txi.script_sig)

        writers.write_varint(txh, tx.outputs_cnt)

        for i in range(tx.outputs_cnt):
            # STAGE_REQUEST_2_PREV_OUTPUT in legacy
            txo_bin = await helpers.request_tx_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
                self.check_prevtx_output(txo_bin)

        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 SigningError(
                FailureType.ProcessError, "Encountered invalid prev_hash"
            )

        return amount_out
Beispiel #4
0
    def check_input(self, txi: TxInputType) -> None:
        if self.attribute is self.MISMATCH:
            return  # There was already a mismatch when adding inputs, ignore it now.

        # All added inputs had a matching attribute, allowing a change-output.
        # Ensure that this input still has the same attribute.
        if self.attribute != self.attribute_from_tx(txi):
            raise SigningError(FailureType.ProcessError,
                               "Transaction has changed during signing")
Beispiel #5
0
    async def step3_confirm_tx(self) -> None:
        fee = self.total_in - self.total_out

        if fee < 0:
            self.on_negative_fee()

        # fee > (coin.maxfee per byte * tx size)
        if fee > (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4):
            if not await helpers.confirm_feeoverthreshold(fee, self.coin):
                raise SigningError(FailureType.ActionCancelled, "Signing cancelled")

        if self.tx.lock_time > 0:
            if not await helpers.confirm_nondefault_locktime(self.tx.lock_time):
                raise SigningError(FailureType.ActionCancelled, "Locktime cancelled")

        if not await helpers.confirm_total(
            self.total_in - self.change_out, fee, self.coin
        ):
            raise SigningError(FailureType.ActionCancelled, "Total cancelled")
Beispiel #6
0
    async def confirm_output(self, txo: TxOutputType, script_pubkey: bytes) -> None:
        if self.change_out == 0 and self.output_is_change(txo):
            # output is change and does not need confirmation
            self.change_out = txo.amount
        elif not await helpers.confirm_output(txo, self.coin):
            raise SigningError(FailureType.ActionCancelled, "Output cancelled")

        self.write_tx_output(self.h_confirmed, txo, script_pubkey)
        self.hash143_add_output(txo, script_pubkey)
        self.total_out += txo.amount
Beispiel #7
0
    def output_derive_script(self, txo: TxOutputType) -> bytes:
        if txo.address_n:
            # change output
            try:
                input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[
                    txo.script_type]
            except KeyError:
                raise SigningError(FailureType.DataError,
                                   "Invalid script type")
            node = self.keychain.derive(txo.address_n)
            txo.address = addresses.get_address(input_script_type, self.coin,
                                                node, txo.multisig)

        return scripts.output_derive_script(txo, self.coin)
Beispiel #8
0
    async def serialize_segwit_input(self, i: int) -> None:
        # STAGE_REQUEST_SEGWIT_INPUT in legacy
        txi = await helpers.request_tx_input(self.tx_req, i, self.coin)

        if not input_is_segwit(txi):
            raise SigningError(FailureType.ProcessError,
                               "Transaction has changed during signing")
        self.wallet_path.check_input(txi)
        # 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.

        node = self.keychain.derive(txi.address_n)
        key_sign_pub = node.public_key()
        script_sig = self.input_derive_script(txi, key_sign_pub)
        self.write_tx_input(self.serialized_tx, txi, script_sig)
Beispiel #9
0
def derive_script_code(txi: TxInputType, pubkeyhash: bytes) -> bytearray:

    if txi.multisig:
        return output_script_multisig(
            multisig_get_pubkeys(txi.multisig), txi.multisig.m
        )

    p2pkh = txi.script_type == InputScriptType.SPENDADDRESS
    if p2pkh:
        return output_script_p2pkh(pubkeyhash)

    else:
        raise SigningError(
            FailureType.DataError, "Unknown input script type for zip143 script code"
        )
Beispiel #10
0
    async def process_input(self, txi: TxInputType) -> None:
        self.wallet_path.add_input(txi)
        self.multisig_fingerprint.add_input(txi)
        writers.write_tx_input_check(self.h_confirmed, txi)
        self.hash143_add_input(txi)  # all inputs are included (non-segwit as well)

        if not addresses.validate_full_path(txi.address_n, self.coin, txi.script_type):
            await helpers.confirm_foreign_address(txi.address_n)

        if input_is_segwit(txi):
            await self.process_segwit_input(txi)
        elif input_is_nonsegwit(txi):
            await self.process_nonsegwit_input(txi)
        else:
            raise SigningError(FailureType.DataError, "Wrong input script type")
Beispiel #11
0
    def __init__(self, tx: SignTx, keychain: Keychain, coin: CoinInfo) -> None:
        ensure(coin.overwintered)
        super().__init__(tx, keychain, coin)

        if self.tx.version == 3:
            if not self.tx.branch_id:
                self.tx.branch_id = 0x5BA81B19  # Overwinter
        elif self.tx.version == 4:
            if not self.tx.branch_id:
                self.tx.branch_id = 0x76B809BB  # Sapling
        else:
            raise SigningError(
                FailureType.DataError,
                "Unsupported version for overwintered transaction",
            )
Beispiel #12
0
    async def sign_nonsegwit_bip143_input(self, i_sign: int) -> None:
        txi = await helpers.request_tx_input(self.tx_req, i_sign, self.coin)

        if not input_is_nonsegwit(txi):
            raise SigningError(FailureType.ProcessError,
                               "Transaction has changed during signing")
        public_key, signature = self.sign_bip143_input(txi)

        # if multisig, do a sanity check to ensure we are signing with a key that is included in the multisig
        if txi.multisig:
            multisig.multisig_pubkey_index(txi.multisig, public_key)

        # serialize input with correct signature
        gc.collect()
        script_sig = self.input_derive_script(txi, public_key, signature)
        self.write_tx_input(self.serialized_tx, txi, script_sig)
        self.set_serialized_signature(i_sign, signature)
Beispiel #13
0
    def sign_bip143_input(self, txi: TxInputType) -> Tuple[bytes, bytes]:
        self.wallet_path.check_input(txi)
        self.multisig_fingerprint.check_input(txi)

        if txi.amount > self.bip143_in:
            raise SigningError(FailureType.ProcessError,
                               "Transaction has changed during signing")
        self.bip143_in -= txi.amount

        node = self.keychain.derive(txi.address_n)
        public_key = node.public_key()
        hash143_hash = self.hash143_preimage_hash(
            txi, addresses.ecdsa_hash_pubkey(public_key, self.coin))

        signature = ecdsa_sign(node, hash143_hash)

        return public_key, signature
Beispiel #14
0
    async def step7_finish(self) -> None:
        self.write_tx_footer(self.serialized_tx, self.tx)

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

        await helpers.request_tx_finish(self.tx_req)
Beispiel #15
0
    async def sign_segwit_input(self, i: int) -> None:
        # STAGE_REQUEST_SEGWIT_WITNESS in legacy
        txi = await helpers.request_tx_input(self.tx_req, i, self.coin)

        if not input_is_segwit(txi):
            raise SigningError(FailureType.ProcessError,
                               "Transaction has changed during signing")

        public_key, signature = self.sign_bip143_input(txi)

        self.set_serialized_signature(i, signature)
        if txi.multisig:
            # find out place of our signature based on the pubkey
            signature_index = multisig.multisig_pubkey_index(
                txi.multisig, public_key)
            self.serialized_tx.extend(
                scripts.witness_p2wsh(txi.multisig, signature, signature_index,
                                      self.get_hash_type()))
        else:
            self.serialized_tx.extend(
                scripts.witness_p2wpkh(signature, public_key,
                                       self.get_hash_type()))
Beispiel #16
0
 def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None:
     if txo_bin.decred_script_version != 0:
         raise SigningError(
             FailureType.ProcessError,
             "Cannot use utxo that has script_version != 0",
         )
Beispiel #17
0
    async def sign_nonsegwit_input(self, i_sign: int) -> None:
        # hash of what we are signing with this input
        h_sign = self.create_hash_writer()
        # should come out the same as h_confirmed, checked before signing the digest
        h_check = self.create_hash_writer()

        self.write_tx_header(h_sign, self.tx, witness_marker=False)
        writers.write_varint(h_sign, self.tx.inputs_count)

        for i in range(self.tx.inputs_count):
            # STAGE_REQUEST_4_INPUT in legacy
            txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
            writers.write_tx_input_check(h_check, txi)
            if i == i_sign:
                self.wallet_path.check_input(txi)
                self.multisig_fingerprint.check_input(txi)
                # NOTE: wallet_path is checked in write_tx_input_check()
                node = self.keychain.derive(txi.address_n, self.coin.curve_name)
                key_sign_pub = node.public_key()
                # if multisig, do a sanity check to ensure we are signing with a key that is included in the multisig
                if txi.multisig:
                    multisig.multisig_pubkey_index(txi.multisig, key_sign_pub)

                # For the signing process the previous UTXO's scriptPubKey is included in h_sign.
                if txi.script_type == InputScriptType.SPENDMULTISIG:
                    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 SigningError(
                        FailureType.ProcessError, "Unknown transaction type"
                    )
                txi_sign = txi
            else:
                script_pubkey = bytes()
            self.write_tx_input(h_sign, txi, script_pubkey)

        writers.write_varint(h_sign, self.tx.outputs_count)

        for i in range(self.tx.outputs_count):
            # STAGE_REQUEST_4_OUTPUT in legacy
            txo = await helpers.request_tx_output(self.tx_req, i, self.coin)
            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, self.tx.lock_time)
        writers.write_uint32(h_sign, self.get_hash_type())

        # check the control digests
        if self.h_confirmed.get_digest() != h_check.get_digest():
            raise SigningError(
                FailureType.ProcessError, "Transaction has changed during signing"
            )

        # compute the signature from the tx digest
        signature = ecdsa_sign(
            node, writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
        )

        # serialize input with correct signature
        gc.collect()
        script_sig = self.input_derive_script(txi_sign, key_sign_pub, signature)
        self.write_tx_input(self.serialized_tx, txi_sign, script_sig)
        self.set_serialized_signature(i_sign, signature)
Beispiel #18
0
 def on_negative_fee(self) -> None:
     raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
Beispiel #19
0
    def hash143_preimage_hash(self, txi: TxInputType, pubkeyhash: bytes) -> bytes:
        h_preimage = HashWriter(
            blake2b(
                outlen=32,
                personal=b"ZcashSigHash" + struct.pack("<I", self.tx.branch_id),
            )
        )

        # 1. nVersion | fOverwintered
        write_uint32(h_preimage, self.tx.version | OVERWINTERED)
        # 2. nVersionGroupId
        write_uint32(h_preimage, self.tx.version_group_id)
        # 3. hashPrevouts
        write_bytes_fixed(h_preimage, get_tx_hash(self.h_prevouts), TX_HASH_SIZE)
        # 4. hashSequence
        write_bytes_fixed(h_preimage, get_tx_hash(self.h_sequence), TX_HASH_SIZE)
        # 5. hashOutputs
        write_bytes_fixed(h_preimage, get_tx_hash(self.h_outputs), TX_HASH_SIZE)

        if self.tx.version == 3:
            # 6. hashJoinSplits
            write_bytes_fixed(h_preimage, b"\x00" * TX_HASH_SIZE, TX_HASH_SIZE)
            # 7. nLockTime
            write_uint32(h_preimage, self.tx.lock_time)
            # 8. expiryHeight
            write_uint32(h_preimage, self.tx.expiry)
            # 9. nHashType
            write_uint32(h_preimage, self.get_hash_type())
        elif self.tx.version == 4:
            zero_hash = b"\x00" * TX_HASH_SIZE
            # 6. hashJoinSplits
            write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE)
            # 7. hashShieldedSpends
            write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE)
            # 8. hashShieldedOutputs
            write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE)
            # 9. nLockTime
            write_uint32(h_preimage, self.tx.lock_time)
            # 10. expiryHeight
            write_uint32(h_preimage, self.tx.expiry)
            # 11. valueBalance
            write_uint64(h_preimage, 0)
            # 12. nHashType
            write_uint32(h_preimage, self.get_hash_type())
        else:
            raise SigningError(
                FailureType.DataError,
                "Unsupported version for overwintered transaction",
            )

        # 10a /13a. outpoint
        write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE)
        write_uint32(h_preimage, txi.prev_index)

        # 10b / 13b. scriptCode
        script_code = derive_script_code(txi, pubkeyhash)
        write_bytes_prefixed(h_preimage, script_code)

        # 10c / 13c. value
        write_uint64(h_preimage, txi.amount)

        # 10d / 13d. nSequence
        write_uint32(h_preimage, txi.sequence)

        return get_tx_hash(h_preimage)
Beispiel #20
0
 async def process_bip143_input(self, txi: TxInputType) -> None:
     if not txi.amount:
         raise SigningError(FailureType.DataError, "Expected input with amount")
     self.bip143_in += txi.amount
     self.total_in += txi.amount
Beispiel #21
0
 async def process_segwit_input(self, txi: TxInputType) -> None:
     if not self.coin.segwit:
         raise SigningError(FailureType.DataError,
                            "Segwit not enabled on this coin")
     await super().process_segwit_input(txi)