feat(core/prodtest): introduce tropic stress test
What changed, and why it matters
This commit adds a new factory/production-test command called tropic-stress-test. It is meant for manufacturing diagnostics and repeatedly exercises the Tropic secure chip's session, MAC-and-destroy, and signing functions to make sure the chip does not enter an alarm state. There is no indication this change fixes a security bug or introduces a vulnerability; it is a testing feature.
No security action required. Treat as a normal feature addition. If desired, verify the new prodtest command is excluded from release firmware builds and that the moved constants do not expose sensitive configuration.
Security signals we found
New production-test CLI command added
Repeated cryptographic operations on secure element (Tropic)
Constants for mac-and-destroy slots moved to public header
No input sanitization beyond uint32 CLI parsing and slot-count bounds check
No privilege escalation or memory-unsafe patterns visible in diff
Evidence from the diff
The patch introduces prodtest_tropic_stress_test() in core/embed/projects/prodtest/cmd/prodtest_tropic.c and registers the CLI command tropic-stress-test. The function loops over lt_session_start/invalidation, lt_mac_and_destroy on unprivileged slots, and lt_ecc_key_generate/lt_ecc_eddsa_sign/lt_ecc_key_erase. It also moves three TROPIC_MAC_AND_DESTROY slot constants from the private tropic.c implementation file to the public tropic.h header so the prodtest code can reference them. The command is gated behind the prodtest build and CLI, not normal firmware runtime.
Changed components
core/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/projects/prodtest/README.mdcore/embed/sec/tropic/inc/sec/tropic.hcore/embed/sec/tropic/tropic.cInspect captured patch +161 / −5
diff --git a/core/embed/projects/prodtest/.changelog.d/6254.added b/core/embed/projects/prodtest/.changelog.d/6254.added
new file mode 100644
index 000000000..329b84977
--- /dev/null
+++ b/core/embed/projects/prodtest/.changelog.d/6254.added
@@ -0,0 +1 @@
+Add the `tropic-stress-test` command.
diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md
index 072407ad8..24cc5e676 100644
--- a/core/embed/projects/prodtest/README.md
+++ b/core/embed/projects/prodtest/README.md
@@ -1311,6 +1311,10 @@ secure-channel-handshake-2 e08e84b91413ad8f7b07853c8ce4c1b5547a12d9dd65f30e3adaa
OK
```
+### tropic-stress-test
+
+Runs a Tropic stress test that repeatedly calls `lt_session_start()`, `lt_mac_and_destroy()` and `lt_ecc_key_generate()` to test that Tropic doesn't enter alarm mode.
+
### wpc-info
Retrieves detailed information from the wireless power receiver, including chip identification, firmware version, configuration settings, and error status.
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 0adba5725..f865edd7d 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -25,6 +25,7 @@
#include <rtl/cli.h>
#include <sec/tropic.h>
+#include <sys/rng.h>
#include <sys/systick.h>
#include <sec/secret.h>
@@ -1582,6 +1583,149 @@ cleanup:
tropic_deinit();
}
+static void prodtest_tropic_stress_test(cli_t* cli) {
+ if (cli_arg_count(cli) > 4) {
+ cli_error_arg_count(cli);
+ return;
+ }
+
+ uint32_t start_session_iterations = 10;
+ uint32_t mac_and_destroy_slot_count = TROPIC_MAC_AND_DESTROY_SLOTS_COUNT;
+ uint32_t mac_and_destroy_per_slot_iterations = 3;
+ uint32_t signing_iterations = 10;
+
+ if (cli_arg_count(cli) != 0) {
+ if (!cli_arg_uint32(cli, "start-session-iterations",
+ &start_session_iterations)) {
+ cli_error_arg(cli, "Expecting number of start-session iterations.");
+ return;
+ }
+ if (!cli_arg_uint32(cli, "mac-and-destroy-slot-count",
+ &mac_and_destroy_slot_count) ||
+ mac_and_destroy_slot_count > TROPIC_MAC_AND_DESTROY_SLOTS_COUNT) {
+ cli_error_arg(cli,
+ "Expecting number of MAC-and-destroy slots in range 0-%d.",
+ TROPIC_MAC_AND_DESTROY_SLOTS_COUNT);
+ return;
+ }
+ if (!cli_arg_uint32(cli, "mac-and-destroy-per-slot-iterations",
+ &mac_and_destroy_per_slot_iterations)) {
+ cli_error_arg(cli,
+ "Expecting number of MAC-and-destroy iterations per slot.");
+ return;
+ }
+ if (!cli_arg_uint32(cli, "signing-iterations", &signing_iterations)) {
+ cli_error_arg(cli, "Expecting number of signing iterations.");
+ return;
+ }
+ }
+
+ cli_trace(cli, "Start-session iterations: %d", start_session_iterations);
+ cli_trace(cli, "MAC-and-destroy slot count: %d", mac_and_destroy_slot_count);
+ cli_trace(cli, "MAC-and-destroy iterations per slot: %d",
+ mac_and_destroy_per_slot_iterations);
+ cli_trace(cli, "Signing iterations: %d", signing_iterations);
+
+ tropic_handshake_state = TROPIC_HANDSHAKE_STATE_0;
+
+ lt_ret_t res = LT_FAIL;
+ lt_pkey_index_t pairing_key_index = -1;
+
+ // Find an available pairing key
+ for (lt_pkey_index_t i = TROPIC_FACTORY_PAIRING_KEY_SLOT;
+ i <= TROPIC_PRIVILEGED_PAIRING_KEY_SLOT; i++) {
+ res = tropic_custom_session_start(i);
+ if (res == LT_OK) {
+ pairing_key_index = i;
+ break;
+ }
+ if (res != LT_L2_HSK_ERR) {
+ cli_error(
+ cli, CLI_ERROR,
+ "`tropic_custom_session_start() for key %d failed with error '%s'", i,
+ lt_ret_verbose(res));
+ return;
+ }
+ }
+
+ if (pairing_key_index == -1) {
+ cli_error(cli, CLI_ERROR, "No pairing key is available");
+ return;
+ }
+
+ cli_trace(cli, "Established session using pairing key %d", pairing_key_index);
+
+ // Test `lt_session_start()`
+ for (int i = 0; i < start_session_iterations; i++) {
+ res = tropic_session_invalidate();
+ if (res != LT_OK) {
+ cli_error(
+ cli, CLI_ERROR,
+ "`Call #%d of tropic_session_invalidate() failed with error '%s'",
+ i + 1, lt_ret_verbose(res));
+ return;
+ }
+ res = tropic_custom_session_start(pairing_key_index);
+ if (res != LT_OK) {
+ cli_error(cli, CLI_ERROR,
+ "Call #%d of `tropic_custom_session_start()"
+ "failed with error '%s'",
+ i + 1, lt_ret_verbose(res));
+ return;
+ }
+ }
+
+ // Test `lt_mac_and_destroy()`
+ for (int slot_index = TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED;
+ slot_index < TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED +
+ mac_and_destroy_slot_count;
+ slot_index++) {
+ for (int i = 0; i < mac_and_destroy_per_slot_iterations; i++) {
+ uint8_t buffer[TROPIC_MAC_AND_DESTROY_SIZE] = {0};
+ rng_fill_buffer(buffer, sizeof(buffer));
+ res = lt_mac_and_destroy(tropic_get_handle(), slot_index, buffer, buffer);
+ if (res != LT_OK) {
+ cli_error(cli, CLI_ERROR,
+ "Call #%d of `lt_mac_and_destroy()` for slot %d failed "
+ "with error '%s'",
+ i + 1, slot_index, lt_ret_verbose(res));
+ return;
+ }
+ }
+ }
+
+ // Test `lt_ecc_key_generate()`
+ uint8_t message[32] = {0};
+ ed25519_signature signature = {0};
+ lt_ecc_slot_t ecc_slot = TR01_ECC_SLOT_31;
+ res = lt_ecc_key_generate(tropic_get_handle(), ecc_slot, TR01_CURVE_ED25519);
+ if (res != LT_OK) {
+ cli_error(cli, CLI_ERROR, "`lt_ecc_key_generate()` failed with error '%s'",
+ lt_ret_verbose(res));
+ return;
+ }
+ for (int i = 0; i < signing_iterations; i++) {
+ rng_fill_buffer(message, sizeof(message));
+ res = lt_ecc_eddsa_sign(tropic_get_handle(), ecc_slot, message,
+ sizeof(message), signature);
+ if (res != LT_OK) {
+ cli_error(cli, CLI_ERROR,
+ "Call #%d of `lt_ecc_eddsa_sign()` failed with error '%s'",
+ i + 1, lt_ret_verbose(res));
+ lt_ecc_key_erase(tropic_get_handle(), ecc_slot);
+ return;
+ }
+ }
+ res = lt_ecc_key_erase(tropic_get_handle(), ecc_slot);
+ if (res != LT_OK) {
+ cli_error(cli, CLI_ERROR, "`lt_ecc_key_erase()` failed with error '%s'",
+ lt_ret_verbose(res));
+ return;
+ }
+
+ cli_ok(cli, "");
+}
+
// clang-format off
PRODTEST_CLI_CMD(
@@ -1703,4 +1847,11 @@ PRODTEST_CLI_CMD(
.args = ""
);
+PRODTEST_CLI_CMD(
+ .name = "tropic-stress-test",
+ .func = prodtest_tropic_stress_test,
+ .info = "Run stress test for Tropic",
+ .args = "[<start-session-iterations> <mac-and-destroy-slot-count> <mac-and-destroy-per-slot-iterations> <signing-iterations>]"
+);
+
#endif
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index 3be5cdd10..af765c850 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -51,6 +51,11 @@
// Pairing key used by official firmware.
#define TROPIC_PRIVILEGED_PAIRING_KEY_SLOT 2 // TR01_PAIRING_KEY_SLOT_INDEX_2
+// Mac-and-destroy slots used in PIN verification
+#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED 0
+#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED 64
+#define TROPIC_MAC_AND_DESTROY_SLOTS_COUNT 64
+
#define TROPIC_MAC_AND_DESTROY_SIZE 32
#ifdef KERNEL_MODE
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index eb67b98e9..46d6baf71 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -48,11 +48,6 @@
#define TROPIC_KEK_MASKS_PRIVILEGED_SLOT 128
#define TROPIC_KEK_MASKS_UNPRIVILEGED_SLOT 256
-// Mac-and-destroy slots used in PIN verification
-#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED 0
-#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED 64
-#define TROPIC_MAC_AND_DESTROY_SLOTS_COUNT 64
-
// The value by which the index of the first mac-and-destroy slot is shifted
// before every PIN change. The value is coprime to
// TROPIC_MAC_AND_DESTROY_SLOTS_COUNT to ensure that
Why this scored 20/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.