SFT-8161: honour USE_BIP39_GENERATE
What changed, and why it matters
This commit removes an unused function called `trezorcrypto.bip39.generate()` from the firmware's exposed programming interface. The function was supposed to be disabled by a build flag (`USE_BIP39_GENERATE=0`) but the flag was being ignored, so the function was still visible. Passport does not actually use this function to create wallet seeds—it creates seeds through a different path—so the change is mostly cleanup and defense in depth. A new test checks that the function stays removed and that the rest of the crypto API still works.
No immediate action required. Treat as a hardening/cleanup change. Reviewers may want to confirm that no other build flags in the vendored trezor-firmware tree are similarly ignored, and that the new regression test runs in CI.
Security signals we found
Build flag was not being honored, leaving an unused cryptographic API exposed
Change reduces firmware API surface by removing a seed-generation binding
Regression test added to prevent accidental re-exposure
Comments clarify randomness source separation between routine crypto and seed generation
Evidence from the diff
The patch guards mod_trezorcrypto_bip39_generate() and its MicroPython binding with #if !defined(USE_BIP39_GENERATE) || USE_BIP39_GENERATE. Because py.mk defines -DUSE_BIP39_GENERATE=0, the function and its MP_QSTR_generate entry are now excluded from the trezorcrypto.bip39 module in Passport builds. A regression test (crypto_api_surface.py) asserts that trezorcrypto.bip39.generate is absent, that expected functions remain, and that from_data, random.bytes, and random.shuffle still behave. The noise.c change adds comments documenting which callers rely on random32()/random_buffer() and clarifies that seed generation uses noise_get_random_bytes() instead.
Changed components
extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.hports/stm32/boards/Passport/modules/tests/test_unit.pyports/stm32/boards/Passport/modules/tests/unit/crypto_api_surface.pyports/stm32/boards/Passport/noise.cInspect captured patch +50 / −0
### extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
@@ -60,6 +60,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(
mod_trezorcrypto_bip39_word_completion_mask_obj,
mod_trezorcrypto_bip39_word_completion_mask);
+// py.mk asks for this to be off with -DUSE_BIP39_GENERATE=0, and nothing here read
+// the flag. Passport generates seeds in new_seed_task rather than through this.
+// Default to on where the flag is not set, so upstream builds are unaffected.
+#if !defined(USE_BIP39_GENERATE) || USE_BIP39_GENERATE
/// def generate(strength: int) -> str:
/// """
/// Generate a mnemonic of given strength (128, 160, 192, 224 and 256 bits).
@@ -79,6 +83,7 @@ STATIC mp_obj_t mod_trezorcrypto_bip39_generate(mp_obj_t strength) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_bip39_generate_obj,
mod_trezorcrypto_bip39_generate);
+#endif
/// def from_data(data: bytes) -> str:
/// """
@@ -177,8 +182,10 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_bip39_globals_table[] = {
MP_ROM_PTR(&mod_trezorcrypto_bip39_complete_word_obj)},
{MP_ROM_QSTR(MP_QSTR_word_completion_mask),
MP_ROM_PTR(&mod_trezorcrypto_bip39_word_completion_mask_obj)},
+#if !defined(USE_BIP39_GENERATE) || USE_BIP39_GENERATE
{MP_ROM_QSTR(MP_QSTR_generate),
MP_ROM_PTR(&mod_trezorcrypto_bip39_generate_obj)},
+#endif
{MP_ROM_QSTR(MP_QSTR_from_data),
MP_ROM_PTR(&mod_trezorcrypto_bip39_from_data_obj)},
{MP_ROM_QSTR(MP_QSTR_check), MP_ROM_PTR(&mod_trezorcrypto_bip39_check_obj)},
### ports/stm32/boards/Passport/modules/tests/test_unit.py
@@ -28,6 +28,10 @@ def test_passphrase_length(test):
assert test('passphrase_length.py') == b'OK'
+def test_crypto_api_surface(test):
+ assert test('crypto_api_surface.py') == b'OK'
+
+
def test_psbt_multisig_approval(test):
assert test('psbt_multisig_approval.py') == b'OK'
### ports/stm32/boards/Passport/modules/tests/unit/crypto_api_surface.py
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: © 2026 Foundation Devices, Inc. <hello@foundation.xyz>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# py.mk sets -DUSE_BIP39_GENERATE=0, so bip39.generate() is not part of the API
+# surface. Passport generates seeds in new_seed_task rather than through it.
+
+import trezorcrypto
+
+assert not hasattr(trezorcrypto.bip39, 'generate'), \
+ 'bip39.generate() is exposed again, and USE_BIP39_GENERATE asks for it to be off'
+
+# Everything else the seed flows use is still there.
+for name in ('from_data', 'seed', 'check', 'find_word', 'complete_word',
+ 'word_completion_mask', 'get_word'):
+ assert hasattr(trezorcrypto.bip39, name), 'bip39.{} went missing'.format(name)
+
+# from_data() takes bytes the caller chose, so it stays: new_seed_task hands it the
+# result of its own noise request. A known vector, to show it still works.
+assert trezorcrypto.bip39.from_data(bytes(16)) == \
+ 'abandon abandon abandon abandon abandon abandon abandon abandon ' \
+ 'abandon abandon abandon about'
+assert trezorcrypto.bip39.check(trezorcrypto.bip39.from_data(bytes(32)))
+
+# ext_settings.py relies on both of these for wear levelling and padding.
+assert len(trezorcrypto.random.bytes(256)) == 256
+
+options = list(range(16))
+trezorcrypto.random.shuffle(options)
+assert sorted(options) == list(range(16))
+
+return_value.write(b'OK')
### ports/stm32/boards/Passport/noise.c
@@ -166,6 +166,14 @@ bool noise_get_random_bytes(uint8_t sources, void* buf, size_t buf_len) {
}
// trezor-firmware randomness functions
+//
+// These back trezor-crypto's internal randomness, where an avalanche sample per
+// call would be paid on every operation. Their consumers are blinding and nonce
+// material in ecdsa.c and zkp_context.c, and trezorcrypto.random, which
+// ext_settings.py uses for wear levelling and deniability padding.
+//
+// Secret generation is not one of them - see new_seed_task, which asks
+// noise_get_random_bytes() for every source.
void random_reseed(const uint32_t value) {
(void)value;Why this scored 26/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.