sapling_crypto/pczt/
parse.rs

1use core::fmt;
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use ff::PrimeField;
8use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey};
9use zip32::ChildIndex;
10
11use super::{Bundle, Output, Spend, Zip32Derivation};
12use crate::{
13    bundle::GrothProofBytes,
14    keys::{SpendAuthorizingKey, SpendValidatingKey},
15    note::ExtractedNoteCommitment,
16    value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
17    Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed,
18};
19
20impl Bundle {
21    /// Parses a PCZT bundle from its component parts.
22    pub fn parse(
23        spends: Vec<Spend>,
24        outputs: Vec<Output>,
25        value_sum: i128,
26        anchor: [u8; 32],
27        bsk: Option<[u8; 32]>,
28    ) -> Result<Self, ParseError> {
29        let value_sum = ValueSum::from_raw(value_sum);
30
31        let anchor = Anchor::from_bytes(anchor)
32            .into_option()
33            .ok_or(ParseError::InvalidAnchor)?;
34
35        let bsk = bsk
36            .map(redjubjub::SigningKey::try_from)
37            .transpose()
38            .map_err(|_| ParseError::InvalidBindingSignatureSigningKey)?;
39
40        Ok(Self {
41            spends,
42            outputs,
43            value_sum,
44            anchor,
45            bsk,
46        })
47    }
48}
49
50impl Spend {
51    /// Parses a PCZT spend from its component parts.
52    #[allow(clippy::too_many_arguments)]
53    pub fn parse(
54        cv: [u8; 32],
55        nullifier: [u8; 32],
56        rk: [u8; 32],
57        zkproof: Option<GrothProofBytes>,
58        spend_auth_sig: Option<[u8; 64]>,
59        recipient: Option<[u8; 43]>,
60        value: Option<u64>,
61        rcm: Option<[u8; 32]>,
62        rseed: Option<[u8; 32]>,
63        rcv: Option<[u8; 32]>,
64        proof_generation_key: Option<([u8; 32], [u8; 32])>,
65        witness: Option<(u32, [[u8; 32]; 32])>,
66        alpha: Option<[u8; 32]>,
67        zip32_derivation: Option<Zip32Derivation>,
68        dummy_ask: Option<[u8; 32]>,
69        proprietary: BTreeMap<String, Vec<u8>>,
70    ) -> Result<Self, ParseError> {
71        let cv = ValueCommitment::from_bytes_not_small_order(&cv)
72            .into_option()
73            .ok_or(ParseError::InvalidValueCommitment)?;
74
75        let nullifier = Nullifier(nullifier);
76
77        let rk = redjubjub::VerificationKey::try_from(rk)
78            .map_err(|_| ParseError::InvalidRandomizedKey)?;
79
80        let spend_auth_sig = spend_auth_sig.map(redjubjub::Signature::from);
81
82        let recipient = recipient
83            .as_ref()
84            .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient))
85            .transpose()?;
86
87        let value = value.map(NoteValue::from_raw);
88
89        let rseed = match (rcm, rseed) {
90            (None, None) => Ok(None),
91            (Some(rcm), None) => jubjub::Scalar::from_repr(rcm)
92                .into_option()
93                .ok_or(ParseError::InvalidNoteCommitRandomness)
94                .map(Rseed::BeforeZip212)
95                .map(Some),
96            (None, Some(rseed)) => Ok(Some(Rseed::AfterZip212(rseed))),
97            (Some(_), Some(_)) => Err(ParseError::MixedNoteCommitRandomnessAndRseed),
98        }?;
99
100        let rcv = rcv
101            .map(|rcv| {
102                ValueCommitTrapdoor::from_bytes(rcv)
103                    .into_option()
104                    .ok_or(ParseError::InvalidValueCommitTrapdoor)
105            })
106            .transpose()?;
107
108        let proof_generation_key = proof_generation_key
109            .map(|(ak, nsk)| {
110                Ok(ProofGenerationKey {
111                    ak: SpendValidatingKey::from_bytes(&ak)
112                        .ok_or(ParseError::InvalidProofGenerationKey)?,
113                    nsk: jubjub::Scalar::from_repr(nsk)
114                        .into_option()
115                        .ok_or(ParseError::InvalidProofGenerationKey)?,
116                })
117            })
118            .transpose()?;
119
120        let witness = witness
121            .map(|(position, auth_path_bytes)| {
122                let path_elems = auth_path_bytes
123                    .into_iter()
124                    .map(|hash| {
125                        Node::from_bytes(hash)
126                            .into_option()
127                            .ok_or(ParseError::InvalidWitness)
128                    })
129                    .collect::<Result<Vec<_>, _>>()?;
130
131                MerklePath::from_parts(path_elems, u64::from(position).into())
132                    .map_err(|()| ParseError::InvalidWitness)
133            })
134            .transpose()?;
135
136        let alpha = alpha
137            .map(|alpha| {
138                jubjub::Scalar::from_repr(alpha)
139                    .into_option()
140                    .ok_or(ParseError::InvalidSpendAuthRandomizer)
141            })
142            .transpose()?;
143
144        let dummy_ask = dummy_ask
145            .map(|dummy_ask| {
146                SpendAuthorizingKey::from_bytes(&dummy_ask)
147                    .ok_or(ParseError::InvalidDummySpendAuthorizingKey)
148            })
149            .transpose()?;
150
151        Ok(Self {
152            cv,
153            nullifier,
154            rk,
155            zkproof,
156            spend_auth_sig,
157            recipient,
158            value,
159            rseed,
160            rcv,
161            proof_generation_key,
162            witness,
163            alpha,
164            zip32_derivation,
165            dummy_ask,
166            proprietary,
167        })
168    }
169}
170
171impl Output {
172    /// Parses a PCZT output from its component parts.
173    #[allow(clippy::too_many_arguments)]
174    pub fn parse(
175        cv: [u8; 32],
176        cmu: [u8; 32],
177        ephemeral_key: [u8; 32],
178        enc_ciphertext: Vec<u8>,
179        out_ciphertext: Vec<u8>,
180        zkproof: Option<GrothProofBytes>,
181        recipient: Option<[u8; 43]>,
182        value: Option<u64>,
183        rseed: Option<[u8; 32]>,
184        rcv: Option<[u8; 32]>,
185        ock: Option<[u8; 32]>,
186        zip32_derivation: Option<Zip32Derivation>,
187        user_address: Option<String>,
188        proprietary: BTreeMap<String, Vec<u8>>,
189    ) -> Result<Self, ParseError> {
190        let cv = ValueCommitment::from_bytes_not_small_order(&cv)
191            .into_option()
192            .ok_or(ParseError::InvalidValueCommitment)?;
193
194        let cmu = ExtractedNoteCommitment::from_bytes(&cmu)
195            .into_option()
196            .ok_or(ParseError::InvalidExtractedNoteCommitment)?;
197
198        let ephemeral_key = EphemeralKeyBytes(ephemeral_key);
199
200        let enc_ciphertext = enc_ciphertext
201            .as_slice()
202            .try_into()
203            .map_err(|_| ParseError::InvalidEncCiphertext)?;
204
205        let out_ciphertext = out_ciphertext
206            .as_slice()
207            .try_into()
208            .map_err(|_| ParseError::InvalidOutCiphertext)?;
209
210        let recipient = recipient
211            .as_ref()
212            .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient))
213            .transpose()?;
214
215        let value = value.map(NoteValue::from_raw);
216
217        let rcv = rcv
218            .map(|rcv| {
219                ValueCommitTrapdoor::from_bytes(rcv)
220                    .into_option()
221                    .ok_or(ParseError::InvalidValueCommitTrapdoor)
222            })
223            .transpose()?;
224
225        let ock = ock.map(OutgoingCipherKey);
226
227        Ok(Self {
228            cv,
229            cmu,
230            ephemeral_key,
231            enc_ciphertext,
232            out_ciphertext,
233            zkproof,
234            recipient,
235            value,
236            rseed,
237            rcv,
238            ock,
239            zip32_derivation,
240            user_address,
241            proprietary,
242        })
243    }
244}
245
246impl Zip32Derivation {
247    /// Parses a ZIP 32 derivation path from its component parts.
248    ///
249    /// Returns an error if any of the derivation path indices are non-hardened (which
250    /// this crate does not support, even though Sapling does).
251    pub fn parse(
252        seed_fingerprint: [u8; 32],
253        derivation_path: Vec<u32>,
254    ) -> Result<Self, ParseError> {
255        Ok(Self {
256            seed_fingerprint,
257            derivation_path: derivation_path
258                .into_iter()
259                .map(|i| ChildIndex::from_index(i).ok_or(ParseError::InvalidZip32Derivation))
260                .collect::<Result<_, _>>()?,
261        })
262    }
263}
264
265/// Errors that can occur while parsing a PCZT bundle.
266#[derive(Debug)]
267#[non_exhaustive]
268pub enum ParseError {
269    /// An invalid `anchor` was provided.
270    InvalidAnchor,
271    /// An invalid `bsk` was provided.
272    InvalidBindingSignatureSigningKey,
273    /// An invalid `dummy_ask` was provided.
274    InvalidDummySpendAuthorizingKey,
275    /// An invalid `enc_ciphertext` was provided.
276    InvalidEncCiphertext,
277    /// An invalid `cmu` was provided.
278    InvalidExtractedNoteCommitment,
279    /// An invalid `rcm` was provided.
280    InvalidNoteCommitRandomness,
281    /// An invalid `out_ciphertext` was provided.
282    InvalidOutCiphertext,
283    /// An invalid `proof_generation_key` was provided.
284    InvalidProofGenerationKey,
285    /// An invalid `rk` was provided.
286    InvalidRandomizedKey,
287    /// An invalid `recipient` was provided.
288    InvalidRecipient,
289    /// An invalid `alpha` was provided.
290    InvalidSpendAuthRandomizer,
291    /// An invalid `cv` was provided.
292    InvalidValueCommitment,
293    /// An invalid `rcv` was provided.
294    InvalidValueCommitTrapdoor,
295    /// An invalid `witness` was provided.
296    InvalidWitness,
297    /// An invalid `zip32_derivation` was provided.
298    InvalidZip32Derivation,
299    /// Both `rcm` and `rseed` were provided for a Spend.
300    MixedNoteCommitRandomnessAndRseed,
301}
302
303impl fmt::Display for ParseError {
304    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
305        match self {
306            ParseError::InvalidAnchor => write!(f, "invalid anchor"),
307            ParseError::InvalidBindingSignatureSigningKey => write!(f, "invalid `bsk`"),
308            ParseError::InvalidDummySpendAuthorizingKey => write!(f, "invalid `dummy_ask`"),
309            ParseError::InvalidEncCiphertext => write!(f, "invalid `enc_ciphertext`"),
310            ParseError::InvalidExtractedNoteCommitment => write!(f, "invalid `cmu`"),
311            ParseError::InvalidNoteCommitRandomness => write!(f, "invalid `rcm`"),
312            ParseError::InvalidOutCiphertext => write!(f, "invalid `out_ciphertext`"),
313            ParseError::InvalidProofGenerationKey => write!(f, "invalid `proof_generation_key`"),
314            ParseError::InvalidRandomizedKey => write!(f, "invalid `rk`"),
315            ParseError::InvalidRecipient => write!(f, "invalid `recipient`"),
316            ParseError::InvalidSpendAuthRandomizer => write!(f, "invalid `alpha`"),
317            ParseError::InvalidValueCommitment => write!(f, "invalid `cv`"),
318            ParseError::InvalidValueCommitTrapdoor => write!(f, "invalid `rcv`"),
319            ParseError::InvalidWitness => write!(f, "invalid `witness`"),
320            ParseError::InvalidZip32Derivation => write!(f, "invalid `zip32_derivation`"),
321            ParseError::MixedNoteCommitRandomnessAndRseed => {
322                write!(f, "both `rcm` and `rseed` were provided for a spend")
323            }
324        }
325    }
326}
327
328#[cfg(feature = "std")]
329impl std::error::Error for ParseError {}