embed/extmod: add modtrezori18n

i18n-block
Pavol Rusnak 4 years ago
parent 28b22cef22
commit b816fda7e0
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D

@ -130,6 +130,12 @@ if EVERYTHING:
'vendor/secp256k1-zkp/src/asm/field_10x26_arm.s'
]
# modtrezori18n
SOURCE_MOD += [
'embed/extmod/modtrezori18n/i18n-block.c',
'embed/extmod/modtrezori18n/modtrezori18n.c',
]
# modtrezorio
SOURCE_MOD += [
'embed/extmod/modtrezorio/ff.c',

@ -124,6 +124,12 @@ if EVERYTHING:
'vendor/secp256k1-zkp/src/secp256k1.c',
]
# modtrezori18n
SOURCE_MOD += [
'embed/extmod/modtrezori18n/i18n-block.c',
'embed/extmod/modtrezori18n/modtrezori18n.c',
]
# modtrezorio
SOURCE_MOD += [
'embed/extmod/modtrezorio/ff.c',

@ -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

@ -152,6 +152,7 @@
#define MICROPY_PY_TREZORCONFIG (1)
#define MICROPY_PY_TREZORCRYPTO (1)
#define MICROPY_PY_TREZORI18N (1)
#define MICROPY_PY_TREZORIO (1)
#define MICROPY_PY_TREZORUI (1)
#define MICROPY_PY_TREZORUTILS (1)

@ -184,6 +184,7 @@ extern const struct _mp_print_t mp_stderr_print;
#define MICROPY_PY_TREZORCONFIG (1)
#define MICROPY_PY_TREZORCRYPTO (1)
#define MICROPY_PY_TREZORI18N (1)
#define MICROPY_PY_TREZORIO (1)
#define MICROPY_PY_TREZORUI (1)
#define MICROPY_PY_TREZORUTILS (1)

@ -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…
Cancel
Save