feat(zcash): add batch-policy preflight for checked batch messages
What changed, and why it matters
This commit adds a new Rust function for Zcash that pre-checks a batch of private (shielded) transaction data before signing. It is a feature addition with explicit safety checks and tests; the diff itself does not show a vulnerability being fixed.
No security action required; review as normal feature code. If auditing, verify that `ShieldedActionPolicy::Batch` correctly restricts what may be signed in a batch context.
Security signals we found
New preflight function enforces account ownership and batch policy before signing
Reuses existing `check_parsed_pczt_cypherpunk` and `signable_shielded_actions` helpers
Adds tests for negative cases (wrong account, unsupported Sapling outputs)
No removal of unsafe code, no bounds-check fix, no privilege escalation, no cryptographic bypass
Evidence from the diff
The change introduces preflight_batch_pczt_cypherpunk in rust/apps/zcash/src/lib.rs. It parses a PCZT (Partially Created Zcash Transaction), runs existing policy checks, enforces a batch-specific shielded-action policy, ensures at least one action belongs to the requested account, and returns a normalized serialized PCZT. It also adds unit tests covering acceptance of Orchard/Ironwood spends, rejection of wrong-account inputs, and rejection of Sapling outputs. No bug fix or security patch is visible in the diff.
Changed components
rust/apps/zcash/src/lib.rsInspect captured patch +76 / −0
diff --git a/rust/apps/zcash/src/lib.rs b/rust/apps/zcash/src/lib.rs
index 79e0847..03d0ab7 100644
--- a/rust/apps/zcash/src/lib.rs
+++ b/rust/apps/zcash/src/lib.rs
@@ -122,6 +122,37 @@ pub fn preflight_pczt_cypherpunk<P: consensus::Parameters>(
Ok(pczt.serialize())
}
+/// Batch preflight for one `ZcashSignBatch` message: parses once, runs the full
+/// policy checks, enforces the batch shielded-action policy (the PCZT must be
+/// batch-signable by this account), and returns the normalized encoding. See
+/// `preflight_pczt_cypherpunk` for the normalization contract.
+#[cfg(feature = "cypherpunk")]
+pub fn preflight_batch_pczt_cypherpunk<P: consensus::Parameters>(
+ params: &P,
+ pczt_bytes: &[u8],
+ ufvk_text: &str,
+ seed_fingerprint: &[u8; 32],
+ account_index: u32,
+) -> Result<Vec<u8>> {
+ let pczt = pczt::parse_pczt(pczt_bytes)?;
+ // FUTURE(qr-v2-omitted-fields): recompute-or-check omitted fields here, as
+ // in preflight_pczt_cypherpunk.
+ check_parsed_pczt_cypherpunk(params, &pczt, ufvk_text, seed_fingerprint, account_index)?;
+ let account_id = zip32::AccountId::try_from(account_index)
+ .map_err(|_e| ZcashError::InvalidDataError("invalid account index".to_string()))?;
+ let (actions, pczt) = signable_shielded_actions(
+ params,
+ pczt,
+ seed_fingerprint,
+ account_id,
+ ShieldedActionPolicy::Batch,
+ )?;
+ if actions.is_empty() {
+ return Err(ZcashError::PcztNoMyInputs);
+ }
+ Ok(pczt.serialize())
+}
+
#[cfg(feature = "multi_coins")]
pub fn check_pczt_multi_coins<P: consensus::Parameters>(
params: &P,
@@ -1617,4 +1648,49 @@ mod tests {
0,
));
}
+
+ #[cfg(zcash_unstable = "nu6.3")]
+ #[test]
+ fn test_preflight_batch_pczt_accepts_orchard_and_ironwood_spends() {
+ for sample in [
+ pczt::test_support::sample_orchard_change_pczt(),
+ pczt::test_support::sample_ironwood_pczt(),
+ ] {
+ let normalized = preflight_batch_pczt_cypherpunk(
+ &pczt::test_support::Nu6_3Network,
+ &sample.bytes,
+ &sample.ufvk_text,
+ &sample.seed_fingerprint,
+ 0,
+ )
+ .unwrap();
+ assert!(Pczt::parse(&normalized).is_ok());
+
+ // Account 1 owns nothing in these PCZTs: batch policy rejects.
+ assert_eq!(
+ preflight_batch_pczt_cypherpunk(
+ &pczt::test_support::Nu6_3Network,
+ &sample.bytes,
+ &sample.ufvk_text,
+ &sample.seed_fingerprint,
+ 1,
+ )
+ .unwrap_err(),
+ ZcashError::PcztNoMyInputs
+ );
+ }
+ }
+
+ #[cfg(zcash_unstable = "nu6.3")]
+ #[test]
+ fn test_preflight_batch_pczt_rejects_sapling_outputs() {
+ let sample = pczt_with_sapling_output();
+ assert_batch_unsupported_sapling_error(preflight_batch_pczt_cypherpunk(
+ &pczt::test_support::Nu6_3Network,
+ &sample.bytes,
+ &sample.ufvk_text,
+ &sample.seed_fingerprint,
+ 0,
+ ));
+ }
}
Why this scored 12/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.