refactor(core): rust bindings for trezor-crypto primitives needed by THP
What changed, and why it matters
This commit adds Rust wrappers around existing low-level cryptographic functions in the Trezor firmware so they can be used by a new feature called THP. It does not appear to fix a known bug or vulnerability. The changes are mostly a refactor: they expose AES-GCM, Curve25519, HMAC-SHA256, SHA-512, CRC32, and a random-byte helper to Rust code, and add unit tests. There is no direct evidence in the commit that this resolves a security issue, but any new crypto binding introduces a small chance of misuse or memory-safety mistakes.
Treat as a routine refactor. Review the unsafe Memory<T>::inner() and Pin usage for soundness, verify that zeroize covers all sensitive C context fields, and ensure AES-GCM tag verification is enforced by callers. Monitor follow-up THP commits that consume these bindings to confirm they are used securely.
Security signals we found
New unsafe FFI bindings to cryptographic primitives (AES-GCM, Curve25519, HMAC-SHA256, SHA-512)
Generic Memory<T> helper uses MaybeUninit::zeroed().assume_init() and zeroize_flat_type on C structs
Pin-based API intended to prevent context structs from moving in memory
No changelog entry; commit is labeled refactor for THP
Adds unit tests with known test vectors, suggesting correctness validation rather than vulnerability fix
Evidence from the diff
The commit refactors the Rust crypto layer in core/embed/rust. It introduces a generic pinned Memory
Changed components
core/embed/rust/Cargo.lockcore/embed/rust/Cargo.tomlcore/embed/rust/build.rscore/embed/rust/crypto.hcore/embed/rust/src/crypto/aesgcm.rscore/embed/rust/src/crypto/crc32.rscore/embed/rust/src/crypto/curve25519.rscore/embed/rust/src/crypto/hmac.rscore/embed/rust/src/crypto/memory.rscore/embed/rust/src/crypto/mod.rscore/embed/rust/src/crypto/sha256.rscore/embed/rust/src/crypto/sha512.rscore/embed/rust/src/trezorhal/random.rsInspect captured patch +1227 / −27
diff --git a/core/embed/rust/Cargo.lock b/core/embed/rust/Cargo.lock
index 1cb96bf7..241be78f 100644
--- a/core/embed/rust/Cargo.lock
+++ b/core/embed/rust/Cargo.lock
@@ -123,6 +123,12 @@ dependencies = [
"ufmt-write",
]
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
[[package]]
name = "itertools"
version = "0.13.0"
@@ -347,6 +353,7 @@ dependencies = [
"easer",
"glob",
"heapless",
+ "hex",
"minicbor",
"num-derive",
"num-traits",
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index 4618275f..71971ac3 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -174,4 +174,5 @@ optional = true
version = "0.3.0"
[dev-dependencies]
+hex = "0.4.3"
serde_json = "1.0.96"
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 85c5f576..45ab8435 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -398,6 +398,7 @@ fn generate_trezorhal_bindings() {
.allowlist_var("SLIP39_WORDLIST")
.allowlist_var("SLIP39_WORD_COUNT")
// random
+ .allowlist_function("random_buffer")
.allowlist_function("random_uniform")
// rgb led
.allowlist_type("rgb_led_effect_type_t")
@@ -502,18 +503,45 @@ fn generate_crypto_bindings() {
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_type("SHA256_CTX")
.no_copy("SHA256_CTX")
.allowlist_function("sha256_Init")
.allowlist_function("sha256_Update")
- .allowlist_function("sha256_Final");
+ .allowlist_function("sha256_Final")
+ // sha512
+ .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");
// Write the bindings to a file in the OUR_DIR.
bindings
diff --git a/core/embed/rust/crypto.h b/core/embed/rust/crypto.h
index ac81987c..790e13d7 100644
--- a/core/embed/rust/crypto.h
+++ b/core/embed/rust/crypto.h
@@ -1,2 +1,5 @@
+#include "aes/aesgcm.h"
#include "ed25519-donna/ed25519.h"
+#include "elligator2.h"
+#include "hmac.h"
#include "sha2.h"
diff --git a/core/embed/rust/src/crypto/aesgcm.rs b/core/embed/rust/src/crypto/aesgcm.rs
new file mode 100644
index 00000000..e82c2e91
--- /dev/null
+++ b/core/embed/rust/src/crypto/aesgcm.rs
@@ -0,0 +1,522 @@
+use core::pin::Pin;
+
+use zeroize::Zeroize;
+
+use super::{ffi, memory::Memory, 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,
+ Encrypting,
+ Decrypting,
+ Finished,
+ Failed,
+}
+
+pub struct AesGcm<'a> {
+ ctx: Pin<&'a mut Memory<ffi::gcm_ctx>>,
+ state: State,
+}
+
+impl<'a> AesGcm<'a> {
+ pub 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)
+ }
+
+ pub 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;
+ }
+
+ 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.check_state(&[State::Init, State::Encrypting])?;
+ self.state = State::Encrypting;
+
+ let res = unsafe {
+ ffi::gcm_encrypt(
+ data.as_mut_ptr(),
+ data.len() as cty::c_ulong,
+ self.ctx.inner(),
+ )
+ };
+ ensure!(res == RETURN_GOOD, "gcm_encrypt");
+ Ok(())
+ }
+
+ 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.check_state(&[State::Init, State::Decrypting])?;
+ self.state = State::Decrypting;
+
+ // SAFETY: ffi
+ let res = unsafe {
+ ffi::gcm_decrypt(
+ data.as_mut_ptr(),
+ data.len() as cty::c_ulong,
+ self.ctx.inner(),
+ )
+ };
+ ensure!(res == RETURN_GOOD, "gcm_decrypt");
+ Ok(())
+ }
+
+ pub fn auth(&mut self, data: &[u8]) -> Result<(), Error> {
+ self.check_state(&[State::Init, State::Encrypting, State::Decrypting])?;
+
+ // 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(())
+ }
+
+ pub fn finish(&mut self) -> Result<Tag, Error> {
+ self.check_state(&[State::Init, State::Encrypting, State::Decrypting])?;
+ 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(())
+ }
+
+ pub fn memory() -> Memory<ffi::gcm_ctx> {
+ Memory::default()
+ }
+}
+
+impl Drop for AesGcm<'_> {
+ fn drop(&mut self) {
+ self.ctx.zeroize();
+ }
+}
+
+#[allow(unused_macros)]
+macro_rules! init_ctx {
+ ($name:ident, $key:expr, $iv:expr) => {
+ // assign the backing memory to $name...
+ let mut $name = crate::crypto::aesgcm::AesGcm::memory();
+ // ... then make it inaccessible by overwriting the binding, and pin it
+ #[allow(unused_mut)]
+ let mut $name = unsafe {
+ crate::crypto::aesgcm::AesGcm::new(core::pin::Pin::new_unchecked(&mut $name), $key, $iv)
+ };
+ };
+}
+
+#[cfg(test)]
+mod test {
+ 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>) {
+ 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();
+ (key, iv, aad, pt, ct)
+ }
+ }
+
+ // first 10 vectors from https://github.com/BrianGladman/modes/blob/master/testvals/gcm.1
+ const AES_GCM_VECTORS: &[Vector] = &[
+ 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]
+ fn test_vectors() {
+ for v in AES_GCM_VECTORS {
+ let (key, iv, aad, plaintext, ciphertext) = v.decoded();
+
+ init_ctx!(ctx_enc, &key, &iv);
+ let mut ctx_enc = ctx_enc.unwrap();
+ init_ctx!(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);
+ let result = ctx_dec.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+ }
+ }
+
+ #[test]
+ fn test_state() {
+ init_ctx!(ctx, &[0u8; 16], b"1");
+ let mut ctx = ctx.unwrap();
+
+ // ok: empty string tag
+ ctx.finish().unwrap();
+
+ // ok: any single operation
+ // not ok: after reset
+ let mut dest = [0u8; 16];
+ ctx.reset(b"2");
+ ctx.encrypt(b"asdf", &mut dest).unwrap();
+ ctx.finish().unwrap();
+ assert!(ctx.encrypt(b"asdf", &mut dest).is_err());
+
+ ctx.reset(b"3");
+ ctx.decrypt(b"fdsa", &mut dest).unwrap();
+ ctx.finish().unwrap();
+ assert!(ctx.decrypt(b"fdsa", &mut dest).is_err());
+
+ ctx.reset(b"5");
+ ctx.auth(b"foobar").unwrap();
+ ctx.finish().unwrap();
+ assert!(ctx.auth(b"foobar").is_err());
+
+ // not ok: mixing encrypt and decrypt
+ ctx.reset(b"6");
+ ctx.encrypt(b"asdf", &mut dest).unwrap();
+ ctx.encrypt_in_place(&mut dest).unwrap();
+ assert!(ctx.decrypt(b"fdsa", &mut dest).is_err());
+
+ ctx.reset(b"7");
+ ctx.decrypt_in_place(&mut dest).unwrap();
+ ctx.auth(b"foobar").unwrap();
+ assert!(ctx.encrypt(b"fdsa", &mut dest).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) = v.decoded();
+
+ // Test encryption.
+ init_ctx!(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.
+ ctx.reset(&iv);
+ if !aad.is_empty() {
+ ctx.auth(&aad).unwrap();
+ }
+ let result = ctx.decrypt(&ct, &mut buffer).unwrap();
+ assert_eq!(hex::encode(result), v.plaintext);
+
+ let result = ctx.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+ }
+ }
+
+ #[test]
+ fn test_gcm_in_place() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct) = v.decoded();
+
+ // Test encryption.
+ init_ctx!(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.
+ ctx.reset(&iv);
+ 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);
+
+ let result = ctx.finish().unwrap();
+ assert_eq!(hex::encode(result), v.tag);
+ }
+ }
+
+ #[test]
+ fn test_gcm_chunks() {
+ for v in NIST_VECTORS {
+ let (key, iv, aad, pt, ct) = v.decoded();
+ let chunk_len = pt.len() / 3;
+ let mut buffer = vec![0; pt.len()];
+
+ init_ctx!(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);
+ assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
+
+ buffer = vec![0; pt.len()];
+ ctx.reset(&iv);
+ 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) = v.decoded();
+ let chunk_len = pt.len() / 3;
+
+ let mut buffer = ct;
+ init_ctx!(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);
+ assert_eq!(hex::encode(ctx.finish().unwrap()), v.tag);
+
+ let mut buffer = pt;
+ ctx.reset(&iv);
+ 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/crc32.rs b/core/embed/rust/src/crypto/crc32.rs
new file mode 100644
index 00000000..74ada0b8
--- /dev/null
+++ b/core/embed/rust/src/crypto/crc32.rs
@@ -0,0 +1,79 @@
+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 ^= *b as u32;
+ 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 crate::strutil::hexlify;
+
+ use super::*;
+
+ 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
new file mode 100644
index 00000000..7185f927
--- /dev/null
+++ b/core/embed/rust/src/crypto/curve25519.rs
@@ -0,0 +1,212 @@
+use zeroize::Zeroize;
+
+use super::ffi;
+
+pub struct Point {
+ bytes: [u8; 32],
+}
+
+impl Drop for Point {
+ fn drop(&mut self) {
+ self.bytes.zeroize()
+ }
+}
+
+pub struct Scalar {
+ bytes: [u8; 32],
+}
+
+impl Drop for Scalar {
+ fn drop(&mut self) {
+ self.bytes.zeroize()
+ }
+}
+
+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
+ }
+
+ #[cfg(feature = "test")]
+ 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/hmac.rs b/core/embed/rust/src/crypto/hmac.rs
new file mode 100644
index 00000000..ffdae50b
--- /dev/null
+++ b/core/embed/rust/src/crypto/hmac.rs
@@ -0,0 +1,174 @@
+use core::pin::Pin;
+
+use zeroize::Zeroize as _;
+
+use super::{ffi, memory::Memory};
+
+pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
+pub type Digest = [u8; DIGEST_SIZE];
+
+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()) };
+ }
+}
+
+impl Drop for HmacSha256<'_> {
+ fn drop(&mut self) {
+ self.ctx.zeroize();
+ }
+}
+
+macro_rules! init_ctx {
+ ($name:ident, $key:expr) => {
+ // assign the backing memory to $name...
+ let mut $name = crate::crypto::hmac::HmacSha256::memory();
+ // ... then make it inaccessible by overwriting the binding, and pin it
+ #[allow(unused_mut)]
+ let mut $name = unsafe {
+ crate::crypto::hmac::HmacSha256::new(core::pin::Pin::new_unchecked(&mut $name), $key)
+ };
+ };
+}
+
+pub(crate) use init_ctx;
+
+pub fn digest_into(key: &[u8], data: &[u8], out: &mut Digest) {
+ init_ctx!(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 crate::strutil::hexlify;
+
+ use super::*;
+
+ 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!(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!(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!(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
new file mode 100644
index 00000000..a6f151b8
--- /dev/null
+++ b/core/embed/rust/src/crypto/memory.rs
@@ -0,0 +1,57 @@
+use core::{marker::PhantomPinned, mem::MaybeUninit, pin::Pin};
+
+use zeroize::{zeroize_flat_type, Zeroize};
+
+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();
+ }
+ }
+}
diff --git a/core/embed/rust/src/crypto/mod.rs b/core/embed/rust/src/crypto/mod.rs
index 9b753f96..282d6fcb 100644
--- a/core/embed/rust/src/crypto/mod.rs
+++ b/core/embed/rust/src/crypto/mod.rs
@@ -1,11 +1,18 @@
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;
+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,
@@ -13,6 +20,8 @@ pub enum Error {
InvalidEncoding,
// Provided parameters are not accepted (e.g., signature threshold out of bounds)
InvalidParams,
+ // State precondition check failed (possibly raised by C implementation)
+ InvalidContext,
}
impl From<Error> for crate::error::Error {
@@ -21,6 +30,7 @@ impl From<Error> for crate::error::Error {
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"),
}
}
}
diff --git a/core/embed/rust/src/crypto/sha256.rs b/core/embed/rust/src/crypto/sha256.rs
index fc1c4c05..36037e96 100644
--- a/core/embed/rust/src/crypto/sha256.rs
+++ b/core/embed/rust/src/crypto/sha256.rs
@@ -1,53 +1,36 @@
-use core::{mem::MaybeUninit, pin::Pin};
+use core::pin::Pin;
-use zeroize::{DefaultIsZeroes, Zeroize as _};
+use zeroize::Zeroize as _;
-use super::ffi;
-
-type Memory = ffi::SHA256_CTX;
-
-impl Default for Memory {
- fn default() -> Self {
- // SAFETY: a zeroed block of memory is a valid SHA256_CTX
- unsafe { MaybeUninit::<Memory>::zeroed().assume_init() }
- }
-}
-
-impl DefaultIsZeroes for Memory {}
+use super::{ffi, memory::Memory};
pub const DIGEST_SIZE: usize = ffi::SHA256_DIGEST_LENGTH as usize;
pub type Digest = [u8; DIGEST_SIZE];
pub struct Sha256<'a> {
- ctx: Pin<&'a mut Memory>,
+ ctx: Pin<&'a mut Memory<ffi::SHA256_CTX>>,
}
impl<'a> Sha256<'a> {
- pub fn new(mut ctx: Pin<&'a mut Memory>) -> Self {
+ 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.as_mut().get_unchecked_mut()) };
+ unsafe { ffi::sha256_Init(ctx.inner()) };
Self { ctx }
}
pub fn update(&mut self, data: &[u8]) {
// SAFETY: safe
- unsafe {
- ffi::sha256_Update(
- self.ctx.as_mut().get_unchecked_mut(),
- data.as_ptr(),
- data.len(),
- )
- };
+ unsafe { ffi::sha256_Update(self.ctx.inner(), data.as_ptr(), data.len()) };
}
- pub fn memory() -> Memory {
+ 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.as_mut().get_unchecked_mut(), out.as_mut_ptr()) };
+ unsafe { ffi::sha256_Final(self.ctx.inner(), out.as_mut_ptr()) };
}
}
diff --git a/core/embed/rust/src/crypto/sha512.rs b/core/embed/rust/src/crypto/sha512.rs
new file mode 100644
index 00000000..b7d773cd
--- /dev/null
+++ b/core/embed/rust/src/crypto/sha512.rs
@@ -0,0 +1,120 @@
+use core::pin::Pin;
+
+use super::{ffi, memory::Memory};
+
+use zeroize::Zeroize as _;
+
+pub const DIGEST_SIZE: usize = ffi::SHA512_DIGEST_LENGTH as usize;
+pub type Digest = [u8; DIGEST_SIZE];
+
+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()) };
+ }
+}
+
+impl Drop for Sha512<'_> {
+ fn drop(&mut self) {
+ self.ctx.zeroize();
+ }
+}
+
+macro_rules! init_ctx {
+ ($name:ident) => {
+ // assign the backing memory to $name...
+ let mut $name = crate::crypto::sha512::Sha512::memory();
+ // ... then make it inaccessible by overwriting the binding, and pin it
+ #[allow(unused_mut)]
+ let mut $name = unsafe {
+ crate::crypto::sha512::Sha512::new(core::pin::Pin::new_unchecked(&mut $name))
+ };
+ };
+}
+
+pub(crate) use init_ctx;
+
+pub fn digest_into(data: &[u8], out: &mut Digest) {
+ init_ctx!(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 crate::strutil::hexlify;
+
+ use super::*;
+
+ 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!(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/trezorhal/random.rs b/core/embed/rust/src/trezorhal/random.rs
index 40ef3a16..a20b1d4a 100644
--- a/core/embed/rust/src/trezorhal/random.rs
+++ b/core/embed/rust/src/trezorhal/random.rs
@@ -28,6 +28,10 @@ pub fn uniform_between_except(min: u32, max: u32, except: u32) -> u32 {
}
}
+pub fn bytes(dest: &mut [u8]) {
+ unsafe { super::ffi::random_buffer(dest.as_mut_ptr(), dest.len()) }
+}
+
#[cfg(test)]
mod tests {
use super::*;
Why this scored 20/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.