feat(core): support dev-signed definitions on non-production builds
What changed, and why it matters
This commit moves the public keys used to verify coin/token definitions from Python code into a new Rust module. It also makes development-signed definitions accepted on non-production builds, similar to an existing feature for translations. The change is a build/configuration refactor rather than a fix for an active security flaw. Production firmware still requires production signatures; dev keys are only accepted when the `dev_keys` feature is enabled, which is intended for development builds.
No immediate action required. Treat as a normal feature/refactor commit. Reviewers should confirm that production release builds do not enable the `dev_keys` feature, and that the new Rust `verify()` function preserves the original threshold and public-key semantics. Continue routine monitoring for any follow-up commits that adjust gating or key material.
Security signals we found
Moved signature verification of external token/coin definitions from Python to Rust
Added development-key fallback gated by `#[cfg(feature = "dev_keys")]`
Removed `__debug__`-gated dev-key acceptance from Python; replaced with feature-gated Rust fallback
Production public keys remain unchanged and are still required on production builds
Evidence from the diff
The patch introduces core/embed/rust/src/definitions/constants.rs containing production and development Ed25519/COSI public keys and a threshold of 2. A new MicroPython module trezordefinitions exposes a verify() function implemented in Rust. The Python verifier in apps/common/definitions.py now calls this Rust function instead of the previous Python cosi_verify. On builds with the dev_keys feature, if production-key verification fails, the code falls back to verifying against development keys. The module is only compiled into non-Bitcoin-only (universal_fw) builds. The old Python constants (THRESHOLD, PUBLIC_KEYS, DEV_PUBLIC_KEYS) are removed from definitions_constants.py and its mako template.
Changed components
Trezor Core firmware definition signature verificationcore/embed/rust/src/definitions/constants.rscore/embed/rust/src/definitions/obj.rscore/src/apps/common/definitions.pycore/src/apps/common/definitions_constants.pyInspect captured patch +115 / −52
### core/embed/projects/firmware/mpconfigport.h
@@ -166,15 +166,16 @@
// allocate traceback data only on debug builds
#define MICROPY_PY_SYS_TRACEBACK_DISABLE (PYOPT)
-#define MICROPY_PY_TREZORCONFIG (1)
-#define MICROPY_PY_TREZORCRYPTO (1)
-#define MICROPY_PY_TREZORIO (1)
-#define MICROPY_PY_TREZORUI (1)
-#define MICROPY_PY_TREZORUTILS (1)
-#define MICROPY_PY_TREZORPROTO (1)
-#define MICROPY_PY_TREZORTRANSLATE (1)
-#define MICROPY_PY_TREZORUI_API (1)
-#define MICROPY_PY_TREZORAPP (USE_APP_LOADING)
+#define MICROPY_PY_TREZORCONFIG (1)
+#define MICROPY_PY_TREZORCRYPTO (1)
+#define MICROPY_PY_TREZORDEFINITIONS (!BITCOIN_ONLY)
+#define MICROPY_PY_TREZORIO (1)
+#define MICROPY_PY_TREZORUI (1)
+#define MICROPY_PY_TREZORUTILS (1)
+#define MICROPY_PY_TREZORPROTO (1)
+#define MICROPY_PY_TREZORTRANSLATE (1)
+#define MICROPY_PY_TREZORUI_API (1)
+#define MICROPY_PY_TREZORAPP (USE_APP_LOADING)
#define MP_STATE_PORT MP_STATE_VM
### core/embed/projects/unix/mpconfigport.h
@@ -216,15 +216,16 @@ extern const struct _mp_print_t mp_stderr_print;
// extern const struct _mp_print_t mp_stderr_print;
-#define MICROPY_PY_TREZORCONFIG (1)
-#define MICROPY_PY_TREZORCRYPTO (1)
-#define MICROPY_PY_TREZORIO (1)
-#define MICROPY_PY_TREZORUI (1)
-#define MICROPY_PY_TREZORUTILS (1)
-#define MICROPY_PY_TREZORPROTO (1)
-#define MICROPY_PY_TREZORTRANSLATE (1)
-#define MICROPY_PY_TREZORUI_API (1)
-#define MICROPY_PY_TREZORAPP (USE_APP_LOADING)
+#define MICROPY_PY_TREZORCONFIG (1)
+#define MICROPY_PY_TREZORCRYPTO (1)
+#define MICROPY_PY_TREZORDEFINITIONS (!BITCOIN_ONLY)
+#define MICROPY_PY_TREZORIO (1)
+#define MICROPY_PY_TREZORUI (1)
+#define MICROPY_PY_TREZORUTILS (1)
+#define MICROPY_PY_TREZORPROTO (1)
+#define MICROPY_PY_TREZORTRANSLATE (1)
+#define MICROPY_PY_TREZORUI_API (1)
+#define MICROPY_PY_TREZORAPP (USE_APP_LOADING)
#define MP_STATE_PORT MP_STATE_VM
### core/embed/rust/librust.h
@@ -9,6 +9,7 @@ mp_obj_t protobuf_debug_msg_def_type();
extern const mp_obj_module_t mp_module_trezorproto;
extern const mp_obj_module_t mp_module_trezorui_api;
+extern const mp_obj_module_t mp_module_trezordefinitions;
extern const mp_obj_module_t mp_module_trezortranslate;
extern const mp_obj_module_t mp_module_trezorble;
extern const mp_obj_module_t mp_module_trezorthp;
### core/embed/rust/src/definitions/constants.rs
@@ -0,0 +1,16 @@
+use crypto::ed25519;
+
+pub const THRESHOLD: u8 = 2;
+
+#[cfg(feature = "dev_keys")]
+pub const PUBLIC_KEYS_DEVEL: [ed25519::PublicKey; 3] = [
+ *b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44",
+ *b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44",
+ *b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12",
+];
+
+pub const PUBLIC_KEYS_PRODUCTION: [ed25519::PublicKey; 3] = [
+ *b"\x43\x34\x99\x63\x43\x62\x3e\x46\x2f\x0f\xc9\x33\x11\xfe\xf1\x48\x4c\xa2\x3d\x2f\xf1\xee\xc6\xdf\x1f\xa8\xeb\x7e\x35\x73\xb3\xdb",
+ *b"\xa9\xa2\x2c\xc2\x65\xa0\xcb\x1d\x6c\xb3\x29\xbc\x0e\x60\xbc\x45\xdf\x76\xb9\xab\x28\xfb\x87\xb6\x11\x36\xfe\xaf\x8d\x8f\xdc\x96",
+ *b"\xb8\xd2\xb2\x1d\xe2\x71\x24\xf0\x51\x1f\x90\x3a\xe7\xe6\x0e\x07\x96\x18\x10\xa0\xb8\xf2\x8e\xa7\x55\xfa\x50\x36\x7a\x8a\x2b\x8b",
+];
### core/embed/rust/src/definitions/mod.rs
@@ -0,0 +1,3 @@
+mod constants;
+#[cfg(feature = "micropython")]
+mod obj;
### core/embed/rust/src/definitions/obj.rs
@@ -0,0 +1,54 @@
+use crypto::{cosi, ed25519};
+
+use super::constants;
+use crate::error::Error;
+use crate::micropython::buffer::get_buffer;
+use crate::micropython::macros::{obj_fn_3, obj_module};
+use crate::micropython::module::Module;
+use crate::micropython::obj::Obj;
+use crate::micropython::qstr::Qstr;
+use crate::micropython::util;
+
+fn verify_with_keys(
+ digest: &[u8],
+ sig: &cosi::Signature,
+ public_keys: &[ed25519::PublicKey; 3],
+) -> Result<(), Error> {
+ Ok(cosi::verify(
+ constants::THRESHOLD,
+ digest,
+ public_keys,
+ sig,
+ )?)
+}
+
+extern "C" fn verify(digest: Obj, sig: Obj, sigmask: Obj) -> Obj {
+ let block = || {
+ // SAFETY: reference is discarded at the end of the block
+ let digest = unsafe { get_buffer(digest)? };
+ let signature = unsafe { get_buffer(sig)? };
+
+ let sig = cosi::Signature::new(
+ u8::try_from(sigmask)?,
+ signature.try_into().map_err(|_| Error::TypeError)?,
+ );
+ #[allow(unused_mut)]
+ let mut result = verify_with_keys(digest, &sig, &constants::PUBLIC_KEYS_PRODUCTION);
+ #[cfg(feature = "dev_keys")]
+ if result.is_err() {
+ // allow development keys
+ result = verify_with_keys(digest, &sig, &constants::PUBLIC_KEYS_DEVEL);
+ }
+ result.map(|()| Obj::const_none())
+ };
+
+ unsafe { util::try_or_raise(block) }
+}
+
+#[no_mangle]
+#[rustfmt::skip]
+pub static mp_module_trezordefinitions: Module = obj_module! {
+ /// def verify(digest: AnyBytes, sig: AnyBytes, sigmask: int) -> None:
+ /// """Verify the definitions signature."""
+ Qstr::MP_QSTR_verify => obj_fn_3!(verify).as_obj(),
+};
### core/embed/rust/src/lib.rs
@@ -23,6 +23,8 @@ mod macros;
mod align;
#[cfg(feature = "debug")]
mod coverage;
+#[cfg(feature = "universal_fw")]
+mod definitions;
mod error;
mod io;
mod maybe_trace;
### core/embed/upymod/rustmods.c
@@ -32,6 +32,10 @@ MP_REGISTER_MODULE(MP_QSTR_trezorproto, mp_module_trezorproto);
MP_REGISTER_MODULE(MP_QSTR_trezortranslate, mp_module_trezortranslate);
#endif
+#if MICROPY_PY_TREZORDEFINITIONS
+MP_REGISTER_MODULE(MP_QSTR_trezordefinitions, mp_module_trezordefinitions);
+#endif
+
#ifdef USE_BLE
MP_REGISTER_MODULE(MP_QSTR_trezorble, mp_module_trezorble);
#endif
### core/mocks/generated/trezordefinitions.pyi
@@ -0,0 +1,7 @@
+from typing import *
+from buffer_types import *
+
+
+# rust/src/definitions/obj.rs
+def verify(digest: AnyBytes, sig: AnyBytes, sigmask: int) -> None:
+ """Verify the definitions signature."""
### core/src/apps/common/definitions.py
@@ -23,11 +23,11 @@
def decode_definition(definition: AnyBytes, expected_type: type[DefType]) -> DefType:
- from trezor.crypto.cosi import verify as cosi_verify
from trezor.crypto.hashlib import sha256
from trezor.enums import DefinitionType
from trezor.protobuf import decode as protobuf_decode
from trezor.utils import BufferReader
+ from trezordefinitions import verify
from apps.common import readers
@@ -87,13 +87,9 @@ def decode_definition(definition: AnyBytes, expected_type: type[DefType]) -> Def
raise DataError("Invalid definition")
# verify signature
- result = cosi_verify(signature, hash, consts.THRESHOLD, consts.PUBLIC_KEYS, sigmask)
- if __debug__:
- debug_result = cosi_verify(
- signature, hash, consts.THRESHOLD, consts.DEV_PUBLIC_KEYS, sigmask
- )
- result = result or debug_result
- if not result:
+ try:
+ verify(hash, signature, sigmask)
+ except ValueError:
raise DataError("Invalid definition signature")
# decode it if it's OK
### core/src/apps/common/definitions_constants.py
@@ -2,19 +2,8 @@
# (by running `make templates` in `core`)
# do not edit manually!
-THRESHOLD = 2
-PUBLIC_KEYS = (
- b"\x43\x34\x99\x63\x43\x62\x3e\x46\x2f\x0f\xc9\x33\x11\xfe\xf1\x48\x4c\xa2\x3d\x2f\xf1\xee\xc6\xdf\x1f\xa8\xeb\x7e\x35\x73\xb3\xdb",
- b"\xa9\xa2\x2c\xc2\x65\xa0\xcb\x1d\x6c\xb3\x29\xbc\x0e\x60\xbc\x45\xdf\x76\xb9\xab\x28\xfb\x87\xb6\x11\x36\xfe\xaf\x8d\x8f\xdc\x96",
- b"\xb8\xd2\xb2\x1d\xe2\x71\x24\xf0\x51\x1f\x90\x3a\xe7\xe6\x0e\x07\x96\x18\x10\xa0\xb8\xf2\x8e\xa7\x55\xfa\x50\x36\x7a\x8a\x2b\x8b",
-)
-
MIN_DATA_VERSION = 1783520408
FORMAT_VERSION = b"trzd1"
-if __debug__:
- DEV_PUBLIC_KEYS = (
- b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44",
- b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44",
- b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12",
- )
+# The public keys and signature threshold for definitions verification
+# live in Rust (core/embed/rust/src/definitions/constants.rs).
### core/src/apps/common/definitions_constants.py.mako
@@ -2,19 +2,8 @@
# (by running `make templates` in `core`)
# do not edit manually!
-THRESHOLD = 2
-PUBLIC_KEYS = (
- b"\x43\x34\x99\x63\x43\x62\x3e\x46\x2f\x0f\xc9\x33\x11\xfe\xf1\x48\x4c\xa2\x3d\x2f\xf1\xee\xc6\xdf\x1f\xa8\xeb\x7e\x35\x73\xb3\xdb",
- b"\xa9\xa2\x2c\xc2\x65\xa0\xcb\x1d\x6c\xb3\x29\xbc\x0e\x60\xbc\x45\xdf\x76\xb9\xab\x28\xfb\x87\xb6\x11\x36\xfe\xaf\x8d\x8f\xdc\x96",
- b"\xb8\xd2\xb2\x1d\xe2\x71\x24\xf0\x51\x1f\x90\x3a\xe7\xe6\x0e\x07\x96\x18\x10\xa0\xb8\xf2\x8e\xa7\x55\xfa\x50\x36\x7a\x8a\x2b\x8b",
-)
-
MIN_DATA_VERSION = ${defs_timestamp}
FORMAT_VERSION = b"trzd1"
-if __debug__:
- DEV_PUBLIC_KEYS = (
- b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44",
- b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44",
- b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12",
- )
+# The public keys and signature threshold for definitions verification
+# live in Rust (core/embed/rust/src/definitions/constants.rs).Why this scored 19/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.