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