feat(core/prodtest): implement tropic FW update
What changed, and why it matters
This commit adds a new production-test command to Trezor hardware wallets that updates the firmware of an internal Tropic security chip. It also speeds up the SPI bus used to talk to that chip. The change is part of the factory/production testing tooling, not the normal user wallet firmware, and there is no claim in the commit that it fixes a security bug. The main things to watch are that the update mechanism only accepts one hard-coded silicon revision ('ABAB') and that the SPI speed increase is reliable.
Review the `lt_do_mutable_fw_update()` implementation in libtropic for authentication, signature verification, and rollback protection. Verify that the SPI speed increase to prescaler 8 has been tested across all supported board revisions and cable lengths. Confirm that the `ABAB` revision gate is appropriate for all units this tool will service, and ensure the production-test CLI is not reachable in retail firmware.
Security signals we found
New firmware-update path for a secure element / auxiliary security chip
Silicon-revision check is hard-coded to a single value ('ABAB') via compile-time define
SPI clock prescaler changed from /32 to /8, increasing bus speed and potentially signal-integrity risk
Update is performed in production-test CLI context, not end-user wallet runtime
No input validation beyond argument count; firmware blobs are embedded and sized with sizeof()
Evidence from the diff
The patch introduces prodtest_tropic_update_fw() and a CLI command tropic-update-fw in core/embed/projects/prodtest/cmd/prodtest_tropic.c. The function initializes the Tropic chip, verifies the silicon revision is exactly ‘ABAB’ (controlled by the ABAB compile-time define), reboots the chip into maintenance mode, updates the RISC-V CPU firmware from the embedded fw_CPU blob and the SPECT firmware from fw_SPECT using lt_do_mutable_fw_update(), then reboots into application mode and reads back version strings. The build files for T3W1 emulator/revA/revB/revC add the firmware blob include path and define ABAB and LT_HELPERS. Separately, core/embed/sec/tropic/stm32/tropic01.c changes the SPI baud-rate prescaler from 32 to 8, increasing bus speed.
Changed components
core/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/sec/tropic/stm32/tropic01.ccore/site_scons/models/T3W1/emulator.pycore/site_scons/models/T3W1/trezor_t3w1_revA.pycore/site_scons/models/T3W1/trezor_t3w1_revB.pycore/site_scons/models/T3W1/trezor_t3w1_revC.pyInspect captured patch +166 / −1
diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md
index 8a7211016..9a0becf51 100644
--- a/core/embed/projects/prodtest/README.md
+++ b/core/embed/projects/prodtest/README.md
@@ -965,6 +965,28 @@ tropic-get-chip-id
OK 00000001000000000000000000000000000000000000000000000000000000000000000001000000054400000000FFFFFFFFFFFF01F00F000544545354303103001300000B54524F50494330312D4553FFFFFFFF000100000000FFFF000100000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13000300
```
+
+### tropic-update-fw
+
+Updates Tropic firmware to the embedded version.
+
+Example:
+```
+tropic-update-fw
+# Silicon revision: ABAB
+# Rebooting into Maintenance mode
+# Chip is executing bootloader
+# Updating RISC-V FW
+# Updating SPECT FW
+# Rebooting into Application mode
+# Reading RISC-V FW version
+# Chip is executing RISC-V application FW version: 1.0.0 (+ .0)
+# Reading SPECT FW version
+# Chip is executing SPECT FW version: 1.0.0 (+ .0)
+OK
+```
+
+
### 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 466aafcd3..d7c3833c1 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -29,6 +29,8 @@
#include "memzero.h"
+#include "fw_CPU.h"
+#include "fw_SPECT.h"
#include "libtropic.h"
#include "secure_channel.h"
@@ -402,6 +404,124 @@ static void prodtest_tropic_certfido_write(cli_t* cli) {
cli_ok(cli, "");
}
+static void prodtest_tropic_update_fw(cli_t* cli) {
+#define FW_APP_UPDATE_BANK FW_BANK_FW1
+#define FW_SPECT_UPDATE_BANK FW_BANK_SPECT1
+
+ if (cli_arg_count(cli) > 0) {
+ cli_error_arg_count(cli);
+ return;
+ }
+
+ tropic_init();
+
+ lt_handle_t* h = tropic_get_handle();
+
+ lt_chip_id_t chip_id = {0};
+ if (lt_get_info_chip_id(h, &chip_id) != LT_OK) {
+ cli_error(cli, CLI_ERROR, "Unable to get CHIP ID");
+ return;
+ }
+
+ cli_trace(cli, "Silicon revision: %c%c%c%c", chip_id.silicon_rev[0],
+ chip_id.silicon_rev[1], chip_id.silicon_rev[2],
+ chip_id.silicon_rev[3]);
+
+#ifdef ABAB
+ if (strncmp((char*)chip_id.silicon_rev, "ABAB", 4) != 0) {
+ cli_error(cli, CLI_ERROR, "Wrong tropic chip silicon revision");
+ return;
+ }
+#else
+ cli_error(cli, CLI_ERROR, "Tropic chip silicon revision not set");
+ return;
+#endif
+
+ // For firmware update chip must be rebooted into MAINTENANCE mode.
+ cli_trace(cli, "Rebooting into Maintenance mode");
+ lt_ret_t ret = lt_reboot(h, LT_MODE_MAINTENANCE);
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "lt_reboot() failed, ret=%s",
+ lt_ret_verbose(ret));
+ return;
+ }
+
+ if (h->l2.mode != LT_MODE_MAINTENANCE) {
+ cli_error(cli, CLI_ERROR, "Chip couldn't get into MAINTENANCE mode");
+ return;
+ }
+
+ cli_trace(cli, "Chip is executing bootloader");
+
+ cli_trace(cli, "Updating RISC-V FW");
+ ret = lt_do_mutable_fw_update(h, fw_CPU, sizeof(fw_CPU), FW_APP_UPDATE_BANK);
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "RISC-V FW update failed, ret=%s",
+ lt_ret_verbose(ret));
+ goto cleanup;
+ }
+
+ cli_trace(cli, "Updating SPECT FW");
+ ret = lt_do_mutable_fw_update(h, fw_SPECT, sizeof(fw_SPECT),
+ FW_SPECT_UPDATE_BANK);
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "SPECT FW update failed, ret=%s",
+ lt_ret_verbose(ret));
+ goto cleanup;
+ }
+
+ // To read firmware versions chip must be rebooted into application mode.
+ cli_trace(cli, "Rebooting into Application mode");
+ ret = lt_reboot(h, LT_MODE_APP);
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "lt_reboot() failed, ret=%s",
+ lt_ret_verbose(ret));
+ goto cleanup;
+ }
+
+ if (h->l2.mode != LT_MODE_APP) {
+ cli_error(cli, CLI_ERROR,
+ "Device couldn't get into APP mode, APP and SPECT firmwares in "
+ "fw banks are not valid or banks are empty");
+ goto cleanup;
+ }
+
+ cli_trace(cli, "Reading RISC-V FW version");
+
+ uint8_t risc_fw_ver[LT_L2_GET_INFO_RISCV_FW_SIZE] = {0};
+ ret = lt_get_info_riscv_fw_ver(h, risc_fw_ver, LT_L2_GET_INFO_RISCV_FW_SIZE);
+
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "Failed to get RISC-V FW version, ret=%s",
+ lt_ret_verbose(ret));
+ goto cleanup;
+ }
+
+ cli_trace(cli,
+ "Chip is executing RISC-V application FW version: %d.%d.%d (+ .%d)",
+ risc_fw_ver[3], risc_fw_ver[2], risc_fw_ver[1], risc_fw_ver[0]);
+
+ cli_trace(cli, "Reading SPECT FW version");
+ uint8_t spect_fw_ver[LT_L2_GET_INFO_SPECT_FW_SIZE] = {0};
+ ret = lt_get_info_spect_fw_ver(h, spect_fw_ver, LT_L2_GET_INFO_SPECT_FW_SIZE);
+
+ if (ret != LT_OK) {
+ cli_error(cli, CLI_ERROR, "Failed to get SPECT FW version, ret=%s",
+ lt_ret_verbose(ret));
+ goto cleanup;
+ }
+
+ cli_trace(cli, "Chip is executing SPECT FW version: %d.%d.%d (+ .%d)",
+ spect_fw_ver[3], spect_fw_ver[2], spect_fw_ver[1], spect_fw_ver[0]);
+
+ cli_ok(cli, "");
+
+ return;
+
+cleanup:
+ tropic_deinit();
+}
+
// clang-format off
PRODTEST_CLI_CMD(
@@ -509,4 +629,11 @@ PRODTEST_CLI_CMD(
.args = ""
);
+PRODTEST_CLI_CMD(
+ .name = "tropic-update-fw",
+ .func = prodtest_tropic_update_fw,
+ .info = "Update tropic FW to embedded binary",
+ .args = ""
+);
+
#endif
diff --git a/core/embed/sec/tropic/stm32/tropic01.c b/core/embed/sec/tropic/stm32/tropic01.c
index c7dfd52ed..3687173c5 100644
--- a/core/embed/sec/tropic/stm32/tropic01.c
+++ b/core/embed/sec/tropic/stm32/tropic01.c
@@ -108,7 +108,7 @@ lt_ret_t lt_port_init(lt_handle_t *h) {
drv->spi.Init.CLKPolarity = SPI_POLARITY_LOW;
drv->spi.Init.CLKPhase = SPI_PHASE_1EDGE;
drv->spi.Init.NSS = SPI_NSS_HARD_OUTPUT;
- drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
+ drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
drv->spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
drv->spi.Init.TIMode = SPI_TIMODE_DISABLE;
drv->spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
diff --git a/core/site_scons/models/T3W1/emulator.py b/core/site_scons/models/T3W1/emulator.py
index ac768f417..8b5afc1c6 100644
--- a/core/site_scons/models/T3W1/emulator.py
+++ b/core/site_scons/models/T3W1/emulator.py
@@ -89,9 +89,13 @@ def configure(
paths += ["vendor/libtropic/src"]
defines += ["USE_TREZOR_CRYPTO"]
defines += [("LT_USE_TREZOR_CRYPTO", "1")]
+ defines += [("LT_HELPERS", "1")]
features_available.append("tropic")
defines += [("USE_TROPIC", "1")]
+ paths += ["vendor/libtropic/TROPIC01_fw_update_files/boot_v_1_0_1/fw_v_1_0_0"]
+ defines += [("ABAB", "1")]
+
if "input" in features_wanted:
sources += ["embed/io/touch/unix/touch.c"]
sources += ["embed/io/touch/touch_poll.c"]
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
index ea2998d46..aff4c2911 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
@@ -216,6 +216,10 @@ def configure(
paths += ["vendor/libtropic/src"]
defines += [("USE_TROPIC", "1")]
defines += [("LT_USE_TREZOR_CRYPTO", "1")]
+ defines += [("LT_HELPERS", "1")]
+
+ paths += ["vendor/libtropic/TROPIC01_fw_update_files/boot_v_1_0_1/fw_v_1_0_0"]
+ defines += [("ABAB", "1")]
if "sbu" in features_wanted:
sources += ["embed/io/sbu/stm32/sbu.c"]
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revB.py b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
index 4a4789aee..a11fed29b 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revB.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
@@ -217,6 +217,10 @@ def configure(
paths += ["vendor/libtropic/src"]
defines += [("USE_TROPIC", "1")]
defines += [("LT_USE_TREZOR_CRYPTO", "1")]
+ defines += [("LT_HELPERS", "1")]
+
+ paths += ["vendor/libtropic/TROPIC01_fw_update_files/boot_v_1_0_1/fw_v_1_0_0"]
+ defines += [("ABAB", "1")]
if "sbu" in features_wanted:
sources += ["embed/io/sbu/stm32/sbu.c"]
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index fb40baef5..1b1feda59 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -216,6 +216,10 @@ def configure(
paths += ["vendor/libtropic/src"]
defines += [("USE_TROPIC", "1")]
defines += [("LT_USE_TREZOR_CRYPTO", "1")]
+ defines += [("LT_HELPERS", "1")]
+
+ paths += ["vendor/libtropic/TROPIC01_fw_update_files/boot_v_1_0_1/fw_v_1_0_0"]
+ defines += [("ABAB", "1")]
if "sbu" in features_wanted:
sources += ["embed/io/sbu/stm32/sbu.c"]
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.