Ejemplo n.º 1
0
async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin,
                             keychain: Keychain, coin: CoinInfo) -> Success:
    if len(msg.coordinator) > _MAX_COORDINATOR_LEN or not all(
            32 <= ord(x) <= 126 for x in msg.coordinator):
        raise wire.DataError("Invalid coordinator name.")

    if msg.max_rounds > _MAX_ROUNDS and safety_checks.is_strict():
        raise wire.DataError("The number of rounds is unexpectedly large.")

    if (msg.max_coordinator_fee_rate > _MAX_COORDINATOR_FEE_RATE
            and safety_checks.is_strict()):
        raise wire.DataError(
            "The coordination fee rate is unexpectedly large.")

    if msg.max_fee_per_kvbyte > 10 * coin.maxfee_kb and safety_checks.is_strict(
    ):
        raise wire.DataError("The fee per vbyte is unexpectedly large.")

    if not msg.address_n:
        raise wire.DataError("Empty path not allowed.")

    await confirm_action(
        ctx,
        "coinjoin_coordinator",
        title="Authorize CoinJoin",
        description=
        "Do you really want to take part in a CoinJoin transaction at:\n{}",
        description_param=msg.coordinator,
        description_param_font=ui.MONO,
        icon=ui.ICON_RECOVERY,
    )

    max_fee_per_vbyte = format_amount(msg.max_fee_per_kvbyte, 3)
    await confirm_coinjoin(ctx, coin.coin_name, msg.max_rounds,
                           max_fee_per_vbyte)

    validation_path = msg.address_n + [0] * BIP32_WALLET_DEPTH
    await validate_path(
        ctx,
        keychain,
        validation_path,
        validate_path_against_script_type(coin,
                                          address_n=validation_path,
                                          script_type=msg.script_type),
    )

    if msg.max_fee_per_kvbyte > coin.maxfee_kb:
        await confirm_metadata(
            ctx,
            "fee_over_threshold",
            "High mining fee",
            "The mining fee of\n{} sats/vbyte\nis unexpectedly high.",
            max_fee_per_vbyte,
            ButtonRequestType.FeeOverThreshold,
        )

    authorization.set(msg)

    return Success(message="CoinJoin authorized")
Ejemplo n.º 2
0
    async def approve_tx(self) -> None:
        fee = self.total_in - self.total_out

        # some coins require negative fees for reward TX
        if fee < 0 and not self.coin.negative_fee:
            raise wire.NotEnoughFunds("Not enough funds")

        total = self.total_in - self.change_out
        spending = total - self.external_in
        # fee_threshold = (coin.maxfee per byte * tx size)
        fee_threshold = (self.coin.maxfee_kb /
                         1000) * (self.weight.get_total() / 4)

        # fee > (coin.maxfee per byte * tx size)
        if fee > fee_threshold:
            if fee > 10 * fee_threshold and safety_checks.is_strict():
                raise wire.DataError("The fee is unexpectedly large")
            await helpers.confirm_feeoverthreshold(fee, self.coin)
        if self.change_count > self.MAX_SILENT_CHANGE_COUNT:
            await helpers.confirm_change_count_over_threshold(self.change_count
                                                              )
        if self.tx.lock_time > 0:
            lock_time_disabled = self.min_sequence == _SEQUENCE_FINAL
            await helpers.confirm_nondefault_locktime(self.tx.lock_time,
                                                      lock_time_disabled)
        if not self.external_in:
            await helpers.confirm_total(total, fee, self.coin)
        else:
            await helpers.confirm_joint_total(spending, total, self.coin)
Ejemplo n.º 3
0
async def _fail_or_warn_path(
    ctx: wire.Context, path: list[int], path_name: str
) -> None:
    if safety_checks.is_strict():
        raise wire.DataError(f"Invalid {path_name.lower()}")
    else:
        await show_warning_path(ctx, path, path_name)
Ejemplo n.º 4
0
async def _fail_or_warn_if_invalid_path(ctx: wire.Context, schema: PathSchema,
                                        path: List[int],
                                        path_name: str) -> None:
    if not schema.match(path):
        if safety_checks.is_strict():
            raise wire.DataError("Invalid %s" % path_name.lower())
        else:
            await show_warning_path(ctx, path, path_name)
