use alloc::string::String;
use alloc::vec::Vec;
use crate::{Pczt, common::Global};
#[cfg(feature = "orchard")]
mod orchard;
#[cfg(feature = "orchard")]
pub use orchard::OrchardError;
#[cfg(feature = "sapling")]
mod sapling;
#[cfg(feature = "sapling")]
pub use sapling::SaplingError;
#[cfg(feature = "transparent")]
mod transparent;
#[cfg(feature = "transparent")]
pub use transparent::TransparentError;
pub struct Updater {
pczt: Pczt,
}
impl Updater {
pub fn new(pczt: Pczt) -> Self {
Self { pczt }
}
pub fn update_global_with<F>(self, f: F) -> Self
where
F: FnOnce(GlobalUpdater<'_>),
{
let Pczt {
mut global,
transparent,
sapling,
orchard,
ironwood,
} = self.pczt;
f(GlobalUpdater(&mut global));
Self {
pczt: Pczt {
global,
transparent,
sapling,
orchard,
ironwood,
},
}
}
#[cfg(feature = "sapling")]
pub fn set_sapling_anchor(
mut self,
anchor: ::sapling::Anchor,
) -> Result<Self, AnchorUpdateError> {
ensure_anchor_update_supported(&self.pczt.global)?;
ensure_no_sapling_spend_proof(&self.pczt.sapling)?;
set_anchor(&mut self.pczt.sapling.anchor, anchor.to_bytes())?;
Ok(self)
}
#[cfg(feature = "orchard")]
pub fn set_orchard_anchor(
mut self,
anchor: ::orchard::Anchor,
) -> Result<Self, AnchorUpdateError> {
ensure_anchor_update_supported(&self.pczt.global)?;
ensure_no_orchard_proof_for_anchor(&self.pczt.orchard)?;
set_anchor(&mut self.pczt.orchard.anchor, anchor.to_bytes())?;
Ok(self)
}
#[cfg(feature = "orchard")]
pub fn set_ironwood_anchor(
mut self,
anchor: ::orchard::Anchor,
) -> Result<Self, AnchorUpdateError> {
ensure_anchor_update_supported(&self.pczt.global)?;
ensure_no_orchard_proof_for_anchor(&self.pczt.ironwood)?;
set_anchor(&mut self.pczt.ironwood.anchor, anchor.to_bytes())?;
Ok(self)
}
#[cfg(feature = "sapling")]
pub fn set_sapling_spend_witnesses(
mut self,
witnesses: impl IntoIterator<Item = (usize, ::sapling::MerklePath)>,
) -> Result<Self, SpendWitnessUpdateError> {
set_sapling_spend_witnesses(&mut self.pczt.sapling.spends, witnesses)?;
Ok(self)
}
#[cfg(feature = "orchard")]
pub fn set_orchard_spend_witnesses(
mut self,
witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
) -> Result<Self, SpendWitnessUpdateError> {
if self.pczt.orchard.note_version != crate::orchard::NoteVersion::V2 {
return Err(SpendWitnessUpdateError::UnexpectedNoteVersion);
}
ensure_no_orchard_proof_for_witness(&self.pczt.orchard)?;
set_orchard_spend_witnesses(&mut self.pczt.orchard.actions, witnesses)?;
Ok(self)
}
#[cfg(feature = "orchard")]
pub fn set_ironwood_spend_witnesses(
mut self,
witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
) -> Result<Self, SpendWitnessUpdateError> {
if self.pczt.ironwood.note_version != crate::orchard::NoteVersion::V3 {
return Err(SpendWitnessUpdateError::UnexpectedNoteVersion);
}
ensure_no_orchard_proof_for_witness(&self.pczt.ironwood)?;
set_orchard_spend_witnesses(&mut self.pczt.ironwood.actions, witnesses)?;
Ok(self)
}
pub fn finish(self) -> Pczt {
self.pczt
}
}
#[cfg(feature = "sapling")]
fn ensure_no_sapling_spend_proof(bundle: &crate::sapling::Bundle) -> Result<(), AnchorUpdateError> {
if bundle.spends.iter().any(|spend| spend.zkproof.is_some()) {
Err(AnchorUpdateError::ProofAlreadyPresent)
} else {
Ok(())
}
}
#[cfg(feature = "orchard")]
fn ensure_no_orchard_proof_for_anchor(
bundle: &crate::orchard::Bundle,
) -> Result<(), AnchorUpdateError> {
if bundle.zkproof.is_some() {
Err(AnchorUpdateError::ProofAlreadyPresent)
} else {
Ok(())
}
}
#[cfg(feature = "orchard")]
fn ensure_no_orchard_proof_for_witness(
bundle: &crate::orchard::Bundle,
) -> Result<(), SpendWitnessUpdateError> {
if bundle.zkproof.is_some() {
Err(SpendWitnessUpdateError::ProofAlreadyPresent)
} else {
Ok(())
}
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
fn ensure_anchor_update_supported(global: &Global) -> Result<(), AnchorUpdateError> {
use zcash_protocol::{
consensus::BranchId,
constants::{V6_TX_VERSION, V6_VERSION_GROUP_ID},
};
if global.tx_version < V6_TX_VERSION
|| (global.tx_version == V6_TX_VERSION && global.version_group_id != V6_VERSION_GROUP_ID)
{
return Err(AnchorUpdateError::UnsupportedTransactionFormat);
}
match BranchId::try_from(global.consensus_branch_id) {
Ok(BranchId::Nu6_3) => Ok(()),
#[cfg(zcash_unstable = "nu7")]
Ok(BranchId::Nu7) => Ok(()),
Ok(_) => Err(AnchorUpdateError::UnsupportedConsensusBranchId),
Err(_) => Err(AnchorUpdateError::UnknownConsensusBranchId),
}
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
fn set_anchor(slot: &mut Option<[u8; 32]>, anchor: [u8; 32]) -> Result<(), AnchorUpdateError> {
match slot {
Some(existing) if *existing != anchor => Err(AnchorUpdateError::ConflictingAnchor),
_ => {
*slot = Some(anchor);
Ok(())
}
}
}
#[cfg(feature = "sapling")]
fn set_sapling_spend_witnesses(
spends: &mut [crate::sapling::Spend],
witnesses: impl IntoIterator<Item = (usize, ::sapling::MerklePath)>,
) -> Result<(), SpendWitnessUpdateError> {
for (spend_index, merkle_path) in witnesses {
let spend = spends
.get_mut(spend_index)
.ok_or(SpendWitnessUpdateError::InvalidSpendIndex(spend_index))?;
if spend.zkproof.is_some() {
return Err(SpendWitnessUpdateError::ProofAlreadyPresent);
}
let position = u32::try_from(u64::from(merkle_path.position()))
.map_err(|_| SpendWitnessUpdateError::PositionOutOfRange)?;
let mut auth_path = [[0; 32]; 32];
for (target, node) in auth_path.iter_mut().zip(merkle_path.path_elems()) {
*target = node.to_bytes();
}
spend.witness = Some((position, auth_path));
}
Ok(())
}
#[cfg(feature = "orchard")]
fn set_orchard_spend_witnesses(
actions: &mut [crate::orchard::Action],
witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
) -> Result<(), SpendWitnessUpdateError> {
for (action_index, merkle_path) in witnesses {
let action = actions
.get_mut(action_index)
.ok_or(SpendWitnessUpdateError::InvalidSpendIndex(action_index))?;
action.spend.witness = Some((
merkle_path.position(),
merkle_path.auth_path().map(|node| node.to_bytes()),
));
}
Ok(())
}
pub struct GlobalUpdater<'a>(&'a mut Global);
impl GlobalUpdater<'_> {
pub fn set_proprietary(&mut self, key: String, value: Vec<u8>) {
self.0.proprietary.insert(key, value);
}
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AnchorUpdateError {
UnsupportedTransactionFormat,
UnknownConsensusBranchId,
UnsupportedConsensusBranchId,
ProofAlreadyPresent,
ConflictingAnchor,
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
impl core::fmt::Display for AnchorUpdateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AnchorUpdateError::UnsupportedTransactionFormat => {
write!(
f,
"PCZT transaction format does not support shielded anchor updates"
)
}
AnchorUpdateError::UnknownConsensusBranchId => {
write!(f, "unknown consensus branch ID")
}
AnchorUpdateError::UnsupportedConsensusBranchId => {
write!(
f,
"consensus branch ID does not support shielded anchor updates"
)
}
AnchorUpdateError::ProofAlreadyPresent => {
write!(
f,
"shielded proof that depends on the anchor is already present"
)
}
AnchorUpdateError::ConflictingAnchor => {
write!(f, "bundle already contains a different anchor")
}
}
}
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SpendWitnessUpdateError {
InvalidSpendIndex(usize),
ProofAlreadyPresent,
PositionOutOfRange,
UnexpectedNoteVersion,
}
#[cfg(any(feature = "sapling", feature = "orchard"))]
impl core::fmt::Display for SpendWitnessUpdateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SpendWitnessUpdateError::InvalidSpendIndex(index) => {
write!(f, "spend index {index} does not exist")
}
SpendWitnessUpdateError::ProofAlreadyPresent => {
write!(f, "proof for target spend or bundle is already present")
}
SpendWitnessUpdateError::PositionOutOfRange => {
write!(
f,
"witness position cannot be represented in the PCZT wire format"
)
}
SpendWitnessUpdateError::UnexpectedNoteVersion => {
write!(
f,
"bundle note-plaintext version does not match the pool being updated"
)
}
}
}
}