mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 14:28:07 +00:00
feat(core/rust): add trezorcrypto bindings
for now, we use sha256 and a little of ed25519 for CoSi purposes also add the Merkle root algorithm
This commit is contained in:
parent
76296ad417
commit
3b88116bba
7
core/embed/rust/Cargo.lock
generated
7
core/embed/rust/Cargo.lock
generated
@ -327,6 +327,7 @@ dependencies = [
|
||||
"qrcodegen",
|
||||
"serde_json",
|
||||
"trezor-tjpgdec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -356,3 +357,9 @@ name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
|
@ -8,6 +8,7 @@ build = "build.rs"
|
||||
[features]
|
||||
default = ["model_tt"]
|
||||
bitcoin_only = []
|
||||
crypto = ["zeroize"]
|
||||
model_tt = ["jpeg"]
|
||||
model_tr = []
|
||||
micropython = []
|
||||
@ -32,10 +33,11 @@ rgb_led = []
|
||||
backlight = []
|
||||
usb = []
|
||||
optiga = []
|
||||
translations = []
|
||||
translations = ["crypto"]
|
||||
test = [
|
||||
"button",
|
||||
"cc",
|
||||
"crypto",
|
||||
"debug",
|
||||
"glob",
|
||||
"micropython",
|
||||
@ -74,6 +76,7 @@ debug = 2
|
||||
[dependencies]
|
||||
qrcodegen = { version = "1.8.0", path = "../../vendor/QR-Code-generator/rust-no-heap" }
|
||||
trezor-tjpgdec = { version = "0.1.0", path = "../../../rust/trezor-tjpgdec" }
|
||||
zeroize = { version = "1.7.0", default-features = false, optional = true }
|
||||
|
||||
# Runtime dependencies
|
||||
|
||||
|
@ -8,6 +8,8 @@ fn main() {
|
||||
#[cfg(feature = "micropython")]
|
||||
generate_micropython_bindings();
|
||||
generate_trezorhal_bindings();
|
||||
#[cfg(feature = "crypto")]
|
||||
generate_crypto_bindings();
|
||||
#[cfg(feature = "test")]
|
||||
link_core_objects();
|
||||
}
|
||||
@ -390,6 +392,37 @@ fn generate_trezorhal_bindings() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn generate_crypto_bindings() {
|
||||
let out_path = 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")
|
||||
// ed25519
|
||||
.allowlist_type("ed25519_signature")
|
||||
.allowlist_type("ed25519_public_key")
|
||||
.allowlist_function("ed25519_cosi_combine_publickeys")
|
||||
// incorrect signature from bindgen, see crypto::ed25519:ffi_override
|
||||
//.allowlist_function("ed25519_sign_open")
|
||||
// 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");
|
||||
|
||||
// Write the bindings to a file in the OUR_DIR.
|
||||
bindings
|
||||
.clang_arg("-fgnuc-version=0") // avoid weirdness with ed25519.h CONST definition
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file(PathBuf::from(out_path).join("crypto.rs"))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn is_firmware() -> bool {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
target.starts_with("thumbv7")
|
||||
|
2
core/embed/rust/crypto.h
Normal file
2
core/embed/rust/crypto.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "ed25519-donna/ed25519.h"
|
||||
#include "sha2.h"
|
63
core/embed/rust/src/crypto/cosi.rs
Normal file
63
core/embed/rust/src/crypto/cosi.rs
Normal file
@ -0,0 +1,63 @@
|
||||
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)
|
||||
}
|
||||
}
|
36
core/embed/rust/src/crypto/ed25519.rs
Normal file
36
core/embed/rust/src/crypto/ed25519.rs
Normal file
@ -0,0 +1,36 @@
|
||||
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>();
|
||||
|
||||
mod ffi_override {
|
||||
// bindgen incorrectly generates pk and RS as *mut instead of *const
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2684
|
||||
extern "C" {
|
||||
pub fn ed25519_sign_open(
|
||||
m: *const cty::c_uchar,
|
||||
mlen: usize,
|
||||
pk: *const cty::c_uchar,
|
||||
RS: *const cty::c_uchar,
|
||||
) -> cty::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(message: &[u8], public_key: &PublicKey, signature: &Signature) -> Result<(), Error> {
|
||||
let res = unsafe {
|
||||
ffi_override::ed25519_sign_open(
|
||||
message.as_ptr(),
|
||||
message.len(),
|
||||
public_key.as_ptr(),
|
||||
signature.as_ptr(),
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::SignatureVerificationFailed)
|
||||
}
|
||||
}
|
3
core/embed/rust/src/crypto/ffi.rs
Normal file
3
core/embed/rust/src/crypto/ffi.rs
Normal file
@ -0,0 +1,3 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/crypto.rs"));
|
30
core/embed/rust/src/crypto/merkle.rs
Normal file
30
core/embed/rust/src/crypto/merkle.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use super::sha256;
|
||||
|
||||
/// Calculate a Merkle root based on a leaf element and a proof of inclusion.
|
||||
///
|
||||
/// Expects the Merkle tree format specified in `ethereum-definitions.md`.
|
||||
pub fn merkle_root(elem: &[u8], proof: &[sha256::Digest]) -> sha256::Digest {
|
||||
let mut out = sha256::Digest::default();
|
||||
|
||||
// hash the leaf element
|
||||
sha256::init_ctx!(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)
|
||||
};
|
||||
sha256::init_ctx!(ctx);
|
||||
ctx.update(&[0x01]);
|
||||
ctx.update(min);
|
||||
ctx.update(max);
|
||||
ctx.finalize_into(&mut out);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
24
core/embed/rust/src/crypto/mod.rs
Normal file
24
core/embed/rust/src/crypto/mod.rs
Normal file
@ -0,0 +1,24 @@
|
||||
pub mod cosi;
|
||||
pub mod ed25519;
|
||||
mod ffi;
|
||||
pub mod merkle;
|
||||
pub mod sha256;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
impl From<Error> for crate::error::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error::SignatureVerificationFailed => value_error!("Signature verification failed"),
|
||||
Error::InvalidEncoding => value_error!("Invalid key or signature encoding"),
|
||||
Error::InvalidParams => value_error!("Invalid cryptographic parameters"),
|
||||
}
|
||||
}
|
||||
}
|
128
core/embed/rust/src/crypto/sha256.rs
Normal file
128
core/embed/rust/src/crypto/sha256.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use core::{mem::MaybeUninit, pin::Pin};
|
||||
|
||||
use zeroize::{DefaultIsZeroes, 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 {}
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
impl<'a> Sha256<'a> {
|
||||
pub fn new(mut ctx: Pin<&'a mut Memory>) -> Self {
|
||||
// initialize the context
|
||||
// SAFETY: safe with whatever finds itself as memory contents
|
||||
unsafe { ffi::sha256_Init(ctx.as_mut().get_unchecked_mut()) };
|
||||
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(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn memory() -> Memory {
|
||||
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()) };
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sha256<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.ctx.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! init_ctx {
|
||||
($name:ident) => {
|
||||
// assign the backing memory to $name...
|
||||
let mut $name = crate::crypto::sha256::Sha256::memory();
|
||||
// ... then make it inaccessible by overwriting the binding, and pin it
|
||||
#[allow(unused_mut)]
|
||||
let mut $name = unsafe {
|
||||
crate::crypto::sha256::Sha256::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 = Digest::default();
|
||||
digest_into(data, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::strutil::hexlify;
|
||||
|
||||
use super::*;
|
||||
|
||||
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!(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);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ pub enum Error {
|
||||
#[macro_export]
|
||||
macro_rules! value_error {
|
||||
($msg:expr) => {
|
||||
Error::ValueError(cstr_core::cstr!($msg))
|
||||
$crate::error::Error::ValueError(cstr_core::cstr!($msg))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ mod error;
|
||||
#[macro_use]
|
||||
mod trezorhal;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
mod crypto;
|
||||
mod io;
|
||||
mod maybe_trace;
|
||||
#[cfg(feature = "micropython")]
|
||||
|
Loading…
Reference in New Issue
Block a user