fix(core): adjust random part of BLE name during pairing
What changed, and why it matters
This commit changes how Trezor T3W1 devices generate their temporary Bluetooth (BLE) name during pairing. Previously the name contained three random characters from a pool of 26 letters plus 10 digits. Now it uses a digit-letter-digit pattern. This reduces the number of possible names from 36×36×36 (about 46,656) to 10×26×10 (2,600), making it easier for a nearby attacker to guess or collide with the advertised name during pairing. The change appears intentional to make the displayed name easier to read or type, but it weakens the randomness that helps distinguish devices.
Review whether 2,600 possible BLE names provides sufficient collision resistance for the intended pairing threat model. If users pair in public or crowded environments, consider restoring higher entropy (e.g., four alphanumeric characters or a larger charset) while still meeting usability goals. Document the security trade-off in the changelog or pairing design docs.
Security signals we found
Reduction in randomness/entropy of a user-facing pairing identifier
BLE pairing identifier now follows a predictable digit-letter-digit structure
No security rationale or threat analysis provided in commit message or changelog
Change touches both bootloader and application-layer pairing code, indicating the identifier is security-relevant across boot stages
Evidence from the diff
The patch modifies BLE pairing name generation in both the bootloader C code and the MicroPython pairing flow. It replaces a uniform 36-character alphanumeric charset (A–Z, 0–9) with a fixed digit-uppercase-digit pattern. In C, get_random_char() is replaced by get_random_from_charset() and called with DIGITS, UPPERCASE, DIGITS. In Python, the same pattern is applied. The effective entropy of the advertised name drops from log2(36^3) ≈ 15.5 bits to log2(10·26·10) ≈ 11.3 bits. The commit message and changelog frames this only as an ‘adjustment’ with no security discussion.
Changed components
core/embed/projects/bootloader/wire/wire_iface_ble.ccore/src/apps/management/ble/pair_new_device.pyInspect captured patch +28 / −9
diff --git a/core/.changelog.d/6019.fixed b/core/.changelog.d/6019.fixed
new file mode 100644
index 00000000..af03054a
--- /dev/null
+++ b/core/.changelog.d/6019.fixed
@@ -0,0 +1 @@
+[T3W1] Adjust random part of BLE device name during pairing.
diff --git a/core/embed/projects/bootloader/.changelog.d/6019.fixed b/core/embed/projects/bootloader/.changelog.d/6019.fixed
new file mode 100644
index 00000000..af03054a
--- /dev/null
+++ b/core/embed/projects/bootloader/.changelog.d/6019.fixed
@@ -0,0 +1 @@
+[T3W1] Adjust random part of BLE device name during pairing.
diff --git a/core/embed/projects/bootloader/wire/wire_iface_ble.c b/core/embed/projects/bootloader/wire/wire_iface_ble.c
index 4cdeadcd..6e017eea 100644
--- a/core/embed/projects/bootloader/wire/wire_iface_ble.c
+++ b/core/embed/projects/bootloader/wire/wire_iface_ble.c
@@ -151,11 +151,14 @@ void ble_iface_end_pairing(void) {
}
}
-char get_random_char(void) {
- static const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- const size_t max_index = sizeof(charset) - 1; // exclude terminating '\0'
- uint32_t key = rng_get() % max_index;
- return charset[key];
+static char get_random_from_charset(const char* charset) {
+ const size_t max_index = strlen(charset);
+
+ if (max_index == 0) {
+ return '\0';
+ }
+
+ return charset[rng_get() % max_index];
}
bool ble_iface_start_pairing(void) {
@@ -165,9 +168,15 @@ bool ble_iface_start_pairing(void) {
uint16_t retry_cnt = 0;
+ static const char DIGITS[] = "0123456789";
+ static const char UPPERCASE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
char adv_name[BLE_ADV_NAME_LEN];
mini_snprintf(adv_name, sizeof(adv_name), "%s (%c%c%c)", MODEL_FULL_NAME,
- get_random_char(), get_random_char(), get_random_char());
+ get_random_from_charset(DIGITS),
+ get_random_from_charset(UPPERCASE),
+ get_random_from_charset(DIGITS));
+
if (!ble_enter_pairing_mode((const uint8_t*)adv_name,
strnlen(adv_name, BLE_ADV_NAME_LEN))) {
return false;
diff --git a/core/src/apps/management/ble/pair_new_device.py b/core/src/apps/management/ble/pair_new_device.py
index 9914db52..d4f3da1a 100644
--- a/core/src/apps/management/ble/pair_new_device.py
+++ b/core/src/apps/management/ble/pair_new_device.py
@@ -8,7 +8,7 @@ from trezor.wire import ActionCancelled
def _default_ble_name() -> str:
- """Return model name and three random letters.
+ """Return model name and three random characters.
>>> n1 = _default_ble_name()
>>> n1.startswith(utils.MODE_FULL_NAME)
@@ -19,8 +19,16 @@ def _default_ble_name() -> str:
>>> n1 == n2
False
"""
- charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- random_chars = "".join(charset[random.uniform(len(charset))] for _ in range(3))
+ digits = "0123456789"
+ uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ random_chars = "".join(
+ [
+ digits[random.uniform(len(digits))],
+ uppercase[random.uniform(len(uppercase))],
+ digits[random.uniform(len(digits))],
+ ]
+ )
+
return f"{utils.MODEL_FULL_NAME} ({random_chars})"
Why this scored 23/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.