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() {