feat(core): expose production date to micropython
What changed, and why it matters
This commit simply exposes the device's production date to the Python/MicroPython layer in Trezor firmware. It is a feature addition, not a security fix or vulnerability. The change reads an existing date from device-specific flash memory and makes it available through a new function. There is no indication this introduces a security issue.
No security action required. This is a benign feature commit. Standard code review and testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit adds a new unit_production_date() function to the trezorutils MicroPython module. It extends the unit_properties_t struct with a production_date field (year/month/day), parses the date from an OTP block string, and exposes it as a tuple to Python. The existing get_production_date() helper is generalized to return month and day as well, and its model-specific #ifdef TREZOR_MODEL_T2T1 guard is removed so it runs on all models. The parsed date is also used to retain the existing SD hotswap workaround for early Trezor Model T units (production_year <= 18).
Changed components
core/embed/upymod/modtrezorutils/modtrezorutils.ccore/embed/util/unit_properties/inc/util/unit_properties.hcore/embed/util/unit_properties/stm32/unit_properties.ccore/mocks/generated/trezorutils.pyicore/src/trezor/utils.pyInspect captured patch +57 / −7
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index 59fe844a5..d8a2ba54a 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -290,6 +290,35 @@ STATIC mp_obj_t mod_trezorutils_unit_packaging(void) {
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_unit_packaging_obj,
mod_trezorutils_unit_packaging);
+/// def unit_production_date() -> tuple[int, int, int] | None:
+/// """
+/// Returns the unit production date as (year, month, day), or None if
+/// unavailable.
+/// """
+STATIC mp_obj_t mod_trezorutils_unit_production_date(void) {
+ const unit_properties_t *props = unit_properties();
+ if (props == NULL) {
+ return mp_const_none;
+ }
+
+ const uint16_t year = props->production_date.year;
+ const uint8_t month = props->production_date.month;
+ const uint8_t day = props->production_date.day;
+
+ // If any field is zero, consider the date unavailable
+ if (year == 0 || month == 0 || day == 0) {
+ return mp_const_none;
+ }
+
+ mp_obj_t items[3];
+ items[0] = mp_obj_new_int(year);
+ items[1] = mp_obj_new_int(month);
+ items[2] = mp_obj_new_int(day);
+ return mp_obj_new_tuple(3, items);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_unit_production_date_obj,
+ mod_trezorutils_unit_production_date);
+
#if USE_SERIAL_NUMBER
/// if utils.USE_SERIAL_NUMBER:
@@ -804,6 +833,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
MP_ROM_PTR(&mod_trezorutils_unit_packaging_obj)},
{MP_ROM_QSTR(MP_QSTR_unit_btconly),
MP_ROM_PTR(&mod_trezorutils_unit_btconly_obj)},
+ {MP_ROM_QSTR(MP_QSTR_unit_production_date),
+ MP_ROM_PTR(&mod_trezorutils_unit_production_date_obj)},
#if USE_SERIAL_NUMBER
{MP_ROM_QSTR(MP_QSTR_serial_number),
MP_ROM_PTR(&mod_trezorutils_serial_number_obj)},
diff --git a/core/embed/util/unit_properties/inc/util/unit_properties.h b/core/embed/util/unit_properties/inc/util/unit_properties.h
index 55d7944c3..2ca445594 100644
--- a/core/embed/util/unit_properties/inc/util/unit_properties.h
+++ b/core/embed/util/unit_properties/inc/util/unit_properties.h
@@ -93,6 +93,13 @@ typedef struct {
* contains a valid value */
bool battery_type_is_valid;
+ /** Device production date */
+ struct {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ } production_date;
+
} unit_properties_t;
/**
diff --git a/core/embed/util/unit_properties/stm32/unit_properties.c b/core/embed/util/unit_properties/stm32/unit_properties.c
index e58ec29fc..08248c594 100644
--- a/core/embed/util/unit_properties/stm32/unit_properties.c
+++ b/core/embed/util/unit_properties/stm32/unit_properties.c
@@ -41,8 +41,6 @@ static unit_properties_driver_t g_unit_properties_driver = {
.initialized = false,
};
-#ifdef TREZOR_MODEL_T2T1
-
// Parse two digit number from the string.
//
// Returns -1 if the string is not a valid two-digit number.
@@ -56,7 +54,7 @@ static inline int parse_two_digits(const char* str) {
// Reads the production date from the OTP block.
//
// Returns `false` in case of and flash read error.
-static bool get_production_date(int* year) {
+static bool get_production_date(int* year, int* month, int* day) {
*year = -1;
uint8_t otp_data[FLASH_OTP_BLOCK_SIZE];
@@ -78,14 +76,14 @@ static bool get_production_date(int* year) {
if (i >= 0 && str[i] == '-') {
*year = parse_two_digits(&str[i + 1]);
+ *month = parse_two_digits(&str[i + 3]);
+ *day = parse_two_digits(&str[i + 5]);
}
}
return true;
}
-#endif // TREZOR_MODEL_T2T1
-
// Reads and parses the unit properties from the OTP block.
//
// Returns `false` in case of and flash read error.
@@ -134,12 +132,17 @@ static bool detect_properties(unit_properties_t* props) {
break;
}
+ int production_year = 0, production_month = 0, production_day = 0;
+ get_production_date(&production_year, &production_month, &production_day);
+ props->production_date.year = 2000 + production_year;
+ props->production_date.month = production_month;
+ props->production_date.day = production_day;
+
props->sd_hotswap_enabled = true;
#ifdef TREZOR_MODEL_T2T1
// Early produced TTs have a HW bug that prevents hotswapping of the SD card,
// lets check the build data and decide based on that.
- int production_year;
- get_production_date(&production_year);
+
if (production_year <= 18) {
props->sd_hotswap_enabled = false;
}
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index 7076190ed..c5ca2e791 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -97,6 +97,14 @@ def unit_packaging() -> int | None:
"""
Returns the packaging version of the unit.
"""
+
+
+# upymod/modtrezorutils/modtrezorutils.c
+def unit_production_date() -> tuple[int, int, int] | None:
+ """
+ Returns the unit production date as (year, month, day), or None if
+ unavailable.
+ """
if utils.USE_SERIAL_NUMBER:
def serial_number() -> str:
"""
diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py
index bce74d97b..f90f67a82 100644
--- a/core/src/trezor/utils.py
+++ b/core/src/trezor/utils.py
@@ -52,6 +52,7 @@ from trezorutils import ( # noqa: F401
unit_btconly,
unit_color,
unit_packaging,
+ unit_production_date,
)
if USE_NRF:
Why this scored 15/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.