What changed, and why it matters
This commit updates the Trezor One (legacy firmware) to use version 2 of the external Ethereum token/network definition format. The key change is that legacy firmware now requires only one valid signature instead of two, and explicitly rejects the older version 1 format. This is a feature update to align legacy devices with the newer definition format, not a fix for an active security bug. The change is documented and includes tests verifying that version 1 definitions are rejected on legacy firmware.
No immediate action required. Treat as a normal feature/format upgrade. Ensure downstream users and definition issuers are aware that legacy firmware now requires v2-format definitions. Review whether lowering the signature threshold from 2 to 1 is acceptable given the threat model for legacy devices.
Security signals we found
Change in signature threshold from 2 to 1 for legacy firmware
Format version bump from v1 to v2 for legacy accepted definitions
New test ensuring v1 definitions are rejected on legacy
Documentation update describing the version policy change
Evidence from the diff
The commit modifies legacy firmware’s Ethereum external definition handling. It changes the accepted format version string from ‘trzd1’ to ‘trzd2’ and lowers the signature threshold from 2 to 1. The documentation notes that Trezor One accepts only format version 2 since firmware 1.14.2. Tests are updated: the ‘not enough signatures’ test is now restricted to core models using a downgraded v1 payload, and a new legacy-only test verifies that v1 format version is rejected. The fixtures JSON is updated accordingly. This is a protocol/format upgrade rather than a vulnerability patch.
Changed components
legacy/firmware/ethereum_definitions.clegacy/firmware/ethereum_definitions_constants.h.makodocs/common/external-definitions.mdtests/definitions.pytests/device_tests/ethereum/test_definitions_bad.pytests/ui_tests/fixtures.jsonInspect captured patch +28 / −6
### docs/common/external-definitions.md
@@ -120,6 +120,7 @@ signed by the CoSi algorithm.
The format version is bumped on backward incompatible change.
For versions 1 and 2, the data structure is identical but the number of necessary signatures is changed from 2 to 1.
+Trezor One (legacy firmware) accepts only format version 2 since version 1.14.2.
The full format of the definition is as follows:
### legacy/firmware/ethereum_definitions.c
@@ -37,7 +37,7 @@
typedef pb_byte_t proof_entry[SHA256_DIGEST_LENGTH];
-#define SIGNATURE_THRESHOLD 2
+#define SIGNATURE_THRESHOLD 1
#define DEFS_PUBLIC_KEYS_COUNT 3
const ed25519_public_key DEFS_PUBLIC_KEYS[DEFS_PUBLIC_KEYS_COUNT] = {
### legacy/firmware/ethereum_definitions_constants.h.mako
@@ -11,6 +11,6 @@
#define MIN_DATA_VERSION ${defs_timestamp}
#define FORMAT_VERSION_LENGTH 5
-#define FORMAT_VERSION (const pb_byte_t *)"trzd1"
+#define FORMAT_VERSION (const pb_byte_t *)"trzd2"
#endif
### tests/definitions.py
@@ -40,7 +40,7 @@ def make_eth_token(
def make_payload(
magic: bytes = b"trzd",
- format_version: bytes = b"1",
+ format_version: bytes = b"2",
data_type: messages.DefinitionType = messages.DefinitionType.ETHEREUM_NETWORK,
timestamp: int = 0xFFFF_FFFF,
message: (
### tests/device_tests/ethereum/test_definitions_bad.py
@@ -109,6 +109,16 @@ def _make_display_format_payload(
)
+def downgrade_to_v1(payload: bytes) -> bytes:
+ """Downgrade a v2 payload to v1 by swapping the version byte.
+
+ Formats 1 and 2 differ only in the version byte, so this is equivalent to
+ building the payload with format_version=b"1". Must be done before
+ signing, since the version byte is covered by the signature.
+ """
+ return b"trzd1" + payload[5:]
+
+
def _cases(session: Session) -> list[tuple]:
cases: list[tuple] = [
(make_payload, _fails_network),
@@ -133,13 +143,24 @@ def test_mangled_signature(session: Session) -> None:
check(session, payload + proof + bad_signature, "Invalid definition signature")
+@pytest.mark.models("core")
def test_not_enough_signatures(session: Session) -> None:
+ # version 1 requires two signatures, one is not enough
for make, check in _cases(session):
- payload = make()
+ payload = downgrade_to_v1(make())
proof, signature = sign_payload(payload, [], threshold=1)
check(session, payload + proof + signature, "Invalid definition signature")
+@pytest.mark.models("legacy")
+def test_v1_format_version_rejected(session: Session) -> None:
+ # legacy firmware accepts only format version 2
+ for make, check in _cases(session):
+ payload = downgrade_to_v1(make())
+ proof, signature = sign_payload(payload, [])
+ check(session, payload + proof + signature, "Invalid definition")
+
+
def test_missing_signature(session: Session) -> None:
for make, check in _cases(session):
payload = make()
@@ -183,7 +204,7 @@ def test_bad_prefix(session: Session) -> None:
for make, check in _cases(session):
payload = make()
# mangle the magic, keep a valid version byte
- payload = b"trze1" + payload[5:]
+ payload = b"trze" + payload[4:]
proof, signature = sign_payload(payload, [])
check(session, payload + proof + signature, "Invalid definition")
### tests/ui_tests/fixtures.json
@@ -450,13 +450,13 @@
"T1B1_en_ethereum-test_definitions_bad.py::test_mangled_payload": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_mangled_signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_missing_signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
-"T1B1_en_ethereum-test_definitions_bad.py::test_not_enough_signatures": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_outdated": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_proof_length_mismatch": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_protobuf_mismatch": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_short_message": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_trailing_garbage": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_definitions_bad.py::test_trimmed_proof": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+"T1B1_en_ethereum-test_definitions_bad.py::test_v1_format_version_rejected": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1B1_en_ethereum-test_getaddress.py::test_getaddress[ETC]": "6c9f221da7bfc7c66f0332ef96130f3af353f4d5a8a57698093febaff9ec70ed",
"T1B1_en_ethereum-test_getaddress.py::test_getaddress[Ledger Live legacy path]": "bc8a1f830af1231939970782cb96ead9d160cd1acd6e998e3e0e81910bcb9be2",
"T1B1_en_ethereum-test_getaddress.py::test_getaddress[parameters0-result0]": "31ba4391758d58378afca4d00cd0b21dd0180bff35e3327c12b850e73e04e372",Why this scored 31/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.