feat(core/rust): separate trezor-crypto to its own Rust crate
What changed, and why it matters
This commit is a large but straightforward internal refactoring: the cryptographic code is moved from one Rust crate (`rtl`) into a new dedicated crate (`crypto`), and all project dependencies are updated to point to the new crate. The actual cryptographic algorithms, build flags, and source files remain the same. There is no indication of a security bug fix or a vulnerability being introduced.
No security action required. Treat as a normal build-system refactoring commit. If reviewing for release, verify that the new `crypto` crate builds correctly for all target projects and that the moved unit tests still pass.
Security signals we found
No security-relevant code changes detected
Refactoring only: moving existing crypto wrappers and build logic into a new crate
Feature flags and C source lists preserved from the original `rtl` crate
No new unsafe code introduced beyond the existing FFI wrappers
No vendor disclosure or advisory references present
Evidence from the diff
The change separates trezor-crypto (and related vendored libraries such as secp256k1-zkp, sphincsplus, mldsa-native) from the rtl crate into a new core/embed/crypto crate. The build logic is copied almost verbatim from rtl/build.rs to crypto/build.rs, and the Rust wrapper modules (aesgcm, cosi, crc32, curve25519, ed25519, ffi, hmac, memory, merkle, sha256, sha512) are moved from core/embed/rust/src/crypto/ to core/embed/crypto/src/. Project Cargo.toml files are updated to depend on crypto instead of rtl for the relevant features. The rust crate no longer generates its own crypto FFI bindings; it consumes them from the new crypto crate. No algorithmic or security-relevant behavioral changes are visible in the diff.
Changed components
core/embed/crypto (new crate)core/embed/rtl (crypto build logic removed)core/embed/rust (crypto bindings generation removed, now depends on crypto crate)core/embed/projects/* (Cargo.toml feature flag updates)Inspect captured patch +2213 / −1951
diff --git a/core/embed/Cargo.lock b/core/embed/Cargo.lock
index a465a7dc..bf899f8c 100644
--- a/core/embed/Cargo.lock
+++ b/core/embed/Cargo.lock
@@ -94,6 +94,7 @@ name = "boardloader"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -107,6 +108,7 @@ name = "bootloader"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -121,6 +123,7 @@ name = "bootloader_ci"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -266,6 +269,25 @@ dependencies = [
"owo-colors",
]
+[[package]]
+name = "crypto"
+version = "0.0.0"
+dependencies = [
+ "bindgen",
+ "cc",
+ "color-eyre",
+ "cty",
+ "glob",
+ "heapless",
+ "hex",
+ "models",
+ "rand",
+ "rtl",
+ "trezor-thp",
+ "xbuild",
+ "zeroize",
+]
+
[[package]]
name = "cty"
version = "0.2.2"
@@ -330,6 +352,7 @@ name = "firmware"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -452,6 +475,7 @@ name = "kernel"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -616,6 +640,7 @@ dependencies = [
name = "prodtest"
version = "0.0.0"
dependencies = [
+ "crypto",
"io",
"models",
"rtl",
@@ -638,6 +663,21 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "rand"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
+
[[package]]
name = "regex"
version = "1.12.3"
@@ -712,6 +752,7 @@ name = "sec"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"models",
"rtl",
"sys",
@@ -723,6 +764,7 @@ name = "secmon"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"models",
"rtl",
"sec",
@@ -852,6 +894,7 @@ name = "sys"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"models",
"rtl",
"xbuild",
@@ -959,6 +1002,7 @@ dependencies = [
"bindgen",
"cc",
"cfg-if",
+ "crypto",
"cty",
"easer",
"glob",
@@ -1029,6 +1073,7 @@ name = "unix"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
@@ -1053,6 +1098,7 @@ name = "upymod"
version = "0.0.0"
dependencies = [
"color-eyre",
+ "crypto",
"io",
"models",
"rtl",
diff --git a/core/embed/Cargo.toml b/core/embed/Cargo.toml
index 140c6154..c843eca6 100644
--- a/core/embed/Cargo.toml
+++ b/core/embed/Cargo.toml
@@ -4,6 +4,7 @@ cargo-features = ["panic-immediate-abort"]
members = [
"models",
"rtl",
+ "crypto",
"sys",
"sec",
"io",
@@ -93,6 +94,7 @@ zeroize = { version = "1.8.2", default-features = false, features = ["zeroize_de
models = { path = "models" }
rtl = { path = "rtl" }
+crypto = { path = "crypto" }
sys = { path = "sys" }
sec = { path = "sec" }
io = { path = "io" }
@@ -113,7 +115,7 @@ hex = "0.4.3"
owo-colors = "4.3.0"
pathdiff = "0.2.3"
pkg-config = "0.3.28"
-rand = { version = "0.10.1", default-features = false }
+rand = { version = "0.10.0", default-features = false }
serde = "1.0.228"
serde_json = "1.0.145"
tempfile = { version = "3.25.0", default-features = false }
diff --git a/core/embed/crypto/Cargo.toml b/core/embed/crypto/Cargo.toml
new file mode 100644
index 00000000..8ebd1480
--- /dev/null
+++ b/core/embed/crypto/Cargo.toml
@@ -0,0 +1,59 @@
+[package]
+name = "crypto"
+version = "0.0.0"
+edition = "2024"
+links = "crypto"
+
+[dependencies]
+cty.workspace = true
+heapless = { workspace = true, default-features = false }
+models = { workspace = true }
+rtl = { workspace = true }
+trezor-thp = { workspace = true, optional = true }
+zeroize = { workspace = true, default-features = false, features = ["derive"] }
+
+[build-dependencies]
+color-eyre.workspace = true
+xbuild.workspace = true
+bindgen = { workspace = true, default-features = false }
+cc.workspace = true
+glob.workspace = true
+
+[dev-dependencies]
+hex.workspace = true
+rand.workspace = true
+
+[features]
+model_t2t1 = ["models/model_t2t1"]
+
+# --------------------------------------------------------------------------
+# Selectable features
+# --------------------------------------------------------------------------
+
+emulator = ["models/emulator", "insecure_prng"]
+
+aes_gcm = []
+ed25519_no_precomp = []
+eos = []
+insecure_prng = []
+mldsa = []
+nem = []
+noise = ["aes_gcm"]
+production = ["rtl/production"]
+secp256k1_zkp = []
+sphincsplus = []
+thp = ["dep:trezor-thp"]
+universal_fw = []
+
+test = [
+ "aes_gcm",
+ "emulator",
+ "insecure_prng",
+ "mldsa",
+ "models/mcu_stm32u5g",
+ "models/model_t3w1",
+ "noise",
+ "secp256k1_zkp",
+ "sphincsplus",
+ "universal_fw",
+]
diff --git a/core/embed/crypto/build.rs b/core/embed/crypto/build.rs
new file mode 100644
index 00000000..862c0ca3
--- /dev/null
+++ b/core/embed/crypto/build.rs
@@ -0,0 +1,344 @@
+use std::path::PathBuf;
+
+use xbuild::{CLibrary, CompileAttrs, Result, bail};
+
+const CRYPTO_PATH: &str = "../../vendor/trezor-crypto";
+const SECP256K1_PATH: &str = "../../vendor/secp256k1-zkp";
+const SPHINCSPLUS_PATH: &str = "../../vendor/sphincsplus/ref";
+const MLDSA_PATH: &str = "../../vendor/mldsa-native/mldsa";
+
+fn main() -> Result<()> {
+ xbuild::build(|lib| {
+ lib.import_lib("rtl")?;
+
+ lib.add_include("inc");
+
+ let attrs = CompileAttrs::new()
+ .with_flag("-ftrivial-auto-var-init=zero")
+ .with_flag("-ffreestanding");
+
+ add_crypto_base(lib, &attrs)?;
+
+ if cfg!(feature = "insecure_prng") {
+ add_insecure_prng(lib)?;
+ }
+
+ if cfg!(feature = "aes_gcm") {
+ add_aes_gcm(lib, &attrs)?;
+ }
+
+ if cfg!(feature = "noise") {
+ add_noise(lib, &attrs)?;
+ }
+
+ if cfg!(feature = "secp256k1_zkp") {
+ add_secp256k1_zkp(lib, &attrs)?;
+ }
+
+ if cfg!(feature = "sphincsplus") {
+ add_sphincsplus(lib, &attrs)?;
+ }
+
+ if cfg!(feature = "mldsa") {
+ add_mldsa(lib, &attrs)?;
+ }
+
+ if cfg!(feature = "test") {
+ lib.add_source("src/test_setup.c");
+ }
+
+ Ok(())
+ })
+}
+
+fn add_crypto_base(lib: &mut CLibrary, common_attrs: &CompileAttrs) -> Result<()> {
+ lib.add_include(CRYPTO_PATH);
+
+ lib.add_defines([
+ ("AES_128", None),
+ ("AES_192", None),
+ ("USE_KECCAK", Some("1")),
+ ("USE_BIP32_CACHE", Some("0")),
+ ]);
+
+ if cfg!(feature = "ed25519_no_precomp") {
+ lib.add_define("ED25519_NO_PRECOMP", None);
+ }
+
+ lib.add_sources_in_dir_with_attrs(
+ CRYPTO_PATH,
+ [
+ "address.c",
+ "aes/aes_modes.c",
+ "aes/aesccm.c",
+ "aes/aescrypt.c",
+ "aes/aeskey.c",
+ "aes/aestab.c",
+ "base32.c",
+ "base58.c",
+ "bignum.c",
+ "bip32.c",
+ "bip39.c",
+ "bip39_english.c",
+ "blake256.c",
+ "blake2b.c",
+ "blake2s.c",
+ "buffer.c",
+ "chacha20poly1305/chacha20poly1305.c",
+ "chacha20poly1305/chacha_merged.c",
+ "chacha20poly1305/poly1305-donna.c",
+ "chacha20poly1305/rfc7539.c",
+ "chacha_drbg.c",
+ "consteq.c",
+ "curves.c",
+ "der.c",
+ "ecdsa.c",
+ "ed25519-donna/curve25519-donna-32bit.c",
+ "ed25519-donna/curve25519-donna-helpers.c",
+ "ed25519-donna/curve25519-donna-scalarmult-base.c",
+ "ed25519-donna/ed25519-donna-32bit-tables.c",
+ "ed25519-donna/ed25519-donna-basepoint-table.c",
+ "ed25519-donna/ed25519-donna-impl-base.c",
+ "ed25519-donna/ed25519-keccak.c",
+ "ed25519-donna/ed25519-sha3.c",
+ "ed25519-donna/ed25519.c",
+ "ed25519-donna/modm-donna-32bit.c",
+ "elligator2.c",
+ "groestl.c",
+ "hash_to_curve.c",
+ "hasher.c",
+ "hmac.c",
+ "hmac_drbg.c",
+ "memzero.c",
+ "nem.c",
+ "nist256p1.c",
+ "pbkdf2.c",
+ "rand.c",
+ "rfc6979.c",
+ "ripemd160.c",
+ "secp256k1.c",
+ "segwit_addr.c",
+ "sha2.c",
+ "sha3.c",
+ "shamir.c",
+ "slip39.c",
+ "slip39_english.c",
+ "tls_prf.c",
+ ],
+ Some(common_attrs.clone()),
+ );
+
+ let val = cfg!(feature = "universal_fw");
+ let val = Some(if val { "1" } else { "0" });
+ lib.add_defines([
+ ("USE_ETHEREUM", val),
+ ("USE_MONERO", val),
+ ("USE_CARDANO", val),
+ ]);
+
+ let nem = cfg!(feature = "universal_fw") && cfg!(feature = "nem");
+ lib.add_define("USE_NEM", Some(if nem { "1" } else { "0" }));
+ let eos = cfg!(feature = "universal_fw") && cfg!(feature = "eos");
+ lib.add_define("USE_EOS", Some(if eos { "1" } else { "0" }));
+
+ if cfg!(feature = "universal_fw") {
+ lib.add_sources_in_dir_with_attrs(
+ CRYPTO_PATH,
+ [
+ "cardano.c",
+ "monero/base58.c",
+ "monero/serialize.c",
+ "monero/xmr.c",
+ ],
+ Some(common_attrs.clone()),
+ );
+ }
+
+ lib.add_rust_bindings(|builder| {
+ Ok(builder
+ .header(format!("{CRYPTO_PATH}/ed25519-donna/ed25519.h"))
+ .header(format!("{CRYPTO_PATH}/elligator2.h"))
+ .header(format!("{CRYPTO_PATH}/hmac.h"))
+ .header(format!("{CRYPTO_PATH}/sha2.h"))
+ // curve25519
+ .allowlist_function("curve25519_scalarmult")
+ .allowlist_function("curve25519_scalarmult_basepoint")
+ // ed25519
+ .allowlist_type("ed25519_signature")
+ .allowlist_type("ed25519_public_key")
+ .allowlist_function("ed25519_cosi_combine_publickeys")
+ .allowlist_function("ed25519_sign_open")
+ // elligator2
+ .allowlist_function("map_to_curve_elligator2_curve25519")
+ // hmac
+ .allowlist_type("HMAC_SHA256_CTX")
+ .no_copy("HMAC_SHA256_CTX")
+ .allowlist_function("hmac_sha256_Init")
+ .allowlist_function("hmac_sha256_Update")
+ .allowlist_function("hmac_sha256_Final")
+ // sha256
+ .allowlist_var("SHA256_BLOCK_LENGTH")
+ .allowlist_var("SHA256_DIGEST_LENGTH")
+ .allowlist_type("SHA256_CTX")
+ .no_copy("SHA256_CTX")
+ .allowlist_function("sha256_Init")
+ .allowlist_function("sha256_Update")
+ .allowlist_function("sha256_Final")
+ // sha512
+ .allowlist_var("SHA512_BLOCK_LENGTH")
+ .allowlist_var("SHA512_DIGEST_LENGTH")
+ .allowlist_type("SHA512_CTX")
+ .no_copy("SHA512_CTX")
+ .allowlist_function("sha512_Init")
+ .allowlist_function("sha512_Update")
+ .allowlist_function("sha512_Final"))
+ })?;
+
+ Ok(())
+}
+
+fn add_insecure_prng(lib: &mut CLibrary) -> Result<()> {
+ if cfg!(feature = "production") {
+ if !xbuild::is_rust_analyzer() {
+ bail!("insecure_prng cannot be enabled in production builds");
+ }
+ }
+ lib.add_define("USE_INSECURE_PRNG", Some("1"));
+ lib.add_source(PathBuf::from(CRYPTO_PATH).join("rand_insecure.c"));
+
+ Ok(())
+}
+
+fn add_aes_gcm(lib: &mut CLibrary, attrs: &CompileAttrs) -> Result<()> {
+ lib.add_defines([("AES_VAR", None), ("USE_AES_GCM", Some("1"))]);
+
+ lib.add_sources_in_dir_with_attrs(
+ CRYPTO_PATH,
+ ["aes/gf128mul.c", "aes/aesgcm.c"],
+ Some(attrs.clone()),
+ );
+
+ lib.add_rust_bindings(|builder| {
+ Ok(builder
+ .header(format!("{CRYPTO_PATH}/aes/aesgcm.h"))
+ .allowlist_type("gcm_ctx")
+ .no_copy("gcm_ctx")
+ .allowlist_function("gcm_init_and_key")
+ .allowlist_function("gcm_init_message")
+ .allowlist_function("gcm_encrypt")
+ .allowlist_function("gcm_decrypt")
+ .allowlist_function("gcm_auth_header")
+ .allowlist_function("gcm_compute_tag"))
+ })?;
+
+ Ok(())
+}
+
+fn add_noise(lib: &mut CLibrary, attrs: &CompileAttrs) -> Result<()> {
+ lib.add_sources_in_dir_with_attrs(CRYPTO_PATH, ["noise_kk1.c"], Some(attrs.clone()));
+
+ Ok(())
+}
+
+fn add_secp256k1_zkp(lib: &mut CLibrary, _attrs: &CompileAttrs) -> Result<()> {
+ // Deliberately not inheriting `attrs`: they include
+ // `-ftrivial-auto-var-init=zero`, which we'd like to apply to the whole
+ // crypto library, but the resulting code growth overflows the bootloader
+ // flash, where secp256k1 is also used.
+ // let attrs = attrs.clone().with_flag("-Wno-unused-function");
+ let attrs = CompileAttrs::new()
+ .with_flag("-ffreestanding")
+ .with_flag("-Wno-unused-function");
+
+ lib.add_defines([
+ ("USE_SECP256K1_ZKP", None),
+ ("USE_SECP256K1_ZKP_ECDSA", None),
+ ("USE_EXTERNAL_DEFAULT_CALLBACKS", None),
+ ("ECMULT_GEN_PREC_BITS", Some("2")),
+ ("ECMULT_WINDOW_SIZE", Some("2")),
+ ("ENABLE_MODULE_GENERATOR", None),
+ ("ENABLE_MODULE_RECOVERY", None),
+ ("ENABLE_MODULE_SCHNORRSIG", None),
+ ("ENABLE_MODULE_EXTRAKEYS", None),
+ ("ENABLE_MODULE_ECDH", None),
+ ]);
+
+ // TODO get rid of #include <vendor/... includes in crypto/zkp_*.c
+ // lib.add_include(PathBuf::from(SECP256K1_PATH).join("include"));
+ lib.add_include("../.."); // points to vendor folder
+
+ if cfg!(feature = "emulator") {
+ lib.add_define("SECP256K1_CONTEXT_SIZE", Some("208"));
+ } else {
+ lib.add_define("SECP256K1_CONTEXT_SIZE", Some("180"));
+ lib.add_define("USE_EXTERNAL_ASM", None);
+ lib.add_sources_in_dir(SECP256K1_PATH, ["src/asm/field_10x26_arm.s"]);
+ }
+
+ lib.add_sources_in_dir_with_attrs(
+ SECP256K1_PATH,
+ [
+ "src/secp256k1.c",
+ "src/precomputed_ecmult.c",
+ "src/precomputed_ecmult_gen.c",
+ ],
+ Some(attrs.clone()),
+ );
+
+ lib.add_sources_in_dir_with_attrs(
+ CRYPTO_PATH,
+ ["zkp_context.c", "zkp_ecdsa.c", "zkp_bip340.c"],
+ Some(attrs.clone()),
+ );
+ Ok(())
+}
+
+fn add_sphincsplus(lib: &mut CLibrary, attrs: &CompileAttrs) -> Result<()> {
+ lib.add_include(SPHINCSPLUS_PATH);
+
+ lib.add_define("PARAMS", Some("sphincs-sha2-128s"));
+
+ let attrs = attrs.clone().with_flag("-Wno-incompatible-pointer-types");
+
+ lib.add_sources_in_dir_with_attrs(
+ SPHINCSPLUS_PATH,
+ [
+ "address.c",
+ "fors.c",
+ "hash_sha2.c",
+ "sha2.c",
+ "sign.c",
+ "thash_sha2_simple.c",
+ "utils.c",
+ "wots.c",
+ ],
+ Some(attrs),
+ );
+ Ok(())
+}
+
+fn add_mldsa(lib: &mut CLibrary, attrs: &CompileAttrs) -> Result<()> {
+ lib.add_include(MLDSA_PATH);
+
+ lib.add_defines([
+ ("MLD_CONFIG_NAMESPACE_PREFIX", Some("mldsa")),
+ ("MLD_CONFIG_NO_RANDOMIZED_API", Some("1")),
+ ]);
+
+ lib.add_sources_in_dir_with_attrs(
+ MLDSA_PATH,
+ [
+ "src/fips202/fips202.c",
+ "src/fips202/fips202x4.c",
+ "src/fips202/keccakf1600.c",
+ "src/packing.c",
+ "src/poly.c",
+ "src/poly_kl.c",
+ "src/polyvec.c",
+ "src/sign.c",
+ ],
+ Some(attrs.clone()),
+ );
+
+ Ok(())
+}
diff --git a/core/embed/crypto/src/aesgcm.rs b/core/embed/crypto/src/aesgcm.rs
new file mode 100644
index 00000000..86d02162
--- /dev/null
+++ b/core/embed/crypto/src/aesgcm.rs
@@ -0,0 +1,600 @@
+use core::pin::Pin;
+
+use rtl::error::ensure;
+use zeroize::Zeroize;
+
+use super::memory::Memory;
+use super::{Error, consteq, ffi};
+
+// Tag size is a parameter but we fix it to 16 here for simplicity.
+pub const TAG_SIZE: usize = 16;
+pub type Tag = [u8; TAG_SIZE];
+
+// for bindgen RETURN_* macros are u32, just redefine the only one we are using
+const RETURN_GOOD: i32 = 0;
+const KEY_SIZES: [usize; 3] = [16, 24, 32];
+
+#[repr(u8)]
+#[derive(PartialEq)]
+enum State {
+ Init,
+ Processing,
+ Finished,
+ Failed,
+}
+
+struct AesGcmInner<'a> {
+ ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
+ state: State,
+}
+
+pub struct AesGcmEncrypt<'a>(AesGcmInner<'a>);
+pub struct AesGcmDecrypt<'a>(AesGcmInner<'a>);
+
+impl<'a> AesGcmInner<'a> {
+ fn new(
+ mut ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
+ key: &[u8],
+ iv: &[u8],
+ ) -> Result<Self, Error> {
+ if !KEY_SIZES.contains(&key.len()) {
+ return Err(Error::InvalidParams);
+ }
+ // initialize the context
+ // SAFETY: ffi
+ let res =
+ unsafe { ffi::gcm_init_and_key(key.as_ptr(), key.len() as cty::c_ulong, ctx.inner()) };
+ ensure!(res == RETURN_GOOD, "gcm_init_and_key");
+ let mut aesgcm = Self {
+ ctx,
+ state: State::Init,
+ };
+ aesgcm.reset(iv);
+ Ok(aesgcm)
+ }
+
+ fn reset(&mut self, iv: &[u8]) {
+ // SAFETY: ffi
+ let res = unsafe {
+ ffi::gcm_init_message(iv.as_ptr(), iv.len() as cty::c_ulong, self.ctx.inner())
+ };
+ ensure!(res == RETURN_GOOD, "gcm_init_message");
+ self.state = State::Init;
+ }
+
+ fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
+ self.check_state(&[State::Init, State::Processing])?;
+
+ // SAFETY: ffi
+ let res = unsafe {
+ ffi::gcm_auth_header(data.as_ptr(), data.len() as cty::c_ulong, self.ctx.inner())
+ };
+ ensure!(res == RETURN_GOOD, "gcm_auth_header");
+ Ok(())
+ }
+
+ fn finish(&mut self) -> Result<Tag, Error> {
+ self.check_state(&[State::Init, State::Processing])?;
+ self.state = State::Finished;
+
+ let mut tag = [0u8; TAG_SIZE];
+ // SAFETY: ffi
+ let res = unsafe {
+ ffi::gcm_compute_tag(
+ tag.as_mut_ptr(),
+ tag.len() as cty::c_ulong,
+ self.ctx.inner(),
+ )
+ };
+ if res != RETURN_GOOD {
+ self.state = State::Failed;
+ return Err(Error::InvalidContext);
+ }
+ Ok(tag)
+ }
+
+ fn check_state(&self, allowed: &[State]) -> Result<(), Error> {
+ if !allowed.contains(&self.state) {
+ return Err(Error::InvalidContext);
+ }
+ Ok(())
+ }
+}
+
+impl<'a> AesGcmEncrypt<'a> {
+ pub fn new(
+ ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
+ key: &[u8],
+ iv: &[u8],
+ ) -> Result<Self, Error> {
+ Ok(Self(AesGcmInner::new(ctx, key, iv)?))
+ }
+
+ pub fn reset(&mut self, iv: &[u8]) {
+ self.0.reset(iv)
+ }
+
+ pub fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
+ self.0.auth(data)
+ }
+
+ pub fn encrypt<'b>(
+ &mut self,
+ plaintext: &[u8],
+ buffer: &'b mut [u8],
+ ) -> Result<&'b [u8], Error> {
+ let buffer = buffer
+ .get_mut(..plaintext.len())
+ .ok_or(Error::InvalidParams)?;
+ buffer.copy_from_slice(plaintext);
+ match self.encrypt_in_place(buffer) {
+ Err(e) => {
+ buffer.zeroize(); // wipe plaintext from buffer on failure
+ Err(e)
+ }
+ _ => Ok(buffer),
+ }
+ }
+
+ pub fn encrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
+ self.0.check_state(&[State::Init, State::Processing])?;
+ self.0.state = State::Processing;
+
+ let res = unsafe {
+ ffi::gcm_encrypt(
+ data.as_mut_ptr(),
+ data.len() as cty::c_ulong,
+ self.0.ctx.inner(),
+ )
+ };
+ ensure!(res == RETURN_GOOD, "gcm_encrypt");
+ Ok(())
+ }
+
+ pub fn finish(&mut self) -> Result<Tag, Error> {
+ self.0.finish()
+ }
+
+ pub fn memory() -> Memory<ffi::gcm_ctx> {
+ Memory::default()
+ }
+}
+
+impl<'a> AesGcmDecrypt<'a> {
+ pub fn new(
+ ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
+ key: &[u8],
+ iv: &[u8],
+ ) -> Result<Self, Error> {
+ Ok(Self(AesGcmInner::new(ctx, key, iv)?))
+ }
+
+ pub fn reset(&mut self, iv: &[u8]) {
+ self.0.reset(iv)
+ }
+
+ pub fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
+ self.0.auth(data)
+ }
+
+ pub fn decrypt<'b>(
+ &mut self,
+ ciphertext: &[u8],
+ buffer: &'b mut [u8],
+ ) -> Result<&'b [u8], Error> {
+ let buffer = buffer
+ .get_mut(..ciphertext.len())
+ .ok_or(Error::InvalidParams)?;
+ buffer.copy_from_slice(ciphertext);
+ self.decrypt_in_place(buffer)?;
+ Ok(buffer)
+ }
+
+ pub fn decrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
+ self.0.check_state(&[State::Init, State::Processing])?;
+ self.0.state = State::Processing;
+
+ // SAFETY: ffi
+ let res = unsafe {
+ ffi::gcm_decrypt(
+ data.as_mut_ptr(),
+ data.len() as cty::c_ulong,
+ self.0.ctx.inner(),
+ )
+ };
+ ensure!(res == RETURN_GOOD, "gcm_decrypt");
+ Ok(())
+ }
+
+ pub fn finish(&mut self, expected_tag: &Tag) -> Result<(), Error> {
+ let computed_tag = self.0.finish()?;
+ if !consteq(&computed_tag, expected_tag) {
+ return Err(Error::AuthenticationFailed);
+ }
+ Ok(())
+ }
+
+ pub fn memory() -> Memory<ffi::gcm_ctx> {
+ Memory::default()
+ }
+}
+
+impl Drop for AesGcmInner<'_> {
+ fn drop(&mut self) {
+ self.ctx.zeroize();
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::super::memory::init_ctx;
+ use super::*;
+
+ struct Vector {
+ key: &'static str,
+ iv: &'static str,
+ aad: &'static str,
+ plaintext: &'static str,
+ ciphertext: &'static str,
+ tag: &'static str,
+ }
+
+ impl Vector {
+ fn decoded(&self) -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Tag) {
+ let key = hex::decode(self.key).unwrap();
+ let iv = hex::decode(self.iv).unwrap();
+ let aad = hex::decode(self.aad).unwrap();
+ let pt = hex::decode(self.plaintext).unwrap();
+ let ct = hex::decode(self.ciphertext).unwrap();
+ let tag = hex::decode(self.tag).unwrap();
+ (key, iv, aad, pt, ct, Tag::try_from(tag).unwrap())
+ }
+ }
+
+ const AES_GCM_VECTORS: &[Vector] = &[
+ // first 10 vectors from https://github.com/BrianGladman/modes/blob/master/testvals/gcm.1
+ Vector {
+ key: "00000000000000000000000000000000",
+ iv: "000000000000000000000000",
+ aad: "",
+ plaintext: "",
+ ciphertext: "",
+ tag: "58e2fccefa7e3061367f1d57a4e7455a",
+ },
+ Vector {
+ key: "00000000000000000000000000000000",
+ iv: "000000000000000000000000",
+ aad: "",
+ plaintext: "00000000000000000000000000000000",
+ ciphertext: "0388dace60b6a392f328c2b971b2fe78",
+ tag: "ab6e47d42cec13bdf53a67b21257bddf",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308",
+ iv: "cafebabefacedbaddecaf888",
+ aad: "",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
+ ciphertext: "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
+ tag: "4d5c2af327cd64a62cf35abd2ba6fab4",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308",
+ iv: "cafebabefacedbaddecaf888",
+ aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
+ ciphertext: "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
+ tag: "5bc94fbc3221a5db94fae95ae7121a47",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308",
+ iv: "cafebabefacedbad",
+ aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
+ ciphertext: "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
+ tag: "3612d2e79e3b0785561be14aaca2fccb",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308",
+ iv: "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b",
+ aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
+ ciphertext: "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
+ tag: "619cc5aefffe0bfa462af43c1699d050",
+ },
+ Vector {
+ key: "000000000000000000000000000000000000000000000000",
+ iv: "000000000000000000000000",
+ aad: "",
+ plaintext: "",
+ ciphertext: "",
+ tag: "cd33b28ac773f74ba00ed1f312572435",
+ },
+ Vector {
+ key: "000000000000000000000000000000000000000000000000",
+ iv: "000000000000000000000000",
+ aad: "",
+ plaintext: "00000000000000000000000000000000",
+ ciphertext: "98e7247c07f0fe411c267e4384b0f600",
+ tag: "2ff58d80033927ab8ef4d4587514f0fb",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308feffe9928665731c",
+ iv: "cafebabefacedbaddecaf888",
+ aad: "",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
+ ciphertext: "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256",
+ tag: "9924a7c8587336bfb118024db8674a14",
+ },
+ Vector {
+ key: "feffe9928665731c6d6a8f9467308308feffe9928665731c",
+ iv: "cafebabefacedbaddecaf888",
+ aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
+ ciphertext: "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710",
+ tag: "2519498e80f1478f37ba55bd6d27618c",
+ },
+ // test vectors from test_trezor.wire.thp.crypto.py
+ Vector {
+ key: "0001020304050607000102030405060700010203040506070001020304050607",
+ iv: "000000000000000000000000",
+ aad: "5564",
+ plaintext: "00010203040506070809",
+ ciphertext: "e2c9dd152fbee5821ea7",
+ tag: "10625812de81b14a46b9f1e5100a6d0c",
+ },
+ Vector {
+ key: "0001020304050607000102030405060700010203040506070001020304050607",
+ iv: "000000000000000000000001",
+ aad: "5564",
+ plaintext: "00010203040506070809",
+ ciphertext: "79811619ddb07c2b99f8",
+ tag: "71c6b872cdc499a7e9a3c7441f053214",
+ },
+ Vector {
+ key: "0001020304050607000102030405060700010203040506070001020304050607",
+ iv: "000000000000000000000171",
+ aad: "5564",
+ plaintext: "000102030405060708090a0b0c0d0e0f",
+ ciphertext: "03bd030390f2dfe815a61c2b157a064f",
+ tag: "c1200f8a7ae9a6d32cef0fff878d55c2",
+ },
+ Vector {
+ key: "0001020304050607000102030405060700010203040506070001020304050607",
+ iv: "000000000000000000000171",
+ aad: "5564738291",
+ plaintext: "000102030405060708090a0b0c0d0e0f",
+ ciphertext: "03bd030390f2dfe815a61c2b157a064f",
+ tag: "693ac160cd93a20f7fc255f049d808d0",
+ },
+ ];
+
+ #[test]
+ fn test_vectors() {
+ for v in AES_GCM_VECTORS {
+ let (key, iv, aad, plaintext, ciphertext, tag) = v.decoded();
+
+ init_ctx!(AesGcmEncrypt, ctx_enc, &key, &iv);
+ let mut ctx_enc = ctx_enc.unwrap();
+ init_ctx!(AesGcmDecrypt, ctx_dec, &key, &iv);
+ let mut ctx_dec = ctx_dec.unwrap();
+
+ if !plaintext.is_empty() {
+ let mut buffer = vec![0; plaintext.len()];
+ let result = ctx_enc.encrypt(&plaintext, &mut buffer).unwrap();
+ assert_eq!(hex::encode(result), v.ciphertext);
+
+ let result = ctx_dec.decrypt(&ciphertext, &mut buffer).unwrap();
+ assert_eq!(hex::encode(result), v.plaintext);
+ }
+
+ if !aad.is_empty() {
+ ctx_enc.auth(&aad).unwrap();
+ ctx_dec.auth(&aad).unwrap();
+ }
+
+ let result = ctx_enc.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+ ctx_dec.finish(&tag).unwrap();
+ }
+ }
+
+ #[test]
+ fn test_state() {
+ // ok: empty string tag - encryption
+ init_ctx!(AesGcmEncrypt, ctx_enc, &[0u8; 16], b"1");
+ let mut ctx_enc = ctx_enc.unwrap();
+ let tag_empty = ctx_enc.finish().unwrap();
+
+ // ok: empty string tag - decryption
+ init_ctx!(AesGcmDecrypt, ctx_dec, &[0u8; 16], b"1");
+ let mut ctx_dec = ctx_dec.unwrap();
+ ctx_dec.finish(&tag_empty).unwrap();
+
+ // ok: any single operation
+ // not ok: after reset
+ let mut dest = [0u8; 4];
+ let mut dest2 = [0u8; 16];
+ ctx_enc.reset(b"2");
+ ctx_enc.encrypt(b"asdf", &mut dest).unwrap();
+ let tag2 = ctx_enc.finish().unwrap();
+ assert!(ctx_enc.encrypt(b"asdf", &mut dest2).is_err());
+
+ ctx_dec.reset(b"2");
+ ctx_dec.decrypt(&dest, &mut dest2).unwrap();
+ ctx_dec.finish(&tag2).unwrap();
+ assert!(ctx_dec.decrypt(b"fdsa", &mut dest).is_err());
+
+ ctx_enc.reset(b"5");
+ ctx_enc.auth(b"foobar").unwrap();
+ let tag5 = ctx_enc.finish().unwrap();
+ assert!(ctx_enc.auth(b"foobar").is_err());
+
+ ctx_dec.reset(b"5");
+ ctx_dec.auth(b"foobar").unwrap();
+ ctx_dec.finish(&tag5).unwrap();
+ assert!(ctx_dec.auth(b"foobar").is_err());
+ }
+
+ // test vectors from
+ // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/mac/gcmtestvectors.zip
+ const NIST_VECTORS: &[Vector] = &[
+ Vector {
+ key: "11754cd72aec309bf52f7687212e8957",
+ iv: "3c819d9a9bed087615030b65",
+ plaintext: "",
+ aad: "",
+ ciphertext: "",
+ tag: "250327c674aaf477aef2675748cf6971",
+ },
+ Vector {
+ key: "fe9bb47deb3a61e423c2231841cfd1fb",
+ iv: "4d328eb776f500a2f7fb47aa",
+ plaintext: "f1cc3818e421876bb6b8bbd6c9",
+ aad: "",
+ ciphertext: "b88c5c1977b35b517b0aeae967",
+ tag: "43fd4727fe5cdb4b5b42818dea7ef8c9",
+ },
+ Vector {
+ key: "6f44f52c2f62dae4e8684bd2bc7d16ee7c557330305a790d",
+ iv: "9ae35825d7c7edc9a39a0732",
+ plaintext: "37222d30895eb95884bbbbaee4d9cae1",
+ aad: "1b4236b846fc2a0f782881ba48a067e9",
+ ciphertext: "a54b5da33fc1196a8ef31a5321bfcaeb",
+ tag: "1c198086450ae1834dd6c2636796bce2",
+ },
+ Vector {
+ key: "05f714021372ae1c8d72c98e6307fbddb26ee27615860a9fb48ba4c3ea360a00",
+ iv: "c0",
+ plaintext: "ec3afbaa1447e47ce068bffb787bd0cadc9f0deceb11fa78e981271390578ae95891f26664b5e62d1fd5fd0d0767a54da5f86f",
+ aad: "faf9fa457a8e70ea709da28545f18f041351e8d5",
+ ciphertext: "c8c5816ba9e7e0d20820dc0064a519a277889f5ac9661c9882b5a9896fd12836c6721514e885b1d34f5e888d1d85abce8c2ebb",
+ tag: "0856f211fade7d26d64478ca46025a3c",
+ },
+ ];
+
+ // following tests ported from test_trezor.crypto.aesgcm.py
+ #[test]
+ fn test_gcm() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct, tag) = v.decoded();
+
+ // Test encryption.
+ init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ if !aad.is_empty() {
+ ctx.auth(&aad).unwrap();
+ }
+ let mut buffer = vec![0; pt.len()];
+ let result = ctx.encrypt(&pt, &mut buffer).unwrap();
+ assert_eq!(hex::encode(result), v.ciphertext);
+
+ let result = ctx.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+
+ // Test decryption.
+ init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ if !aad.is_empty() {
+ ctx.auth(&aad).unwrap();
+ }
+ let result = ctx.decrypt(&ct, &mut buffer).unwrap();
+ assert_eq!(hex::encode(result), v.plaintext);
+
+ ctx.finish(&tag).unwrap();
+ }
+ }
+
+ #[test]
+ fn test_gcm_in_place() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct, tag) = v.decoded();
+
+ // Test encryption.
+ init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ if !aad.is_empty() {
+ ctx.auth(&aad).unwrap();
+ }
+ let mut buffer = Vec::new();
+ buffer.extend_from_slice(&pt);
+ ctx.encrypt_in_place(&mut buffer).unwrap();
+ assert_eq!(hex::encode(buffer), v.ciphertext);
+
+ let result = ctx.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+
+ // Test decryption.
+ init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ if !aad.is_empty() {
+ ctx.auth(&aad).unwrap();
+ }
+ let mut buffer = Vec::new();
+ buffer.extend_from_slice(&ct);
+ ctx.decrypt_in_place(&mut buffer).unwrap();
+ assert_eq!(hex::encode(buffer), v.plaintext);
+
+ ctx.finish(&tag).unwrap();
+ }
+ }
+
+ #[test]
+ fn test_gcm_chunks() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct, tag) = v.decoded();
+ let chunk_len = pt.len() / 3;
+ let mut buffer = vec![0; pt.len()];
+
+ init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ ctx.decrypt(&ct[..chunk_len], &mut buffer[..chunk_len])
+ .unwrap();
+ ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
+ ctx.decrypt(&ct[chunk_len..], &mut buffer[chunk_len..])
+ .unwrap();
+ ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
+ assert_eq!(hex::encode(buffer), v.plaintext);
+ ctx.finish(&tag).unwrap();
+
+ buffer = vec![0; pt.len()];
+ init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
+ ctx.encrypt(&pt[..chunk_len], &mut buffer[..chunk_len])
+ .unwrap();
+ ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
+ ctx.encrypt(&pt[chunk_len..], &mut buffer[chunk_len..])
+ .unwrap();
+ assert_eq!(hex::encode(buffer), v.ciphertext);
+ assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
+ }
+ }
+
+ #[test]
+ fn test_gcm_chunks_in_place() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct, tag) = v.decoded();
+ let chunk_len = pt.len() / 3;
+
+ let mut buffer = ct;
+ init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ ctx.decrypt_in_place(&mut buffer[..chunk_len]).unwrap();
+ ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
+ ctx.decrypt_in_place(&mut buffer[chunk_len..]).unwrap();
+ ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
+ assert_eq!(hex::encode(buffer), v.plaintext);
+ ctx.finish(&tag).unwrap();
+
+ let mut buffer = pt;
+ init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
+ let mut ctx = ctx.unwrap();
+ ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
+ ctx.encrypt_in_place(&mut buffer[..chunk_len]).unwrap();
+ ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
+ ctx.encrypt_in_place(&mut buffer[chunk_len..]).unwrap();
+ assert_eq!(hex::encode(buffer), v.ciphertext);
+ assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
+ }
+ }
+}
diff --git a/core/embed/crypto/src/cosi.rs b/core/embed/crypto/src/cosi.rs
new file mode 100644
index 00000000..3ac7167c
--- /dev/null
+++ b/core/embed/crypto/src/cosi.rs
@@ -0,0 +1,65 @@
+use heapless::Vec;
+
+use super::{Error, ed25519, ffi};
+
+const MAX_PUBKEYS: usize = 3;
+
+pub struct Signature {
+ sigmask: u8,
+ signature: ed25519::Signature,
+}
+
+impl Signature {
+ pub fn new(sigmask: u8, signature: ed25519::Signature) -> Self {
+ Self { sigmask, signature }
+ }
+}
+
+pub fn verify(
+ threshold: u8,
+ message: &[u8],
+ public_keys: &[ed25519::PublicKey],
+ signature: &Signature,
+) -> Result<(), Error> {
+ if threshold < 1 {
+ return Err(Error::InvalidParams);
+ }
+ let selected_keys = select_keys(signature.sigmask, public_keys)?;
+ if selected_keys.len() < threshold as usize {
+ return Err(Error::InvalidParams);
+ }
+ let combined_key = combine_publickeys(&selected_keys)?;
+ ed25519::verify(message, &combined_key, &signature.signature)
+}
+
+fn select_keys(
+ mut sigmask: u8,
+ keys: &[ed25519::PublicKey],
+) -> Result<Vec<ed25519::PublicKey, MAX_PUBKEYS>, Error> {
+ let mut selected_keys = Vec::new();
+ for key in keys {
+ if sigmask & 1 != 0 {
+ if selected_keys.push(*key).is_err() {
+ return Err(Error::InvalidSigmask);
+ }
+ }
+ sigmask >>= 1;
+ }
+ if sigmask != 0 {
+ Err(Error::InvalidParams)
+ } else {
+ Ok(selected_keys)
+ }
+}
+
+fn combine_publickeys(keys: &[ed25519::PublicKey]) -> Result<ed25519::PublicKey, Error> {
+ let mut combined_key = ed25519::PublicKey::default();
+ let res = unsafe {
+ ffi::ed25519_cosi_combine_publickeys(&mut combined_key as *mut _, keys.as_ptr(), keys.len())
+ };
+ if res == 0 {
+ Ok(combined_key)
+ } else {
+ Err(Error::InvalidEncoding)
+ }
+}
diff --git a/core/embed/crypto/src/crc32.rs b/core/embed/crypto/src/crc32.rs
new file mode 100644
index 00000000..2309c93c
--- /dev/null
+++ b/core/embed/crypto/src/crc32.rs
@@ -0,0 +1,73 @@
+pub struct Crc32 {
+ value: u32,
+}
+
+static CRC32TAB: [u32; 16] = [
+ 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
+];
+
+impl Crc32 {
+ pub fn new() -> Self {
+ Self { value: u32::MAX }
+ }
+
+ pub fn update(mut self, data: &[u8]) -> Self {
+ for b in data {
+ self.value ^= u32::from(*b);
+ self.value = CRC32TAB[(self.value & 0x0f) as usize] ^ (self.value >> 4);
+ self.value = CRC32TAB[(self.value & 0x0f) as usize] ^ (self.value >> 4);
+ }
+ self
+ }
+
+ pub fn finalize(self) -> [u8; 4] {
+ let inverted = self.value ^ u32::MAX;
+ inverted.to_be_bytes()
+ }
+}
+
+pub fn digest(data: &[u8]) -> [u8; 4] {
+ Crc32::new().update(data).finalize()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const CRC32_VECTORS: &[(&[u8], &str)] = &[
+ (b"", "00000000"),
+ (b"a", "e8b7be43"),
+ (b"abc", "352441c2"),
+ (b"message digest", "20159d7f"),
+ (b"abcdefghijklmnopqrstuvwxyz", "4c2750bd"),
+ (
+ b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "1fc2e6d2",
+ ),
+ (
+ b"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+ "7ca94a72",
+ ),
+ ];
+
+ fn hexdigest(data: &[u8]) -> String {
+ let digest = digest(data);
+ hex::encode(digest)
+ }
+
+ #[test]
+ fn test_no_update() {
+ let out = Crc32::new().finalize();
+ let out_hex = hex::encode(out);
+ assert_eq!(out_hex, "00000000");
+ }
+
+ #[test]
+ fn test_vectors() {
+ for (data, expected) in CRC32_VECTORS {
+ let out_hex = hexdigest(data);
+ assert_eq!(out_hex, *expected);
+ }
+ }
+}
diff --git a/core/embed/crypto/src/curve25519.rs b/core/embed/crypto/src/curve25519.rs
new file mode 100644
index 00000000..ca67f48b
--- /dev/null
+++ b/core/embed/crypto/src/curve25519.rs
@@ -0,0 +1,293 @@
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+use super::ffi;
+
+pub type Curve25519KeyBytes = ffi::curve25519_key;
+pub const CURVE25519_KEY_SIZE: usize = core::mem::size_of::<Curve25519KeyBytes>();
+
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct Point {
+ bytes: Curve25519KeyBytes,
+}
+
+#[cfg(feature = "thp")]
+impl trezor_thp::channel::U8Array for Point {
+ fn new() -> Self {
+ Self {
+ bytes: [0u8; CURVE25519_KEY_SIZE],
+ }
+ }
+
+ fn new_with(c: u8) -> Self {
+ Self {
+ bytes: [c; CURVE25519_KEY_SIZE],
+ }
+ }
+
+ fn from_slice(src: &[u8]) -> Self {
+ let mut bytes = [0u8; CURVE25519_KEY_SIZE];
+ bytes.copy_from_slice(src);
+ Self { bytes }
+ }
+
+ fn len() -> usize {
+ CURVE25519_KEY_SIZE
+ }
+
+ fn as_slice(&self) -> &[u8] {
+ &self.bytes
+ }
+
+ fn as_mut(&mut self) -> &mut [u8] {
+ &mut self.bytes
+ }
+}
+
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct Scalar {
+ bytes: Curve25519KeyBytes,
+}
+
+#[cfg(feature = "thp")]
+impl trezor_thp::channel::U8Array for Scalar {
+ fn new() -> Self {
+ Self {
+ bytes: [0u8; CURVE25519_KEY_SIZE],
+ }
+ }
+
+ fn new_with(c: u8) -> Self {
+ Self {
+ bytes: [c; CURVE25519_KEY_SIZE],
+ }
+ }
+
+ fn from_slice(src: &[u8]) -> Self {
+ let mut bytes = [0u8; CURVE25519_KEY_SIZE];
+ bytes.copy_from_slice(src);
+ Self { bytes }
+ }
+
+ fn len() -> usize {
+ CURVE25519_KEY_SIZE
+ }
+
+ fn as_slice(&self) -> &[u8] {
+ &self.bytes
+ }
+
+ fn as_mut(&mut self) -> &mut [u8] {
+ &mut self.bytes
+ }
+}
+
+impl Scalar {
+ pub fn from_bytes(bytes: Curve25519KeyBytes) -> Self {
+ let mut res = Self { bytes };
+ // taken from https://cr.yp.to/ecdh.html
+ res.bytes[0] &= 248;
+ res.bytes[31] &= 127;
+ res.bytes[31] |= 64;
+ res
+ }
+}
+
+impl Point {
+ pub fn from_secret(secret: &Scalar) -> Self {
+ let mut res = Self {
+ bytes: [0u8; CURVE25519_KEY_SIZE],
+ };
+ let dest = res.bytes.as_mut_ptr();
+ let secret_bytes = secret.bytes.as_ptr();
+ // SAFETY: ffi
+ unsafe {
+ ffi::curve25519_scalarmult_basepoint(dest, secret_bytes);
+ }
+ res
+ }
+
+ pub fn multiply(&self, secret: &Scalar) -> Self {
+ let mut res = Self {
+ bytes: [0u8; CURVE25519_KEY_SIZE],
+ };
+ let dest = res.bytes.as_mut_ptr();
+ let secret_bytes = secret.bytes.as_ptr();
+ let point_bytes = self.bytes.as_ptr();
+ // SAFETY: ffi
+ unsafe { ffi::curve25519_scalarmult(dest, secret_bytes, point_bytes) }
+ res
+ }
+
+ // No need for validation, every 32 byte array represents a valid point.
+ // See https://cr.yp.to/ecdh/curve25519-20060209.pdf
+ pub fn from_bytes(bytes: Curve25519KeyBytes) -> Self {
+ Self { bytes }
+ }
+
+ pub fn to_bytes(&self) -> Curve25519KeyBytes {
+ self.bytes
+ }
+
+ pub fn map_to_curve_elligator2(input: &Curve25519KeyBytes) -> Self {
+ let mut res = Self {
+ bytes: [0u8; CURVE25519_KEY_SIZE],
+ };
+ let dest = res.bytes.as_mut_ptr();
+ // SAFETY: ffi
+ let ok = unsafe { ffi::map_to_curve_elligator2_curve25519(input.as_ptr(), dest) };
+ assert!(ok); // always returns true
+ res
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::sync::{LazyLock, Mutex};
+
+ use rand::prelude::*;
+ use rand::rngs::SmallRng;
+
+ use super::*;
+
+ static INSECURE_RNG: LazyLock<Mutex<SmallRng>> = LazyLock::new(|| {
+ let time_seed = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos() as u64;
+ Mutex::new(SmallRng::seed_from_u64(time_seed))
+ });
+
+ fn fill_random_bytes(bytes: &mut [u8]) {
+ INSECURE_RNG.lock().unwrap().fill_bytes(bytes);
+ }
+
+ fn generate_scalar() -> Scalar {
+ let mut bytes = [0u8; CURVE25519_KEY_SIZE];
+ fill_random_bytes(&mut bytes);
+ Scalar::from_bytes(bytes)
+ }
+
+ #[test]
+ fn test_generate() {
+ for _ in 0..100 {
+ let bytes = generate_scalar().bytes;
+ assert!(bytes[0] & 7 == 0 && bytes[31] & 128 == 0 && bytes[31] & 64 == 64)
+ }
+ }
+
+ #[test]
+ fn test_multiply() {
+ const VECTORS: &[(&'static str, &'static str, &'static str)] = &[(
+ "38c9d9b17911de26ed812f5cc19c0029e8d016bcbc6078bc9db2af33f1761e4a",
+ "311b6248af8dabec5cc81eac5bf229925f6d218a12e0547fb1856e015cc76f5d",
+ "a93dbdb23e5c99da743e203bd391af79f2b83fb8d0fd6ec813371c71f08f2d4d",
+ )];
+
+ for (sk, pk, session) in VECTORS {
+ let sk = hex::decode(sk).unwrap();
+ let sk = Scalar::from_bytes(*sk.first_chunk::<32>().unwrap());
+
+ let pk = hex::decode(pk).unwrap();
+ let pk = Point::from_bytes(*pk.first_chunk::<32>().unwrap());
+
+ let session = hex::decode(session).unwrap();
+ let session = session.first_chunk::<32>().unwrap();
+
+ let session2 = pk.multiply(&sk);
+ assert_eq!(session2.to_bytes(), *session);
+ }
+ }
+
+ #[test]
+ fn test_multiply_random() {
+ for _ in 0..100 {
+ let sk1 = generate_scalar();
+ let sk2 = generate_scalar();
+ let pk1 = Point::from_secret(&sk1);
+ let pk2 = Point::from_secret(&sk2);
+ let session1 = pk2.multiply(&sk1);
+ let session2 = pk1.multiply(&sk2);
+ assert_eq!(session1.to_bytes(), session2.to_bytes());
+ }
+ }
+
+ #[test]
+ fn test_clamping() {
+ let mut bytes1 = [0u8; 32];
+ fill_random_bytes(&mut bytes1);
+
+ let mut bytes2 = bytes1;
+ // flipping the bits affected by clamping should not change the results
+ bytes2[0] |= !0xf8;
+ bytes2[31] |= !0x7f;
+ bytes2[31] &= !0x40;
+
+ let sk1 = Scalar::from_bytes(bytes1);
+ let sk2 = Scalar::from_bytes(bytes2);
+
+ let pk1 = Point::from_secret(&sk1);
+ let pk2 = Point::from_secret(&sk2);
+ assert_eq!(pk1.to_bytes(), pk2.to_bytes());
+
+ let sk3 = generate_scalar();
+ let pk3 = Point::from_secret(&sk3);
+ let res1 = pk3.multiply(&sk1);
+ let res2 = pk3.multiply(&sk2);
+ assert_eq!(res1.to_bytes(), res2.to_bytes());
+ }
+
+ #[cfg(feature = "thp")]
+ #[test]
+ fn test_elligator2() {
+ // https://elligator.org/vectors/curve25519_direct.vec
+ const VECTORS: &[(&'static str, &'static str)] = &[
+ (
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ ),
+ (
+ "66665895c5bc6e44ba8d65fd9307092e3244bf2c18877832bd568cb3a2d38a12",
+ "04d44290d13100b2c25290c9343d70c12ed4813487a07ac1176daa5925e7975e",
+ ),
+ (
+ "673a505e107189ee54ca93310ac42e4545e9e59050aaac6f8b5f64295c8ec02f",
+ "242ae39ef158ed60f20b89396d7d7eef5374aba15dc312a6aea6d1e57cacf85e",
+ ),
+ (
+ "990b30e04e1c3620b4162b91a33429bddb9f1b70f1da6e5f76385ed3f98ab131",
+ "998e98021eb4ee653effaa992f3fae4b834de777a953271baaa1fa3fef6b776e",
+ ),
+ (
+ "341a60725b482dd0de2e25a585b208433044bc0a1ba762442df3a0e888ca063c",
+ "683a71d7fca4fc6ad3d4690108be808c2e50a5af3174486741d0a83af52aeb01",
+ ),
+ (
+ "922688fa428d42bc1fa8806998fbc5959ae801817e85a42a45e8ec25a0d7541a",
+ "696f341266c64bcfa7afa834f8c34b2730be11c932e08474d1a22f26ed82410b",
+ ),
+ (
+ "0d3b0eb88b74ed13d5f6a130e03c4ad607817057dc227152827c0506a538bb3a",
+ "0b00df174d9fb0b6ee584d2cf05613130bad18875268c38b377e86dfefef177f",
+ ),
+ (
+ "01a3ea5658f4e00622eeacf724e0bd82068992fae66ed2b04a8599be16662e35",
+ "7ae4c58bc647b5646c9f5ae4c2554ccbf7c6e428e7b242a574a5a9c293c21f7e",
+ ),
+ (
+ "1d991dff82a84afe97874c0f03a60a56616a15212fbe10d6c099aa3afcfabe35",
+ "f81f235696f81df90ac2fc861ceee517bff611a394b5be5faaee45584642fb0a",
+ ),
+ (
+ "185435d2b005a3b63f3187e64a1ef3582533e1958d30e4e4747b4d1d3376c728",
+ "f938b1b320abb0635930bd5d7ced45ae97fa8b5f71cc21d87b4c60905c125d34",
+ ),
+ ];
+
+ for (input, output) in VECTORS {
+ let input_bytes = hex::decode(input).unwrap();
+ let input_bytes = input_bytes.first_chunk::<32>().unwrap();
+ let point = Point::map_to_curve_elligator2(input_bytes);
+ assert_eq!(hex::encode(point.to_bytes()), *output);
+ }
+ }
+}
diff --git a/core/embed/crypto/src/ed25519.rs b/core/embed/crypto/src/ed25519.rs
new file mode 100644
index 00000000..e071476b
--- /dev/null
+++ b/core/embed/crypto/src/ed25519.rs
@@ -0,0 +1,23 @@
+use super::{Error, ffi};
+
+pub type Signature = ffi::ed25519_signature;
+pub const SIGNATURE_SIZE: usize = core::mem::size_of::<Signature>();
+
+pub type PublicKey = ffi::ed25519_public_key;
+pub const PUBLIC_KEY_SIZE: usize = core::mem::size_of::<PublicKey>();
+
+pub fn verify(message: &[u8], public_key: &PublicKey, signature: &Signature) -> Result<(), Error> {
+ let res = unsafe {
+ ffi::ed25519_sign_open(
+ message.as_ptr(),
+ message.len(),
+ public_key.as_ptr(),
+ signature.as_ptr(),
+ )
+ };
+ if res == 0 {
+ Ok(())
+ } else {
+ Err(Error::SignatureVerificationFailed)
+ }
+}
diff --git a/core/embed/crypto/src/ffi.rs b/core/embed/crypto/src/ffi.rs
new file mode 100644
index 00000000..2a5d942e
--- /dev/null
+++ b/core/embed/crypto/src/ffi.rs
@@ -0,0 +1,4 @@
+#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+include!(concat!(env!("OUT_DIR"), "/crypto.rs"));
diff --git a/core/embed/crypto/src/hmac.rs b/core/embed/crypto/src/hmac.rs
new file mode 100644
index 00000000..009bb8cb
--- /dev/null
+++ b/core/embed/crypto/src/hmac.rs
@@ -0,0 +1,149 @@
+use core::pin::Pin;
+
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+use super::ffi;
+use super::memory::{Memory, init_ctx};
+
+pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
+pub type Digest = [u8; DIGEST_SIZE];
+
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct HmacSha256<'a> {
+ ctx: Pin<&'a mut Memory<ffi::HMAC_SHA256_CTX>>,
+}
+
+impl<'a> HmacSha256<'a> {
+ pub fn new(mut ctx: Pin<&'a mut Memory<ffi::HMAC_SHA256_CTX>>, key: &[u8]) -> Self {
+ // initialize the context
+ // SAFETY: ffi
+ unsafe { ffi::hmac_sha256_Init(ctx.inner(), key.as_ptr(), key.len() as u32) };
+ Self { ctx }
+ }
+
+ pub fn update(&mut self, data: &[u8]) {
+ // SAFETY: ffi
+ unsafe { ffi::hmac_sha256_Update(self.ctx.inner(), data.as_ptr(), data.len() as u32) };
+ }
+
+ pub fn memory() -> Memory<ffi::HMAC_SHA256_CTX> {
+ Memory::default()
+ }
+
+ pub fn finalize_into(mut self, out: &mut Digest) {
+ // SAFETY: ffi
+ unsafe { ffi::hmac_sha256_Final(self.ctx.inner(), out.as_mut_ptr()) };
+ }
+}
+
+pub fn digest_into(key: &[u8], data: &[u8], out: &mut Digest) {
+ init_ctx!(HmacSha256, ctx, key);
+ ctx.update(data);
+ ctx.finalize_into(out);
+}
+
+pub fn digest(key: &[u8], data: &[u8]) -> Digest {
+ let mut out = [0u8; DIGEST_SIZE];
+ digest_into(key, data, &mut out);
+ out
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const HMAC_SHA256_EMPTY: &str =
+ "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad";
+ // RFC 4231
+ const HMAC_SHA256_VECTORS: &[(&[u8], &[u8], &str)] = &[
+ (
+ &[0x0b; 20],
+ b"Hi There",
+ "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
+ ),
+ (
+ b"Jefe",
+ b"what do ya want for nothing?",
+ "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
+ ),
+
+ (
+ &[0xaa; 20],
+ &[0xdd; 50],
+ "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
+ ),
+ (
+ &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19],
+ &[0xcd; 50],
+ "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
+ ),
+ // skipping case with truncation
+ (
+ &[0xaa; 131],
+ b"Test Using Larger Than Block-Size Key - Hash Key First",
+ "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
+ ),
+ (
+ &[0xaa; 131],
+ b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.",
+ "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
+ ),
+ (
+ b"",
+ b"",
+ HMAC_SHA256_EMPTY,
+ ),
+ ];
+
+ fn hexdigest(key: &[u8], data: &[u8]) -> String {
+ hex::encode(digest(key, data))
+ }
+
+ #[test]
+ fn test_empty_ctx() {
+ let mut out = [0u8; DIGEST_SIZE];
+
+ init_ctx!(HmacSha256, ctx, b"");
+ ctx.finalize_into(&mut out);
+ let out_hex = hex::encode(out);
+
+ assert_eq!(out_hex, HMAC_SHA256_EMPTY);
+ }
+
+ #[test]
+ fn test_vectors() {
+ for (key, data, expected) in HMAC_SHA256_VECTORS {
+ let out_hex = hexdigest(key, data);
+ assert_eq!(out_hex, *expected);
+ }
+ }
+
+ #[test]
+ fn test_update() {
+ // case 3
+ let key =
+ b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa";
+ init_ctx!(HmacSha256, ctx, key);
+ for _ in 0..50 {
+ ctx.update(b"\xdd");
+ }
+ let mut out = [0u8; DIGEST_SIZE];
+ ctx.finalize_into(&mut out);
+ assert_eq!(
+ hex::encode(out),
+ "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
+ );
+
+ // case 4
+ let key = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19";
+ init_ctx!(HmacSha256, ctx, key);
+ for _ in 0..50 {
+ ctx.update(b"\xcd");
+ }
+ ctx.finalize_into(&mut out);
+ assert_eq!(
+ hex::encode(out),
+ "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
+ );
+ }
+}
diff --git a/core/embed/crypto/src/lib.rs b/core/embed/crypto/src/lib.rs
new file mode 100644
index 00000000..4760f4c0
--- /dev/null
+++ b/core/embed/crypto/src/lib.rs
@@ -0,0 +1,59 @@
+#![cfg_attr(not(test), no_std)]
+
+use core::hint::black_box;
+
+#[cfg(feature = "aes_gcm")]
+pub mod aesgcm;
+pub mod cosi;
+pub mod crc32;
+pub mod curve25519;
+pub mod ed25519;
+mod ffi;
+pub mod hmac;
+pub mod memory;
+pub mod merkle;
+pub mod sha256;
+pub mod sha512;
+
+#[cfg_attr(feature = "test", derive(core::fmt::Debug))]
+pub enum Error {
+ // Signature verification failed
+ SignatureVerificationFailed,
+ // Provided value is not a valid public key / signature / etc.
+ InvalidEncoding,
+ // Provided parameters are not accepted (e.g., signature threshold out of bounds)
+ InvalidParams,
+ // State precondition check failed (possibly raised by C implementation)
+ InvalidContext,
+ // Authentication failed (e.g. AEAD tag mismatch)
+ AuthenticationFailed,
+ // Invalid sigmask
+ InvalidSigmask,
+}
+
+/// Constant time bytestring comparison for two arrays of the same length.
+fn consteq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
+ let mut diff: u8 = 0;
+ for i in 0..N {
+ diff |= a[i] ^ b[i];
+ }
+ black_box(black_box(diff) == 0)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_consteq() {
+ assert!(consteq(&[], &[]));
+ assert!(consteq(&[0u8; 256], &[0u8; 256]));
+ assert!(consteq(&[0xffu8; 256], &[0xffu8; 256]));
+ assert!(consteq(b"0123456789abcdef", b"0123456789abcdef"));
+
+ assert!(!consteq(&[0u8; 256], &[0xffu8; 256]));
+ assert!(!consteq(&[0xffu8; 256], &[0u8; 256]));
+ assert!(!consteq(b"0123456789abcdef", b"123456789abcdef0"));
+ assert!(!consteq(b"0000000000000000", b"0000000000000001"));
+ }
+}
diff --git a/core/embed/crypto/src/memory.rs b/core/embed/crypto/src/memory.rs
new file mode 100644
index 00000000..2919be88
--- /dev/null
+++ b/core/embed/crypto/src/memory.rs
@@ -0,0 +1,81 @@
+use core::marker::PhantomPinned;
+use core::mem::MaybeUninit;
+use core::pin::Pin;
+
+use zeroize::{Zeroize, zeroize_flat_type};
+
+/// Wrapper for a memory used as a context by C functions. Its purpose is to be
+/// !Unpin, thus prevent moves when accessed through a Pin. We want to avoid
+/// moves as they can leave cryptographic data in memory.
+///
+/// T needs to be a plain struct that is valid when zeroed.
+pub struct Memory<T> {
+ inner: T,
+ _phantom: PhantomPinned,
+}
+
+impl<T> Default for Memory<T> {
+ fn default() -> Self {
+ // SAFETY: a zeroed block of memory is valid for C functions
+ let inner = unsafe { MaybeUninit::<T>::zeroed().assume_init() };
+ Self {
+ inner,
+ _phantom: PhantomPinned,
+ }
+ }
+}
+
+impl<T> Zeroize for Memory<T> {
+ fn zeroize(&mut self) {
+ // SAFETY:
+ // - contains no references
+ // - plain struct with not Drop impls
+ // - only called in Drop impl
+ // - zeroed block of memory is valid
+ unsafe { zeroize_flat_type(&mut self.inner as *mut T) };
+ }
+}
+
+impl<T> Memory<T> {
+ // SAFETY:
+ // The caller must ensure that the return value is handled according to the
+ // contract of `Pin::map_unchecked_mut` and `Pin::get_unchecked_mut`.
+ // Notably passing the pointer to a C function should be fine since the notion
+ // of moving doesn't exist there and the entire point of this pinning is not
+ // to leak more data than the C implementation.
+ pub unsafe fn inner(self: &mut Pin<&mut Self>) -> *mut T {
+ unsafe {
+ self.as_mut()
+ .map_unchecked_mut(|m| &mut m.inner)
+ .get_unchecked_mut()
+ }
+ }
+}
+
+impl<T> Zeroize for Pin<&mut Memory<T>> {
+ fn zeroize(&mut self) {
+ // SAFETY: `Memory::zeroize` does not do any moving
+ unsafe {
+ self.as_mut().get_unchecked_mut().zeroize();
+ }
+ }
+}
+
+/// Initializes backing memory on the stack and passes it to a constructor.
+/// The macro is basically a specialized version of `core::pin::pin!` for use
+/// with Memory<T>.
+#[macro_export]
+macro_rules! init_ctx {
+ ($type:ty, $name:ident $(, $arg:expr)*) => {
+ // assign the backing memory to $name...
+ let mut $name = <$type>::memory();
+ // ... then make it inaccessible by overwriting the binding, and pin it
+ // SAFETY: The value is pinned: it is the local above which cannot be named outside this macro.
+ #[allow(unused_mut)]
+ let mut $name = unsafe {
+ <$type>::new(core::pin::Pin::new_unchecked(&mut $name), $($arg),*)
+ };
+ };
+}
+
+pub use init_ctx;
diff --git a/core/embed/crypto/src/merkle.rs b/core/embed/crypto/src/merkle.rs
new file mode 100644
index 00000000..5cb9e8bc
--- /dev/null
+++ b/core/embed/crypto/src/merkle.rs
@@ -0,0 +1,31 @@
+use super::memory::init_ctx;
+use super::sha256;
+
+/// Calculate a Merkle root based on a leaf element and a proof of inclusion.
+///
+/// Expects the Merkle tree format specified in `external-definitions.md`.
+pub fn merkle_root(elem: &[u8], proof: &[sha256::Digest]) -> sha256::Digest {
+ let mut out = sha256::Digest::default();
+
+ // hash the leaf element
+ init_ctx!(sha256::Sha256, ctx);
+ ctx.update(&[0x00]);
+ ctx.update(elem);
+ ctx.finalize_into(&mut out);
+
+ for proof_elem in proof {
+ // hash together the current hash and the proof element
+ let (min, max) = if &out < proof_elem {
+ (&out, proof_elem)
+ } else {
+ (proof_elem, &out)
+ };
+ init_ctx!(sha256::Sha256, ctx);
+ ctx.update(&[0x01]);
+ ctx.update(min);
+ ctx.update(max);
+ ctx.finalize_into(&mut out);
+ }
+
+ out
+}
diff --git a/core/embed/crypto/src/sha256.rs b/core/embed/crypto/src/sha256.rs
new file mode 100644
index 00000000..1d6c2d76
--- /dev/null
+++ b/core/embed/crypto/src/sha256.rs
@@ -0,0 +1,123 @@
+use core::mem::MaybeUninit;
+use core::pin::Pin;
+
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+use super::ffi;
+use super::memory::{Memory, init_ctx};
+
+pub const BLOCK_SIZE: usize = ffi::SHA256_BLOCK_LENGTH as usize;
+pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
+pub type Digest = [u8; DIGEST_SIZE];
+
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct Sha256<'a> {
+ ctx: Pin<&'a mut Memory<ffi::SHA256_CTX>>,
+}
+
+impl<'a> Sha256<'a> {
+ pub fn new(mut ctx: Pin<&'a mut Memory<ffi::SHA256_CTX>>) -> Self {
+ // initialize the context
+ // SAFETY: safe with whatever finds itself as memory contents
+ unsafe { ffi::sha256_Init(ctx.inner()) };
+ Self { ctx }
+ }
+
+ pub fn update(&mut self, data: &[u8]) {
+ // SAFETY: safe
+ unsafe { ffi::sha256_Update(self.ctx.inner(), data.as_ptr(), data.len()) };
+ }
+
+ pub fn memory() -> Memory<ffi::SHA256_CTX> {
+ Memory::default()
+ }
+
+ pub fn finalize_into(mut self, out: &mut Digest) {
+ // SAFETY: safe
+ unsafe { ffi::sha256_Final(self.ctx.inner(), out.as_mut_ptr()) };
+ }
+}
+
+pub fn digest_into(data: &[u8], out: &mut Digest) {
+ init_ctx!(Sha256, ctx);
+ ctx.update(data);
+ ctx.finalize_into(out);
+}
+
+pub fn digest(data: &[u8]) -> Digest {
+ let mut out = Digest::default();
+ digest_into(data, &mut out);
+ out
+}
+
+// Unpinned variant for use with noise-protocol which does not guarantee
+// pinning. If possible please use [`Sha256`] above.
+#[derive(Clone)]
+pub struct NoPinSha256 {
+ ctx: ffi::SHA256_CTX,
+}
+
+impl Drop for NoPinSha256 {
+ fn drop(&mut self) {
+ // C implementation zeroes the state
+ // SAFETY: ffi
+ unsafe { ffi::sha256_Final(&mut self.ctx as *mut _, core::ptr::null_mut()) };
+ }
+}
+
+impl Default for NoPinSha256 {
+ fn default() -> Self {
+ let mut ctx = unsafe { MaybeUninit::<ffi::SHA256_CTX>::zeroed().assume_init() };
+ unsafe { ffi::sha256_Init(&mut ctx) };
+ Self { ctx }
+ }
+}
+
+impl NoPinSha256 {
+ pub fn update(&mut self, data: &[u8]) {
+ // SAFETY: ffi
+ unsafe { ffi::sha256_Update(&mut self.ctx as *mut _, data.as_ptr(), data.len()) };
+ }
+
+ pub fn finalize_into(mut self, out: &mut Digest) {
+ // SAFETY: ffi
+ unsafe { ffi::sha256_Final(&mut self.ctx as *mut _, out.as_mut_ptr()) };
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const SHA256_EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ const SHA256_VECTORS: &[(&[u8], &str)] = &[
+ (b"", SHA256_EMPTY),
+ (
+ b"abc",
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+ ),
+ ];
+
+ fn hexdigest(data: &[u8]) -> String {
+ hex::encode(digest(data))
+ }
+
+ #[test]
+ fn test_empty_ctx() {
+ let mut out = Digest::default();
+
+ init_ctx!(Sha256, ctx);
+ ctx.finalize_into(&mut out);
+
+ let out_hex = hex::encode(out);
+ assert_eq!(out_hex, SHA256_EMPTY.to_string());
+ }
+
+ #[test]
+ fn test_vectors() {
+ for (data, expected) in SHA256_VECTORS {
+ let out_hex = hexdigest(data);
+ assert_eq!(out_hex, *expected);
+ }
+ }
+}
diff --git a/core/embed/crypto/src/sha512.rs b/core/embed/crypto/src/sha512.rs
new file mode 100644
index 00000000..44b80ca2
--- /dev/null
+++ b/core/embed/crypto/src/sha512.rs
@@ -0,0 +1,96 @@
+use core::pin::Pin;
+
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+use super::ffi;
+use super::memory::{Memory, init_ctx};
+
+pub const BLOCK_SIZE: usize = ffi::SHA512_BLOCK_LENGTH as usize;
+pub const DIGEST_SIZE: usize = ffi::SHA512_DIGEST_LENGTH as usize;
+pub type Digest = [u8; DIGEST_SIZE];
+
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct Sha512<'a> {
+ ctx: Pin<&'a mut Memory<ffi::SHA512_CTX>>,
+}
+
+impl<'a> Sha512<'a> {
+ pub fn new(ctx: Pin<&'a mut Memory<ffi::SHA512_CTX>>) -> Self {
+ // initialize the context
+ let mut res = Self { ctx };
+ // SAFETY: safe with whatever finds itself as memory contents
+ unsafe { ffi::sha512_Init(res.ctx.inner()) };
+ res
+ }
+
+ pub fn update(&mut self, data: &[u8]) {
+ // SAFETY: ffi
+ unsafe { ffi::sha512_Update(self.ctx.inner(), data.as_ptr(), data.len()) };
+ }
+
+ pub fn memory() -> Memory<ffi::SHA512_CTX> {
+ Memory::default()
+ }
+
+ pub fn finalize_into(mut self, out: &mut Digest) {
+ // SAFETY: ffi
+ unsafe { ffi::sha512_Final(self.ctx.inner(), out.as_mut_ptr()) };
+ }
+}
+
+pub fn digest_into(data: &[u8], out: &mut Digest) {
+ init_ctx!(Sha512, ctx);
+ ctx.update(data);
+ ctx.finalize_into(out);
+}
+
+pub fn digest(data: &[u8]) -> Digest {
+ let mut out = [0u8; DIGEST_SIZE];
+ digest_into(data, &mut out);
+ out
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const SHA512_EMPTY: &str = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+ const SHA512_VECTORS: &[(&[u8], &str)] = &[
+ (b"", SHA512_EMPTY),
+ (
+ b"abc",
+ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
+ ),
+ (
+ b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445",
+ ),
+ (
+ b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909",
+ ),
+ ];
+
+ fn hexdigest(data: &[u8]) -> String {
+ hex::encode(digest(data))
+ }
+
+ #[test]
+ fn test_empty_ctx() {
+ let mut out = [0u8; DIGEST_SIZE];
+
+ init_ctx!(Sha512, ctx);
+ ctx.finalize_into(&mut out);
+
+ let out_hex = hex::encode(out);
+ assert_eq!(out_hex, SHA512_EMPTY);
+ }
+
+ #[test]
+ fn test_vectors() {
+ for (data, expected) in SHA512_VECTORS {
+ let out_hex = hexdigest(data);
+ assert_eq!(out_hex, *expected);
+ }
+ }
+}
diff --git a/core/embed/crypto/src/test_setup.c b/core/embed/crypto/src/test_setup.c
new file mode 100644
index 00000000..bfdb8285
--- /dev/null
+++ b/core/embed/crypto/src/test_setup.c
@@ -0,0 +1,49 @@
+/*
+ * 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/>.
+ */
+
+// Provide definitions of the system exit functions so that they can be
+// called without linking the sys crate. This is needed when compiling the
+// tests for the crates that don't depend on sys, such as the crypto crate.
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void system_exit_error(const char *title, const char *message,
+ const char *footer) {
+ printf("Error: %s\n", message);
+ if (title != NULL && *title != '\0') {
+ printf("Title: %s\n", title);
+ }
+
+ if (footer != NULL && *footer != '\0') {
+ printf("Footer: %s\n", footer);
+ }
+
+ exit(1);
+}
+
+void system_exit_fatal(const char *message, const char *file, int line) {
+ printf("Fatal error: %s", message);
+ if (file != NULL && *file != '\0') {
+ printf(" at %s:%d", file, line);
+ }
+ printf("\n");
+
+ exit(1);
+}
diff --git a/core/embed/projects/boardloader/Cargo.toml b/core/embed/projects/boardloader/Cargo.toml
index e5733a0d..2410f370 100644
--- a/core/embed/projects/boardloader/Cargo.toml
+++ b/core/embed/projects/boardloader/Cargo.toml
@@ -19,6 +19,7 @@ xbuild.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -44,7 +45,7 @@ default = [
"io/kernel_mode",
"io/secure_mode",
"models/boardloader",
- "rtl/ed25519_no_precomp",
+ "crypto/ed25519_no_precomp",
]
mcu_stm32f427 = ["io/mcu_stm32f427", "mcu_stm32f4"]
diff --git a/core/embed/projects/bootloader/Cargo.toml b/core/embed/projects/bootloader/Cargo.toml
index 50e40088..2da57e79 100644
--- a/core/embed/projects/bootloader/Cargo.toml
+++ b/core/embed/projects/bootloader/Cargo.toml
@@ -13,6 +13,7 @@ trezor_lib.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -47,7 +48,7 @@ default = [
"io/secure_mode",
"io/usb_iface_wire",
"models/bootloader",
- "rtl/ed25519_no_precomp",
+ "crypto/ed25519_no_precomp",
"trezor_lib/with_new_crates", # only for transitional period
diff --git a/core/embed/projects/bootloader_ci/Cargo.toml b/core/embed/projects/bootloader_ci/Cargo.toml
index 3271edbd..3236ad02 100644
--- a/core/embed/projects/bootloader_ci/Cargo.toml
+++ b/core/embed/projects/bootloader_ci/Cargo.toml
@@ -18,6 +18,7 @@ xbuild.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -45,7 +46,7 @@ default = [
"io/secure_mode",
"io/usb_iface_wire",
"models/bootloader",
- "rtl/ed25519_no_precomp",
+ "crypto/ed25519_no_precomp",
]
mcu_stm32f427 = ["io/mcu_stm32f427", "mcu_stm32f4"]
diff --git a/core/embed/projects/firmware/Cargo.toml b/core/embed/projects/firmware/Cargo.toml
index 98aa201b..875fa986 100644
--- a/core/embed/projects/firmware/Cargo.toml
+++ b/core/embed/projects/firmware/Cargo.toml
@@ -13,6 +13,7 @@ trezor_lib.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
upymod.workspace = true
@@ -25,7 +26,7 @@ upymod.workspace = true
block_on_vcp = ["sys/block_on_vcp"]
bootloader_devel = ["sec/bootloader_devel"]
-benchmark = ["upymod/benchmark", "rtl/aes_gcm"]
+benchmark = ["upymod/benchmark", "crypto/aes_gcm"]
clippy = ["trezor_lib/clippy"]
dbg_console = ["sys/dbg_console", "trezor_lib/dbg_console"]
debug = ["trezor_lib/debug"]
@@ -56,7 +57,7 @@ default = [
"io/display",
"io/fancy_fatal_error",
"io/usb_iface_wire",
- "rtl/secp256k1_zkp",
+ "crypto/secp256k1_zkp",
"trezor_lib/with_new_crates", # only for transitional period
"trezor_lib/with_upymod", # only for transitional period
@@ -110,7 +111,7 @@ secret = ["sec/secret"]
secure_aes = ["sec/secure_aes"]
serial_number = ["upymod/serial_number", "trezor_lib/serial_number"]
suspend = ["io/suspend"]
-thp = ["upymod/thp", "rtl/aes_gcm", "trezor_lib/thp"]
+thp = ["upymod/thp", "crypto/aes_gcm", "trezor_lib/thp"]
tamper = ["sec/tamper"]
telemetry = ["sec/telemetry", "upymod/telemetry", "trezor_lib/telemetry"]
touch = ["io/touch", "upymod/touch", "trezor_lib/touch"]
@@ -119,8 +120,8 @@ tropic = ["sec/tropic", "upymod/tropic", "trezor_lib/tropic"]
# Legacy coin support (T2T1 only)
decred = ["upymod/decred"]
-eos = ["rtl/eos", "upymod/eos"]
-nem = ["rtl/nem", "upymod/nem"]
+eos = ["crypto/eos", "upymod/eos"]
+nem = ["crypto/nem", "upymod/nem"]
display_mono = ["trezor_lib/display_mono"]
display_rgb565 = ["trezor_lib/display_rgb565"]
diff --git a/core/embed/projects/kernel/Cargo.toml b/core/embed/projects/kernel/Cargo.toml
index 5f197d9d..213badc6 100644
--- a/core/embed/projects/kernel/Cargo.toml
+++ b/core/embed/projects/kernel/Cargo.toml
@@ -12,6 +12,7 @@ xbuild.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
trezor_lib.workspace = true
@@ -50,7 +51,7 @@ default = [
"sec/rdi",
"sec/storage",
"models/kernel",
- "rtl/aes_gcm",
+ "crypto/aes_gcm",
"trezor_lib/with_new_crates", # only for transitional period
]
diff --git a/core/embed/projects/prodtest/Cargo.toml b/core/embed/projects/prodtest/Cargo.toml
index d3e532e0..02a1744e 100644
--- a/core/embed/projects/prodtest/Cargo.toml
+++ b/core/embed/projects/prodtest/Cargo.toml
@@ -12,6 +12,7 @@ trezor_lib.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -45,9 +46,9 @@ default = [
"io/usb_iface_vcp",
"models/prodtest",
"rtl/sprintf",
- "rtl/mldsa",
- "rtl/aes_gcm",
- "rtl/noise",
+ "crypto/aes_gcm",
+ "crypto/mldsa",
+ "crypto/noise",
"trezor_lib/with_new_crates", # only for transitional period
diff --git a/core/embed/projects/secmon/Cargo.toml b/core/embed/projects/secmon/Cargo.toml
index 447e806e..35264f9a 100644
--- a/core/embed/projects/secmon/Cargo.toml
+++ b/core/embed/projects/secmon/Cargo.toml
@@ -17,6 +17,7 @@ xbuild.workspace = true
[dependencies]
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -49,7 +50,7 @@ default = [
"sec/secure_mode",
"sec/storage",
"sec/rdi",
- "rtl/aes_gcm",
+ "crypto/aes_gcm",
]
mcu_stm32u58 = ["sec/mcu_stm32u58", "mcu_stm32u5"]
diff --git a/core/embed/projects/unix/Cargo.toml b/core/embed/projects/unix/Cargo.toml
index 0119d161..2bc0df1f 100644
--- a/core/embed/projects/unix/Cargo.toml
+++ b/core/embed/projects/unix/Cargo.toml
@@ -13,6 +13,7 @@ trezor_lib.workspace = true
io.workspace = true
sec.workspace = true
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
upymod.workspace = true
@@ -24,7 +25,7 @@ upymod.workspace = true
# --------------------------------------------------------------------------
asan = ["models/asan"]
-benchmark = ["upymod/benchmark", "rtl/aes_gcm"]
+benchmark = ["upymod/benchmark", "crypto/aes_gcm"]
clippy = ["trezor_lib/clippy"]
dbg_console = ["sys/dbg_console", "trezor_lib/dbg_console"]
debug = ["trezor_lib/debug"]
@@ -56,8 +57,8 @@ default = [
"io/secure_mode",
"io/usb_iface_wire",
"sec/rdi",
- "rtl/secp256k1_zkp",
- "rtl/aes_gcm",
+ "crypto/aes_gcm",
+ "crypto/secp256k1_zkp",
"trezor_lib/with_new_crates", # only for transitional period
"trezor_lib/with_upymod", # only for transitional period
@@ -109,15 +110,15 @@ serial_number = ["upymod/serial_number", "trezor_lib/serial_number"]
suspend = ["io/suspend"]
tamper = ["sec/tamper"]
telemetry = ["sec/telemetry", "upymod/telemetry", "trezor_lib/telemetry"]
-thp = ["upymod/thp", "rtl/aes_gcm", "trezor_lib/thp"]
+thp = ["upymod/thp", "crypto/aes_gcm", "trezor_lib/thp"]
touch = ["io/touch", "upymod/touch", "trezor_lib/touch"]
touch_wakeup = ["io/touch_wakeup", "upymod/touch_wakeup", "trezor_lib/touch_wakeup"]
tropic = ["sec/tropic", "upymod/tropic", "trezor_lib/tropic"]
# Legacy coin support
decred = ["upymod/decred"]
-eos = ["rtl/eos", "upymod/eos"]
-nem = ["rtl/nem", "upymod/nem"]
+eos = ["crypto/eos", "upymod/eos"]
+nem = ["crypto/nem", "upymod/nem"]
display_mono = ["trezor_lib/display_mono"]
display_rgb565 = ["trezor_lib/display_rgb565"]
diff --git a/core/embed/rtl/Cargo.toml b/core/embed/rtl/Cargo.toml
index 81ee9a82..e267e89b 100644
--- a/core/embed/rtl/Cargo.toml
+++ b/core/embed/rtl/Cargo.toml
@@ -11,24 +11,14 @@ xbuild.workspace = true
[dependencies]
models.workspace = true
-
[features]
# --------------------------------------------------------------------------
# Selectable features
# --------------------------------------------------------------------------
-aes_gcm = []
-ed25519_no_precomp = []
-emulator = ["insecure_prng", "models/emulator"]
-eos = []
-insecure_prng = []
-mldsa = []
-nem = []
-noise = ["aes_gcm"]
+emulator = ["models/emulator"]
production = ["models/production"]
-secp256k1_zkp = []
-sphincsplus = []
sprintf = []
universal_fw = []
diff --git a/core/embed/rtl/build.rs b/core/embed/rtl/build.rs
index a7095d3a..08c5e5c6 100644
--- a/core/embed/rtl/build.rs
+++ b/core/embed/rtl/build.rs
@@ -1,6 +1,6 @@
use std::process::Command;
-use xbuild::{Result, WrapErr, bail, ensure};
+use xbuild::{Result, WrapErr, ensure};
fn main() -> Result<()> {
xbuild::build(|lib| {
@@ -22,8 +22,6 @@ fn main() -> Result<()> {
lib.add_source("printf.c");
}
- add_crypto(lib)?;
-
add_uzlib(lib);
if cfg!(feature = "test") {
@@ -70,249 +68,6 @@ fn get_scm_revision_short() -> Result<String> {
Ok(format!("{{{}}}", init_val))
}
-fn add_crypto(lib: &mut xbuild::CLibrary) -> Result<()> {
- let crypto_path = "../../vendor/trezor-crypto";
-
- lib.add_include(crypto_path);
-
- lib.add_defines([
- ("AES_128", None),
- ("AES_192", None),
- ("USE_KECCAK", Some("1")),
- ("USE_BIP32_CACHE", Some("0")),
- ]);
-
- let val = cfg!(feature = "universal_fw");
- let val = Some(if val { "1" } else { "0" });
- lib.add_defines([
- ("USE_ETHEREUM", val),
- ("USE_MONERO", val),
- ("USE_CARDANO", val),
- ]);
-
- let nem = cfg!(feature = "universal_fw") && cfg!(feature = "nem");
- lib.add_define("USE_NEM", Some(if nem { "1" } else { "0" }));
- let eos = cfg!(feature = "universal_fw") && cfg!(feature = "eos");
- lib.add_define("USE_EOS", Some(if eos { "1" } else { "0" }));
-
- if cfg!(feature = "ed25519_no_precomp") {
- lib.add_define("ED25519_NO_PRECOMP", None);
- }
-
- let crypto_attrs = xbuild::CompileAttrs::new()
- .with_flag("-ftrivial-auto-var-init=zero")
- .with_flag("-ffreestanding");
-
- lib.add_sources_in_dir_with_attrs(
- crypto_path,
- [
- "address.c",
- "aes/aes_modes.c",
- "aes/aesccm.c",
- "aes/aescrypt.c",
- "aes/aeskey.c",
- "aes/aestab.c",
- "base32.c",
- "base58.c",
- "bignum.c",
- "bip32.c",
- "bip39.c",
- "bip39_english.c",
- "blake256.c",
- "blake2b.c",
- "blake2s.c",
- "buffer.c",
- "chacha20poly1305/chacha20poly1305.c",
- "chacha20poly1305/chacha_merged.c",
- "chacha20poly1305/poly1305-donna.c",
- "chacha20poly1305/rfc7539.c",
- "chacha_drbg.c",
- "consteq.c",
- "curves.c",
- "der.c",
- "ecdsa.c",
- "ed25519-donna/curve25519-donna-32bit.c",
- "ed25519-donna/curve25519-donna-helpers.c",
- "ed25519-donna/curve25519-donna-scalarmult-base.c",
- "ed25519-donna/ed25519-donna-32bit-tables.c",
- "ed25519-donna/ed25519-donna-basepoint-table.c",
- "ed25519-donna/ed25519-donna-impl-base.c",
- "ed25519-donna/ed25519-keccak.c",
- "ed25519-donna/ed25519-sha3.c",
- "ed25519-donna/ed25519.c",
- "ed25519-donna/modm-donna-32bit.c",
- "elligator2.c",
- "groestl.c",
- "hash_to_curve.c",
- "hasher.c",
- "hmac.c",
- "hmac_drbg.c",
- "memzero.c",
- "nem.c",
- "nist256p1.c",
- "pbkdf2.c",
- "rand.c",
- "rfc6979.c",
- "ripemd160.c",
- "secp256k1.c",
- "segwit_addr.c",
- "sha2.c",
- "sha3.c",
- "shamir.c",
- "slip39.c",
- "slip39_english.c",
- "tls_prf.c",
- ],
- Some(crypto_attrs.clone()),
- );
-
- if cfg!(feature = "emulator") {
- lib.add_sources_in_dir_with_attrs(
- crypto_path,
- ["rand_insecure.c"],
- Some(crypto_attrs.clone()),
- );
- }
-
- if cfg!(feature = "universal_fw") {
- lib.add_sources_in_dir_with_attrs(
- crypto_path,
- [
- "cardano.c",
- "monero/base58.c",
- "monero/serialize.c",
- "monero/xmr.c",
- ],
- Some(crypto_attrs.clone()),
- );
- }
-
- if cfg!(feature = "insecure_prng") {
- if cfg!(feature = "production") && !xbuild::is_rust_analyzer() {
- bail!("insecure_prng cannot be enabled in production builds");
- }
- lib.add_define("USE_INSECURE_PRNG", Some("1"));
- }
-
- if cfg!(feature = "aes_gcm") {
- lib.add_defines([("AES_VAR", None), ("USE_AES_GCM", Some("1"))]);
-
- lib.add_sources_in_dir_with_attrs(
- crypto_path,
- ["aes/gf128mul.c", "aes/aesgcm.c"],
- Some(crypto_attrs.clone()),
- );
- }
-
- if cfg!(feature = "noise") {
- lib.add_sources_in_dir_with_attrs(crypto_path, ["noise_kk1.c"], Some(crypto_attrs.clone()));
- }
-
- if cfg!(feature = "secp256k1_zkp") {
- let secp256k1_path = "../../vendor/secp256k1-zkp";
-
- lib.add_defines([
- ("USE_SECP256K1_ZKP", None),
- ("USE_SECP256K1_ZKP_ECDSA", None),
- ("USE_EXTERNAL_DEFAULT_CALLBACKS", None),
- ("ECMULT_GEN_PREC_BITS", Some("2")),
- ("ECMULT_WINDOW_SIZE", Some("2")),
- ("ENABLE_MODULE_GENERATOR", None),
- ("ENABLE_MODULE_RECOVERY", None),
- ("ENABLE_MODULE_SCHNORRSIG", None),
- ("ENABLE_MODULE_EXTRAKEYS", None),
- ("ENABLE_MODULE_ECDH", None),
- ]);
-
- // TODO get rid of #include <vendor/... includes in crypto/zkp_*.c
- // lib.add_include(PathBuf::from(secp256k1_path).join("include"));
- lib.add_include("../.."); // points to vendor folder
-
- if cfg!(feature = "emulator") {
- lib.add_define("SECP256K1_CONTEXT_SIZE", Some("208"));
- } else {
- lib.add_define("SECP256K1_CONTEXT_SIZE", Some("180"));
- lib.add_define("USE_EXTERNAL_ASM", None);
- lib.add_sources_in_dir(secp256k1_path, ["src/asm/field_10x26_arm.s"]);
- }
-
- let secp256k1_attrs = xbuild::CompileAttrs::new()
- .with_flag("-ffreestanding")
- .with_flag("-Wno-unused-function");
-
- lib.add_sources_in_dir_with_attrs(
- secp256k1_path,
- [
- "src/secp256k1.c",
- "src/precomputed_ecmult.c",
- "src/precomputed_ecmult_gen.c",
- ],
- Some(secp256k1_attrs.clone()),
- );
-
- lib.add_sources_in_dir_with_attrs(
- crypto_path,
- ["zkp_context.c", "zkp_ecdsa.c", "zkp_bip340.c"],
- Some(secp256k1_attrs.clone()),
- );
- }
-
- if cfg!(feature = "sphincsplus") {
- let sphincsplus_path = "../../vendor/sphincsplus/ref";
-
- lib.add_include(sphincsplus_path);
-
- lib.add_define("PARAMS", Some("sphincs-sha2-128s"));
-
- let attrs = xbuild::CompileAttrs::new()
- .with_flag("-Wno-incompatible-pointer-types")
- .with_flag("-ftrivial-auto-var-init=zero");
-
- lib.add_sources_in_dir_with_attrs(
- sphincsplus_path,
- [
- "address.c",
- "fors.c",
- "hash_sha2.c",
- "sha2.c",
- "sign.c",
- "thash_sha2_simple.c",
- "utils.c",
- "wots.c",
- ],
- Some(attrs),
- );
- }
-
- if cfg!(feature = "mldsa") {
- let mldsa_path = "../../vendor/mldsa-native/mldsa";
-
- lib.add_include(mldsa_path);
-
- lib.add_defines([
- ("MLD_CONFIG_NAMESPACE_PREFIX", Some("mldsa")),
- ("MLD_CONFIG_NO_RANDOMIZED_API", Some("1")),
- ]);
-
- lib.add_sources_in_dir_with_attrs(
- mldsa_path,
- [
- "src/fips202/fips202.c",
- "src/fips202/fips202x4.c",
- "src/fips202/keccakf1600.c",
- "src/packing.c",
- "src/poly.c",
- "src/poly_kl.c",
- "src/polyvec.c",
- "src/sign.c",
- ],
- Some(crypto_attrs.clone()),
- );
- }
-
- Ok(())
-}
-
fn add_uzlib(lib: &mut xbuild::CLibrary) {
let uzlib_path = "../../vendor/micropython/lib/uzlib";
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index aa46ad64..fddfb353 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -7,7 +7,7 @@ build = "build.rs"
[features]
default = ["layout_bolt"]
-crypto = ["dep:zeroize"]
+crypto = ["dep:crypto"]
layout_bolt = []
layout_caesar = []
layout_delizia = []
@@ -63,7 +63,7 @@ translations = ["crypto"]
secmon_layout = []
dbg_console = []
app_loading = []
-thp = ["crypto", "dep:trezor-thp", "dep:zeroize"]
+thp = ["crypto/thp", "dep:trezor-thp", "dep:zeroize"]
test = [
"backlight",
"button",
@@ -118,6 +118,7 @@ ufmt.workspace = true
unsize.workspace = true
without-alloc.workspace = true
+crypto = { workspace = true, optional = true }
io = { workspace = true, optional = true }
trezor-thp = { workspace = true, optional = true }
upymod = { workspace = true, optional = true }
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index d5b95ea7..ee13a33f 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -16,8 +16,6 @@ fn main() {
#[cfg(feature = "micropython")]
generate_micropython_bindings();
generate_trezorhal_bindings();
- #[cfg(feature = "crypto")]
- generate_crypto_bindings();
#[cfg(all(feature = "test", not(feature = "with_new_crates")))]
link_core_objects();
@@ -618,66 +616,6 @@ fn generate_trezorhal_bindings() {
.unwrap();
}
-#[cfg(feature = "crypto")]
-fn generate_crypto_bindings() {
- let out_dir = env::var("OUT_DIR").unwrap();
-
- // Tell cargo to invalidate the built crate whenever the header changes.
- println!("cargo:rerun-if-changed=crypto.h");
-
- let bindings = prepare_bindings()
- .header("crypto.h")
- // aesgcm
- .allowlist_type("gcm_ctx")
- .no_copy("gcm_ctx")
- .allowlist_function("gcm_init_and_key")
- .allowlist_function("gcm_init_message")
- .allowlist_function("gcm_encrypt")
- .allowlist_function("gcm_decrypt")
- .allowlist_function("gcm_auth_header")
- .allowlist_function("gcm_compute_tag")
- // curve25519
- .allowlist_function("curve25519_scalarmult")
- .allowlist_function("curve25519_scalarmult_basepoint")
- // ed25519
- .allowlist_type("ed25519_signature")
- .allowlist_type("ed25519_public_key")
- .allowlist_function("ed25519_cosi_combine_publickeys")
- .allowlist_function("ed25519_sign_open")
- // elligator2
- .allowlist_function("map_to_curve_elligator2_curve25519")
- // hmac
- .allowlist_type("HMAC_SHA256_CTX")
- .no_copy("HMAC_SHA256_CTX")
- .allowlist_function("hmac_sha256_Init")
- .allowlist_function("hmac_sha256_Update")
- .allowlist_function("hmac_sha256_Final")
- // sha256
- .allowlist_var("SHA256_DIGEST_LENGTH")
- .allowlist_var("SHA256_BLOCK_LENGTH")
- .allowlist_type("SHA256_CTX")
- .no_copy("SHA256_CTX")
- .allowlist_function("sha256_Init")
- .allowlist_function("sha256_Update")
- .allowlist_function("sha256_Final")
- // sha512
- .allowlist_var("SHA512_DIGEST_LENGTH")
- .allowlist_var("SHA512_BLOCK_LENGTH")
- .allowlist_type("SHA512_CTX")
- .no_copy("SHA512_CTX")
- .allowlist_function("sha512_Init")
- .allowlist_function("sha512_Update")
- .allowlist_function("sha512_Final");
-
- // Write the bindings to a file in the OUR_DIR.
- bindings
- .clang_arg("-Wno-unused-function") // mode_hdr.h has static inline functions unused at parse time
- .generate()
- .expect("Unable to generate bindings")
- .write_to_file(PathBuf::from(out_dir).join("crypto.rs"))
- .unwrap();
-}
-
fn is_firmware() -> bool {
let target = env::var("TARGET").unwrap();
target.starts_with("thumbv7") || target.starts_with("thumbv8")
diff --git a/core/embed/rust/src/crypto/aesgcm.rs b/core/embed/rust/src/crypto/aesgcm.rs
deleted file mode 100644
index a0a697f2..00000000
--- a/core/embed/rust/src/crypto/aesgcm.rs
+++ /dev/null
@@ -1,599 +0,0 @@
-use core::pin::Pin;
-
-use zeroize::Zeroize;
-
-use super::memory::Memory;
-use super::{consteq, ffi, Error};
-
-// Tag size is a parameter but we fix it to 16 here for simplicity.
-pub const TAG_SIZE: usize = 16;
-pub type Tag = [u8; TAG_SIZE];
-
-// for bindgen RETURN_* macros are u32, just redefine the only one we are using
-const RETURN_GOOD: i32 = 0;
-const KEY_SIZES: [usize; 3] = [16, 24, 32];
-
-#[repr(u8)]
-#[derive(PartialEq)]
-enum State {
- Init,
- Processing,
- Finished,
- Failed,
-}
-
-struct AesGcmInner<'a> {
- ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
- state: State,
-}
-
-pub struct AesGcmEncrypt<'a>(AesGcmInner<'a>);
-pub struct AesGcmDecrypt<'a>(AesGcmInner<'a>);
-
-impl<'a> AesGcmInner<'a> {
- fn new(
- mut ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
- key: &[u8],
- iv: &[u8],
- ) -> Result<Self, Error> {
- if !KEY_SIZES.contains(&key.len()) {
- return Err(Error::InvalidParams);
- }
- // initialize the context
- // SAFETY: ffi
- let res =
- unsafe { ffi::gcm_init_and_key(key.as_ptr(), key.len() as cty::c_ulong, ctx.inner()) };
- ensure!(res == RETURN_GOOD, "gcm_init_and_key");
- let mut aesgcm = Self {
- ctx,
- state: State::Init,
- };
- aesgcm.reset(iv);
- Ok(aesgcm)
- }
-
- fn reset(&mut self, iv: &[u8]) {
- // SAFETY: ffi
- let res = unsafe {
- ffi::gcm_init_message(iv.as_ptr(), iv.len() as cty::c_ulong, self.ctx.inner())
- };
- ensure!(res == RETURN_GOOD, "gcm_init_message");
- self.state = State::Init;
- }
-
- fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
- self.check_state(&[State::Init, State::Processing])?;
-
- // SAFETY: ffi
- let res = unsafe {
- ffi::gcm_auth_header(data.as_ptr(), data.len() as cty::c_ulong, self.ctx.inner())
- };
- ensure!(res == RETURN_GOOD, "gcm_auth_header");
- Ok(())
- }
-
- fn finish(&mut self) -> Result<Tag, Error> {
- self.check_state(&[State::Init, State::Processing])?;
- self.state = State::Finished;
-
- let mut tag = [0u8; TAG_SIZE];
- // SAFETY: ffi
- let res = unsafe {
- ffi::gcm_compute_tag(
- tag.as_mut_ptr(),
- tag.len() as cty::c_ulong,
- self.ctx.inner(),
- )
- };
- if res != RETURN_GOOD {
- self.state = State::Failed;
- return Err(Error::InvalidContext);
- }
- Ok(tag)
- }
-
- fn check_state(&self, allowed: &[State]) -> Result<(), Error> {
- if !allowed.contains(&self.state) {
- return Err(Error::InvalidContext);
- }
- Ok(())
- }
-}
-
-impl<'a> AesGcmEncrypt<'a> {
- pub fn new(
- ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
- key: &[u8],
- iv: &[u8],
- ) -> Result<Self, Error> {
- Ok(Self(AesGcmInner::new(ctx, key, iv)?))
- }
-
- pub fn reset(&mut self, iv: &[u8]) {
- self.0.reset(iv)
- }
-
- pub fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
- self.0.auth(data)
- }
-
- pub fn encrypt<'b>(
- &mut self,
- plaintext: &[u8],
- buffer: &'b mut [u8],
- ) -> Result<&'b [u8], Error> {
- let buffer = buffer
- .get_mut(..plaintext.len())
- .ok_or(Error::InvalidParams)?;
- buffer.copy_from_slice(plaintext);
- match self.encrypt_in_place(buffer) {
- Err(e) => {
- buffer.zeroize(); // wipe plaintext from buffer on failure
- Err(e)
- }
- _ => Ok(buffer),
- }
- }
-
- pub fn encrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
- self.0.check_state(&[State::Init, State::Processing])?;
- self.0.state = State::Processing;
-
- let res = unsafe {
- ffi::gcm_encrypt(
- data.as_mut_ptr(),
- data.len() as cty::c_ulong,
- self.0.ctx.inner(),
- )
- };
- ensure!(res == RETURN_GOOD, "gcm_encrypt");
- Ok(())
- }
-
- pub fn finish(&mut self) -> Result<Tag, Error> {
- self.0.finish()
- }
-
- pub fn memory() -> Memory<ffi::gcm_ctx> {
- Memory::default()
- }
-}
-
-impl<'a> AesGcmDecrypt<'a> {
- pub fn new(
- ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
- key: &[u8],
- iv: &[u8],
- ) -> Result<Self, Error> {
- Ok(Self(AesGcmInner::new(ctx, key, iv)?))
- }
-
- pub fn reset(&mut self, iv: &[u8]) {
- self.0.reset(iv)
- }
-
- pub fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
- self.0.auth(data)
- }
-
- pub fn decrypt<'b>(
- &mut self,
- ciphertext: &[u8],
- buffer: &'b mut [u8],
- ) -> Result<&'b [u8], Error> {
- let buffer = buffer
- .get_mut(..ciphertext.len())
- .ok_or(Error::InvalidParams)?;
- buffer.copy_from_slice(ciphertext);
- self.decrypt_in_place(buffer)?;
- Ok(buffer)
- }
-
- pub fn decrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
- self.0.check_state(&[State::Init, State::Processing])?;
- self.0.state = State::Processing;
-
- // SAFETY: ffi
- let res = unsafe {
- ffi::gcm_decrypt(
- data.as_mut_ptr(),
- data.len() as cty::c_ulong,
- self.0.ctx.inner(),
- )
- };
- ensure!(res == RETURN_GOOD, "gcm_decrypt");
- Ok(())
- }
-
- pub fn finish(&mut self, expected_tag: &Tag) -> Result<(), Error> {
- let computed_tag = self.0.finish()?;
- if !consteq(&computed_tag, expected_tag) {
- return Err(Error::AuthenticationFailed);
- }
- Ok(())
- }
-
- pub fn memory() -> Memory<ffi::gcm_ctx> {
- Memory::default()
- }
-}
-
-impl Drop for AesGcmInner<'_> {
- fn drop(&mut self) {
- self.ctx.zeroize();
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::super::memory::init_ctx;
- use super::*;
-
- struct Vector {
- key: &'static str,
- iv: &'static str,
- aad: &'static str,
- plaintext: &'static str,
- ciphertext: &'static str,
- tag: &'static str,
- }
-
- impl Vector {
- fn decoded(&self) -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Tag) {
- let key = hex::decode(self.key).unwrap();
- let iv = hex::decode(self.iv).unwrap();
- let aad = hex::decode(self.aad).unwrap();
- let pt = hex::decode(self.plaintext).unwrap();
- let ct = hex::decode(self.ciphertext).unwrap();
- let tag = hex::decode(self.tag).unwrap();
- (key, iv, aad, pt, ct, Tag::try_from(tag).unwrap())
- }
- }
-
- const AES_GCM_VECTORS: &[Vector] = &[
- // first 10 vectors from https://github.com/BrianGladman/modes/blob/master/testvals/gcm.1
- Vector {
- key: "00000000000000000000000000000000",
- iv: "000000000000000000000000",
- aad: "",
- plaintext: "",
- ciphertext: "",
- tag: "58e2fccefa7e3061367f1d57a4e7455a",
- },
- Vector {
- key: "00000000000000000000000000000000",
- iv: "000000000000000000000000",
- aad: "",
- plaintext: "00000000000000000000000000000000",
- ciphertext: "0388dace60b6a392f328c2b971b2fe78",
- tag: "ab6e47d42cec13bdf53a67b21257bddf",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308",
- iv: "cafebabefacedbaddecaf888",
- aad: "",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
- ciphertext: "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
- tag: "4d5c2af327cd64a62cf35abd2ba6fab4",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308",
- iv: "cafebabefacedbaddecaf888",
- aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
- ciphertext: "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
- tag: "5bc94fbc3221a5db94fae95ae7121a47",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308",
- iv: "cafebabefacedbad",
- aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
- ciphertext: "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
- tag: "3612d2e79e3b0785561be14aaca2fccb",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308",
- iv: "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b",
- aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
- ciphertext: "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
- tag: "619cc5aefffe0bfa462af43c1699d050",
- },
- Vector {
- key: "000000000000000000000000000000000000000000000000",
- iv: "000000000000000000000000",
- aad: "",
- plaintext: "",
- ciphertext: "",
- tag: "cd33b28ac773f74ba00ed1f312572435",
- },
- Vector {
- key: "000000000000000000000000000000000000000000000000",
- iv: "000000000000000000000000",
- aad: "",
- plaintext: "00000000000000000000000000000000",
- ciphertext: "98e7247c07f0fe411c267e4384b0f600",
- tag: "2ff58d80033927ab8ef4d4587514f0fb",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308feffe9928665731c",
- iv: "cafebabefacedbaddecaf888",
- aad: "",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
- ciphertext: "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256",
- tag: "9924a7c8587336bfb118024db8674a14",
- },
- Vector {
- key: "feffe9928665731c6d6a8f9467308308feffe9928665731c",
- iv: "cafebabefacedbaddecaf888",
- aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
- plaintext: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
- ciphertext: "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710",
- tag: "2519498e80f1478f37ba55bd6d27618c",
- },
- // test vectors from test_trezor.wire.thp.crypto.py
- Vector {
- key: "0001020304050607000102030405060700010203040506070001020304050607",
- iv: "000000000000000000000000",
- aad: "5564",
- plaintext: "00010203040506070809",
- ciphertext: "e2c9dd152fbee5821ea7",
- tag: "10625812de81b14a46b9f1e5100a6d0c",
- },
- Vector {
- key: "0001020304050607000102030405060700010203040506070001020304050607",
- iv: "000000000000000000000001",
- aad: "5564",
- plaintext: "00010203040506070809",
- ciphertext: "79811619ddb07c2b99f8",
- tag: "71c6b872cdc499a7e9a3c7441f053214",
- },
- Vector {
- key: "0001020304050607000102030405060700010203040506070001020304050607",
- iv: "000000000000000000000171",
- aad: "5564",
- plaintext: "000102030405060708090a0b0c0d0e0f",
- ciphertext: "03bd030390f2dfe815a61c2b157a064f",
- tag: "c1200f8a7ae9a6d32cef0fff878d55c2",
- },
- Vector {
- key: "0001020304050607000102030405060700010203040506070001020304050607",
- iv: "000000000000000000000171",
- aad: "5564738291",
- plaintext: "000102030405060708090a0b0c0d0e0f",
- ciphertext: "03bd030390f2dfe815a61c2b157a064f",
- tag: "693ac160cd93a20f7fc255f049d808d0",
- },
- ];
-
- #[test]
- fn test_vectors() {
- for v in AES_GCM_VECTORS {
- let (key, iv, aad, plaintext, ciphertext, tag) = v.decoded();
-
- init_ctx!(AesGcmEncrypt, ctx_enc, &key, &iv);
- let mut ctx_enc = ctx_enc.unwrap();
- init_ctx!(AesGcmDecrypt, ctx_dec, &key, &iv);
- let mut ctx_dec = ctx_dec.unwrap();
-
- if !plaintext.is_empty() {
- let mut buffer = vec![0; plaintext.len()];
- let result = ctx_enc.encrypt(&plaintext, &mut buffer).unwrap();
- assert_eq!(hex::encode(result), v.ciphertext);
-
- let result = ctx_dec.decrypt(&ciphertext, &mut buffer).unwrap();
- assert_eq!(hex::encode(result), v.plaintext);
- }
-
- if !aad.is_empty() {
- ctx_enc.auth(&aad).unwrap();
- ctx_dec.auth(&aad).unwrap();
- }
-
- let result = ctx_enc.finish().unwrap();
- assert_eq!(hex::encode(result), v.tag);
- ctx_dec.finish(&tag).unwrap();
- }
- }
-
- #[test]
- fn test_state() {
- // ok: empty string tag - encryption
- init_ctx!(AesGcmEncrypt, ctx_enc, &[0u8; 16], b"1");
- let mut ctx_enc = ctx_enc.unwrap();
- let tag_empty = ctx_enc.finish().unwrap();
-
- // ok: empty string tag - decryption
- init_ctx!(AesGcmDecrypt, ctx_dec, &[0u8; 16], b"1");
- let mut ctx_dec = ctx_dec.unwrap();
- ctx_dec.finish(&tag_empty).unwrap();
-
- // ok: any single operation
- // not ok: after reset
- let mut dest = [0u8; 4];
- let mut dest2 = [0u8; 16];
- ctx_enc.reset(b"2");
- ctx_enc.encrypt(b"asdf", &mut dest).unwrap();
- let tag2 = ctx_enc.finish().unwrap();
- assert!(ctx_enc.encrypt(b"asdf", &mut dest2).is_err());
-
- ctx_dec.reset(b"2");
- ctx_dec.decrypt(&dest, &mut dest2).unwrap();
- ctx_dec.finish(&tag2).unwrap();
- assert!(ctx_dec.decrypt(b"fdsa", &mut dest).is_err());
-
- ctx_enc.reset(b"5");
- ctx_enc.auth(b"foobar").unwrap();
- let tag5 = ctx_enc.finish().unwrap();
- assert!(ctx_enc.auth(b"foobar").is_err());
-
- ctx_dec.reset(b"5");
- ctx_dec.auth(b"foobar").unwrap();
- ctx_dec.finish(&tag5).unwrap();
- assert!(ctx_dec.auth(b"foobar").is_err());
- }
-
- // test vectors from
- // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/mac/gcmtestvectors.zip
- const NIST_VECTORS: &[Vector] = &[
- Vector {
- key: "11754cd72aec309bf52f7687212e8957",
- iv: "3c819d9a9bed087615030b65",
- plaintext: "",
- aad: "",
- ciphertext: "",
- tag: "250327c674aaf477aef2675748cf6971",
- },
- Vector {
- key: "fe9bb47deb3a61e423c2231841cfd1fb",
- iv: "4d328eb776f500a2f7fb47aa",
- plaintext: "f1cc3818e421876bb6b8bbd6c9",
- aad: "",
- ciphertext: "b88c5c1977b35b517b0aeae967",
- tag: "43fd4727fe5cdb4b5b42818dea7ef8c9",
- },
- Vector {
- key: "6f44f52c2f62dae4e8684bd2bc7d16ee7c557330305a790d",
- iv: "9ae35825d7c7edc9a39a0732",
- plaintext: "37222d30895eb95884bbbbaee4d9cae1",
- aad: "1b4236b846fc2a0f782881ba48a067e9",
- ciphertext: "a54b5da33fc1196a8ef31a5321bfcaeb",
- tag: "1c198086450ae1834dd6c2636796bce2",
- },
- Vector {
- key: "05f714021372ae1c8d72c98e6307fbddb26ee27615860a9fb48ba4c3ea360a00",
- iv: "c0",
- plaintext: "ec3afbaa1447e47ce068bffb787bd0cadc9f0deceb11fa78e981271390578ae95891f26664b5e62d1fd5fd0d0767a54da5f86f",
- aad: "faf9fa457a8e70ea709da28545f18f041351e8d5",
- ciphertext: "c8c5816ba9e7e0d20820dc0064a519a277889f5ac9661c9882b5a9896fd12836c6721514e885b1d34f5e888d1d85abce8c2ebb",
- tag: "0856f211fade7d26d64478ca46025a3c",
- },
- ];
-
- // following tests ported from test_trezor.crypto.aesgcm.py
- #[test]
- fn test_gcm() {
- for v in NIST_VECTORS {
- let (key, iv, aad, pt, ct, tag) = v.decoded();
-
- // Test encryption.
- init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- if !aad.is_empty() {
- ctx.auth(&aad).unwrap();
- }
- let mut buffer = vec![0; pt.len()];
- let result = ctx.encrypt(&pt, &mut buffer).unwrap();
- assert_eq!(hex::encode(result), v.ciphertext);
-
- let result = ctx.finish().unwrap();
- assert_eq!(hex::encode(result), v.tag);
-
- // Test decryption.
- init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- if !aad.is_empty() {
- ctx.auth(&aad).unwrap();
- }
- let result = ctx.decrypt(&ct, &mut buffer).unwrap();
- assert_eq!(hex::encode(result), v.plaintext);
-
- ctx.finish(&tag).unwrap();
- }
- }
-
- #[test]
- fn test_gcm_in_place() {
- for v in NIST_VECTORS {
- let (key, iv, aad, pt, ct, tag) = v.decoded();
-
- // Test encryption.
- init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- if !aad.is_empty() {
- ctx.auth(&aad).unwrap();
- }
- let mut buffer = Vec::new();
- buffer.extend_from_slice(&pt);
- ctx.encrypt_in_place(&mut buffer).unwrap();
- assert_eq!(hex::encode(buffer), v.ciphertext);
-
- let result = ctx.finish().unwrap();
- assert_eq!(hex::encode(result), v.tag);
-
- // Test decryption.
- init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- if !aad.is_empty() {
- ctx.auth(&aad).unwrap();
- }
- let mut buffer = Vec::new();
- buffer.extend_from_slice(&ct);
- ctx.decrypt_in_place(&mut buffer).unwrap();
- assert_eq!(hex::encode(buffer), v.plaintext);
-
- ctx.finish(&tag).unwrap();
- }
- }
-
- #[test]
- fn test_gcm_chunks() {
- for v in NIST_VECTORS {
- let (key, iv, aad, pt, ct, tag) = v.decoded();
- let chunk_len = pt.len() / 3;
- let mut buffer = vec![0; pt.len()];
-
- init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- ctx.decrypt(&ct[..chunk_len], &mut buffer[..chunk_len])
- .unwrap();
- ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
- ctx.decrypt(&ct[chunk_len..], &mut buffer[chunk_len..])
- .unwrap();
- ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
- assert_eq!(hex::encode(buffer), v.plaintext);
- ctx.finish(&tag).unwrap();
-
- buffer = vec![0; pt.len()];
- init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
- ctx.encrypt(&pt[..chunk_len], &mut buffer[..chunk_len])
- .unwrap();
- ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
- ctx.encrypt(&pt[chunk_len..], &mut buffer[chunk_len..])
- .unwrap();
- assert_eq!(hex::encode(buffer), v.ciphertext);
- assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
- }
- }
-
- #[test]
- fn test_gcm_chunks_in_place() {
- for v in NIST_VECTORS {
- let (key, iv, aad, pt, ct, tag) = v.decoded();
- let chunk_len = pt.len() / 3;
-
- let mut buffer = ct;
- init_ctx!(AesGcmDecrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- ctx.decrypt_in_place(&mut buffer[..chunk_len]).unwrap();
- ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
- ctx.decrypt_in_place(&mut buffer[chunk_len..]).unwrap();
- ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
- assert_eq!(hex::encode(buffer), v.plaintext);
- ctx.finish(&tag).unwrap();
-
- let mut buffer = pt;
- init_ctx!(AesGcmEncrypt, ctx, &key, &iv);
- let mut ctx = ctx.unwrap();
- ctx.auth(aad.get(..7).unwrap_or(&[])).unwrap();
- ctx.encrypt_in_place(&mut buffer[..chunk_len]).unwrap();
- ctx.auth(aad.get(7..).unwrap_or(&[])).unwrap();
- ctx.encrypt_in_place(&mut buffer[chunk_len..]).unwrap();
- assert_eq!(hex::encode(buffer), v.ciphertext);
- assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
- }
- }
-}
diff --git a/core/embed/rust/src/crypto/cosi.rs b/core/embed/rust/src/crypto/cosi.rs
deleted file mode 100644
index 2bdca576..00000000
--- a/core/embed/rust/src/crypto/cosi.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use heapless::Vec;
-
-use super::{ed25519, ffi, Error};
-
-const MAX_PUBKEYS: usize = 3;
-
-pub struct Signature {
- sigmask: u8,
- signature: ed25519::Signature,
-}
-
-impl Signature {
- pub fn new(sigmask: u8, signature: ed25519::Signature) -> Self {
- Self { sigmask, signature }
- }
-}
-
-pub fn verify(
- threshold: u8,
- message: &[u8],
- public_keys: &[ed25519::PublicKey],
- signature: &Signature,
-) -> Result<(), Error> {
- if threshold < 1 {
- return Err(Error::InvalidParams);
- }
- let selected_keys = select_keys(signature.sigmask, public_keys)?;
- if selected_keys.len() < threshold as usize {
- return Err(Error::InvalidParams);
- }
- let combined_key = combine_publickeys(&selected_keys)?;
- ed25519::verify(message, &combined_key, &signature.signature)
-}
-
-fn select_keys(
- mut sigmask: u8,
- keys: &[ed25519::PublicKey],
-) -> Result<Vec<ed25519::PublicKey, MAX_PUBKEYS>, Error> {
- let mut selected_keys = Vec::new();
- for key in keys {
- if sigmask & 1 != 0 {
- unwrap!(selected_keys.push(*key));
- }
- sigmask >>= 1;
- }
- if sigmask != 0 {
- Err(Error::InvalidParams)
- } else {
- Ok(selected_keys)
- }
-}
-
-fn combine_publickeys(keys: &[ed25519::PublicKey]) -> Result<ed25519::PublicKey, Error> {
- let mut combined_key = ed25519::PublicKey::default();
- let res = unsafe {
- ffi::ed25519_cosi_combine_publickeys(&mut combined_key as *mut _, keys.as_ptr(), keys.len())
- };
- if res == 0 {
- Ok(combined_key)
- } else {
- Err(Error::InvalidEncoding)
- }
-}
diff --git a/core/embed/rust/src/crypto/crc32.rs b/core/embed/rust/src/crypto/crc32.rs
deleted file mode 100644
index 471dda7c..00000000
--- a/core/embed/rust/src/crypto/crc32.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-pub struct Crc32 {
- value: u32,
-}
-
-static CRC32TAB: [u32; 16] = [
- 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
- 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
-];
-
-impl Crc32 {
- pub fn new() -> Self {
- Self { value: u32::MAX }
- }
-
- pub fn update(mut self, data: &[u8]) -> Self {
- for b in data {
- self.value ^= u32::from(*b);
- self.value = CRC32TAB[(self.value & 0x0f) as usize] ^ (self.value >> 4);
- self.value = CRC32TAB[(self.value & 0x0f) as usize] ^ (self.value >> 4);
- }
- self
- }
-
- pub fn finalize(self) -> [u8; 4] {
- let inverted = self.value ^ u32::MAX;
- inverted.to_be_bytes()
- }
-}
-
-pub fn digest(data: &[u8]) -> [u8; 4] {
- Crc32::new().update(data).finalize()
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::strutil::hexlify;
-
- const CRC32_VECTORS: &[(&[u8], &[u8])] = &[
- (b"", b"00000000"),
- (b"a", b"e8b7be43"),
- (b"abc", b"352441c2"),
- (b"message digest", b"20159d7f"),
- (b"abcdefghijklmnopqrstuvwxyz", b"4c2750bd"),
- (
- b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
- b"1fc2e6d2",
- ),
- (
- b"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
- b"7ca94a72",
- ),
- ];
-
- fn hexdigest(data: &[u8]) -> [u8; 8] {
- let mut out_hex = [0u8; 8];
- let digest = digest(data);
- hexlify(&digest, &mut out_hex);
- out_hex
- }
-
- #[test]
- fn test_no_update() {
- let out = Crc32::new().finalize();
- let mut out_hex = [0u8; 8];
- hexlify(&out, &mut out_hex);
-
- assert_eq!(out_hex, *b"00000000");
- }
-
- #[test]
- fn test_vectors() {
- for (data, expected) in CRC32_VECTORS {
- let out_hex = hexdigest(data);
- assert_eq!(out_hex, *expected);
- }
- }
-}
diff --git a/core/embed/rust/src/crypto/curve25519.rs b/core/embed/rust/src/crypto/curve25519.rs
deleted file mode 100644
index f09b1674..00000000
--- a/core/embed/rust/src/crypto/curve25519.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-use zeroize::{Zeroize, ZeroizeOnDrop};
-
-use super::ffi;
-
-#[derive(Zeroize, ZeroizeOnDrop)]
-pub struct Point {
- bytes: [u8; 32],
-}
-
-#[cfg(feature = "thp")]
-impl trezor_thp::channel::U8Array for Point {
- fn new() -> Self {
- Self { bytes: [0u8; 32] }
- }
-
- fn new_with(c: u8) -> Self {
- Self { bytes: [c; 32] }
- }
-
- fn from_slice(src: &[u8]) -> Self {
- let mut bytes = [0u8; 32];
- bytes.copy_from_slice(src);
- Self { bytes }
- }
-
- fn len() -> usize {
- 32
- }
-
- fn as_slice(&self) -> &[u8] {
- &self.bytes
- }
-
- fn as_mut(&mut self) -> &mut [u8] {
- &mut self.bytes
- }
-}
-
-#[derive(Zeroize, ZeroizeOnDrop)]
-pub struct Scalar {
- bytes: [u8; 32],
-}
-
-#[cfg(feature = "thp")]
-impl trezor_thp::channel::U8Array for Scalar {
- fn new() -> Self {
- Self { bytes: [0u8; 32] }
- }
-
- fn new_with(c: u8) -> Self {
- Self { bytes: [c; 32] }
- }
-
- fn from_slice(src: &[u8]) -> Self {
- let mut bytes = [0u8; 32];
- bytes.copy_from_slice(src);
- Self { bytes }
- }
-
- fn len() -> usize {
- 32
- }
-
- fn as_slice(&self) -> &[u8] {
- &self.bytes
- }
-
- fn as_mut(&mut self) -> &mut [u8] {
- &mut self.bytes
- }
-}
-
-impl Scalar {
- pub fn from_bytes(bytes: [u8; 32]) -> Self {
- let mut res = Self { bytes };
- // taken from https://cr.yp.to/ecdh.html
- res.bytes[0] &= 248;
- res.bytes[31] &= 127;
- res.bytes[31] |= 64;
- res
- }
-
- pub fn generate() -> Self {
- let mut bytes = [0u8; 32];
- crate::trezorhal::random::bytes(&mut bytes);
- Self::from_bytes(bytes)
- }
-}
-
-impl Point {
- pub fn from_secret(secret: &Scalar) -> Self {
- let mut res = Self { bytes: [0u8; 32] };
- let dest = res.bytes.as_mut_ptr();
- let secret_bytes = secret.bytes.as_ptr();
- // SAFETY: ffi
- unsafe {
- ffi::curve25519_scalarmult_basepoint(dest, secret_bytes);
- }
- res
- }
-
- pub fn multiply(&self, secret: &Scalar) -> Self {
- let mut res = Self { bytes: [0u8; 32] };
- let dest = res.bytes.as_mut_ptr();
- let secret_bytes = secret.bytes.as_ptr();
- let point_bytes = self.bytes.as_ptr();
- // SAFETY: ffi
- unsafe { ffi::curve25519_scalarmult(dest, secret_bytes, point_bytes) }
- res
- }
-
- // No need for validation, every 32 byte array represents a valid point.
- // See https://cr.yp.to/ecdh/curve25519-20060209.pdf
- pub fn from_bytes(bytes: [u8; 32]) -> Self {
- Self { bytes }
- }
-
- pub fn to_bytes(&self) -> [u8; 32] {
- self.bytes
- }
-
- pub fn map_to_curve_elligator2(input: &[u8; 32]) -> Self {
- let mut res = Self { bytes: [0u8; 32] };
- let dest = res.bytes.as_mut_ptr();
- // SAFETY: ffi
- let ok = unsafe { ffi::map_to_curve_elligator2_curve25519(input.as_ptr(), dest) };
- assert!(ok); // always returns true
- res
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_generate() {
- for _ in 0..100 {
- let bytes = Scalar::generate().bytes;
- assert!(bytes[0] & 7 == 0 && bytes[31] & 128 == 0 && bytes[31] & 64 == 64)
- }
- }
-
- #[test]
- fn test_multiply() {
- const VECTORS: &[(&'static str, &'static str, &'static str)] = &[(
- "38c9d9b17911de26ed812f5cc19c0029e8d016bcbc6078bc9db2af33f1761e4a",
- "311b6248af8dabec5cc81eac5bf229925f6d218a12e0547fb1856e015cc76f5d",
- "a93dbdb23e5c99da743e203bd391af79f2b83fb8d0fd6ec813371c71f08f2d4d",
- )];
-
- for (sk, pk, session) in VECTORS {
- let sk = hex::decode(sk).unwrap();
- let sk = Scalar::from_bytes(*sk.first_chunk::<32>().unwrap());
-
- let pk = hex::decode(pk).unwrap();
- let pk = Point::from_bytes(*pk.first_chunk::<32>().unwrap());
-
- let session = hex::decode(session).unwrap();
- let session = session.first_chunk::<32>().unwrap();
-
- let session2 = pk.multiply(&sk);
- assert_eq!(session2.to_bytes(), *session);
- }
- }
-
- #[test]
- fn test_multiply_random() {
- for _ in 0..100 {
- let sk1 = Scalar::generate();
- let sk2 = Scalar::generate();
- let pk1 = Point::from_secret(&sk1);
- let pk2 = Point::from_secret(&sk2);
- let session1 = pk2.multiply(&sk1);
- let session2 = pk1.multiply(&sk2);
- assert_eq!(session1.to_bytes(), session2.to_bytes());
- }
- }
-
- #[test]
- fn test_clamping() {
- let mut bytes1 = [0u8; 32];
- crate::trezorhal::random::bytes(&mut bytes1);
-
- let mut bytes2 = bytes1;
- // flipping the bits affected by clamping should not change the results
- bytes2[0] |= !0xf8;
- bytes2[31] |= !0x7f;
- bytes2[31] &= !0x40;
-
- let sk1 = Scalar::from_bytes(bytes1);
- let sk2 = Scalar::from_bytes(bytes2);
-
- let pk1 = Point::from_secret(&sk1);
- let pk2 = Point::from_secret(&sk2);
- assert_eq!(pk1.to_bytes(), pk2.to_bytes());
-
- let sk3 = Scalar::generate();
- let pk3 = Point::from_secret(&sk3);
- let res1 = pk3.multiply(&sk1);
- let res2 = pk3.multiply(&sk2);
- assert_eq!(res1.to_bytes(), res2.to_bytes());
- }
-
- #[cfg(feature = "layout_eckhart")] // TODO replace with feature = "thp"
- #[test]
- fn test_elligator2() {
- // https://elligator.org/vectors/curve25519_direct.vec
- const VECTORS: &[(&'static str, &'static str)] = &[
- (
- "0000000000000000000000000000000000000000000000000000000000000000",
- "0000000000000000000000000000000000000000000000000000000000000000",
- ),
- (
- "66665895c5bc6e44ba8d65fd9307092e3244bf2c18877832bd568cb3a2d38a12",
- "04d44290d13100b2c25290c9343d70c12ed4813487a07ac1176daa5925e7975e",
- ),
- (
- "673a505e107189ee54ca93310ac42e4545e9e59050aaac6f8b5f64295c8ec02f",
- "242ae39ef158ed60f20b89396d7d7eef5374aba15dc312a6aea6d1e57cacf85e",
- ),
- (
- "990b30e04e1c3620b4162b91a33429bddb9f1b70f1da6e5f76385ed3f98ab131",
- "998e98021eb4ee653effaa992f3fae4b834de777a953271baaa1fa3fef6b776e",
- ),
- (
- "341a60725b482dd0de2e25a585b208433044bc0a1ba762442df3a0e888ca063c",
- "683a71d7fca4fc6ad3d4690108be808c2e50a5af3174486741d0a83af52aeb01",
- ),
- (
- "922688fa428d42bc1fa8806998fbc5959ae801817e85a42a45e8ec25a0d7541a",
- "696f341266c64bcfa7afa834f8c34b2730be11c932e08474d1a22f26ed82410b",
- ),
- (
- "0d3b0eb88b74ed13d5f6a130e03c4ad607817057dc227152827c0506a538bb3a",
- "0b00df174d9fb0b6ee584d2cf05613130bad18875268c38b377e86dfefef177f",
- ),
- (
- "01a3ea5658f4e00622eeacf724e0bd82068992fae66ed2b04a8599be16662e35",
- "7ae4c58bc647b5646c9f5ae4c2554ccbf7c6e428e7b242a574a5a9c293c21f7e",
- ),
- (
- "1d991dff82a84afe97874c0f03a60a56616a15212fbe10d6c099aa3afcfabe35",
- "f81f235696f81df90ac2fc861ceee517bff611a394b5be5faaee45584642fb0a",
- ),
- (
- "185435d2b005a3b63f3187e64a1ef3582533e1958d30e4e4747b4d1d3376c728",
- "f938b1b320abb0635930bd5d7ced45ae97fa8b5f71cc21d87b4c60905c125d34",
- ),
- ];
-
- for (input, output) in VECTORS {
- let input_bytes = hex::decode(input).unwrap();
- let input_bytes = input_bytes.first_chunk::<32>().unwrap();
- let point = Point::map_to_curve_elligator2(input_bytes);
- assert_eq!(hex::encode(point.to_bytes()), *output);
- }
- }
-}
diff --git a/core/embed/rust/src/crypto/ed25519.rs b/core/embed/rust/src/crypto/ed25519.rs
deleted file mode 100644
index 03873578..00000000
--- a/core/embed/rust/src/crypto/ed25519.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use super::{ffi, Error};
-
-pub type Signature = ffi::ed25519_signature;
-pub const SIGNATURE_SIZE: usize = core::mem::size_of::<Signature>();
-
-pub type PublicKey = ffi::ed25519_public_key;
-pub const PUBLIC_KEY_SIZE: usize = core::mem::size_of::<PublicKey>();
-
-pub fn verify(message: &[u8], public_key: &PublicKey, signature: &Signature) -> Result<(), Error> {
- let res = unsafe {
- ffi::ed25519_sign_open(
- message.as_ptr(),
- message.len(),
- public_key.as_ptr(),
- signature.as_ptr(),
- )
- };
- if res == 0 {
- Ok(())
- } else {
- Err(Error::SignatureVerificationFailed)
- }
-}
diff --git a/core/embed/rust/src/crypto/ffi.rs b/core/embed/rust/src/crypto/ffi.rs
deleted file mode 100644
index a4c8a4b8..00000000
--- a/core/embed/rust/src/crypto/ffi.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-#![allow(non_camel_case_types)]
-
-include!(concat!(env!("OUT_DIR"), "/crypto.rs"));
diff --git a/core/embed/rust/src/crypto/hmac.rs b/core/embed/rust/src/crypto/hmac.rs
deleted file mode 100644
index 061e7cf3..00000000
--- a/core/embed/rust/src/crypto/hmac.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-use core::pin::Pin;
-
-use zeroize::{Zeroize, ZeroizeOnDrop};
-
-use super::ffi;
-use super::memory::{init_ctx, Memory};
-
-pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
-pub type Digest = [u8; DIGEST_SIZE];
-
-#[derive(Zeroize, ZeroizeOnDrop)]
-pub struct HmacSha256<'a> {
- ctx: Pin<&'a mut Memory<ffi::HMAC_SHA256_CTX>>,
-}
-
-impl<'a> HmacSha256<'a> {
- pub fn new(mut ctx: Pin<&'a mut Memory<ffi::HMAC_SHA256_CTX>>, key: &[u8]) -> Self {
- // initialize the context
- // SAFETY: ffi
- unsafe { ffi::hmac_sha256_Init(ctx.inner(), key.as_ptr(), key.len() as u32) };
- Self { ctx }
- }
-
- pub fn update(&mut self, data: &[u8]) {
- // SAFETY: ffi
- unsafe { ffi::hmac_sha256_Update(self.ctx.inner(), data.as_ptr(), data.len() as u32) };
- }
-
- pub fn memory() -> Memory<ffi::HMAC_SHA256_CTX> {
- Memory::default()
- }
-
- pub fn finalize_into(mut self, out: &mut Digest) {
- // SAFETY: ffi
- unsafe { ffi::hmac_sha256_Final(self.ctx.inner(), out.as_mut_ptr()) };
- }
-}
-
-pub fn digest_into(key: &[u8], data: &[u8], out: &mut Digest) {
- init_ctx!(HmacSha256, ctx, key);
- ctx.update(data);
- ctx.finalize_into(out);
-}
-
-pub fn digest(key: &[u8], data: &[u8]) -> Digest {
- let mut out = [0u8; DIGEST_SIZE];
- digest_into(key, data, &mut out);
- out
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::strutil::hexlify;
-
- const HMAC_SHA256_EMPTY: &[u8] =
- b"b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad";
- // RFC 4231
- const HMAC_SHA256_VECTORS: &[(&[u8], &[u8], &[u8])] = &[
- (
- &[0x0b; 20],
- b"Hi There",
- b"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
- ),
- (
- b"Jefe",
- b"what do ya want for nothing?",
- b"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
- ),
-
- (
- &[0xaa; 20],
- &[0xdd; 50],
- b"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
- ),
- (
- &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19],
- &[0xcd; 50],
- b"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
- ),
- // skipping case with truncation
- (
- &[0xaa; 131],
- b"Test Using Larger Than Block-Size Key - Hash Key First",
- b"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
- ),
- (
- &[0xaa; 131],
- b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.",
- b"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
- ),
- (
- b"",
- b"",
- HMAC_SHA256_EMPTY,
- ),
- ];
-
- fn hexdigest(key: &[u8], data: &[u8]) -> [u8; DIGEST_SIZE * 2] {
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- let digest = digest(key, data);
- hexlify(&digest, &mut out_hex);
- out_hex
- }
-
- #[test]
- fn test_empty_ctx() {
- let mut out = [0u8; DIGEST_SIZE];
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- init_ctx!(HmacSha256, ctx, b"");
- ctx.finalize_into(&mut out);
- hexlify(&out, &mut out_hex);
-
- assert_eq!(out_hex, HMAC_SHA256_EMPTY);
- }
-
- #[test]
- fn test_vectors() {
- for (key, data, expected) in HMAC_SHA256_VECTORS {
- let out_hex = hexdigest(key, data);
- assert_eq!(out_hex, *expected);
- }
- }
-
- #[test]
- fn test_update() {
- // case 3
- let key =
- b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa";
- init_ctx!(HmacSha256, ctx, key);
- for _ in 0..50 {
- ctx.update(b"\xdd");
- }
- let mut out = [0u8; DIGEST_SIZE];
- ctx.finalize_into(&mut out);
- assert_eq!(
- hex::encode(out),
- "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
- );
-
- // case 4
- let key = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19";
- init_ctx!(HmacSha256, ctx, key);
- for _ in 0..50 {
- ctx.update(b"\xcd");
- }
- ctx.finalize_into(&mut out);
- assert_eq!(
- hex::encode(out),
- "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
- );
- }
-}
diff --git a/core/embed/rust/src/crypto/memory.rs b/core/embed/rust/src/crypto/memory.rs
deleted file mode 100644
index 39d118b4..00000000
--- a/core/embed/rust/src/crypto/memory.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use core::marker::PhantomPinned;
-use core::mem::MaybeUninit;
-use core::pin::Pin;
-
-use zeroize::{zeroize_flat_type, Zeroize};
-
-/// Wrapper for a memory used as a context by C functions. Its purpose is to be
-/// !Unpin, thus prevent moves when accessed through a Pin. We want to avoid
-/// moves as they can leave cryptographic data in memory.
-///
-/// T needs to be a plain struct that is valid when zeroed.
-pub struct Memory<T> {
- inner: T,
- _phantom: PhantomPinned,
-}
-
-impl<T> Default for Memory<T> {
- fn default() -> Self {
- // SAFETY: a zeroed block of memory is valid for C functions
- let inner = unsafe { MaybeUninit::<T>::zeroed().assume_init() };
- Self {
- inner,
- _phantom: PhantomPinned,
- }
- }
-}
-
-impl<T> Zeroize for Memory<T> {
- fn zeroize(&mut self) {
- // SAFETY:
- // - contains no references
- // - plain struct with not Drop impls
- // - only called in Drop impl
- // - zeroed block of memory is valid
- unsafe { zeroize_flat_type(&mut self.inner as *mut T) };
- }
-}
-
-type PinnedMemory<'a, T> = Pin<&'a mut Memory<T>>;
-
-impl<T> Memory<T> {
- // SAFETY:
- // The caller must ensure that the return value is handled according to the
- // contract of `Pin::map_unchecked_mut` and `Pin::get_unchecked_mut`.
- // Notably passing the pointer to a C function should be fine since the notion
- // of moving doesn't exist there and the entire point of this pinning is not
- // to leak more data than the C implementation.
- pub unsafe fn inner(self: &mut Pin<&mut Self>) -> *mut T {
- unsafe {
- self.as_mut()
- .map_unchecked_mut(|m| &mut m.inner)
- .get_unchecked_mut()
- }
- }
-}
-
-impl<T> Zeroize for Pin<&mut Memory<T>> {
- fn zeroize(&mut self) {
- // SAFETY: `Memory::zeroize` does not do any moving
- unsafe {
- self.as_mut().get_unchecked_mut().zeroize();
- }
- }
-}
-
-/// Initializes backing memory on the stack and passes it to a constructor.
-/// The macro is basically a specialized version of `core::pin::pin!` for use
-/// with Memory<T>.
-#[allow(unused_macros)]
-macro_rules! init_ctx {
- ($type:ty, $name:ident $(, $arg:expr)*) => {
- // assign the backing memory to $name...
- let mut $name = <$type>::memory();
- // ... then make it inaccessible by overwriting the binding, and pin it
- // SAFETY: The value is pinned: it is the local above which cannot be named outside this macro.
- #[allow(unused_mut)]
- let mut $name = unsafe {
- <$type>::new(core::pin::Pin::new_unchecked(&mut $name), $($arg),*)
- };
- };
-}
-
-pub(crate) use init_ctx;
diff --git a/core/embed/rust/src/crypto/merkle.rs b/core/embed/rust/src/crypto/merkle.rs
deleted file mode 100644
index 5cb9e8bc..00000000
--- a/core/embed/rust/src/crypto/merkle.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use super::memory::init_ctx;
-use super::sha256;
-
-/// Calculate a Merkle root based on a leaf element and a proof of inclusion.
-///
-/// Expects the Merkle tree format specified in `external-definitions.md`.
-pub fn merkle_root(elem: &[u8], proof: &[sha256::Digest]) -> sha256::Digest {
- let mut out = sha256::Digest::default();
-
- // hash the leaf element
- init_ctx!(sha256::Sha256, ctx);
- ctx.update(&[0x00]);
- ctx.update(elem);
- ctx.finalize_into(&mut out);
-
- for proof_elem in proof {
- // hash together the current hash and the proof element
- let (min, max) = if &out < proof_elem {
- (&out, proof_elem)
- } else {
- (proof_elem, &out)
- };
- init_ctx!(sha256::Sha256, ctx);
- ctx.update(&[0x01]);
- ctx.update(min);
- ctx.update(max);
- ctx.finalize_into(&mut out);
- }
-
- out
-}
diff --git a/core/embed/rust/src/crypto/mod.rs b/core/embed/rust/src/crypto/mod.rs
deleted file mode 100644
index 72f9002f..00000000
--- a/core/embed/rust/src/crypto/mod.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-use core::hint::black_box;
-
-use crate::error::value_error;
-
-pub mod aesgcm;
-pub mod cosi;
-pub mod crc32;
-pub mod curve25519;
-pub mod ed25519;
-mod ffi;
-pub mod hmac;
-pub mod memory;
-pub mod merkle;
-pub mod sha256;
-pub mod sha512;
-
-#[cfg_attr(feature = "test", derive(core::fmt::Debug))]
-pub enum Error {
- // Signature verification failed
- SignatureVerificationFailed,
- // Provided value is not a valid public key / signature / etc.
- InvalidEncoding,
- // Provided parameters are not accepted (e.g., signature threshold out of bounds)
- InvalidParams,
- // State precondition check failed (possibly raised by C implementation)
- InvalidContext,
- // Authentication failed (e.g. AEAD tag mismatch)
- AuthenticationFailed,
-}
-
-impl From<Error> for crate::error::Error {
- fn from(e: Error) -> Self {
- match e {
- Error::SignatureVerificationFailed => value_error!(c"Signature verification failed"),
- Error::InvalidEncoding => value_error!(c"Invalid key or signature encoding"),
- Error::InvalidParams => value_error!(c"Invalid cryptographic parameters"),
- Error::InvalidContext => value_error!(c"Invalid cryptographic context"),
- Error::AuthenticationFailed => value_error!(c"Authentication failed"),
- }
- }
-}
-
-/// Constant time bytestring comparison for two arrays of the same length.
-fn consteq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
- let mut diff: u8 = 0;
- for i in 0..N {
- diff |= a[i] ^ b[i];
- }
- black_box(black_box(diff) == 0)
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_consteq() {
- assert!(consteq(&[], &[]));
- assert!(consteq(&[0u8; 256], &[0u8; 256]));
- assert!(consteq(&[0xffu8; 256], &[0xffu8; 256]));
- assert!(consteq(b"0123456789abcdef", b"0123456789abcdef"));
-
- assert!(!consteq(&[0u8; 256], &[0xffu8; 256]));
- assert!(!consteq(&[0xffu8; 256], &[0u8; 256]));
- assert!(!consteq(b"0123456789abcdef", b"123456789abcdef0"));
- assert!(!consteq(b"0000000000000000", b"0000000000000001"));
- }
-}
diff --git a/core/embed/rust/src/crypto/sha256.rs b/core/embed/rust/src/crypto/sha256.rs
deleted file mode 100644
index bf2dc24c..00000000
--- a/core/embed/rust/src/crypto/sha256.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-use core::mem::MaybeUninit;
-use core::pin::Pin;
-
-use zeroize::{Zeroize, ZeroizeOnDrop};
-
-use super::ffi;
-use super::memory::{init_ctx, Memory};
-
-pub const BLOCK_SIZE: usize = ffi::SHA256_BLOCK_LENGTH as usize;
-pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
-pub type Digest = [u8; DIGEST_SIZE];
-
-#[derive(Zeroize, ZeroizeOnDrop)]
-pub struct Sha256<'a> {
- ctx: Pin<&'a mut Memory<ffi::SHA256_CTX>>,
-}
-
-impl<'a> Sha256<'a> {
- pub fn new(mut ctx: Pin<&'a mut Memory<ffi::SHA256_CTX>>) -> Self {
- // initialize the context
- // SAFETY: safe with whatever finds itself as memory contents
- unsafe { ffi::sha256_Init(ctx.inner()) };
- Self { ctx }
- }
-
- pub fn update(&mut self, data: &[u8]) {
- // SAFETY: safe
- unsafe { ffi::sha256_Update(self.ctx.inner(), data.as_ptr(), data.len()) };
- }
-
- pub fn memory() -> Memory<ffi::SHA256_CTX> {
- Memory::default()
- }
-
- pub fn finalize_into(mut self, out: &mut Digest) {
- // SAFETY: safe
- unsafe { ffi::sha256_Final(self.ctx.inner(), out.as_mut_ptr()) };
- }
-}
-
-pub fn digest_into(data: &[u8], out: &mut Digest) {
- init_ctx!(Sha256, ctx);
- ctx.update(data);
- ctx.finalize_into(out);
-}
-
-pub fn digest(data: &[u8]) -> Digest {
- let mut out = Digest::default();
- digest_into(data, &mut out);
- out
-}
-
-// Unpinned variant for use with noise-protocol which does not guarantee
-// pinning. If possible please use [`Sha256`] above.
-#[derive(Clone)]
-pub struct NoPinSha256 {
- ctx: ffi::SHA256_CTX,
-}
-
-impl Drop for NoPinSha256 {
- fn drop(&mut self) {
- // C implementation zeroes the state
- // SAFETY: ffi
- unsafe { ffi::sha256_Final(&mut self.ctx as *mut _, core::ptr::null_mut()) };
- }
-}
-
-impl Default for NoPinSha256 {
- fn default() -> Self {
- let mut ctx = unsafe { MaybeUninit::<ffi::SHA256_CTX>::zeroed().assume_init() };
- unsafe { ffi::sha256_Init(&mut ctx) };
- Self { ctx }
- }
-}
-
-impl NoPinSha256 {
- pub fn update(&mut self, data: &[u8]) {
- // SAFETY: ffi
- unsafe { ffi::sha256_Update(&mut self.ctx as *mut _, data.as_ptr(), data.len()) };
- }
-
- pub fn finalize_into(mut self, out: &mut Digest) {
- // SAFETY: ffi
- unsafe { ffi::sha256_Final(&mut self.ctx as *mut _, out.as_mut_ptr()) };
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::strutil::hexlify;
-
- const SHA256_EMPTY: &[u8] = b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
- const SHA256_VECTORS: &[(&[u8], &[u8])] = &[
- (b"", SHA256_EMPTY),
- (
- b"abc",
- b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
- ),
- ];
-
- fn hexdigest(data: &[u8]) -> [u8; DIGEST_SIZE * 2] {
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- let digest = digest(data);
- hexlify(&digest, &mut out_hex);
- out_hex
- }
-
- #[test]
- fn test_empty_ctx() {
- let mut out = Digest::default();
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- init_ctx!(Sha256, ctx);
- ctx.finalize_into(&mut out);
- hexlify(&out, &mut out_hex);
-
- assert_eq!(out_hex, SHA256_EMPTY);
- }
-
- #[test]
- fn test_vectors() {
- for (data, expected) in SHA256_VECTORS {
- let out_hex = hexdigest(data);
- assert_eq!(out_hex, *expected);
- }
- }
-}
diff --git a/core/embed/rust/src/crypto/sha512.rs b/core/embed/rust/src/crypto/sha512.rs
deleted file mode 100644
index d8d4fc41..00000000
--- a/core/embed/rust/src/crypto/sha512.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-use core::pin::Pin;
-
-use zeroize::{Zeroize, ZeroizeOnDrop};
-
-use super::ffi;
-use super::memory::{init_ctx, Memory};
-
-pub const BLOCK_SIZE: usize = ffi::SHA512_BLOCK_LENGTH as usize;
-pub const DIGEST_SIZE: usize = ffi::SHA512_DIGEST_LENGTH as usize;
-pub type Digest = [u8; DIGEST_SIZE];
-
-#[derive(Zeroize, ZeroizeOnDrop)]
-pub struct Sha512<'a> {
- ctx: Pin<&'a mut Memory<ffi::SHA512_CTX>>,
-}
-
-impl<'a> Sha512<'a> {
- pub fn new(ctx: Pin<&'a mut Memory<ffi::SHA512_CTX>>) -> Self {
- // initialize the context
- let mut res = Self { ctx };
- // SAFETY: safe with whatever finds itself as memory contents
- unsafe { ffi::sha512_Init(res.ctx.inner()) };
- res
- }
-
- pub fn update(&mut self, data: &[u8]) {
- // SAFETY: ffi
- unsafe { ffi::sha512_Update(self.ctx.inner(), data.as_ptr(), data.len()) };
- }
-
- pub fn memory() -> Memory<ffi::SHA512_CTX> {
- Memory::default()
- }
-
- pub fn finalize_into(mut self, out: &mut Digest) {
- // SAFETY: ffi
- unsafe { ffi::sha512_Final(self.ctx.inner(), out.as_mut_ptr()) };
- }
-}
-
-pub fn digest_into(data: &[u8], out: &mut Digest) {
- init_ctx!(Sha512, ctx);
- ctx.update(data);
- ctx.finalize_into(out);
-}
-
-pub fn digest(data: &[u8]) -> Digest {
- let mut out = [0u8; DIGEST_SIZE];
- digest_into(data, &mut out);
- out
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::strutil::hexlify;
-
- const SHA512_EMPTY: &[u8] = b"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
- const SHA512_VECTORS: &[(&[u8], &[u8])] = &[
- (b"", SHA512_EMPTY),
- (
- b"abc",
- b"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
- ),
- (
- b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
- b"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445",
- ),
- (
- b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
- b"8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909",
- ),
- ];
-
- fn hexdigest(data: &[u8]) -> [u8; DIGEST_SIZE * 2] {
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- let digest = digest(data);
- hexlify(&digest, &mut out_hex);
- out_hex
- }
-
- #[test]
- fn test_empty_ctx() {
- let mut out = [0u8; DIGEST_SIZE];
- let mut out_hex = [0u8; DIGEST_SIZE * 2];
-
- init_ctx!(Sha512, ctx);
- ctx.finalize_into(&mut out);
- hexlify(&out, &mut out_hex);
-
- assert_eq!(out_hex, SHA512_EMPTY);
- }
-
- #[test]
- fn test_vectors() {
- for (data, expected) in SHA512_VECTORS {
- let out_hex = hexdigest(data);
- assert_eq!(out_hex, *expected);
- }
- }
-}
diff --git a/core/embed/rust/src/error.rs b/core/embed/rust/src/error.rs
index ea12383f..57f99915 100644
--- a/core/embed/rust/src/error.rs
+++ b/core/embed/rust/src/error.rs
@@ -113,3 +113,19 @@ impl From<trezor_thp::Error> for Error {
}
}
}
+
+#[cfg(feature = "crypto")]
+impl From<crypto::Error> for crate::error::Error {
+ fn from(e: crypto::Error) -> Self {
+ match e {
+ crypto::Error::SignatureVerificationFailed => {
+ value_error!(c"Signature verification failed")
+ }
+ crypto::Error::InvalidEncoding => value_error!(c"Invalid key or signature encoding"),
+ crypto::Error::InvalidParams => value_error!(c"Invalid cryptographic parameters"),
+ crypto::Error::InvalidContext => value_error!(c"Invalid cryptographic context"),
+ crypto::Error::AuthenticationFailed => value_error!(c"Authentication failed"),
+ crypto::Error::InvalidSigmask => value_error!(c"Invalid sigmask"),
+ }
+ }
+}
diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs
index 94a0f574..7ca18819 100644
--- a/core/embed/rust/src/lib.rs
+++ b/core/embed/rust/src/lib.rs
@@ -23,8 +23,6 @@ mod macros;
mod align;
#[cfg(feature = "debug")]
mod coverage;
-#[cfg(feature = "crypto")]
-mod crypto;
#[cfg(feature = "debug")]
mod debug;
mod error;
diff --git a/core/embed/rust/src/thp/crypto.rs b/core/embed/rust/src/thp/crypto.rs
index 9b4d6fd8..4e6c0273 100644
--- a/core/embed/rust/src/thp/crypto.rs
+++ b/core/embed/rust/src/thp/crypto.rs
@@ -1,9 +1,8 @@
+use crypto::memory::init_ctx;
+use crypto::{aesgcm, curve25519, sha256};
use trezor_thp::channel::{Backend, Cipher, Hash, U8Array, DH};
use zeroize::{Zeroize, Zeroizing};
-use crate::crypto::memory::init_ctx;
-use crate::crypto::{aesgcm, curve25519, sha256};
-
/// Array wrapper that zeroizes on `drop()`. Can't use zeroizing directly due to
/// the orphan rule.
pub struct Sensitive<A: U8Array + Zeroize>(Zeroizing<A>);
@@ -56,7 +55,9 @@ impl DH for TrezorCryptoCurve25519 {
}
fn genkey() -> Self::Key {
- curve25519::Scalar::generate()
+ let mut bytes = [0u8; curve25519::CURVE25519_KEY_SIZE];
+ crate::trezorhal::random::bytes(&mut bytes);
+ curve25519::Scalar::from_bytes(bytes)
}
fn pubkey(privkey: &Self::Key) -> Self::Pubkey {
@@ -175,7 +176,8 @@ impl Cipher for TrezorCryptoAesGcm {
}
}
-pub type TrezorCryptoSha256 = sha256::NoPinSha256;
+#[derive(Default)]
+pub struct TrezorCryptoSha256(sha256::NoPinSha256);
impl Hash for TrezorCryptoSha256 {
fn name() -> &'static str {
@@ -186,12 +188,12 @@ impl Hash for TrezorCryptoSha256 {
type Output = Sensitive<sha256::Digest>;
fn input(&mut self, data: &[u8]) {
- self.update(data);
+ self.0.update(data);
}
fn result(&mut self) -> Self::Output {
let mut digest = sha256::Digest::default();
- self.clone().finalize_into(&mut digest);
+ self.0.clone().finalize_into(&mut digest);
Self::Output::from_slice(&digest)
}
}
diff --git a/core/embed/rust/src/translations/blob.rs b/core/embed/rust/src/translations/blob.rs
index 868fe1de..e277d6ce 100644
--- a/core/embed/rust/src/translations/blob.rs
+++ b/core/embed/rust/src/translations/blob.rs
@@ -1,11 +1,11 @@
use core::{mem, str};
+use crypto::merkle::merkle_root;
+use crypto::{cosi, ed25519, sha256};
use heapless::Vec;
use super::public_keys;
use super::translated_string::TranslatedString;
-use crate::crypto::merkle::merkle_root;
-use crate::crypto::{cosi, ed25519, sha256};
use crate::error::{value_error, Error};
use crate::io::InputStream;
diff --git a/core/embed/rust/src/translations/public_keys.rs b/core/embed/rust/src/translations/public_keys.rs
index 9d5cab6e..06369ffb 100644
--- a/core/embed/rust/src/translations/public_keys.rs
+++ b/core/embed/rust/src/translations/public_keys.rs
@@ -1,4 +1,4 @@
-use crate::crypto::ed25519;
+use crypto::ed25519;
#[cfg(feature = "dev_keys")]
pub const PUBLIC_KEYS_DEVEL: [ed25519::PublicKey; 3] = [
diff --git a/core/embed/sec/Cargo.toml b/core/embed/sec/Cargo.toml
index 3106447c..fd3a9447 100644
--- a/core/embed/sec/Cargo.toml
+++ b/core/embed/sec/Cargo.toml
@@ -10,6 +10,7 @@ xbuild.workspace = true
[dependencies]
sys.workspace = true
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -38,14 +39,14 @@ secure_mode = ["sys/secure_mode"]
backup_ram = []
bootloader_devel = []
-boot_ucb = ["sys/boot_ucb", "rtl/sphincsplus"]
+boot_ucb = ["sys/boot_ucb", "crypto/sphincsplus"]
consumption_mask = []
emulator = ["sys/emulator"]
hash_processor = []
hw_revision = []
iwdg = []
lockable_bootloader = ["sys/lockable_bootloader"]
-mcu_attestation = ["rtl/mldsa"]
+mcu_attestation = ["crypto/mldsa"]
nrf_auth = []
optiga = ["sys/i2c_bus", "time_estimate"]
optiga_testing = []
diff --git a/core/embed/supply-chain/imports.lock b/core/embed/supply-chain/imports.lock
index 1c06ecb4..1640733e 100644
--- a/core/embed/supply-chain/imports.lock
+++ b/core/embed/supply-chain/imports.lock
@@ -436,6 +436,21 @@ Still no `unsafe` anywhere.
"""
aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT"
+[[audits.google.audits.rand]]
+who = "Lukasz Anforowicz <lukasza@chromium.org>"
+criteria = "safe-to-deploy"
+version = "0.8.5"
+notes = """
+For more detailed unsafe review notes please see https://crrev.com/c/6362797
+"""
+aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT"
+
+[[audits.google.audits.rand_core]]
+who = "Android Legacy"
+criteria = "safe-to-run"
+version = "0.6.4"
+aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT"
+
[[audits.google.audits.serde]]
who = "Lukasz Anforowicz <lukasza@chromium.org>"
criteria = "safe-to-deploy"
@@ -697,6 +712,36 @@ who = "David Cook <dcook@divviup.org>"
criteria = "safe-to-deploy"
delta = "1.0.3 -> 1.0.4"
+[[audits.isrg.audits.rand]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.8.5 -> 0.9.1"
+
+[[audits.isrg.audits.rand]]
+who = "Tim Geoghegan <timg@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.9.1 -> 0.9.2"
+
+[[audits.isrg.audits.rand]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.9.2 -> 0.10.0"
+
+[[audits.isrg.audits.rand_core]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.6.4 -> 0.9.3"
+
+[[audits.isrg.audits.rand_core]]
+who = "J.C. Jones <jc@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.9.3 -> 0.9.5"
+
+[[audits.isrg.audits.rand_core]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+delta = "0.9.5 -> 0.10.0"
+
[[audits.isrg.audits.serde]]
who = "J.C. Jones <jc@divviup.org>"
criteria = "safe-to-deploy"
diff --git a/core/embed/sys/Cargo.toml b/core/embed/sys/Cargo.toml
index 088edfbb..5d11a97d 100644
--- a/core/embed/sys/Cargo.toml
+++ b/core/embed/sys/Cargo.toml
@@ -9,6 +9,7 @@ color-eyre.workspace = true
xbuild.workspace = true
[dependencies]
+crypto.workspace = true
rtl.workspace = true
models.workspace = true
@@ -39,10 +40,11 @@ applets = []
app_loading = ["applets"]
block_on_vcp = []
boot_ucb = []
-emulator = ["rtl/emulator"]
+emulator = ["rtl/emulator", "crypto/emulator"]
i2c_bus = []
ipc = []
lockable_bootloader = []
+production = ["rtl/production", "crypto/production"]
pvd = []
rtc = []
trustzone = []
@@ -53,7 +55,7 @@ dbg_console_system_view = ["dbg_console", "system_view"]
sdram = []
sdram_stm32f429i_disc1 = []
system_view = []
-production = ["rtl/production"]
+
test = [
"mcu_stm32u5g",
diff --git a/core/embed/sys/build.rs b/core/embed/sys/build.rs
index 7febdf2d..bfdafe79 100644
--- a/core/embed/sys/build.rs
+++ b/core/embed/sys/build.rs
@@ -38,6 +38,7 @@ mod trustzone;
fn main() -> Result<()> {
xbuild::build(|lib| {
lib.import_lib("rtl")?;
+ lib.import_lib("crypto")?;
lib.add_includes([
"syscall/inc", // temporary hack
diff --git a/core/embed/upymod/Cargo.toml b/core/embed/upymod/Cargo.toml
index e4640162..f3c3d1fd 100644
--- a/core/embed/upymod/Cargo.toml
+++ b/core/embed/upymod/Cargo.toml
@@ -10,6 +10,7 @@ xbuild.workspace = true
[dependencies]
models.workspace = true
+crypto.workspace = true
rtl.workspace = true
sec.workspace = true
io.workspace = true
@@ -62,7 +63,7 @@ thp = []
touch = ["io/touch"]
touch_wakeup = ["io/touch_wakeup"]
tropic = ["sec/tropic"]
-universal_fw = ["rtl/universal_fw"]
+universal_fw = ["crypto/universal_fw"]
# --------------------------------------------------------------------------
# Automatically derived features (do not enable from outside)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.