From 89b120f3092f74eea568942b392054eed75a1199 Mon Sep 17 00:00:00 2001 From: neithanmo Date: Fri, 15 Mar 2024 12:24:04 -0600 Subject: [PATCH 1/5] Export C function to rust for calling heart_beat --- app/rust/src/lib.rs | 9 +++++++++ app/src/c_api/rust.c | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index b5efc7a8..fd0f3101 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -44,3 +44,12 @@ fn debug(_msg: &str) {} fn panic(_info: &PanicInfo) -> ! { loop {} } + +extern "C" { + fn io_heart_beat(); +} + +// Lets the device breath between computations +pub(crate) fn heart_beat() { + unsafe { io_heart_beat() } +} diff --git a/app/src/c_api/rust.c b/app/src/c_api/rust.c index 744aaf4f..1520e884 100644 --- a/app/src/c_api/rust.c +++ b/app/src/c_api/rust.c @@ -174,3 +174,7 @@ void c_jubjub_spending_base_scalarmult(uint8_t *point, const uint8_t *scalar) { MEMZERO(point, JUBJUB_FIELD_BYTES); } } + +void io_heart_beat() { + io_seproxyhal_io_heartbeat(); +} From 6470853a2c158c7bc83766f56d706186466871f7 Mon Sep 17 00:00:00 2001 From: neithanmo Date: Mon, 18 Mar 2024 10:38:35 -0600 Subject: [PATCH 2/5] feat/add heartbeat functin in rust --- app/rust/src/lib.rs | 6 +++++- app/rust/src/note_encryption.rs | 24 +++++++++++++----------- app/rust/src/pedersen.rs | 1 - app/rust/src/redjubjub.rs | 8 +++++--- app/rust/src/zeccrypto.rs | 3 +++ 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index fd0f3101..c9d21ed7 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -45,11 +45,15 @@ fn panic(_info: &PanicInfo) -> ! { loop {} } +#[cfg(not(test))] extern "C" { fn io_heart_beat(); } // Lets the device breath between computations pub(crate) fn heart_beat() { - unsafe { io_heart_beat() } + #[cfg(not(test))] + unsafe { + io_heart_beat() + } } diff --git a/app/rust/src/note_encryption.rs b/app/rust/src/note_encryption.rs index 57dcfea9..01c829f4 100644 --- a/app/rust/src/note_encryption.rs +++ b/app/rust/src/note_encryption.rs @@ -22,11 +22,7 @@ pub extern "C" fn blake2b_prf(input_ptr: *const [u8; 128], out_ptr: *mut [u8; 32 } #[no_mangle] -pub fn get_epk( - esk_ptr: *const [u8; 32], - d_ptr: *const [u8; 11], - output_ptr: *mut [u8; 32], -) { +pub fn get_epk(esk_ptr: *const [u8; 32], d_ptr: *const [u8; 11], output_ptr: *mut [u8; 32]) { let esk = unsafe { &*esk_ptr }; //ovk, cv, cmu, epk let d = unsafe { &*d_ptr }; let output = unsafe { &mut *output_ptr }; @@ -35,18 +31,22 @@ pub fn get_epk( } #[no_mangle] -pub extern "C" fn rseed_get_esk_epk(rseed_ptr: *const [u8; 32], - d_ptr: *const [u8; 11], - output_esk_ptr: *mut [u8; 32], - output_epk_ptr: *mut [u8; 32]) { +pub extern "C" fn rseed_get_esk_epk( + rseed_ptr: *const [u8; 32], + d_ptr: *const [u8; 11], + output_esk_ptr: *mut [u8; 32], + output_epk_ptr: *mut [u8; 32], +) { + crate::heart_beat(); let rseed = unsafe { &*rseed_ptr }; -// let d = unsafe { &*d_ptr }; + // let d = unsafe { &*d_ptr }; let output_esk = unsafe { &mut *output_esk_ptr }; let output_epk = unsafe { &mut *output_epk_ptr }; rseed_get_esk(rseed, output_esk); //let epk = multwithgd(output_esk, d); - get_epk(output_esk,d_ptr,output_epk); + get_epk(output_esk, d_ptr, output_epk); + crate::heart_beat(); //output_epk.copy_from_slice(&epk); } @@ -57,6 +57,7 @@ pub extern "C" fn ka_to_key( epk_ptr: *const [u8; 32], output_ptr: *mut [u8; 32], ) { + crate::heart_beat(); let esk = unsafe { &*esk_ptr }; //ovk, cv, cmu, epk let pkd = unsafe { &*pkd_ptr }; let epk = unsafe { &*epk_ptr }; @@ -64,6 +65,7 @@ pub extern "C" fn ka_to_key( let key = kdf_sapling(&shared_secret, epk); let output = unsafe { &mut *output_ptr }; //ovk, cv, cmu, epk output.copy_from_slice(&key); + crate::heart_beat(); } #[no_mangle] diff --git a/app/rust/src/pedersen.rs b/app/rust/src/pedersen.rs index a8b9b5ae..02156194 100644 --- a/app/rust/src/pedersen.rs +++ b/app/rust/src/pedersen.rs @@ -261,7 +261,6 @@ pub fn pedersen_hash_pointbytes(m: &[u8], bitsize: u32) -> [u8; 32] { extended_to_bytes(&result_point) } - #[cfg(test)] mod tests { use super::*; diff --git a/app/rust/src/redjubjub.rs b/app/rust/src/redjubjub.rs index 179fd5d7..2f3ba665 100644 --- a/app/rust/src/redjubjub.rs +++ b/app/rust/src/redjubjub.rs @@ -55,12 +55,14 @@ pub fn sign_compute_sbar(msg: &[u8], r: &Fr, rbar: &[u8], sfr: &Fr) -> [u8; 32] #[inline(never)] pub fn sign_complete(msg: &[u8], sk: &Fr) -> [u8; 64] { + crate::heart_beat(); let r = sign_generate_r(&msg); let rbar = sign_compute_rbar(&r.to_bytes()); let sbar = sign_compute_sbar(msg, &r, &rbar, sk); let mut sig = [0u8; 64]; sig[..32].copy_from_slice(&rbar); sig[32..].copy_from_slice(&sbar); + crate::heart_beat(); sig } @@ -127,12 +129,12 @@ pub extern "C" fn randomized_secret_from_seed( alpha_ptr: *const [u8; 32], output_ptr: *mut [u8; 32], ) { - let mut ask = [0u8;32]; - let mut nsk = [0u8;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(seed_ptr,&mut ask, &mut nsk, pos); + zip32_child_ask_nsk(seed_ptr, &mut ask, &mut nsk, pos); let mut skfr = Fr::from_bytes(&ask).unwrap(); let alphafr = Fr::from_bytes(&alpha).unwrap(); diff --git a/app/rust/src/zeccrypto.rs b/app/rust/src/zeccrypto.rs index 654873e9..bbf9753a 100644 --- a/app/rust/src/zeccrypto.rs +++ b/app/rust/src/zeccrypto.rs @@ -52,6 +52,7 @@ pub fn kdf_sapling(dhsecret: &[u8; 32], epk: &[u8; 32]) -> [u8; 32] { (&mut input[..32]).copy_from_slice(dhsecret); (&mut input[32..]).copy_from_slice(epk); pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF"; + crate::heart_beat(); bolos::blake2b32_with_personalization(KDF_SAPLING_PERSONALIZATION, &input) } @@ -62,12 +63,14 @@ pub fn prf_ock(ovk: &[u8; 32], cv: &[u8; 32], cmu: &[u8; 32], epk: &[u8; 32]) -> ock_input[64..96].copy_from_slice(cmu); ock_input[96..128].copy_from_slice(epk); pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock"; + crate::heart_beat(); bolos::blake2b32_with_personalization(PRF_OCK_PERSONALIZATION, &ock_input) } #[inline(never)] pub fn prf_sessionkey(data: &[u8]) -> [u8; 32] { pub const PRF_SESSION_PERSONALIZATION: &[u8; 16] = b"Zcash_SessionKey"; + crate::heart_beat(); bolos::blake2b32_with_personalization(PRF_SESSION_PERSONALIZATION, &data) } From 907e1cd88a6fc32cb36bbbede4cca0306e24c1d1 Mon Sep 17 00:00:00 2001 From: neithanmo Date: Mon, 18 Mar 2024 10:39:00 -0600 Subject: [PATCH 3/5] feat: call heartbeat in crypto functions that might take time --- app/src/crypto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/crypto.c b/app/src/crypto.c index ed033fba..418b6474 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -197,9 +197,11 @@ zxerr_t crypto_fillSaplingSeed(uint8_t *sk) { MEMZERO(sk, ED25519_SK_SIZE); zxerr_t error = zxerr_unknown; + io_seproxyhal_io_heartbeat(); CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, CX_CURVE_Ed25519, path, HDPATH_LEN_DEFAULT, sk, NULL, NULL, 0)); + io_seproxyhal_io_heartbeat(); error = zxerr_ok; catch_cx_error: From f7e80a565332454db1336ebc84284bb4c00817d0 Mon Sep 17 00:00:00 2001 From: ftheirs Date: Mon, 18 Mar 2024 15:53:41 -0300 Subject: [PATCH 4/5] more hearbeats --- app/rust/src/commitments.rs | 3 + app/rust/src/note_encryption.rs | 2 +- app/rust/src/redjubjub.rs | 2 + app/rust/src/zeccrypto.rs | 1 + app/rust/src/zip32.rs | 27 +++++--- app/src/chacha.c | 1 + app/src/crypto.c | 109 ++++++++++++++++++-------------- app/src/sighash.c | 9 +++ app/src/txid.c | 2 + 9 files changed, 97 insertions(+), 59 deletions(-) diff --git a/app/rust/src/commitments.rs b/app/rust/src/commitments.rs index 8aeb8380..9fcdffc9 100644 --- a/app/rust/src/commitments.rs +++ b/app/rust/src/commitments.rs @@ -271,9 +271,12 @@ pub extern "C" fn compute_nullifier( let nsk = unsafe { &*nsk_ptr }; let mut nk = [0u8; 32]; nsk_to_nk(nsk, &mut nk); + crate::heart_beat(); let scalar = Fr::from(pos); let e = bytes_to_extended(ncm); + crate::heart_beat(); let rho = mixed_pedersen(&e, scalar); + crate::heart_beat(); let output = unsafe { &mut *output_ptr }; output.copy_from_slice(&prf_nf(&nk, &rho)); } diff --git a/app/rust/src/note_encryption.rs b/app/rust/src/note_encryption.rs index 01c829f4..45578b22 100644 --- a/app/rust/src/note_encryption.rs +++ b/app/rust/src/note_encryption.rs @@ -63,9 +63,9 @@ pub extern "C" fn ka_to_key( let epk = unsafe { &*epk_ptr }; let shared_secret = sapling_ka_agree(esk, pkd); let key = kdf_sapling(&shared_secret, epk); + crate::heart_beat(); let output = unsafe { &mut *output_ptr }; //ovk, cv, cmu, epk output.copy_from_slice(&key); - crate::heart_beat(); } #[no_mangle] diff --git a/app/rust/src/redjubjub.rs b/app/rust/src/redjubjub.rs index 2f3ba665..11c86a16 100644 --- a/app/rust/src/redjubjub.rs +++ b/app/rust/src/redjubjub.rs @@ -57,7 +57,9 @@ pub fn sign_compute_sbar(msg: &[u8], r: &Fr, rbar: &[u8], sfr: &Fr) -> [u8; 32] pub fn sign_complete(msg: &[u8], sk: &Fr) -> [u8; 64] { crate::heart_beat(); let r = sign_generate_r(&msg); + crate::heart_beat(); let rbar = sign_compute_rbar(&r.to_bytes()); + crate::heart_beat(); let sbar = sign_compute_sbar(msg, &r, &rbar, sk); let mut sig = [0u8; 64]; sig[..32].copy_from_slice(&rbar); diff --git a/app/rust/src/zeccrypto.rs b/app/rust/src/zeccrypto.rs index bbf9753a..b11e237b 100644 --- a/app/rust/src/zeccrypto.rs +++ b/app/rust/src/zeccrypto.rs @@ -14,6 +14,7 @@ use crate::{bolos, pedersen::extended_to_bytes, zip32}; #[inline(never)] pub fn rseed_generate_rcm(rseed: &[u8; 32]) -> Fr { let bytes = zip32::prf_expand(rseed, &[0x04]); + crate::heart_beat(); jubjub::Fr::from_bytes_wide(&bytes) } diff --git a/app/rust/src/zip32.rs b/app/rust/src/zip32.rs index 84060ca7..ebff5b9d 100644 --- a/app/rust/src/zip32.rs +++ b/app/rust/src/zip32.rs @@ -196,6 +196,8 @@ pub fn ff1aes_list_with_startingindex_default( let mut ff1 = BinaryFF1::new(&cipher, 11, &[], &mut scratch).unwrap(); let mut d: [u8; 11]; + crate::heart_beat(); + let size = 4; for c in 0..size { @@ -388,12 +390,14 @@ pub fn derive_zip32_ovk_fromseedandpath(seed: &[u8; 32], path: &[u32]) -> [u8; 3 chain.copy_from_slice(&tmp[32..]); let mut ask = Fr::from_bytes_wide(&prf_expand(&key, &[0x00])); - let mut nsk = Fr::from_bytes_wide(&prf_expand(&key, &[0x01])); + crate::heart_beat(); let mut expkey: [u8; 96]; expkey = expandedspendingkey_zip32(&key); //96 //master divkey + crate::heart_beat(); + let mut divkey = [0u8; 32]; divkey.copy_from_slice(&diversifier_key_zip32(&key)); //32 for &p in path { @@ -415,6 +419,7 @@ pub fn derive_zip32_ovk_fromseedandpath(seed: &[u8; 32], path: &[u32]) -> [u8; 3 LittleEndian::write_u32(&mut le_i, c); tmp = bolos::blake2b_expand_vec_four(&chain, &[0x12], &fvk, &divkey, &le_i); } + crate::heart_beat(); //extract key and chainkey key.copy_from_slice(&tmp[..32]); chain.copy_from_slice(&tmp[32..]); @@ -503,7 +508,7 @@ pub fn master_nsk_from_seed(seed: &[u8; 32]) -> [u8; 32] { let mut key = [0u8; 32]; //32 key.copy_from_slice(&tmp[..32]); - + crate::heart_beat(); let nsk = Fr::from_bytes_wide(&prf_expand(&key, &[0x01])); let mut result = [0u8; 32]; result.copy_from_slice(&nsk.to_bytes()); @@ -522,9 +527,12 @@ pub fn derive_zip32_child_fromseedandpath(seed: &[u8; 32], path: &[u32], child_c let mut ask = Fr::from_bytes_wide(&prf_expand(tmp[..32].try_into().unwrap(), &[0x00])); let mut nsk = Fr::from_bytes_wide(&prf_expand(tmp[..32].try_into().unwrap(), &[0x01])); + crate::heart_beat(); let mut expkey: [u8; 96]; expkey = expandedspendingkey_zip32(&tmp[..32].try_into().unwrap()); //96 + crate::heart_beat(); + //master divkey let mut divkey = [0u8; 32]; divkey.copy_from_slice(&diversifier_key_zip32(&tmp[..32].try_into().unwrap())); //32 @@ -548,6 +556,7 @@ pub fn derive_zip32_child_fromseedandpath(seed: &[u8; 32], path: &[u32], child_c tmp = bolos::blake2b_expand_vec_four(&tmp[32..], &[0x12], &fvk, &divkey, &le_i); } + crate::heart_beat(); let ask_cur = Fr::from_bytes_wide(&prf_expand(&tmp[..32], &[0x13])); let nsk_cur = Fr::from_bytes_wide(&prf_expand(&tmp[..32], &[0x14])); @@ -562,7 +571,7 @@ pub fn derive_zip32_child_fromseedandpath(seed: &[u8; 32], path: &[u32], child_c // Get ak from ask let mut ak = [0u8; 32]; bolos::sdk_jubjub_scalarmult_spending_base(&mut ak, &ask.to_bytes()); - + crate::heart_beat(); // Get nk from nsk = k[64..96] let nk_tmp = PROVING_KEY_BASE.multiply_bits(&nsk.to_bytes()); @@ -680,17 +689,16 @@ pub extern "C" fn get_default_diversifier_without_start_index( while !found { ff1aes_list_with_startingindex_default(&dk[0..32].try_into().unwrap(), &mut start, &mut div_list); - for i in 0..DIV_DEFAULT_LIST_LEN - { + for i in 0..DIV_DEFAULT_LIST_LEN { if !found && is_valid_diversifier( - &div_list[i*DIV_SIZE..(i+1)*DIV_SIZE].try_into().unwrap()) - { + &div_list[i*DIV_SIZE..(i+1)*DIV_SIZE].try_into().unwrap()) { found = true; div.copy_from_slice(&div_list[i*DIV_SIZE..(i+1)*DIV_SIZE]); - } } } + crate::heart_beat(); } +} #[no_mangle] pub extern "C" fn zip32_master( @@ -846,7 +854,7 @@ pub extern "C" fn get_pkd_from_seed( let div = unsafe {&mut *diversifier_ptr}; let mut div_list = [0u8;DIV_SIZE*DIV_DEFAULT_LIST_LEN]; - + crate::heart_beat(); let dk_ak_nk = derive_zip32_child_fromseedandpath(&seed, &[FIRSTVALUE, COIN_TYPE, pos], @@ -866,6 +874,7 @@ pub extern "C" fn get_pkd_from_seed( div.copy_from_slice(&div_list[i*DIV_SIZE..(i+1)*DIV_SIZE]); } } + crate::heart_beat(); } let ivk = aknk_to_ivk(&dk_ak_nk[32..64].try_into().unwrap(), &dk_ak_nk[64..96].try_into().unwrap()); diff --git a/app/src/chacha.c b/app/src/chacha.c index 985b7600..236b3898 100644 --- a/app/src/chacha.c +++ b/app/src/chacha.c @@ -118,6 +118,7 @@ void chacha(uint8_t *out, const uint8_t *in, size_t in_len, const uint8_t *key, todo = in_len; } chacha_core(buf, input); + io_seproxyhal_io_heartbeat(); for (i = 0; i < todo; i++) { out[i] = in[i] ^ buf[i]; } diff --git a/app/src/crypto.c b/app/src/crypto.c index 418b6474..469b1a64 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -188,28 +188,28 @@ zxerr_t crypto_fillAddress_secp256k1(uint8_t *buffer, uint16_t buffer_len, } zxerr_t crypto_fillSaplingSeed(uint8_t *sk) { - zemu_log_stack("crypto_fillSaplingSeed"); + zemu_log_stack("crypto_fillSaplingSeed"); - // Generate randomness using a fixed path related to the device mnemonic - const uint32_t path[HDPATH_LEN_DEFAULT] = { - 0x8000002c, 0x80000085, MASK_HARDENED, MASK_HARDENED, MASK_HARDENED, - }; - MEMZERO(sk, ED25519_SK_SIZE); + // Generate randomness using a fixed path related to the device mnemonic + const uint32_t path[HDPATH_LEN_DEFAULT] = { + 0x8000002c, 0x80000085, MASK_HARDENED, MASK_HARDENED, MASK_HARDENED, + }; + MEMZERO(sk, ED25519_SK_SIZE); - zxerr_t error = zxerr_unknown; + zxerr_t error = zxerr_unknown; io_seproxyhal_io_heartbeat(); - CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, CX_CURVE_Ed25519, - path, HDPATH_LEN_DEFAULT, sk, - NULL, NULL, 0)); + CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, CX_CURVE_Ed25519, + path, HDPATH_LEN_DEFAULT, sk, + NULL, NULL, 0)); io_seproxyhal_io_heartbeat(); - error = zxerr_ok; + error = zxerr_ok; -catch_cx_error: - if (error != zxerr_ok) { - MEMZERO(sk, 64); - } + catch_cx_error: + if (error != zxerr_ok) { + MEMZERO(sk, 64); + } - return error; + return error; } // handleInitTX step 1/2 @@ -710,6 +710,8 @@ zxerr_t crypto_checkspend_sapling(uint8_t *buffer, uint16_t bufferLen, // we later need nsk zip32_child_ask_nsk(tmp.step1.zip32_seed, tmp.step2.ask, tmp.step2.nsk, item->path); + io_seproxyhal_io_heartbeat(); + get_rk(tmp.step2.ask, (uint8_t *)item->alpha, tmp.step3.rk); if (MEMCMP(tmp.step3.rk, start_spenddata + INDEX_SPEND_RK + i * SPEND_TX_LEN, @@ -724,6 +726,7 @@ zxerr_t crypto_checkspend_sapling(uint8_t *buffer, uint16_t bufferLen, VALUE_COMMITMENT_SIZE) != 0) { CHECK_ZXERROR_AND_CLEAN(zxerr_unknown) } + io_seproxyhal_io_heartbeat(); compute_note_commitment_fullpoint(tmp_buf->pedersen_hash, start_spendolddata + INDEX_SPEND_OLD_RCM + @@ -842,10 +845,15 @@ zxerr_t crypto_checkoutput_sapling(uint8_t *buffer, uint16_t bufferLen, } rseed_get_rcm(item->rseed, rcm); + io_seproxyhal_io_heartbeat(); + compute_note_commitment(ncm.step4.notecommitment, rcm, item->value, item->div, item->pkd); + io_seproxyhal_io_heartbeat(); + compute_value_commitment(item->value, item->rcmvalue, ncm.step4.valuecommitment); + io_seproxyhal_io_heartbeat(); if (MEMCMP(ncm.step4.valuecommitment, start_outputdata + INDEX_OUTPUT_VALUECMT + i * OUTPUT_TX_LEN, @@ -1098,6 +1106,7 @@ static zxerr_t address_to_script(uint8_t *address, uint8_t *output) { cx_hash_sha256(address, PK_LEN_SECP256K1, tmp, CX_SHA256_SIZE); CHECK_ZXERR(ripemd160(tmp, CX_SHA256_SIZE, script + SCRIPT_CONSTS_SIZE)); + io_seproxyhal_io_heartbeat(); script[24] = 0x88; script[25] = 0xac; @@ -1176,6 +1185,7 @@ zxerr_t crypto_sign_and_check_transparent(uint8_t *buffer, uint16_t bufferLen, &cx_publicKey)); CATCH_CXERROR(cx_ecfp_generate_pair_no_throw(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); + io_seproxyhal_io_heartbeat(); for (int j = 0; j < PUB_KEY_SIZE; j++) { pubKey[j] = cx_publicKey.W[SIG_S_SIZE + SIG_R_SIZE - j]; @@ -1305,6 +1315,7 @@ zxerr_t crypto_signspends_sapling(uint8_t *buffer, uint16_t bufferLen, rsk_to_rk((uint8_t *)tmp.step3.rsk, message); sign_redjubjub(tmp.step3.rsk, message, buffer); + io_seproxyhal_io_heartbeat(); CHECK_ZXERROR_AND_CLEAN(spend_signatures_append(buffer)) MEMZERO(&tmp, sizeof(tmp_sign_s)); CHECK_APP_CANARY() @@ -1588,43 +1599,43 @@ zxerr_t crypto_fillAddress_with_diversifier_sapling(uint8_t *buffer, // handleGetAddrSapling zxerr_t crypto_fillAddress_sapling(uint8_t *buffer, uint16_t bufferLen, uint32_t p, uint16_t *replyLen) { - if (bufferLen < sizeof(tmp_buf_addr_s)) { - return zxerr_unknown; - } + if (bufferLen < sizeof(tmp_buf_addr_s)) { + return zxerr_unknown; + } - zemu_log_stack("crypto_fillAddress_sapling"); - tmp_buf_addr_s *const out = (tmp_buf_addr_s *)buffer; - MEMZERO(buffer, bufferLen); + zemu_log_stack("crypto_fillAddress_sapling"); + tmp_buf_addr_s *const out = (tmp_buf_addr_s *)buffer; + MEMZERO(buffer, bufferLen); - // the path in zip32 is [FIRST_VALUE, COIN_TYPE, p] where p is u32 and last - // part of hdPath - uint8_t zip32_seed[ZIP32_SEED_SIZE] = {0}; + // the path in zip32 is [FIRST_VALUE, COIN_TYPE, p] where p is u32 and last + // part of hdPath + uint8_t zip32_seed[ZIP32_SEED_SIZE] = {0}; - // Temporarily get sk from Ed25519 - if (crypto_fillSaplingSeed(zip32_seed) != zxerr_ok) { - MEMZERO(zip32_seed, sizeof(zip32_seed)); - *replyLen = 0; - return zxerr_unknown; - } - CHECK_APP_CANARY() + // Temporarily get sk from Ed25519 + if (crypto_fillSaplingSeed(zip32_seed) != zxerr_ok) { + MEMZERO(zip32_seed, sizeof(zip32_seed)); + *replyLen = 0; + return zxerr_unknown; + } + CHECK_APP_CANARY() - get_pkd_from_seed(zip32_seed, p, out->startindex, out->diversifier, out->pkd); - MEMZERO(out + DIV_SIZE, MAX_SIZE_BUF_ADDR - DIV_SIZE); - CHECK_APP_CANARY() + get_pkd_from_seed(zip32_seed, p, out->startindex, out->diversifier, out->pkd); + MEMZERO(out + DIV_SIZE, MAX_SIZE_BUF_ADDR - DIV_SIZE); + CHECK_APP_CANARY() - MEMZERO(zip32_seed, sizeof(zip32_seed)); - if (bech32EncodeFromBytes(out->address_bech32, - sizeof_field(tmp_buf_addr_s, address_bech32), - BECH32_HRP, out->address_raw, - sizeof_field(tmp_buf_addr_s, address_raw), 1, - BECH32_ENCODING_BECH32) != zxerr_ok) { - MEMZERO(out, bufferLen); - *replyLen = 0; - return zxerr_unknown; - } - CHECK_APP_CANARY() + MEMZERO(zip32_seed, sizeof(zip32_seed)); + if (bech32EncodeFromBytes(out->address_bech32, + sizeof_field(tmp_buf_addr_s, address_bech32), + BECH32_HRP, out->address_raw, + sizeof_field(tmp_buf_addr_s, address_raw), 1, + BECH32_ENCODING_BECH32) != zxerr_ok) { + MEMZERO(out, bufferLen); + *replyLen = 0; + return zxerr_unknown; + } + CHECK_APP_CANARY() - *replyLen = sizeof_field(tmp_buf_addr_s, address_raw) + - strlen((const char *)out->address_bech32); - return zxerr_ok; + *replyLen = sizeof_field(tmp_buf_addr_s, address_raw) + + strlen((const char *)out->address_bech32); + return zxerr_ok; } diff --git a/app/src/sighash.c b/app/src/sighash.c index 35f55b57..1e048f30 100644 --- a/app/src/sighash.c +++ b/app/src/sighash.c @@ -51,6 +51,7 @@ zxerr_t sapling_transparent_prevouts_hash(const uint8_t *input, uint8_t *output) if (cx_hash_no_throw(&ctx.header, 0, data, 36, NULL, 0) != CX_OK) { return zxerr_invalid_crypto_settings; } + io_seproxyhal_io_heartbeat(); } const cx_err_t error = cx_hash_no_throw(&ctx.header, CX_LAST, data, 36, output, HASH_SIZE); @@ -71,6 +72,7 @@ zxerr_t sapling_transparent_sequence_hash(const uint8_t *input, uint8_t *output) const uint8_t *data = input + INDEX_TIN_SEQ; for (uint8_t i = 0; i < n - 1; i++, data += T_IN_TX_LEN) { CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, data, 4, NULL, 0)); + io_seproxyhal_io_heartbeat(); } CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, data, 4, output, HASH_SIZE)); @@ -95,6 +97,7 @@ zxerr_t v4_transparent_outputs_hash(uint8_t *output) { MEMCPY(data, (uint8_t *)&(item->value), 8); MEMCPY(data + 8, item->address, SCRIPT_SIZE); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, data, sizeof(data), NULL, 0)); + io_seproxyhal_io_heartbeat(); } t_output_item_t *item = t_outlist_retrieve_item(i); MEMCPY(data, (uint8_t *)&(item->value), 8); @@ -168,14 +171,19 @@ static zxerr_t signature_hash_v5(const uint8_t *input, uint8_t *start_signdata, uint8_t orchard_digest[32] = {0}; CHECK_ZXERR(hash_header_txid_data(start_signdata, header_digest)); + io_seproxyhal_io_heartbeat(); CHECK_ZXERR(transparent_sig_digest(input, start_signdata, index, type, transparent_digest)); + io_seproxyhal_io_heartbeat(); CHECK_ZXERR(hash_sapling_txid_data(start_signdata, sapling_digest)); + io_seproxyhal_io_heartbeat(); CHECK_ZXERR(hash_empty_orchard_txid_data(orchard_digest)); + io_seproxyhal_io_heartbeat(); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, header_digest, HASH_SIZE, NULL, 0)); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, transparent_digest, HASH_SIZE, NULL, 0)); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, sapling_digest, HASH_SIZE, NULL, 0)); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, orchard_digest, HASH_SIZE, output, HASH_SIZE)); + io_seproxyhal_io_heartbeat(); return zxerr_ok; } @@ -208,6 +216,7 @@ static zxerr_t signature_script_hash_v4(const uint8_t *input, uint16_t inputlen, CHECK_CX_OK(cx_blake2b_init2_no_throw(&ctx, 256, NULL, 0, (uint8_t *)personalization, PERSONALIZATION_SIZE)); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, input, inputlen, NULL, 0)); CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, script, scriptlen, output, HASH_SIZE)); + io_seproxyhal_io_heartbeat(); return zxerr_ok; } diff --git a/app/src/txid.c b/app/src/txid.c index b95db9a3..6cf61246 100644 --- a/app/src/txid.c +++ b/app/src/txid.c @@ -57,6 +57,7 @@ zxerr_t nu5_transparent_prevouts_hash(const uint8_t *input, uint8_t *output) { const uint8_t *data = input + INDEX_TIN_PREVOUT; for (uint8_t i = 0; i < n - 1; i++, data += T_IN_TX_LEN) { CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, data, PREVOUT_SIZE, NULL, 0)); + io_seproxyhal_io_heartbeat(); } CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, data, PREVOUT_SIZE, output, HASH_SIZE)); return zxerr_ok; @@ -80,6 +81,7 @@ zxerr_t nu5_transparent_sequence_hash(const uint8_t *input, uint8_t *output) { const uint8_t *data = input + INDEX_TIN_SEQ; for (uint8_t i = 0; i < n - 1; i++, data += T_IN_TX_LEN) { CHECK_CX_OK(cx_hash_no_throw(&ctx.header, 0, data, SEQUENCE_SIZE, NULL, 0)); + io_seproxyhal_io_heartbeat(); } CHECK_CX_OK(cx_hash_no_throw(&ctx.header, CX_LAST, data, SEQUENCE_SIZE, output, HASH_SIZE)); return zxerr_ok; From 785bb481839b8c802de5fd940c4b0de1aea633a9 Mon Sep 17 00:00:00 2001 From: ftheirs Date: Tue, 19 Mar 2024 10:01:27 -0300 Subject: [PATCH 5/5] bump version & update snapshots --- app/Makefile.version | 2 +- tests_zemu/snapshots/s-mainmenu/00004.png | Bin 475 -> 461 bytes tests_zemu/snapshots/s-mainmenu/00010.png | Bin 475 -> 461 bytes tests_zemu/snapshots/sp-mainmenu/00004.png | Bin 445 -> 435 bytes tests_zemu/snapshots/sp-mainmenu/00010.png | Bin 445 -> 435 bytes tests_zemu/snapshots/st-mainmenu/00001.png | Bin 14133 -> 14148 bytes tests_zemu/snapshots/x-mainmenu/00004.png | Bin 445 -> 435 bytes tests_zemu/snapshots/x-mainmenu/00010.png | Bin 445 -> 435 bytes 8 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Makefile.version b/app/Makefile.version index b7117931..7ab2e516 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -3,4 +3,4 @@ APPVERSION_M=3 # This is the minor version of this release APPVERSION_N=3 # This is the patch version of this release -APPVERSION_P=4 +APPVERSION_P=5 diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 3e94decb3a68182819bc2f275baf08419efe6c92..1d9acabf3591bd8cd4191406057bbbd4e28cf5cc 100644 GIT binary patch delta 435 zcmV;k0Zjhe1I+`FB!84iL_t(|ob8!ivxFcBhHdBc|9@~7?L}u810+P3{pfvWFSZgf zP(UjM006*USu&LDo2Gdk?a=6<*0`*CduJ!TQl0$G>d2xV?(7#(aH@Fe7~G^dfRIZ; zovL)Hwvn<KF3ipjpl6ppD5D#Yd(ke7D+jP= zwh!$66p$SD6y-^%3c8&5ftuakyY)W(9tIrH)xgt#ODg49_+6yzgju>q_O8g? z9m&}zz`q-U23Ek7iENir##8_4&}Z|G?*X79%K_>~C@d9wVf|eG@SO#{XtB>KLjeE) d006+Z@dS&!>Jr>fwvzw=002ovPDHLkV1hqc+B^UN delta 449 zcmV;y0Y3iC1KR_TB!8kwL_t(|ob8#xvcn(vVR{HX)jN-QB|j>a8#0V zFmMMLD~`4Ch#TacbRMquS!qeTJ=BohGd>5@>X6g+^gvBq3QbO%*7_hz1!&79V?z)f zYt^`A{3#JwkFj*!DTfaULF#V3Y;_ILk7idZqmb`j<1a>N z2gpd$mWWnH%YOl|W?#&pDjaZ?4Ym)iz^BG*AYt~r7iM<{;sR zL60?Q$da$*>;-N=op#}88g@XJg>08n3lv)4_#V&&x;mfI#2y;u0QDo3ioLODP@=C$ rr;SCvcR?STUY9Zy0000008Gd~{c`p1rKH!H00000NkvXXu0mjfha%BS diff --git a/tests_zemu/snapshots/s-mainmenu/00010.png b/tests_zemu/snapshots/s-mainmenu/00010.png index 3e94decb3a68182819bc2f275baf08419efe6c92..1d9acabf3591bd8cd4191406057bbbd4e28cf5cc 100644 GIT binary patch delta 435 zcmV;k0Zjhe1I+`FB!84iL_t(|ob8!ivxFcBhHdBc|9@~7?L}u810+P3{pfvWFSZgf zP(UjM006*USu&LDo2Gdk?a=6<*0`*CduJ!TQl0$G>d2xV?(7#(aH@Fe7~G^dfRIZ; zovL)Hwvn<KF3ipjpl6ppD5D#Yd(ke7D+jP= zwh!$66p$SD6y-^%3c8&5ftuakyY)W(9tIrH)xgt#ODg49_+6yzgju>q_O8g? z9m&}zz`q-U23Ek7iENir##8_4&}Z|G?*X79%K_>~C@d9wVf|eG@SO#{XtB>KLjeE) d006+Z@dS&!>Jr>fwvzw=002ovPDHLkV1hqc+B^UN delta 449 zcmV;y0Y3iC1KR_TB!8kwL_t(|ob8#xvcn(vVR{HX)jN-QB|j>a8#0V zFmMMLD~`4Ch#TacbRMquS!qeTJ=BohGd>5@>X6g+^gvBq3QbO%*7_hz1!&79V?z)f zYt^`A{3#JwkFj*!DTfaULF#V3Y;_ILk7idZqmb`j<1a>N z2gpd$mWWnH%YOl|W?#&pDjaZ?4Ym)iz^BG*AYt~r7iM<{;sR zL60?Q$da$*>;-N=op#}88g@XJg>08n3lv)4_#V&&x;mfI#2y;u0QDo3ioLODP@=C$ rr;SCvcR?STUY9Zy0000008Gd~{c`p1rKH!H00000NkvXXu0mjfha%BS diff --git a/tests_zemu/snapshots/sp-mainmenu/00004.png b/tests_zemu/snapshots/sp-mainmenu/00004.png index 91d8e76022b03c86531d8c34e31dee2238329d97..ccb9533dca97c097f73af580964dba89268dc01f 100644 GIT binary patch delta 409 zcmV;K0cQTa1G58=B!7BIL_t(|obB0BvV$NDKv7@k?S%a=iM{lrQ-(<(1_|oed%jvB zfI+B3w+#RQ000000000004(u0-$c$?UrZ@YHfXd^_n1;@)^|2C8g-zi?SC>}3?gb+ z24@#b#}FuB0t&qpnvZJv+N`5vov9S{#XR+TF$Uj@YSujL6@S>)hZt!35}(CwlxiE`HLMXi{C$StTI>(Jfm{U^||pKJfcNpb}YJyRt_eAvD@%{kY0!i)p8-kDmlVw{g<9AOS^L7t*(-Ej;0DUtt( z1?v6N`#d+D08PJZmXpPyA^-pY0000000000@FU(YJ`y_U)VXGO00000NkvXXu0mjf DZd1gK delta 419 zcmV;U0bKsG1HA)~B!7fSL_t(|obB1su7e;9fMIvCH{tysiFY{{%Lr+q6e()5|GRQZ z0gIqv#{mEU00000000000JeCoSCMnpCsRs`6&f|vEvA&3<(-wxMx7{WdoRX|PDFLf z;A~=R9|8poK%u8X^HD87n`LyYGnJye7^hwW)Gq0FS5vwyKRTDQAeUyXhCKVk;J)3Z9oy`{;S z)7dd7QO=URs2u|kxdhdHow{4S{scPry7r%3Bxk_XGgU&w!ST&$&bfX!)<1RlG4uWB zFRz={QO&+JqknpL;WS9^jwuGAn`E+onMSI~{>2!BJFyDgX^>>bU@M*8fZfmw7+;Xi zEu#~k*Jv1SX7nySc4o(K#w`+=T2CC${PRg&?>P!Ft N002ovPDHLkV1gQ9#)SX? diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index 91d8e76022b03c86531d8c34e31dee2238329d97..ccb9533dca97c097f73af580964dba89268dc01f 100644 GIT binary patch delta 409 zcmV;K0cQTa1G58=B!7BIL_t(|obB0BvV$NDKv7@k?S%a=iM{lrQ-(<(1_|oed%jvB zfI+B3w+#RQ000000000004(u0-$c$?UrZ@YHfXd^_n1;@)^|2C8g-zi?SC>}3?gb+ z24@#b#}FuB0t&qpnvZJv+N`5vov9S{#XR+TF$Uj@YSujL6@S>)hZt!35}(CwlxiE`HLMXi{C$StTI>(Jfm{U^||pKJfcNpb}YJyRt_eAvD@%{kY0!i)p8-kDmlVw{g<9AOS^L7t*(-Ej;0DUtt( z1?v6N`#d+D08PJZmXpPyA^-pY0000000000@FU(YJ`y_U)VXGO00000NkvXXu0mjf DZd1gK delta 419 zcmV;U0bKsG1HA)~B!7fSL_t(|obB1su7e;9fMIvCH{tysiFY{{%Lr+q6e()5|GRQZ z0gIqv#{mEU00000000000JeCoSCMnpCsRs`6&f|vEvA&3<(-wxMx7{WdoRX|PDFLf z;A~=R9|8poK%u8X^HD87n`LyYGnJye7^hwW)Gq0FS5vwyKRTDQAeUyXhCKVk;J)3Z9oy`{;S z)7dd7QO=URs2u|kxdhdHow{4S{scPry7r%3Bxk_XGgU&w!ST&$&bfX!)<1RlG4uWB zFRz={QO&+JqknpL;WS9^jwuGAn`E+onMSI~{>2!BJFyDgX^>>bU@M*8fZfmw7+;Xi zEu#~k*Jv1SX7nySc4o(K#w`+=T2CC${PRg&?>P!Ft N002ovPDHLkV1gQ9#)SX? diff --git a/tests_zemu/snapshots/st-mainmenu/00001.png b/tests_zemu/snapshots/st-mainmenu/00001.png index 686b1f1c66330e0a3e9a4fd4219d58779f3a6132..8547b43f19b3da90dc8dadcd517787f94ee97511 100644 GIT binary patch literal 14148 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 literal 14133 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 o&8sdd&i*$RsK31Q`$xw}3?gb+ z24@#b#}FuB0t&qpnvZJv+N`5vov9S{#XR+TF$Uj@YSujL6@S>)hZt!35}(CwlxiE`HLMXi{C$StTI>(Jfm{U^||pKJfcNpb}YJyRt_eAvD@%{kY0!i)p8-kDmlVw{g<9AOS^L7t*(-Ej;0DUtt( z1?v6N`#d+D08PJZmXpPyA^-pY0000000000@FU(YJ`y_U)VXGO00000NkvXXu0mjf DZd1gK delta 419 zcmV;U0bKsG1HA)~B!7fSL_t(|obB1su7e;9fMIvCH{tysiFY{{%Lr+q6e()5|GRQZ z0gIqv#{mEU00000000000JeCoSCMnpCsRs`6&f|vEvA&3<(-wxMx7{WdoRX|PDFLf z;A~=R9|8poK%u8X^HD87n`LyYGnJye7^hwW)Gq0FS5vwyKRTDQAeUyXhCKVk;J)3Z9oy`{;S z)7dd7QO=URs2u|kxdhdHow{4S{scPry7r%3Bxk_XGgU&w!ST&$&bfX!)<1RlG4uWB zFRz={QO&+JqknpL;WS9^jwuGAn`E+onMSI~{>2!BJFyDgX^>>bU@M*8fZfmw7+;Xi zEu#~k*Jv1SX7nySc4o(K#w`+=T2CC${PRg&?>P!Ft N002ovPDHLkV1gQ9#)SX? diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index 91d8e76022b03c86531d8c34e31dee2238329d97..ccb9533dca97c097f73af580964dba89268dc01f 100644 GIT binary patch delta 409 zcmV;K0cQTa1G58=B!7BIL_t(|obB0BvV$NDKv7@k?S%a=iM{lrQ-(<(1_|oed%jvB zfI+B3w+#RQ000000000004(u0-$c$?UrZ@YHfXd^_n1;@)^|2C8g-zi?SC>}3?gb+ z24@#b#}FuB0t&qpnvZJv+N`5vov9S{#XR+TF$Uj@YSujL6@S>)hZt!35}(CwlxiE`HLMXi{C$StTI>(Jfm{U^||pKJfcNpb}YJyRt_eAvD@%{kY0!i)p8-kDmlVw{g<9AOS^L7t*(-Ej;0DUtt( z1?v6N`#d+D08PJZmXpPyA^-pY0000000000@FU(YJ`y_U)VXGO00000NkvXXu0mjf DZd1gK delta 419 zcmV;U0bKsG1HA)~B!7fSL_t(|obB1su7e;9fMIvCH{tysiFY{{%Lr+q6e()5|GRQZ z0gIqv#{mEU00000000000JeCoSCMnpCsRs`6&f|vEvA&3<(-wxMx7{WdoRX|PDFLf z;A~=R9|8poK%u8X^HD87n`LyYGnJye7^hwW)Gq0FS5vwyKRTDQAeUyXhCKVk;J)3Z9oy`{;S z)7dd7QO=URs2u|kxdhdHow{4S{scPry7r%3Bxk_XGgU&w!ST&$&bfX!)<1RlG4uWB zFRz={QO&+JqknpL;WS9^jwuGAn`E+onMSI~{>2!BJFyDgX^>>bU@M*8fZfmw7+;Xi zEu#~k*Jv1SX7nySc4o(K#w`+=T2CC${PRg&?>P!Ft N002ovPDHLkV1gQ9#)SX?