Merge pull request #701 from Foundation-Devices/SFT-8161-use-bip39-generate
What changed, and why it matters
This commit removes the `bip39.generate()` function from the firmware's exposed programming interface when a build flag called `USE_BIP39_GENERATE` is set to 0. Passport already creates wallet seed phrases through a different internal path that uses its own noise/randomness source, so this change simply hides an unused function and adds a test to make sure it stays hidden. It is a defensive hardening change, not a fix for an active bug or exploit.
No immediate action required. Treat as routine hardening. Reviewers may want to confirm that no other code paths call `trezorcrypto.bip39.generate()` and that the build flag is consistently applied across release configurations.
Security signals we found
Reduction of firmware API surface for unused secret-generation function
Defensive build-time gating with preprocessor flag
Regression test verifying absence of a sensitive function
Documentation of randomness-source separation between routine crypto and seed generation
Evidence from the diff
The merge wraps mod_trezorcrypto_bip39_generate() and its MicroPython binding in #if !defined(USE_BIP39_GENERATE) || USE_BIP39_GENERATE. Because Passport’s build (py.mk) passes -DUSE_BIP39_GENERATE=0, trezorcrypto.bip39.generate() is no longer compiled into the API surface. A regression test (crypto_api_surface.py) asserts that generate is absent while the remaining BIP-39 functions (from_data, seed, check, etc.) and trezorcrypto.random remain present. A comment in noise.c clarifies that the low-level random_reseed/random_uniform helpers feed blinding/nonces and trezorcrypto.random, not secret seed generation, which goes through noise_get_random_bytes() with all sources in new_seed_task.
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 28/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.