Refactor PCZT handling: extract shared helpers (no dependency changes)
What changed, and why it matters
This commit is a pure internal code reorganization (refactor) for Zcash PCZT handling. It moves helper functions around, renames some UI rendering code, and adds a shared PCZT parsing helper, but the commit message and diff show no intentional behavior change. The original test suite is retained and reported to pass. There is no indication this fixes or introduces a security vulnerability.
No security action required. Treat as normal code-quality refactor; review in standard code-review workflow if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The refactor extracts validate_sapling_bundle_consistency into pczt/mod.rs behind validate_supported_pczt, introduces parse_pczt, extracts transparent_derivation_matches_selected_account and map_orchard_verifier_error, makes check_pczt_transparent return a bool indicating whether a wallet input was found, threads a pool_label through Orchard helpers, and generalizes the UI renderer GuiZcashOverviewOrchard to GuiZcashOverviewShielded. No dependency changes. The diff shows functionally equivalent logic moved into helpers, with minor string-message changes (e.g., ‘input’ vs ‘output’ labels).
Changed components
rust/apps/zcash/src/lib.rsrust/apps/zcash/src/pczt/check.rsrust/apps/zcash/src/pczt/mod.rssrc/ui/gui_chain/multi/gui_zcash.cInspect captured patch +229 / −152
diff --git a/rust/apps/zcash/src/lib.rs b/rust/apps/zcash/src/lib.rs
index f932195..f617a77 100644
--- a/rust/apps/zcash/src/lib.rs
+++ b/rust/apps/zcash/src/lib.rs
@@ -13,7 +13,6 @@ use alloc::{
};
use pczt::structs::ParsedPczt;
use zcash_vendor::{
- pczt::Pczt,
zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey},
zcash_protocol::consensus::{self},
zip32,
@@ -64,8 +63,7 @@ pub fn check_pczt_cypherpunk<P: consensus::Parameters>(
seed_fingerprint: &[u8; 32],
account_index: u32,
) -> Result<()> {
- let pczt =
- Pczt::parse(pczt).map_err(|_e| ZcashError::InvalidPczt("invalid pczt data".to_string()))?;
+ let pczt = pczt::parse_pczt(pczt)?;
let account_index = zip32::AccountId::try_from(account_index)
.map_err(|_e| ZcashError::InvalidDataError("invalid account index".to_string()))?;
let ufvk = UnifiedFullViewingKey::decode(params, ufvk_text)
@@ -74,7 +72,15 @@ pub fn check_pczt_cypherpunk<P: consensus::Parameters>(
"transparent xpub is not present".to_string(),
))?;
pczt::check::check_pczt_orchard(params, seed_fingerprint, account_index, &ufvk, &pczt)?;
- pczt::check::check_pczt_transparent(params, seed_fingerprint, account_index, xpub, &pczt, false)
+ pczt::check::check_pczt_transparent(
+ params,
+ seed_fingerprint,
+ account_index,
+ xpub,
+ &pczt,
+ false,
+ )?;
+ Ok(())
}
#[cfg(feature = "multi_coins")]
@@ -85,6 +91,26 @@ pub fn check_pczt_multi_coins<P: consensus::Parameters>(
seed_fingerprint: &[u8; 32],
account_index: u32,
) -> Result<()> {
+ let pczt = pczt::parse_pczt(pczt)?;
+ let account_pubkey = transparent_account_pubkey_from_xpub(xpub)?;
+ let account_index = zip32::AccountId::try_from(account_index)
+ .map_err(|_e| ZcashError::InvalidDataError("invalid account index".to_string()))?;
+
+ pczt::check::check_pczt_transparent(
+ params,
+ seed_fingerprint,
+ account_index,
+ &account_pubkey,
+ &pczt,
+ true,
+ )?;
+ Ok(())
+}
+
+#[cfg(feature = "multi_coins")]
+fn transparent_account_pubkey_from_xpub(
+ xpub: &str,
+) -> Result<zcash_vendor::transparent::keys::AccountPubKey> {
use core::str::FromStr;
use zcash_vendor::{bip32, transparent};
@@ -101,22 +127,8 @@ pub fn check_pczt_multi_coins<P: consensus::Parameters>(
bytes
};
- let account_pubkey = transparent::keys::AccountPubKey::deserialize(&key)
- .map_err(|e| ZcashError::InvalidDataError(e.to_string()))?;
-
- let pczt =
- Pczt::parse(pczt).map_err(|_e| ZcashError::InvalidPczt("invalid pczt data".to_string()))?;
- let account_index = zip32::AccountId::try_from(account_index)
- .map_err(|_e| ZcashError::InvalidDataError("invalid account index".to_string()))?;
-
- pczt::check::check_pczt_transparent(
- params,
- seed_fingerprint,
- account_index,
- &account_pubkey,
- &pczt,
- true,
- )
+ transparent::keys::AccountPubKey::deserialize(&key)
+ .map_err(|e| ZcashError::InvalidDataError(e.to_string()))
}
/// Parses a Partially Created Zcash Transaction (PCZT) and extracts its details.
@@ -129,7 +141,6 @@ pub fn check_pczt_multi_coins<P: consensus::Parameters>(
/// * `pczt` - The binary representation of the PCZT to parse
/// * `ufvk_text` - The string representation of the Unified Full Viewing Key
/// * `seed_fingerprint` - A 32-byte fingerprint of the seed used to derive keys
-///
/// # Returns
/// * `Result<ParsedPczt>` - A structured representation of the PCZT if successful
///
@@ -146,8 +157,7 @@ pub fn parse_pczt_cypherpunk<P: consensus::Parameters>(
) -> Result<ParsedPczt> {
let ufvk = UnifiedFullViewingKey::decode(params, ufvk_text)
.map_err(|e| ZcashError::InvalidDataError(e.to_string()))?;
- let pczt =
- Pczt::parse(pczt).map_err(|_e| ZcashError::InvalidPczt("invalid pczt data".to_string()))?;
+ let pczt = pczt::parse_pczt(pczt)?;
pczt::parse::parse_pczt_cypherpunk(params, seed_fingerprint, &ufvk, &pczt)
}
@@ -156,6 +166,7 @@ mod additional_tests {
use super::*;
use zcash_vendor::zcash_protocol::consensus::MAIN_NETWORK;
+ #[cfg(feature = "cypherpunk")]
#[test]
fn test_get_address() {
let ufvk_text = "uview10zf3gnxd08cne6g7ryh6lln79duzsayg0qxktvyc3l6uutfk0agmyclm5g82h5z0lqv4c2gzp0eu0qc0nxzurxhj4ympwn3gj5c3dc9g7ca4eh3q09fw9kka7qplzq0wnauekf45w9vs4g22khtq57sc8k6j6s70kz0rtqlyat6zsjkcqfrlm9quje8vzszs8y9mjvduf7j2vx329hk2v956g6svnhqswxfp3n760mw233w7ffgsja2szdhy5954hsfldalf28wvav0tctxwkmkgrk43tq2p7sqchzc6";
@@ -180,8 +191,7 @@ pub fn parse_pczt_multi_coins<P: consensus::Parameters>(
pczt: &[u8],
seed_fingerprint: &[u8; 32],
) -> Result<ParsedPczt> {
- let pczt =
- Pczt::parse(pczt).map_err(|_e| ZcashError::InvalidPczt("invalid pczt data".to_string()))?;
+ let pczt = pczt::parse_pczt(pczt)?;
pczt::parse::parse_pczt_multi_coins(params, seed_fingerprint, &pczt)
}
@@ -193,7 +203,7 @@ pub fn parse_pczt_multi_coins<P: consensus::Parameters>(
///
/// # Parameters
/// * `pczt` - The binary representation of the PCZT to sign
-/// * `seed` - The seed to sign the PCZT with
+/// * `seed` - The seed to sign the PCZT with
///
/// # Returns
/// * `Result<Vec<u8>>` - The signed PCZT if successful, or an error otherwise
@@ -202,8 +212,7 @@ pub fn parse_pczt_multi_coins<P: consensus::Parameters>(
/// * `ZcashError::InvalidPczt` - If the PCZT data is malformed or cannot be parsed
/// * Other errors from the underlying signing process
pub fn sign_pczt(pczt: &[u8], seed: &[u8]) -> Result<Vec<u8>> {
- let pczt =
- Pczt::parse(pczt).map_err(|_e| ZcashError::InvalidPczt("invalid pczt data".to_string()))?;
+ let pczt = pczt::parse_pczt(pczt)?;
pczt::sign::sign_pczt(pczt, seed)
}
diff --git a/rust/apps/zcash/src/pczt/check.rs b/rust/apps/zcash/src/pczt/check.rs
index bdde3f5..02f4291 100644
--- a/rust/apps/zcash/src/pczt/check.rs
+++ b/rust/apps/zcash/src/pczt/check.rs
@@ -1,11 +1,14 @@
// checking logic for PCZT
-use alloc::{string::ToString, vec};
+use alloc::string::ToString;
-use super::*;
+use crate::errors::ZcashError;
#[cfg(feature = "cypherpunk")]
-use orchard::{keys::FullViewingKey, value::ValueSum, Address};
+use zcash_vendor::{
+ orchard::{self, keys::FullViewingKey, value::ValueSum, Address},
+ zcash_keys::keys::UnifiedFullViewingKey,
+};
use zcash_vendor::{
pczt::{self, roles::verifier::Verifier, Pczt},
@@ -13,32 +16,21 @@ use zcash_vendor::{
sha2::{Digest, Sha256},
transparent::{self, address::TransparentAddress, keys::AccountPubKey},
zcash_address::{ToAddress, ZcashAddress},
- zcash_protocol::{
- consensus::{self, NetworkConstants},
- value::ZatBalance,
- },
+ zcash_protocol::consensus::{self},
zip32,
};
-fn validate_sapling_bundle_consistency(pczt: &Pczt) -> Result<(), ZcashError> {
- let value_balance = (*pczt.sapling().value_sum())
- .try_into()
- .ok()
- .and_then(|v| ZatBalance::from_i64(v).ok())
- .ok_or(ZcashError::InvalidPczt(
- "sapling value_sum is invalid".to_string(),
- ))?;
- let sapling_value_sum: i64 = value_balance.into();
- let has_sapling_bundle =
- !pczt.sapling().spends().is_empty() || !pczt.sapling().outputs().is_empty();
-
- if !has_sapling_bundle && sapling_value_sum != 0 {
- return Err(ZcashError::InvalidPczt(
- "sapling value_sum must be zero when Sapling bundle is empty".to_string(),
- ));
- }
+#[cfg(feature = "cypherpunk")]
+use zcash_vendor::zcash_protocol::consensus::NetworkConstants;
- Ok(())
+#[cfg(feature = "cypherpunk")]
+fn map_orchard_verifier_error(
+ error: pczt::roles::verifier::OrchardError<ZcashError>,
+) -> ZcashError {
+ match error {
+ pczt::roles::verifier::OrchardError::Custom(error) => error,
+ error => ZcashError::InvalidDataError(alloc::format!("{error:?}")),
+ }
}
#[cfg(feature = "cypherpunk")]
@@ -49,13 +41,21 @@ pub fn check_pczt_orchard<P: consensus::Parameters>(
ufvk: &UnifiedFullViewingKey,
pczt: &Pczt,
) -> Result<(), ZcashError> {
- validate_sapling_bundle_consistency(pczt)?;
+ super::validate_supported_pczt(pczt)?;
Verifier::new(pczt.clone())
.with_orchard(|bundle| {
- check_orchard(params, seed_fingerprint, account_index, ufvk, bundle)
- .map_err(pczt::roles::verifier::OrchardError::Custom)
+ check_orchard(
+ params,
+ seed_fingerprint,
+ account_index,
+ ufvk,
+ bundle,
+ "Orchard",
+ )
+ .map_err(pczt::roles::verifier::OrchardError::Custom)?;
+ Ok(())
})
- .map_err(|e| ZcashError::InvalidDataError(alloc::format!("{e:?}")))?;
+ .map_err(map_orchard_verifier_error)?;
Ok(())
}
@@ -66,11 +66,12 @@ pub fn check_pczt_transparent<P: consensus::Parameters>(
xpub: &AccountPubKey,
pczt: &Pczt,
check_sfp: bool,
-) -> Result<(), ZcashError> {
- validate_sapling_bundle_consistency(pczt)?;
+) -> Result<bool, ZcashError> {
+ super::validate_supported_pczt(pczt)?;
+ let mut has_my_input = false;
Verifier::new(pczt.clone())
.with_transparent(|bundle| {
- check_transparent(
+ has_my_input = check_transparent(
params,
seed_fingerprint,
account_index,
@@ -78,13 +79,14 @@ pub fn check_pczt_transparent<P: consensus::Parameters>(
bundle,
check_sfp,
)
- .map_err(pczt::roles::verifier::TransparentError::Custom)
+ .map_err(pczt::roles::verifier::TransparentError::Custom)?;
+ Ok(())
})
.map_err(|e| match e {
pczt::roles::verifier::TransparentError::Custom(e) => e,
_e => ZcashError::InvalidDataError(alloc::format!("{:?}", _e)),
})?;
- Ok(())
+ Ok(has_my_input)
}
fn check_transparent<P: consensus::Parameters>(
@@ -94,7 +96,7 @@ fn check_transparent<P: consensus::Parameters>(
xpub: &AccountPubKey,
bundle: &transparent::pczt::Bundle,
check_sfp: bool,
-) -> Result<(), ZcashError> {
+) -> Result<bool, ZcashError> {
let mut has_my_input = false;
bundle.inputs().iter().try_for_each(|input| {
let _has = check_transparent_input(params, seed_fingerprint, account_index, xpub, input)?;
@@ -110,7 +112,7 @@ fn check_transparent<P: consensus::Parameters>(
if check_sfp && !has_my_input {
return Err(ZcashError::PcztNoMyInputs);
}
- Ok(())
+ Ok(has_my_input)
}
fn check_transparent_input<P: consensus::Parameters>(
@@ -136,30 +138,23 @@ fn check_transparent_input<P: consensus::Parameters>(
}
Some((pubkey, derivation)) => {
// 2: derive my pubkey
- let target = xpub
- .derive_pubkey_at_bip32_path(
+ let belongs_to_selected_account =
+ super::transparent_derivation_matches_selected_account(
params,
+ seed_fingerprint,
account_index,
- derivation.derivation_path(),
- )
- .map_err(|_| {
- ZcashError::InvalidPczt(
- "transparent input bip32 derivation path invalid".to_string(),
- )
- })?;
- // 3: check my pubkey
- if &target.serialize() != pubkey {
- return Err(ZcashError::InvalidPczt(
- "transparent input script pubkey mismatch".to_string(),
- ));
- }
- // 4: check script pubkey
+ xpub,
+ pubkey,
+ derivation,
+ "input",
+ )?;
+ // 3: check script pubkey
if hash[..] != Ripemd160::digest(Sha256::digest(pubkey))[..] {
return Err(ZcashError::InvalidPczt(
"transparent input script pubkey mismatch".to_string(),
));
}
- Ok(true)
+ Ok(belongs_to_selected_account)
}
}
}
@@ -206,24 +201,15 @@ fn check_transparent_output<P: consensus::Parameters>(
match output.bip32_derivation().get(pubkey) {
Some(bip32_derivation) => {
if seed_fingerprint == bip32_derivation.seed_fingerprint() {
- //verify public key
- let target = xpub
- .derive_pubkey_at_bip32_path(
- params,
- account_index,
- bip32_derivation.derivation_path(),
- )
- .map_err(|_| {
- ZcashError::InvalidPczt(
- "transparent input bip32 derivation path invalid"
- .to_string(),
- )
- })?;
- if &target.serialize() != pubkey {
- return Err(ZcashError::InvalidPczt(
- "transparent output script pubkey mismatch".to_string(),
- ));
- }
+ super::transparent_derivation_matches_selected_account(
+ params,
+ seed_fingerprint,
+ account_index,
+ xpub,
+ pubkey,
+ bip32_derivation,
+ "output",
+ )?;
Ok(())
} else {
//not my output, pass
@@ -267,24 +253,15 @@ fn check_transparent_output<P: consensus::Parameters>(
Ok(())
}
Some((pubkey, derivation)) => {
- // 2: derive my pubkey
- let target = xpub
- .derive_pubkey_at_bip32_path(
- params,
- account_index,
- derivation.derivation_path(),
- )
- .map_err(|_| {
- ZcashError::InvalidPczt(
- "transparent input bip32 derivation path invalid".to_string(),
- )
- })?;
- // 3: check my pubkey
- if &target.serialize() != pubkey {
- return Err(ZcashError::InvalidPczt(
- "transparent input script pubkey mismatch".to_string(),
- ));
- }
+ super::transparent_derivation_matches_selected_account(
+ params,
+ seed_fingerprint,
+ account_index,
+ xpub,
+ pubkey,
+ derivation,
+ "output",
+ )?;
// TODO: find a proper way to check script pubkey
Ok(())
}
@@ -304,9 +281,17 @@ fn check_orchard<P: consensus::Parameters>(
account_index: zip32::AccountId,
ufvk: &UnifiedFullViewingKey,
bundle: &orchard::pczt::Bundle,
+ pool_label: &str,
) -> Result<(), ZcashError> {
bundle.actions().iter().try_for_each(|action| {
- check_action(params, seed_fingerprint, account_index, ufvk, action)?;
+ check_action(
+ params,
+ seed_fingerprint,
+ account_index,
+ ufvk,
+ action,
+ pool_label,
+ )?;
Ok::<_, ZcashError>(())
})?;
@@ -322,9 +307,9 @@ fn check_orchard<P: consensus::Parameters>(
match calculated_value_balance {
Ok(value_balance) if &value_balance == bundle.value_sum() => Ok(()),
- _ => Err(ZcashError::InvalidPczt(
- "invalid Orchard bundle value balance".into(),
- )),
+ _ => Err(ZcashError::InvalidPczt(alloc::format!(
+ "invalid {pool_label} bundle value balance"
+ ))),
}
}
@@ -336,18 +321,29 @@ fn check_action<P: consensus::Parameters>(
account_index: zip32::AccountId,
ufvk: &UnifiedFullViewingKey,
action: &orchard::pczt::Action,
+ pool_label: &str,
) -> Result<(), ZcashError> {
// 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| {
- ZcashError::InvalidPczt(alloc::format!("invalid cv_net in Orchard action: {e:?}"))
+ ZcashError::InvalidPczt(alloc::format!(
+ "invalid cv_net in {pool_label} action: {e:?}"
+ ))
})?;
let fvk = ufvk.orchard().ok_or(ZcashError::InvalidDataError(
"orchard fvk is not present".to_string(),
))?;
- check_action_spend(params, seed_fingerprint, account_index, fvk, action.spend())?;
- check_action_output(ufvk, action)
+ check_action_spend(
+ params,
+ seed_fingerprint,
+ account_index,
+ fvk,
+ action.spend(),
+ pool_label,
+ )?;
+ check_action_output(params, ufvk, action, pool_label)?;
+ Ok(())
}
#[cfg(feature = "cypherpunk")]
@@ -358,6 +354,7 @@ fn check_action_spend<P: consensus::Parameters>(
account_index: zip32::AccountId,
fvk: &FullViewingKey,
spend: &orchard::pczt::Spend,
+ pool_label: &str,
) -> Result<(), ZcashError> {
// 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()) {
@@ -382,10 +379,12 @@ fn check_action_spend<P: consensus::Parameters>(
if let Some(expected_fvk) = can_verify_nf_rk {
spend.verify_nullifier(expected_fvk).map_err(|e| {
- ZcashError::InvalidPczt(alloc::format!("invalid Orchard action nullifier: {e:?}"))
+ ZcashError::InvalidPczt(alloc::format!(
+ "invalid {pool_label} action nullifier: {e:?}"
+ ))
})?;
spend.verify_rk(expected_fvk).map_err(|e| {
- ZcashError::InvalidPczt(alloc::format!("invalid Orchard action rk: {e:?}"))
+ ZcashError::InvalidPczt(alloc::format!("invalid {pool_label} action rk: {e:?}"))
})?;
}
@@ -403,15 +402,17 @@ fn is_wallet_orchard_address(fvk: &FullViewingKey, address: &Address) -> bool {
#[cfg(feature = "cypherpunk")]
// check output cmx and internal-ovk output ownership constraints
-fn check_action_output(
+fn check_action_output<P: consensus::Parameters>(
+ _params: &P,
ufvk: &UnifiedFullViewingKey,
action: &orchard::pczt::Action,
+ pool_label: &str,
) -> Result<(), ZcashError> {
action
.output()
.verify_note_commitment(action.spend())
.map_err(|e| {
- ZcashError::InvalidPczt(alloc::format!("invalid Orchard action cmx: {e:?}"))
+ ZcashError::InvalidPczt(alloc::format!("invalid {pool_label} action cmx: {e:?}"))
})?;
let fvk = ufvk.orchard().ok_or(ZcashError::InvalidDataError(
@@ -423,18 +424,19 @@ fn check_action_output(
.transparent()
.map(|k| orchard::keys::OutgoingViewingKey::from(k.internal_ovk().as_bytes()));
- let mut keys = vec![(Some(external_ovk), false), (Some(internal_ovk), true)];
+ let mut keys = alloc::vec![(Some(external_ovk), false), (Some(internal_ovk), true)];
if let Some(ovk) = transparent_internal_ovk {
keys.push((Some(ovk), true));
}
for (vk, is_internal_ovk) in keys {
- if let Some((_, address, _)) = super::parse::decode_output_enc_ciphertext(action, vk.as_ref())?
+ if let Some((_, address, _)) =
+ super::parse::decode_output_enc_ciphertext(action, vk.as_ref())?
{
if is_internal_ovk && !is_wallet_orchard_address(fvk, &address) {
- return Err(ZcashError::InvalidPczt(
- "Orchard output was recoverable with an internal OVK but does not belong to this wallet".into(),
- ));
+ return Err(ZcashError::InvalidPczt(alloc::format!(
+ "{pool_label} output was recoverable with an internal OVK but does not belong to this wallet"
+ )));
}
break;
}
diff --git a/rust/apps/zcash/src/pczt/mod.rs b/rust/apps/zcash/src/pczt/mod.rs
index a0624c8..6939ba9 100644
--- a/rust/apps/zcash/src/pczt/mod.rs
+++ b/rust/apps/zcash/src/pczt/mod.rs
@@ -1,18 +1,84 @@
-use alloc::{string::ToString, vec::Vec};
+pub mod check;
+pub mod parse;
+pub mod sign;
+pub mod structs;
+use alloc::{format, string::ToString, vec::Vec};
+// Shared imports consumed by the not-yet-refactored `parse`/`sign` submodules via `use super::*`.
+// These move into those modules when they are refactored in a later commit.
use keystore::algorithms::secp256k1::get_public_key_by_seed;
use keystore::algorithms::zcash::calculate_seed_fingerprint;
-use zcash_vendor::zcash_keys::keys::UnifiedFullViewingKey;
-
-#[cfg(feature = "cypherpunk")]
-use zcash_vendor::orchard;
-
#[cfg(feature = "cypherpunk")]
use keystore::algorithms::zcash::sign_message_orchard;
+use zcash_vendor::{
+ pczt::Pczt,
+ transparent,
+ zcash_protocol::value::ZatBalance,
+ zip32,
+};
+#[cfg(feature = "cypherpunk")]
+use zcash_vendor::orchard;
use crate::errors::ZcashError;
-pub mod check;
-pub mod parse;
-pub mod sign;
-pub mod structs;
+pub(crate) fn parse_pczt(bytes: &[u8]) -> Result<Pczt, ZcashError> {
+ Pczt::parse(bytes).map_err(|_| ZcashError::InvalidPczt("invalid pczt data".to_string()))
+}
+
+pub(crate) fn validate_supported_pczt(pczt: &Pczt) -> Result<(), ZcashError> {
+ validate_sapling_bundle_consistency(pczt)?;
+
+ Ok(())
+}
+
+fn validate_sapling_bundle_consistency(pczt: &Pczt) -> Result<(), ZcashError> {
+ let value_balance = (*pczt.sapling().value_sum())
+ .try_into()
+ .ok()
+ .and_then(|v| ZatBalance::from_i64(v).ok())
+ .ok_or(ZcashError::InvalidPczt(
+ "sapling value_sum is invalid".to_string(),
+ ))?;
+ let sapling_value_sum: i64 = value_balance.into();
+ let has_sapling_bundle =
+ !pczt.sapling().spends().is_empty() || !pczt.sapling().outputs().is_empty();
+
+ if !has_sapling_bundle && sapling_value_sum != 0 {
+ return Err(ZcashError::InvalidPczt(
+ "sapling value_sum must be zero when Sapling bundle is empty".to_string(),
+ ));
+ }
+
+ Ok(())
+}
+
+pub(crate) fn transparent_derivation_matches_selected_account<
+ P: zcash_vendor::zcash_protocol::consensus::Parameters,
+>(
+ params: &P,
+ seed_fingerprint: &[u8; 32],
+ account_index: zip32::AccountId,
+ xpub: &transparent::keys::AccountPubKey,
+ pubkey: &[u8; 33],
+ derivation: &transparent::pczt::Bip32Derivation,
+ field_label: &str,
+) -> Result<bool, ZcashError> {
+ if seed_fingerprint != derivation.seed_fingerprint() {
+ return Ok(false);
+ }
+
+ let target = xpub
+ .derive_pubkey_at_bip32_path(params, account_index, derivation.derivation_path())
+ .map_err(|_| {
+ ZcashError::InvalidPczt(format!(
+ "transparent {field_label} bip32 derivation path invalid"
+ ))
+ })?;
+ if &target.serialize() != pubkey {
+ return Err(ZcashError::InvalidPczt(format!(
+ "transparent {field_label} script pubkey mismatch"
+ )));
+ }
+
+ Ok(true)
+}
diff --git a/src/ui/gui_chain/multi/gui_zcash.c b/src/ui/gui_chain/multi/gui_zcash.c
index 5657822..f55027d 100644
--- a/src/ui/gui_chain/multi/gui_zcash.c
+++ b/src/ui/gui_chain/multi/gui_zcash.c
@@ -55,7 +55,7 @@ void *GuiGetZcashGUIData(void)
}
static lv_obj_t* GuiZcashOverviewTransparent(lv_obj_t *parent, lv_obj_t *last_view);
-static lv_obj_t* GuiZcashOverviewOrchard(lv_obj_t *parent, lv_obj_t *last_view);
+static lv_obj_t* GuiZcashOverviewShielded(lv_obj_t *parent, lv_obj_t *last_view, DisplayOrchard *pool, const char *labelText);
static lv_obj_t* GuiZcashOverviewFrom(lv_obj_t *parent, VecFFI_DisplayFrom *from, lv_obj_t *last_view);
static lv_obj_t* GuiZcashOverviewTo(lv_obj_t *parent, VecFFI_DisplayTo *to, lv_obj_t *last_view);
@@ -83,7 +83,7 @@ void GuiZcashOverview(lv_obj_t *parent, void *totalData)
}
if (g_zcashData->orchard != NULL) {
- last_view = GuiZcashOverviewOrchard(container, last_view);
+ last_view = GuiZcashOverviewShielded(container, last_view, g_zcashData->orchard, _("Orchard"));
}
}
@@ -108,21 +108,21 @@ static lv_obj_t* GuiZcashOverviewTransparent(lv_obj_t *parent, lv_obj_t *last_vi
return inner_last_view;
}
-static lv_obj_t* GuiZcashOverviewOrchard(lv_obj_t* parent, lv_obj_t *last_view)
+static lv_obj_t* GuiZcashOverviewShielded(lv_obj_t* parent, lv_obj_t *last_view, DisplayOrchard *pool, const char *labelText)
{
lv_obj_t* inner_last_view;
- lv_obj_t* label = GuiCreateIllustrateLabel(parent, _("Orchard"));
+ lv_obj_t* label = GuiCreateIllustrateLabel(parent, labelText);
lv_obj_align_to(label, last_view, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 24);
inner_last_view = label;
- if (g_zcashData->orchard->from->size > 0) {
- lv_obj_t* from_view = GuiZcashOverviewFrom(parent, g_zcashData->orchard->from, inner_last_view);
+ if (pool->from->size > 0) {
+ lv_obj_t* from_view = GuiZcashOverviewFrom(parent, pool->from, inner_last_view);
inner_last_view = from_view;
}
- if (g_zcashData->orchard->to->size > 0) {
- lv_obj_t* to_view = GuiZcashOverviewTo(parent, g_zcashData->orchard->to, inner_last_view);
+ if (pool->to->size > 0) {
+ lv_obj_t* to_view = GuiZcashOverviewTo(parent, pool->to, inner_last_view);
inner_last_view = to_view;
}
@@ -333,4 +333,4 @@ void FreeZcashMemory(void)
CHECK_FREE_UR_RESULT(g_urResult, false);
CHECK_FREE_UR_RESULT(g_urMultiResult, true);
CHECK_FREE_PARSE_RESULT(g_parseResult);
-}
\ No newline at end of file
+}
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.