use crate::encoding::{StateWrite, WriteBytesExt};
use core::borrow::Borrow;
use core::convert::TryFrom;
use core2::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params};
use ff::PrimeField;
use ::orchard::bundle::{self as orchard};
use ::sapling::bundle::{OutputDescription, SpendDescription};
use ::transparent::bundle::{self as transparent, TxIn, TxOut};
use zcash_protocol::{
consensus::{BlockHeight, BranchId},
value::ZatBalance,
};
use super::{
Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion,
};
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
use zcash_protocol::value::Zatoshis;
#[cfg(zcash_unstable = "zfuture")]
use super::{
components::tze::{self, TzeIn, TzeOut},
TzeDigests,
};
const ZCASH_TX_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashTxHash_";
const ZCASH_HEADERS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdHeadersHash";
pub(crate) const ZCASH_TRANSPARENT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTranspaHash";
const ZCASH_SAPLING_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSaplingHash";
#[cfg(zcash_unstable = "zfuture")]
const ZCASH_TZE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZE____Hash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOutputsHash";
#[cfg(zcash_unstable = "zfuture")]
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEIns_Hash";
#[cfg(zcash_unstable = "zfuture")]
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEOutsHash";
const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash";
const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash";
const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash";
const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash";
const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash";
const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash";
const ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutN__Hash";
const ZCASH_AUTH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZTxAuthHash_";
const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTransHash";
const ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthSapliHash";
#[cfg(zcash_unstable = "zfuture")]
const ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTZE__Hash";
fn hasher(personal: &[u8; 16]) -> StateWrite {
StateWrite(Params::new().hash_length(32).personal(personal).to_state())
}
pub(crate) fn transparent_prevout_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_PREVOUTS_HASH_PERSONALIZATION);
for t_in in vin {
t_in.prevout().write(&mut h).unwrap();
}
h.finalize()
}
pub(crate) fn transparent_sequence_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_SEQUENCE_HASH_PERSONALIZATION);
for t_in in vin {
h.write_u32_le(t_in.sequence()).unwrap();
}
h.finalize()
}
pub(crate) fn transparent_outputs_hash<T: Borrow<TxOut>>(vout: &[T]) -> Blake2bHash {
let mut h = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION);
for t_out in vout {
t_out.borrow().write(&mut h).unwrap();
}
h.finalize()
}
#[cfg(zcash_unstable = "zfuture")]
pub(crate) fn hash_tze_inputs<A>(tze_inputs: &[TzeIn<A>]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION);
for tzein in tze_inputs {
tzein.write_without_witness(&mut h).unwrap();
}
h.finalize()
}
#[cfg(zcash_unstable = "zfuture")]
pub(crate) fn hash_tze_outputs(tze_outputs: &[TzeOut]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION);
for tzeout in tze_outputs {
tzeout.write(&mut h).unwrap();
}
h.finalize()
}
pub(crate) fn hash_sapling_spends<A: sapling::bundle::Authorization>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION);
if !shielded_spends.is_empty() {
let mut ch = hasher(ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION);
for s_spend in shielded_spends {
ch.write_all(s_spend.nullifier().as_ref()).unwrap();
nh.write_all(&s_spend.cv().to_bytes()).unwrap();
nh.write_all(&s_spend.anchor().to_repr()).unwrap();
nh.write_all(&<[u8; 32]>::from(*s_spend.rk())).unwrap();
}
let compact_digest = ch.finalize();
h.write_all(compact_digest.as_bytes()).unwrap();
let noncompact_digest = nh.finalize();
h.write_all(noncompact_digest.as_bytes()).unwrap();
}
h.finalize()
}
pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>]) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION);
if !shielded_outputs.is_empty() {
let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION);
for s_out in shielded_outputs {
ch.write_all(s_out.cmu().to_bytes().as_ref()).unwrap();
ch.write_all(s_out.ephemeral_key().as_ref()).unwrap();
ch.write_all(&s_out.enc_ciphertext()[..52]).unwrap();
mh.write_all(&s_out.enc_ciphertext()[52..564]).unwrap();
nh.write_all(&s_out.cv().to_bytes()).unwrap();
nh.write_all(&s_out.enc_ciphertext()[564..]).unwrap();
nh.write_all(&s_out.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();
}
h.finalize()
}
fn transparent_digests<A: transparent::Authorization>(
bundle: &transparent::Bundle<A>,
) -> TransparentDigests<Blake2bHash> {
TransparentDigests {
prevouts_digest: transparent_prevout_hash(&bundle.vin),
sequence_digest: transparent_sequence_hash(&bundle.vin),
outputs_digest: transparent_outputs_hash(&bundle.vout),
}
}
#[cfg(zcash_unstable = "zfuture")]
fn tze_digests<A: tze::Authorization>(bundle: &tze::Bundle<A>) -> TzeDigests<Blake2bHash> {
TzeDigests {
inputs_digest: hash_tze_inputs(&bundle.vin),
outputs_digest: hash_tze_outputs(&bundle.vout),
per_input_digest: None,
}
}
fn hash_header_txid_data(
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
zip233_amount: &Zatoshis,
) -> Blake2bHash {
let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION);
h.write_u32_le(version.header()).unwrap();
h.write_u32_le(version.version_group_id()).unwrap();
h.write_u32_le(consensus_branch_id.into()).unwrap();
h.write_u32_le(lock_time).unwrap();
h.write_u32_le(expiry_height.into()).unwrap();
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
if version.has_zip233() {
h.write_u64_le((*zip233_amount).into()).unwrap();
}
h.finalize()
}
pub(crate) fn hash_transparent_txid_data(
t_digests: Option<&TransparentDigests<Blake2bHash>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
if let Some(d) = t_digests {
h.write_all(d.prevouts_digest.as_bytes()).unwrap();
h.write_all(d.sequence_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
}
h.finalize()
}
fn hash_sapling_txid_data<A: sapling::bundle::Authorization>(
bundle: &sapling::Bundle<A, ZatBalance>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
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(&bundle.value_balance().to_i64_le_bytes())
.unwrap();
}
h.finalize()
}
fn hash_sapling_txid_empty() -> Blake2bHash {
hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize()
}
#[cfg(zcash_unstable = "zfuture")]
fn hash_tze_txid_data(tze_digests: Option<&TzeDigests<Blake2bHash>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_HASH_PERSONALIZATION);
if let Some(d) = tze_digests {
h.write_all(d.inputs_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
if let Some(s) = d.per_input_digest {
h.write_all(s.as_bytes()).unwrap();
}
}
h.finalize()
}
pub struct TxIdDigester;
impl<A: Authorization> TransactionDigest<A> for TxIdDigester {
type HeaderDigest = Blake2bHash;
type TransparentDigest = Option<TransparentDigests<Blake2bHash>>;
type SaplingDigest = Option<Blake2bHash>;
type OrchardDigest = Option<Blake2bHash>;
#[cfg(zcash_unstable = "zfuture")]
type TzeDigest = Option<TzeDigests<Blake2bHash>>;
type Digest = TxDigests<Blake2bHash>;
fn digest_header(
&self,
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
zip233_amount: &Zatoshis,
) -> Self::HeaderDigest {
hash_header_txid_data(
version,
consensus_branch_id,
lock_time,
expiry_height,
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
zip233_amount,
)
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<A::TransparentAuth>>,
) -> Self::TransparentDigest {
transparent_bundle.map(transparent_digests)
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth, ZatBalance>>,
) -> Self::SaplingDigest {
sapling_bundle.map(hash_sapling_txid_data)
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<A::OrchardAuth, ZatBalance>>,
) -> Self::OrchardDigest {
orchard_bundle.map(|b| b.commitment().0)
}
#[cfg(zcash_unstable = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<A::TzeAuth>>) -> Self::TzeDigest {
tze_bundle.map(tze_digests)
}
fn combine(
&self,
header_digest: Self::HeaderDigest,
transparent_digests: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(zcash_unstable = "zfuture")] tze_digests: Self::TzeDigest,
) -> Self::Digest {
TxDigests {
header_digest,
transparent_digests,
sapling_digest,
orchard_digest,
#[cfg(zcash_unstable = "zfuture")]
tze_digests,
}
}
}
pub(crate) fn to_hash(
_txversion: TxVersion,
consensus_branch_id: BranchId,
header_digest: Blake2bHash,
transparent_digest: Blake2bHash,
sapling_digest: Option<Blake2bHash>,
orchard_digest: Option<Blake2bHash>,
#[cfg(zcash_unstable = "zfuture")] tze_digests: Option<&TzeDigests<Blake2bHash>>,
) -> Blake2bHash {
let mut personal = [0; 16];
personal[..12].copy_from_slice(ZCASH_TX_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32_le(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
h.write_all(header_digest.as_bytes()).unwrap();
h.write_all(transparent_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(orchard::commitments::hash_bundle_txid_empty)
.as_bytes(),
)
.unwrap();
#[cfg(zcash_unstable = "zfuture")]
if _txversion.has_tze() {
h.write_all(hash_tze_txid_data(tze_digests).as_bytes())
.unwrap();
}
h.finalize()
}
pub fn to_txid(
txversion: TxVersion,
consensus_branch_id: BranchId,
digests: &TxDigests<Blake2bHash>,
) -> TxId {
let txid_digest = to_hash(
txversion,
consensus_branch_id,
digests.header_digest,
hash_transparent_txid_data(digests.transparent_digests.as_ref()),
digests.sapling_digest,
digests.orchard_digest,
#[cfg(zcash_unstable = "zfuture")]
digests.tze_digests.as_ref(),
);
TxId::from_bytes(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}
pub struct BlockTxCommitmentDigester;
impl TransactionDigest<Authorized> for BlockTxCommitmentDigester {
type HeaderDigest = BranchId;
type TransparentDigest = Blake2bHash;
type SaplingDigest = Blake2bHash;
type OrchardDigest = Blake2bHash;
#[cfg(zcash_unstable = "zfuture")]
type TzeDigest = Blake2bHash;
type Digest = Blake2bHash;
fn digest_header(
&self,
_version: TxVersion,
consensus_branch_id: BranchId,
_lock_time: u32,
_expiry_height: BlockHeight,
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
_zip233_amount: &Zatoshis,
) -> Self::HeaderDigest {
consensus_branch_id
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<transparent::Authorized>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION);
if let Some(bundle) = transparent_bundle {
for txin in &bundle.vin {
txin.script_sig().write(&mut h).unwrap();
}
}
h.finalize()
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized, ZatBalance>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = sapling_bundle {
for spend in bundle.shielded_spends() {
h.write_all(spend.zkproof()).unwrap();
}
for spend in bundle.shielded_spends() {
h.write_all(&<[u8; 64]>::from(*spend.spend_auth_sig()))
.unwrap();
}
for output in bundle.shielded_outputs() {
h.write_all(output.zkproof()).unwrap();
}
h.write_all(&<[u8; 64]>::from(bundle.authorization().binding_sig))
.unwrap();
}
h.finalize()
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<orchard::Authorized, ZatBalance>>,
) -> Self::OrchardDigest {
orchard_bundle.map_or_else(orchard::commitments::hash_bundle_auth_empty, |b| {
b.authorizing_commitment().0
})
}
#[cfg(zcash_unstable = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<tze::Authorized>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION);
if let Some(bundle) = tze_bundle {
for tzein in &bundle.vin {
h.write_all(&tzein.witness.payload.0).unwrap();
}
}
h.finalize()
}
fn combine(
&self,
consensus_branch_id: Self::HeaderDigest,
transparent_digest: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(zcash_unstable = "zfuture")] tze_digest: Self::TzeDigest,
) -> Self::Digest {
let digests = [transparent_digest, sapling_digest, orchard_digest];
let mut personal = [0; 16];
personal[..12].copy_from_slice(ZCASH_AUTH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32_le(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
for digest in &digests {
h.write_all(digest.as_bytes()).unwrap();
}
#[cfg(zcash_unstable = "zfuture")]
if TxVersion::suggested_for_branch(consensus_branch_id).has_tze() {
h.write_all(tze_digest.as_bytes()).unwrap();
}
h.finalize()
}
}