chore: Update mldsa-native submodule to v1.0.0-beta
What changed, and why it matters
This commit updates the mldsa-native submodule to a new beta version and adjusts the build system and source code to use the new API names and file paths. ML-DSA is a post-quantum digital signature algorithm used for device attestation in production testing. The change itself is a routine dependency/API migration, but because it touches cryptographic verification and key generation code, it warrants a security-aware review. There is no direct evidence in the commit of a vulnerability being fixed.
Treat as a dependency/API migration requiring verification that the new mldsa-native beta version does not introduce behavioral changes in signature verification, keypair derivation, or constant-time properties. Review the upstream mldsa-native v1.0.0-beta release notes for security fixes. Validate that the renamed constants evaluate to the same byte lengths as the old CRYPTO_* macros and that the build still compiles all required translation units.
Security signals we found
Cryptographic API migration for ML-DSA-44 device attestation
Changes to signature verification path in prodtest command common.c
Changes to keypair generation and signing in prodtest_secrets.c
Build system reorganization moving ML-DSA sources into T3W1 model configs
Submodule bump to v1.0.0-beta without disclosed security rationale
Evidence from the diff
The commit migrates Trezor firmware from an older mldsa-native layout to v1.0.0-beta. It removes explicit per-file SOURCE_MLDSA lists from SConscript.prodtest and SConscript.prodtest_emu, replacing them with model-specific source lists in T3W1 emulator and revC build scripts. Code in prodtest command files switches from internal headers like sign.h/params.h to the public mldsa_native.h, and renames symbols: CRYPTO_PUBLICKEYBYTES/CRYPTO_SECRETKEYBYTES/CRYPTO_BYTES become MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET) etc., and crypto_sign_ functions become mldsa_ equivalents. A new poly_kl.c source is added. The commit is tagged [no changelog] and framed as a chore.
Changed components
vendor/mldsa-native submodulecore/embed/projects/prodtest/cmd/common.ccore/embed/projects/prodtest/cmd/prodtest_secrets.ccore/embed/sec/secret_keys/inc/sec/secret_keys.hcore/site_scons/models/T3W1/emulator.pycore/site_scons/models/T3W1/trezor_t3w1_revC.pycore/SConscript.prodtestcore/SConscript.prodtest_emuInspect captured patch +59 / −50
diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest
index bf0c9af1..d3953d0c 100644
--- a/core/SConscript.prodtest
+++ b/core/SConscript.prodtest
@@ -145,17 +145,6 @@ if 'boot_ucb' in FEATURES_AVAILABLE:
'vendor/sphincsplus/ref/wots.c',
]
-SOURCE_MLDSA = [
- 'vendor/mldsa-native/mldsa/fips202/fips202.c',
- 'vendor/mldsa-native/mldsa/fips202/fips202x4.c',
- 'vendor/mldsa-native/mldsa/fips202/keccakf1600.c',
- 'vendor/mldsa-native/mldsa/ntt.c',
- 'vendor/mldsa-native/mldsa/packing.c',
- 'vendor/mldsa-native/mldsa/poly.c',
- 'vendor/mldsa-native/mldsa/polyvec.c',
- 'vendor/mldsa-native/mldsa/sign.c',
-]
-
# modtrezorui
CPPPATH_MOD += [
'vendor/micropython/lib/uzlib'
@@ -318,7 +307,6 @@ obj_program.extend(env.Object(source=SOURCE_MOD))
obj_program.extend(env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftrivial-auto-var-init=zero'))
obj_program.extend(env.Object(source=SOURCE_PRODTEST))
obj_program.extend(env.Object(source=SOURCE_HAL))
-obj_program.extend(env.Object(source=SOURCE_MLDSA))
if 'boot_ucb' in FEATURES_AVAILABLE:
obj_program += env.Object(
diff --git a/core/SConscript.prodtest_emu b/core/SConscript.prodtest_emu
index 158684b7..cd683dd3 100644
--- a/core/SConscript.prodtest_emu
+++ b/core/SConscript.prodtest_emu
@@ -111,17 +111,6 @@ if FEATURE_FLAGS["AES_GCM"]:
'vendor/trezor-crypto/aes/aesgcm.c',
]
-SOURCE_MLDSA = [
- 'vendor/mldsa-native/mldsa/fips202/fips202.c',
- 'vendor/mldsa-native/mldsa/fips202/fips202x4.c',
- 'vendor/mldsa-native/mldsa/fips202/keccakf1600.c',
- 'vendor/mldsa-native/mldsa/ntt.c',
- 'vendor/mldsa-native/mldsa/packing.c',
- 'vendor/mldsa-native/mldsa/poly.c',
- 'vendor/mldsa-native/mldsa/polyvec.c',
- 'vendor/mldsa-native/mldsa/sign.c',
-]
-
# modtrezorui
CPPPATH_MOD += [
'vendor/micropython/lib/uzlib'
@@ -304,7 +293,6 @@ obj_program += env.Object(source=SOURCE_MOD)
obj_program += env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftrivial-auto-var-init=zero')
obj_program += env.Object(source=SOURCE_PRODTEST)
obj_program += env.Object(source=SOURCE_HAL)
-obj_program += env.Object(source=SOURCE_MLDSA)
obj_program += [rust]
program_elf = env.Command(
diff --git a/core/embed/projects/prodtest/cmd/common.c b/core/embed/projects/prodtest/cmd/common.c
index 77899b87..520b107e 100644
--- a/core/embed/projects/prodtest/cmd/common.c
+++ b/core/embed/projects/prodtest/cmd/common.c
@@ -33,7 +33,7 @@
#include "sha2.h"
#include "string.h"
-#include <../vendor/mldsa-native/mldsa/sign.h>
+#include <mldsa_native.h>
// HSM root certification authority public keys.
const uint8_t ROOT_KEYS_P256[][ECDSA_PUBLIC_KEY_SIZE] = {
@@ -72,20 +72,21 @@ const ed25519_public_key ROOT_KEYS_ED25519[] = {
#endif
};
-const uint8_t ROOT_KEYS_MLDSA44[][CRYPTO_PUBLICKEYBYTES] = {
+const uint8_t
+ ROOT_KEYS_MLDSA44[][MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)] = {
#if PRODUCTION
#ifdef DEV_AUTH_ROOT_PROD_MLDSA44
- DEV_AUTH_ROOT_PROD_MLDSA44,
+ DEV_AUTH_ROOT_PROD_MLDSA44,
#endif
#ifdef DEV_AUTH_ROOT_PROD_BACKUP_MLDSA44
- DEV_AUTH_ROOT_PROD_BACKUP_MLDSA44,
+ DEV_AUTH_ROOT_PROD_BACKUP_MLDSA44,
#endif
#else
#ifdef DEV_AUTH_ROOT_DEBUG_MLDSA44
- DEV_AUTH_ROOT_DEBUG_MLDSA44,
+ DEV_AUTH_ROOT_DEBUG_MLDSA44,
#endif
#ifdef DEV_AUTH_ROOT_STAGING_MLDSA44
- DEV_AUTH_ROOT_STAGING_MLDSA44,
+ DEV_AUTH_ROOT_STAGING_MLDSA44,
#endif
#endif
};
@@ -346,12 +347,12 @@ static bool verify_signature(alg_id_t alg_id, const uint8_t* pub_key,
}
if (alg_id == ALG_ID_MLDSA44) {
- if (pub_key_size != CRYPTO_PUBLICKEYBYTES) {
+ if (pub_key_size != MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)) {
return false;
}
- if (crypto_sign_verify(sig, sig_size, msg, msg_size, (const uint8_t*)"", 0,
- pub_key) != 0) {
+ if (mldsa_verify(sig, sig_size, msg, msg_size, (const uint8_t*)"", 0,
+ pub_key) != 0) {
return false;
}
diff --git a/core/embed/projects/prodtest/cmd/prodtest_secrets.c b/core/embed/projects/prodtest/cmd/prodtest_secrets.c
index 291e972a..5a8bb69d 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_secrets.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_secrets.c
@@ -48,7 +48,7 @@
#include <trezor_model.h>
#endif
-#include <../vendor/mldsa-native/mldsa/sign.h>
+#include <mldsa_native.h>
secbool set_random_secret(uint8_t slot, size_t length) {
uint8_t secret[length];
@@ -182,10 +182,10 @@ static void prodtest_secrets_get_mcu_device_key(cli_t* cli) {
goto cleanup;
}
- uint8_t mcu_public[CRYPTO_PUBLICKEYBYTES] = {0};
- uint8_t mcu_private[CRYPTO_SECRETKEYBYTES] = {0};
- if (crypto_sign_keypair_internal(mcu_public, mcu_private, seed) != 0) {
- cli_error(cli, CLI_ERROR, "`crypto_sign_keypair_internal()` failed.");
+ uint8_t mcu_public[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)] = {0};
+ uint8_t mcu_private[MLDSA_SECRETKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)] = {0};
+ if (mldsa_keypair_internal(mcu_public, mcu_private, seed) != 0) {
+ cli_error(cli, CLI_ERROR, "`mldsa_keypair_internal()` failed.");
goto cleanup;
}
@@ -215,10 +215,10 @@ static bool check_device_cert_chain(cli_t* cli, const uint8_t* chain,
goto cleanup;
}
- uint8_t mcu_public[CRYPTO_PUBLICKEYBYTES] = {0};
- uint8_t mcu_private[CRYPTO_SECRETKEYBYTES] = {0};
- if (crypto_sign_keypair_internal(mcu_public, mcu_private, seed) != 0) {
- cli_error(cli, CLI_ERROR, "`crypto_sign_keypair_internal()` failed.");
+ uint8_t mcu_public[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)] = {0};
+ uint8_t mcu_private[MLDSA_SECRETKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)] = {0};
+ if (mldsa_keypair_internal(mcu_public, mcu_private, seed) != 0) {
+ cli_error(cli, CLI_ERROR, "`mldsa_keypair_internal()` failed.");
goto cleanup;
}
@@ -228,13 +228,13 @@ static bool check_device_cert_chain(cli_t* cli, const uint8_t* chain,
// The challenge is intentionally constant zero.
const uint8_t ENCODED_EMPTY_CONTEXT_STRING[] = {0, 0};
uint8_t challenge[CHALLENGE_SIZE] = {0};
- uint8_t signature[CRYPTO_BYTES] = {0};
+ uint8_t signature[MLDSA_BYTES(MLD_CONFIG_API_PARAMETER_SET)] = {0};
size_t siglen = 0;
- if (crypto_sign_signature_internal(
- signature, &siglen, challenge, sizeof(challenge),
- ENCODED_EMPTY_CONTEXT_STRING, sizeof(ENCODED_EMPTY_CONTEXT_STRING),
- rnd, mcu_private, 0) != 0) {
- cli_error(cli, CLI_ERROR, "`crypto_sign_signature()` failed.");
+ if (mldsa_signature_internal(signature, &siglen, challenge, sizeof(challenge),
+ ENCODED_EMPTY_CONTEXT_STRING,
+ sizeof(ENCODED_EMPTY_CONTEXT_STRING), rnd,
+ mcu_private, 0) != 0) {
+ cli_error(cli, CLI_ERROR, "`mldsa_signature_internal()` failed.");
goto cleanup;
}
diff --git a/core/embed/sec/secret_keys/inc/sec/secret_keys.h b/core/embed/sec/secret_keys/inc/sec/secret_keys.h
index 39db5ac8..75008d34 100644
--- a/core/embed/sec/secret_keys/inc/sec/secret_keys.h
+++ b/core/embed/sec/secret_keys/inc/sec/secret_keys.h
@@ -32,7 +32,7 @@ secbool secret_key_delegated_identity(uint16_t rotation_index,
#define SECRET_KEY_MASKING
-#include <../vendor/mldsa-native/mldsa/params.h>
+#include <mldsa_native.h>
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]);
diff --git a/core/site_scons/models/T3W1/emulator.py b/core/site_scons/models/T3W1/emulator.py
index 28bfc166..1733fe2f 100644
--- a/core/site_scons/models/T3W1/emulator.py
+++ b/core/site_scons/models/T3W1/emulator.py
@@ -55,6 +55,22 @@ def configure(
sources += ["embed/sec/secret_keys/secret_keys_common.c"]
defines += [("USE_SECRET_KEYS", "1")]
+ # MCU device attestation with ML-DSA-44
+ defines += [("USE_MCU_ATTESTATION", "1")]
+ defines += [("MLD_CONFIG_NAMESPACE_PREFIX", "mldsa")]
+ defines += [("MLD_CONFIG_NO_RANDOMIZED_API", "1")]
+ paths += ["embed/sec/mcu_attestation/inc"]
+ sources += ["embed/sec/mcu_attestation/mcu_attestation.c"]
+ paths += ["vendor/mldsa-native/mldsa"]
+ sources += ["vendor/mldsa-native/mldsa/src/sign.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/poly.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/poly_kl.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/polyvec.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/packing.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/fips202.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/fips202x4.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/keccakf1600.c"]
+
if "sbu" in features_wanted:
sources += ["embed/io/sbu/unix/sbu.c"]
paths += ["embed/io/sbu/inc"]
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index e30fb4b4..8c1bad2d 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -70,6 +70,22 @@ def configure(
sources += ["embed/sec/secret_keys/secret_keys_common.c"]
defines += [("USE_SECRET_KEYS", "1")]
+ # MCU device attestation with ML-DSA-44
+ defines += [("USE_MCU_ATTESTATION", "1")]
+ defines += [("MLD_CONFIG_NAMESPACE_PREFIX", "mldsa")]
+ defines += [("MLD_CONFIG_NO_RANDOMIZED_API", "1")]
+ paths += ["embed/sec/mcu_attestation/inc"]
+ sources += ["embed/sec/mcu_attestation/mcu_attestation.c"]
+ paths += ["vendor/mldsa-native/mldsa"]
+ sources += ["vendor/mldsa-native/mldsa/src/sign.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/poly.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/poly_kl.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/polyvec.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/packing.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/fips202.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/fips202x4.c"]
+ sources += ["vendor/mldsa-native/mldsa/src/fips202/keccakf1600.c"]
+
if "boot_ucb" in features_wanted:
sources += ["embed/sec/image/boot_header.c"]
sources += ["embed/sec/image/boot_ucb.c"]
Why this scored 27/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.