mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +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",
|
"qrcodegen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"trezor-tjpgdec",
|
"trezor-tjpgdec",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -356,3 +357,9 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]
|
[features]
|
||||||
default = ["model_tt"]
|
default = ["model_tt"]
|
||||||
bitcoin_only = []
|
bitcoin_only = []
|
||||||
|
crypto = ["zeroize"]
|
||||||
model_tt = ["jpeg"]
|
model_tt = ["jpeg"]
|
||||||
model_tr = []
|
model_tr = []
|
||||||
micropython = []
|
micropython = []
|
||||||
@ -32,10 +33,11 @@ rgb_led = []
|
|||||||
backlight = []
|
backlight = []
|
||||||
usb = []
|
usb = []
|
||||||
optiga = []
|
optiga = []
|
||||||
translations = []
|
translations = ["crypto"]
|
||||||
test = [
|
test = [
|
||||||
"button",
|
"button",
|
||||||
"cc",
|
"cc",
|
||||||
|
"crypto",
|
||||||
"debug",
|
"debug",
|
||||||
"glob",
|
"glob",
|
||||||
"micropython",
|
"micropython",
|
||||||
@ -74,6 +76,7 @@ debug = 2
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
qrcodegen = { version = "1.8.0", path = "../../vendor/QR-Code-generator/rust-no-heap" }
|
qrcodegen = { version = "1.8.0", path = "../../vendor/QR-Code-generator/rust-no-heap" }
|
||||||
trezor-tjpgdec = { version = "0.1.0", path = "../../../rust/trezor-tjpgdec" }
|
trezor-tjpgdec = { version = "0.1.0", path = "../../../rust/trezor-tjpgdec" }
|
||||||
|
zeroize = { version = "1.7.0", default-features = false, optional = true }
|
||||||
|
|
||||||
# Runtime dependencies
|
# Runtime dependencies
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ fn main() {
|
|||||||
#[cfg(feature = "micropython")]
|
#[cfg(feature = "micropython")]
|
||||||
generate_micropython_bindings();
|
generate_micropython_bindings();
|
||||||
generate_trezorhal_bindings();
|
generate_trezorhal_bindings();
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
generate_crypto_bindings();
|
||||||
#[cfg(feature = "test")]
|
#[cfg(feature = "test")]
|
||||||
link_core_objects();
|
link_core_objects();
|
||||||
}
|
}
|
||||||
@ -390,6 +392,37 @@ fn generate_trezorhal_bindings() {
|
|||||||
.unwrap();
|
.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 {
|
fn is_firmware() -> bool {
|
||||||
let target = env::var("TARGET").unwrap();
|
let target = env::var("TARGET").unwrap();
|
||||||
target.starts_with("thumbv7")
|
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_export]
|
||||||
macro_rules! value_error {
|
macro_rules! value_error {
|
||||||
($msg:expr) => {
|
($msg:expr) => {
|
||||||
Error::ValueError(cstr_core::cstr!($msg))
|
$crate::error::Error::ValueError(cstr_core::cstr!($msg))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ mod error;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod trezorhal;
|
mod trezorhal;
|
||||||
|
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
mod crypto;
|
||||||
mod io;
|
mod io;
|
||||||
mod maybe_trace;
|
mod maybe_trace;
|
||||||
#[cfg(feature = "micropython")]
|
#[cfg(feature = "micropython")]
|
||||||
|
Loading…
Reference in New Issue
Block a user