From d71bb79e4a96feb1e2fd921c485d739b3184ecf2 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 28 Aug 2024 07:43:40 +0200 Subject: [PATCH] Add ZIP32 support. --- app/rust/Cargo.lock | 101 ++++ app/rust/Cargo.toml | 5 + app/rust/include/rslib.h | 7 +- app/rust/src/bolos/aes.rs | 52 ++ app/rust/src/bolos/blake2b.rs | 252 ++++++++ app/rust/src/bolos/canary.rs | 13 + app/rust/src/bolos/heartbeat.rs | 12 + app/rust/src/bolos/jubjub.rs | 22 + app/rust/src/bolos/mod.rs | 15 + app/rust/src/bolos/seed.rs | 63 ++ app/rust/src/bolos/zemu.rs | 13 + app/rust/src/constants.rs | 36 ++ app/rust/src/cryptoops.rs | 57 ++ app/rust/src/lib.rs | 41 +- app/rust/src/personalization.rs | 8 + app/rust/src/sapling.rs | 57 ++ app/rust/src/types.rs | 111 ++++ app/rust/src/zip32.rs | 986 +++++++++++++++++++++++++++++++ app/rust/src/zip32_extern.rs | 195 ++++++ app/src/c_api/rust.c | 143 +++++ app/src/coin.h | 3 + app/src/constants.h | 1 + app/src/crypto.c | 67 +-- app/src/crypto.h | 1 + app/src/crypto_helper.c | 116 ---- app/src/crypto_helper.h | 5 - app/src/keys_def.h | 4 +- app/ztruct/Cargo.lock | 47 ++ app/ztruct/Cargo.toml | 12 + app/ztruct/src/lib.rs | 117 ++++ app/ztruct/tests/simple.rs | 74 +++ app/ztruct/tests/simple_array.rs | 80 +++ 32 files changed, 2514 insertions(+), 202 deletions(-) create mode 100644 app/rust/src/bolos/aes.rs create mode 100644 app/rust/src/bolos/blake2b.rs create mode 100644 app/rust/src/bolos/canary.rs create mode 100644 app/rust/src/bolos/heartbeat.rs create mode 100644 app/rust/src/bolos/jubjub.rs create mode 100644 app/rust/src/bolos/mod.rs create mode 100644 app/rust/src/bolos/seed.rs create mode 100644 app/rust/src/bolos/zemu.rs create mode 100644 app/rust/src/cryptoops.rs create mode 100644 app/rust/src/personalization.rs create mode 100644 app/rust/src/sapling.rs create mode 100644 app/rust/src/types.rs create mode 100644 app/rust/src/zip32.rs create mode 100644 app/rust/src/zip32_extern.rs create mode 100644 app/src/c_api/rust.c create mode 100644 app/ztruct/Cargo.lock create mode 100644 app/ztruct/Cargo.toml create mode 100644 app/ztruct/src/lib.rs create mode 100644 app/ztruct/tests/simple.rs create mode 100644 app/ztruct/tests/simple_array.rs diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index beefd997..2e159bf8 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -14,6 +14,18 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "binary-ff1" version = "0.2.0" @@ -35,6 +47,28 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "bls12_381" version = "0.8.0" @@ -46,6 +80,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.0" @@ -61,6 +101,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -127,6 +173,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -139,6 +191,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + [[package]] name = "radium" version = "0.7.0" @@ -157,8 +227,13 @@ version = "0.0.1" dependencies = [ "aes", "binary-ff1", + "blake2b_simd", + "blake2s_simd", + "byteorder", "jubjub", + "log", "panic-halt", + "ztruct", ] [[package]] @@ -167,6 +242,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tap" version = "1.0.1" @@ -179,6 +265,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "version_check" version = "0.9.4" @@ -193,3 +285,12 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "ztruct" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index 9680fd00..6b8073e9 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -11,9 +11,14 @@ name = "rslib" crate-type = ["staticlib"] [dependencies] +ztruct = { path = "../ztruct", version = "*" } jubjub = { version = "0.10.0", default-features = false } aes = { version = "0.7", default-features = false } binary-ff1 = { version = "0.2", default-features = false } +blake2s_simd = { version = "0.5", default-features = false } +blake2b_simd = { version = "0.5", default-features = false } +byteorder = { version = "1.5", default-features = false } +log = "0.4" [target.thumbv6m-none-eabi.dev-dependencies] panic-halt = "0.2.0" diff --git a/app/rust/include/rslib.h b/app/rust/include/rslib.h index ec433aec..7a3fdc02 100644 --- a/app/rust/include/rslib.h +++ b/app/rust/include/rslib.h @@ -8,10 +8,11 @@ parser_error_t from_bytes_wide(const uint8_t input[64], uint8_t output[32]); parser_error_t scalar_multiplication(const uint8_t input[32], constant_key_t key, uint8_t output[32]); parser_error_t get_default_diversifier_list(const uint8_t dk[32], uint8_t start_index[11], uint8_t d_l[44]); -parser_error_t get_default_diversifier(const uint8_t dk[32], uint8_t start_index[11], uint8_t d[11]); -parser_error_t get_pkd(const uint8_t ivk_ptr[32], const uint8_t hash[32], uint8_t pk_d[32]); -parser_error_t get_pkd(const uint8_t ivk_ptr[32], const uint8_t hash[32], uint8_t pk_d[32]); +void get_pkd(uint32_t zip32_account, const uint8_t *diversifier_ptr, uint8_t *pkd); bool is_valid_diversifier(const uint8_t hash[32]); parser_error_t randomized_secret_from_seed(const uint8_t ask[32], const uint8_t alpha[32], uint8_t output[32]); parser_error_t compute_sbar(const uint8_t s[32], uint8_t r[32], uint8_t rsk[32], uint8_t sbar[32]); parser_error_t add_points(const uint8_t hash[32], const uint8_t value[32], const uint8_t scalar[32], uint8_t cv[32]); +void zip32_ovk(uint32_t zip32_account, uint8_t *ovk); +void zip32_child_ask_nsk(uint32_t account, uint8_t *ask, uint8_t *nsk); +void diversifier_find_valid(uint32_t zip32_account, uint8_t *default_diversifier); diff --git a/app/rust/src/bolos/aes.rs b/app/rust/src/bolos/aes.rs new file mode 100644 index 00000000..3f61b9bf --- /dev/null +++ b/app/rust/src/bolos/aes.rs @@ -0,0 +1,52 @@ +use aes::cipher::generic_array::typenum::{U16, U32, U8}; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::BlockEncrypt; +use aes::cipher::NewBlockCipher; +use aes::cipher::{BlockCipher, BlockCipherKey}; +use aes::Aes256; + +/// Encrypts a block using AES-256. +/// This function uses the Rust `aes` crate for encryption in test environments. +pub fn aes256_encrypt_block(k: &[u8], a: &[u8]) -> [u8; 16] { + let cipher: Aes256 = Aes256::new(GenericArray::from_slice(k)); + + let mut b = GenericArray::clone_from_slice(a); + cipher.encrypt_block(&mut b); + + let out: [u8; 16] = b.as_slice().try_into().expect("err"); + out +} + +pub struct AesBOLOS { + key: [u8; 32], +} + +impl AesBOLOS { + pub fn new(k: &[u8; 32]) -> AesBOLOS { + AesBOLOS { key: *k } + } +} + +impl BlockCipher for AesBOLOS { + type BlockSize = U16; + type ParBlocks = U8; +} + +impl NewBlockCipher for AesBOLOS { + type KeySize = U32; + + #[inline(never)] + fn new(key: &BlockCipherKey) -> Self { + let v: [u8; 32] = key.as_slice().try_into().expect("Wrong length"); + AesBOLOS { key: v } + } +} +impl BlockEncrypt for AesBOLOS { + #[inline(never)] + fn encrypt_block(&self, block: &mut GenericArray) { + let x: [u8; 16] = block.as_slice().try_into().expect("err"); + let y = aes256_encrypt_block(&self.key, &x); + + block.copy_from_slice(&y); + } +} diff --git a/app/rust/src/bolos/blake2b.rs b/app/rust/src/bolos/blake2b.rs new file mode 100644 index 00000000..a263c68e --- /dev/null +++ b/app/rust/src/bolos/blake2b.rs @@ -0,0 +1,252 @@ +use crate::bolos; +use crate::personalization::{ + KEY_DIVERSIFICATION_PERSONALIZATION, PRF_EXPAND_PERSONALIZATION, REDJUBJUB_PERSONALIZATION, +}; +use blake2b_simd::Params as Blake2bParams; +use blake2s_simd::Params as Blake2sParams; + +extern "C" { + fn c_zcash_blake2b_expand_seed( + input_a: *const u8, + input_a_len: u32, + input_b: *const u8, + input_b_len: u32, + out: *mut u8, + ); + fn c_zcash_blake2b_expand_vec_two( + input_a: *const u8, + input_a_len: u32, + input_b: *const u8, + input_b_len: u32, + input_c: *const u8, + input_c_len: u32, + out: *mut u8, + ); + + fn c_blake2b32_withpersonal(person: *const u8, input: *const u8, input_len: u32, out: *mut u8); + fn c_blake2b64_withpersonal(person: *const u8, input: *const u8, input_len: u32, out: *mut u8); + + fn c_zcash_blake2b_expand_vec_four( + input_a: *const u8, + input_a_len: u32, + input_b: *const u8, + input_b_len: u32, + input_c: *const u8, + input_c_len: u32, + input_d: *const u8, + input_d_len: u32, + input_e: *const u8, + input_e_len: u32, + out: *mut u8, + ); + fn c_zcash_blake2b_zip32master(a: *const u8, a_len: u32, out: *mut u8); + + fn zcash_blake2b_expand_seed(a: *const u8, a_len: u32, b: *const u8, b_len: u32, out: *mut u8); + fn c_zcash_blake2b_redjubjub(a: *const u8, a_len: u32, b: *const u8, b_len: u32, out: *mut u8); +} + +#[cfg(test)] +pub fn blake2b32_with_personalization(person: &[u8; 16], data: &[u8]) -> [u8; 32] { + let h = Blake2bParams::new() + .hash_length(32) + .personal(person) + .hash(data); + let mut hash = [0u8; 32]; + hash.copy_from_slice(h.as_bytes()); + hash +} + +#[cfg(not(test))] +pub fn blake2b32_with_personalization(person: &[u8; 16], data: &[u8]) -> [u8; 32] { + let mut hash = [0; 32]; + unsafe { + c_blake2b32_withpersonal( + person.as_ptr(), + data.as_ptr(), + data.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(test)] +pub fn blake2b64_with_personalization(person: &[u8; 16], data: &[u8]) -> [u8; 64] { + let h = Blake2bParams::new() + .hash_length(64) + .personal(person) + .hash(data); + let mut hash = [0u8; 64]; + hash.copy_from_slice(h.as_bytes()); + hash +} + +#[cfg(not(test))] +pub fn blake2b64_with_personalization(person: &[u8; 16], data: &[u8]) -> [u8; 64] { + let mut hash = [0; 64]; + unsafe { + c_blake2b64_withpersonal( + person.as_ptr(), + data.as_ptr(), + data.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(test)] +pub fn blake2b_redjubjub(a: &[u8], b: &[u8]) -> [u8; 64] { + let h = Blake2bParams::new() + .hash_length(64) + .personal(REDJUBJUB_PERSONALIZATION) + .to_state() + .update(a) + .update(b) + .finalize(); + + let result: [u8; 64] = *h.as_array(); + result +} + +#[cfg(not(test))] +pub fn blake2b_redjubjub(a: &[u8], b: &[u8]) -> [u8; 64] { + let mut hash = [0; 64]; + unsafe { + c_zcash_blake2b_redjubjub( + a.as_ptr(), + a.len() as u32, + b.as_ptr(), + b.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(not(test))] +pub fn blake2b_expand_seed(a: &[u8], b: &[u8]) -> [u8; 64] { + let mut hash = [0; 64]; + unsafe { + c_zcash_blake2b_expand_seed( + a.as_ptr(), + a.len() as u32, + b.as_ptr(), + b.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(not(test))] +pub fn blake2b_expand_vec_two(in_a: &[u8], in_b: &[u8], in_c: &[u8]) -> [u8; 64] { + let mut hash = [0; 64]; + unsafe { + c_zcash_blake2b_expand_vec_two( + in_a.as_ptr(), + in_a.len() as u32, + in_b.as_ptr(), + in_b.len() as u32, + in_c.as_ptr(), + in_c.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(not(test))] +pub fn blake2b_expand_v4( + in_a: &[u8], + in_b: &[u8], + in_c: &[u8], + in_d: &[u8], + in_e: &[u8], +) -> [u8; 64] { + let mut hash = [0; 64]; + unsafe { + c_zcash_blake2b_expand_vec_four( + in_a.as_ptr(), + in_a.len() as u32, + in_b.as_ptr(), + in_b.len() as u32, + in_c.as_ptr(), + in_c.len() as u32, + in_d.as_ptr(), + in_d.len() as u32, + in_e.as_ptr(), + in_e.len() as u32, + hash.as_mut_ptr(), + ); + } + hash +} + +#[cfg(test)] +pub fn blake2b_expand_seed(a: &[u8], b: &[u8]) -> [u8; 64] { + let h = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state() + .update(a) + .update(b) + .finalize(); + + let result: [u8; 64] = *h.as_array(); + result +} + +#[inline(never)] +pub fn blake2s_diversification(tag: &[u8]) -> [u8; 32] { + pub const GH_FIRST_BLOCK: &[u8; 64] = + b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; + + // FIXME: not using bolos blake!? + let h = Blake2sParams::new() + .hash_length(32) + .personal(KEY_DIVERSIFICATION_PERSONALIZATION) + .to_state() + .update(GH_FIRST_BLOCK) + .update(tag) + .finalize(); + + let result: [u8; 32] = *h.as_array(); + result +} + +#[cfg(test)] +pub fn blake2b_expand_vec_two(sk: &[u8], a: &[u8], b: &[u8]) -> [u8; 64] { + let mut h = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + h.update(sk); + h.update(a); + h.update(b); + let mut hash = [0u8; 64]; + hash.copy_from_slice(h.finalize().as_bytes()); + hash +} + +#[cfg(test)] +pub fn blake2b_expand_v4( + in_a: &[u8], + in_b: &[u8], + in_c: &[u8], + in_d: &[u8], + in_e: &[u8], +) -> [u8; 64] { + let mut blake2b_state = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + blake2b_state.update(in_a); + blake2b_state.update(in_b); + blake2b_state.update(in_c); + blake2b_state.update(in_d); + blake2b_state.update(in_e); + let mut hash = [0u8; 64]; + hash.copy_from_slice(blake2b_state.finalize().as_bytes()); + hash +} diff --git a/app/rust/src/bolos/canary.rs b/app/rust/src/bolos/canary.rs new file mode 100644 index 00000000..fc527e36 --- /dev/null +++ b/app/rust/src/bolos/canary.rs @@ -0,0 +1,13 @@ +use crate::bolos; + +extern "C" { + fn check_app_canary(); +} + +#[cfg(not(test))] +pub fn c_check_app_canary() { + unsafe { check_app_canary() } +} + +#[cfg(test)] +pub fn c_check_app_canary() {} diff --git a/app/rust/src/bolos/heartbeat.rs b/app/rust/src/bolos/heartbeat.rs new file mode 100644 index 00000000..5174e548 --- /dev/null +++ b/app/rust/src/bolos/heartbeat.rs @@ -0,0 +1,12 @@ +#[cfg(not(test))] +extern "C" { + fn io_heartbeat(); +} + +// Lets the device breath between computations +pub(crate) fn heartbeat() { + #[cfg(not(test))] + unsafe { + io_heartbeat() + } +} diff --git a/app/rust/src/bolos/jubjub.rs b/app/rust/src/bolos/jubjub.rs new file mode 100644 index 00000000..cd9a3ca2 --- /dev/null +++ b/app/rust/src/bolos/jubjub.rs @@ -0,0 +1,22 @@ +use crate::bolos::canary::c_check_app_canary; +use crate::{bolos, constants}; +use jubjub::AffinePoint; + +pub fn scalarmult(point: &mut [u8], scalar: &[u8]) { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(point); + let mut scalarbytes = [0u8; 32]; + scalarbytes.copy_from_slice(scalar); + let result = AffinePoint::from_bytes(bytes) + .unwrap() + .to_niels() + .multiply_bits(&scalarbytes); + point.copy_from_slice(&AffinePoint::from(result).to_bytes()); +} + +pub fn scalarmult_spending_base(point: &mut [u8], scalar: &[u8]) { + let mut scalarbytes = [0u8; 32]; + scalarbytes.copy_from_slice(scalar); + let result = constants::SPENDING_KEY_BASE.multiply_bits(&scalarbytes); + point.copy_from_slice(&AffinePoint::from(result).to_bytes()); +} diff --git a/app/rust/src/bolos/mod.rs b/app/rust/src/bolos/mod.rs new file mode 100644 index 00000000..7364979d --- /dev/null +++ b/app/rust/src/bolos/mod.rs @@ -0,0 +1,15 @@ +//! Rust interfaces to Ledger SDK APIs. + +pub(crate) mod aes; +pub(crate) mod blake2b; +pub(crate) mod canary; +pub(crate) mod heartbeat; +pub(crate) mod jubjub; +pub(crate) mod seed; +pub(crate) mod zemu; + +pub(crate) use heartbeat::heartbeat; + +pub use canary::c_check_app_canary; +pub use seed::c_device_seed; +pub use zemu::c_zemu_log_stack; diff --git a/app/rust/src/bolos/seed.rs b/app/rust/src/bolos/seed.rs new file mode 100644 index 00000000..eb991499 --- /dev/null +++ b/app/rust/src/bolos/seed.rs @@ -0,0 +1,63 @@ +use crate::types::Zip32Seed; + +extern "C" { + fn crypto_fillDeviceSeed(seed: *mut u8); +} + +#[cfg(not(test))] +pub fn c_device_seed() -> Zip32Seed { + let mut seed: Zip32Seed = [0; 32]; + unsafe { + crypto_fillDeviceSeed(seed.as_mut_ptr()); + } + seed +} + +#[cfg(test)] +use lazy_static::lazy_static; + +#[cfg(test)] +use parking_lot::ReentrantMutex; + +#[cfg(test)] +use std::cell::RefCell; + +#[cfg(test)] +lazy_static! { + static ref CUSTOM_TEST_SEED: ReentrantMutex>> = + ReentrantMutex::new(RefCell::new(None)); +} + +#[cfg(test)] +pub fn with_device_seed_context(temporary_seed: Zip32Seed, test: F) { + let guard = CUSTOM_TEST_SEED.lock(); + + guard.replace(Some(temporary_seed)); + + // Run the test lambda + test(); + + guard.replace(None); +} + +#[cfg(test)] +pub fn c_device_seed() -> Zip32Seed { + let guard = CUSTOM_TEST_SEED.lock(); + let seed_ref = guard.borrow(); + + match &*seed_ref { + Some(temporary_seed) => { + // Handle the case where the seed is Some + // `seed` here is a reference to the value inside Some + temporary_seed.clone() + } + None => { + let mut seed: Zip32Seed = [0; 32]; + // Handle the case where the override seed is None + for (i, elem) in seed.iter_mut().enumerate() { + *elem = i as u8; + } + seed + } + } +} diff --git a/app/rust/src/bolos/zemu.rs b/app/rust/src/bolos/zemu.rs new file mode 100644 index 00000000..97793c08 --- /dev/null +++ b/app/rust/src/bolos/zemu.rs @@ -0,0 +1,13 @@ +use crate::bolos; + +extern "C" { + fn zemu_log_stack(buffer: *const u8); +} + +#[cfg(not(test))] +pub fn c_zemu_log_stack(s: &[u8]) { + unsafe { zemu_log_stack(s.as_ptr()) } +} + +#[cfg(test)] +pub fn c_zemu_log_stack(_s: &[u8]) {} diff --git a/app/rust/src/constants.rs b/app/rust/src/constants.rs index a2dd5714..a8f25800 100644 --- a/app/rust/src/constants.rs +++ b/app/rust/src/constants.rs @@ -70,3 +70,39 @@ pub const DIV_DEFAULT_LIST_LEN: usize = 4; pub const KEY_DIVERSIFICATION_PERSONALIZATION: &[u8; 8] = b"MASP__gd"; pub const GH_FIRST_BLOCK: &[u8; 64] = b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; + +pub const FIRSTVALUE: u32 = 32 ^ 0x8000_0000; +pub const COIN_TYPE: u32 = 877 ^ 0x8000_0000; + +// ZIP32 Child components +pub const AK_NK: u8 = 0; +pub const DK: u8 = 2; +pub const AK_NSK: u8 = 3; +pub const ASK_NSK: u8 = 4; +pub const DK_AK_NK: u8 = 5; + +/******************************************************************************* +* (c) 2018 - 2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +pub const SPENDING_KEY_BASE: AffineNielsPoint = SPENDING_KEY_GENERATOR; + +pub const PROVING_KEY_BASE: AffineNielsPoint = PROOF_GENERATION_KEY_GENERATOR; + +/// https://zips.z.cash/zip-0032#key-path-levels +/// m/PURPOSE/COIN/account +pub const ZIP32_PURPOSE: u32 = 0x8000_0020; +pub const ZIP32_COIN_TYPE: u32 = 0x8000_036d; +pub const ZIP32_HARDENED: u32 = 0x8000_0000; diff --git a/app/rust/src/cryptoops.rs b/app/rust/src/cryptoops.rs new file mode 100644 index 00000000..b77b67be --- /dev/null +++ b/app/rust/src/cryptoops.rs @@ -0,0 +1,57 @@ +use crate::bolos::blake2b; +use crate::bolos::blake2b::blake2b_expand_seed; +use crate::types::Diversifier; +use jubjub::{AffinePoint, ExtendedPoint, Fr}; + +#[inline(always)] +pub fn prf_expand(sk: &[u8], t: &[u8]) -> [u8; 64] { + crate::bolos::heartbeat(); + blake2b_expand_seed(sk, t) +} + +#[inline(never)] +pub fn mult_by_gd(scalar: &[u8; 32], d: &Diversifier) -> [u8; 32] { + let h = blake2b::blake2s_diversification(d); + + let v = AffinePoint::from_bytes(h) + .unwrap() + .mul_by_cofactor() + .to_niels(); + + let t = v.multiply_bits(scalar); + extended_to_bytes(&t) +} + +#[inline(never)] +pub fn mul_by_cofactor(p: &mut ExtendedPoint) { + *p = p.mul_by_cofactor(); +} + +#[inline(never)] +pub fn extended_to_u_bytes(point: &ExtendedPoint) -> [u8; 32] { + AffinePoint::from(*point).get_u().to_bytes() +} + +#[inline(never)] +pub fn extended_to_bytes(point: &ExtendedPoint) -> [u8; 32] { + AffinePoint::from(*point).to_bytes() +} + +#[inline(never)] +pub fn bytes_to_extended(m: [u8; 32]) -> ExtendedPoint { + ExtendedPoint::from(AffinePoint::from_bytes(m).unwrap()) +} + +// pub fn add_points(a: ExtendedPoint, b: ExtendedPoint) -> ExtendedPoint { +// a + b +// } + +#[inline(never)] +pub fn add_to_point(point: &mut ExtendedPoint, p: &ExtendedPoint) { + *point += p; +} + +#[inline(never)] +pub fn niels_multbits(p: &mut ExtendedPoint, b: &[u8; 32]) { + *p = p.to_niels().multiply_bits(b); +} diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index 1a467277..1daf1ea0 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -20,8 +20,15 @@ use core::panic::PanicInfo; -use constants::{DIV_DEFAULT_LIST_LEN, DIV_SIZE, SPENDING_KEY_GENERATOR, KEY_DIVERSIFICATION_PERSONALIZATION, GH_FIRST_BLOCK}; +use constants::{DIV_DEFAULT_LIST_LEN, DIV_SIZE, SPENDING_KEY_GENERATOR, KEY_DIVERSIFICATION_PERSONALIZATION, GH_FIRST_BLOCK, DK}; mod constants; +mod bolos; +mod zip32; +mod zip32_extern; +mod personalization; +mod sapling; +mod types; +mod cryptoops; use aes::Aes256; use aes::cipher::{ BlockCipher, BlockEncrypt, BlockDecrypt, NewBlockCipher, @@ -29,6 +36,7 @@ use aes::cipher::{ }; use binary_ff1::BinaryFF1; use jubjub::{AffinePoint, ExtendedPoint, Fr}; +use crate::bolos::{c_check_app_canary, c_zemu_log_stack}; fn debug(_msg: &str) {} @@ -132,37 +140,6 @@ pub extern "C" fn is_valid_diversifier( false } -#[no_mangle] -pub extern "C" fn get_pkd( - ivk_ptr: &[u8; 32], - h: &[u8; 32], - pk_d: &mut [u8; 32], -) -> ParserError { - - let affine = AffinePoint::from_bytes(*h).unwrap(); - let extended = ExtendedPoint::from(affine); - let cofactor = extended.mul_by_cofactor(); - let p = cofactor.to_niels().multiply_bits(ivk_ptr); - *pk_d = AffinePoint::from(p).to_bytes(); - - ParserError::ParserOk -} - -#[no_mangle] -pub extern "C" fn randomized_secret_from_seed( - ask: &[u8; 32], - alpha: &[u8; 32], - output: &mut [u8; 32], -) -> ParserError{ - - let mut skfr = Fr::from_bytes(ask).unwrap(); - let alphafr = Fr::from_bytes(alpha).unwrap(); - skfr += alphafr; - output.copy_from_slice(&skfr.to_bytes()); - - ParserError::ParserOk -} - #[no_mangle] pub extern "C" fn compute_sbar( s: &[u8; 32], diff --git a/app/rust/src/personalization.rs b/app/rust/src/personalization.rs new file mode 100644 index 00000000..6e8bea8e --- /dev/null +++ b/app/rust/src/personalization.rs @@ -0,0 +1,8 @@ +pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"MASP__Derive_ock"; +pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"MASP__SaplingKDF"; +pub const CRH_IVK_PERSONALIZATION: &[u8; 8] = b"MASP_ivk"; +pub const CRH_NF: &[u8; 8] = b"MASP__nf"; +pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"MASP_IP32Sapling"; +pub const KEY_DIVERSIFICATION_PERSONALIZATION: &[u8; 8] = b"MASP__gd"; +pub const REDJUBJUB_PERSONALIZATION: &[u8; 16] = b"MASP__RedJubjubH"; +pub const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"MASP__ExpandSeed"; diff --git a/app/rust/src/sapling.rs b/app/rust/src/sapling.rs new file mode 100644 index 00000000..19d7e493 --- /dev/null +++ b/app/rust/src/sapling.rs @@ -0,0 +1,57 @@ +use crate::bolos::blake2b::blake2b32_with_personalization; +use crate::bolos::jubjub::scalarmult_spending_base; +use crate::constants::PROVING_KEY_BASE; +use crate::cryptoops::niels_multbits; +use crate::cryptoops::{bytes_to_extended, extended_to_bytes, mul_by_cofactor}; +use crate::personalization::{CRH_IVK_PERSONALIZATION, KDF_SAPLING_PERSONALIZATION}; +use crate::types::{AkBytes, AskBytes, IvkBytes, NkBytes, NskBytes}; +use blake2s_simd::Params as Blake2sParams; +use jubjub::AffinePoint; + +#[inline(never)] +pub fn sapling_ask_to_ak(ask: &AskBytes) -> AkBytes { + let mut point = [0u8; 32]; + scalarmult_spending_base(&mut point, &ask[..]); + point +} + +#[inline(never)] +pub fn sapling_nsk_to_nk(nsk: &NskBytes) -> NkBytes { + let nk = PROVING_KEY_BASE.multiply_bits(nsk); + AffinePoint::from(nk).to_bytes() +} + +#[inline(never)] +pub fn sapling_asknsk_to_ivk(ask: &AskBytes, nsk: &NskBytes) -> IvkBytes { + let ak = sapling_ask_to_ak(ask); + let nk = sapling_nsk_to_nk(nsk); + + // FIXME: not using bolos blake!? + let h = Blake2sParams::new() + .hash_length(32) + .personal(CRH_IVK_PERSONALIZATION) + .to_state() + .update(&ak) + .update(&nk) + .finalize(); + + let mut x: [u8; 32] = *h.as_array(); + x[31] &= 0b0000_0111; //check this + x +} + +#[inline(never)] +pub fn sapling_aknk_to_ivk(ak: &AkBytes, nk: &NkBytes) -> IvkBytes { + // FIXME: not using bolos blake!? + let h = Blake2sParams::new() + .hash_length(32) + .personal(CRH_IVK_PERSONALIZATION) + .to_state() + .update(ak) + .update(nk) + .finalize(); + + let mut x: [u8; 32] = *h.as_array(); + x[31] &= 0b0000_0111; //check this + x +} diff --git a/app/rust/src/types.rs b/app/rust/src/types.rs new file mode 100644 index 00000000..333f95c0 --- /dev/null +++ b/app/rust/src/types.rs @@ -0,0 +1,111 @@ +use ztruct::create_ztruct; + +pub type Diversifier = [u8; 11]; + +pub fn diversifier_zero() -> Diversifier { + [0u8; 11] +} + +// FIXME: This is not good design. Mayeb something like +pub type DiversifierList4 = [u8; 44]; +pub type DiversifierList10 = [u8; 110]; + +pub type DiversifierList20 = [u8; 220]; + +pub fn diversifier_list20_zero() -> DiversifierList20 { + [0u8; 220] +} + +pub type AskBytes = [u8; 32]; + +pub type NskBytes = [u8; 32]; + +pub type AkBytes = [u8; 32]; + +pub type NkBytes = [u8; 32]; + +pub type IvkBytes = [u8; 32]; + +pub type OvkBytes = [u8; 32]; + +pub type DkBytes = [u8; 32]; + +pub type NfBytes = [u8; 32]; + +// This can be between 32 and 252 bytes +// FIXME: move to 64 to align with ed25519 private key? +pub type Zip32Seed = [u8; 32]; + +pub type Zip32Path = [u32]; + +pub type Zip32MasterSpendingKey = [u8; 32]; +pub type Zip32MasterChainCode = [u8; 32]; + +create_ztruct! { + // I based on https://zips.z.cash/zip-0032#sapling-master-key-generation + pub struct Zip32MasterKey { + // I_L based on https://zips.z.cash/zip-0032#sapling-master-key-generation + pub spending_key: Zip32MasterSpendingKey, + // I_R based on https://zips.z.cash/zip-0032#sapling-master-key-generation + pub chain_code: Zip32MasterChainCode, + } +} + +create_ztruct! { + pub struct FullViewingKey { + pub ak: AkBytes, + pub nk: NkBytes, + pub ovk: OvkBytes, + } +} + +// https://zips.z.cash/zip-0032#specification-sapling-key-derivation +create_ztruct! { + pub struct SaplingExtendedFullViewingKey { + pub ak: AkBytes, + pub nk: NkBytes, + pub ovk: OvkBytes, + pub dk: DkBytes, + pub chain_code: Zip32MasterChainCode, + } +} + +// https://zips.z.cash/zip-0032#specification-sapling-key-derivation +create_ztruct! { + pub struct SaplingExpandedSpendingKey { + pub ask: AskBytes, + pub nsk: NskBytes, + pub ovk: OvkBytes, + } +} + +create_ztruct! { + pub struct SaplingKeyBundle { + pub ask: AskBytes, + pub nsk: NskBytes, + pub ovk: OvkBytes, + pub dk: DkBytes, + } +} + +// https://zips.z.cash/zip-0032#specification-sapling-key-derivation +create_ztruct! { + pub struct SaplingExtendedSpendingKey { + pub ask: AskBytes, + pub nsk: NskBytes, + pub ovk: OvkBytes, + pub dk: DkBytes, + pub chain_code: Zip32MasterChainCode, + } +} + +create_ztruct! { + pub struct CompactNoteExt { + pub version: u8, + pub diversifier: Diversifier, + pub value: [u8; 8], + pub rcm: DkBytes, + // FIXME: why an additional byte? + pub memotype: u8 + } +} diff --git a/app/rust/src/zip32.rs b/app/rust/src/zip32.rs new file mode 100644 index 00000000..679f9d21 --- /dev/null +++ b/app/rust/src/zip32.rs @@ -0,0 +1,986 @@ +use core::convert::TryInto; + +use binary_ff1::BinaryFF1; +use byteorder::{ByteOrder, LittleEndian}; +use jubjub::{AffinePoint, ExtendedPoint, Fr}; +use log::debug; + +use crate::bolos::aes::AesBOLOS; +use crate::bolos::blake2b; +use crate::bolos::blake2b::{ + blake2b64_with_personalization, blake2b_expand_v4, blake2b_expand_vec_two, +}; +use crate::bolos::c_check_app_canary; +use crate::constants::{DIV_DEFAULT_LIST_LEN, DIV_SIZE, ZIP32_COIN_TYPE, ZIP32_PURPOSE}; +use crate::cryptoops::bytes_to_extended; +use crate::cryptoops::extended_to_bytes; +use crate::personalization::ZIP32_SAPLING_MASTER_PERSONALIZATION; +use crate::sapling::{ + sapling_aknk_to_ivk, sapling_ask_to_ak, sapling_asknsk_to_ivk, sapling_nsk_to_nk, +}; +use crate::types::{ + diversifier_zero, AskBytes, Diversifier, DiversifierList10, DiversifierList20, + DiversifierList4, DkBytes, FullViewingKey, IvkBytes, NskBytes, OvkBytes, + SaplingExpandedSpendingKey, SaplingKeyBundle, Zip32MasterKey, Zip32MasterSpendingKey, + Zip32Path, Zip32Seed, +}; +use crate::zip32_extern::diversifier_is_valid; +use crate::{cryptoops, zip32}; + +#[inline(never)] +// Calculates I based on https://zips.z.cash/zip-0032#sapling-master-key-generation +fn zip32_master_key_i() -> Zip32MasterKey { + let seed = crate::bolos::c_device_seed(); + + Zip32MasterKey::from_bytes(&blake2b64_with_personalization( + ZIP32_SAPLING_MASTER_PERSONALIZATION, + &seed, + )) +} + +#[inline(never)] +// As per ask_m formula at https://zips.z.cash/zip-0032#sapling-master-key-generation +fn zip32_sapling_ask_m(sk_m: &Zip32MasterSpendingKey) -> AskBytes { + let t = cryptoops::prf_expand(sk_m, &[0x00]); + let ask = Fr::from_bytes_wide(&t); + ask.to_bytes() +} + +#[inline(never)] +// As per nsk_m formula at https://zips.z.cash/zip-0032#sapling-master-key-generation +fn zip32_sapling_nsk_m(sk_m: &Zip32MasterSpendingKey) -> NskBytes { + let t = cryptoops::prf_expand(sk_m, &[0x01]); + let nsk = Fr::from_bytes_wide(&t); + nsk.to_bytes() +} + +#[inline(never)] +// As per ovk_m formula at https://zips.z.cash/zip-0032#sapling-master-key-generation +fn zip32_sapling_ovk_m(key: &[u8; 32]) -> OvkBytes { + let prf_output = cryptoops::prf_expand(key, &[0x02]); + + // truncate + let mut ovk = [0u8; 32]; + ovk.copy_from_slice(&prf_output[..32]); + ovk +} + +#[inline(never)] +// As per dk_m formula at https://zips.z.cash/zip-0032#sapling-master-key-generation +fn zip32_sapling_dk_m(sk_m: &Zip32MasterSpendingKey) -> DkBytes { + let prf_output = cryptoops::prf_expand(sk_m, &[0x10]); + + // truncate + let mut dk_m = [0u8; 32]; + dk_m.copy_from_slice(&prf_output[..32]); + dk_m +} + +#[inline(never)] +fn zip32_sapling_i_ask(sk_m: &Zip32MasterSpendingKey) -> AskBytes { + let t = cryptoops::prf_expand(sk_m, &[0x13]); + let ask = Fr::from_bytes_wide(&t); + ask.to_bytes() +} + +#[inline(never)] +fn zip32_sapling_i_nsk(sk_m: &Zip32MasterSpendingKey) -> NskBytes { + let t = cryptoops::prf_expand(sk_m, &[0x14]); + let nsk = Fr::from_bytes_wide(&t); + nsk.to_bytes() +} + +//////////// + +#[inline(never)] +fn zip32_sapling_ask_i_update(sk_m: &Zip32MasterSpendingKey, ask_i: &mut AskBytes) { + let i_ask = zip32_sapling_i_ask(sk_m); + *ask_i = (Fr::from_bytes(ask_i).unwrap() + Fr::from_bytes(&i_ask).unwrap()).to_bytes(); +} + +#[inline(never)] +fn zip32_sapling_nsk_i_update(sk_m: &Zip32MasterSpendingKey, nsk_i: &mut NskBytes) { + let i_nsk = zip32_sapling_i_nsk(sk_m); + *nsk_i = (Fr::from_bytes(nsk_i).unwrap() + Fr::from_bytes(&i_nsk).unwrap()).to_bytes(); +} + +#[inline(never)] +fn zip32_sapling_ovk_i_update(sk_m: &[u8], ovk_i: &mut DkBytes) { + let mut ovk_copy = [0u8; 32]; + ovk_copy.copy_from_slice(ovk_i); + + let t = &blake2b_expand_vec_two(sk_m, &[0x15], &ovk_copy); + + ovk_i.copy_from_slice(&t[0..32]); +} + +#[inline(never)] +fn zip32_sapling_dk_i_update(sk_m: &[u8], dk_i: &mut DkBytes) { + let mut dk_copy = [0u8; 32]; + dk_copy.copy_from_slice(dk_i); + + let t = &blake2b_expand_vec_two(sk_m, &[0x16], &dk_copy); + + dk_i.copy_from_slice(&t[0..32]); +} + +// #[inline(never)] +// fn diversifier_group_hash_check(hash: &[u8; 32]) -> bool { +// let u = AffinePoint::from_bytes(*hash); +// if u.is_some().unwrap_u8() == 1 { +// let v = u.unwrap(); +// let q = v.mul_by_cofactor(); +// let i = ExtendedPoint::identity(); +// return q != i; +// } +// +// false +// } + +pub fn diversifier_find_valid(dk: &DkBytes, start: &Diversifier) -> Diversifier { + let mut div_list = [0u8; DIV_SIZE * DIV_DEFAULT_LIST_LEN]; + let mut div_out = diversifier_zero(); + + let mut cur_div = diversifier_zero(); + cur_div.copy_from_slice(start); + + let mut found = false; + while !found { + // we get some small list + diversifier_get_list(dk, &mut cur_div, &mut div_list); + + for i in 0..DIV_DEFAULT_LIST_LEN { + let tmp = &div_list[i * DIV_SIZE..(i + 1) * DIV_SIZE] + .try_into() + .unwrap(); + + if diversifier_is_valid(tmp) { + div_out.copy_from_slice(tmp); + found = true; + break; + } + } + + crate::bolos::heartbeat(); + } + + div_out +} + +#[inline(never)] +pub fn diversifier_get_list( + dk: &DkBytes, + start_diversifier: &mut Diversifier, + result: &mut DiversifierList4, +) { + let diversifier_list_size = 4; + + let mut scratch = [0u8; 12]; + + let cipher = AesBOLOS::new(dk); + let mut ff1 = BinaryFF1::new(&cipher, 11, &[], &mut scratch).unwrap(); + + let mut d: Diversifier; + + for c in 0..diversifier_list_size { + d = *start_diversifier; + ff1.encrypt(&mut d).unwrap(); + result[c * 11..(c + 1) * 11].copy_from_slice(&d); + for k in 0..11 { + start_diversifier[k] = start_diversifier[k].wrapping_add(1); + if start_diversifier[k] != 0 { + // No overflow + break; + } + } + } +} + +#[inline(never)] +pub fn diversifier_get_list_large( + dk: &DkBytes, + start_diversifier: &Diversifier, + result: &mut DiversifierList20, +) { + let diversifier_list_size = 20; + + let mut scratch = [0u8; 12]; + let cipher = AesBOLOS::new(dk); + let mut ff1 = BinaryFF1::new(&cipher, 11, &[], &mut scratch).unwrap(); + + let mut d: Diversifier; + + let mut counter: Diversifier = diversifier_zero(); + counter.copy_from_slice(start_diversifier); + + for c in 0..diversifier_list_size { + d = counter; + ff1.encrypt(&mut d).unwrap(); + result[c * 11..(c + 1) * 11].copy_from_slice(&d); + for k in 0..11 { + counter[k] = counter[k].wrapping_add(1); + if counter[k] != 0 { + // No overflow + break; + } + } + } +} + +#[inline(never)] +pub fn pkd_group_hash(d: &Diversifier) -> [u8; 32] { + let h = blake2b::blake2s_diversification(d); + + let v = AffinePoint::from_bytes(h).unwrap(); + let q = v.mul_by_cofactor(); + let t = AffinePoint::from(q); + + t.to_bytes() +} + +#[inline(never)] +pub fn pkd_default(ivk: &IvkBytes, d: &Diversifier) -> [u8; 32] { + let h = blake2b::blake2s_diversification(d); + let mut y = bytes_to_extended(h); + + cryptoops::mul_by_cofactor(&mut y); + cryptoops::niels_multbits(&mut y, ivk); + + extended_to_bytes(&y) +} + +#[inline(never)] +pub(crate) fn zip32_sapling_fvk(k: &SaplingKeyBundle) -> FullViewingKey { + FullViewingKey::new( + sapling_ask_to_ak(&k.ask()), + sapling_nsk_to_nk(&k.nsk()), + k.ovk(), + ) +} + +fn zip32_sapling_derive_child( + ik: &mut Zip32MasterKey, + path_i: u32, + key_bundle_i: &mut SaplingKeyBundle, +) { + let hardened = (path_i & 0x8000_0000) != 0; + let c = path_i & 0x7FFF_FFFF; + + let mut le_i = [0; 4]; + + if hardened { + if cfg!(test) { + debug!("---- path_i: {:x} | HARDENED", path_i); + } + + LittleEndian::write_u32(&mut le_i, c + (1 << 31)); + + //make index LE + //zip32 child derivation + let c_i = &ik.chain_code(); + + let prf_result = blake2b_expand_v4(c_i, &[0x11], key_bundle_i.to_bytes(), &[], &le_i); + + ik.to_bytes_mut().copy_from_slice(&prf_result); + } else { + //non-hardened child + // NOTE: WARNING: CURRENTLY COMPUTING NON-HARDENED PATHS DO NOT FIT IN MEMORY + LittleEndian::write_u32(&mut le_i, c); + + // FIXME: Duplicated work? + let s_k = &ik.spending_key(); + + let ask = zip32_sapling_ask_m(s_k); + let nsk = zip32_sapling_nsk_m(s_k); + let ovk = zip32_sapling_ovk_m(s_k); + let ak = sapling_ask_to_ak(&ask); + let nk = sapling_nsk_to_nk(&nsk); + + let fvk = FullViewingKey::new(ak, nk, ovk); + + let prf_result = blake2b_expand_v4( + &ik.chain_code(), + &[0x12], + fvk.to_bytes(), + &key_bundle_i.dk(), + &le_i, + ); + + ik.to_bytes_mut().copy_from_slice(&prf_result); + } + crate::bolos::heartbeat(); + + // https://zips.z.cash/zip-0032#deriving-a-child-extended-spending-key + zip32_sapling_ask_i_update(&ik.spending_key(), key_bundle_i.ask_mut()); + zip32_sapling_nsk_i_update(&ik.spending_key(), key_bundle_i.nsk_mut()); + zip32_sapling_ovk_i_update(&ik.spending_key(), key_bundle_i.ovk_mut()); + zip32_sapling_dk_i_update(&ik.spending_key(), key_bundle_i.dk_mut()); +} + +#[inline(never)] +pub fn zip32_sapling_derive(path: &Zip32Path) -> SaplingKeyBundle { + // ik as in capital I (https://zips.z.cash/zip-0032#sapling-child-key-derivation) + let mut ik = zip32_master_key_i(); + + let mut key_bundle_i = SaplingKeyBundle::new( + zip32_sapling_ask_m(&ik.spending_key()), + zip32_sapling_nsk_m(&ik.spending_key()), + zip32_sapling_ovk_m(&ik.spending_key()), + zip32_sapling_dk_m(&ik.spending_key()), + ); + + for path_i in path.iter().copied() { + zip32_sapling_derive_child(&mut ik, path_i, &mut key_bundle_i); + c_check_app_canary(); + } + + key_bundle_i +} + +#[inline(never)] +pub(crate) fn diversifier_group_hash_light(tag: &[u8]) -> bool { + if tag == diversifier_zero() { + return false; + } + let hash_tag = blake2b::blake2s_diversification(tag); + + // diversifier_group_hash_check(&x) + + let u = AffinePoint::from_bytes(hash_tag); + if u.is_some().unwrap_u8() == 1 { + let q = u.unwrap().mul_by_cofactor(); + return q != ExtendedPoint::identity(); + } + + false +} + +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use crate::bolos::seed::with_device_seed_context; + use crate::constants::ZIP32_HARDENED; + use crate::sapling::{sapling_aknk_to_ivk, sapling_ask_to_ak, sapling_nsk_to_nk}; + use crate::types::diversifier_list20_zero; + + use super::*; + + fn zip32_sapling_derive_master(_seed: &Zip32Seed) -> SaplingKeyBundle { + let master_key = zip32_master_key_i(); + + let ask = zip32_sapling_ask_m(&master_key.spending_key()); + let nsk = zip32_sapling_nsk_m(&master_key.spending_key()); + let dk = zip32_sapling_dk_m(&master_key.spending_key()); + let ovk = zip32_sapling_ovk_m(&master_key.spending_key()); + + SaplingKeyBundle::new(ask, nsk, ovk, dk) + } + + pub fn find_valid_diversifier(list: &DiversifierList20) -> Option { + let mut result = diversifier_zero(); + + for c in 0..20 { + result.copy_from_slice(&list[c * 11..(c + 1) * 11]); + //c[1] += 1; + if diversifier_is_valid(&result) { + return Some(result); + } + } + + None + } + + // Based on test vectors at + // https://github.com/zcash/zcash-test-vectors/blob/master/zcash_test_vectors/sapling/zip32.py + // https://github.com/zcash/zcash-test-vectors/blob/master/test-vectors/zcash/sapling_zip32.json + + #[test] + fn test_zip32_master() { + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let keys = zip32_sapling_derive_master(&seed); + assert_eq!( + hex::encode(keys.ask()), + "b6c00c93d36032b9a268e99e86a860776560bf0e83c1a10b51f607c954742506" + ); + assert_eq!( + hex::encode(keys.nsk()), + "8204ede83b2f1fbd84f9b45d7f996e2ebd0a030ad243b48ed39f748a8821ea06" + ); + assert_eq!( + hex::encode(keys.dk()), + "77c17cb75b7796afb39f0f3e91c924607da56fa9a20e283509bc8a3ef996a172" + ); + + let ak = sapling_ask_to_ak(&keys.ask()); + let nk = sapling_nsk_to_nk(&keys.nsk()); + assert_eq!( + hex::encode(ak), + "93442e5feffbff16e7217202dc7306729ffffe85af5683bce2642e3eeb5d3871" + ); + assert_eq!( + hex::encode(nk), + "dce8e7edece04b8950417f85ba57691b783c45b1a27422db1693dceb67b10106" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "4847a130e799d3dbea36a1c16467d621fb2d80e30b3b1d1a426893415dad6601" + ); + }) + } + + #[test] + fn test_zip32_master_empty() { + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = []; + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "b6c00c93d36032b9a268e99e86a860776560bf0e83c1a10b51f607c954742506" + ); + assert_eq!( + hex::encode(k.nsk()), + "8204ede83b2f1fbd84f9b45d7f996e2ebd0a030ad243b48ed39f748a8821ea06" + ); + assert_eq!( + hex::encode(k.dk()), + "77c17cb75b7796afb39f0f3e91c924607da56fa9a20e283509bc8a3ef996a172" + ); + + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + assert_eq!( + hex::encode(ak), + "93442e5feffbff16e7217202dc7306729ffffe85af5683bce2642e3eeb5d3871" + ); + assert_eq!( + hex::encode(nk), + "dce8e7edece04b8950417f85ba57691b783c45b1a27422db1693dceb67b10106" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "4847a130e799d3dbea36a1c16467d621fb2d80e30b3b1d1a426893415dad6601" + ); + + assert_eq!( + hex::encode(fvk.ak()), + "93442e5feffbff16e7217202dc7306729ffffe85af5683bce2642e3eeb5d3871" + ); + assert_eq!( + hex::encode(fvk.nk()), + "dce8e7edece04b8950417f85ba57691b783c45b1a27422db1693dceb67b10106" + ); + assert_eq!( + hex::encode(fvk.ovk()), + "395884890323b9d4933c021db89bcf767df21977b2ff0683848321a4df4afb21" + ); + }); + } + + #[test] + fn test_zip32_derivation_1() { + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = [1]; + + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "282bc197a516287c8ea8f68c424abad302b45cdf95407961d7b8b455267a350c" + ); + assert_eq!( + hex::encode(k.nsk()), + "e7a32988fdca1efcd6d1c4c562e629c2e96b2c3f7eda04ac4efd1810ff6bba01" + ); + assert_eq!( + hex::encode(k.dk()), + "e04de832a2d791ec129ab9002b91c9e9cdeed79241a7c4960e5178d870c1b4dc" + ); + + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + assert_eq!( + hex::encode(ak), + "dc14b514d3a92594c21925af2f7765a547b30e73fa7b700ea1bff2e5efaaa88b" + ); + assert_eq!( + hex::encode(nk), + "6152eb7fdb252779ddcb95d217ea4b6fd34036e9adadb3b5c9cbeceb41ba452a" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "155a8ee205d3872d12f8a3e639914633c23cde1f30ed5051e52130b1d0104c06" + ); + + assert_eq!( + hex::encode(fvk.ak()), + "dc14b514d3a92594c21925af2f7765a547b30e73fa7b700ea1bff2e5efaaa88b" + ); + assert_eq!( + hex::encode(fvk.nk()), + "6152eb7fdb252779ddcb95d217ea4b6fd34036e9adadb3b5c9cbeceb41ba452a" + ); + assert_eq!( + hex::encode(fvk.ovk()), + "5f1381fc8886da6a02dffeefcf503c40fa8f5a36f7a7142fd81b5518c5a47474" + ); + }); + } + + #[test] + fn test_zip32_derivation_1_hard() { + crate::tests::setup_logging(); + + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = [1 + ZIP32_HARDENED]; + + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "d5f7e92efb7abe04dc8c148b0b3b0fc23e0429f00208ff93b68d21a6e131bd04" + ); + assert_eq!( + hex::encode(k.nsk()), + "372a7c6822cbe603f3465c4b9b6558f3a3512decd434012e67bffcf657e5750a" + ); + assert_eq!( + hex::encode(k.dk()), + "f288400fd65f9adfe3a7c3720aceee0dae050d0a819d619f92e9e2cb4434d526" + ); + + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + assert_eq!( + hex::encode(ak), + "cfca79d337bc689813e409a54e3e72ad8e2f703ae6f8223c9becbde9a8a35f53" + ); + assert_eq!( + hex::encode(nk), + "513de64085d35a3adf23d89d5a21cdee4db4c625bd6a3c3c624bef4344141deb" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "f6e75cd980c30eabc61f49ac68f488573ab3e6afe15376375d34e406702ffd02" + ); + + assert_eq!( + hex::encode(fvk.ak()), + "cfca79d337bc689813e409a54e3e72ad8e2f703ae6f8223c9becbde9a8a35f53" + ); + assert_eq!( + hex::encode(fvk.nk()), + "513de64085d35a3adf23d89d5a21cdee4db4c625bd6a3c3c624bef4344141deb" + ); + assert_eq!( + hex::encode(fvk.ovk()), + "2530761933348c1fcf14355433a8d291167fbb37b2ce37ca97160a47ec331c69" + ); + }) + } + + #[test] + fn test_zip32_derivation_2() { + crate::tests::setup_logging(); + + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = [1, 2 + ZIP32_HARDENED]; + + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "8be8113cee3413a71f82c41fc8da517be134049832e6825c92da6b84fee4c60d" + ); + assert_eq!( + hex::encode(k.nsk()), + "3778059dc569e7d0d32391573f951bbde92fc6b9cf614773661c5c273aa6990c" + ); + assert_eq!( + hex::encode(k.dk()), + "a3eda19f9eff46ca12dfa1bf10371b48d1b4a40c4d05a0d8dce0e7dc62b07b37" + ); + + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + assert_eq!( + hex::encode(ak), + "a6c5925a0f85fa4f1e405e3a4970d0c4a4b4814438f4e9d4520e20f7fdcf3841" + ); + assert_eq!( + hex::encode(nk), + "304e305916216beb7b654d8aae50ecd188fcb384bc36c00c664f307725e2ee11" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "a2a13c1e38b45984445803e430a683c90bb2e14d4c8692ff253a6484dd9bb504" + ); + + assert_eq!( + hex::encode(fvk.ak()), + "a6c5925a0f85fa4f1e405e3a4970d0c4a4b4814438f4e9d4520e20f7fdcf3841" + ); + assert_eq!( + hex::encode(fvk.nk()), + "304e305916216beb7b654d8aae50ecd188fcb384bc36c00c664f307725e2ee11" + ); + assert_eq!( + hex::encode(fvk.ovk()), + "cf81182e96223c028ce3d6eb4794d3113b95069d14c57588e193b65efc2813bc" + ); + }) + } + + #[test] + fn test_zip32_derivation_2_hard() { + crate::tests::setup_logging(); + + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = [1 + ZIP32_HARDENED, 2 + ZIP32_HARDENED]; + + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "7ff35db69e13c36f59ad9c08d32d5227378da0cff971fd424baef9a6332f5106" + ); + assert_eq!( + hex::encode(k.nsk()), + "779c6ee4a03944eba28bc9bdc1329a391407f48c410d5ae0a364f59959bfde00" + ); + assert_eq!( + hex::encode(k.dk()), + "e4699e9a86e031c54b21cdd0960ac18ddd61ec9f7ae98d5582a6faf65f3248d1" + ); + + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + assert_eq!( + hex::encode(ak), + "9a853f9544713797e0851764da392e68534b1d948dae4742ee765c727572ab4e" + ); + assert_eq!( + hex::encode(nk), + "f166a28a4f88cec12141a82d2120bd6d8caf879c9a1b3ad2118501364f5d4fbe" + ); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "33bd46015a2cad17d6e015eb88861b0c917796246570521c9e1ae4b1c8311d06" + ); + + assert_eq!( + hex::encode(fvk.ak()), + "9a853f9544713797e0851764da392e68534b1d948dae4742ee765c727572ab4e" + ); + assert_eq!( + hex::encode(fvk.nk()), + "f166a28a4f88cec12141a82d2120bd6d8caf879c9a1b3ad2118501364f5d4fbe" + ); + assert_eq!( + hex::encode(fvk.ovk()), + "d9fc7101bf907f41886a7330a5d6a7bd23535e305eb7679bc23d7605936185ac" + ); + }); + } + + #[test] + fn test_zip32_childaddress() { + let seed = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + with_device_seed_context(seed, || { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, 0x8000_0001]; + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + assert_eq!( + hex::encode(k.ask()), + "1958fca13501c7d69c98b216daac75dbdc55cfec4b7990fd9d125d2f5f9ed603" + ); + assert_eq!( + hex::encode(k.nsk()), + "248b84b0c4640ab52fbed8b7263c85bad59cf17506a3ed78c9c683d7e6de7800" + ); + + let ivk = sapling_aknk_to_ivk(&fvk.ak(), &fvk.nk()); + assert_eq!( + hex::encode(ivk), + "f71c77c659a641f59a2c8ed0df0c55febd8243a69f09cc39f6024deeeb30fc00" + ); + + let mut listbytes = diversifier_list20_zero(); + let div_start = diversifier_zero(); + diversifier_get_list_large(&k.dk(), &div_start, &mut listbytes); + + let default_d = find_valid_diversifier(&listbytes).unwrap(); + + let pk_d = pkd_default(&ivk, &default_d); + + assert_eq!(hex::encode(default_d), "9f6e0bf90a18fc0b9b83ae"); + assert_eq!( + hex::encode(pk_d), + "9f23ad4358648638482b5def8975635b66fd8a708335f9235a3186ec0f033f84" + ); + }); + } + + #[test] + fn test_zip32_childaddress_ledgerkey() { + let s = hex::decode("b08e3d98da431cef4566a13c1bb348b982f7d8e743b43bb62557ba51994b1257") + .expect("error"); + let seed: [u8; 32] = s.as_slice().try_into().expect("error decoding seed"); + + with_device_seed_context(seed, || { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, 0x8000_1000]; + let k = zip32_sapling_derive(&path); + let fvk = zip32_sapling_fvk(&k); + + let ivk = sapling_aknk_to_ivk(&fvk.ak(), &fvk.nk()); + + assert_eq!( + hex::encode(ivk), + "0daa34a185ad9e97219390dac6df6cd14c26163462de20856fbc50e3c0cadc05" + ); + + let mut listbytes = diversifier_list20_zero(); + let div_start = diversifier_zero(); + diversifier_get_list_large(&k.dk(), &div_start, &mut listbytes); + + let default_d = find_valid_diversifier(&listbytes).unwrap(); + + let pk_d = pkd_default(&ivk, &default_d); + + assert_eq!(hex::encode(default_d), "186df0ee24e1b09f3a8c0f"); + assert_eq!( + hex::encode(pk_d), + "3b46a69761d5d22c186d922791e19c9e2d043f70bb4616668863bf2d5def0409" + ); + }); + } + + #[test] + fn test_zip32_master_address_ledgerkey() { + let s = hex::decode("b08e3d98da431cef4566a13c1bb348b982f7d8e743b43bb62557ba51994b1257") + .expect("error decoding hex"); + let seed: [u8; 32] = s.as_slice().try_into().expect(""); + + with_device_seed_context(seed, || { + let k = zip32_sapling_derive_master(&seed); + + let ask = k.ask(); + let nsk = k.nsk(); + let nk: [u8; 32] = sapling_nsk_to_nk(&nsk); + let ak: [u8; 32] = sapling_ask_to_ak(&ask); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + + let mut listbytes = diversifier_list20_zero(); + let div_start = diversifier_zero(); + diversifier_get_list_large(&k.dk(), &div_start, &mut listbytes); + + let default_d = find_valid_diversifier(&listbytes).unwrap(); + + let pk_d = pkd_default(&ivk, &default_d); + + assert_eq!(hex::encode(default_d), "f93dcfe2047253eebc17d4"); + assert_eq!( + hex::encode(pk_d), + "dc351792496b9d014e626c3bc929e6d32f507fb80b664f5cae97d37bf742dba9" + ); + }); + } + + #[test] + fn test_zip32_master_address_allzero() { + let seed = [0u8; 32]; + with_device_seed_context(seed, || { + let k = zip32_sapling_derive_master(&seed); + + let ask = k.ask(); + let nsk = k.nsk(); + let nk = sapling_nsk_to_nk(&nsk); + let ak = sapling_ask_to_ak(&ask); + + let ivk = sapling_aknk_to_ivk(&ak, &nk); + + let mut listbytes = diversifier_list20_zero(); + let div_start = diversifier_zero(); + diversifier_get_list_large(&k.dk(), &div_start, &mut listbytes); + + let default_d = find_valid_diversifier(&listbytes).unwrap(); + + let pk_d = pkd_default(&ivk, &default_d); + + assert_eq!(hex::encode(default_d), "3bf6fa1f83bf4563c8a713"); + assert_eq!( + hex::encode(pk_d), + "0454c014135ec695a1860f8d65b373546b623f388abbecd0c8b2111abdec301d" + ); + }); + } + + #[test] + fn test_div() { + let nk = hex::decode("f7cf9e77f2e58683383c1519ac7b062d30040e27a725fb88fb19a978bd3fd6ba") + .expect("error decoding hex") + .try_into() + .unwrap(); + let ak = hex::decode("f344ec380fe1273e3098c2588c5d3a791fd7ba958032760777fd0efa8ef11620") + .expect("error decoding hex") + .try_into() + .unwrap(); + + let ivk: [u8; 32] = sapling_aknk_to_ivk(&ak, &nk); + let default_d = [ + 0xf1, 0x9d, 0x9b, 0x79, 0x7e, 0x39, 0xf3, 0x37, 0x44, 0x58, 0x39, + ]; + + let result = pkd_group_hash(&default_d); + let x = AffinePoint::from_bytes(result); + if x.is_some().unwrap_u8() == 1 { + let y = super::ExtendedPoint::from(x.unwrap()); + let v = y.to_niels().multiply_bits(&ivk); + let t = super::AffinePoint::from(v); + let pk_d = t.to_bytes(); + assert_eq!( + hex::encode(pk_d), + "db4cd2b0aac4f7eb8ca131f16567c445a9555126d3c29f14e3d776e841ae7415" + ); + } + } + + #[test] + fn test_default_diversifier_fromlist() { + let seed = [0u8; 32]; + + let mut list = diversifier_list20_zero(); + let div_start = diversifier_zero(); + diversifier_get_list_large(&seed, &div_start, &mut list); + + let default_d = find_valid_diversifier(&list).unwrap(); + let expected_d = "dce77ebcec0a26afd6998c"; + assert_eq!(hex::encode(default_d), expected_d); + } + + #[test] + fn test_grouphash_default() { + let default_d = hex::decode("f19d9b797e39f337445839") + .expect("error decoding hex") + .try_into() + .unwrap(); + + let result = pkd_group_hash(&default_d); + + let x = AffinePoint::from_bytes(result); + assert_eq!(x.is_some().unwrap_u8(), 1); + + let expected_result = "3a71e348169e0cedbc4f3633a260d0e785ea8f8927ce4501cef3216ed075cea2"; + assert_eq!(hex::encode(result), expected_result); + } + + #[test] + fn test_ak() { + let sk_m = [0u8; 32]; + + let ask: [u8; 32] = zip32_sapling_ask_m(&sk_m); + assert_eq!( + hex::encode(ask), + "8548a14a473ea547aa2378402044f818cf1911cf5dd2054f678345f00d0e8806" + ); + let ak: [u8; 32] = sapling_ask_to_ak(&ask); + assert_eq!( + hex::encode(ak), + "f344ec380fe1273e3098c2588c5d3a791fd7ba958032760777fd0efa8ef11620" + ); + } + + #[test] + fn test_nk() { + let seed = [0u8; 32]; + + let nsk = zip32_sapling_nsk_m(&seed); + let nk = sapling_nsk_to_nk(&nsk); + + assert_eq!( + hex::encode(nsk), + "30114ea0dd0bb61cf0eaeab6ec3331f581b0425e27338501262d7eac745e6e05" + ); + assert_eq!( + hex::encode(nk), + "f7cf9e77f2e58683383c1519ac7b062d30040e27a725fb88fb19a978bd3fd6ba" + ); + } + + #[test] + fn test_ivk() { + let nk = hex::decode("f7cf9e77f2e58683383c1519ac7b062d30040e27a725fb88fb19a978bd3fd6ba") + .expect("error decoding hex") + .try_into() + .unwrap(); + let ak = hex::decode("f344ec380fe1273e3098c2588c5d3a791fd7ba958032760777fd0efa8ef11620") + .expect("error decoding hex") + .try_into() + .unwrap(); + + let ivk: [u8; 32] = sapling_aknk_to_ivk(&ak, &nk); + assert_eq!( + hex::encode(ivk), + "b70b7cd0ed03cbdfd7ada9502ee245b13e569d54a5719d2daa0f5f1451479204" + ); + } +} diff --git a/app/rust/src/zip32_extern.rs b/app/rust/src/zip32_extern.rs new file mode 100644 index 00000000..5be8b948 --- /dev/null +++ b/app/rust/src/zip32_extern.rs @@ -0,0 +1,195 @@ +use jubjub::Fr; + +use crate::bolos::{c_device_seed, c_zemu_log_stack}; +use crate::constants::{DIV_DEFAULT_LIST_LEN, DIV_SIZE, ZIP32_COIN_TYPE, ZIP32_PURPOSE}; +use crate::sapling::{ + sapling_aknk_to_ivk, sapling_ask_to_ak, sapling_asknsk_to_ivk, sapling_nsk_to_nk, +}; +use crate::types::{ + diversifier_zero, Diversifier, DiversifierList10, DiversifierList20, DiversifierList4, + FullViewingKey, IvkBytes, NskBytes, Zip32Seed, +}; +use crate::zip32::{diversifier_group_hash_light, zip32_sapling_derive, zip32_sapling_fvk}; +use crate::{sapling, zip32}; + +#[no_mangle] +pub extern "C" fn zip32_ivk(account: u32, ivk_ptr: *mut IvkBytes) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let ivk = unsafe { &mut *ivk_ptr }; + + crate::bolos::heartbeat(); + + let k = zip32_sapling_derive(&path); + let ak = sapling_ask_to_ak(&k.ask()); + let nk = sapling_nsk_to_nk(&k.nsk()); + + let tmp_ivk = sapling_aknk_to_ivk(&ak, &nk); + + ivk.copy_from_slice(&tmp_ivk) +} + +// This only tries to find ONE diversifier!!! +// Related to handleGetKeyIVK +#[no_mangle] +pub extern "C" fn diversifier_find_valid(zip32_account: u32, div_ptr: *mut Diversifier) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, zip32_account]; + let div_out = unsafe { &mut *div_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + let dk = key_bundle.dk(); + + let start = diversifier_zero(); + div_out.copy_from_slice(&zip32::diversifier_find_valid(&dk, &start)); +} + +//this function is consistent with zecwallet code +#[no_mangle] +pub extern "C" fn zip32_ovk(account: u32, ovk_ptr: *mut [u8; 32]) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let ovk = unsafe { &mut *ovk_ptr }; + + crate::bolos::heartbeat(); + + let key_bundle = zip32_sapling_derive(&path); + + ovk.copy_from_slice(&key_bundle.ovk()); +} + +//this function is consistent with zecwallet code +#[no_mangle] +pub extern "C" fn zip32_fvk(account: u32, fvk_ptr: *mut FullViewingKey) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let fvk_out = unsafe { &mut *fvk_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + + let fvk = zip32_sapling_fvk(&key_bundle); + + fvk_out.to_bytes_mut().copy_from_slice(fvk.to_bytes()); +} + +#[no_mangle] +pub extern "C" fn zip32_child_proof_key( + account: u32, + ak_ptr: *mut [u8; 32], + nsk_ptr: *mut [u8; 32], +) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let ak = unsafe { &mut *ak_ptr }; + let nsk = unsafe { &mut *nsk_ptr }; + + let k = zip32_sapling_derive(&path); + + ak.copy_from_slice(&sapling_ask_to_ak(&k.ask())); + nsk.copy_from_slice(&k.nsk()); +} + +#[no_mangle] +pub extern "C" fn zip32_child_ask_nsk( + account: u32, + ask_ptr: *mut [u8; 32], + nsk_ptr: *mut [u8; 32], +) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let ask = unsafe { &mut *ask_ptr }; + let nsk = unsafe { &mut *nsk_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + + ask.copy_from_slice(&key_bundle.ask()); + nsk.copy_from_slice(&key_bundle.nsk()); +} + +#[no_mangle] +pub extern "C" fn zip32_nsk(account: u32, nsk_ptr: *mut NskBytes) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let nsk = unsafe { &mut *nsk_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + + nsk.copy_from_slice(&key_bundle.nsk()); +} + +// This will generate a list of 20 diversifiers starting from the given diversifier +// related to handleGetDiversifierList +#[no_mangle] +pub extern "C" fn diversifier_get_list( + zip32_account: u32, + start_index: *const Diversifier, + diversifier_list_ptr: *mut DiversifierList20, +) { + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, zip32_account]; + let start = unsafe { &*start_index }; + let diversifier = unsafe { &mut *diversifier_list_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + + zip32::diversifier_get_list_large(&key_bundle.dk(), start, diversifier); +} + +#[no_mangle] +pub extern "C" fn get_pkd( + account: u32, + diversifier_ptr: *const Diversifier, + pkd_ptr: *mut [u8; 32], +) { + let ivk_ptr = &mut [0u8; 32]; + let diversifier = unsafe { &*diversifier_ptr }; + let pkd = unsafe { &mut *pkd_ptr }; + + zip32_ivk(account, ivk_ptr); + + let tmp_pkd = zip32::pkd_default(ivk_ptr, diversifier); + pkd.copy_from_slice(&tmp_pkd) +} + +#[no_mangle] +pub extern "C" fn get_pkd_from_seed( + account: u32, + start_diversifier: *mut Diversifier, + div_ptr: *mut Diversifier, + pkd_ptr: *mut [u8; 32], +) { + crate::bolos::heartbeat(); + let path = [ZIP32_PURPOSE, ZIP32_COIN_TYPE, account]; + let start = unsafe { &mut *start_diversifier }; + let div_out = unsafe { &mut *div_ptr }; + + let key_bundle = zip32_sapling_derive(&path); + let dk = key_bundle.dk(); + + div_out.copy_from_slice(&zip32::diversifier_find_valid(&dk, start)); + crate::bolos::heartbeat(); + + let ivk = sapling_asknsk_to_ivk(&key_bundle.ask(), &key_bundle.nsk()); + crate::bolos::heartbeat(); + let tmp_pkd = zip32::pkd_default(&ivk, div_out); + + let pkd_out = unsafe { &mut *pkd_ptr }; + pkd_out.copy_from_slice(&tmp_pkd); +} + +#[no_mangle] +pub extern "C" fn randomized_secret_from_seed( + account: u32, + alpha_ptr: *const [u8; 32], + output_ptr: *mut [u8; 32], +) { + let mut ask = [0u8; 32]; + let mut nsk = [0u8; 32]; + let alpha = unsafe { &*alpha_ptr }; + let output = unsafe { &mut *output_ptr }; + + zip32_child_ask_nsk(account, &mut ask, &mut nsk); + + let mut skfr = Fr::from_bytes(&ask).unwrap(); + let alphafr = Fr::from_bytes(alpha).unwrap(); + skfr += alphafr; + output.copy_from_slice(&skfr.to_bytes()); +} + +#[no_mangle] +pub extern "C" fn diversifier_is_valid(div_ptr: *const Diversifier) -> bool { + let div = unsafe { &*div_ptr }; + diversifier_group_hash_light(div) +} diff --git a/app/src/c_api/rust.c b/app/src/c_api/rust.c new file mode 100644 index 00000000..3db15bae --- /dev/null +++ b/app/src/c_api/rust.c @@ -0,0 +1,143 @@ +//#include "aes.h" +#include "coin.h" +#include "cx.h" +//#include "jubjub.h" +#include "os.h" +#include +#include +#include +#include +//#include "zcash_utils.h" + +#define CTX_REDJUBJUB "MASP__RedJubjubH" +#define CTX_REDJUBJUB_LEN 16 +#define CTX_REDJUBJUB_HASH_LEN 64 + +#define CTX_EXPAND_SEED "MASP__ExpandSeed" +#define CTX_EXPAND_SEED_LEN 16 +#define CTX_EXPAND_SEED_HASH_LEN 64 + +#include +#include +#include + +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || \ + defined(TARGET_NANOS2) || defined(TARGET_STAX) +#include "lcx_rng.h" +unsigned char *bolos_cx_rng(uint8_t *buffer, size_t len) { + cx_rng_no_throw(buffer, len); + return buffer; +} +#endif + +zxerr_t c_blake2b32_withpersonal(const uint8_t *person, const uint8_t *a, + uint32_t a_len, uint8_t *out) { + if (person == NULL || a == NULL || out == NULL) { + return zxerr_no_data; + } + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t *)person, 16)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, a, a_len, out, 256)); + return zxerr_ok; +}; + +zxerr_t c_blake2b64_withpersonal(const uint8_t *person, const uint8_t *a, + uint32_t a_len, uint8_t *out) { + if (person == NULL || a == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 512, NULL, 0, (uint8_t *)person, 16)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, a, a_len, out, 512)); + return zxerr_ok; +}; + +zxerr_t c_zcash_blake2b_redjubjub(const uint8_t *a, uint32_t a_len, + const uint8_t *b, uint32_t b_len, uint8_t *out) { + if (a == NULL || b == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 8 * CTX_REDJUBJUB_HASH_LEN, NULL, 0,(uint8_t *)CTX_REDJUBJUB, CTX_REDJUBJUB_LEN)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, a, a_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, b, b_len, out, CTX_REDJUBJUB_HASH_LEN)); + return zxerr_ok; +} + +zxerr_t c_zcash_blake2b_expand_seed(const uint8_t *a, uint32_t a_len, + const uint8_t *b, uint32_t b_len, uint8_t *out) { + if (a == NULL || b == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 8 * CTX_EXPAND_SEED_HASH_LEN, NULL, 0,(uint8_t *)CTX_EXPAND_SEED, CTX_EXPAND_SEED_LEN)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, a, a_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, b, b_len, out,CTX_EXPAND_SEED_HASH_LEN)); + return zxerr_ok; +} + +zxerr_t c_zcash_blake2b_expand_vec_two(const uint8_t *a, uint32_t a_len, + const uint8_t *b, uint32_t b_len, + const uint8_t *c, uint32_t c_len, + uint8_t *out) { + if (a == NULL || b == NULL || c == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 8 * CTX_EXPAND_SEED_HASH_LEN, NULL, 0, (uint8_t *)CTX_EXPAND_SEED, CTX_EXPAND_SEED_LEN)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, a, a_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, b, b_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, c, c_len, out, CTX_EXPAND_SEED_HASH_LEN)); + return zxerr_ok; +} + +zxerr_t c_zcash_blake2b_expand_vec_four(const uint8_t *a, uint32_t a_len, + const uint8_t *b, uint32_t b_len, + const uint8_t *c, uint32_t c_len, + const uint8_t *d, uint32_t d_len, + const uint8_t *e, uint32_t e_len, + uint8_t *out) { + if (a == NULL || b == NULL || c == NULL || d == NULL || e == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t ctx = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 8 * CTX_EXPAND_SEED_HASH_LEN, NULL, 0, (uint8_t *)CTX_EXPAND_SEED, CTX_EXPAND_SEED_LEN)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, a, a_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, b, b_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, c, c_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, d, d_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, e, e_len, out, CTX_EXPAND_SEED_HASH_LEN)); + return zxerr_ok; +} + +zxerr_t zcash_blake2b_hash_two(const uint8_t *perso, uint32_t perso_len, + const uint8_t *a, uint32_t a_len, const uint8_t *b, + uint32_t b_len, uint8_t *out, uint32_t out_len) { + if (perso == NULL || a == NULL || b == NULL || out == NULL) { + return zxerr_no_data; + } + + cx_blake2b_t zcashHashBlake2b = {0}; + CHECK_CX_OK(cx_blake2b_init2_no_throw(&zcashHashBlake2b, 8 * out_len, NULL, 0, (uint8_t *)perso, perso_len)); + CHECK_CX_OK(cx_hash_no_throw(&zcashHashBlake2b.header, 0, a, a_len, NULL, 0)); + CHECK_CX_OK(cx_hash_no_throw(&zcashHashBlake2b.header, CX_LAST, b, b_len, out, out_len)); + return zxerr_ok; +} + +uint16_t fp_uint64_to_str(char *out, uint16_t outLen, const uint64_t value, + uint8_t decimals) { + return fpuint64_to_str(out, outLen, value, decimals); +} + +void check_canary() {} + +void _zemu_log_stack(uint8_t *buffer) { zemu_log_stack((char *)buffer); } + +void io_heartbeat() { + io_seproxyhal_io_heartbeat(); +} diff --git a/app/src/coin.h b/app/src/coin.h index e46c05c7..1055fa66 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -33,6 +33,8 @@ extern "C" { #define HDPATH_3_DEFAULT (0u) #define HDPATH_4_DEFAULT (0u) +#define MASK_HARDENED 0x80000000 + #define SECP256K1_PK_LEN 65u #define COMPRESSED_SECP256K1_PK_LEN 33u #define SECP256K1_SK_LEN 32u @@ -74,6 +76,7 @@ extern "C" { /// For payment addresses on the Testnet, the Human-Readable Part is "patest" #define SAPLING_PAYMENT_ADDR_HRP "patest" +#define HDPATH_LEN_BIP44 5 #define COIN_AMOUNT_DECIMAL_PLACES 6 #define COIN_TICKER "NAM " diff --git a/app/src/constants.h b/app/src/constants.h index 5b376adc..cd50dcea 100644 --- a/app/src/constants.h +++ b/app/src/constants.h @@ -20,3 +20,4 @@ #define CHECKSUM_SIZE 4 #define VERSION_P2SH 0x1CBD #define VERSION_P2PKH 0x1CB8 +#define ED25519_SK_SIZE 64 diff --git a/app/src/crypto.c b/app/src/crypto.c index 64fc75af..e4099191 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -486,12 +486,8 @@ static zxerr_t computeKeys(keys_t * saplingKeys) { } // Compute ask, nsk, ovk - CHECK_PARSER_OK(convertKey(saplingKeys->spendingKey, MODIFIER_ASK, saplingKeys->ask, true)); - CHECK_PARSER_OK(convertKey(saplingKeys->spendingKey, MODIFIER_NSK, saplingKeys->nsk, true)); - CHECK_PARSER_OK(convertKey(saplingKeys->spendingKey, MODIFIER_OVK, saplingKeys->ovk, true)); - - // Compute diversifier key - dk - CHECK_PARSER_OK(convertKey(saplingKeys->spendingKey, MODIFIER_DK, saplingKeys->dk, true)); + zip32_child_ask_nsk(hdPath[2], saplingKeys->ask, saplingKeys->nsk); + zip32_ovk(hdPath[2], saplingKeys->ovk); // Compute ak, nk, ivk CHECK_PARSER_OK(generate_key(saplingKeys->ask, SpendingKeyGenerator, saplingKeys->ak)); @@ -499,10 +495,10 @@ static zxerr_t computeKeys(keys_t * saplingKeys) { CHECK_PARSER_OK(computeIVK(saplingKeys->ak, saplingKeys->nk, saplingKeys->ivk)); // Compute diversifier - CHECK_PARSER_OK(computeDiversifier(saplingKeys->dk, saplingKeys->diversifier_start_index, saplingKeys->diversifier)); + diversifier_find_valid(hdPath[2], saplingKeys->diversifier); // Compute address - CHECK_PARSER_OK(computePkd(saplingKeys->ivk, saplingKeys->diversifier, saplingKeys->address)); + get_pkd(hdPath[2], saplingKeys->diversifier, saplingKeys->address); return zxerr_ok; } @@ -544,26 +540,29 @@ __Z_INLINE zxerr_t copyKeys(keys_t *saplingKeys, key_kind_e requestedKeys, uint8 return zxerr_ok; } -zxerr_t crypto_computeSaplingSeed(uint8_t spendingKey[static KEY_LENGTH]) { - if (spendingKey == NULL ) { - return zxerr_no_data; - } +zxerr_t crypto_fillDeviceSeed(uint8_t *device_seed) { + zemu_log_stack("crypto_fillDeviceSeed"); + + // Generate randomness using a fixed path related to the device mnemonic + const uint32_t path[HDPATH_LEN_BIP44] = { + HDPATH_0_DEFAULT, HDPATH_1_DEFAULT, MASK_HARDENED, MASK_HARDENED, MASK_HARDENED, + }; + + MEMZERO(device_seed, ED25519_SK_SIZE); + uint8_t raw_privkey[64]; // Allocate 64 bytes to respect Syscall API but only 32 will be used + zxerr_t error = zxerr_unknown; - uint8_t privateKeyData[2*KEY_LENGTH] = {0}; - CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, - CX_CURVE_Ed25519, - hdPath, - hdPathLen, - privateKeyData, - NULL, NULL, 0)); - memcpy(spendingKey, privateKeyData, KEY_LENGTH); - error = zxerr_ok; + io_seproxyhal_io_heartbeat(); + CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_ED25519_SLIP10, CX_CURVE_Ed25519, path, HDPATH_LEN_BIP44, raw_privkey, NULL, + NULL, 0)); -catch_cx_error: - MEMZERO(privateKeyData, sizeof(privateKeyData)); + io_seproxyhal_io_heartbeat(); + error = zxerr_ok; + MEMCPY(device_seed, raw_privkey, 32); - if(error != zxerr_ok) { - MEMZERO(spendingKey, KEY_LENGTH); +catch_cx_error: + if (error != zxerr_ok) { + MEMZERO(raw_privkey, 64); } return error; @@ -578,15 +577,6 @@ zxerr_t crypto_generateSaplingKeys(uint8_t *output, uint16_t outputLen, key_kind MEMZERO(output, outputLen); keys_t saplingKeys = {0}; - uint8_t sk[KEY_LENGTH] = {0}; - - // sk erased inside in case of error - CHECK_ZXERR(crypto_computeSaplingSeed(sk)) - - if (computeMasterFromSeed((const uint8_t*) sk, saplingKeys.spendingKey) != parser_ok) { - MEMZERO(sk, sizeof(sk)); - return zxerr_unknown; - } error = computeKeys(&saplingKeys); @@ -595,7 +585,6 @@ zxerr_t crypto_generateSaplingKeys(uint8_t *output, uint16_t outputLen, key_kind error = copyKeys(&saplingKeys, requestedKey, output, outputLen); } - MEMZERO(sk, sizeof(sk)); MEMZERO(&saplingKeys, sizeof(saplingKeys)); return error; } @@ -912,17 +901,10 @@ zxerr_t crypto_sign_masp(const parser_tx_t *txObj, uint8_t *output, uint16_t out } // Get keys - uint8_t sapling_seed[KEY_LENGTH] = {0}; keys_t keys = {0}; - CHECK_ZXERR(crypto_computeSaplingSeed(sapling_seed)); - if (computeMasterFromSeed(sapling_seed, keys.spendingKey)) { - MEMZERO(sapling_seed, sizeof(sapling_seed)); - return zxerr_unknown; - } if (computeKeys(&keys) != zxerr_ok || crypto_check_masp(txObj, &keys) != zxerr_ok || crypto_sign_spends_sapling(txObj, &keys) != zxerr_ok) { - MEMZERO(sapling_seed, sizeof(sapling_seed)); MEMZERO(&keys, sizeof(keys)); return zxerr_invalid_crypto_settings; } @@ -930,7 +912,6 @@ zxerr_t crypto_sign_masp(const parser_tx_t *txObj, uint8_t *output, uint16_t out //Hash buffer and retreive for verify purpose zxerr_t err = crypto_hash_messagebuffer(output, outputLen, tx_get_buffer(), tx_get_buffer_length()); - MEMZERO(sapling_seed, sizeof(sapling_seed)); MEMZERO(&keys, sizeof(keys)); return err; } diff --git a/app/src/crypto.h b/app/src/crypto.h index 88a07ca6..574bf905 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -35,6 +35,7 @@ zxerr_t crypto_fillMASP(uint8_t *buffer, uint16_t bufferLen, uint16_t *cmdRespon zxerr_t crypto_sign_masp(const parser_tx_t *txObj, uint8_t *output, uint16_t outputLen); zxerr_t crypto_extract_spend_signature(uint8_t *buffer, uint16_t bufferLen, uint16_t *cmdResponseLen); zxerr_t crypto_computeRandomness(masp_type_e type, uint8_t *out, uint16_t outLen, uint16_t *replyLen); +zxerr_t crypto_fillDeviceSeed(uint8_t *device_seed); #ifdef __cplusplus } #endif diff --git a/app/src/crypto_helper.c b/app/src/crypto_helper.c index 5086b27f..49e086de 100644 --- a/app/src/crypto_helper.c +++ b/app/src/crypto_helper.c @@ -446,35 +446,6 @@ zxerr_t crypto_serializeData(const uint64_t dataSize, uint8_t *buffer, uint16_t return zxerr_ok; } -// MASP Section -parser_error_t convertKey(const uint8_t spendingKey[KEY_LENGTH], const uint8_t modifier, uint8_t outputKey[KEY_LENGTH], - bool reduceWideByte) { - uint8_t output[64] = {0}; -#if defined(LEDGER_SPECIFIC) - cx_blake2b_t ctx = {0}; - ASSERT_CX_OK(cx_blake2b_init2_no_throw(&ctx, BLAKE2B_OUTPUT_LEN, NULL, 0, (uint8_t *)EXPANDED_SPEND_BLAKE2_KEY, - sizeof(EXPANDED_SPEND_BLAKE2_KEY))); - ASSERT_CX_OK(cx_blake2b_update(&ctx, spendingKey, KEY_LENGTH)); - ASSERT_CX_OK(cx_blake2b_update(&ctx, &modifier, 1)); - cx_blake2b_final(&ctx, output); -#else - blake2b_state state = {0}; - blake2b_init_with_personalization(&state, BLAKE2B_OUTPUT_LEN, (const uint8_t *)EXPANDED_SPEND_BLAKE2_KEY, - sizeof(EXPANDED_SPEND_BLAKE2_KEY)); - blake2b_update(&state, spendingKey, KEY_LENGTH); - blake2b_update(&state, &modifier, 1); - blake2b_final(&state, output, sizeof(output)); -#endif - - if (reduceWideByte) { - from_bytes_wide(output, outputKey); - } else { - memcpy(outputKey, output, KEY_LENGTH); - } - - return parser_ok; -} - parser_error_t generate_key(const uint8_t expandedKey[KEY_LENGTH], constant_key_t keyType, uint8_t output[KEY_LENGTH]) { if (keyType >= InvalidKey) { return parser_value_out_of_range; @@ -498,27 +469,6 @@ parser_error_t computeIVK(const ak_t ak, const nk_t nk, ivk_t ivk) { return parser_ok; } -parser_error_t computeMasterFromSeed(const uint8_t seed[KEY_LENGTH], uint8_t master_sk[KEY_LENGTH]) { - if(seed == NULL || master_sk == NULL) { - return parser_unexpected_error; - } -#if defined(LEDGER_SPECIFIC) - cx_blake2b_t ctx = {0}; - ASSERT_CX_OK(cx_blake2b_init2_no_throw(&ctx, BLAKE2B_OUTPUT_LEN, NULL, 0, (uint8_t *)SAPLING_MASTER_PERSONALIZATION, - sizeof(SAPLING_MASTER_PERSONALIZATION))); - ASSERT_CX_OK(cx_blake2b_update(&ctx, seed, KEY_LENGTH)); - cx_blake2b_final(&ctx, master_sk); -#else - blake2b_state state = {0}; - blake2b_init_with_personalization(&state, BLAKE2B_OUTPUT_LEN, (const uint8_t *)SAPLING_MASTER_PERSONALIZATION, - sizeof(SAPLING_MASTER_PERSONALIZATION)); - blake2b_update(&state, seed, KEY_LENGTH); - blake2b_final(&state, master_sk, EXTENDED_KEY_LENGTH); -#endif - - return parser_ok; -} - bool check_diversifier(const uint8_t d[DIVERSIFIER_LENGTH]) { if(d == NULL) { return parser_unexpected_error; @@ -535,72 +485,6 @@ bool check_diversifier(const uint8_t d[DIVERSIFIER_LENGTH]) { return is_valid_diversifier(hash); } -// Return list with 4 diversifiers, starting computing form start_index -parser_error_t computeDiversifiersList(const uint8_t dk[KEY_LENGTH], uint8_t start_index[DIVERSIFIER_LENGTH], uint8_t diversifier_list[DIVERSIFIER_LIST_LENGTH]) { - if(dk == NULL || start_index == NULL || diversifier_list == NULL) { - return parser_unexpected_error; - } - - return get_default_diversifier_list(dk, start_index, diversifier_list); -} - -static bool reached_max_index(uint8_t diversifier_index[DIVERSIFIER_LENGTH]) { - for (int i = 0; i < DIVERSIFIER_LENGTH; i++) { - if (diversifier_index[i] != UINT8_MAX) { - return false; - } - } - return true; -} -// Return a valid diversifier from the diversifier list, if not found, compute a new list, strating from the incremented -// start_index -parser_error_t computeDiversifier(const uint8_t dk[KEY_LENGTH], uint8_t start_index[DIVERSIFIER_LENGTH], uint8_t diversifier[DIVERSIFIER_LENGTH]) { - bool found = false; - uint8_t diversifier_list[DIVERSIFIER_LIST_LENGTH] = {0}; - - while (!found) - { - CHECK_ERROR(computeDiversifiersList(dk, start_index, diversifier_list)); - for (uint8_t i = 0; i < 4; i++) - { - uint8_t d[DIVERSIFIER_LENGTH] = {0}; - memcpy(d, diversifier_list + i*DIVERSIFIER_LENGTH, DIVERSIFIER_LENGTH); - if (check_diversifier(d) && !found) - { - memcpy(diversifier, d, DIVERSIFIER_LENGTH); - found = true; - break; - } - } - - if (reached_max_index(start_index)) - { - return parser_diversifier_not_found; - } - } - - return parser_ok; -} - -parser_error_t computePkd(const uint8_t ivk[KEY_LENGTH], const uint8_t diversifier[DIVERSIFIER_LENGTH], uint8_t pk_d[KEY_LENGTH]) { - if(ivk == NULL || diversifier == NULL || pk_d == NULL) { - return parser_unexpected_error; - } - - uint8_t hash[32] = {0}; - - blake2s_state state = {0}; - blake2s_init_with_personalization(&state, 32, (const uint8_t *)KEY_DIVERSIFICATION_PERSONALIZATION, sizeof(KEY_DIVERSIFICATION_PERSONALIZATION)); - blake2s_update(&state, (const uint8_t *)GH_FIRST_BLOCK, sizeof(GH_FIRST_BLOCK)); - blake2s_update(&state, diversifier, DIVERSIFIER_LENGTH); - blake2s_final(&state, hash, KEY_LENGTH); - - zemu_log_stack("computePkd got hash"); - CHECK_ERROR(get_pkd(ivk, hash, pk_d)); - zemu_log_stack("computePkd after get_pkd"); - return parser_ok; -} - static void u64_to_bytes(uint64_t value, uint8_t array[32]) { MEMZERO(array, 32); diff --git a/app/src/crypto_helper.h b/app/src/crypto_helper.h index 2cfb133b..adfb1255 100644 --- a/app/src/crypto_helper.h +++ b/app/src/crypto_helper.h @@ -59,13 +59,8 @@ zxerr_t ensureBip32(); zxerr_t ensureZip32(); // MASP SECTION -parser_error_t convertKey(const uint8_t spendingKey[KEY_LENGTH], const uint8_t modifier, uint8_t outputKey[KEY_LENGTH], bool reduceWideByte); parser_error_t generate_key(const uint8_t expandedKey[KEY_LENGTH], constant_key_t keyType, uint8_t output[KEY_LENGTH]); parser_error_t computeIVK(const ak_t ak, const nk_t nk, ivk_t ivk); -parser_error_t computeMasterFromSeed(const uint8_t seed[KEY_LENGTH], uint8_t master_sk[EXTENDED_KEY_LENGTH]); -parser_error_t computeDiversifiersList(const uint8_t dk[KEY_LENGTH], uint8_t div_start_index[DIVERSIFIER_LENGTH], uint8_t diversifier_list[DIVERSIFIER_LIST_LENGTH]); -parser_error_t computeDiversifier(const uint8_t dk[KEY_LENGTH], uint8_t start_index[DIVERSIFIER_LENGTH], uint8_t diversifier[DIVERSIFIER_LENGTH]); -parser_error_t computePkd(const uint8_t ivk[KEY_LENGTH], const uint8_t diversifier[DIVERSIFIER_LENGTH], uint8_t pk_d[KEY_LENGTH]); parser_error_t computeValueCommitment(uint64_t value, uint8_t *rcv, uint8_t *identifier, uint8_t *cv); parser_error_t computeRk(keys_t *keys, uint8_t *alpha, uint8_t *rk); parser_error_t crypto_encodeLargeBech32( const uint8_t *address, size_t addressLen, uint8_t *output, size_t outputLen, bool paymentAddr); diff --git a/app/src/keys_def.h b/app/src/keys_def.h index 271001c3..63ae4e93 100644 --- a/app/src/keys_def.h +++ b/app/src/keys_def.h @@ -35,6 +35,7 @@ typedef enum { #define EXTENDED_KEY_LENGTH 64 #define DIVERSIFIER_LENGTH 11 #define DIVERSIFIER_LIST_LENGTH 44 +#define ZIP32_SEED_SIZE 64 typedef uint8_t spending_key_t[KEY_LENGTH]; typedef uint8_t ask_t[KEY_LENGTH]; typedef uint8_t nsk_t[KEY_LENGTH]; @@ -50,16 +51,13 @@ typedef uint8_t d_t[DIVERSIFIER_LENGTH]; typedef uint8_t public_address_t[KEY_LENGTH]; typedef struct { - spending_key_t spendingKey; ask_t ask; ak_t ak; nsk_t nsk; nk_t nk; - dk_t dk; ivk_t ivk; ovk_t ovk; d_t diversifier; - d_t diversifier_start_index; public_address_t address; } keys_t; diff --git a/app/ztruct/Cargo.lock b/app/ztruct/Cargo.lock new file mode 100644 index 00000000..2e6debcf --- /dev/null +++ b/app/ztruct/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "proc-macro2" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "ztruct" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/app/ztruct/Cargo.toml b/app/ztruct/Cargo.toml new file mode 100644 index 00000000..4088e30d --- /dev/null +++ b/app/ztruct/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ztruct" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +syn = { version = "2", features = ["full"] } +quote = "1.0" diff --git a/app/ztruct/src/lib.rs b/app/ztruct/src/lib.rs new file mode 100644 index 00000000..29b825a3 --- /dev/null +++ b/app/ztruct/src/lib.rs @@ -0,0 +1,117 @@ +extern crate proc_macro; + +use quote::{quote}; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro] +pub fn create_ztruct(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; // Struct name + let visibility = input.vis; // This captures the visibility specified in the macro input + let fields = match input.data { + syn::Data::Struct(s) => s.fields, + _ => panic!("Expected a struct"), + }; + + let mut total_size = quote! { 0 }; + let mut field_initializers = proc_macro2::TokenStream::new(); + let mut constructor_params = proc_macro2::TokenStream::new(); + let mut field_accessors = proc_macro2::TokenStream::new(); + let mut mutable_field_accessors = proc_macro2::TokenStream::new(); + let mut offsets = vec![]; + + for (i, f) in fields.iter().enumerate() { + let field_name = f.ident.clone().unwrap(); + let field_type = &f.ty; + let field_size = quote! { ::core::mem::size_of::<#field_type>() }; + + total_size = quote! { #total_size + #field_size }; + let offset = quote! { #total_size - #field_size }; + offsets.push((field_name.clone(), field_type.clone(), offset.clone())); + + let param = quote! { #field_name: #field_type }; + constructor_params.extend(param); + if i < fields.len() - 1 { + constructor_params.extend(quote! { , }); + } + + // Generate accessor for each field + let accessor = quote! { + pub fn #field_name(&self) -> #field_type { + let ptr = self.data.as_ptr() as *const u8; + unsafe { *(ptr.add(#offset) as *const #field_type) } + } + }; + field_accessors.extend(accessor); + + // Generate mutable accessor for each field with 'mut' suffix + let mutable_accessor_name = quote! { #field_name }.to_string() + "_mut"; + let mutable_accessor_ident = syn::Ident::new(&mutable_accessor_name, proc_macro2::Span::call_site()); + let mutable_accessor = quote! { + pub fn #mutable_accessor_ident(&mut self) -> &mut #field_type { + let ptr = self.data.as_mut_ptr() as *mut u8; + unsafe { &mut *(ptr.add(#offset) as *mut #field_type) } + } + }; + mutable_field_accessors.extend(mutable_accessor); + } + + for (field_name, field_type, offset) in &offsets { + let initializer = quote! { + let ptr = instance.data.as_mut_ptr() as *mut u8; + unsafe { + ::core::ptr::write(ptr.add(#offset) as *mut #field_type, #field_name); + } + }; + field_initializers.extend(initializer); + } + let from_bytes_method = quote! { + pub fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() == #total_size, "Byte slice length does not match struct size"); + let mut instance = Self { data: [0u8; #total_size] }; + instance.data.copy_from_slice(bytes); + instance + } + }; + + let buffer_accessors = quote! { + pub fn to_bytes(&self) -> &[u8] { + &self.data + } + + pub fn to_bytes_mut(&mut self) -> &mut [u8] { + &mut self.data + } + }; + + let empty_constructor = quote! { + pub fn empty() -> Self { + Self { data: [0u8; #total_size] } + } + }; + + let expanded = quote! { + #visibility struct #name { + data: [u8; #total_size], + } + + impl #name { + pub fn new(#constructor_params) -> Self { + let mut instance = Self { data: [0u8; #total_size] }; + #field_initializers + instance + } + + #empty_constructor + + #from_bytes_method + + #field_accessors + #mutable_field_accessors + #buffer_accessors + } + }; + + proc_macro::TokenStream::from(expanded) +} \ No newline at end of file diff --git a/app/ztruct/tests/simple.rs b/app/ztruct/tests/simple.rs new file mode 100644 index 00000000..52e496d5 --- /dev/null +++ b/app/ztruct/tests/simple.rs @@ -0,0 +1,74 @@ +use ztruct::create_ztruct; + +#[cfg(test)] +mod tests { + use super::*; + + create_ztruct! { + pub struct SimpleStruct { + pub f1: u32, + pub f2: u32, + } + } + + #[test] + fn test_new() { + let instance = SimpleStruct::new(0x01020304, 0x05060708); + assert_eq!(instance.f1(), 0x01020304); + assert_eq!(instance.f2(), 0x05060708); + assert_eq!(instance.to_bytes(), &[0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05]); + } + + #[test] + fn test_from_bytes() { + let bytes = [0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05]; + let instance = SimpleStruct::from_bytes(&bytes); + assert_eq!(instance.to_bytes(), &bytes); + } + + #[test] + fn test_to_bytes() { + let instance = SimpleStruct::new(0x01020304, 0x05060708); + let bytes = instance.to_bytes(); + assert_eq!(bytes, &[0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05]); + } + + #[test] + fn test_to_bytes_mut() { + let mut instance = SimpleStruct::new(0x01020304, 0x05060708); + let bytes = instance.to_bytes_mut(); + bytes[0] = 0xFF; // Modify the first byte + assert_eq!(instance.to_bytes(), &[0xFF, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05]); + } + + #[test] + fn test_field_accessors() { + let instance = SimpleStruct::new(0x12345678, 0x9ABCDEF0); + assert_eq!(instance.f1(), 0x12345678); + assert_eq!(instance.f2(), 0x9ABCDEF0); + } + + #[test] + fn test_mutate_fields() { + let mut instance = SimpleStruct::new(0x12345678, 0x9ABCDEF0); + *instance.f1_mut() = 0x87654321; + *instance.f2_mut() = 0x0FEDCBA9; + assert_eq!(instance.f1(), 0x87654321); + assert_eq!(instance.f2(), 0x0FEDCBA9); + } + + #[test] + fn test_partial_updates() { + let mut instance = SimpleStruct::new(0x12345678, 0x9ABCDEF0); + *instance.f1_mut() = 0x11111111; + assert_eq!(instance.to_bytes(), &[0x11, 0x11, 0x11, 0x11, 0xF0, 0xDE, 0xBC, 0x9A]); + } + + #[test] + fn test_zero_initialization() { + let instance = SimpleStruct::new(0, 0); + assert_eq!(instance.f1(), 0); + assert_eq!(instance.f2(), 0); + assert_eq!(instance.to_bytes(), &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + } +} diff --git a/app/ztruct/tests/simple_array.rs b/app/ztruct/tests/simple_array.rs new file mode 100644 index 00000000..767fa370 --- /dev/null +++ b/app/ztruct/tests/simple_array.rs @@ -0,0 +1,80 @@ +use ztruct::create_ztruct; + +#[cfg(test)] +mod tests { + use super::*; + + create_ztruct! { + pub struct SimpleStruct { + pub f1: u32, + pub f2: i64, + pub f3: [u8; 4], + } + } + + #[test] + fn test_new() { + let instance = SimpleStruct::new(0x01020304, 0x05060708090A0B0C, [1, 2, 3, 4]); + assert_eq!(instance.f1(), 0x01020304); + assert_eq!(instance.f2(), 0x05060708090A0B0C); + assert_eq!(instance.f3(), [1, 2, 3, 4]); + } + + #[test] + fn test_from_bytes() { + let bytes = [0x04, 0x03, 0x02, 0x01, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 1, 2, 3, 4]; + let instance = SimpleStruct::from_bytes(&bytes); + assert_eq!(instance.to_bytes(), &bytes); + } + + #[test] + fn test_to_bytes() { + let instance = SimpleStruct::new(0x01020304, 0x05060708090A0B0C, [1, 2, 3, 4]); + let bytes = instance.to_bytes(); + assert_eq!(bytes, &[0x04, 0x03, 0x02, 0x01, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 1, 2, 3, 4]); + } + + #[test] + fn test_to_bytes_mut() { + let mut instance = SimpleStruct::new(0x01020304, 0x05060708090A0B0C, [1, 2, 3, 4]); + let bytes = instance.to_bytes_mut(); + bytes[0] = 0xFF; // Modify the first byte + assert_eq!(instance.to_bytes(), &[0xFF, 0x03, 0x02, 0x01, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 1, 2, 3, 4]); + } + + #[test] + fn test_field_accessors() { + let instance = SimpleStruct::new(0x12345678, 0x3ABCDEF012345678, [5, 6, 7, 8]); + assert_eq!(instance.f1(), 0x12345678); + assert_eq!(instance.f2(), 0x3ABCDEF012345678); + assert_eq!(instance.f3(), [5, 6, 7, 8]); + } + + #[test] + fn test_mutate_fields() { + let mut instance = SimpleStruct::new(0x12345678, 0x3ABCDEF012345678, [5, 6, 7, 8]); + *instance.f1_mut() = 0x87654321; + *instance.f2_mut() = 0x0FEDCBA987654321; + *instance.f3_mut() = [8, 7, 6, 5]; + assert_eq!(instance.f1(), 0x87654321); + assert_eq!(instance.f2(), 0x0FEDCBA987654321); + assert_eq!(instance.f3(), [8, 7, 6, 5]); + } + + #[test] + fn test_partial_updates() { + let mut instance = SimpleStruct::new(0x12345678, 0x3ABCDEF012345678, [5, 6, 7, 8]); + *instance.f1_mut() = 0x11111111; + assert_eq!(instance.to_bytes(), &[0x11, 0x11, 0x11, 0x11, 0x78, 0x56, 0x34, 0x12, 0xF0, 0xDE, 0xBC, 0x3A, 5, 6, 7, 8]); + } + + #[test] + fn test_zero_initialization() { + let instance = SimpleStruct::new(0, 0, [0, 0, 0, 0]); + assert_eq!(instance.f1(), 0); + assert_eq!(instance.f2(), 0); + assert_eq!(instance.f3(), [0, 0, 0, 0]); + assert_eq!(instance.to_bytes(), &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0]); + } + +}