use core::marker::PhantomData;
use rand_core::OsRng;
use zcash_primitives::transaction::{
Authorization, Transaction,
sighash::{SignableInput, signature_hash},
txid::TxIdDigester,
};
use crate::Pczt;
mod orchard;
pub use self::orchard::OrchardError;
pub type IronwoodError = OrchardError;
mod sapling;
pub use self::sapling::SaplingError;
mod transparent;
pub use self::transparent::TransparentError;
pub struct TransactionExtractor<'a> {
pczt: Pczt,
sapling_vk: Option<(
&'a ::sapling::circuit::SpendVerifyingKey,
&'a ::sapling::circuit::OutputVerifyingKey,
)>,
orchard_vk: Option<&'a ::orchard::circuit::VerifyingKey>,
_unused: PhantomData<&'a ()>,
}
impl<'a> TransactionExtractor<'a> {
pub fn new(pczt: Pczt) -> Self {
Self {
pczt,
sapling_vk: None,
orchard_vk: None,
_unused: PhantomData,
}
}
pub fn with_sapling(
mut self,
spend_vk: &'a ::sapling::circuit::SpendVerifyingKey,
output_vk: &'a ::sapling::circuit::OutputVerifyingKey,
) -> Self {
self.sapling_vk = Some((spend_vk, output_vk));
self
}
pub fn with_orchard(mut self, orchard_vk: &'a ::orchard::circuit::VerifyingKey) -> Self {
self.orchard_vk = Some(orchard_vk);
self
}
pub fn extract(self) -> Result<Transaction, Error> {
let Self {
pczt,
sapling_vk,
orchard_vk,
_unused,
} = self;
let crate::ParsedPczt { tx_data, .. } = pczt.extract_tx_data::<Unbound, Error>(
crate::common::AnchorRequirement::Required,
|t| {
t.extract()
.map_err(|e| Error::Transparent(TransparentError::Extract(e)))
},
|s| {
s.extract()
.map_err(|e| Error::Sapling(SaplingError::Extract(e)))
},
|o| {
o.extract()
.map_err(|e| Error::Orchard(OrchardError::Extract(e)))
},
|i| {
i.extract()
.map_err(|e| Error::Ironwood(IronwoodError::Extract(e)))
},
)?;
let txid_parts = tx_data.digest(TxIdDigester);
let shielded_sighash = signature_hash(&tx_data, &SignableInput::Shielded, &txid_parts);
let tx_data = tx_data.try_map_bundles(
|t| Ok(t.map(|t| t.map_authorization(transparent::RemoveInputInfo))),
|s| {
s.map(|s| {
s.apply_binding_signature(*shielded_sighash.as_ref(), OsRng)
.ok_or(Error::SighashMismatch)
})
.transpose()
},
|o| {
o.map(|o| {
o.apply_binding_signature(*shielded_sighash.as_ref(), OsRng)
.ok_or(Error::SighashMismatch)
})
.transpose()
},
)?;
let tx = tx_data.freeze().expect("txid construction can't fail here");
if let Some(bundle) = tx.sapling_bundle() {
let (spend_vk, output_vk) = sapling_vk.ok_or(Error::SaplingRequired)?;
sapling::verify_bundle(bundle, spend_vk, output_vk, *shielded_sighash.as_ref())
.map_err(Error::Sapling)?;
}
if let Some(bundle) = tx.orchard_bundle() {
orchard::verify_bundle(bundle, orchard_vk, *shielded_sighash.as_ref())
.map_err(Error::Orchard)?;
}
if let Some(bundle) = tx.ironwood_bundle() {
orchard::verify_bundle(bundle, orchard_vk, *shielded_sighash.as_ref())
.map_err(Error::Ironwood)?;
}
Ok(tx)
}
}
struct Unbound;
impl Authorization for Unbound {
type TransparentAuth = ::transparent::pczt::Unbound;
type SaplingAuth = ::sapling::pczt::Unbound;
type OrchardAuth = ::orchard::pczt::Unbound;
}
#[derive(Debug)]
pub enum Error {
Extract(crate::ExtractError),
Ironwood(IronwoodError),
Orchard(OrchardError),
Sapling(SaplingError),
SaplingRequired,
SighashMismatch,
Transparent(TransparentError),
}
impl From<crate::ExtractError> for Error {
fn from(e: crate::ExtractError) -> Self {
Error::Extract(e)
}
}