pczt/
sapling.rs

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