diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index f9ab38296..eb010ed97 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -950,24 +950,22 @@ impl Transaction { orchard_serialization::write_action_without_auth(w, a) })?; - if !bundle.actions().is_empty() { - orchard_serialization::write_flags(&mut writer, &bundle.flags())?; - writer.write_all(&bundle.value_balance().to_i64_le_bytes())?; - orchard_serialization::write_anchor(&mut writer, bundle.anchor())?; - Vector::write( - &mut writer, - bundle.authorization().proof().as_ref(), - |w, b| w.write_u8(*b), - )?; - Array::write( - &mut writer, - bundle.actions().iter().map(|a| a.authorization()), - |w, auth| w.write_all(&<[u8; 64]>::from(*auth)), - )?; - writer.write_all(&<[u8; 64]>::from( - bundle.authorization().binding_signature(), - ))?; - } + orchard_serialization::write_flags(&mut writer, &bundle.flags())?; + writer.write_all(&bundle.value_balance().to_i64_le_bytes())?; + orchard_serialization::write_anchor(&mut writer, bundle.anchor())?; + Vector::write( + &mut writer, + bundle.authorization().proof().as_ref(), + |w, b| w.write_u8(*b), + )?; + Array::write( + &mut writer, + bundle.actions().iter().map(|a| a.authorization()), + |w, auth| w.write_all(&<[u8; 64]>::from(*auth)), + )?; + writer.write_all(&<[u8; 64]>::from( + bundle.authorization().binding_signature(), + ))?; } else { CompactSize::write(&mut writer, 0)?; } @@ -1013,8 +1011,8 @@ pub struct TzeDigests { pub struct TxDigests { pub header_digest: A, pub transparent_digests: Option>, - pub sapling_digest: A, - pub orchard_digest: A, + pub sapling_digest: Option, + pub orchard_digest: Option, #[cfg(feature = "zfuture")] pub tze_digests: Option>, } diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index c2c1140c4..e7d1ff5b2 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -253,25 +253,25 @@ fn hash_transparent_txid_data(t_digests: Option<&TransparentDigests h.finalize() } -fn hash_sapling_txid_data( - sapling_bundle: Option<&sapling::Bundle>, -) -> Blake2bHash { +fn hash_sapling_txid_data(bundle: &sapling::Bundle) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION); - if let Some(bundle) = sapling_bundle { - if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) { - h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes()) - .unwrap(); + if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) { + h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes()) + .unwrap(); - h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes()) - .unwrap(); + h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes()) + .unwrap(); - h.write_all(&bundle.value_balance.to_i64_le_bytes()) - .unwrap(); - } + h.write_all(&bundle.value_balance.to_i64_le_bytes()) + .unwrap(); } h.finalize() } +fn hash_sapling_txid_empty() -> Blake2bHash { + hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize() +} + /// Write disjoint parts of each Orchard shielded action as 3 separate hashes: /// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized /// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION @@ -283,43 +283,45 @@ fn hash_sapling_txid_data( /// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard), /// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION fn hash_orchard_txid_data( - orchard_bundle: Option<&orchard::Bundle>, + bundle: &orchard::Bundle, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); - if let Some(bundle) = orchard_bundle { - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); - let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); + let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); + let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); + let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); - for action in bundle.actions().iter() { - ch.write_all(&action.nullifier().to_bytes()).unwrap(); - ch.write_all(&action.cmx().to_bytes()).unwrap(); - ch.write_all(&action.encrypted_note().epk_bytes).unwrap(); - ch.write_all(&action.encrypted_note().enc_ciphertext[..52]) - .unwrap(); - - mh.write_all(&action.encrypted_note().enc_ciphertext[52..564]) - .unwrap(); - - nh.write_all(&action.cv_net().to_bytes()).unwrap(); - nh.write_all(&<[u8; 32]>::from(action.rk())).unwrap(); - nh.write_all(&action.encrypted_note().enc_ciphertext[564..]) - .unwrap(); - nh.write_all(&action.encrypted_note().out_ciphertext) - .unwrap(); - } - - h.write_all(&ch.finalize().as_bytes()).unwrap(); - h.write_all(&mh.finalize().as_bytes()).unwrap(); - h.write_all(&nh.finalize().as_bytes()).unwrap(); - ser_orch::write_flags(&mut h, bundle.flags()).unwrap(); - h.write_all(&bundle.value_balance().to_i64_le_bytes()) + for action in bundle.actions().iter() { + ch.write_all(&action.nullifier().to_bytes()).unwrap(); + ch.write_all(&action.cmx().to_bytes()).unwrap(); + ch.write_all(&action.encrypted_note().epk_bytes).unwrap(); + ch.write_all(&action.encrypted_note().enc_ciphertext[..52]) + .unwrap(); + + mh.write_all(&action.encrypted_note().enc_ciphertext[52..564]) + .unwrap(); + + nh.write_all(&action.cv_net().to_bytes()).unwrap(); + nh.write_all(&<[u8; 32]>::from(action.rk())).unwrap(); + nh.write_all(&action.encrypted_note().enc_ciphertext[564..]) + .unwrap(); + nh.write_all(&action.encrypted_note().out_ciphertext) .unwrap(); - ser_orch::write_anchor(&mut h, bundle.anchor()).unwrap(); } + + h.write_all(&ch.finalize().as_bytes()).unwrap(); + h.write_all(&mh.finalize().as_bytes()).unwrap(); + h.write_all(&nh.finalize().as_bytes()).unwrap(); + ser_orch::write_flags(&mut h, bundle.flags()).unwrap(); + h.write_all(&bundle.value_balance().to_i64_le_bytes()) + .unwrap(); + ser_orch::write_anchor(&mut h, bundle.anchor()).unwrap(); h.finalize() } +fn hash_orchard_txid_empty() -> Blake2bHash { + hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() +} + #[cfg(feature = "zfuture")] fn hash_tze_txid_data(tze_digests: Option<&TzeDigests>) -> Blake2bHash { let mut h = hasher(ZCASH_TZE_HASH_PERSONALIZATION); @@ -343,8 +345,8 @@ pub struct TxIdDigester; impl TransactionDigest for TxIdDigester { type HeaderDigest = Blake2bHash; type TransparentDigest = Option>; - type SaplingDigest = Blake2bHash; - type OrchardDigest = Blake2bHash; + type SaplingDigest = Option; + type OrchardDigest = Option; #[cfg(feature = "zfuture")] type TzeDigest = Option>; @@ -372,14 +374,14 @@ impl TransactionDigest for TxIdDigester { &self, sapling_bundle: Option<&sapling::Bundle>, ) -> Self::SaplingDigest { - hash_sapling_txid_data(sapling_bundle) + sapling_bundle.map(hash_sapling_txid_data) } fn digest_orchard( &self, orchard_bundle: Option<&orchard::Bundle>, ) -> Self::OrchardDigest { - hash_orchard_txid_data(orchard_bundle) + orchard_bundle.map(hash_orchard_txid_data) } #[cfg(feature = "zfuture")] @@ -406,13 +408,13 @@ impl TransactionDigest for TxIdDigester { } } -pub fn to_hash( +pub(crate) fn to_hash( _txversion: TxVersion, consensus_branch_id: BranchId, header_digest: Blake2bHash, transparent_digests: Option<&TransparentDigests>, - sapling_digest: Blake2bHash, - orchard_digest: Blake2bHash, + sapling_digest: Option, + orchard_digest: Option, #[cfg(feature = "zfuture")] tze_digests: Option<&TzeDigests>, ) -> Blake2bHash { let mut personal = [0; 16]; @@ -425,8 +427,18 @@ pub fn to_hash( h.write_all(header_digest.as_bytes()).unwrap(); h.write_all(hash_transparent_txid_data(transparent_digests).as_bytes()) .unwrap(); - h.write_all(sapling_digest.as_bytes()).unwrap(); - h.write_all(orchard_digest.as_bytes()).unwrap(); + h.write_all( + sapling_digest + .unwrap_or_else(hash_sapling_txid_empty) + .as_bytes(), + ) + .unwrap(); + h.write_all( + orchard_digest + .unwrap_or_else(hash_orchard_txid_empty) + .as_bytes(), + ) + .unwrap(); #[cfg(feature = "zfuture")] if _txversion.has_tze() {