use std::{io, ops::Deref, sync::Arc};
use zcash_primitives::transaction::{self as zp_tx, TxDigests};
use zcash_protocol::value::{BalanceError, ZatBalance, Zatoshis};
use zcash_script::script;
use crate::{
amount::{Amount, NonNegative},
parameters::NetworkUpgrade,
serialization::ZcashSerialize,
transaction::{AuthDigest, HashType, SigHash, Transaction},
transparent::{self, Script},
Error,
};
#[derive(Clone, Debug)]
struct TransparentAuth {
all_prev_outputs: Arc<Vec<transparent::Output>>,
}
impl zcash_transparent::bundle::Authorization for TransparentAuth {
type ScriptSig = zcash_transparent::address::Script;
}
impl zcash_transparent::sighash::TransparentAuthorizingContext for TransparentAuth {
fn input_amounts(&self) -> Vec<Zatoshis> {
self.all_prev_outputs
.iter()
.map(|prevout| {
prevout
.value
.try_into()
.expect("will not fail since it was previously validated")
})
.collect()
}
fn input_scriptpubkeys(&self) -> Vec<zcash_transparent::address::Script> {
self.all_prev_outputs
.iter()
.map(|prevout| {
zcash_transparent::address::Script(script::Code(
prevout.lock_script.as_raw_bytes().into(),
))
})
.collect()
}
}
struct MapTransparent {
auth: TransparentAuth,
}
impl zcash_transparent::bundle::MapAuth<zcash_transparent::bundle::Authorized, TransparentAuth>
for MapTransparent
{
fn map_script_sig(
&self,
s: <zcash_transparent::bundle::Authorized as zcash_transparent::bundle::Authorization>::ScriptSig,
) -> <TransparentAuth as zcash_transparent::bundle::Authorization>::ScriptSig {
s
}
fn map_authorization(&self, _: zcash_transparent::bundle::Authorized) -> TransparentAuth {
self.auth.clone()
}
}
struct IdentityMap;
impl
zp_tx::components::sapling::MapAuth<
sapling_crypto::bundle::Authorized,
sapling_crypto::bundle::Authorized,
> for IdentityMap
{
fn map_spend_proof(
&mut self,
p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof,
) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof
{
p
}
fn map_output_proof(
&mut self,
p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof,
) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof
{
p
}
fn map_auth_sig(
&mut self,
s: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig,
) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig
{
s
}
fn map_authorization(
&mut self,
a: sapling_crypto::bundle::Authorized,
) -> sapling_crypto::bundle::Authorized {
a
}
}
impl zp_tx::components::orchard::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
for IdentityMap
{
fn map_spend_auth(
&self,
s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth {
s
}
fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized {
a
}
}
#[derive(Debug)]
struct PrecomputedAuth {}
impl zp_tx::Authorization for PrecomputedAuth {
type TransparentAuth = TransparentAuth;
type SaplingAuth = sapling_crypto::bundle::Authorized;
type OrchardAuth = orchard::bundle::Authorized;
#[cfg(zcash_unstable = "zfuture")]
type TzeAuth = zp_tx::components::tze::Authorized;
}
impl TryFrom<&transparent::Output> for zcash_transparent::bundle::TxOut {
type Error = io::Error;
#[allow(clippy::unwrap_in_result)]
fn try_from(output: &transparent::Output) -> Result<Self, Self::Error> {
let serialized_output_bytes = output
.zcash_serialize_to_vec()
.expect("zcash_primitives and Zebra transparent output formats must be compatible");
zcash_transparent::bundle::TxOut::read(&mut serialized_output_bytes.as_slice())
}
}
impl TryFrom<transparent::Output> for zcash_transparent::bundle::TxOut {
type Error = io::Error;
#[allow(clippy::needless_borrow)]
fn try_from(output: transparent::Output) -> Result<Self, Self::Error> {
(&output).try_into()
}
}
impl TryFrom<Amount<NonNegative>> for zcash_protocol::value::Zatoshis {
type Error = BalanceError;
fn try_from(amount: Amount<NonNegative>) -> Result<Self, Self::Error> {
zcash_protocol::value::Zatoshis::from_nonnegative_i64(amount.into())
}
}
impl TryFrom<Amount> for ZatBalance {
type Error = BalanceError;
fn try_from(amount: Amount) -> Result<Self, Self::Error> {
ZatBalance::from_i64(amount.into())
}
}
impl From<&Script> for zcash_transparent::address::Script {
fn from(script: &Script) -> Self {
zcash_transparent::address::Script(script::Code(script.as_raw_bytes().to_vec()))
}
}
impl From<Script> for zcash_transparent::address::Script {
#[allow(clippy::needless_borrow)]
fn from(script: Script) -> Self {
(&script).into()
}
}
#[derive(Debug)]
pub(crate) struct PrecomputedTxData {
tx_data: zp_tx::TransactionData<PrecomputedAuth>,
txid_parts: TxDigests<blake2b_simd::Hash>,
all_previous_outputs: Arc<Vec<transparent::Output>>,
}
impl PrecomputedTxData {
pub(crate) fn new(
tx: &Transaction,
nu: NetworkUpgrade,
all_previous_outputs: Arc<Vec<transparent::Output>>,
) -> Result<PrecomputedTxData, Error> {
let tx = tx.to_librustzcash(nu)?;
let txid_parts = tx.deref().digest(zp_tx::txid::TxIdDigester);
let f_transparent = MapTransparent {
auth: TransparentAuth {
all_prev_outputs: all_previous_outputs.clone(),
},
};
let tx_data: zp_tx::TransactionData<PrecomputedAuth> = tx.into_data().map_authorization(
f_transparent,
IdentityMap,
IdentityMap,
#[cfg(zcash_unstable = "zfuture")]
(),
);
Ok(PrecomputedTxData {
tx_data,
txid_parts,
all_previous_outputs,
})
}
pub fn orchard_bundle(
&self,
) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
self.tx_data.orchard_bundle().cloned()
}
pub fn sapling_bundle(
&self,
) -> Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>> {
self.tx_data.sapling_bundle().cloned()
}
}
#[derive(Debug)]
enum SighashError {
InputIndexOutOfBounds {
input_index: usize,
input_count: usize,
},
NoTransparentBundle,
BundleInputCountMismatch {
input_index: usize,
bundle_vin_len: usize,
all_prev_outputs_len: usize,
},
InvalidPreviousOutputAmount,
}
impl std::fmt::Display for SighashError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InputIndexOutOfBounds {
input_index,
input_count,
} => write!(
f,
"input_index {input_index} is out of bounds (transaction has \
{input_count} transparent inputs)"
),
Self::NoTransparentBundle => f.write_str(
"transparent sighash requested for a transaction with no \
transparent bundle (vin and vout both empty)",
),
Self::BundleInputCountMismatch {
input_index,
bundle_vin_len,
all_prev_outputs_len,
} => write!(
f,
"input_index {input_index} valid for all_previous_outputs (len \
{all_prev_outputs_len}) but out of bounds for the parsed \
transparent bundle (vin len {bundle_vin_len}); this indicates \
a serialize/deserialize round-trip inconsistency"
),
Self::InvalidPreviousOutputAmount => f.write_str(
"previous output amount could not be converted to Zatoshis; \
the amount should have been validated before sighash \
computation",
),
}
}
}
impl std::error::Error for SighashError {}
pub(crate) fn sighash(
precomputed_tx_data: &PrecomputedTxData,
hash_type: HashType,
input_index_script_code: Option<(usize, Vec<u8>)>,
) -> SigHash {
sighash_inner(
precomputed_tx_data,
hash_type.try_into().expect("hash type should be canonical"),
input_index_script_code,
)
.expect(
"sighash precondition violated: callers must pass an in-bounds \
input_index when computing a transparent sighash, and the transaction \
must contain the transparent input being signed",
)
}
pub(crate) fn sighash_v4_raw(
precomputed_tx_data: &PrecomputedTxData,
raw_hash_type: u8,
input_index_script_code: Option<(usize, Vec<u8>)>,
) -> SigHash {
sighash_inner(
precomputed_tx_data,
zcash_transparent::sighash::SighashType::from_raw(raw_hash_type),
input_index_script_code,
)
.expect(
"sighash precondition violated: callers must pass an in-bounds \
input_index when computing a transparent sighash, and the transaction \
must contain the transparent input being signed",
)
}
fn sighash_inner(
precomputed_tx_data: &PrecomputedTxData,
sighash_type: zcash_transparent::sighash::SighashType,
input_index_script_code: Option<(usize, Vec<u8>)>,
) -> Result<SigHash, SighashError> {
let lock_script: zcash_transparent::address::Script;
let unlock_script: zcash_transparent::address::Script;
let signable_input = match input_index_script_code {
Some((input_index, script_code)) => {
let all_prev_outputs_len = precomputed_tx_data.all_previous_outputs.len();
let output = precomputed_tx_data
.all_previous_outputs
.get(input_index)
.ok_or(SighashError::InputIndexOutOfBounds {
input_index,
input_count: all_prev_outputs_len,
})?;
let bundle = precomputed_tx_data
.tx_data
.transparent_bundle()
.ok_or(SighashError::NoTransparentBundle)?;
lock_script = output.lock_script.clone().into();
unlock_script = zcash_transparent::address::Script(script::Code(script_code));
let value = output
.value
.try_into()
.map_err(|_| SighashError::InvalidPreviousOutputAmount)?;
let from_parts = zcash_transparent::sighash::SignableInput::from_parts(
bundle,
sighash_type,
input_index,
&unlock_script,
&lock_script,
value,
)
.map_err(|_| SighashError::BundleInputCountMismatch {
input_index,
bundle_vin_len: bundle.vin.len(),
all_prev_outputs_len,
})?;
zp_tx::sighash::SignableInput::Transparent(from_parts)
}
None => zp_tx::sighash::SignableInput::Shielded,
};
Ok(SigHash(
*zp_tx::sighash::signature_hash(
&precomputed_tx_data.tx_data,
&signable_input,
&precomputed_tx_data.txid_parts,
)
.as_ref(),
))
}
pub(crate) fn auth_digest(tx: &Transaction) -> AuthDigest {
let nu = tx.network_upgrade().expect("V5 tx has a network upgrade");
AuthDigest(
tx.to_librustzcash(nu)
.expect("V5 tx is convertible to its `zcash_params` equivalent")
.auth_commitment()
.as_ref()
.try_into()
.expect("digest has the correct size"),
)
}