1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-04 22:02:34 +00:00

WIP - improve blob generation

This commit is contained in:
matejcik 2024-01-29 13:46:11 +01:00
parent 413ccc4b77
commit 82cf2d9bc7

View File

@ -16,9 +16,17 @@ from ..tools import EnumAdapter, TupleAdapter
# All sections need to be aligned to 2 bytes for the offset tables using u16 to work properly # All sections need to be aligned to 2 bytes for the offset tables using u16 to work properly
ALIGNMENT = 2 ALIGNMENT = 2
# "align end of struct" subcon. The builtin c.Aligned does not do the right thing,
# because it assumes that the alignment is relative to the start of the subcon, not the
# start of the whole struct.
# TODO this spelling may or may not align in context of the stream as a whole (as
# opposed to the containing struct). This is prooobably not a problem -- we want the
# top-level alignment to always be ALIGNMENT anyway. But if someone were to use some
# of the structs separately, they might get a surprise. Maybe. Didn't test this.
ALIGN_SUBCON = c.Padding(
lambda ctx: (ALIGNMENT - (ctx._io.tell() % ALIGNMENT)) % ALIGNMENT
)
JsonTranslationData = t.Dict[str, t.Dict[str, str]]
TranslatedStrings = t.Dict[str, str]
JsonFontInfo = t.Dict[str, str] JsonFontInfo = t.Dict[str, str]
Order = t.Dict[int, str] Order = t.Dict[int, str]
@ -32,7 +40,7 @@ class JsonHeader(TypedDict):
class JsonDef(TypedDict): class JsonDef(TypedDict):
header: JsonHeader header: JsonHeader
translations: JsonTranslationData translations: dict[str, str]
fonts: dict[str, JsonFontInfo] fonts: dict[str, JsonFontInfo]
@ -54,7 +62,7 @@ def _version_to_tuple(version: str) -> tuple[int, int, int, int]:
return (*items, 0) return (*items, 0)
class TranslationsHeader(Struct): class Header(Struct):
language: str language: str
model: Model model: Model
firmware_version: tuple[int, int, int, int] firmware_version: tuple[int, int, int, int]
@ -73,13 +81,13 @@ class TranslationsHeader(Struct):
"data_hash" / c.Bytes(32), "data_hash" / c.Bytes(32),
"change_language_title" / c.PascalString(c.Int8ul, "utf8"), "change_language_title" / c.PascalString(c.Int8ul, "utf8"),
"change_language_prompt" / c.PascalString(c.Int8ul, "utf8"), "change_language_prompt" / c.PascalString(c.Int8ul, "utf8"),
c.Aligned(ALIGNMENT, c.Pass), ALIGN_SUBCON,
c.Terminated, c.Terminated,
) )
# fmt: on # fmt: on
class TranslationsProof(Struct): class Proof(Struct):
merkle_proof: list[bytes] merkle_proof: list[bytes]
sigmask: int sigmask: int
signature: bytes signature: bytes
@ -89,6 +97,7 @@ class TranslationsProof(Struct):
"merkle_proof" / c.PrefixedArray(c.Int8ul, c.Bytes(32)), "merkle_proof" / c.PrefixedArray(c.Int8ul, c.Bytes(32)),
"sigmask" / c.Byte, "sigmask" / c.Byte,
"signature" / c.Bytes(64), "signature" / c.Bytes(64),
ALIGN_SUBCON,
c.Terminated, c.Terminated,
) )
# fmt: on # fmt: on
@ -105,7 +114,7 @@ class BlobTable(Struct):
"_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1), "_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1),
"offsets" / c.Array(c.this._length + 1, TupleAdapter(c.Int16ul, c.Int16ul)), "offsets" / c.Array(c.this._length + 1, TupleAdapter(c.Int16ul, c.Int16ul)),
"data" / c.GreedyBytes, "data" / c.GreedyBytes,
c.Aligned(ALIGNMENT, c.Pass), ALIGN_SUBCON,
c.Terminated, c.Terminated,
) )
# fmt: on # fmt: on
@ -135,7 +144,7 @@ class BlobTable(Struct):
return None return None
class TranslationsData(Struct): class TranslatedStrings(Struct):
offsets: list[int] offsets: list[int]
strings: bytes strings: bytes
@ -143,7 +152,8 @@ class TranslationsData(Struct):
SUBCON = c.Struct( SUBCON = c.Struct(
"_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1), "_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1),
"offsets" / c.Array(c.this._length + 1, c.Int16ul), "offsets" / c.Array(c.this._length + 1, c.Int16ul),
"strings" / c.Aligned(ALIGNMENT, c.GreedyBytes), "strings" / c.GreedyBytes,
ALIGN_SUBCON,
c.Terminated, c.Terminated,
) )
# fmt: on # fmt: on
@ -214,7 +224,7 @@ class FontsTable(BlobTable):
# ========= # =========
class TranslationsPayload(Struct): class Payload(Struct):
translations_bytes: bytes translations_bytes: bytes
fonts_bytes: bytes fonts_bytes: bytes
@ -230,7 +240,7 @@ class TranslationsPayload(Struct):
class TranslationsBlob(Struct): class TranslationsBlob(Struct):
header_bytes: bytes header_bytes: bytes
proof_bytes: bytes proof_bytes: bytes
payload: TranslationsPayload = subcon(TranslationsPayload) payload: Payload = subcon(Payload)
# fmt: off # fmt: off
SUBCON = c.Struct( SUBCON = c.Struct(
@ -248,7 +258,7 @@ class TranslationsBlob(Struct):
"_start_offset" / c.Tell, "_start_offset" / c.Tell,
"header_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), "header_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes),
"proof_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), "proof_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes),
"payload" / TranslationsPayload.SUBCON, "payload" / Payload.SUBCON,
"_end_offset" / c.Tell, "_end_offset" / c.Tell,
c.Terminated, c.Terminated,
@ -258,60 +268,59 @@ class TranslationsBlob(Struct):
@property @property
def header(self): def header(self):
return TranslationsHeader.parse(self.header_bytes) return Header.parse(self.header_bytes)
@property @property
def proof(self): def proof(self):
return TranslationsProof.parse(self.proof_bytes) return Proof.parse(self.proof_bytes)
@proof.setter @proof.setter
def proof(self, proof: TranslationsProof): def proof(self, proof: Proof):
self.proof_bytes = proof.build() self.proof_bytes = proof.build()
@property @property
def translations(self): def translations(self):
return TranslationsData.parse(self.payload.translations_bytes) return TranslatedStrings.parse(self.payload.translations_bytes)
@property @property
def fonts(self): def fonts(self):
return FontsTable.parse(self.payload.fonts_bytes) return FontsTable.parse(self.payload.fonts_bytes)
def verify(self) -> None: def build(self) -> bytes:
header = self.header assert len(self.header_bytes) % ALIGNMENT == 0
data = self.payload.build() assert len(self.proof_bytes) % ALIGNMENT == 0
assert len(self.payload.translations_bytes) % ALIGNMENT == 0
assert header.data_len == len(data) assert len(self.payload.fonts_bytes) % ALIGNMENT == 0
assert header.data_hash == sha256(data).digest() return super().build()
# ==================== # ====================
def make_blob(dir: Path, lang: str, model: TrezorModel) -> TranslationsBlob: def order_from_json(json_order: dict[str, str]) -> Order:
lang_file = dir / f"{lang}.json" return {int(k): v for k, v in json_order.items()}
fonts_dir = dir / "fonts"
lang_data: JsonDef = json.loads(lang_file.read_text())
def blob_from_defs(
lang_data: JsonDef,
order: Order,
model: TrezorModel,
fonts_dir: Path,
) -> TranslationsBlob:
json_header: JsonHeader = lang_data["header"] json_header: JsonHeader = lang_data["header"]
json_order = json.loads((dir / "order.json").read_text())
order: Order = {int(k): v for k, v in json_order.items()}
# flatten translations
translations_flattened = {
f"{section}__{key}": value
for section, section_data in lang_data["translations"].items()
for key, value in section_data.items()
}
# order translations -- python dicts keep insertion order # order translations -- python dicts keep insertion order
translations_ordered = [ translations_ordered = [
translations_flattened.get(key, "") for _, key in sorted(order.items()) lang_data["translations"].get(key, "") for _, key in sorted(order.items())
] ]
translations = TranslationsData.from_items(translations_ordered) translations = TranslatedStrings.from_items(translations_ordered)
if model.internal_name not in lang_data["fonts"]: if model.internal_name not in lang_data["fonts"]:
raise ValueError(f"Model {model.internal_name} not found in {lang_file}") raise ValueError(
f"Model {model.internal_name} not found in header for {json_header['language']} v{json_header['version']}"
)
model_fonts = lang_data["fonts"][model.internal_name] model_fonts = lang_data["fonts"][model.internal_name]
fonts = FontsTable.from_dir(model_fonts, fonts_dir) fonts = FontsTable.from_dir(model_fonts, fonts_dir)
@ -320,13 +329,13 @@ def make_blob(dir: Path, lang: str, model: TrezorModel) -> TranslationsBlob:
fonts_bytes = fonts.build() fonts_bytes = fonts.build()
assert len(fonts_bytes) % ALIGNMENT == 0 assert len(fonts_bytes) % ALIGNMENT == 0
payload = TranslationsPayload( payload = Payload(
translations_bytes=translations_bytes, translations_bytes=translations_bytes,
fonts_bytes=fonts_bytes, fonts_bytes=fonts_bytes,
) )
data = payload.build() data = payload.build()
header = TranslationsHeader( header = Header(
language=json_header["language"], language=json_header["language"],
model=Model.from_trezor_model(model), model=Model.from_trezor_model(model),
firmware_version=_version_to_tuple(json_header["version"]), firmware_version=_version_to_tuple(json_header["version"]),
@ -341,3 +350,12 @@ def make_blob(dir: Path, lang: str, model: TrezorModel) -> TranslationsBlob:
proof_bytes=b"", proof_bytes=b"",
payload=payload, payload=payload,
) )
def blob_from_dir(dir: Path, lang: str, model: TrezorModel) -> TranslationsBlob:
lang_file = dir / f"{lang}.json"
fonts_dir = dir / "fonts"
json_order = json.loads((dir / "order.json").read_text())
lang_data = json.loads(lang_file.read_text())
order = order_from_json(json_order)
return blob_from_defs(lang_data, order, model, fonts_dir)