parent
28b22cef22
commit
b816fda7e0
@ -0,0 +1 @@
|
||||
*.dat
|
@ -0,0 +1,101 @@
|
||||
#include "i18n-block.h"
|
||||
#include <string.h>
|
||||
#include "blake2s.h"
|
||||
#include "ed25519-donna/ed25519.h"
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *ptr_items;
|
||||
const uint8_t *ptr_values;
|
||||
uint32_t items_count;
|
||||
uint32_t values_size;
|
||||
const uint8_t *label;
|
||||
char code[6];
|
||||
} i18n_block_t;
|
||||
|
||||
static bool i18n_initialized = false;
|
||||
static i18n_block_t block = {0};
|
||||
static ed25519_public_key i18n_pubkey = {0xa3, 0x0c, 0x46, 0x1c, 0xdd, 0x0c, 0xfe, 0xc9, 0x5f, 0xf4, 0xa6, 0xfe, 0x09, 0xc0, 0xd4, 0x7f, 0x5d, 0x2a, 0x18, 0x6c, 0xbc, 0x8b, 0x51, 0xd2, 0xad, 0xeb, 0x5c, 0xe3, 0xac, 0x3a, 0xa0, 0x64};
|
||||
|
||||
#ifdef TREZOR_EMULATOR
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static bool _i18n_load(const uint8_t *ptr) {
|
||||
// check magic
|
||||
if (0 != memcmp(ptr, "TRIB", 4)) {
|
||||
return false;
|
||||
}
|
||||
// check signature
|
||||
const ed25519_signature *sig = (const ed25519_signature *)(ptr + 0xC0);
|
||||
if (0 != ed25519_sign_open(ptr, 0xC0, i18n_pubkey, *sig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy language code
|
||||
block.code[0] = ptr[0x2C];
|
||||
block.code[1] = ptr[0x2D];
|
||||
block.code[2] = '-';
|
||||
block.code[3] = ptr[0x2E];
|
||||
block.code[4] = ptr[0x2F];
|
||||
block.code[5] = 0;
|
||||
// assign label
|
||||
block.label = ptr + 0x30;
|
||||
|
||||
// assign items/values
|
||||
memcpy(&(block.items_count), ptr + 4, sizeof(uint32_t));
|
||||
memcpy(&(block.values_size), ptr + 8, sizeof(uint32_t));
|
||||
block.ptr_items = ptr + 256;
|
||||
block.ptr_values = ptr + 256 + 4 * block.items_count;
|
||||
|
||||
// compute the hash of items + values
|
||||
uint8_t hash[BLAKE2S_DIGEST_LENGTH];
|
||||
blake2s(block.ptr_items, 4 * block.items_count + block.values_size, hash,
|
||||
BLAKE2S_DIGEST_LENGTH);
|
||||
// compare the hash
|
||||
if (0 != memcmp(ptr + 0x0C, hash, BLAKE2S_DIGEST_LENGTH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// all OK
|
||||
i18n_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool i18n_init(void) {
|
||||
#ifdef TREZOR_EMULATOR
|
||||
// TODO: this could use some love (similar to mmap in flash.c)
|
||||
int fd = open("i18n.dat", O_RDONLY);
|
||||
if (fd < 0) return false;
|
||||
struct stat s;
|
||||
fstat(fd, &s);
|
||||
const uint8_t *ptr =
|
||||
(const uint8_t *)mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
#else
|
||||
// the last 128K sector of flash
|
||||
const uint8_t *ptr = (const uint8_t *)0x081E0000;
|
||||
#endif
|
||||
return _i18n_load(ptr);
|
||||
}
|
||||
|
||||
|
||||
const char *i18n_get(uint16_t id, uint16_t *len) {
|
||||
if (!i18n_initialized || id >= block.items_count) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(len, block.ptr_items + 4 * id + 2, 2);
|
||||
if (*len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
uint32_t offset = 0;
|
||||
memcpy(&offset, block.ptr_items + 4 * id, 2);
|
||||
offset *= 4;
|
||||
if (offset + *len > block.values_size) {
|
||||
return NULL;
|
||||
}
|
||||
return (const char *)(block.ptr_values + offset);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __I18N_BLOCK_H__
|
||||
#define __I18N_BLOCK_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool i18n_init(void);
|
||||
const char *i18n_get(uint16_t id, uint16_t *len);
|
||||
|
||||
#endif
|
@ -0,0 +1,46 @@
|
||||
# Internationalization (i18n) Block
|
||||
|
||||
Fixed storage for array of "bytes" values meant to store localization strings
|
||||
and other internationalization data.
|
||||
|
||||
The block consists of 3 logical parts:
|
||||
|
||||
1. header (256 bytes)
|
||||
2. items section (variable size)
|
||||
3. values section (variable size)
|
||||
|
||||
## Header
|
||||
|
||||
This section has always 256 bytes.
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|-----------------|--------------|
|
||||
| 0x0000 | 4 | `magic` | magic `TRIB` |
|
||||
| 0x0004 | 4 | `items_count` | number of stored items |
|
||||
| 0x0008 | 4 | `values_size` | length of the values section |
|
||||
| 0x000C | 32 | `data_hash` | hash of the items + values sections |
|
||||
| 0x002C | 4 | `code` | BCP-47 language code without dash (e.g. "csCZ") |
|
||||
| 0x0030 | 32 | `label` | block label (e.g. "Czech") |
|
||||
| 0x0050 | 112 | `reserved` | - |
|
||||
| 0x00C0 | 64 | `sig` | signature of the header |
|
||||
|
||||
## Items Section
|
||||
|
||||
This section has size `4 * items_count`
|
||||
|
||||
Each item has the following structure:
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|----------|--------------|
|
||||
| ... | 2 | `offset` | offset of the data in the values section (divided by 4) |
|
||||
| ... | 2 | `length` | length of the data in the values section |
|
||||
|
||||
## Values Section
|
||||
|
||||
This section has size `values_size`
|
||||
|
||||
Each value has the following structure:
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|--------|--------------|
|
||||
| ... | variable | `data` | value data (padded to multiple of 4) |
|
@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
from hashlib import blake2s
|
||||
import ed25519
|
||||
|
||||
i18n_sk = ed25519.SigningKey(b"I18N" * 8)
|
||||
# i18n_vk = i18n_sk.get_verifying_key() # a30c461cdd0cfec95ff4a6fe09c0d47f5d2a186cbc8b51d2adeb5ce3ac3aa064
|
||||
|
||||
ids = json.load(open("i18n/ids.json"))
|
||||
if list(range(len(ids))) != sorted(ids.values()):
|
||||
raise ValueError("IDs are not a sequence starting with zero")
|
||||
|
||||
|
||||
class LocalizationBlock:
|
||||
def __init__(self, code):
|
||||
data = json.load(open("i18n/%s.json" % code, "rt"))
|
||||
self.code = code
|
||||
self.label = data["label"]
|
||||
del data["label"]
|
||||
if data.keys() != ids.keys():
|
||||
print(data.keys() - ids.keys(), ids.keys() - data.keys())
|
||||
raise ValueError("Keys mismatch")
|
||||
self.strings = [None] * len(data)
|
||||
for k in ids.keys():
|
||||
self.strings[ids[k]] = data[k]
|
||||
|
||||
def write(self, out):
|
||||
|
||||
header_bin = bytearray()
|
||||
items_bin = bytearray()
|
||||
values_bin = bytearray()
|
||||
values = {}
|
||||
|
||||
for s in self.strings:
|
||||
if s is None:
|
||||
items_bin.extend(b"\x00\x00\x00\x00") # null entry
|
||||
continue
|
||||
assert len(s) > 0
|
||||
assert len(s) < 65536
|
||||
assert len(values_bin) % 4 == 0
|
||||
if s in values: # reuse existing value
|
||||
offset = values[s]
|
||||
else: # add value if new
|
||||
offset = len(values_bin)
|
||||
values_bin.extend(s.encode()) # string
|
||||
if len(s) % 4 > 0: # pad to multiple of 4
|
||||
values_bin.extend(b"\x00" * (4 - (len(s) % 4)))
|
||||
values[s] = offset
|
||||
# add item
|
||||
items_bin.extend((offset // 4).to_bytes(2, byteorder="little")) # offset/4
|
||||
items_bin.extend(len(s).to_bytes(2, byteorder="little")) # length
|
||||
|
||||
assert len(items_bin) == 4 * len(self.strings)
|
||||
assert 256 + len(items_bin) + len(values_bin) <= 128 * 1024
|
||||
assert len(self.code) == 5
|
||||
assert self.code[2] == "-"
|
||||
assert len(self.label) < 32
|
||||
|
||||
# header section
|
||||
header_bin.extend(b"TRIB")
|
||||
header_bin.extend(len(self.strings).to_bytes(4, byteorder="little"))
|
||||
header_bin.extend(len(values_bin).to_bytes(4, byteorder="little"))
|
||||
header_bin.extend(blake2s(items_bin + values_bin).digest())
|
||||
header_bin.extend(self.code.replace("-", "").encode())
|
||||
header_bin.extend(self.label.encode().ljust(32, b"\x00"))
|
||||
header_bin.extend(b"\x00" * 112) # reserved
|
||||
header_bin.extend(i18n_sk.sign(bytes(header_bin)))
|
||||
out.write(header_bin)
|
||||
# items section
|
||||
out.write(items_bin)
|
||||
# values section
|
||||
out.write(values_bin)
|
||||
|
||||
|
||||
for lang in ["cs-CZ", "en-US"]:
|
||||
lb = LocalizationBlock(lang)
|
||||
lb.write(open("%s.dat" % lang, "wb"))
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Cestina",
|
||||
"TR_CANCEL": "Zrusit",
|
||||
"TR_OK": "OK",
|
||||
"TR_RECEIVE": "Prijmout",
|
||||
"TR_SEND": "Poslat",
|
||||
"TR_SIGN": "Podepsat"
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "English",
|
||||
"TR_CANCEL": "Cancel",
|
||||
"TR_OK": "OK",
|
||||
"TR_RECEIVE": "Receive",
|
||||
"TR_SEND": "Send",
|
||||
"TR_SIGN": "Sign"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"TR_CANCEL": 1,
|
||||
"TR_OK": 0,
|
||||
"TR_RECEIVE": 4,
|
||||
"TR_SEND": 3,
|
||||
"TR_SIGN": 2
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "embed/extmod/trezorobj.h"
|
||||
#include "py/objstr.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
#include "i18n-block.h"
|
||||
|
||||
#if MICROPY_PY_TREZORI18N
|
||||
|
||||
/// def init() -> None:
|
||||
/// """
|
||||
/// Gets
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezori18n_init(void) {
|
||||
i18n_init();
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezori18n_init_obj, mod_trezori18n_init);
|
||||
|
||||
/// def get(id: int) -> str:
|
||||
/// """
|
||||
/// Gets
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezori18n_get(mp_obj_t _id) {
|
||||
uint16_t id = trezor_obj_get_uint(_id);
|
||||
uint16_t len;
|
||||
const char *str = i18n_get(id, &len);
|
||||
if (!str) {
|
||||
return mp_const_none;
|
||||
}
|
||||
return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)str, len);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezori18n_get_obj, mod_trezori18n_get);
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_trezori18n_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezori18n)},
|
||||
{MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mod_trezori18n_init_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&mod_trezori18n_get_obj)},
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_trezori18n_globals,
|
||||
mp_module_trezori18n_globals_table);
|
||||
|
||||
const mp_obj_module_t mp_module_trezori18n = {
|
||||
.base = {&mp_type_module},
|
||||
.globals = (mp_obj_dict_t *)&mp_module_trezori18n_globals,
|
||||
};
|
||||
|
||||
MP_REGISTER_MODULE(MP_QSTR_trezori18n, mp_module_trezori18n,
|
||||
MICROPY_PY_TREZORI18N);
|
||||
|
||||
#endif // MICROPY_PY_TREZORI18N
|
@ -0,0 +1 @@
|
||||
from trezori18n import init, get
|
@ -1,40 +0,0 @@
|
||||
# Add-on Block
|
||||
|
||||
Fixed storage which stores an immutable binary-search tree of items.
|
||||
Each item consists of a pair of pointers to binary data of arbitrary length.
|
||||
These two pointers are interpreted as a `key` and a `value`.
|
||||
Since items contain only pointers, multiple instances of data are stored just once.
|
||||
|
||||
Block contains 3 logical parts:
|
||||
a) header (256 bytes)
|
||||
b) binary-search tree (variable size)
|
||||
c) item data (variable size)
|
||||
|
||||
## Header
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|--------------|--------------|
|
||||
| 0x0000 | 4 | magic | magic `TRAB` |
|
||||
| 0x0004 | 64 | sig | signature of the whole blob (except magic and sig) |
|
||||
| 0x0044 | 4 | tree_count | number of elements in the tree |
|
||||
| 0x0048 | 4 | items_size | length of the item section |
|
||||
| 0x004A | 182 | reserved | - |
|
||||
|
||||
## Binary-Search Tree
|
||||
|
||||
Each node of the tree has the following structure:
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|--------------|--------------|
|
||||
| ... | 2 | key_offset | key offset (divided by 4) |
|
||||
| ... | 2 | value_offset | value offset (divided by 4) |
|
||||
|
||||
# Item Data
|
||||
|
||||
Each item has the following structure:
|
||||
|
||||
| offset | length | name | description |
|
||||
|-------:|---------:|--------------|--------------|
|
||||
| ... | 2 | item_len | item (key or value) length |
|
||||
| ... | 2 | item_flags | item (key or value) flags |
|
||||
| ... | item_len | item_data | item (key or value) data (padded to multiple of 4) |
|
Loading…
Reference in new issue