feat(core): add mock RNGs for MCU, Optiga and Tropic
What changed, and why it matters
This commit adds deterministic (predictable) fake random-number generators for Trezor's software emulator only, so automated tests can reproduce exact outputs. It removes the old insecure-prng feature flag from production build paths and gates the new mocks behind emulator-only compile checks. The change is designed to keep fake randomness out of real hardware wallets, but it still increases the amount of insecure-prng code in the repository.
Verify that no production device target can compile the new mock files, that CI still rejects USE_INSECURE_PRNG in release builds, and that the removed insecure_prng feature is not silently reintroduced. Review whether the deleted production-build bail-out in add_insecure_prng needs a replacement guard elsewhere.
Security signals we found
Adds deterministic / predictable RNG implementations (insecure by design)
Removes the insecure_prng feature flag and the production-build bail-out that rejected it
Gates mock RNG code behind TREZOR_EMULATOR and USE_INSECURE_PRNG macros with multiple compile-time safety checks
Replaces centralized insecure PRNG with per-source mock streams so tests can verify each entropy source independently
No changelog entry ([no changelog])
Evidence from the diff
The patch replaces the previous insecure_prng Cargo feature with emulator-only mock RNG sources for MCU, Optiga and Tropic entropy. It introduces rng_mock_stream_t, a SHA-256-based deterministic stream keyed by a source tag, seed and counter, plus reseed APIs. The old rand_insecure.c source is no longer compiled in. Multiple compile-time guards (#ifndef TREZOR_EMULATOR, _Static_assert(sizeof(void*)==8), hosted-environment checks) try to prevent the mocks from being built for device firmware. Production builds previously bailed if insecure_prng was enabled; that check is removed because the feature itself is removed.
Changed components
core/embed/sys/rng (emulator RNG)core/embed/sec/optiga (emulator Optiga mock)core/embed/sec/tropic (emulator Tropic mock)core/embed/crypto Cargo/build configurationMicroPython trezorcrypto.random.reseed bindingInspect captured patch +325 / −43
### core/embed/crypto/Cargo.toml
@@ -30,12 +30,11 @@ model_t2t1 = ["models/model_t2t1"]
# Selectable features
# --------------------------------------------------------------------------
-emulator = ["models/emulator", "insecure_prng"]
+emulator = ["models/emulator"]
aes_gcm = []
ed25519_no_precomp = []
eos = []
-insecure_prng = []
mldsa = []
nem = []
noise = ["aes_gcm"]
@@ -48,7 +47,6 @@ universal_fw = []
test = [
"aes_gcm",
"emulator",
- "insecure_prng",
"mldsa",
"models/mcu_stm32u5g",
"models/model_t3w1",
### core/embed/crypto/build.rs
@@ -1,6 +1,4 @@
-use std::path::PathBuf;
-
-use xbuild::{CLibrary, CompileAttrs, Result, bail};
+use xbuild::{CLibrary, CompileAttrs, Result};
const CRYPTO_PATH: &str = "../../vendor/trezor-crypto";
const SECP256K1_PATH: &str = "../../vendor/secp256k1-zkp";
@@ -19,10 +17,6 @@ fn main() -> Result<()> {
add_crypto_base(lib, &attrs)?;
- if cfg!(feature = "insecure_prng") {
- add_insecure_prng(lib)?;
- }
-
if cfg!(feature = "aes_gcm") {
add_aes_gcm(lib, &attrs)?;
}
@@ -195,18 +189,6 @@ fn add_crypto_base(lib: &mut CLibrary, common_attrs: &CompileAttrs) -> Result<()
Ok(())
}
-fn add_insecure_prng(lib: &mut CLibrary) -> Result<()> {
- if cfg!(feature = "production") {
- if !xbuild::is_rust_analyzer() {
- bail!("insecure_prng cannot be enabled in production builds");
- }
- }
- lib.add_define("USE_INSECURE_PRNG", Some("1"));
- lib.add_source(PathBuf::from(CRYPTO_PATH).join("rand_insecure.c"));
-
- Ok(())
-}
-
fn add_aes_gcm(lib: &mut CLibrary, attrs: &CompileAttrs) -> Result<()> {
lib.add_defines([("AES_VAR", None), ("USE_AES_GCM", Some("1"))]);
### core/embed/sec/optiga/build.rs
@@ -15,6 +15,7 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
lib.add_sources([
"optiga/unix/optiga_commands.c",
"optiga/unix/optiga_hal.c",
+ "optiga/unix/optiga_mock.c",
"optiga/unix/optiga_transport.c",
"optiga/unix/optiga.c",
]);
### core/embed/sec/optiga/inc/sec/optiga.h
@@ -63,6 +63,10 @@ bool __wur optiga_random_buffer(uint8_t *dest, size_t size);
void optiga_random_buffer_time(uint32_t *time_ms);
+#ifdef TREZOR_EMULATOR
+void optiga_random_reseed(uint32_t seed);
+#endif
+
bool __wur optiga_pin_init(optiga_ui_progress_t ui_progress);
void optiga_pin_init_time(uint32_t *time_ms);
### core/embed/sec/optiga/unix/optiga.c
@@ -22,7 +22,6 @@
#include <sec/optiga.h>
#include <sec/optiga_common.h>
#include <sec/storage.h>
-#include <sys/rng.h>
#include "ecdsa.h"
#include "nist256p1.h"
@@ -117,11 +116,6 @@ uint32_t optiga_estimate_time_ms(storage_pin_op_t op, uint8_t slot_index) {
return 0;
}
-bool optiga_random_buffer(uint8_t *dest, size_t size) {
- rng_fill_buffer(dest, size);
- return true;
-}
-
void optiga_random_buffer_time(uint32_t *time_ms) {}
bool optiga_pin_set(
### core/embed/sec/optiga/unix/optiga_mock.c
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <sec/optiga.h>
+#include <sys/rng_mock.h>
+
+// Guard against this file ever being compiled into a bare-metal build.
+// These checks are intentionally duplicated across all mock RNGs to prevent
+// their accidental removal.
+
+#ifndef TREZOR_EMULATOR
+#error "Mock RNG must not be compiled into a non-emulator build"
+#endif
+
+_Static_assert(sizeof(void*) == 8,
+ "Mock RNG compiled for a 32-bit target -- device build?");
+
+#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
+#error "Insecure PRNG is not supported on this target"
+#endif
+
+#if __STDC_HOSTED__ == 0
+#error "Insecure PRNG must not be compiled for a freestanding target"
+#endif
+
+#ifdef USE_INSECURE_PRNG
+
+// Deterministic, Optiga-unique random stream.
+static rng_mock_stream_t random_stream = {.tag = "<PRNG-Optiga>"};
+
+void optiga_random_reseed(uint32_t seed) {
+ rng_mock_reseed(&random_stream, seed);
+}
+
+bool optiga_random_buffer(uint8_t* dest, size_t size) {
+ rng_mock_fill(&random_stream, dest, size);
+ return true;
+}
+
+#endif // USE_INSECURE_PRNG
### core/embed/sec/tropic/build.rs
@@ -11,7 +11,7 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
let tropic_dir = PathBuf::from("../../vendor/libtropic");
if cfg!(feature = "emulator") {
- lib.add_sources(["tropic/unix/tropic01.c"]);
+ lib.add_sources(["tropic/unix/tropic01.c", "tropic/unix/tropic_mock.c"]);
lib.add_sources_in_dir(&tropic_dir, ["hal/posix/tcp/libtropic_port_posix_tcp.c"]);
} else if cfg!(feature = "mcu_stm32u5") {
### core/embed/sec/tropic/inc/sec/tropic.h
@@ -154,6 +154,11 @@ bool tropic_random_buffer(void* buffer, size_t length);
void tropic_random_buffer_time(uint32_t* time_ms);
+#ifdef TREZOR_EMULATOR
+void tropic_random_reseed(uint32_t seed);
+bool tropic_session_start(void);
+#endif
+
#ifdef USE_STORAGE
void tropic_session_start_time(uint32_t* time_ms);
### core/embed/sec/tropic/tropic.c
@@ -1162,6 +1162,11 @@ void tropic_get_factory_privkey(curve25519_key privkey) {
memcpy(privkey, factory_private, sizeof(curve25519_key));
}
+// Gated on the same condition as the mock that replaces it, so the two halves
+// of the choice cannot drift apart: with TREZOR_EMULATOR the deterministic
+// stream in tropic/unix/tropic_mock.c provides this function instead.
+#ifndef TREZOR_EMULATOR
+
bool tropic_random_buffer(void *buffer, size_t length) {
tropic_driver_t *drv = &g_tropic_driver;
@@ -1185,6 +1190,8 @@ bool tropic_random_buffer(void *buffer, size_t length) {
return true;
}
+#endif // TREZOR_EMULATOR
+
void tropic_random_buffer_time(uint32_t *time_ms) {
// Assuming the data size is 32 bytes
*time_ms += 10;
### core/embed/sec/tropic/unix/tropic_mock.c
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <sec/tropic.h>
+#include <sys/rng_mock.h>
+
+#ifndef TREZOR_EMULATOR
+#error "Mock RNG must not be compiled into a non-emulator build"
+#endif
+
+// Guard against this file ever being compiled into a bare-metal build.
+// These checks are intentionally duplicated across all mock RNGs to prevent
+// their accidental removal.
+
+_Static_assert(sizeof(void*) == 8,
+ "Mock RNG compiled for a 32-bit target -- device build?");
+
+#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
+#error "Insecure PRNG is not supported on this target"
+#endif
+
+#if __STDC_HOSTED__ == 0
+#error "Insecure PRNG must not be compiled for a freestanding target"
+#endif
+
+#ifdef USE_INSECURE_PRNG
+
+// Deterministic, Tropic-unique random stream.
+static rng_mock_stream_t random_stream = {.tag = "<PRNG-Tropic>"};
+
+void tropic_random_reseed(uint32_t seed) {
+ rng_mock_reseed(&random_stream, seed);
+}
+
+bool tropic_random_buffer(void* buffer, size_t length) {
+ // Return false if the Tropic session cannot start, matching the real
+ // implementation.
+ if (!tropic_session_start()) {
+ return false;
+ }
+
+ rng_mock_fill(&random_stream, (uint8_t*)buffer, length);
+ return true;
+}
+
+#endif // USE_INSECURE_PRNG
### core/embed/sys/rng/build.rs
@@ -4,7 +4,9 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
lib.add_include("rng/inc");
if cfg!(feature = "emulator") {
- lib.add_source("rng/unix/rng.c");
+ lib.add_define("USE_INSECURE_PRNG", Some("1"));
+
+ lib.add_sources(["rng/unix/rng.c", "rng/unix/rng_mock.c"]);
} else if cfg!(feature = "mcu_stm32") {
lib.add_source("rng/stm32/rng.c");
} else {
### core/embed/sys/rng/inc/sys/rng.h
@@ -31,6 +31,16 @@ void rng_init(void);
#endif
+#ifdef TREZOR_EMULATOR
+/**
+ * @brief Reseeds the deterministic random number
+ * generator used in the emulator.
+ *
+ * @param seed The seed to use for reseeding.
+ */
+void rng_reseed(uint32_t seed);
+#endif
+
/**
* @brief Fills a buffer with random bytes using the hardware RNG
*
### core/embed/sys/rng/inc/sys/rng_mock.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifdef TREZOR_EMULATOR
+
+#include <trezor_types.h>
+
+/**
+ * Deterministic, source-unique random stream for emulated entropy sources.
+ *
+ * On real hardware each entropy source of rng_fill_buffer_strong() is an
+ * independent chip. The emulator mirrors that with one deterministic stream
+ * per source, so tests can verify every source's contribution to the strong
+ * RNG output.
+ *
+ * The stream is SHA256(tag || seed || counter) consumed in 32-byte blocks,
+ * with the diversification tag naming the source.
+ */
+typedef struct {
+ const char* tag; // diversification string, e.g. "Optiga"
+ uint32_t seed; // set by rng_mock_reseed()
+ uint32_t counter; // advances by one per 32-byte block
+} rng_mock_stream_t;
+
+/**
+ * @brief Resets the stream to the beginning of the sequence for `seed`.
+ */
+void rng_mock_reseed(rng_mock_stream_t* stream, uint32_t seed);
+
+/**
+ * @brief Fills a buffer from the stream.
+ */
+void rng_mock_fill(rng_mock_stream_t* stream, uint8_t* dest, size_t size);
+
+#endif // TREZOR_EMULATOR
### core/embed/sys/rng/unix/rng.c
@@ -20,24 +20,42 @@
#include <trezor_rtl.h>
#include <sys/rng.h>
+#include <sys/rng_mock.h>
#include "rand.h"
-void rng_fill_buffer(void* buffer, size_t buffer_size) {
-#ifdef USE_INSECURE_PRNG
+// Guard against this file ever being compiled into a bare-metal build.
+// These checks are intentionally duplicated across all mock RNGs to prevent
+// their accidental removal.
- // Use PRNG implemented in crypto/rand_insecure.c
- random_buffer((uint8_t*)buffer, buffer_size);
+#ifndef TREZOR_EMULATOR
+#error "Mock RNG must not be compiled into a non-emulator build"
+#endif
-#else
+_Static_assert(sizeof(void*) == 8,
+ "Mock RNG compiled for a 32-bit target -- device build?");
- static FILE* frand = NULL;
- if (!frand) {
- frand = fopen("/dev/urandom", "r");
- }
- ensure(sectrue * (frand != NULL), "fopen failed");
- ensure(sectrue * (buffer_size == fread(buffer, 1, buffer_size, frand)),
- "fread failed");
+#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
+#error "Insecure PRNG is not supported on this target"
+#endif
+#if __STDC_HOSTED__ == 0
+#error "Insecure PRNG must not be compiled for a freestanding target"
#endif
+
+#ifdef USE_INSECURE_PRNG
+
+// Deterministic, MCU-unique random stream.
+static rng_mock_stream_t random_stream = {.tag = "<PRNG-MCU>"};
+
+void rng_reseed(uint32_t seed) { rng_mock_reseed(&random_stream, seed); }
+
+void rng_fill_buffer(void* buffer, size_t buffer_size) {
+ rng_mock_fill(&random_stream, (uint8_t*)buffer, buffer_size);
}
+
+// Implements random_buffer() function declared in crypto/rand.h
+// as a wrapper for rng_fill_buffer().
+void random_buffer(uint8_t* buf, size_t len) { rng_fill_buffer(buf, len); }
+
+#endif // USE_INSECURE_PRNG
### core/embed/sys/rng/unix/rng_mock.c
@@ -0,0 +1,71 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <sys/rng_mock.h>
+
+#include "sha2.h"
+
+// Guard against this file ever being compiled into a bare-metal build.
+// These checks are intentionally duplicated across all mock RNGs to prevent
+// their accidental removal.
+
+#ifndef TREZOR_EMULATOR
+#error "Mock RNG must not be compiled into a non-emulator build"
+#endif
+
+_Static_assert(sizeof(void*) == 8,
+ "Mock RNG compiled for a 32-bit target -- device build?");
+
+#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
+#error "Insecure PRNG is not supported on this target"
+#endif
+
+#if __STDC_HOSTED__ == 0
+#error "Insecure PRNG must not be compiled for a freestanding target"
+#endif
+
+#ifdef USE_INSECURE_PRNG
+
+void rng_mock_reseed(rng_mock_stream_t* stream, uint32_t seed) {
+ stream->seed = seed;
+ stream->counter = 0;
+}
+
+void rng_mock_fill(rng_mock_stream_t* stream, uint8_t* dest, size_t size) {
+ while (size > 0) {
+ uint8_t block[SHA256_DIGEST_LENGTH] = {0};
+ SHA256_CTX ctx = {0};
+ sha256_Init(&ctx);
+ sha256_Update(&ctx, (const uint8_t*)stream->tag, strlen(stream->tag));
+ sha256_Update(&ctx, (const uint8_t*)&stream->seed, sizeof(stream->seed));
+ sha256_Update(&ctx, (const uint8_t*)&stream->counter,
+ sizeof(stream->counter));
+ sha256_Final(&ctx, block);
+ stream->counter++;
+
+ size_t chunk = MIN(size, sizeof(block));
+ memcpy(dest, block, chunk);
+ dest += chunk;
+ size -= chunk;
+ }
+}
+
+#endif // USE_INSECURE_PRNG
### core/embed/upymod/modtrezorcrypto/modtrezorcrypto-random.h
@@ -92,12 +92,27 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_random_shuffle_obj,
mod_trezorcrypto_random_shuffle);
#ifdef TREZOR_EMULATOR
+
+#if USE_OPTIGA
+#include <sec/optiga.h>
+#endif
+#if USE_TROPIC
+#include <sec/tropic.h>
+#endif
+
/// def reseed(value: int) -> None:
/// """
/// Re-seed the RNG with given value.
/// """
static mp_obj_t mod_trezorcrypto_random_reseed(mp_obj_t data) {
- random_reseed(trezor_obj_get_uint(data));
+ uint32_t seed = trezor_obj_get_uint(data);
+ rng_reseed(seed);
+#if USE_OPTIGA
+ optiga_random_reseed(seed);
+#endif
+#if USE_TROPIC
+ tropic_random_reseed(seed);
+#endif
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_random_reseed_obj,Why this scored 21/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.