What changed, and why it matters
This commit adds a new feature to Trezor hardware wallets that lets users confirm on the device when they are buying cryptocurrency with regular money (fiat) through a third-party service. It changes how payment requests are verified and displayed on the device screen. The change removes the requirement that a payment amount be included in the request for these fiat-to-crypto purchases, because the amount being spent is in regular currency rather than cryptocurrency.
No immediate action required. This appears to be a feature addition. A normal security review should verify that removing the amount requirement does not weaken verification for non-fiat payment requests, that the SLIP-0024 keychain derivation is correct, and that the confirm_payment_request UI cannot be bypassed or confused by crafted memos.
Security signals we found
Payment request signature verification is still performed before user confirmation
Amount field is explicitly required to be absent for fiat purchase flow, preventing amount mismatch attacks in this path
User-facing confirmation screen is added for fiat-to-crypto purchases
SLIP-0024 keychain is now obtained explicitly rather than via decorator
Refund memos are intentionally not processed for this fiat purchase flow
Evidence from the diff
The commit modifies the PaymentRequest protobuf definition and the payment_notification handler in Trezor firmware. It removes the @with_slip44_keychain decorator and instead obtains a SLIP-0024 keychain explicitly. It adds validation that payment_req.amount must be None for fiat purchases, then verifies the payment request signature. It parses memos (text, text details, and coin purchase) and renders a confirmation screen via confirm_payment_request(). The amount field comment is updated to clarify it should not be set for fiat amounts.
Changed components
core/src/apps/misc/payment_notification.pycommon/protob/messages-common.protoPaymentRequest message definitionPaymentNotification handlerInspect captured patch +50 / −7
diff --git a/common/protob/messages-common.proto b/common/protob/messages-common.proto
index 1777b3c44..a3a2be1b4 100644
--- a/common/protob/messages-common.proto
+++ b/common/protob/messages-common.proto
@@ -185,7 +185,8 @@ message PaymentRequest {
required string recipient_name = 2; // merchant's name
repeated PaymentRequestMemo memos = 3; // any memos that were signed as part of the request
reserved 4; // this existed briefly. obsoleted by this change: https://github.com/satoshilabs/slips/commit/08d36aa61722275a21617ac6a713e31ec23fdec4
- optional bytes amount = 6; // the sum of the external output amounts requested, required for non-CoinJoin, encoded in little endian on either 8 or 32 bytes
+ optional bytes amount = 6; // the sum of the external output amounts requested, encoded in little endian on either 8 or 32 bytes
+ // required for non-CoinJoin transactions; do not set this for fiat amounts (BUY crypto with fiat)
required bytes signature = 5; // the trusted party's signature of the paymentRequestDigest
message PaymentRequestMemo {
diff --git a/core/src/apps/misc/payment_notification.py b/core/src/apps/misc/payment_notification.py
index f7cfed7fd..f48ea83fd 100644
--- a/core/src/apps/misc/payment_notification.py
+++ b/core/src/apps/misc/payment_notification.py
@@ -3,23 +3,65 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from trezor.messages import PaymentNotification, Success
- from apps.common.keychain import Keychain
+from apps.common.paths import address_n_to_str
-from apps.common.keychain import with_slip44_keychain
+# this module implements SLIP-0024 payment requests for crypto purchases using fiat
-@with_slip44_keychain(slip44_id=0, slip21_namespaces=[[b"SLIP-0024"]])
-async def payment_notification(msg: PaymentNotification, keychain: Keychain) -> Success:
+async def payment_notification(msg: PaymentNotification) -> Success:
from trezor.messages import Success
+ from trezor.ui.layouts import confirm_payment_request
from trezor.wire import DataError
+ from apps.common.keychain import get_keychain
from apps.common.payment_request import PaymentRequestVerifier
if msg.payment_req is None:
raise DataError("Missing payment request.")
- PaymentRequestVerifier(msg.payment_req, 0, keychain).verify()
+ if msg.payment_req.amount is not None:
+ raise DataError("Payment request amount must be missing")
- # TODO Show payment request memos.
+ slip21_keychain = await get_keychain("", [], [[b"SLIP-0024"]])
+ PaymentRequestVerifier(msg.payment_req, 0, slip21_keychain).verify()
+
+ verified_payment_request = msg.payment_req
+
+ texts: list[tuple[str | None, str]] = []
+ trades: list[tuple[str | None, str, str, str | None, str | None]] = []
+ for memo in verified_payment_request.memos:
+ # Note: we do not process RefundMemo here:
+ # if the swap fails, the fiat amount just remains in your custodial account, it does not get refunded anywhere
+ if memo.text_memo is not None:
+ texts.append((None, memo.text_memo.text))
+ elif memo.text_details_memo is not None:
+ texts.append((memo.text_details_memo.title, memo.text_details_memo.text))
+ elif memo.coin_purchase_memo:
+ coin_purchase_account_path = address_n_to_str(
+ memo.coin_purchase_memo.address_n
+ )
+ trades.append(
+ (
+ None, # if we later decide to somehow pass the fiat amount (and currency!) as part of the payment request in a more structured fashion,
+ # we should include it here so it gets shown on the trade screen, but for now we just have the fiat amount ad-hoc as part of a text memo.
+ f"+\u00a0{memo.coin_purchase_memo.amount}", # amount of crypto purchased
+ memo.coin_purchase_memo.address,
+ None,
+ coin_purchase_account_path,
+ )
+ )
+ else:
+ raise DataError("Unrecognized memo type in payment request memo.")
+
+ await confirm_payment_request(
+ recipient_name=verified_payment_request.recipient_name,
+ recipient_address=None, # no address for the fiat being spent
+ texts=texts,
+ refunds=[],
+ trades=trades,
+ account_items=[],
+ transaction_fee=None,
+ fee_info_items=None,
+ )
return Success()
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.