pczt/orchard.rs
1//! The Orchard fields of a PCZT.
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::cmp::Ordering;
7
8#[cfg(feature = "orchard")]
9use ff::PrimeField;
10use getset::Getters;
11use serde::{Deserialize, Serialize};
12use serde_with::serde_as;
13
14use crate::{
15 common::{Global, Zip32Derivation},
16 roles::combiner::{merge_map, merge_optional},
17};
18
19/// PCZT fields that are specific to producing the transaction's Orchard bundle (if any).
20#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
21pub struct Bundle {
22 /// The Orchard actions in this bundle.
23 ///
24 /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer,
25 /// Signer, Combiner, or Spend Finalizer.
26 #[getset(get = "pub")]
27 pub(crate) actions: Vec<Action>,
28
29 /// The flags for the Orchard bundle.
30 ///
31 /// Contains:
32 /// - `enableSpendsOrchard` flag (bit 0)
33 /// - `enableOutputsOrchard` flag (bit 1)
34 /// - Reserved, zeros (bits 2..=7)
35 ///
36 /// This is set by the Creator. The Constructor MUST only add spends and outputs that
37 /// are consistent with these flags (i.e. are dummies as appropriate).
38 #[getset(get = "pub")]
39 pub(crate) flags: u8,
40
41 /// The net value of Orchard spends minus outputs.
42 ///
43 /// This is initialized by the Creator, and updated by the Constructor as spends or
44 /// outputs are added to the PCZT. It enables per-spend and per-output values to be
45 /// redacted from the PCZT after they are no longer necessary.
46 #[getset(get = "pub")]
47 pub(crate) value_sum: (u64, bool),
48
49 /// The Orchard anchor for this transaction.
50 ///
51 /// Set by the Creator.
52 #[getset(get = "pub")]
53 pub(crate) anchor: [u8; 32],
54
55 /// The Orchard bundle proof.
56 ///
57 /// This is `None` until it is set by the Prover.
58 pub(crate) zkproof: Option<Vec<u8>>,
59
60 /// The Orchard binding signature signing key.
61 ///
62 /// - This is `None` until it is set by the IO Finalizer.
63 /// - The Transaction Extractor uses this to produce the binding signature.
64 pub(crate) bsk: Option<[u8; 32]>,
65}
66
67/// Information about an Orchard action within a transaction.
68#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
69pub struct Action {
70 //
71 // Action effecting data.
72 //
73 // These are required fields that are part of the final transaction, and are filled in
74 // by the Constructor when adding an output.
75 //
76 #[getset(get = "pub")]
77 pub(crate) cv_net: [u8; 32],
78 #[getset(get = "pub")]
79 pub(crate) spend: Spend,
80 #[getset(get = "pub")]
81 pub(crate) output: Output,
82
83 /// The value commitment randomness.
84 ///
85 /// - This is set by the Constructor.
86 /// - The IO Finalizer compresses it into the bsk.
87 /// - This is required by the Prover.
88 /// - This may be used by Signers to verify that the value correctly matches `cv`.
89 ///
90 /// This opens `cv` for all participants. For Signers who don't need this information,
91 /// or after proofs / signatures have been applied, this can be redacted.
92 pub(crate) rcv: Option<[u8; 32]>,
93}
94
95/// Information about the spend part of an Orchard action.
96#[serde_as]
97#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
98pub struct Spend {
99 //
100 // Spend-specific Action effecting data.
101 //
102 // These are required fields that are part of the final transaction, and are filled in
103 // by the Constructor when adding a spend.
104 //
105 #[getset(get = "pub")]
106 pub(crate) nullifier: [u8; 32],
107 #[getset(get = "pub")]
108 pub(crate) rk: [u8; 32],
109
110 /// The spend authorization signature.
111 ///
112 /// This is set by the Signer.
113 #[serde_as(as = "Option<[_; 64]>")]
114 pub(crate) spend_auth_sig: Option<[u8; 64]>,
115
116 /// The [raw encoding] of the Orchard payment address that received the note being spent.
117 ///
118 /// - This is set by the Constructor.
119 /// - This is required by the Prover.
120 ///
121 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
122 #[serde_as(as = "Option<[_; 43]>")]
123 pub(crate) recipient: Option<[u8; 43]>,
124
125 /// The value of the input being spent.
126 ///
127 /// - This is required by the Prover.
128 /// - This may be used by Signers to verify that the value matches `cv`, and to
129 /// confirm the values and change involved in the transaction.
130 ///
131 /// This exposes the input value to all participants. For Signers who don't need this
132 /// information, or after signatures have been applied, this can be redacted.
133 pub(crate) value: Option<u64>,
134
135 /// The rho value for the note being spent.
136 ///
137 /// - This is set by the Constructor.
138 /// - This is required by the Prover.
139 pub(crate) rho: Option<[u8; 32]>,
140
141 /// The seed randomness for the note being spent.
142 ///
143 /// - This is set by the Constructor.
144 /// - This is required by the Prover.
145 pub(crate) rseed: Option<[u8; 32]>,
146
147 /// The full viewing key that received the note being spent.
148 ///
149 /// - This is set by the Updater.
150 /// - This is required by the Prover.
151 #[serde_as(as = "Option<[_; 96]>")]
152 pub(crate) fvk: Option<[u8; 96]>,
153
154 /// A witness from the note to the bundle's anchor.
155 ///
156 /// - This is set by the Updater.
157 /// - This is required by the Prover.
158 pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
159
160 /// The spend authorization randomizer.
161 ///
162 /// - This is chosen by the Constructor.
163 /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to
164 /// validate `rk`.
165 /// - After `zkproof` / `spend_auth_sig` has been set, this can be redacted.
166 pub(crate) alpha: Option<[u8; 32]>,
167
168 /// The ZIP 32 derivation path at which the spending key can be found for the note
169 /// being spent.
170 pub(crate) zip32_derivation: Option<Zip32Derivation>,
171
172 /// The spending key for this spent note, if it is a dummy note.
173 ///
174 /// - This is chosen by the Constructor.
175 /// - This is required by the IO Finalizer, and is cleared by it once used.
176 /// - Signers MUST reject PCZTs that contain `dummy_sk` values.
177 pub(crate) dummy_sk: Option<[u8; 32]>,
178
179 /// Proprietary fields related to the note being spent.
180 #[getset(get = "pub")]
181 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
182}
183
184/// Information about the output part of an Orchard action.
185#[serde_as]
186#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
187pub struct Output {
188 //
189 // Output-specific Action effecting data.
190 //
191 // These are required fields that are part of the final transaction, and are filled in
192 // by the Constructor when adding an output.
193 //
194 #[getset(get = "pub")]
195 pub(crate) cmx: [u8; 32],
196 #[getset(get = "pub")]
197 pub(crate) ephemeral_key: [u8; 32],
198 /// The encrypted note plaintext for the output.
199 ///
200 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
201 ///
202 /// Once we have memo bundles, we will be able to set memos independently of Outputs.
203 /// For now, the Constructor sets both at the same time.
204 #[getset(get = "pub")]
205 pub(crate) enc_ciphertext: Vec<u8>,
206 /// The encrypted note plaintext for the output.
207 ///
208 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
209 #[getset(get = "pub")]
210 pub(crate) out_ciphertext: Vec<u8>,
211
212 /// The [raw encoding] of the Orchard payment address that will receive the output.
213 ///
214 /// - This is set by the Constructor.
215 /// - This is required by the Prover.
216 ///
217 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
218 #[serde_as(as = "Option<[_; 43]>")]
219 #[getset(get = "pub")]
220 pub(crate) recipient: Option<[u8; 43]>,
221
222 /// The value of the output.
223 ///
224 /// This may be used by Signers to verify that the value matches `cv`, and to confirm
225 /// the values and change involved in the transaction.
226 ///
227 /// This exposes the value to all participants. For Signers who don't need this
228 /// information, we can drop the values and compress the rcvs into the bsk global.
229 #[getset(get = "pub")]
230 pub(crate) value: Option<u64>,
231
232 /// The seed randomness for the output.
233 ///
234 /// - This is set by the Constructor.
235 /// - This is required by the Prover, instead of disclosing `shared_secret` to them.
236 #[getset(get = "pub")]
237 pub(crate) rseed: Option<[u8; 32]>,
238
239 /// The `ock` value used to encrypt `out_ciphertext`.
240 ///
241 /// This enables Signers to verify that `out_ciphertext` is correctly encrypted.
242 ///
243 /// This may be `None` if the Constructor added the output using an OVK policy of
244 /// "None", to make the output unrecoverable from the chain by the sender.
245 pub(crate) ock: Option<[u8; 32]>,
246
247 /// The ZIP 32 derivation path at which the spending key can be found for the output.
248 pub(crate) zip32_derivation: Option<Zip32Derivation>,
249
250 /// The user-facing address to which this output is being sent, if any.
251 ///
252 /// - This is set by an Updater.
253 /// - Signers must parse this address (if present) and confirm that it contains
254 /// `recipient` (either directly, or e.g. as a receiver within a Unified Address).
255 #[getset(get = "pub")]
256 pub(crate) user_address: Option<String>,
257
258 /// Proprietary fields related to the note being created.
259 #[getset(get = "pub")]
260 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
261}
262
263impl Bundle {
264 /// Merges this bundle with another.
265 ///
266 /// Returns `None` if the bundles have conflicting data.
267 pub(crate) fn merge(
268 mut self,
269 other: Self,
270 self_global: &Global,
271 other_global: &Global,
272 ) -> Option<Self> {
273 // Destructure `other` to ensure we handle everything.
274 let Self {
275 mut actions,
276 flags,
277 value_sum,
278 anchor,
279 zkproof,
280 bsk,
281 } = other;
282
283 if self.flags != flags {
284 return None;
285 }
286
287 // If `bsk` is set on either bundle, the IO Finalizer has run, which means we
288 // cannot have differing numbers of actions, and the value sums must match.
289 match (self.bsk.as_mut(), bsk) {
290 (Some(lhs), Some(rhs)) if lhs != &rhs => return None,
291 (Some(_), _) | (_, Some(_))
292 if self.actions.len() != actions.len() || self.value_sum != value_sum =>
293 {
294 return None
295 }
296 // IO Finalizer has run, and neither bundle has excess spends or outputs.
297 (Some(_), _) | (_, Some(_)) => (),
298 // IO Finalizer has not run on either bundle.
299 (None, None) => match (
300 self_global.shielded_modifiable(),
301 other_global.shielded_modifiable(),
302 self.actions.len().cmp(&actions.len()),
303 ) {
304 // Fail if the merge would add actions to a non-modifiable bundle.
305 (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
306 // If the other bundle has more actions than us, move them over; these
307 // cannot conflict by construction.
308 (true, _, Ordering::Less) => {
309 self.actions.extend(actions.drain(self.actions.len()..));
310
311 // We check below that the overlapping actions match. Assuming here
312 // that they will, we can take the other bundle's value sum.
313 self.value_sum = value_sum;
314 }
315 // Do nothing otherwise.
316 (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
317 },
318 }
319
320 if self.anchor != anchor {
321 return None;
322 }
323
324 if !merge_optional(&mut self.zkproof, zkproof) {
325 return None;
326 }
327
328 // Leverage the early-exit behaviour of zip to confirm that the remaining data in
329 // the other bundle matches this one.
330 for (lhs, rhs) in self.actions.iter_mut().zip(actions.into_iter()) {
331 // Destructure `rhs` to ensure we handle everything.
332 let Action {
333 cv_net,
334 spend:
335 Spend {
336 nullifier,
337 rk,
338 spend_auth_sig,
339 recipient,
340 value,
341 rho,
342 rseed,
343 fvk,
344 witness,
345 alpha,
346 zip32_derivation: spend_zip32_derivation,
347 dummy_sk,
348 proprietary: spend_proprietary,
349 },
350 output:
351 Output {
352 cmx,
353 ephemeral_key,
354 enc_ciphertext,
355 out_ciphertext,
356 recipient: output_recipient,
357 value: output_value,
358 rseed: output_rseed,
359 ock,
360 zip32_derivation: output_zip32_derivation,
361 user_address,
362 proprietary: output_proprietary,
363 },
364 rcv,
365 } = rhs;
366
367 if lhs.cv_net != cv_net
368 || lhs.spend.nullifier != nullifier
369 || lhs.spend.rk != rk
370 || lhs.output.cmx != cmx
371 || lhs.output.ephemeral_key != ephemeral_key
372 || lhs.output.enc_ciphertext != enc_ciphertext
373 || lhs.output.out_ciphertext != out_ciphertext
374 {
375 return None;
376 }
377
378 if !(merge_optional(&mut lhs.spend.spend_auth_sig, spend_auth_sig)
379 && merge_optional(&mut lhs.spend.recipient, recipient)
380 && merge_optional(&mut lhs.spend.value, value)
381 && merge_optional(&mut lhs.spend.rho, rho)
382 && merge_optional(&mut lhs.spend.rseed, rseed)
383 && merge_optional(&mut lhs.spend.fvk, fvk)
384 && merge_optional(&mut lhs.spend.witness, witness)
385 && merge_optional(&mut lhs.spend.alpha, alpha)
386 && merge_optional(&mut lhs.spend.zip32_derivation, spend_zip32_derivation)
387 && merge_optional(&mut lhs.spend.dummy_sk, dummy_sk)
388 && merge_map(&mut lhs.spend.proprietary, spend_proprietary)
389 && merge_optional(&mut lhs.output.recipient, output_recipient)
390 && merge_optional(&mut lhs.output.value, output_value)
391 && merge_optional(&mut lhs.output.rseed, output_rseed)
392 && merge_optional(&mut lhs.output.ock, ock)
393 && merge_optional(&mut lhs.output.zip32_derivation, output_zip32_derivation)
394 && merge_optional(&mut lhs.output.user_address, user_address)
395 && merge_map(&mut lhs.output.proprietary, output_proprietary)
396 && merge_optional(&mut lhs.rcv, rcv))
397 {
398 return None;
399 }
400 }
401
402 Some(self)
403 }
404}
405
406#[cfg(feature = "orchard")]
407impl Bundle {
408 pub(crate) fn into_parsed(self) -> Result<orchard::pczt::Bundle, orchard::pczt::ParseError> {
409 let actions = self
410 .actions
411 .into_iter()
412 .map(|action| {
413 let spend = orchard::pczt::Spend::parse(
414 action.spend.nullifier,
415 action.spend.rk,
416 action.spend.spend_auth_sig,
417 action.spend.recipient,
418 action.spend.value,
419 action.spend.rho,
420 action.spend.rseed,
421 action.spend.fvk,
422 action.spend.witness,
423 action.spend.alpha,
424 action
425 .spend
426 .zip32_derivation
427 .map(|z| {
428 orchard::pczt::Zip32Derivation::parse(
429 z.seed_fingerprint,
430 z.derivation_path,
431 )
432 })
433 .transpose()?,
434 action.spend.dummy_sk,
435 action.spend.proprietary,
436 )?;
437
438 let output = orchard::pczt::Output::parse(
439 *spend.nullifier(),
440 action.output.cmx,
441 action.output.ephemeral_key,
442 action.output.enc_ciphertext,
443 action.output.out_ciphertext,
444 action.output.recipient,
445 action.output.value,
446 action.output.rseed,
447 action.output.ock,
448 action
449 .output
450 .zip32_derivation
451 .map(|z| {
452 orchard::pczt::Zip32Derivation::parse(
453 z.seed_fingerprint,
454 z.derivation_path,
455 )
456 })
457 .transpose()?,
458 action.output.user_address,
459 action.output.proprietary,
460 )?;
461
462 orchard::pczt::Action::parse(action.cv_net, spend, output, action.rcv)
463 })
464 .collect::<Result<_, _>>()?;
465
466 orchard::pczt::Bundle::parse(
467 actions,
468 self.flags,
469 self.value_sum,
470 self.anchor,
471 self.zkproof,
472 self.bsk,
473 )
474 }
475
476 pub(crate) fn serialize_from(bundle: orchard::pczt::Bundle) -> Self {
477 let actions = bundle
478 .actions()
479 .iter()
480 .map(|action| {
481 let spend = action.spend();
482 let output = action.output();
483
484 Action {
485 cv_net: action.cv_net().to_bytes(),
486 spend: Spend {
487 nullifier: spend.nullifier().to_bytes(),
488 rk: spend.rk().into(),
489 spend_auth_sig: spend.spend_auth_sig().as_ref().map(|s| s.into()),
490 recipient: action
491 .spend()
492 .recipient()
493 .map(|recipient| recipient.to_raw_address_bytes()),
494 value: spend.value().map(|value| value.inner()),
495 rho: spend.rho().map(|rho| rho.to_bytes()),
496 rseed: spend.rseed().map(|rseed| *rseed.as_bytes()),
497 fvk: spend.fvk().as_ref().map(|fvk| fvk.to_bytes()),
498 witness: spend.witness().as_ref().map(|witness| {
499 (
500 u32::try_from(u64::from(witness.position()))
501 .expect("Sapling positions fit in u32"),
502 witness
503 .auth_path()
504 .iter()
505 .map(|node| node.to_bytes())
506 .collect::<Vec<_>>()[..]
507 .try_into()
508 .expect("path is length 32"),
509 )
510 }),
511 alpha: spend.alpha().map(|alpha| alpha.to_repr()),
512 zip32_derivation: spend.zip32_derivation().as_ref().map(|z| {
513 Zip32Derivation {
514 seed_fingerprint: *z.seed_fingerprint(),
515 derivation_path: z
516 .derivation_path()
517 .iter()
518 .map(|i| i.index())
519 .collect(),
520 }
521 }),
522 dummy_sk: action
523 .spend()
524 .dummy_sk()
525 .map(|dummy_sk| *dummy_sk.to_bytes()),
526 proprietary: spend.proprietary().clone(),
527 },
528 output: Output {
529 cmx: output.cmx().to_bytes(),
530 ephemeral_key: output.encrypted_note().epk_bytes,
531 enc_ciphertext: output.encrypted_note().enc_ciphertext.to_vec(),
532 out_ciphertext: output.encrypted_note().out_ciphertext.to_vec(),
533 recipient: action
534 .output()
535 .recipient()
536 .map(|recipient| recipient.to_raw_address_bytes()),
537 value: output.value().map(|value| value.inner()),
538 rseed: output.rseed().map(|rseed| *rseed.as_bytes()),
539 ock: output.ock().as_ref().map(|ock| ock.0),
540 zip32_derivation: output.zip32_derivation().as_ref().map(|z| {
541 Zip32Derivation {
542 seed_fingerprint: *z.seed_fingerprint(),
543 derivation_path: z
544 .derivation_path()
545 .iter()
546 .map(|i| i.index())
547 .collect(),
548 }
549 }),
550 user_address: output.user_address().clone(),
551 proprietary: output.proprietary().clone(),
552 },
553 rcv: action.rcv().as_ref().map(|rcv| rcv.to_bytes()),
554 }
555 })
556 .collect();
557
558 let value_sum = {
559 let (magnitude, sign) = bundle.value_sum().magnitude_sign();
560 (magnitude, matches!(sign, orchard::value::Sign::Negative))
561 };
562
563 Self {
564 actions,
565 flags: bundle.flags().to_byte(),
566 value_sum,
567 anchor: bundle.anchor().to_bytes(),
568 zkproof: bundle
569 .zkproof()
570 .as_ref()
571 .map(|zkproof| zkproof.as_ref().to_vec()),
572 bsk: bundle.bsk().as_ref().map(|bsk| bsk.into()),
573 }
574 }
575}