refactor(core/rust/crypto: introduce testing harness and zkp support
What changed, and why it matters
This commit reorganizes how a test-only random number generator and certain Bitcoin-related cryptographic error handlers are wired up in the Trezor firmware's Rust crypto code. It moves an insecure PRNG used only in unit tests into its own module and exports it under a detectable name so test builds can find it. It also moves default error callbacks for the secp256k1-zkp library from the Python module layer into the core crypto crate so all users of the crate share them. The change removes a Python ValueError raise for illegal library arguments, replacing it with a system error shutdown. There is no direct evidence in the commit of a vulnerability being fixed; it reads as a refactoring to support testing and a new cryptographic feature (zero-knowledge proofs).
Treat as a routine refactoring commit. Reviewers should confirm that the test-only random_buffer symbol is not linked into production firmware builds and that the new system_exit_error path for secp256k1-zkp illegal callbacks does not introduce unexpected denial-of-service behavior for malformed but attacker-supplied inputs. No immediate security patch or incident response is indicated by the supplied materials.
Security signals we found
Insecure PRNG is explicitly test-only and relocated to a dedicated testutil module
random_buffer extern "C" export is test-only and XORed with a detectable string <PRNG-Rust-Tests>
secp256k1-zkp default error/illegal callbacks moved from Python module layer to core crypto crate
Illegal-argument callback behavior changed from Python ValueError to system_exit_error shutdown
No production randomness path is modified in the diff
Evidence from the diff
The commit refactors core/embed/crypto to support a Rust test harness and secp256k1-zkp. Key changes: (1) test-only rand/SmallRng is moved from curve25519.rs into testutil.rs and exposed via a no_mangle extern “C” random_buffer that XORs output with the identifier
Changed components
core/embed/crypto/build.rscore/embed/crypto/src/curve25519.rscore/embed/crypto/src/fault_handler.ccore/embed/crypto/src/lib.rscore/embed/crypto/src/testutil.rscore/embed/upymod/modtrezorcrypto/modtrezorcrypto.cInspect captured patch +75 / −32
### core/embed/crypto/build.rs
@@ -147,15 +147,20 @@ fn add_crypto_base(lib: &mut CLibrary, common_attrs: &CompileAttrs) -> Result<()
}
lib.add_rust_bindings(|builder| {
- Ok(builder
+ let mut builder = builder
.header(format!("{CRYPTO_PATH}/ecdsa.h"))
.header(format!("{CRYPTO_PATH}/ed25519-donna/ed25519.h"))
.header(format!("{CRYPTO_PATH}/elligator2.h"))
.header(format!("{CRYPTO_PATH}/hmac.h"))
.header(format!("{CRYPTO_PATH}/nist256p1.h"))
.header(format!("{CRYPTO_PATH}/secp256k1.h"))
.header(format!("{CRYPTO_PATH}/sha2.h"))
- .header(format!("{CRYPTO_PATH}/sha3.h"))
+ .header(format!("{CRYPTO_PATH}/sha3.h"));
+
+ if cfg!(feature = "secp256k1_zkp") {
+ builder = builder.header(format!("{CRYPTO_PATH}/zkp_context.h"));
+ }
+ Ok(builder
// curve25519
.allowlist_function("curve25519_scalarmult")
.allowlist_function("curve25519_scalarmult_basepoint")
@@ -170,6 +175,7 @@ fn add_crypto_base(lib: &mut CLibrary, common_attrs: &CompileAttrs) -> Result<()
.allowlist_var("nist256p1")
.allowlist_function("ecdsa_verify_digest")
.allowlist_function("ecdsa_recover_pub_from_sig")
+ .allowlist_function("zkp_context_init")
// ed25519
.allowlist_type("ed25519_signature")
.allowlist_type("ed25519_public_key")
### core/embed/crypto/src/curve25519.rs
@@ -142,24 +142,8 @@ impl Point {
#[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);
- }
+ use crate::testutil::fill_random_bytes;
fn generate_scalar() -> Scalar {
let mut bytes = [0u8; CURVE25519_KEY_SIZE];
### core/embed/crypto/src/fault_handler.c
@@ -26,3 +26,15 @@
void tc_fault_handler(const char *message) {
system_exit_error(NULL, message, NULL);
}
+
+#ifdef USE_SECP256K1_ZKP
+void secp256k1_default_illegal_callback_fn(const char *str, void *data) {
+ (void)data;
+ system_exit_error(NULL, str, NULL);
+}
+
+void secp256k1_default_error_callback_fn(const char *str, void *data) {
+ (void)data;
+ system_exit_error(NULL, str, NULL);
+}
+#endif
### core/embed/crypto/src/lib.rs
@@ -1,4 +1,7 @@
#![cfg_attr(not(test), no_std)]
+#![feature(custom_test_frameworks)]
+#![reexport_test_harness_main = "test_main"]
+#![no_main]
use core::hint::black_box;
@@ -17,6 +20,9 @@ pub mod sha256;
pub mod sha3;
pub mod sha512;
+#[cfg(test)]
+mod testutil;
+
/// Error returned by cryptographic operations in this crate.
#[cfg_attr(feature = "test", derive(core::fmt::Debug))]
pub enum Error {
### core/embed/crypto/src/testutil.rs
@@ -0,0 +1,48 @@
+use std::sync::{LazyLock, Mutex};
+
+use rand::prelude::*;
+use rand::rngs::SmallRng;
+use rtl::CSliceMut;
+
+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))
+});
+
+pub fn fill_random_bytes(bytes: &mut [u8]) {
+ const IDENTIFIER_MASK: &[u8] = b"<PRNG-Rust-Tests>";
+
+ INSECURE_RNG.lock().unwrap().fill_bytes(bytes);
+
+ for i in 0..bytes.len() {
+ bytes[i] ^= IDENTIFIER_MASK[i % IDENTIFIER_MASK.len()];
+ }
+}
+
+#[unsafe(no_mangle)]
+unsafe extern "C" fn random_buffer(buf: *mut u8, len: usize) {
+ // SAFETY: caller must pass a valid pointer+len
+ let mut slice = unsafe { CSliceMut::from_ptr_and_len(buf, len) };
+ fill_random_bytes(slice.as_slice_mut());
+}
+
+#[unsafe(no_mangle)]
+pub fn main() -> i32 {
+ // Initialize the ZKP context
+ #[cfg(feature = "secp256k1_zkp")]
+ unsafe {
+ crate::ffi::zkp_context_init()
+ };
+
+ // Call the Rust test harness main function
+ // The function panics if any test fails.
+ // Asserting that it returns () to ensure that if a future Rust version
+ // changes the signature and behavior, we'll be notified.
+ assert_eq!(crate::test_main(), ());
+
+ // Return 0 to indicate success
+ 0
+}
### core/embed/upymod/modtrezorcrypto/modtrezorcrypto.c
@@ -167,17 +167,4 @@ const mp_obj_module_t mp_module_trezorcrypto = {
MP_REGISTER_MODULE(MP_QSTR_trezorcrypto, mp_module_trezorcrypto);
-#ifdef USE_SECP256K1_ZKP
-void secp256k1_default_illegal_callback_fn(const char *str, void *data) {
- (void)data;
- mp_raise_ValueError((mp_rom_error_text_t)str);
- return;
-}
-
-void secp256k1_default_error_callback_fn(const char *str, void *data) {
- (void)data;
- error_shutdown(str);
-}
-#endif
-
#endif // MICROPY_PY_TREZORCRYPTOWhy this scored 18/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.