What changed, and why it matters
This commit adds new Rust code that lets other parts of the Trezor firmware call existing C-language ECDSA cryptographic functions (signature verification and public-key recovery). It is a feature addition that exposes already-trusted crypto code through a thin Rust wrapper. There is no indication in the commit that it fixes a bug or vulnerability.
No immediate security action is required. As part of normal review, ensure the documented safety invariants for the unsafe FFI calls are complete and that callers cannot bypass the length checks. Consider adding unit tests for the new bindings.
Security signals we found
New FFI bindings to cryptographic primitives
Use of unsafe blocks with documented safety invariants
Public-key format validation (compressed/uncompressed SEC1) before FFI call
Evidence from the diff
The change introduces a new Rust module core/embed/crypto/src/ecdsa.rs that provides safe bindings to trezor-crypto’s C ECDSA routines: ecdsa_verify_digest and ecdsa_recover_pub_from_sig. It defines constants and types for digests, signatures, and public keys, an enum for secp256k1/nist256p1 curves, a SEC1 public-key encoding check, and two public Rust functions. The module is registered in lib.rs. The wrappers include length and encoding validation before calling the unsafe FFI functions.
Changed components
core/embed/crypto/src/ecdsa.rscore/embed/crypto/src/lib.rsInspect captured patch +106 / −0
### core/embed/crypto/src/ecdsa.rs
@@ -0,0 +1,105 @@
+use super::{Error, ffi};
+
+pub const ECDSA_DIGEST_SIZE: usize = ffi::ECDSA_SCALAR_SIZE as usize;
+pub const ECDSA_SIGNATURE_SIZE: usize = ffi::ECDSA_RAW_SIGNATURE_SIZE as usize;
+pub const ECDSA_PUBLIC_KEY_SIZE: usize = ffi::ECDSA_PUBLIC_KEY_SIZE as usize;
+pub const ECDSA_PUBLIC_KEY_COMPRESSED_SIZE: usize = ffi::ECDSA_PUBLIC_KEY_COMPRESSED_SIZE as usize;
+
+pub type EcdsaDigest = [u8; ECDSA_DIGEST_SIZE];
+pub type EcdsaSignature = [u8; ECDSA_SIGNATURE_SIZE];
+pub type EcdsaPublicKey = [u8; ECDSA_PUBLIC_KEY_SIZE];
+
+/// Supported Weierstrass curves for ECDSA.
+pub enum Curve {
+ Secp256k1,
+ Nist256p1,
+}
+
+impl Curve {
+ fn to_ffi_curve(&self) -> *const ffi::ecdsa_curve {
+ match self {
+ // SAFETY: `secp256k1` / `nist256p1` are immutable C statics provided by
+ // trezor-crypto and live for the whole program.
+ Curve::Secp256k1 => unsafe { &ffi::secp256k1 },
+ Curve::Nist256p1 => unsafe { &ffi::nist256p1 },
+ }
+ }
+}
+
+/// Check that `pubkey` is a compressed (`0x02`/`0x03`) or uncompressed (`0x04`)
+/// SEC1 encoding of the expected length.
+fn verify_pubkey_slice(pubkey: &[u8]) -> Result<(), Error> {
+ if pubkey.is_empty() {
+ return Err(Error::InvalidEncoding);
+ }
+ match pubkey[0] {
+ 0x02 | 0x03 if pubkey.len() == ECDSA_PUBLIC_KEY_COMPRESSED_SIZE => Ok(()),
+ 0x04 if pubkey.len() == ECDSA_PUBLIC_KEY_SIZE => Ok(()),
+ _ => Err(Error::InvalidEncoding),
+ }
+}
+
+/// Verify an ECDSA signature of `digest` against `public_key` on `curve`.
+///
+/// `public_key` may be compressed or uncompressed SEC1 encoding.
+pub fn verify_digest(
+ curve: Curve,
+ public_key: &[u8],
+ signature: &EcdsaSignature,
+ digest: &EcdsaDigest,
+) -> Result<(), Error> {
+ verify_pubkey_slice(public_key)?;
+ let ffi_curve = curve.to_ffi_curve();
+ // SAFETY:
+ // * ffi_curve is one of the supported builtin curves
+ // * public_key is either a compressed or uncompressed public key of correct
+ // size
+ // * signature has correct length
+ // * digest has correct length
+ let result = unsafe {
+ ffi::ecdsa_verify_digest(
+ ffi_curve,
+ public_key.as_ptr(),
+ signature.as_ptr(),
+ digest.as_ptr(),
+ )
+ };
+ if result == 0 {
+ Ok(())
+ } else {
+ Err(Error::SignatureVerificationFailed)
+ }
+}
+
+/// Recover the uncompressed public key from an ECDSA signature of `digest`.
+///
+/// `recid` is the recovery id (`0..=3`) identifying which of the candidate
+/// points was used.
+pub fn verify_recover(
+ curve: Curve,
+ signature: &EcdsaSignature,
+ recid: u8,
+ digest: &EcdsaDigest,
+) -> Result<EcdsaPublicKey, Error> {
+ let ffi_curve = curve.to_ffi_curve();
+ let mut public_key = [0u8; ECDSA_PUBLIC_KEY_SIZE];
+ // SAFETY:
+ // * ffi_curve is one of the supported builtin curves
+ // * signature has correct length
+ // * digest has correct length
+ // * public_key is a pointer to a valid sized buffer
+ let result = unsafe {
+ ffi::ecdsa_recover_pub_from_sig(
+ ffi_curve,
+ public_key.as_mut_ptr(),
+ signature.as_ptr(),
+ digest.as_ptr(),
+ recid as cty::c_int,
+ )
+ };
+ if result == 0 {
+ Ok(public_key)
+ } else {
+ Err(Error::SignatureVerificationFailed)
+ }
+}
### core/embed/crypto/src/lib.rs
@@ -7,6 +7,7 @@ pub mod aesgcm;
pub mod cosi;
pub mod crc32;
pub mod curve25519;
+pub mod ecdsa;
pub mod ed25519;
mod ffi;
pub mod hmac;Why this scored 12/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.