use std::io;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use zcash_transparent::sighash::{
SIGHASH_ALL, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE,
};
use super::SigHash;
use crate::{
parameters::{NetworkUpgrade, SAPLING_VERSION_GROUP_ID},
primitives::ZkSnarkProof,
sapling,
serialization::ZcashSerialize,
transaction::{JoinSplitData, Transaction},
transparent,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
const OVERWINTERED_FLAG: u32 = 1 << 31;
const V4_VERSION: u32 = 4;
const ZERO_DIGEST: [u8; 32] = [0; 32];
fn hasher(personalization: &[u8; 16]) -> State {
Params::new()
.hash_length(32)
.personal(personalization)
.to_state()
}
struct HashWriter<'a>(&'a mut State);
impl io::Write for HashWriter<'_> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.0.update(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn update_serialized<T: ZcashSerialize>(state: &mut State, value: &T) {
value
.zcash_serialize(HashWriter(state))
.expect("writing to a BLAKE2b state is infallible");
}
fn update_outpoint(state: &mut State, input: &transparent::Input) {
match input {
transparent::Input::PrevOut { outpoint, .. } => update_serialized(state, outpoint),
transparent::Input::Coinbase { .. } => {
state.update(&[0; 32]);
state.update(&u32::MAX.to_le_bytes());
}
}
}
fn prevouts_hash(inputs: &[transparent::Input]) -> Blake2bHash {
let mut state = hasher(ZCASH_PREVOUTS_HASH_PERSONALIZATION);
for input in inputs {
update_outpoint(&mut state, input);
}
state.finalize()
}
fn sequence_hash(inputs: &[transparent::Input]) -> Blake2bHash {
let mut state = hasher(ZCASH_SEQUENCE_HASH_PERSONALIZATION);
for input in inputs {
state.update(&input.sequence().to_le_bytes());
}
state.finalize()
}
fn output_hash(output: &transparent::Output) -> Blake2bHash {
let mut state = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION);
update_serialized(&mut state, output);
state.finalize()
}
fn outputs_hash(outputs: &[transparent::Output]) -> Blake2bHash {
let mut state = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION);
for output in outputs {
update_serialized(&mut state, output);
}
state.finalize()
}
fn joinsplits_hash<P: ZkSnarkProof>(
joinsplit_data: Option<&JoinSplitData<P>>,
) -> Option<Blake2bHash> {
let joinsplit_data = joinsplit_data?;
let mut state = hasher(ZCASH_JOINSPLITS_HASH_PERSONALIZATION);
for joinsplit in joinsplit_data.joinsplits() {
update_serialized(&mut state, joinsplit);
}
state.update(joinsplit_data.pub_key.as_ref());
Some(state.finalize())
}
fn sapling_spends_hash(
shielded_data: &sapling::ShieldedData<sapling::PerSpendAnchor>,
) -> Option<Blake2bHash> {
shielded_data.spends().next()?;
let mut state = hasher(ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION);
for spend in shielded_data.spends() {
state.update(&spend.cv.to_bytes());
update_serialized(&mut state, &spend.per_spend_anchor);
state.update(&<[u8; 32]>::from(spend.nullifier));
state.update(&<[u8; 32]>::from(spend.rk.clone()));
update_serialized(&mut state, &spend.zkproof);
}
Some(state.finalize())
}
fn sapling_outputs_hash(
shielded_data: &sapling::ShieldedData<sapling::PerSpendAnchor>,
) -> Option<Blake2bHash> {
shielded_data.outputs().next()?;
let mut state = hasher(ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION);
for output in shielded_data.outputs() {
update_serialized(&mut state, &sapling::OutputInTransactionV4(output.clone()));
}
Some(state.finalize())
}
#[derive(Clone, Copy, Debug)]
struct InputTail {
outpoint: Option<transparent::OutPoint>,
sequence: u32,
}
#[derive(Clone, Debug)]
pub(super) struct V4Sighash {
consensus_branch_id: u32,
lock_time: u32,
expiry_height: u32,
inputs: Vec<InputTail>,
previous_output_values: Vec<i64>,
prevouts: Blake2bHash,
sequence: Blake2bHash,
outputs: Blake2bHash,
single_outputs: Vec<Blake2bHash>,
joinsplits: Option<Blake2bHash>,
value_balance: i64,
sapling_spends: Option<Blake2bHash>,
sapling_outputs: Option<Blake2bHash>,
}
impl V4Sighash {
pub(super) fn new(
transaction: &Transaction,
network_upgrade: NetworkUpgrade,
all_previous_outputs: &[transparent::Output],
) -> Option<Self> {
let consensus_branch_id = u32::from(network_upgrade.branch_id()?);
match transaction {
Transaction::V4 {
inputs,
outputs,
expiry_height,
joinsplit_data,
sapling_shielded_data,
..
} => {
let value_balance = sapling_shielded_data
.as_ref()
.map_or(0, |data| data.value_balance.zatoshis());
let sapling_spends = sapling_shielded_data.as_ref().and_then(sapling_spends_hash);
let sapling_outputs = sapling_shielded_data
.as_ref()
.and_then(sapling_outputs_hash);
Some(Self {
consensus_branch_id,
lock_time: transaction.raw_lock_time(),
expiry_height: expiry_height.0,
inputs: inputs
.iter()
.map(|input| InputTail {
outpoint: input.outpoint(),
sequence: input.sequence(),
})
.collect(),
previous_output_values: all_previous_outputs
.iter()
.map(|output| output.value.zatoshis())
.collect(),
prevouts: prevouts_hash(inputs),
sequence: sequence_hash(inputs),
outputs: outputs_hash(outputs),
single_outputs: outputs.iter().take(inputs.len()).map(output_hash).collect(),
joinsplits: joinsplits_hash(joinsplit_data.as_ref()),
value_balance,
sapling_spends,
sapling_outputs,
})
}
_ => None,
}
}
pub(super) fn signature_hash(
&self,
raw_hash_type: u8,
input_index_script_code: Option<(usize, &[u8])>,
) -> Option<SigHash> {
let transparent_input = match input_index_script_code {
Some((index, script_code)) => Some((
self.inputs.get(index)?,
*self.previous_output_values.get(index)?,
index,
script_code,
)),
None => None,
};
let raw_hash_type = if transparent_input.is_some() {
raw_hash_type
} else {
SIGHASH_ALL
};
let output_mode = raw_hash_type & SIGHASH_MASK;
let mut personalization = [0; 16];
personalization[..12].copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
personalization[12..].copy_from_slice(&self.consensus_branch_id.to_le_bytes());
let mut state = hasher(&personalization);
state.update(&(OVERWINTERED_FLAG | V4_VERSION).to_le_bytes());
state.update(&SAPLING_VERSION_GROUP_ID.to_le_bytes());
if raw_hash_type & SIGHASH_ANYONECANPAY == 0 {
state.update(self.prevouts.as_bytes());
} else {
state.update(&ZERO_DIGEST);
}
if raw_hash_type & SIGHASH_ANYONECANPAY == 0
&& output_mode != SIGHASH_SINGLE
&& output_mode != SIGHASH_NONE
{
state.update(self.sequence.as_bytes());
} else {
state.update(&ZERO_DIGEST);
}
if output_mode != SIGHASH_SINGLE && output_mode != SIGHASH_NONE {
state.update(self.outputs.as_bytes());
} else if output_mode == SIGHASH_SINGLE {
match transparent_input
.as_ref()
.and_then(|(_, _, index, _)| self.single_outputs.get(*index))
{
Some(output) => state.update(output.as_bytes()),
None => state.update(&ZERO_DIGEST),
};
} else {
state.update(&ZERO_DIGEST);
}
state.update(
self.joinsplits
.as_ref()
.map_or(ZERO_DIGEST.as_slice(), Blake2bHash::as_bytes),
);
state.update(
self.sapling_spends
.as_ref()
.map_or(ZERO_DIGEST.as_slice(), Blake2bHash::as_bytes),
);
state.update(
self.sapling_outputs
.as_ref()
.map_or(ZERO_DIGEST.as_slice(), Blake2bHash::as_bytes),
);
state.update(&self.lock_time.to_le_bytes());
state.update(&self.expiry_height.to_le_bytes());
state.update(&self.value_balance.to_le_bytes());
state.update(&u32::from(raw_hash_type).to_le_bytes());
if let Some((input, value, _, script_code)) = transparent_input {
match input.outpoint {
Some(outpoint) => update_serialized(&mut state, &outpoint),
None => {
state.update(&[0; 32]);
state.update(&u32::MAX.to_le_bytes());
}
}
update_serialized(&mut state, &transparent::Script::new(script_code));
state.update(&value.to_le_bytes());
state.update(&input.sequence.to_le_bytes());
}
let digest = state.finalize();
Some(SigHash(
digest
.as_bytes()
.try_into()
.expect("BLAKE2b output length is configured to 32 bytes"),
))
}
}