diff --git a/ci/shell.nix b/ci/shell.nix
index c2648b61c..693dc5e1e 100644
--- a/ci/shell.nix
+++ b/ci/shell.nix
@@ -2,25 +2,35 @@
, hardwareTest ? false
}:
-# the last successful build of nixpkgs-unstable as of 2021-03-25
-with import (builtins.fetchTarball {
- url = "https://github.com/NixOS/nixpkgs/archive/c0e881852006b132236cbf0301bd1939bb50867e.tar.gz";
- sha256 = "0fy7z7yxk5n7yslsvx5cyc6h21qwi4bhxf3awhirniszlbvaazy2";
-})
-{ };
-
let
- moneroTests = fetchurl {
+ mozillaOverlay = import (builtins.fetchTarball {
+ url = "https://github.com/mozilla/nixpkgs-mozilla/archive/8c007b60731c07dd7a052cce508de3bb1ae849b4.tar.gz";
+ sha256 = "1zybp62zz0h077zm2zmqs2wcg3whg6jqaah9hcl1gv4x8af4zhs6";
+ });
+ # the last successful build of nixpkgs-unstable as of 2021-03-25
+ nixpkgs = import (builtins.fetchTarball {
+ url = "https://github.com/NixOS/nixpkgs/archive/c0e881852006b132236cbf0301bd1939bb50867e.tar.gz";
+ sha256 = "0fy7z7yxk5n7yslsvx5cyc6h21qwi4bhxf3awhirniszlbvaazy2";
+ }) { overlays = [ mozillaOverlay ]; };
+ moneroTests = nixpkgs.fetchurl {
url = "https://github.com/ph4r05/monero/releases/download/v0.17.1.9-tests/trezor_tests";
sha256 = "410bc4ff2ff1edc65e17f15b549bd1bf8a3776cf67abdea86aed52cf4bce8d9d";
};
- moneroTestsPatched = runCommandCC "monero_trezor_tests" {} ''
+ moneroTestsPatched = nixpkgs.runCommandCC "monero_trezor_tests" {} ''
cp ${moneroTests} $out
chmod +wx $out
- ${patchelf}/bin/patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out"
+ ${nixpkgs.patchelf}/bin/patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out"
chmod -w $out
'';
+ rustNightly = (nixpkgs.rustChannelOf { date = "2021-03-29"; channel = "nightly"; }).rust.override {
+ targets = [
+ "x86_64-unknown-linux-gnu" # emulator
+ "thumbv7em-none-eabihf" # TT
+ "thumbv7em-none-eabi" # T1
+ ];
+ };
in
+with nixpkgs;
stdenv.mkDerivation ({
name = "trezor-firmware-env";
buildInputs = lib.optionals fullDeps [
@@ -38,6 +48,7 @@ stdenv.mkDerivation ({
bash
check
clang-tools
+ clang
editorconfig-checker
gcc
gcc-arm-embedded
@@ -52,6 +63,8 @@ stdenv.mkDerivation ({
pkgconfig
poetry
protobuf3_6
+ rustfmt
+ rustNightly
wget
zlib
] ++ lib.optionals (!stdenv.isDarwin) [
@@ -80,6 +93,8 @@ stdenv.mkDerivation ({
# Fix bdist-wheel problem by setting source date epoch to a more recent date
SOURCE_DATE_EPOCH = 1600000000;
+ # Used by rust bindgen
+ LIBCLANG_PATH = "${llvmPackages.libclang}/lib";
} // (lib.optionalAttrs fullDeps) {
TREZOR_MONERO_TESTS_PATH = moneroTestsPatched;
})
diff --git a/core/.changelog.d/1540.added b/core/.changelog.d/1540.added
new file mode 100644
index 000000000..736131c0b
--- /dev/null
+++ b/core/.changelog.d/1540.added
@@ -0,0 +1 @@
+Rust FFI for MicroPython.
diff --git a/core/Makefile b/core/Makefile
index 8790fbbc7..e78fdba4c 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -12,6 +12,7 @@ PRODTEST_BUILD_DIR = $(BUILD_DIR)/prodtest
REFLASH_BUILD_DIR = $(BUILD_DIR)/reflash
FIRMWARE_BUILD_DIR = $(BUILD_DIR)/firmware
UNIX_BUILD_DIR = $(BUILD_DIR)/unix
+RUST_BUILD_DIR = $(BUILD_DIR)/rust
UNAME_S := $(shell uname -s)
UNIX_PORT_OPTS ?=
@@ -179,10 +180,10 @@ clean_reflash: ## clean reflash build
rm -rf $(REFLASH_BUILD_DIR)
clean_firmware: ## clean firmware build
- rm -rf $(FIRMWARE_BUILD_DIR)
+ rm -rf $(FIRMWARE_BUILD_DIR) $(RUST_BUILD_DIR)
clean_unix: ## clean unix build
- rm -rf $(UNIX_BUILD_DIR)
+ rm -rf $(UNIX_BUILD_DIR) $(RUST_BUILD_DIR)
clean_cross: ## clean mpy-cross build
$(MAKE) -C vendor/micropython/mpy-cross clean $(CROSS_PORT_OPTS)
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 0aab59cee..2ff590f5d 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -1,4 +1,5 @@
# pylint: disable=E0602
+# fmt: off
import os
@@ -49,6 +50,7 @@ CPPDEFINES_MOD += [
('USE_EOS', '1' if EVERYTHING else '0'),
]
SOURCE_MOD += [
+ 'embed/extmod/trezorobj.c',
'embed/extmod/modtrezorcrypto/crc.c',
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/rand.c',
@@ -394,11 +396,13 @@ if TREZOR_MODEL == 'T':
CPU_CCFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 '
CPU_MODEL = 'STM32F427xx'
LD_VARIANT = '_zkp' if FEATURE_FLAGS["SECP256K1_ZKP"] else ''
+ RUST_TARGET = 'thumbv7em-none-eabihf'
elif TREZOR_MODEL == '1':
CPU_ASFLAGS = '-mthumb -mcpu=cortex-m3 -mfloat-abi=soft'
CPU_CCFLAGS = '-mthumb -mtune=cortex-m3 -mcpu=cortex-m3 -mfloat-abi=soft '
CPU_MODEL = 'STM32F405xx'
LD_VARIANT = '' if EVERYTHING else '_min'
+ RUST_TARGET = 'thumbv7em-none-eabi'
else:
raise ValueError('Unknown Trezor model')
@@ -416,6 +420,7 @@ env.Replace(
LINKFLAGS='-T embed/firmware/memory_${TREZOR_MODEL}%s.ld -Wl,--gc-sections -Wl,-Map=build/firmware/firmware.map -Wl,--warn-common' % LD_VARIANT,
CPPPATH=[
'.',
+ 'embed/rust',
'embed/firmware',
'embed/trezorhal',
'embed/extmod/modtrezorui',
@@ -616,6 +621,33 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
action='cd ${SOURCE.dir}; ./gen_context',
)
+#
+# Rust library
+#
+
+RUST_PROFILE = 'release'
+RUST_LIB = 'trezor_lib'
+RUST_LIBDIR = f'build/rust/{RUST_TARGET}/{RUST_PROFILE}'
+RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
+
+def cargo_build():
+ # Determine the profile build flags.
+ if RUST_PROFILE == 'release':
+ profile = '--release'
+ else:
+ profile = ''
+ return f'cd embed/rust; cargo build {profile} --target={RUST_TARGET}'
+
+rust = env.Command(
+ target=RUST_LIBPATH,
+ source='',
+ action=cargo_build(), )
+
+env.Depends(rust, qstr_generated)
+
+env.Append(LINKFLAGS=f' -L{RUST_LIBDIR}')
+env.Append(LINKFLAGS=f' -l{RUST_LIB}')
+
#
# Program objects
#
@@ -658,9 +690,11 @@ program_elf = env.Command(
target='firmware.elf',
source=obj_program,
action=
- '$LINK -o $TARGET $CCFLAGS $CFLAGS $LINKFLAGS $SOURCES -lc_nano -lm -lgcc',
+ '$LINK -o $TARGET $CCFLAGS $CFLAGS $SOURCES $LINKFLAGS -lc_nano -lm -lgcc',
)
+env.Depends(program_elf, rust)
+
if TREZOR_MODEL == 'T':
action_bin=[
'$OBJCOPY -O binary -j .vendorheader -j .header -j .flash -j .data --pad-to 0x08100000 $SOURCE ${TARGET}.p1',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 2dbe27d05..5f232631d 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -1,4 +1,5 @@
# pylint: disable=E0602
+# fmt: off
import os
@@ -48,6 +49,7 @@ CPPDEFINES_MOD += [
('USE_EOS', '1' if EVERYTHING else '0'),
]
SOURCE_MOD += [
+ 'embed/extmod/trezorobj.c',
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/crc.c',
'vendor/trezor-crypto/address.c',
@@ -367,6 +369,7 @@ env.Replace(
LIBS=['m'],
CPPPATH=[
'.',
+ 'embed/rust',
'embed/unix',
'embed/extmod/modtrezorui',
'vendor/micropython',
@@ -567,6 +570,33 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
action='cd ${SOURCE.dir}; ./gen_context',
)
+#
+# Rust library
+#
+
+RUST_PROFILE = 'release'
+RUST_LIB = 'trezor_lib'
+RUST_LIBDIR = f'build/rust/{RUST_PROFILE}'
+RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
+
+def cargo_build():
+ # Determine the profile build flags.
+ if RUST_PROFILE == 'release':
+ profile = '--release'
+ else:
+ profile = ''
+ return f'cd embed/rust; cargo build {profile}'
+
+rust = env.Command(
+ target=RUST_LIBPATH,
+ source='',
+ action=cargo_build(), )
+
+env.Depends(rust, qstr_generated)
+
+env.Append(LINKFLAGS=f'-L{RUST_LIBDIR}')
+env.Append(LINKFLAGS=f'-l{RUST_LIB}')
+
#
# Program objects
#
@@ -588,3 +618,5 @@ program = env.Command(
target='trezor-emu-core',
source=obj_program,
action='$CC -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $LINKFLAGS', )
+
+env.Depends(program, rust)
diff --git a/core/embed/extmod/trezorobj.c b/core/embed/extmod/trezorobj.c
new file mode 100644
index 000000000..518d44db2
--- /dev/null
+++ b/core/embed/extmod/trezorobj.c
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+
+#include
+
+#include "memzero.h"
+#include "py/objint.h"
+
+static bool mpz_as_ll_checked(const mpz_t *i, long long *value) {
+ // Analogue of `mpz_as_int_checked` from mpz.c
+
+ unsigned long long val = 0;
+ mpz_dig_t *d = i->dig + i->len;
+
+ while (d-- > i->dig) {
+ if (val > (~0x8000000000000000 >> MPZ_DIG_SIZE)) {
+ // will overflow
+ *value = 0;
+ return false;
+ }
+ val = (val << MPZ_DIG_SIZE) | *d;
+ }
+
+ if (i->neg != 0) {
+ val = -val;
+ }
+
+ *value = val;
+ return true;
+}
+
+bool trezor_obj_get_ll_checked(mp_obj_t obj, long long *value) {
+ if (mp_obj_is_small_int(obj)) {
+ // Value is fitting in a small int range. Return it directly.
+ *value = MP_OBJ_SMALL_INT_VALUE(obj);
+ return true;
+
+ } else if (mp_obj_is_type(obj, &mp_type_int)) {
+ // Value is not fitting into small int range, but is an integer.
+ mp_obj_int_t *self = MP_OBJ_TO_PTR(obj);
+ // Try to get the long long value out of the MPZ struct.
+ return mpz_as_ll_checked(&self->mpz, value);
+ } else {
+ // Value is not integer.
+ *value = 0;
+ return false;
+ }
+}
diff --git a/core/embed/extmod/trezorobj.h b/core/embed/extmod/trezorobj.h
index 1c984ec1d..98a53703b 100644
--- a/core/embed/extmod/trezorobj.h
+++ b/core/embed/extmod/trezorobj.h
@@ -75,4 +75,6 @@ static inline uint8_t trezor_obj_get_uint8(mp_obj_t obj) {
return u;
}
+bool trezor_obj_get_ll_checked(mp_obj_t obj, long long *value);
+
#endif
diff --git a/core/embed/firmware/memory_1.ld b/core/embed/firmware/memory_1.ld
index a6669973c..abd745fd9 100644
--- a/core/embed/firmware/memory_1.ld
+++ b/core/embed/firmware/memory_1.ld
@@ -26,7 +26,8 @@ sram_end = ORIGIN(SRAM) + LENGTH(SRAM);
_ram_start = sram_start;
_ram_end = sram_end;
-_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx);
+/* .ARM.exidx.text.__aeabi_ui2f is probably not needed as long as we are using panic = "abort" */
+_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx) + SIZEOF(.ARM.exidx.text.__aeabi_ui2f);
_flash_start = ORIGIN(FLASH);
_flash_end = ORIGIN(FLASH) + LENGTH(FLASH);
_heap_start = ADDR(.heap);
diff --git a/core/embed/firmware/memory_1_min.ld b/core/embed/firmware/memory_1_min.ld
index 34bbbfb8f..0a8ec38ac 100644
--- a/core/embed/firmware/memory_1_min.ld
+++ b/core/embed/firmware/memory_1_min.ld
@@ -26,7 +26,8 @@ sram_end = ORIGIN(SRAM) + LENGTH(SRAM);
_ram_start = sram_start;
_ram_end = sram_end;
-_codelen = SIZEOF(.flash) + SIZEOF(.data);
+/* .ARM.exidx.text.__aeabi_ui2f is probably not needed as long as we are using panic = "abort" */
+_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx.text.__aeabi_ui2f);
_flash_start = ORIGIN(FLASH);
_flash_end = ORIGIN(FLASH) + LENGTH(FLASH);
_heap_start = ADDR(.heap);
diff --git a/core/embed/rust/.cargo/config.toml b/core/embed/rust/.cargo/config.toml
new file mode 100644
index 000000000..e0a2aaa11
--- /dev/null
+++ b/core/embed/rust/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target-dir = "../../build/rust"
diff --git a/core/embed/rust/Cargo.lock b/core/embed/rust/Cargo.lock
new file mode 100644
index 000000000..0e4653490
--- /dev/null
+++ b/core/embed/rust/Cargo.lock
@@ -0,0 +1,385 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5d2549a1ca679efa833b9d88a424955109722c9fd95418b95414c19fd6f4bda"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cexpr"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cstr_core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26bdf52fc09b421407bd990b4662a380420b6b6f61708bcba417731dca1b65ef"
+dependencies = [
+ "cty",
+ "memchr",
+]
+
+[[package]]
+name = "cty"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3"
+
+[[package]]
+name = "env_logger"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
+
+[[package]]
+name = "libloading"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "shlex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
+
+[[package]]
+name = "staticvec"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c87f4be0fd89694157f3814ca88715ad8ba6010c453b1e89ca264aee04d70b9"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "trezor_lib"
+version = "0.1.0"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cstr_core",
+ "cty",
+ "staticvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
new file mode 100644
index 000000000..f2c7f11cc
--- /dev/null
+++ b/core/embed/rust/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "trezor_lib"
+version = "0.1.0"
+authors = ["SatoshiLabs "]
+edition = "2018"
+resolver = "2"
+build = "build.rs"
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.dev]
+panic = "abort"
+split-debuginfo = "unpacked"
+
+[profile.release]
+panic = "abort"
+opt-level = "z"
+lto = true
+codegen-units = 1
+
+[dependencies]
+cty = "0.2.1"
+
+[dependencies.staticvec]
+version = "0.10.5"
+default_features = false
+
+[dependencies.cstr_core]
+version = "0.2.2"
+default_features = false
+
+[build-dependencies]
+bindgen = "0.58.0"
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
new file mode 100644
index 000000000..c675e4592
--- /dev/null
+++ b/core/embed/rust/build.rs
@@ -0,0 +1,166 @@
+use std::{env, path::PathBuf, process::Command};
+
+fn main() {
+ generate_qstr_bindings();
+ generate_micropython_bindings();
+}
+
+/// Generates Rust module that exports QSTR constants used in firmware.
+fn generate_qstr_bindings() {
+ let out_path = env::var("OUT_DIR").unwrap();
+ let target = env::var("TARGET").unwrap();
+
+ // Tell cargo to invalidate the built crate whenever the header changes.
+ println!("cargo:rerun-if-changed=qstr.h");
+
+ bindgen::Builder::default()
+ .header("qstr.h")
+ // Build the Qstr enum as a newtype so we can define method on it.
+ .default_enum_style(bindgen::EnumVariation::NewType { is_bitfield: false })
+ // Pass in correct include paths.
+ .clang_args(&[
+ "-I",
+ if target.starts_with("thumbv7em-none-eabi") {
+ "../../build/firmware"
+ } else {
+ "../../build/unix"
+ },
+ ])
+ // Customize the standard types.
+ .use_core()
+ .ctypes_prefix("cty")
+ .size_t_is_usize(true)
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files change.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ .generate()
+ .expect("Unable to generate Rust QSTR bindings")
+ .write_to_file(PathBuf::from(out_path).join("qstr.rs"))
+ .unwrap();
+}
+
+fn generate_micropython_bindings() {
+ let out_path = env::var("OUT_DIR").unwrap();
+ let target = env::var("TARGET").unwrap();
+
+ // Tell cargo to invalidate the built crate whenever the header changes.
+ println!("cargo:rerun-if-changed=micropython.h");
+
+ let mut bindings = bindgen::Builder::default()
+ .header("micropython.h")
+ // obj
+ .new_type_alias("mp_obj_t")
+ .allowlist_type("mp_obj_type_t")
+ .allowlist_type("mp_obj_base_t")
+ .allowlist_function("mp_obj_new_int")
+ .allowlist_function("mp_obj_new_int_from_ll")
+ .allowlist_function("mp_obj_new_int_from_ull")
+ .allowlist_function("mp_obj_new_int_from_uint")
+ .allowlist_function("mp_obj_new_bytes")
+ .allowlist_function("mp_obj_new_str")
+ .allowlist_function("mp_obj_get_int_maybe")
+ .allowlist_function("mp_obj_is_true")
+ .allowlist_function("mp_call_function_n_kw")
+ .allowlist_function("trezor_obj_get_ll_checked")
+ .allowlist_function("trezor_obj_get_ull_checked")
+ // buffer
+ .allowlist_function("mp_get_buffer")
+ .allowlist_var("MP_BUFFER_READ")
+ .allowlist_var("MP_BUFFER_WRITE")
+ .allowlist_var("MP_BUFFER_RW")
+ // dict
+ .allowlist_type("mp_obj_dict_t")
+ .allowlist_function("mp_obj_new_dict")
+ .allowlist_function("mp_obj_dict_store")
+ .allowlist_var("mp_type_dict")
+ // fun
+ .allowlist_type("mp_obj_fun_builtin_fixed_t")
+ .allowlist_var("mp_type_fun_builtin_1")
+ .allowlist_var("mp_type_fun_builtin_2")
+ .allowlist_var("mp_type_fun_builtin_3")
+ // gc
+ .allowlist_function("gc_alloc")
+ // iter
+ .allowlist_type("mp_obj_iter_buf_t")
+ .allowlist_function("mp_getiter")
+ .allowlist_function("mp_iternext")
+ // list
+ .allowlist_type("mp_obj_list_t")
+ .allowlist_function("mp_obj_new_list")
+ .allowlist_function("mp_obj_list_append")
+ .allowlist_var("mp_type_list")
+ // map
+ .allowlist_type("mp_map_elem_t")
+ .allowlist_type("mp_map_lookup_kind_t")
+ .allowlist_function("mp_map_init")
+ .allowlist_function("mp_map_init_fixed_table")
+ .allowlist_function("mp_map_lookup")
+ // runtime
+ .allowlist_function("mp_raise_ValueError")
+ // typ
+ .allowlist_var("mp_type_type");
+
+ // `ffi::mp_map_t` type is not allowed to be `Clone` or `Copy` because we tie it
+ // to the data lifetimes with the `MapRef` type, see `src/micropython/map.rs`.
+ // TODO: We should disable `Clone` and `Copy` for all types and only allow-list
+ // the specific cases we require.
+ bindings = bindings.no_copy("_mp_map_t");
+
+ // Pass in correct include paths and defines.
+ if target.starts_with("thumbv7em-none-eabi") {
+ bindings = bindings.clang_args(&[
+ "-nostdinc",
+ "-I../firmware",
+ "-I../trezorhal",
+ "-I../../build/firmware",
+ "-I../../vendor/micropython",
+ "-I../../vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Inc",
+ "-I../../vendor/micropython/lib/stm32lib/CMSIS/STM32F4xx/Include",
+ "-I../../vendor/micropython/lib/cmsis/inc",
+ "-DTREZOR_MODEL=T",
+ "-DSTM32F405xx",
+ "-DUSE_HAL_DRIVER",
+ "-DSTM32_HAL_H=",
+ ]);
+ // Append gcc-arm-none-eabi's include paths.
+ let cc_output = Command::new("arm-none-eabi-gcc")
+ .arg("-E")
+ .arg("-Wp,-v")
+ .arg("-")
+ .output()
+ .expect("arm-none-eabi-gcc failed to execute");
+ if !cc_output.status.success() {
+ panic!("arm-none-eabi-gcc failed");
+ }
+ let include_paths =
+ String::from_utf8(cc_output.stderr).expect("arm-none-eabi-gcc returned invalid output");
+ let include_args = include_paths
+ .lines()
+ .skip_while(|s| !s.contains("search starts here:"))
+ .take_while(|s| !s.contains("End of search list."))
+ .filter(|s| s.starts_with(" "))
+ .map(|s| format!("-I{}", s.trim()));
+
+ bindings = bindings.clang_args(include_args);
+ } else {
+ bindings = bindings.clang_args(&[
+ "-I../unix",
+ "-I../../build/unix",
+ "-I../../vendor/micropython",
+ ]);
+ }
+
+ bindings
+ // Customize the standard types.
+ .use_core()
+ .ctypes_prefix("cty")
+ .size_t_is_usize(true)
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files change.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ // Write the bindings to a file in the OUR_DIR.
+ .generate()
+ .expect("Unable to generate Rust Micropython bindings")
+ .write_to_file(PathBuf::from(out_path).join("micropython.rs"))
+ .unwrap();
+}
diff --git a/core/embed/rust/micropython.h b/core/embed/rust/micropython.h
new file mode 100644
index 000000000..9838a9cd3
--- /dev/null
+++ b/core/embed/rust/micropython.h
@@ -0,0 +1,5 @@
+#include "py/gc.h"
+#include "py/obj.h"
+#include "py/runtime.h"
+
+#include "../extmod/trezorobj.h"
diff --git a/core/embed/rust/qstr.h b/core/embed/rust/qstr.h
new file mode 100644
index 000000000..70348fb52
--- /dev/null
+++ b/core/embed/rust/qstr.h
@@ -0,0 +1,5 @@
+enum Qstr {
+#define QDEF(id, str) id,
+#include "genhdr/qstrdefs.generated.h"
+#undef QDEF
+};
diff --git a/core/embed/rust/rust-toolchain b/core/embed/rust/rust-toolchain
new file mode 100644
index 000000000..c1a4c6bb4
--- /dev/null
+++ b/core/embed/rust/rust-toolchain
@@ -0,0 +1,2 @@
+nightly
+
diff --git a/core/embed/rust/rustfmt.toml b/core/embed/rust/rustfmt.toml
new file mode 100644
index 000000000..859f26e7c
--- /dev/null
+++ b/core/embed/rust/rustfmt.toml
@@ -0,0 +1,2 @@
+wrap_comments = true
+
diff --git a/core/embed/rust/src/error.rs b/core/embed/rust/src/error.rs
new file mode 100644
index 000000000..8cb2622af
--- /dev/null
+++ b/core/embed/rust/src/error.rs
@@ -0,0 +1,49 @@
+use core::convert::Infallible;
+use core::fmt;
+
+use cstr_core::CStr;
+
+pub enum Error {
+ Missing,
+ OutOfRange,
+ InvalidType,
+ NotBuffer,
+ NotInt,
+}
+
+impl Error {
+ pub fn as_cstr(&self) -> &'static CStr {
+ // SAFETY: Safe because we are passing in \0-terminated literals.
+ unsafe {
+ let cstr = |s: &'static str| CStr::from_bytes_with_nul_unchecked(s.as_bytes());
+ match self {
+ Error::Missing => cstr("Missing\0"),
+ Error::OutOfRange => cstr("OutOfRange\0"),
+ Error::InvalidType => cstr("InvalidType\0"),
+ Error::NotBuffer => cstr("NotBuffer\0"),
+ Error::NotInt => cstr("NotInt\0"),
+ }
+ }
+ }
+}
+
+impl From for &'static CStr {
+ fn from(val: Error) -> Self {
+ val.as_cstr()
+ }
+}
+
+impl fmt::Debug for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.as_cstr().fmt(f)
+ }
+}
+
+// Implements a conversion from `core::convert::Infallible` to `Error` to so
+// that code generic over `TryFrom` can work with values covered by the blanket
+// impl for `Into`: `https://doc.rust-lang.org/std/convert/enum.Infallible.html`
+impl From for Error {
+ fn from(_: Infallible) -> Self {
+ unreachable!()
+ }
+}
diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs
new file mode 100644
index 000000000..ae28ac1b6
--- /dev/null
+++ b/core/embed/rust/src/lib.rs
@@ -0,0 +1,37 @@
+#![cfg_attr(not(test), no_std)]
+#![feature(never_type)]
+#![feature(unsize)]
+#![feature(coerce_unsized)]
+#![feature(dispatch_from_dyn)]
+#![deny(clippy::all)]
+#![deny(unsafe_op_in_unsafe_fn)]
+#![allow(dead_code)]
+
+mod error;
+#[macro_use]
+mod micropython;
+mod trezorhal;
+mod util;
+
+#[cfg(not(test))]
+use core::panic::PanicInfo;
+#[cfg(not(test))]
+use cstr_core::CStr;
+
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ // Although it would be ideal to use the original error message, ignoring it
+ // lets us avoid the `fmt` machinery and its code size and is also important for
+ // security reasons, as we do not always controls the message contents. We
+ // should also avoid printing "panic" or "rust" on the user screen to avoid any
+ // confusion.
+
+ // SAFETY: Safe because we are passing in \0-terminated literals.
+ let empty = unsafe { CStr::from_bytes_with_nul_unchecked("\0".as_bytes()) };
+ let msg = unsafe { CStr::from_bytes_with_nul_unchecked("rs\0".as_bytes()) };
+
+ // TODO: Ideally we would take the file and line info out of
+ // `PanicInfo::location()`.
+ trezorhal::common::fatal_error(&empty, &msg, &empty, 0, &empty);
+}
diff --git a/core/embed/rust/src/micropython/buffer.rs b/core/embed/rust/src/micropython/buffer.rs
new file mode 100644
index 000000000..49930cbf1
--- /dev/null
+++ b/core/embed/rust/src/micropython/buffer.rs
@@ -0,0 +1,66 @@
+use core::{convert::TryFrom, ops::Deref, ptr, slice};
+
+use crate::{error::Error, micropython::obj::Obj};
+
+use super::ffi;
+
+/// Represents an immutable slice of bytes stored on the MicroPython heap and
+/// owned by values that obey the buffer protocol, such as `bytes`, `str`,
+/// `bytearray` or `memoryview`.
+///
+/// # Safety
+///
+/// In most cases, it is unsound to store `Buffer` values in a GC-unreachable
+/// location, such as static data.
+pub struct Buffer {
+ ptr: *const u8,
+ len: usize,
+}
+
+impl TryFrom for Buffer {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let mut bufinfo = ffi::mp_buffer_info_t {
+ buf: ptr::null_mut(),
+ len: 0,
+ typecode: 0,
+ };
+ // SAFETY: We assume that if `ffi::mp_get_buffer` returns successfully,
+ // `bufinfo.buf` contains a pointer to data of `bufinfo.len` bytes. Here
+ // we consider this data either GC-allocated or effectively 'static, and
+ // store the pointer directly in `Buffer`. It is unsound to store
+ // `Buffer` values in a GC-unreachable location, such as static data.
+ if unsafe { ffi::mp_get_buffer(obj, &mut bufinfo, ffi::MP_BUFFER_READ as _) } {
+ Ok(Self {
+ ptr: bufinfo.buf as _,
+ len: bufinfo.len as _,
+ })
+ } else {
+ Err(Error::NotBuffer)
+ }
+ }
+}
+
+impl Deref for Buffer {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ self.as_ref()
+ }
+}
+
+impl AsRef<[u8]> for Buffer {
+ fn as_ref(&self) -> &[u8] {
+ if self.ptr.is_null() {
+ // `self.ptr` can be null if len == 0.
+ &[]
+ } else {
+ // SAFETY: We assume that `self.ptr` is pointing to memory:
+ // - without any mutable references,
+ // - immutable for the whole lifetime of `&self`,
+ // - with at least `self.len` bytes.
+ unsafe { slice::from_raw_parts(self.ptr, self.len) }
+ }
+ }
+}
diff --git a/core/embed/rust/src/micropython/dict.rs b/core/embed/rust/src/micropython/dict.rs
new file mode 100644
index 000000000..2d4b9dd20
--- /dev/null
+++ b/core/embed/rust/src/micropython/dict.rs
@@ -0,0 +1,70 @@
+use core::convert::TryFrom;
+
+use crate::error::Error;
+
+use super::{ffi, gc::Gc, map::Map, obj::Obj};
+
+/// Insides of the MicroPython `dict` object.
+pub type Dict = ffi::mp_obj_dict_t;
+
+impl Dict {
+ /// Allocate a new dictionary on the GC heap, empty, but with a capacity of
+ /// `capacity` items.
+ pub fn alloc_with_capacity(capacity: usize) -> Gc {
+ unsafe {
+ // SAFETY: We expect that `ffi::mp_obj_new_dict` either returns a GC-allocated
+ // pointer to `ffi::mp_obj_dict_t` or raises (i.e. on allocation failure).
+ let ptr = ffi::mp_obj_new_dict(capacity);
+ Gc::from_raw(ptr.as_ptr().cast())
+ }
+ }
+
+ /// Constructs a dictionary definition by taking ownership of given [`Map`].
+ pub fn with_map(map: Map) -> Self {
+ unsafe {
+ Self {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_dict,
+ },
+ map,
+ }
+ }
+ }
+
+ /// Returns a reference to the contained [`Map`].
+ pub fn map(&self) -> &Map {
+ &self.map
+ }
+
+ /// Returns a mutable reference to the contained [`Map`].
+ pub fn map_mut(&mut self) -> &mut Map {
+ &mut self.map
+ }
+}
+
+impl From> for Obj {
+ fn from(value: Gc) -> Self {
+ // SAFETY:
+ // - `value` is an object struct with a base and a type.
+ // - `value` is GC-allocated.
+ unsafe { Obj::from_ptr(Gc::into_raw(value).cast()) }
+ }
+}
+
+impl TryFrom for Gc {
+ type Error = Error;
+
+ fn try_from(value: Obj) -> Result {
+ if unsafe { ffi::mp_type_dict.is_type_of(value) } {
+ // SAFETY: We assume that if `value` is an object pointer with the correct type,
+ // it is managed by MicroPython GC (see `Gc::from_raw` for details).
+ let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
+ Ok(this)
+ } else {
+ Err(Error::InvalidType)
+ }
+ }
+}
+
+// SAFETY: We are in a single-threaded environment.
+unsafe impl Sync for Dict {}
diff --git a/core/embed/rust/src/micropython/ffi.rs b/core/embed/rust/src/micropython/ffi.rs
new file mode 100644
index 000000000..d268afd30
--- /dev/null
+++ b/core/embed/rust/src/micropython/ffi.rs
@@ -0,0 +1,5 @@
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(dead_code)]
+
+include!(concat!(env!("OUT_DIR"), "/micropython.rs"));
diff --git a/core/embed/rust/src/micropython/func.rs b/core/embed/rust/src/micropython/func.rs
new file mode 100644
index 000000000..038157555
--- /dev/null
+++ b/core/embed/rust/src/micropython/func.rs
@@ -0,0 +1,18 @@
+use super::{ffi, obj::Obj};
+
+pub type Func = ffi::mp_obj_fun_builtin_fixed_t;
+
+impl Func {
+ /// Convert a "static const" function to a MicroPython object.
+ pub const fn to_obj(&'static self) -> Obj {
+ // SAFETY:
+ // - We are an object struct with a base and a type.
+ // - 'static lifetime holds us in place.
+ // - MicroPython is smart enough not to mutate `mp_obj_fun_builtin_fixed_t`
+ // objects.
+ unsafe { Obj::from_ptr(self as *const _ as *mut _) }
+ }
+}
+
+// SAFETY: We are in a single-threaded environment.
+unsafe impl Sync for Func {}
diff --git a/core/embed/rust/src/micropython/gc.rs b/core/embed/rust/src/micropython/gc.rs
new file mode 100644
index 000000000..e4f6ece9d
--- /dev/null
+++ b/core/embed/rust/src/micropython/gc.rs
@@ -0,0 +1,81 @@
+use core::{
+ alloc::Layout,
+ marker::Unsize,
+ ops::{CoerceUnsized, Deref, DispatchFromDyn},
+ ptr::{self, NonNull},
+};
+
+use super::ffi;
+
+/// A pointer type for values on the garbage-collected heap.
+///
+/// Although a garbage-collected pointer type technically should implement
+/// `Copy` and `Clone`, we avoid doing this until proven necessary.
+pub struct Gc(NonNull);
+
+impl, U: ?Sized> CoerceUnsized> for Gc {}
+impl, U: ?Sized> DispatchFromDyn> for Gc {}
+
+impl Gc {
+ /// Allocate memory on the heap managed by the MicroPython garbage collector
+ /// and then place `v` into it. `v` will _not_ get its destructor called.
+ pub fn new(v: T) -> Self {
+ let layout = Layout::for_value(&v);
+ // TODO: Assert that `layout.align()` is the same as the GC alignment.
+ // SAFETY:
+ // - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
+ // not support custom alignment.
+ // - `ptr` is guaranteed to stay valid as long as it's reachable from the stack
+ // or the MicroPython heap.
+ unsafe {
+ let raw = ffi::gc_alloc(layout.size(), 0).cast();
+ ptr::write(raw, v);
+ Self::from_raw(raw)
+ }
+ }
+
+ /// Return a mutable reference to the value.
+ ///
+ /// # Safety
+ ///
+ /// `Gc` values can originate in the MicroPython interpreter, and these can
+ /// be both shared and mutable. Before calling this function, you have to
+ /// ensure that `this` is unique for the whole lifetime of the
+ /// returned mutable reference.
+ pub unsafe fn as_mut(this: &mut Self) -> &mut T {
+ // SAFETY: The caller must guarantee that `this` meets all the requirements for
+ // a mutable reference.
+ unsafe { this.0.as_mut() }
+ }
+}
+
+impl Gc {
+ /// Construct a `Gc` from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// This function is unsafe because the caller has to guarantee that `ptr`
+ /// is pointing to a memory understood by the MicroPython GC, that is:
+ /// - previously allocated through `Gc::new()` or `gc_alloc()`, or
+ /// - through the MicroPython interpreter, or
+ /// - one of the GC roots (sys.argv, sys.modules, etc.).
+ pub unsafe fn from_raw(ptr: *mut T) -> Self {
+ // SAFETY: The caller must guarantee that `ptr` is something the MicroPython GC
+ // can reason about.
+ unsafe { Self(NonNull::new_unchecked(ptr)) }
+ }
+
+ /// Convert `this` into a raw pointer. This will _not_ drop the contained
+ /// value.
+ pub fn into_raw(this: Self) -> *mut T {
+ this.0.as_ptr()
+ }
+}
+
+impl Deref for Gc {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ unsafe { self.0.as_ref() }
+ }
+}
diff --git a/core/embed/rust/src/micropython/iter.rs b/core/embed/rust/src/micropython/iter.rs
new file mode 100644
index 000000000..90dbb5cbf
--- /dev/null
+++ b/core/embed/rust/src/micropython/iter.rs
@@ -0,0 +1,69 @@
+use core::ptr;
+
+use crate::error::Error;
+use crate::micropython::obj::Obj;
+
+use super::ffi;
+
+pub struct IterBuf {
+ iter_buf: ffi::mp_obj_iter_buf_t,
+}
+
+impl IterBuf {
+ pub fn new() -> Self {
+ Self {
+ iter_buf: ffi::mp_obj_iter_buf_t {
+ base: ffi::mp_obj_base_t {
+ type_: ptr::null_mut(),
+ },
+ buf: [Obj::const_null(), Obj::const_null(), Obj::const_null()],
+ },
+ }
+ }
+}
+
+pub struct Iter<'a> {
+ iter: Obj,
+ iter_buf: &'a mut IterBuf,
+ finished: bool,
+}
+
+impl<'a> Iter<'a> {
+ pub fn try_from_obj_with_buf(o: Obj, iter_buf: &'a mut IterBuf) -> Result {
+ // SAFETY:
+ // - In the common case, `ffi::mp_getiter` does not heap-allocate, but instead
+ // uses memory from the passed `iter_buf`. We maintain this invariant by
+ // taking a mut ref to `IterBuf` and tying it to the lifetime of returned
+ // `Iter`.
+ // - Raises if `o` is not iterable and in other cases as well.
+ // - Returned obj is referencing into `iter_buf`.
+ // TODO: Use a fn that doesn't raise on non-iterable object.
+ let iter = unsafe { ffi::mp_getiter(o, &mut iter_buf.iter_buf) };
+ Ok(Self {
+ iter,
+ iter_buf,
+ finished: false,
+ })
+ }
+}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = Obj;
+
+ fn next(&mut self) -> Option {
+ if self.finished {
+ return None;
+ }
+ // SAFETY:
+ // - We assume that `mp_iternext` returns objects without any lifetime
+ // invariants, i.e. heap-allocated, unlike `mp_getiter`.
+ // - Can raise.
+ let item = unsafe { ffi::mp_iternext(self.iter) };
+ if item == Obj::const_stop_iteration() {
+ self.finished = true;
+ None
+ } else {
+ Some(item)
+ }
+ }
+}
diff --git a/core/embed/rust/src/micropython/list.rs b/core/embed/rust/src/micropython/list.rs
new file mode 100644
index 000000000..28120bf89
--- /dev/null
+++ b/core/embed/rust/src/micropython/list.rs
@@ -0,0 +1,50 @@
+use core::convert::TryFrom;
+
+use crate::error::Error;
+
+use super::{ffi, gc::Gc, obj::Obj};
+
+pub type List = ffi::mp_obj_list_t;
+
+impl List {
+ pub fn alloc(values: &[Obj]) -> Gc {
+ // SAFETY: Altough `values` are copied into the new list and not mutated,
+ // `mp_obj_new_list` is taking them through a mut pointer.
+ unsafe {
+ let list = ffi::mp_obj_new_list(values.len(), values.as_ptr() as *mut Obj);
+ Gc::from_raw(list.as_ptr().cast())
+ }
+ }
+
+ pub fn append(&mut self, value: Obj) {
+ unsafe {
+ let ptr = self as *mut Self;
+ let list = Obj::from_ptr(ptr.cast());
+ ffi::mp_obj_list_append(list, value);
+ }
+ }
+}
+
+impl From> for Obj {
+ fn from(value: Gc) -> Self {
+ // SAFETY:
+ // - `value` is an object struct with a base and a type.
+ // - `value` is GC-allocated.
+ unsafe { Obj::from_ptr(Gc::into_raw(value).cast()) }
+ }
+}
+
+impl TryFrom for Gc {
+ type Error = Error;
+
+ fn try_from(value: Obj) -> Result {
+ if unsafe { ffi::mp_type_list.is_type_of(value) } {
+ // SAFETY: We assume that if `value` is an object pointer with the correct type,
+ // it is managed by MicroPython GC (see `Gc::from_raw` for details).
+ let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
+ Ok(this)
+ } else {
+ Err(Error::InvalidType)
+ }
+ }
+}
diff --git a/core/embed/rust/src/micropython/macros.rs b/core/embed/rust/src/micropython/macros.rs
new file mode 100644
index 000000000..e60526f7a
--- /dev/null
+++ b/core/embed/rust/src/micropython/macros.rs
@@ -0,0 +1,132 @@
+macro_rules! obj_fn_1 {
+ ($f:expr) => {{
+ #[allow(unused_unsafe)]
+ unsafe {
+ use $crate::micropython::ffi;
+
+ ffi::mp_obj_fun_builtin_fixed_t {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_fun_builtin_1,
+ },
+ fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _1: Some($f) },
+ }
+ }
+ }};
+}
+
+macro_rules! obj_fn_2 {
+ ($f:expr) => {{
+ #[allow(unused_unsafe)]
+ unsafe {
+ use $crate::micropython::ffi;
+
+ ffi::mp_obj_fun_builtin_fixed_t {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_fun_builtin_2,
+ },
+ fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _2: Some($f) },
+ }
+ }
+ }};
+}
+
+macro_rules! obj_fn_3 {
+ ($f:expr) => {{
+ #[allow(unused_unsafe)]
+ unsafe {
+ use $crate::micropython::ffi;
+
+ ffi::mp_obj_fun_builtin_fixed_t {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_fun_builtin_3,
+ },
+ fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _3: Some($f) },
+ }
+ }
+ }};
+}
+
+/// Construct fixed static const `Map` from `key` => `val` pairs.
+macro_rules! obj_map {
+ ($($key:expr => $val:expr),*) => ({
+ Map::from_fixed_static(&[
+ $(
+ Map::at($key, $val),
+ )*
+ ])
+ });
+ ($($key:expr => $val:expr),* ,) => ({
+ obj_map!($($key => $val),*)
+ });
+}
+
+/// Construct a `Dict` from the backing `Map`. See `obj_map` above.
+macro_rules! obj_dict {
+ ($map:expr) => {{
+ #[allow(unused_unsafe)]
+ unsafe {
+ use $crate::micropython::ffi;
+
+ ffi::mp_obj_dict_t {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_dict,
+ },
+ map: $map,
+ }
+ }
+ }};
+}
+
+macro_rules! obj_type {
+ (name: $name:expr,
+ $(locals: $locals:expr,)?
+ $(attr_fn: $attr_fn:ident,)?
+ $(call_fn: $call_fn:ident,)?
+ ) => {{
+ #[allow(unused_unsafe)]
+ unsafe {
+ use $crate::micropython::ffi;
+
+ let name = $name.to_u16();
+
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut attr: ffi::mp_attr_fun_t = None;
+ $(attr = Some($attr_fn);)?
+
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut call: ffi::mp_call_fun_t = None;
+ $(call = Some($call_fn);)?
+
+ // TODO: This is safe only if we pass in `Dict` with fixed `Map` (created by
+ // `Map::fixed()`), because only then will Micropython treat `locals_dict` as
+ // immutable, and make the mutable cast safe.
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut locals_dict = ::core::ptr::null_mut();
+ $(locals_dict = $locals as *const _ as *mut _;)?
+
+ ffi::mp_obj_type_t {
+ base: ffi::mp_obj_base_t {
+ type_: &ffi::mp_type_type,
+ },
+ flags: 0,
+ name,
+ print: None,
+ make_new: None,
+ call,
+ unary_op: None,
+ binary_op: None,
+ attr,
+ subscr: None,
+ getiter: None,
+ iternext: None,
+ buffer_p: ffi::mp_buffer_p_t { get_buffer: None },
+ protocol: ::core::ptr::null(),
+ parent: ::core::ptr::null(),
+ locals_dict,
+ }
+ }
+ }};
+}
diff --git a/core/embed/rust/src/micropython/map.rs b/core/embed/rust/src/micropython/map.rs
new file mode 100644
index 000000000..de032878d
--- /dev/null
+++ b/core/embed/rust/src/micropython/map.rs
@@ -0,0 +1,193 @@
+use core::{marker::PhantomData, mem::MaybeUninit, ops::Deref, ptr, slice};
+
+use crate::{
+ error::Error,
+ micropython::{obj::Obj, qstr::Qstr},
+};
+
+use super::ffi;
+
+pub type Map = ffi::mp_map_t;
+pub type MapElem = ffi::mp_map_elem_t;
+
+impl Map {
+ pub const fn from_fixed_static(table: &'static [MapElem; N]) -> Self {
+ // We can't use the regular accessors generated by bindgen for us, as they are
+ // not `const`. Instead, we build the bitfield manually.
+
+ // micropython/py/obj.h MP_DEFINE_CONST_DICT
+ // .all_keys_are_qstrs = 1,
+ // .is_fixed = 1,
+ // .is_ordered = 1,
+ // .used = MP_ARRAY_SIZE(table_name),
+ // .alloc = MP_ARRAY_SIZE(table_name),
+ let bits = 0b111 | N << 3;
+ let bitfield = ffi::__BindgenBitfieldUnit::new(bits.to_ne_bytes());
+ Self {
+ _bitfield_align_1: [],
+ _bitfield_1: bitfield,
+ alloc: N,
+ table: table.as_ptr() as *mut MapElem,
+ }
+ }
+
+ pub const fn at(key: Qstr, value: Obj) -> MapElem {
+ MapElem {
+ key: key.to_obj(),
+ value,
+ }
+ }
+}
+
+impl Map {
+ pub fn from_fixed<'a>(table: &'a [MapElem]) -> MapRef<'a> {
+ let mut map = MaybeUninit::uninit();
+ // SAFETY: `mp_map_init` completely initializes all fields of `map`.
+ unsafe {
+ ffi::mp_map_init_fixed_table(map.as_mut_ptr(), table.len(), table.as_ptr().cast());
+ MapRef::new(map.assume_init())
+ }
+ }
+
+ pub fn with_capacity(capacity: usize) -> Self {
+ let mut map = MaybeUninit::uninit();
+ // SAFETY: `mp_map_init` completely initializes all fields of `map`, allocating
+ // the backing storage for `capacity` items on the heap.
+ unsafe {
+ ffi::mp_map_init(map.as_mut_ptr(), capacity);
+ map.assume_init()
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.used()
+ }
+
+ pub fn elems(&self) -> &[MapElem] {
+ // SAFETY: `self.table` should always point to an array of `MapElem` of
+ // `self.len()` items valid at least for the lifetime of `self`.
+ unsafe { slice::from_raw_parts(self.table, self.len()) }
+ }
+
+ pub fn contains_key(&self, index: impl Into) -> bool {
+ self.get_obj(index.into()).is_ok()
+ }
+
+ pub fn get(&self, index: impl Into) -> Result {
+ self.get_obj(index.into())
+ }
+
+ pub fn get_obj(&self, index: Obj) -> Result {
+ // SAFETY:
+ // - `mp_map_lookup` returns either NULL or a pointer to a `mp_map_elem_t`
+ // value with a lifetime valid for the whole lifetime of the passed immutable
+ // ref of `map`.
+ // - with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP`, `map` stays unmodified and the
+ // cast to mut ptr is therefore safe.
+ unsafe {
+ let map = self as *const Self as *mut Self;
+ let elem = ffi::mp_map_lookup(map, index, ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP)
+ .as_ref()
+ .ok_or(Error::Missing)?;
+ Ok(elem.value)
+ }
+ }
+
+ pub fn set(&mut self, index: impl Into, value: impl Into) {
+ self.set_obj(index.into(), value.into())
+ }
+
+ pub fn set_obj(&mut self, index: Obj, value: Obj) {
+ // SAFETY:
+ // - `mp_map_lookup` with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
+ // returns a pointer to a `mp_map_elem_t` value with a lifetime valid for the
+ // whole lifetime of `&mut self`.
+ // - adding an element is an allocation, so it might raise.
+ // - the original `elem.value` might be an `Obj` that will get GCd when we
+ // replace it.
+ unsafe {
+ let map = self as *mut Self;
+ let elem = ffi::mp_map_lookup(
+ map,
+ index,
+ ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
+ )
+ .as_mut()
+ .unwrap();
+ elem.value = value;
+ }
+ }
+
+ pub fn delete(&mut self, index: impl Into) {
+ self.delete_obj(index.into())
+ }
+
+ pub fn delete_obj(&mut self, index: Obj) {
+ unsafe {
+ let map = self as *mut Self;
+ ffi::mp_map_lookup(
+ map,
+ index,
+ ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_REMOVE_IF_FOUND,
+ );
+ }
+ }
+}
+
+impl Clone for Map {
+ fn clone(&self) -> Self {
+ let mut map = Self::with_capacity(self.len());
+ unsafe {
+ ptr::copy_nonoverlapping(self.table, map.table, self.len());
+ }
+ map.set_used(self.used());
+ map.set_all_keys_are_qstrs(self.all_keys_are_qstrs());
+ map.set_is_ordered(self.is_ordered());
+ map.set_is_fixed(0);
+ map
+ }
+}
+
+impl Default for Map {
+ fn default() -> Self {
+ Self::with_capacity(0)
+ }
+}
+
+// SAFETY: We are in a single-threaded environment.
+unsafe impl Sync for Map {}
+unsafe impl Sync for MapElem {}
+
+/// Wrapper over a private `Map` value with element lifetime `'a`.
+///
+/// `Map` itself does not have any lifetime parameter, so it can effectively
+/// only reference element tables with `'static` lifetime. `MapRef` is tying a
+/// private `Map` value to a lifetime `'a`, exposing it only by-ref within the
+/// `'a` lifetime. It's not possible to move the value out from the reference,
+/// and because `Map` is not `Clone`, it is also impossible to clone it. All
+/// this only holds for immutable ref, therefore we only impl `Deref`, not
+/// `DerefMut`.
+//
+// TODO: Better solution would be to make Map a wrapper type instead of a type
+// alias and add a lifetime parameter.
+pub struct MapRef<'a> {
+ map: Map,
+ _phantom: PhantomData<&'a Map>,
+}
+
+impl<'a> MapRef<'a> {
+ fn new(map: Map) -> Self {
+ Self {
+ map,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<'a> Deref for MapRef<'a> {
+ type Target = Map;
+
+ fn deref(&self) -> &Self::Target {
+ &self.map
+ }
+}
diff --git a/core/embed/rust/src/micropython/mod.rs b/core/embed/rust/src/micropython/mod.rs
new file mode 100644
index 000000000..3b4521619
--- /dev/null
+++ b/core/embed/rust/src/micropython/mod.rs
@@ -0,0 +1,15 @@
+#[macro_use]
+pub mod macros;
+
+pub mod buffer;
+pub mod dict;
+pub mod ffi;
+pub mod func;
+pub mod gc;
+pub mod iter;
+pub mod list;
+pub mod map;
+pub mod obj;
+pub mod qstr;
+pub mod runtime;
+pub mod typ;
diff --git a/core/embed/rust/src/micropython/obj.rs b/core/embed/rust/src/micropython/obj.rs
new file mode 100644
index 000000000..df4c4bbdc
--- /dev/null
+++ b/core/embed/rust/src/micropython/obj.rs
@@ -0,0 +1,327 @@
+use core::convert::{TryFrom, TryInto};
+use core::num::TryFromIntError;
+
+use crate::error::Error;
+
+use super::ffi;
+
+pub type Obj = ffi::mp_obj_t;
+pub type ObjBase = ffi::mp_obj_base_t;
+
+impl PartialEq for Obj {
+ fn eq(&self, other: &Self) -> bool {
+ self.as_bits() == other.as_bits()
+ }
+}
+
+impl Eq for Obj {}
+
+impl Obj {
+ pub const unsafe fn from_ptr(ptr: *mut cty::c_void) -> Self {
+ Self(ptr)
+ }
+
+ pub const fn as_ptr(self) -> *mut cty::c_void {
+ self.0
+ }
+
+ pub const unsafe fn from_bits(bits: cty::uintptr_t) -> Self {
+ Self(bits as _)
+ }
+
+ pub fn as_bits(self) -> cty::uintptr_t {
+ self.0 as _
+ }
+}
+
+impl Obj {
+ pub fn is_small_int(self) -> bool {
+ // micropython/py/obj.h mp_obj_is_small_int
+ self.as_bits() & 1 != 0
+ }
+
+ pub fn is_qstr(self) -> bool {
+ // micropython/py/obj.h mp_obj_is_qstr
+ self.as_bits() & 7 == 2
+ }
+
+ pub fn is_immediate(self) -> bool {
+ // micropython/py/obj.h mp_obj_is_immediate_obj
+ self.as_bits() & 7 == 6
+ }
+
+ pub fn is_ptr(self) -> bool {
+ // micropython/py/obj.h mp_obj_is_obj
+ self.as_bits() & 3 == 0
+ }
+}
+
+impl Obj {
+ pub const fn const_null() -> Self {
+ // micropython/py/obj.h
+ // #define MP_OBJ_NULL (MP_OBJ_FROM_PTR((void *)0))
+ unsafe { Self::from_bits(0) }
+ }
+
+ pub const fn const_stop_iteration() -> Self {
+ // micropython/py/obj.h
+ // #define MP_OBJ_STOP_ITERATION (MP_OBJ_FROM_PTR((void *)0))
+ unsafe { Self::from_bits(0) }
+ }
+
+ pub const fn const_none() -> Self {
+ // micropython/py/obj.h
+ // #define mp_const_none MP_OBJ_NEW_IMMEDIATE_OBJ(0)
+ unsafe { Self::immediate(0) }
+ }
+
+ pub const fn const_false() -> Self {
+ // micropython/py/obj.h
+ // #define mp_const_false MP_OBJ_NEW_IMMEDIATE_OBJ(1)
+ unsafe { Self::immediate(1) }
+ }
+
+ pub const fn const_true() -> Self {
+ // micropython/py/obj.h
+ // #define mp_const_true MP_OBJ_NEW_IMMEDIATE_OBJ(3)
+ unsafe { Self::immediate(3) }
+ }
+
+ const unsafe fn immediate(val: usize) -> Self {
+ // SAFETY:
+ // - `val` is in `0..=3` range.
+ // - MicroPython compiled with `MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A`.
+ // - MicroPython compiled with `MICROPY_OBJ_IMMEDIATE_OBJS`.
+ // micropython/py/obj.h #define MP_OBJ_NEW_IMMEDIATE_OBJ(val)
+ // ((mp_obj_t)(((val) << 3) | 6))
+ unsafe { Self::from_bits((val << 3) | 6) }
+ }
+}
+
+impl Obj {
+ pub fn call_with_n_args(self, args: &[Obj]) -> Obj {
+ // SAFETY:
+ // - Can call python code and therefore raise.
+ // - Each of `args` has no lifetime bounds.
+ unsafe { ffi::mp_call_function_n_kw(self, args.len(), 0, args.as_ptr()) }
+ }
+}
+
+//
+// # Converting `Obj` into plain data.
+//
+
+impl TryFrom for bool {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ // TODO: Avoid type casts on the Python side.
+ // SAFETY:
+ // - Can call python code and therefore raise.
+ if unsafe { ffi::mp_obj_is_true(obj) } {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+}
+
+impl TryFrom for i32 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let mut int: ffi::mp_int_t = 0;
+
+ // TODO: Avoid type casts on the Python side.
+ // SAFETY:
+ // - Can raise if `obj` is int but cannot fit into `cty::mp_int_t`.
+ if unsafe { ffi::mp_obj_get_int_maybe(obj.as_ptr(), &mut int) } {
+ let int = int.try_into()?;
+ Ok(int)
+ } else {
+ Err(Error::NotInt)
+ }
+ }
+}
+
+impl TryFrom for i64 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let mut ll: cty::c_longlong = 0;
+
+ if unsafe { ffi::trezor_obj_get_ll_checked(obj, &mut ll) } {
+ Ok(ll)
+ } else {
+ Err(Error::NotInt)
+ }
+ }
+}
+
+//
+// # Converting plain data into `Obj`.
+//
+
+impl From for Obj {
+ fn from(val: bool) -> Self {
+ if val {
+ Obj::const_true()
+ } else {
+ Obj::const_false()
+ }
+ }
+}
+
+impl From for Obj {
+ fn from(val: i32) -> Self {
+ // `mp_obj_new_int` accepts a `mp_int_t` argument, which is word-sized. We
+ // primarily target 32-bit architecture, and therefore keep the primary signed
+ // conversion type as `i32`, but convert through `into()` if the types differ.
+
+ // SAFETY:
+ // - Can raise if allocation fails.
+ unsafe { ffi::mp_obj_new_int(val.into()) }
+ }
+}
+
+impl From for Obj {
+ fn from(val: i64) -> Self {
+ // Because `mp_obj_new_int_from_ll` allocates even if `val` fits into small-int,
+ // we try to go through `mp_obj_new_int` first.
+ match i32::try_from(val) {
+ Ok(smaller_val) => smaller_val.into(),
+ // SAFETY:
+ // - Can raise if allocation fails.
+ Err(_) => unsafe { ffi::mp_obj_new_int_from_ll(val) },
+ }
+ }
+}
+
+impl From for Obj {
+ fn from(val: u32) -> Self {
+ // `mp_obj_new_int_from_uint` accepts a `mp_uint_t` argument, which is
+ // word-sized. We primarily target 32-bit architecture, and therefore keep
+ // the primary unsigned conversion type as `u32`, but convert through `into()`
+ // if the types differ.
+
+ // SAFETY:
+ // - Can raise if allocation fails.
+ unsafe { ffi::mp_obj_new_int_from_uint(val.into()) }
+ }
+}
+
+impl From for Obj {
+ fn from(val: u64) -> Self {
+ // Because `mp_obj_new_int_from_ull` allocates even if `val` fits into
+ // small-int, we try to go through `mp_obj_new_int_from_uint` first.
+ match u32::try_from(val) {
+ Ok(smaller_val) => smaller_val.into(),
+ // SAFETY:
+ // - Can raise if allocation fails.
+ Err(_) => unsafe { ffi::mp_obj_new_int_from_ull(val) },
+ }
+ }
+}
+
+/// Byte slices are converted into `bytes` MicroPython objects, by allocating
+/// new space on the heap and copying.
+impl From<&[u8]> for Obj {
+ fn from(val: &[u8]) -> Self {
+ // SAFETY:
+ // - Can raise if allocation fails.
+ unsafe { ffi::mp_obj_new_bytes(val.as_ptr(), val.len()) }
+ }
+}
+
+/// String slices are converted into `str` MicroPython objects. Strings that are
+/// already interned will turn up as QSTRs, strings not found in the QSTR pool
+/// will be allocated on the heap and copied.
+impl From<&str> for Obj {
+ fn from(val: &str) -> Self {
+ // SAFETY:
+ // - Can raise if allocation fails.
+ // - `str` is guaranteed to be UTF-8.
+ unsafe { ffi::mp_obj_new_str(val.as_ptr().cast(), val.len()) }
+ }
+}
+
+//
+// # Additional conversions based on the methods above.
+//
+
+impl From for Obj {
+ fn from(val: u8) -> Self {
+ u32::from(val).into()
+ }
+}
+
+impl From for Obj {
+ fn from(val: u16) -> Self {
+ u32::from(val).into()
+ }
+}
+
+impl From for Obj {
+ fn from(val: usize) -> Self {
+ // Willingly truncate the bits on 128-bit architectures.
+ (val as u64).into()
+ }
+}
+
+impl TryFrom for u8 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let val = i32::try_from(obj)?;
+ let this = Self::try_from(val)?;
+ Ok(this)
+ }
+}
+
+impl TryFrom for u16 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let val = i32::try_from(obj)?;
+ let this = Self::try_from(val)?;
+ Ok(this)
+ }
+}
+
+impl TryFrom for u32 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ let val = i64::try_from(obj)?;
+ let this = Self::try_from(val)?;
+ Ok(this)
+ }
+}
+
+impl TryFrom for u64 {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ // TODO: Support full range.
+ let val = i64::try_from(obj)?;
+ let this = Self::try_from(val)?;
+ Ok(this)
+ }
+}
+
+impl TryFrom for usize {
+ type Error = Error;
+
+ fn try_from(obj: Obj) -> Result {
+ // TODO: Support full range.
+ let val = i64::try_from(obj)?;
+ let this = Self::try_from(val)?;
+ Ok(this)
+ }
+}
+
+impl From for Error {
+ fn from(_: TryFromIntError) -> Self {
+ Self::OutOfRange
+ }
+}
diff --git a/core/embed/rust/src/micropython/qstr.rs b/core/embed/rust/src/micropython/qstr.rs
new file mode 100644
index 000000000..1a6425a49
--- /dev/null
+++ b/core/embed/rust/src/micropython/qstr.rs
@@ -0,0 +1,59 @@
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(dead_code)]
+
+use core::convert::TryFrom;
+
+use crate::{error::Error, micropython::obj::Obj};
+
+impl Qstr {
+ pub const fn to_obj(self) -> Obj {
+ // SAFETY:
+ // - Micropython compiled with `MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A`.
+ // micropython/py/obj.h #define MP_OBJ_NEW_QSTR(qst)
+ // ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2))
+ let bits = (self.0 << 3) | 2;
+ unsafe { Obj::from_bits(bits as usize) }
+ }
+
+ pub const fn from_obj_bits(bits: cty::uintptr_t) -> Self {
+ let bits = (bits >> 3) as u16; // See `Self::to_obj`.
+ Self::from_u16(bits)
+ }
+
+ pub const fn to_u16(self) -> u16 {
+ // TODO: Change the internal representation of Qstr to u16.
+ self.0 as _
+ }
+
+ pub const fn from_u16(val: u16) -> Self {
+ // TODO: Change the internal representation of Qstr to u16.
+ Self(val as _)
+ }
+}
+
+impl From for Qstr {
+ fn from(val: u16) -> Self {
+ Self::from_u16(val)
+ }
+}
+
+impl TryFrom for Qstr {
+ type Error = Error;
+
+ fn try_from(value: Obj) -> Result {
+ if value.is_qstr() {
+ Ok(Self::from_obj_bits(value.as_bits()))
+ } else {
+ Err(Error::InvalidType)
+ }
+ }
+}
+
+impl From for Obj {
+ fn from(value: Qstr) -> Self {
+ value.to_obj()
+ }
+}
+
+include!(concat!(env!("OUT_DIR"), "/qstr.rs"));
diff --git a/core/embed/rust/src/micropython/runtime.rs b/core/embed/rust/src/micropython/runtime.rs
new file mode 100644
index 000000000..c39291348
--- /dev/null
+++ b/core/embed/rust/src/micropython/runtime.rs
@@ -0,0 +1,10 @@
+use cstr_core::CStr;
+
+use super::ffi;
+
+pub fn raise_value_error(msg: &'static CStr) -> ! {
+ unsafe {
+ ffi::mp_raise_ValueError(msg.as_ptr());
+ }
+ panic!();
+}
diff --git a/core/embed/rust/src/micropython/typ.rs b/core/embed/rust/src/micropython/typ.rs
new file mode 100644
index 000000000..4666ed562
--- /dev/null
+++ b/core/embed/rust/src/micropython/typ.rs
@@ -0,0 +1,28 @@
+use super::{
+ ffi,
+ obj::{Obj, ObjBase},
+};
+
+pub type Type = ffi::mp_obj_type_t;
+
+impl Type {
+ pub fn is_type_of(&'static self, obj: Obj) -> bool {
+ if obj.is_ptr() {
+ // SAFETY: If `obj` is a pointer, it should always point to an object having
+ // `ObjBase` as the first field, making this cast safe.
+ unsafe {
+ let base = obj.as_ptr() as *const ObjBase;
+ (*base).type_ == self
+ }
+ } else {
+ false
+ }
+ }
+
+ pub fn to_base(&'static self) -> ObjBase {
+ ObjBase { type_: self }
+ }
+}
+
+// SAFETY: We are in a single-threaded environment.
+unsafe impl Sync for Type {}
diff --git a/core/embed/rust/src/trezorhal/common.rs b/core/embed/rust/src/trezorhal/common.rs
new file mode 100644
index 000000000..ea9ab5fc1
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/common.rs
@@ -0,0 +1,24 @@
+use cstr_core::CStr;
+
+extern "C" {
+ // trezorhal/common.c
+ fn __fatal_error(
+ expr: *const cty::c_char,
+ msg: *const cty::c_char,
+ file: *const cty::c_char,
+ line: i32,
+ func: *const cty::c_char,
+ ) -> !;
+}
+
+pub fn fatal_error(expr: &CStr, msg: &CStr, file: &CStr, line: i32, func: &CStr) -> ! {
+ unsafe {
+ __fatal_error(
+ expr.as_ptr(),
+ msg.as_ptr(),
+ file.as_ptr(),
+ line,
+ func.as_ptr(),
+ );
+ }
+}
diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs
new file mode 100644
index 000000000..8068d8570
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/display.rs
@@ -0,0 +1,69 @@
+extern "C" {
+ // trezorhal/display.c
+ fn display_backlight(val: cty::c_int) -> cty::c_int;
+ fn display_text(
+ x: cty::c_int,
+ y: cty::c_int,
+ text: *const cty::c_char,
+ textlen: cty::c_int,
+ font: cty::c_int,
+ fgcolor: cty::uint16_t,
+ bgcolor: cty::uint16_t,
+ );
+ fn display_text_width(
+ text: *const cty::c_char,
+ textlen: cty::c_int,
+ font: cty::c_int,
+ ) -> cty::c_int;
+ fn display_bar(x: cty::c_int, y: cty::c_int, w: cty::c_int, h: cty::c_int, c: cty::uint16_t);
+ fn display_bar_radius(
+ x: cty::c_int,
+ y: cty::c_int,
+ w: cty::c_int,
+ h: cty::c_int,
+ c: cty::uint16_t,
+ b: cty::uint16_t,
+ r: cty::uint8_t,
+ );
+}
+
+const WIDTH: i32 = 240;
+const HEIGHT: i32 = 240;
+
+pub fn width() -> i32 {
+ WIDTH
+}
+
+pub fn height() -> i32 {
+ HEIGHT
+}
+
+pub fn backlight(val: i32) -> i32 {
+ unsafe { display_backlight(val) }
+}
+
+pub fn text(baseline_x: i32, baseline_y: i32, text: &[u8], font: i32, fgcolor: u16, bgcolor: u16) {
+ unsafe {
+ display_text(
+ baseline_x,
+ baseline_y,
+ text.as_ptr() as _,
+ text.len() as _,
+ font,
+ fgcolor,
+ bgcolor,
+ )
+ }
+}
+
+pub fn text_width(text: &[u8], font: i32) -> i32 {
+ unsafe { display_text_width(text.as_ptr() as _, text.len() as _, font) }
+}
+
+pub fn bar(x: i32, y: i32, w: i32, h: i32, fgcolor: u16) {
+ unsafe { display_bar(x, y, w, h, fgcolor) }
+}
+
+pub fn bar_radius(x: i32, y: i32, w: i32, h: i32, fgcolor: u16, bgcolor: u16, radius: u8) {
+ unsafe { display_bar_radius(x, y, w, h, fgcolor, bgcolor, radius) }
+}
diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs
new file mode 100644
index 000000000..3cf8ab10e
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/mod.rs
@@ -0,0 +1,3 @@
+pub mod common;
+pub mod display;
+pub mod random;
diff --git a/core/embed/rust/src/trezorhal/random.rs b/core/embed/rust/src/trezorhal/random.rs
new file mode 100644
index 000000000..6b73f6c07
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/random.rs
@@ -0,0 +1,16 @@
+extern "C" {
+ // trezor-crypto/rand.h
+ fn random_uniform(n: u32) -> u32;
+}
+
+pub fn uniform(n: u32) -> u32 {
+ unsafe { random_uniform(n) }
+}
+
+pub fn shuffle(slice: &mut [T]) {
+ // Fisher-Yates shuffle.
+ for i in (1..slice.len()).rev() {
+ let j = uniform(i as u32 + 1) as usize;
+ slice.swap(i, j);
+ }
+}
diff --git a/core/embed/rust/src/util.rs b/core/embed/rust/src/util.rs
new file mode 100644
index 000000000..0241012e2
--- /dev/null
+++ b/core/embed/rust/src/util.rs
@@ -0,0 +1,63 @@
+use core::slice;
+
+use crate::{
+ error::Error,
+ micropython::{
+ map::{Map, MapElem},
+ obj::Obj,
+ runtime::raise_value_error,
+ },
+};
+
+pub fn try_or_raise(func: impl FnOnce() -> Result) -> Obj {
+ func().unwrap_or_else(|err| raise_value_error(err.as_cstr()))
+}
+
+pub fn try_with_kwargs(kwargs: *const Map, func: impl FnOnce(&Map) -> Result) -> Obj {
+ try_or_raise(|| {
+ let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
+
+ func(kwargs)
+ })
+}
+
+pub fn try_with_args_and_kwargs(
+ n_args: usize,
+ args: *const Obj,
+ kwargs: *const Map,
+ func: impl FnOnce(&[Obj], &Map) -> Result,
+) -> Obj {
+ try_or_raise(|| {
+ let args = if args.is_null() {
+ &[]
+ } else {
+ unsafe { slice::from_raw_parts(args, n_args) }
+ };
+ let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
+
+ func(args, kwargs)
+ })
+}
+
+pub fn try_with_args_and_kwargs_inline(
+ n_args: usize,
+ n_kw: usize,
+ args: *const Obj,
+ func: impl FnOnce(&[Obj], &Map) -> Result,
+) -> Obj {
+ try_or_raise(|| {
+ let args_slice: &[Obj];
+ let kwargs_slice: &[MapElem];
+
+ if args.is_null() {
+ args_slice = &[];
+ kwargs_slice = &[];
+ } else {
+ args_slice = unsafe { slice::from_raw_parts(args, n_args) };
+ kwargs_slice = unsafe { slice::from_raw_parts(args.add(n_args).cast(), n_kw) };
+ }
+
+ let kw_map = Map::from_fixed(kwargs_slice);
+ func(args_slice, &kw_map)
+ })
+}