refactor: thread ShieldedPool enum instead of stringly pool labels
What changed, and why it matters
This is a code cleanup change with no security impact. It replaces free-form text labels like "Orchard" and "Ironwood" with a fixed enum type when checking, parsing, and signing Zcash shielded transaction bundles. The actual behavior and error messages stay the same; only the internal code structure is improved.
No security action required. Treat as normal code-quality review.
Security signals we found
No functional change to validation logic
No change to cryptographic operations
No change to error messages or user-facing behavior
Refactor only: stringly-typed labels replaced with enum
Evidence from the diff
The commit refactors the Zcash PCZT (Partially Created Zcash Transaction) helpers in rust/apps/zcash/src/pczt to thread a ShieldedPool enum instead of a &str pool_label. The enum is moved from sign.rs to mod.rs for shared use across check.rs, parse.rs, and sign.rs. Functions are renamed (check_orchard -> check_shielded_bundle) and signatures updated, but the derived labels and all error strings remain identical. The change is purely type-safety/ergonomics and does not alter validation, signing, or transaction semantics.
Changed components
rust/apps/zcash/src/pczt/check.rsrust/apps/zcash/src/pczt/mod.rsrust/apps/zcash/src/pczt/parse.rsrust/apps/zcash/src/pczt/sign.rsInspect captured patch +57 / −38
diff --git a/rust/apps/zcash/src/pczt/check.rs b/rust/apps/zcash/src/pczt/check.rs
index c722867..77051fc 100644
--- a/rust/apps/zcash/src/pczt/check.rs
+++ b/rust/apps/zcash/src/pczt/check.rs
@@ -23,6 +23,9 @@ use zcash_vendor::{
#[cfg(feature = "cypherpunk")]
use zcash_vendor::zcash_protocol::consensus::NetworkConstants;
+#[cfg(feature = "cypherpunk")]
+use super::ShieldedPool;
+
#[cfg(feature = "cypherpunk")]
fn map_orchard_verifier_error(
error: pczt::roles::verifier::OrchardError<ZcashError>,
@@ -46,13 +49,13 @@ pub fn check_pczt_orchard<P: consensus::Parameters>(
let should_process_ironwood = super::pczt_should_process_ironwood(pczt);
let verifier = Verifier::new(pczt.clone())
.with_orchard(|bundle| {
- check_orchard(
+ check_shielded_bundle(
params,
seed_fingerprint,
account_index,
ufvk,
bundle,
- "Orchard",
+ ShieldedPool::Orchard,
)
.map_err(pczt::roles::verifier::OrchardError::Custom)?;
Ok(())
@@ -62,13 +65,13 @@ pub fn check_pczt_orchard<P: consensus::Parameters>(
if should_process_ironwood {
verifier
.with_ironwood(|bundle| {
- check_orchard(
+ check_shielded_bundle(
params,
seed_fingerprint,
account_index,
ufvk,
bundle,
- "Ironwood",
+ ShieldedPool::Ironwood,
)
.map_err(pczt::roles::verifier::OrchardError::Custom)?;
Ok(())
@@ -294,14 +297,15 @@ fn check_transparent_output<P: consensus::Parameters>(
#[cfg(feature = "cypherpunk")]
// check orchard bundle
-fn check_orchard<P: consensus::Parameters>(
+fn check_shielded_bundle<P: consensus::Parameters>(
params: &P,
seed_fingerprint: &[u8; 32],
account_index: zip32::AccountId,
ufvk: &UnifiedFullViewingKey,
bundle: &orchard::pczt::Bundle,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<(), ZcashError> {
+ let pool_label = pool.label();
bundle.actions().iter().try_for_each(|action| {
check_action(
params,
@@ -309,7 +313,7 @@ fn check_orchard<P: consensus::Parameters>(
account_index,
ufvk,
action,
- pool_label,
+ pool,
)?;
Ok::<_, ZcashError>(())
})?;
@@ -340,8 +344,9 @@ fn check_action<P: consensus::Parameters>(
account_index: zip32::AccountId,
ufvk: &UnifiedFullViewingKey,
action: &orchard::pczt::Action,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<(), ZcashError> {
+ let pool_label = pool.label();
// Check `cv_net` first so we know that the `value` fields for both the spend and the
// output are present and correct.
action.verify_cv_net().map_err(|e| {
@@ -359,9 +364,9 @@ fn check_action<P: consensus::Parameters>(
account_index,
fvk,
action.spend(),
- pool_label,
+ pool,
)?;
- check_action_output(params, ufvk, action, pool_label)?;
+ check_action_output(params, ufvk, action, pool)?;
Ok(())
}
@@ -373,8 +378,9 @@ fn check_action_spend<P: consensus::Parameters>(
account_index: zip32::AccountId,
fvk: &FullViewingKey,
spend: &orchard::pczt::Spend,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<(), ZcashError> {
+ let pool_label = pool.label();
// We can only verify the `nullifier` and `rk` fields of a spend if we know its FVK.
let can_verify_nf_rk = match (spend.value(), spend.fvk(), spend.zip32_derivation()) {
// Dummy notes use randomly-generated FVKs, so if one is already present then
@@ -426,8 +432,9 @@ fn check_action_output<P: consensus::Parameters>(
params: &P,
ufvk: &UnifiedFullViewingKey,
action: &orchard::pczt::Action,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<(), ZcashError> {
+ let pool_label = pool.label();
action
.output()
.verify_note_commitment(action.spend())
diff --git a/rust/apps/zcash/src/pczt/mod.rs b/rust/apps/zcash/src/pczt/mod.rs
index 31304fa..33f655d 100644
--- a/rust/apps/zcash/src/pczt/mod.rs
+++ b/rust/apps/zcash/src/pczt/mod.rs
@@ -100,6 +100,28 @@ pub(crate) fn transparent_derivation_matches_selected_account<
Ok(true)
}
+/// Which shielded pool a bundle belongs to. Orchard and Ironwood share the same
+/// `orchard::pczt::Bundle` representation, so this distinguishes them (e.g. in
+/// error messages) without a free-form string.
+#[cfg(feature = "cypherpunk")]
+#[derive(Clone, Copy)]
+pub(crate) enum ShieldedPool {
+ Orchard,
+ #[cfg(zcash_unstable = "nu6.3")]
+ Ironwood,
+}
+
+#[cfg(feature = "cypherpunk")]
+impl ShieldedPool {
+ pub(crate) fn label(self) -> &'static str {
+ match self {
+ ShieldedPool::Orchard => "Orchard",
+ #[cfg(zcash_unstable = "nu6.3")]
+ ShieldedPool::Ironwood => "Ironwood",
+ }
+ }
+}
+
/// Returns the supported account declared by a shielded spend derivation that
/// belongs to this seed. Missing or different seed fingerprints are not ours
/// and return `None`; matching fingerprints with paths outside
@@ -109,8 +131,9 @@ pub(crate) fn matching_seed_supported_orchard_account(
seed_fingerprint: &[u8; 32],
derivation: Option<&zcash_vendor::orchard::pczt::Zip32Derivation>,
coin_type: u32,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<Option<zcash_vendor::zip32::AccountId>, crate::errors::ZcashError> {
+ let pool_label = pool.label();
let Some(derivation) = derivation else {
return Ok(None);
};
diff --git a/rust/apps/zcash/src/pczt/parse.rs b/rust/apps/zcash/src/pczt/parse.rs
index 567b58e..d49be3b 100644
--- a/rust/apps/zcash/src/pczt/parse.rs
+++ b/rust/apps/zcash/src/pczt/parse.rs
@@ -35,6 +35,9 @@ use zcash_vendor::{
#[cfg(feature = "cypherpunk")]
use super::structs::ParsedOrchard;
use super::structs::{ParsedFrom, ParsedPczt, ParsedTo, ParsedTransparent};
+
+#[cfg(feature = "cypherpunk")]
+use super::ShieldedPool;
use crate::errors::ZcashError;
const ZEC_DIVIDER: u32 = 100_000_000;
@@ -179,7 +182,7 @@ pub fn parse_pczt_cypherpunk<P: consensus::Parameters>(
let verifier = Verifier::new(pczt.clone())
.with_orchard(|bundle| {
- parsed_orchard = parse_orchard(params, seed_fingerprint, ufvk, bundle, "Orchard")
+ parsed_orchard = parse_orchard(params, seed_fingerprint, ufvk, bundle, ShieldedPool::Orchard)
.map_err(pczt::roles::verifier::OrchardError::Custom)?;
Ok(())
})
@@ -188,7 +191,7 @@ pub fn parse_pczt_cypherpunk<P: consensus::Parameters>(
let verifier = if should_process_ironwood {
verifier
.with_ironwood(|bundle| {
- parsed_ironwood = parse_orchard(params, seed_fingerprint, ufvk, bundle, "Ironwood")
+ parsed_ironwood = parse_orchard(params, seed_fingerprint, ufvk, bundle, ShieldedPool::Ironwood)
.map_err(pczt::roles::verifier::OrchardError::Custom)?;
Ok(())
})
@@ -514,7 +517,7 @@ fn parse_orchard<P: consensus::Parameters>(
seed_fingerprint: &[u8; 32],
ufvk: &UnifiedFullViewingKey,
orchard: &orchard::pczt::Bundle,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<Option<ParsedOrchard>, ZcashError> {
let mut parsed_orchard = ParsedOrchard::new(vec![], vec![]);
orchard.actions().iter().try_for_each(|action| {
@@ -527,7 +530,7 @@ fn parse_orchard<P: consensus::Parameters>(
parsed_orchard.add_from(parsed_from);
}
}
- let parsed_to = parse_orchard_output(params, ufvk, action, pool_label)?;
+ let parsed_to = parse_orchard_output(params, ufvk, action, pool)?;
if !parsed_to.get_is_dummy() {
parsed_orchard.add_to(parsed_to);
}
@@ -616,8 +619,9 @@ fn parse_orchard_output<P: consensus::Parameters>(
params: &P,
ufvk: &UnifiedFullViewingKey,
action: &orchard::pczt::Action,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<ParsedTo, ZcashError> {
+ let pool_label = pool.label();
let output = action.output();
let fvk = ufvk.orchard().ok_or(ZcashError::InvalidDataError(
"orchard is not present in ufvk".to_string(),
diff --git a/rust/apps/zcash/src/pczt/sign.rs b/rust/apps/zcash/src/pczt/sign.rs
index 565a63a..62bfdbf 100644
--- a/rust/apps/zcash/src/pczt/sign.rs
+++ b/rust/apps/zcash/src/pczt/sign.rs
@@ -350,23 +350,7 @@ fn collect_transparent_signing_keys(
}
#[cfg(feature = "cypherpunk")]
-#[derive(Clone, Copy)]
-enum ShieldedPool {
- Orchard,
- #[cfg(zcash_unstable = "nu6.3")]
- Ironwood,
-}
-
-#[cfg(feature = "cypherpunk")]
-impl ShieldedPool {
- fn label(self) -> &'static str {
- match self {
- ShieldedPool::Orchard => "Orchard",
- #[cfg(zcash_unstable = "nu6.3")]
- ShieldedPool::Ironwood => "Ironwood",
- }
- }
-}
+use super::ShieldedPool;
#[cfg(feature = "cypherpunk")]
fn collect_orchard_signing_keys(
@@ -426,7 +410,7 @@ fn collect_orchard_bundle_signing_keys(
if action.spend().value().is_none() {
continue;
}
- if let Some(ask) = spend_authorizing_key_for_action(seed, action, pool_label)? {
+ if let Some(ask) = spend_authorizing_key_for_action(seed, action, pool)? {
keys.push((index, ask));
}
}
@@ -437,15 +421,16 @@ fn collect_orchard_bundle_signing_keys(
fn spend_authorizing_key_for_action(
seed: &[u8],
action: &orchard::pczt::Action,
- pool_label: &str,
+ pool: ShieldedPool,
) -> Result<Option<orchard::keys::SpendAuthorizingKey>, ZcashError> {
+ let pool_label = pool.label();
let fingerprint =
calculate_seed_fingerprint(seed).map_err(|e| ZcashError::SigningError(e.to_string()))?;
let Some(account_index) = super::matching_seed_supported_orchard_account(
&fingerprint,
action.spend().zip32_derivation().as_ref(),
133,
- pool_label,
+ pool,
)?
else {
return Ok(None);
Why this scored 14/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.