refactor: allow payment requests without SLIP44 ID
What changed, and why it matters
This commit changes how Trezor handles payment requests that are not tied to a specific cryptocurrency coin type. Previously, payment notifications used a placeholder coin ID of 0 (Bitcoin). Now they use a new 'undefined' marker (0xFFFFFFFF). The change also adds a safety check so that refund memos cannot be used when no coin type is defined, because Trezor cannot hold coins of an undefined type. This appears to be a defensive refactor rather than a fix for an active vulnerability, but it removes a potentially misleading use of Bitcoin's coin ID in non-payment contexts.
Review whether any other code paths pass a hard-coded or default slip44 value when the coin type is actually unknown, and ensure the SLIP44_ID_UNDEFINED sentinel is consistently used. Consider adding a changelog entry documenting the behavior change for integrators.
Security signals we found
Replaces hard-coded slip44=0 with explicit undefined sentinel in payment notification flow
Adds defensive DataError when refund memo is paired with undefined coin type
Prevents potential semantic confusion between Bitcoin (slip44=0) and coin-agnostic payment notifications
No changelog entry suggests developer does not treat this as a security fix
Evidence from the diff
The patch introduces SLIP44_ID_UNDEFINED (0xFFFFFFFF) in apps/common/payment_request.py and uses it in payment_notification.py instead of hard-coded 0 when verifying PaymentNotification messages. It updates the test helper to serialize None slip44 values as 0xFFFFFFFF. A guard is added in PaymentRequestVerifier so that refund_memo processing raises DataError when slip44_id is undefined, since refund memos imply coin ownership and an undefined coin type has none. The change is labeled ‘refactor’ with ‘[no changelog]’.
Changed components
core/src/apps/common/payment_request.pycore/src/apps/misc/payment_notification.pytests/device_tests/misc/test_msg_paymentnotification.pytests/device_tests/payment_req.pyInspect captured patch +16 / −4
diff --git a/core/src/apps/common/payment_request.py b/core/src/apps/common/payment_request.py
index 7532c52f1..0645d04e2 100644
--- a/core/src/apps/common/payment_request.py
+++ b/core/src/apps/common/payment_request.py
@@ -13,6 +13,8 @@ if TYPE_CHECKING:
from apps.common.keychain import Keychain
+SLIP44_ID_UNDEFINED = const(0xFFFF_FFFF)
+
_MEMO_TYPE_TEXT = const(1)
_MEMO_TYPE_REFUND = const(2)
_MEMO_TYPE_COIN_PURCHASE = const(3)
@@ -123,6 +125,10 @@ class PaymentRequestVerifier:
writers.write_uint32_le(self.h_pr, _MEMO_TYPE_TEXT)
writers.write_bytes_prefixed(self.h_pr, memo.text.encode())
elif m.refund_memo is not None:
+ if slip44_id is SLIP44_ID_UNDEFINED:
+ # Trezor can not hold coins of type SLIP44_ID_UNDEFINED,
+ # so a refund for a payment request with that coin type makes no sense
+ raise DataError("Cannot process refund memo.")
memo = m.refund_memo
# Unlike in a coin purchase memo, the coin type is implied by the payment request.
check_address_mac(
diff --git a/core/src/apps/misc/payment_notification.py b/core/src/apps/misc/payment_notification.py
index 4701d4407..2c837edbc 100644
--- a/core/src/apps/misc/payment_notification.py
+++ b/core/src/apps/misc/payment_notification.py
@@ -15,7 +15,7 @@ async def payment_notification(msg: PaymentNotification) -> Success:
from trezor.wire import DataError
from apps.common.keychain import get_keychain
- from apps.common.payment_request import PaymentRequestVerifier
+ from apps.common.payment_request import SLIP44_ID_UNDEFINED, PaymentRequestVerifier
if msg.payment_req is None:
raise DataError("Missing payment request.")
@@ -24,7 +24,9 @@ async def payment_notification(msg: PaymentNotification) -> Success:
raise DataError("Payment request amount must be missing")
slip21_keychain = await get_keychain("", [], [[b"SLIP-0024"]])
- PaymentRequestVerifier(msg.payment_req, 0, slip21_keychain).verify()
+ PaymentRequestVerifier(
+ msg.payment_req, SLIP44_ID_UNDEFINED, slip21_keychain
+ ).verify()
verified_payment_request = msg.payment_req
diff --git a/tests/device_tests/misc/test_msg_paymentnotification.py b/tests/device_tests/misc/test_msg_paymentnotification.py
index ae6ed5aff..299e05af6 100644
--- a/tests/device_tests/misc/test_msg_paymentnotification.py
+++ b/tests/device_tests/misc/test_msg_paymentnotification.py
@@ -42,7 +42,7 @@ def test_paymentnotification(session: Session):
payment_request = make_payment_request(
session,
recipient_name="trezor.io",
- slip44=0,
+ slip44=None,
outputs=None,
memos=[purchase_memo, text_memo],
nonce=nonce,
diff --git a/tests/device_tests/payment_req.py b/tests/device_tests/payment_req.py
index 08f49043e..a05a6b72b 100644
--- a/tests/device_tests/payment_req.py
+++ b/tests/device_tests/payment_req.py
@@ -8,6 +8,8 @@ from trezorlib.transport.session import Session
from ..common import compact_size
+SLIP44_ID_UNDEFINED = 0xFFFF_FFFF
+
@dataclass
class TextMemo:
@@ -112,7 +114,9 @@ def make_payment_request(
else:
raise ValueError
- h_pr.update(slip44.to_bytes(4, "little"))
+ h_pr.update(
+ (slip44 if slip44 is not None else SLIP44_ID_UNDEFINED).to_bytes(4, "little")
+ )
change_address = iter(change_addresses or [])
h_outputs = sha256()
Why this scored 32/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.