feat(core,python): support definitions version 2
What changed, and why it matters
This commit adds support for a new 'version 2' format for external definitions in Trezor firmware. The key change is that version 2 requires only one cryptographic signature to validate a definition, whereas version 1 required two. This is a deliberate design change, not a bug, but it reduces the number of independent parties that must sign off before a definition is trusted by the device. The commit does not by itself introduce an exploit, but it lowers the bar for an attacker who could compromise a single signing key or signer.
Treat this as a protocol change rather than a vulnerability. Review the CoSi signer governance model to ensure a single compromised signer cannot push malicious token/network definitions, and confirm downgrade protections prevent attackers from forcing V1 definitions to V2 or vice versa. Consider whether the security implications of the reduced threshold warrant a changelog or advisory note.
Security signals we found
Reduction in signature threshold from 2-of-N to 1-of-N for new format version
New format version accepted by both firmware and Python library
Documentation explicitly states backward-incompatible change in required signatures
No changelog entry despite a wire-format change
Evidence from the diff
The patch extends the external-definitions format to version 2. It updates Rust and Python constants to accept byte ‘2’ as a valid version, sets the CoSi signature threshold to 1 for V2 (vs 2 for V1), adds a unit test verifying a single signature is accepted for V2, and documents that V2 changes the required signature count from 2 to 1. The data structure itself is unchanged.
Changed components
Trezor Core firmware external definitions verifier (Rust)Trezor Core Python constants for definitionstrezorlib Python definitions handlingExternal definitions wire format / protocolInspect captured patch +16 / −3
### core/embed/rust/src/definitions/constants.rs
@@ -3,19 +3,22 @@ use crypto::ed25519;
// Definition format versions, encoded on the wire as ASCII digit bytes.
pub enum DefsVersion {
V1,
+ V2,
}
impl DefsVersion {
// CoSi signature threshold for this version.
pub const fn threshold(self) -> u8 {
match self {
DefsVersion::V1 => 2,
+ DefsVersion::V2 => 1,
}
}
pub const fn from_byte(byte: u8) -> Option<Self> {
match byte {
b'1' => Some(DefsVersion::V1),
+ b'2' => Some(DefsVersion::V2),
_ => None,
}
}
### core/src/apps/common/definitions_constants.py
@@ -7,7 +7,7 @@
# Supported format versions of the definitions, encoded on the wire as
# ASCII digit bytes ('1' = 0x31, '2' = 0x32, etc.).
-SUPPORTED_FORMAT_VERSIONS = (b"1",)
+SUPPORTED_FORMAT_VERSIONS = (b"1", b"2")
# The public keys and signature thresholds for definitions verification
# live in Rust (core/embed/rust/src/definitions/constants.rs).
### core/src/apps/common/definitions_constants.py.mako
@@ -7,7 +7,7 @@ MAGIC = b"trzd"
# Supported format versions of the definitions, encoded on the wire as
# ASCII digit bytes ('1' = 0x31, '2' = 0x32, etc.).
-SUPPORTED_FORMAT_VERSIONS = (b"1",)
+SUPPORTED_FORMAT_VERSIONS = (b"1", b"2")
# The public keys and signature thresholds for definitions verification
# live in Rust (core/embed/rust/src/definitions/constants.rs).
### core/tests/test_apps.common.definitions.py
@@ -57,6 +57,14 @@ def test_not_enough_signatures(self):
proof, signature = sign_payload(payload, [], threshold=1)
self.assertFailed(payload + proof + signature)
+ def test_v2_single_signature(self):
+ payload = make_payload(format_version=b"2")
+ proof, signature = sign_payload(payload, [], threshold=1)
+ self.assertEqual(
+ decode_definition(payload + proof + signature, EthereumNetworkInfo),
+ make_eth_network(),
+ )
+
def test_missing_signature(self):
payload = make_payload()
proof, _ = sign_payload(payload, [])
### docs/common/external-definitions.md
@@ -119,7 +119,7 @@ A Merkle tree is constructed from all binary definitions (see below) and its roo
signed by the CoSi algorithm.
The format version is bumped on backward incompatible change.
-For versions 1 and 2, the data structure is identical.
+For versions 1 and 2, the data structure is identical but the number of necessary signatures is changed from 2 to 1.
The full format of the definition is as follows:
### python/src/trezorlib/definitions.py
@@ -40,8 +40,10 @@
# Number of CoSi signatures required by definition format version.
# Version 1 requires 2 signatures.
+# Version 2 requires 1 signature.
DEFINITIONS_SIGS_REQUIRED = {
b"1": 2,
+ b"2": 1,
}
DEFINITIONS_PUBLIC_KEYS = [
bytes.fromhex(key)Why this scored 29/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.