1use alloc::{string::String, vec::Vec};
7
8use crate::{Pczt, common::Global};
9
10#[cfg(all(
11 any(feature = "sapling", feature = "orchard"),
12 zcash_unstable = "nutachyon"
13))]
14use zcash_protocol::constants::{V7_TX_VERSION, V7_VERSION_GROUP_ID};
15#[cfg(any(feature = "sapling", feature = "orchard"))]
16use zcash_protocol::{
17 consensus::BranchId,
18 constants::{V6_TX_VERSION, V6_VERSION_GROUP_ID},
19};
20
21#[cfg(feature = "orchard")]
22mod orchard;
23#[cfg(feature = "orchard")]
24pub use orchard::OrchardError;
25
26#[cfg(feature = "sapling")]
27mod sapling;
28#[cfg(feature = "sapling")]
29pub use sapling::SaplingError;
30
31#[cfg(feature = "transparent")]
32mod transparent;
33#[cfg(feature = "transparent")]
34pub use transparent::TransparentError;
35
36pub struct Updater {
37 pczt: Pczt,
38}
39
40impl Updater {
41 pub fn new(pczt: Pczt) -> Self {
43 Self { pczt }
44 }
45
46 pub fn update_global_with<F>(self, f: F) -> Self
48 where
49 F: FnOnce(GlobalUpdater<'_>),
50 {
51 let Pczt {
52 mut global,
53 transparent,
54 sapling,
55 orchard,
56 ironwood,
57 } = self.pczt;
58
59 f(GlobalUpdater(&mut global));
60
61 Self {
62 pczt: Pczt {
63 global,
64 transparent,
65 sapling,
66 orchard,
67 ironwood,
68 },
69 }
70 }
71
72 #[cfg(feature = "sapling")]
83 pub fn set_sapling_anchor(
84 mut self,
85 anchor: ::sapling::Anchor,
86 ) -> Result<Self, AnchorUpdateError> {
87 ensure_anchor_update_supported(&self.pczt.global)?;
88 ensure_no_sapling_spend_proof(&self.pczt.sapling)?;
89 set_anchor(&mut self.pczt.sapling.anchor, anchor.to_bytes())?;
90 Ok(self)
91 }
92
93 #[cfg(feature = "orchard")]
104 pub fn set_orchard_anchor(
105 mut self,
106 anchor: ::orchard::Anchor,
107 ) -> Result<Self, AnchorUpdateError> {
108 ensure_anchor_update_supported(&self.pczt.global)?;
109 ensure_no_orchard_proof_for_anchor(&self.pczt.orchard)?;
110 set_anchor(&mut self.pczt.orchard.anchor, anchor.to_bytes())?;
111 Ok(self)
112 }
113
114 #[cfg(feature = "orchard")]
125 pub fn set_ironwood_anchor(
126 mut self,
127 anchor: ::orchard::Anchor,
128 ) -> Result<Self, AnchorUpdateError> {
129 ensure_anchor_update_supported(&self.pczt.global)?;
130 ensure_no_orchard_proof_for_anchor(&self.pczt.ironwood)?;
131 set_anchor(&mut self.pczt.ironwood.anchor, anchor.to_bytes())?;
132 Ok(self)
133 }
134
135 #[cfg(feature = "sapling")]
144 pub fn set_sapling_spend_witnesses(
145 mut self,
146 witnesses: impl IntoIterator<Item = (usize, ::sapling::MerklePath)>,
147 ) -> Result<Self, SpendWitnessUpdateError> {
148 set_sapling_spend_witnesses(&mut self.pczt.sapling.spends, witnesses)?;
149
150 Ok(self)
151 }
152
153 #[cfg(feature = "orchard")]
163 pub fn set_orchard_spend_witnesses(
164 mut self,
165 witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
166 ) -> Result<Self, SpendWitnessUpdateError> {
167 if self.pczt.orchard.note_version != crate::orchard::NoteVersion::V2 {
168 return Err(SpendWitnessUpdateError::UnexpectedNoteVersion);
169 }
170 ensure_no_orchard_proof_for_witness(&self.pczt.orchard)?;
171 set_orchard_spend_witnesses(&mut self.pczt.orchard.actions, witnesses)?;
172
173 Ok(self)
174 }
175
176 #[cfg(feature = "orchard")]
186 pub fn set_ironwood_spend_witnesses(
187 mut self,
188 witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
189 ) -> Result<Self, SpendWitnessUpdateError> {
190 if self.pczt.ironwood.note_version != crate::orchard::NoteVersion::V3 {
191 return Err(SpendWitnessUpdateError::UnexpectedNoteVersion);
192 }
193 ensure_no_orchard_proof_for_witness(&self.pczt.ironwood)?;
194 set_orchard_spend_witnesses(&mut self.pczt.ironwood.actions, witnesses)?;
195
196 Ok(self)
197 }
198
199 pub fn finish(self) -> Pczt {
201 self.pczt
202 }
203}
204
205#[cfg(feature = "sapling")]
206fn ensure_no_sapling_spend_proof(bundle: &crate::sapling::Bundle) -> Result<(), AnchorUpdateError> {
207 if bundle.spends.iter().any(|spend| spend.zkproof.is_some()) {
208 Err(AnchorUpdateError::ProofAlreadyPresent)
209 } else {
210 Ok(())
211 }
212}
213
214#[cfg(feature = "orchard")]
215fn ensure_no_orchard_proof_for_anchor(
216 bundle: &crate::orchard::Bundle,
217) -> Result<(), AnchorUpdateError> {
218 if bundle.zkproof.is_some() {
219 Err(AnchorUpdateError::ProofAlreadyPresent)
220 } else {
221 Ok(())
222 }
223}
224
225#[cfg(feature = "orchard")]
226fn ensure_no_orchard_proof_for_witness(
227 bundle: &crate::orchard::Bundle,
228) -> Result<(), SpendWitnessUpdateError> {
229 if bundle.zkproof.is_some() {
230 Err(SpendWitnessUpdateError::ProofAlreadyPresent)
231 } else {
232 Ok(())
233 }
234}
235
236#[cfg(any(feature = "sapling", feature = "orchard"))]
237fn ensure_anchor_update_supported(global: &Global) -> Result<(), AnchorUpdateError> {
238 let supported_format = match (global.tx_version, global.version_group_id) {
239 (V6_TX_VERSION, V6_VERSION_GROUP_ID) => true,
240 #[cfg(zcash_unstable = "nutachyon")]
241 (V7_TX_VERSION, V7_VERSION_GROUP_ID) => true,
242 _ => false,
243 };
244 if !supported_format {
245 return Err(AnchorUpdateError::UnsupportedTransactionFormat);
246 }
247
248 match BranchId::try_from(global.consensus_branch_id) {
249 Ok(BranchId::Nu6_3) => Ok(()),
250 Ok(BranchId::Nu7) => Ok(()),
251 #[cfg(zcash_unstable = "nutachyon")]
252 Ok(BranchId::NuTachyon) => Ok(()),
253 Ok(_) => Err(AnchorUpdateError::UnsupportedConsensusBranchId),
254 Err(_) => Err(AnchorUpdateError::UnknownConsensusBranchId),
255 }
256}
257
258#[cfg(any(feature = "sapling", feature = "orchard"))]
259fn set_anchor(slot: &mut Option<[u8; 32]>, anchor: [u8; 32]) -> Result<(), AnchorUpdateError> {
260 match slot {
261 Some(existing) if *existing != anchor => Err(AnchorUpdateError::ConflictingAnchor),
262 _ => {
263 *slot = Some(anchor);
264 Ok(())
265 }
266 }
267}
268
269#[cfg(feature = "sapling")]
270fn set_sapling_spend_witnesses(
271 spends: &mut [crate::sapling::Spend],
272 witnesses: impl IntoIterator<Item = (usize, ::sapling::MerklePath)>,
273) -> Result<(), SpendWitnessUpdateError> {
274 for (spend_index, merkle_path) in witnesses {
275 let spend = spends
276 .get_mut(spend_index)
277 .ok_or(SpendWitnessUpdateError::InvalidSpendIndex(spend_index))?;
278 if spend.zkproof.is_some() {
279 return Err(SpendWitnessUpdateError::ProofAlreadyPresent);
280 }
281 let position = u32::try_from(u64::from(merkle_path.position()))
282 .map_err(|_| SpendWitnessUpdateError::PositionOutOfRange)?;
283 let mut auth_path = [[0; 32]; 32];
284 for (target, node) in auth_path.iter_mut().zip(merkle_path.path_elems()) {
285 *target = node.to_bytes();
286 }
287 spend.witness = Some((position, auth_path));
288 }
289
290 Ok(())
291}
292
293#[cfg(feature = "orchard")]
294fn set_orchard_spend_witnesses(
295 actions: &mut [crate::orchard::Action],
296 witnesses: impl IntoIterator<Item = (usize, ::orchard::tree::MerklePath)>,
297) -> Result<(), SpendWitnessUpdateError> {
298 for (action_index, merkle_path) in witnesses {
299 let action = actions
300 .get_mut(action_index)
301 .ok_or(SpendWitnessUpdateError::InvalidSpendIndex(action_index))?;
302 action.spend.witness = Some((
303 merkle_path.position(),
304 merkle_path.auth_path().map(|node| node.to_bytes()),
305 ));
306 }
307
308 Ok(())
309}
310
311pub struct GlobalUpdater<'a>(&'a mut Global);
313
314impl GlobalUpdater<'_> {
315 pub fn set_proprietary(&mut self, key: String, value: Vec<u8>) {
317 self.0.proprietary.insert(key, value);
318 }
319}
320
321#[cfg(any(feature = "sapling", feature = "orchard"))]
323#[derive(Debug, PartialEq, Eq)]
324#[non_exhaustive]
325pub enum AnchorUpdateError {
326 UnsupportedTransactionFormat,
328 UnknownConsensusBranchId,
330 UnsupportedConsensusBranchId,
332 ProofAlreadyPresent,
334 ConflictingAnchor,
336}
337
338#[cfg(any(feature = "sapling", feature = "orchard"))]
339impl core::fmt::Display for AnchorUpdateError {
340 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341 match self {
342 AnchorUpdateError::UnsupportedTransactionFormat => {
343 write!(
344 f,
345 "PCZT transaction format does not support shielded anchor updates"
346 )
347 }
348 AnchorUpdateError::UnknownConsensusBranchId => {
349 write!(f, "unknown consensus branch ID")
350 }
351 AnchorUpdateError::UnsupportedConsensusBranchId => {
352 write!(
353 f,
354 "consensus branch ID does not support shielded anchor updates"
355 )
356 }
357 AnchorUpdateError::ProofAlreadyPresent => {
358 write!(
359 f,
360 "shielded proof that depends on the anchor is already present"
361 )
362 }
363 AnchorUpdateError::ConflictingAnchor => {
364 write!(f, "bundle already contains a different anchor")
365 }
366 }
367 }
368}
369
370#[cfg(any(feature = "sapling", feature = "orchard"))]
372#[derive(Debug, PartialEq, Eq)]
373#[non_exhaustive]
374pub enum SpendWitnessUpdateError {
375 InvalidSpendIndex(usize),
377 ProofAlreadyPresent,
379 PositionOutOfRange,
381 UnexpectedNoteVersion,
383}
384
385#[cfg(any(feature = "sapling", feature = "orchard"))]
386impl core::fmt::Display for SpendWitnessUpdateError {
387 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388 match self {
389 SpendWitnessUpdateError::InvalidSpendIndex(index) => {
390 write!(f, "spend index {index} does not exist")
391 }
392 SpendWitnessUpdateError::ProofAlreadyPresent => {
393 write!(f, "proof for target spend or bundle is already present")
394 }
395 SpendWitnessUpdateError::PositionOutOfRange => {
396 write!(
397 f,
398 "witness position cannot be represented in the PCZT wire format"
399 )
400 }
401 SpendWitnessUpdateError::UnexpectedNoteVersion => {
402 write!(
403 f,
404 "bundle note-plaintext version does not match the pool being updated"
405 )
406 }
407 }
408 }
409}