use rand_core::OsRng;
use zcash_primitives::transaction::{sighash::SignableInput, txid::TxIdDigester};
use crate::{
ExtractError, ParsedPczt, Pczt,
common::{
FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE,
FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE,
},
sighash,
};
pub struct IoFinalizer {
pczt: Pczt,
}
impl IoFinalizer {
pub fn new(pczt: Pczt) -> Self {
Self { pczt }
}
pub fn finalize_io(self) -> Result<Pczt, Error> {
let Self { pczt } = self;
let has_orchard_actions = !pczt.orchard.actions.is_empty();
let has_ironwood_actions = !pczt.ironwood.actions.is_empty();
let has_sapling_spends = !pczt.sapling.spends.is_empty();
let has_shielded_spends = has_sapling_spends || has_orchard_actions || has_ironwood_actions;
let has_shielded_outputs =
!(pczt.sapling.outputs.is_empty() && !has_orchard_actions && !has_ironwood_actions);
if pczt.transparent.inputs.is_empty() && !has_shielded_spends {
return Err(Error::NoSpends);
}
if pczt.transparent.outputs.is_empty() && !has_shielded_outputs {
return Err(Error::NoOutputs);
}
let anchor_requirement =
crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
let ParsedPczt {
mut global,
transparent,
mut sapling,
mut orchard,
mut ironwood,
tx_data,
} = pczt.extract_tx_data(
anchor_requirement,
|t| {
t.extract_effects()
.map_err(ExtractError::TransparentExtract)
},
|s| s.extract_effects().map_err(ExtractError::SaplingExtract),
|o| o.extract_effects().map_err(ExtractError::OrchardExtract),
|i| i.extract_effects().map_err(ExtractError::IronwoodExtract),
)?;
if has_shielded_spends || has_shielded_outputs {
global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
| FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
| FLAG_SHIELDED_MODIFIABLE);
}
let txid_parts = tx_data.digest(TxIdDigester);
let shielded_sighash = sighash(&tx_data, &SignableInput::Shielded, &txid_parts);
sapling
.bundle
.finalize_io(shielded_sighash, OsRng)
.map_err(Error::SaplingFinalize)?;
if has_orchard_actions {
orchard
.bundle
.finalize_io(shielded_sighash, OsRng)
.map_err(Error::OrchardFinalize)?;
}
if has_ironwood_actions {
ironwood
.bundle
.finalize_io(shielded_sighash, OsRng)
.map_err(Error::IronwoodFinalize)?;
}
Ok(Pczt {
global,
transparent: crate::transparent::Bundle::serialize_from(transparent),
sapling: sapling.reserialize(),
orchard: orchard.reserialize(),
ironwood: ironwood.reserialize(),
})
}
}
#[derive(Debug)]
pub enum Error {
Extract(crate::ExtractError),
NoOutputs,
NoSpends,
IronwoodFinalize(orchard::pczt::IoFinalizerError),
OrchardFinalize(orchard::pczt::IoFinalizerError),
SaplingFinalize(sapling::pczt::IoFinalizerError),
}
impl From<crate::ExtractError> for Error {
fn from(e: crate::ExtractError) -> Self {
Error::Extract(e)
}
}