chore(python): move dev keys from BootloaderV2Image [no changelog]
What changed, and why it matters
This is a code cleanup commit in Trezor's Python firmware tooling. It moves hardcoded developer-only signing keys from one internal file to a shared models file and slightly reorganizes how public keys are looked up. The keys themselves remain in the repository and are still labeled as development keys. There is no indication this fixes a security vulnerability or changes device behavior.
No immediate action required. Treat as routine refactoring. If reviewing broader security posture, verify that development keys are never used in production signing workflows and that the TODO production root key lists are populated with real keys before release.
Security signals we found
Hardcoded development signing keys present in source code
Refactor of key handling code without changing key material
No changelog entry and commit title describes chore/cleanup
Production root key lists remain empty TODOs
Evidence from the diff
The commit refactors dev signing key constants in trezorlib. It removes class-level DEV_PRIVATE_PQ_KEYS, DEV_PUBLIC_PQ_KEYS, DEV_PRIVATE_EC_KEYS, and DEV_PUBLIC_EC_KEYS from BootloaderV2Image and adds equivalent module-level constants ROOT_ED25519_KEYS_DEV, ROOT_SLH_DSA_KEYS_DEV_PRIVATE, and ROOT_SLH_DSA_KEYS_DEV_PUBLIC in firmware/models.py. It also moves public_pq_keys()/public_ec_keys() up to the BootableImage base class, adding a dev_keys flag that returns the dev keys when true. The actual key bytes are unchanged, and production root key lists remain empty TODOs.
Changed components
python/src/trezorlib/_internal/firmware_headers.pypython/src/trezorlib/firmware/core.pypython/src/trezorlib/firmware/models.pyInspect captured patch +43 / −40
diff --git a/python/src/trezorlib/_internal/firmware_headers.py b/python/src/trezorlib/_internal/firmware_headers.py
index fe697cde..35078602 100644
--- a/python/src/trezorlib/_internal/firmware_headers.py
+++ b/python/src/trezorlib/_internal/firmware_headers.py
@@ -436,36 +436,7 @@ class SecmonImage(firmware.SecmonImage, CosiSignedMixin):
class BootloaderV2Image(firmware.BootableImage):
NAME: t.ClassVar[str] = "bootloader"
- DEV_PRIVATE_PQ_KEYS = [
- bytes.fromhex(key)
- for key in (
- "9a8da9d38eb9203bd0d5442db161324f35ce7f6dc78e05507306fb13a7e6c145"
- "ec01e60263024f7e71728013b731f7ba1299f518c27ba3ed8f4a219974127c62",
- "1773a0855e8a9961b66682a1e819c29ac83931c00b84062bfc89f3041364c0eb"
- "8af8878085946ed8b116bd24c0f2aac48b7e8f11bf068725ccfbb152abf7a4cd",
- )
- ]
-
- DEV_PUBLIC_PQ_KEYS = [
- bytes.fromhex(key)
- for key in (
- "ec01e60263024f7e71728013b731f7ba1299f518c27ba3ed8f4a219974127c62",
- "8af8878085946ed8b116bd24c0f2aac48b7e8f11bf068725ccfbb152abf7a4cd",
- )
- ]
-
- DEV_PRIVATE_EC_KEYS = [
- (b"\x41" * 32),
- (b"\x42" * 32),
- ]
-
- DEV_PUBLIC_EC_KEYS = [
- bytes.fromhex(key)
- for key in (
- "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d",
- "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12",
- )
- ]
+ DEV_ED25519_KEYS_PRIVATE = _make_dev_keys(b"\x41", b"\x42")
def signature_present(self) -> bool:
return any(not all_zero(sig) for sig in self.unauth.slh_signatures) or any(
@@ -480,18 +451,18 @@ class BootloaderV2Image(firmware.BootableImage):
digest = self.merkle_root()
# SLH signature signs the image digest
- for idx, key in enumerate(self.DEV_PRIVATE_PQ_KEYS):
+ for idx, key in enumerate(fw_models.ROOT_SLH_DSA_KEYS_DEV_PRIVATE):
key = SecretKey.from_digest(key, sha2_128s)
self.unauth.slh_signatures[idx] = key.sign(digest)
hash_params = self.get_hash_params()
hash_fn = hash_params.hash_function
- for idx, key in enumerate(self.DEV_PRIVATE_EC_KEYS):
+ for idx, key in enumerate(self.DEV_ED25519_KEYS_PRIVATE):
# The EC signature signs both the image digest and the SLH signature
ext_digest = hash_fn(digest + self.unauth.slh_signatures[idx]).digest()
self.unauth.ec_signatures[idx] = _ed25519.signature_unsafe(
- ext_digest, key, self.DEV_PUBLIC_EC_KEYS[idx]
+ ext_digest, key, fw_models.ROOT_ED25519_KEYS_DEV[idx]
)
def format(self, verbose: bool = False) -> str:
@@ -522,12 +493,6 @@ class BootloaderV2Image(firmware.BootableImage):
if not key.verify(digest, self.unauth.slh_signatures[idx]):
raise firmware.InvalidSignatureError("Invalid bootloader signature")
- def public_pq_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
- return self.DEV_PUBLIC_PQ_KEYS
-
- def public_ec_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
- return self.DEV_PUBLIC_EC_KEYS
-
class LegacyFirmware(firmware.LegacyFirmware):
NAME: t.ClassVar[str] = "legacy_firmware_v1"
diff --git a/python/src/trezorlib/firmware/core.py b/python/src/trezorlib/firmware/core.py
index 1979d074..219d9116 100644
--- a/python/src/trezorlib/firmware/core.py
+++ b/python/src/trezorlib/firmware/core.py
@@ -17,6 +17,7 @@
from __future__ import annotations
import hashlib
+import typing as t
from copy import copy
from enum import Enum
@@ -25,7 +26,7 @@ from construct_classes import Struct, subcon
from .. import cosi, merkle_tree
from ..tools import EnumAdapter, TupleAdapter
-from . import consts, util
+from . import consts, models, util
from .models import Model
from .vendor import VendorHeader
@@ -347,3 +348,13 @@ class BootableImage(Struct):
if isinstance(self.header.hw_model, Model):
return self.header.hw_model
return None
+
+ def public_pq_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
+ if dev_keys:
+ return models.ROOT_SLH_DSA_KEYS_DEV_PUBLIC
+ return models.ROOT_SLH_DSA_KEYS
+
+ def public_ec_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
+ if dev_keys:
+ return models.ROOT_ED25519_KEYS_DEV
+ return models.ROOT_ED25519_KEYS
diff --git a/python/src/trezorlib/firmware/models.py b/python/src/trezorlib/firmware/models.py
index d3747423..da0926f0 100644
--- a/python/src/trezorlib/firmware/models.py
+++ b/python/src/trezorlib/firmware/models.py
@@ -298,6 +298,33 @@ T3W1 = ModelKeys(
firmware_sigs_needed=-1,
)
+ROOT_ED25519_KEYS = [] # TODO - add when ready
+ROOT_ED25519_KEYS_DEV = [
+ bytes.fromhex(key)
+ for key in (
+ "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d",
+ "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12",
+ )
+]
+ROOT_SLH_DSA_KEYS = [] # TODO - add when ready
+ROOT_SLH_DSA_KEYS_DEV_PRIVATE = [
+ bytes.fromhex(key)
+ for key in (
+ "9a8da9d38eb9203bd0d5442db161324f35ce7f6dc78e05507306fb13a7e6c145"
+ "ec01e60263024f7e71728013b731f7ba1299f518c27ba3ed8f4a219974127c62",
+ "1773a0855e8a9961b66682a1e819c29ac83931c00b84062bfc89f3041364c0eb"
+ "8af8878085946ed8b116bd24c0f2aac48b7e8f11bf068725ccfbb152abf7a4cd",
+ )
+]
+ROOT_SLH_DSA_KEYS_DEV_PUBLIC = [
+ bytes.fromhex(key)
+ for key in (
+ "ec01e60263024f7e71728013b731f7ba1299f518c27ba3ed8f4a219974127c62",
+ "8af8878085946ed8b116bd24c0f2aac48b7e8f11bf068725ccfbb152abf7a4cd",
+ )
+]
+
+
LEGACY_HASH_PARAMS = FirmwareHashParameters(
hash_function=hashlib.sha256,
chunk_size=1024 * 64,
Why this scored 18/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.