use blake2b_simd::{Hash as Blake2bHash, Params};
use core2::io::Write;
use ::transparent::{
bundle::{self as transparent, TxOut},
sighash::{
TransparentAuthorizingContext, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE,
SIGHASH_SINGLE,
},
};
use zcash_encoding::Array;
use crate::{
encoding::StateWrite,
transaction::{
sighash::SignableInput,
txid::{
hash_transparent_txid_data, to_hash, transparent_outputs_hash,
transparent_prevout_hash, transparent_sequence_hash,
ZCASH_TRANSPARENT_HASH_PERSONALIZATION,
},
Authorization, TransactionData, TransparentDigests, TxDigests,
},
};
#[cfg(zcash_unstable = "zfuture")]
use {
crate::{
encoding::WriteBytesExt,
transaction::{components::tze, TzeDigests},
},
zcash_encoding::{CompactSize, Vector},
};
const ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash___TxInHash";
const ZCASH_TRANSPARENT_AMOUNTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxTrAmountsHash";
const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxTrScriptsHash";
#[cfg(zcash_unstable = "zfuture")]
const ZCASH_TZE_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash__TzeInHash";
fn hasher(personal: &[u8; 16]) -> StateWrite {
StateWrite(Params::new().hash_length(32).personal(personal).to_state())
}
fn transparent_sig_digest<A: TransparentAuthorizingContext>(
tx_data: Option<(&transparent::Bundle<A>, &TransparentDigests<Blake2bHash>)>,
input: &SignableInput<'_>,
) -> Blake2bHash {
match tx_data {
None => hash_transparent_txid_data(None),
Some((bundle, txid_digests)) if bundle.is_coinbase() || bundle.vin.is_empty() => {
hash_transparent_txid_data(Some(txid_digests))
}
Some((bundle, txid_digests)) => {
let hash_type = input.hash_type();
let flag_anyonecanpay = hash_type & SIGHASH_ANYONECANPAY != 0;
let flag_single = hash_type & SIGHASH_MASK == SIGHASH_SINGLE;
let flag_none = hash_type & SIGHASH_MASK == SIGHASH_NONE;
let prevouts_digest = if flag_anyonecanpay {
transparent_prevout_hash::<A>(&[])
} else {
txid_digests.prevouts_digest
};
let amounts_digest = {
let mut h = hasher(ZCASH_TRANSPARENT_AMOUNTS_HASH_PERSONALIZATION);
if !flag_anyonecanpay {
Array::write(&mut h, bundle.authorization.input_amounts(), |w, amount| {
w.write_all(&amount.to_i64_le_bytes())
})
.unwrap();
}
h.finalize()
};
let scripts_digest = {
let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION);
if !flag_anyonecanpay {
Array::write(
&mut h,
bundle.authorization.input_scriptpubkeys(),
|w, script| script.write(w),
)
.unwrap();
}
h.finalize()
};
let sequence_digest = if flag_anyonecanpay {
transparent_sequence_hash::<A>(&[])
} else {
txid_digests.sequence_digest
};
let outputs_digest = if let SignableInput::Transparent(input) = input {
if flag_single {
if *input.index() < bundle.vout.len() {
transparent_outputs_hash(&[&bundle.vout[*input.index()]])
} else {
transparent_outputs_hash::<TxOut>(&[])
}
} else if flag_none {
transparent_outputs_hash::<TxOut>(&[])
} else {
txid_digests.outputs_digest
}
} else {
txid_digests.outputs_digest
};
let mut ch = hasher(ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION);
if let SignableInput::Transparent(input) = input {
let txin = &bundle.vin[*input.index()];
txin.prevout().write(&mut ch).unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
input.script_pubkey().write(&mut ch).unwrap();
ch.write_all(&txin.sequence().to_le_bytes()).unwrap();
}
let txin_sig_digest = ch.finalize();
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
h.write_all(&[hash_type]).unwrap();
h.write_all(prevouts_digest.as_bytes()).unwrap();
h.write_all(amounts_digest.as_bytes()).unwrap();
h.write_all(scripts_digest.as_bytes()).unwrap();
h.write_all(sequence_digest.as_bytes()).unwrap();
h.write_all(outputs_digest.as_bytes()).unwrap();
h.write_all(txin_sig_digest.as_bytes()).unwrap();
h.finalize()
}
}
}
#[cfg(zcash_unstable = "zfuture")]
fn tze_input_sigdigests<A: tze::Authorization>(
bundle: &tze::Bundle<A>,
input: &SignableInput<'_>,
txid_digests: &TzeDigests<Blake2bHash>,
) -> TzeDigests<Blake2bHash> {
let mut ch = hasher(ZCASH_TZE_INPUT_HASH_PERSONALIZATION);
if let SignableInput::Tze {
index,
precondition,
value,
} = input
{
let tzein = &bundle.vin[*index];
tzein.prevout.write(&mut ch).unwrap();
CompactSize::write(&mut ch, precondition.extension_id.try_into().unwrap()).unwrap();
CompactSize::write(&mut ch, precondition.mode.try_into().unwrap()).unwrap();
Vector::write(&mut ch, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
ch.write_all(&value.to_i64_le_bytes()).unwrap();
}
let per_input_digest = ch.finalize();
TzeDigests {
inputs_digest: txid_digests.inputs_digest,
outputs_digest: txid_digests.outputs_digest,
per_input_digest: Some(per_input_digest),
}
}
pub fn v5_signature_hash<
TA: TransparentAuthorizingContext,
A: Authorization<TransparentAuth = TA>,
>(
tx: &TransactionData<A>,
signable_input: &SignableInput<'_>,
txid_parts: &TxDigests<Blake2bHash>,
) -> Blake2bHash {
assert_eq!(
tx.transparent_bundle.is_some(),
txid_parts.transparent_digests.is_some()
);
to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
transparent_sig_digest(
tx.transparent_bundle
.as_ref()
.zip(txid_parts.transparent_digests.as_ref()),
signable_input,
),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(zcash_unstable = "zfuture")]
tx.tze_bundle
.as_ref()
.zip(txid_parts.tze_digests.as_ref())
.map(|(bundle, tze_digests)| tze_input_sigdigests(bundle, signable_input, tze_digests))
.as_ref(),
)
}