Ejemplo n.º 5
0
def _fail_if_strict_and_unusual(
    address_parameters: CardanoAddressParametersType, ) -> None:
    if not safety_checks.is_strict():
        return

    if Credential.payment_credential(address_parameters).is_unusual_path:
        raise wire.DataError(f"Invalid {CHANGE_OUTPUT_PATH_NAME.lower()}")

    if Credential.stake_credential(address_parameters).is_unusual_path:
        raise wire.DataError(
            f"Invalid {CHANGE_OUTPUT_STAKING_PATH_NAME.lower()}")
Ejemplo n.º 6
0
    def add_external_input(self, txi: TxInput) -> None:
        self.weight.add_input(txi)
        self.total_in += txi.amount
        if txi.orig_hash:
            self.orig_total_in += txi.amount

        if input_is_external_unverified(txi):
            if safety_checks.is_strict():
                raise wire.ProcessError("Unverifiable external input.")
        else:
            self.external_in += txi.amount
            if txi.orig_hash:
                self.orig_external_in += txi.amount
Ejemplo n.º 7
0
    async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> None:
        fee = self.total_in - self.total_out

        # some coins require negative fees for reward TX
        if fee < 0 and not self.coin.negative_fee:
            raise wire.NotEnoughFunds("Not enough funds")

        total = self.total_in - self.change_out
        spending = total - self.external_in
        # fee_threshold = (coin.maxfee per byte * tx size)
        fee_threshold = (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4)

        # fee > (coin.maxfee per byte * tx size)
        if fee > fee_threshold:
            if fee > 10 * fee_threshold and safety_checks.is_strict():
                raise wire.DataError("The fee is unexpectedly large")
            await helpers.confirm_feeoverthreshold(fee, self.coin)

        if self.change_count > self.MAX_SILENT_CHANGE_COUNT:
            await helpers.confirm_change_count_over_threshold(self.change_count)

        if orig_txs:
            # Replacement transaction.
            orig_spending = (
                self.orig_total_in - self.orig_change_out - self.orig_external_in
            )
            orig_fee = self.orig_total_in - self.orig_total_out

            # Replacement transactions are only allowed to make amendments which
            # do not increase the amount that we are spending on external outputs.
            # In other words, the total amount being sent out of the wallet must
            # not increase by more than the fee difference (so additional funds
            # can only go towards the fee, which is confirmed by the user).
            if spending - orig_spending > fee - orig_fee:
                raise wire.ProcessError("Invalid replacement transaction.")

            # Replacement transactions must not change the effective nLockTime.
            lock_time = 0 if tx_info.lock_time_disabled() else tx_info.tx.lock_time
            for orig in orig_txs:
                orig_lock_time = 0 if orig.lock_time_disabled() else orig.tx.lock_time
                if lock_time != orig_lock_time:
                    raise wire.ProcessError(
                        "Original transactions must have same effective nLockTime as replacement transaction."
                    )

            if self.external_in > self.orig_external_in:
                description = "PayJoin"
            elif tx_info.rbf_disabled() and any(
                not orig.rbf_disabled() for orig in orig_txs
            ):
                description = "Finalize transaction"
            elif len(orig_txs) > 1:
                description = "Transaction meld"
            else:
                description = "Fee modification"

            for orig in orig_txs:
                await helpers.confirm_replacement(description, orig.orig_hash)

            # Always ask the user to confirm when they are paying more towards the fee.
            # If they are not spending more, then ask for confirmation only if it's not
            # a PayJoin. In complex scenarios where the user is not spending more and
            # there are new external inputs the scenario can be open to multiple
            # interpretations and the dialog would likely cause more confusion than
            # what it's worth, see PR #1292.
            if spending > orig_spending or self.external_in == self.orig_external_in:
                await helpers.confirm_modify_fee(
                    spending - orig_spending, fee, self.coin
                )
        else:
            # Standard transaction.
            if tx_info.tx.lock_time > 0:
                await helpers.confirm_nondefault_locktime(
                    tx_info.tx.lock_time, tx_info.lock_time_disabled()
                )

            if not self.external_in:
                await helpers.confirm_total(total, fee, self.coin)
            else:
                await helpers.confirm_joint_total(spending, total, self.coin)
