What changed, and why it matters
This commit fixes a Rust build error in the Zcash module and, as a side effect, changes how sensitive seed data is handled. It switches from a read-only 'extract_array!' macro to a mutable 'extract_array_mut!' macro so the code can explicitly wipe (zeroize) the seed memory after use in one function. The other changed lines simply add 'mut' to satisfy the new macro's requirements. The commit does not add zeroize calls to the other three functions that now also receive mutable seed buffers, so the cleanup is partial.
Treat as a build fix with partial security hardening. Audit the remaining three Zcash functions to determine whether the seed buffer should also be zeroized before returning, and ensure the C caller cannot read leftover seed bytes from the provided pointer. If the seed is only borrowed for the duration of the call, consider zeroizing in all paths or using a dedicated secret-handling type.
Security signals we found
Sensitive material (BIP-39/HD seed) is now mutable in four FFI functions
Only one of four functions calls seed.zeroize() after use
Commit title is build-fix, not security-fix, suggesting incomplete hardening
No bounds-check or length-validation changes visible in the diff
No explicit memory-locking (mlock) or secure-allocation changes
Evidence from the diff
The patch replaces ‘extract_array!’ with ‘extract_array_mut!’ in rust/rust_c/src/zcash/mod.rs. The new macro returns a mutable slice, requiring local bindings to be declared ‘mut’. Only rust_derive_iv_from_seed() actually takes advantage of mutability by calling seed.zeroize() after deriving the IV. The other functions—derive_zcash_ufvk, calculate_zcash_seed_fingerprint, and sign_zcash_tx—receive mutable seeds but do not zeroize them. The change therefore appears driven primarily by a compilation failure (‘fix: zcash build’) with a single security-hardening addition.
Changed components
rust/rust_c/src/zcash/mod.rsderive_zcash_ufvkcalculate_zcash_seed_fingerprintsign_zcash_txrust_derive_iv_from_seedInspect captured patch +6 / −5
diff --git a/rust/rust_c/src/zcash/mod.rs b/rust/rust_c/src/zcash/mod.rs
index 8b8b9f3..bb0fff0 100644
--- a/rust/rust_c/src/zcash/mod.rs
+++ b/rust/rust_c/src/zcash/mod.rs
@@ -8,7 +8,7 @@ use crate::common::{
ur::{UREncodeResult, FRAGMENT_MAX_LENGTH_DEFAULT},
utils::{convert_c_char, recover_c_char},
};
-use crate::extract_array;
+use crate::{extract_array_mut, extract_array};
use crate::{extract_ptr_with_type, make_free_method};
use alloc::{boxed::Box, format, string::String, string::ToString};
use app_zcash::get_address;
@@ -30,7 +30,7 @@ pub unsafe extern "C" fn derive_zcash_ufvk(
seed_len: u32,
account_path: PtrString,
) -> *mut SimpleResponse<c_char> {
- let seed = extract_array_mut!(seed, u8, seed_len as usize);
+ let mut seed = extract_array_mut!(seed, u8, seed_len as usize);
let account_path = unsafe { recover_c_char(account_path) };
let ufvk_text = derive_ufvk(&MainNetwork, seed, &account_path);
let result = match ufvk_text {
@@ -46,7 +46,7 @@ pub unsafe extern "C" fn calculate_zcash_seed_fingerprint(
seed: PtrBytes,
seed_len: u32,
) -> *mut SimpleResponse<u8> {
- let seed = extract_array_mut!(seed, u8, seed_len as usize);
+ let mut seed = extract_array_mut!(seed, u8, seed_len as usize);
let sfp = calculate_seed_fingerprint(seed);
let result = match sfp {
Ok(bytes) => {
@@ -123,7 +123,7 @@ pub unsafe extern "C" fn sign_zcash_tx(
seed_len: u32,
) -> *mut UREncodeResult {
let pczt = extract_ptr_with_type!(tx, ZcashPczt);
- let seed = extract_array_mut!(seed, u8, seed_len as usize);
+ let mut seed = extract_array_mut!(seed, u8, seed_len as usize);
let result = match app_zcash::sign_pczt(&pczt.get_data(), seed) {
Ok(pczt) => match ZcashPczt::new(pczt).try_into() {
Err(e) => UREncodeResult::from(e).c_ptr(),
@@ -196,11 +196,12 @@ pub unsafe extern "C" fn rust_derive_iv_from_seed(
seed: PtrBytes,
seed_len: u32,
) -> *mut SimpleResponse<u8> {
- let seed = extract_array!(seed, u8, seed_len as usize);
+ let mut seed = extract_array_mut!(seed, u8, seed_len as usize);
let iv_path = "m/44'/1557192335'/0'/2'/0'".to_string();
let iv = get_private_key_by_seed(seed, &iv_path).unwrap();
let mut iv_bytes = [0; 16];
iv_bytes.copy_from_slice(&iv[..16]);
+ seed.zeroize();
SimpleResponse::success(Box::into_raw(Box::new(iv_bytes)) as *mut u8).simple_c_ptr()
}
Why this scored 42/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.