Ejemplo n.º 8
0
    async def approve_tx(self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]) -> None:
        fee = self.total_in - self.total_out

        # some coins require negative fees for reward TX
        if fee < 0 and not self.coin.negative_fee:
            raise wire.NotEnoughFunds("Not enough funds")

        total = self.total_in - self.change_out
        spending = total - self.external_in
        # fee_threshold = (coin.maxfee per byte * tx size)
        fee_threshold = (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4)

        # fee > (coin.maxfee per byte * tx size)
        if fee > fee_threshold:
            if fee > 10 * fee_threshold and safety_checks.is_strict():
                raise wire.DataError("The fee is unexpectedly large")
            await helpers.confirm_feeoverthreshold(fee, self.coin, self.amount_unit)

        if self.change_count > self.MAX_SILENT_CHANGE_COUNT:
            await helpers.confirm_change_count_over_threshold(self.change_count)

        if orig_txs:
            # Replacement transaction.
            orig_spending = (
                self.orig_total_in - self.orig_change_out - self.orig_external_in
            )
            orig_fee = self.orig_total_in - self.orig_total_out

            if fee < 0 or orig_fee < 0:
                raise wire.ProcessError(
                    "Negative fees not supported in transaction replacement."
                )

            # Replacement transactions are only allowed to make amendments which
            # do not increase the amount that we are spending on external outputs.
            # In other words, the total amount being sent out of the wallet must
            # not increase by more than the fee difference (so additional funds
            # can only go towards the fee, which is confirmed by the user).
            if spending - orig_spending > fee - orig_fee:
                raise wire.ProcessError("Invalid replacement transaction.")

            # Replacement transactions must not change the effective nLockTime.
            lock_time = 0 if tx_info.lock_time_disabled() else tx_info.tx.lock_time
            for orig in orig_txs:
                orig_lock_time = 0 if orig.lock_time_disabled() else orig.tx.lock_time
                if lock_time != orig_lock_time:
                    raise wire.ProcessError(
                        "Original transactions must have same effective nLockTime as replacement transaction."
                    )

            if not self.is_payjoin():
                # Not a PayJoin: Show the actual fee difference, since any difference in the fee is
                # coming entirely from the user's own funds and from decreases of external outputs.
                # We consider the decreases as belonging to the user.
                await helpers.confirm_modify_fee(
                    fee - orig_fee, fee, self.coin, self.amount_unit
                )
            elif spending > orig_spending:
                # PayJoin and user is spending more: Show the increase in the user's contribution
                # to the fee, ignoring any contribution from external inputs. Decreasing of
                # external outputs is not allowed in PayJoin, so there is no need to handle those.
                await helpers.confirm_modify_fee(
                    spending - orig_spending, fee, self.coin, self.amount_unit
                )
            else:
                # PayJoin and user is not spending more: When new external inputs are involved and
                # the user is paying less, the scenario can be open to multiple interpretations and
                # the dialog would likely cause more confusion than what it's worth, see PR #1292.
                pass
        else:
            # Standard transaction.
            if tx_info.tx.lock_time > 0:
                await helpers.confirm_nondefault_locktime(
                    tx_info.tx.lock_time, tx_info.lock_time_disabled()
                )

            if not self.external_in:
                await helpers.confirm_total(total, fee, self.coin, self.amount_unit)
            else:
                await helpers.confirm_joint_total(
                    spending, total, self.coin, self.amount_unit
                )
Ejemplo n.º 9
0
 async def _fail_or_warn_path(self, path: list[int],
                              path_name: str) -> None:
     if safety_checks.is_strict():
         raise wire.DataError(f"Invalid {path_name.lower()}")
     else:
         await layout.warn_path(self.ctx, path, path_name)