Skip to main content

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(|b| redjubjub::SigningKey::from_bytes(&b))
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                ProofGenerationKey::from_parts(
111                    SpendValidatingKey::from_bytes(&ak)
112                        .ok_or(ParseError::InvalidProofGenerationKey)?,
113                    jubjub::Scalar::from_repr(nsk)
114                        .into_option()
115                        .ok_or(ParseError::InvalidProofGenerationKey)?,
116                )
117                .ok_or(ParseError::InvalidProofGenerationKey)
118            })
119            .transpose()?;
120
121        let witness = witness
122            .map(|(position, auth_path_bytes)| {
123                let path_elems = auth_path_bytes
124                    .into_iter()
125                    .map(|hash| {
126                        Node::from_bytes(hash)
127                            .into_option()
128                            .ok_or(ParseError::InvalidWitness)
129                    })
130                    .collect::<Result<Vec<_>, _>>()?;
131
132                MerklePath::from_parts(path_elems, u64::from(position).into())
133                    .map_err(|()| ParseError::InvalidWitness)
134            })
135            .transpose()?;
136
137        let alpha = alpha
138            .map(|alpha| {
139                jubjub::Scalar::from_repr(alpha)
140                    .into_option()
141                    .ok_or(ParseError::InvalidSpendAuthRandomizer)
142            })
143            .transpose()?;
144
145        let dummy_ask = dummy_ask
146            .map(|dummy_ask| {
147                SpendAuthorizingKey::from_bytes(&dummy_ask)
148                    .ok_or(ParseError::InvalidDummySpendAuthorizingKey)
149            })
150            .transpose()?;
151
152        Ok(Self {
153            cv,
154            nullifier,
155            rk,
156            zkproof,
157            spend_auth_sig,
158            recipient,
159            value,
160            rseed,
161            rcv,
162            proof_generation_key,
163            witness,
164            alpha,
165            zip32_derivation,
166            dummy_ask,
167            proprietary,
168        })
169    }
170}
171
172impl Output {
173    /// Parses a PCZT output from its component parts.
174    #[allow(clippy::too_many_arguments)]
175    pub fn parse(
176        cv: [u8; 32],
177        cmu: [u8; 32],
178        ephemeral_key: [u8; 32],
179        enc_ciphertext: Vec<u8>,
180        out_ciphertext: Vec<u8>,
181        zkproof: Option<GrothProofBytes>,
182        recipient: Option<[u8; 43]>,
183        value: Option<u64>,
184        rseed: Option<[u8; 32]>,
185        rcv: Option<[u8; 32]>,
186        ock: Option<[u8; 32]>,
187        zip32_derivation: Option<Zip32Derivation>,
188        user_address: Option<String>,
189        proprietary: BTreeMap<String, Vec<u8>>,
190    ) -> Result<Self, ParseError> {
191        let cv = ValueCommitment::from_bytes_not_small_order(&cv)
192            .into_option()
193            .ok_or(ParseError::InvalidValueCommitment)?;
194
195        let cmu = ExtractedNoteCommitment::from_bytes(&cmu)
196            .into_option()
197            .ok_or(ParseError::InvalidExtractedNoteCommitment)?;
198
199        let ephemeral_key = EphemeralKeyBytes(ephemeral_key);
200
201        let enc_ciphertext = enc_ciphertext
202            .as_slice()
203            .try_into()
204            .map_err(|_| ParseError::InvalidEncCiphertext)?;
205
206        let out_ciphertext = out_ciphertext
207            .as_slice()
208            .try_into()
209            .map_err(|_| ParseError::InvalidOutCiphertext)?;
210
211        let recipient = recipient
212            .as_ref()
213            .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient))
214            .transpose()?;
215
216        let value = value.map(NoteValue::from_raw);
217
218        let rcv = rcv
219            .map(|rcv| {
220                ValueCommitTrapdoor::from_bytes(rcv)
221                    .into_option()
222                    .ok_or(ParseError::InvalidValueCommitTrapdoor)
223            })
224            .transpose()?;
225
226        let ock = ock.map(OutgoingCipherKey);
227
228        Ok(Self {
229            cv,
230            cmu,
231            ephemeral_key,
232            enc_ciphertext,
233            out_ciphertext,
234            zkproof,
235            recipient,
236            value,
237            rseed,
238            rcv,
239            ock,
240            zip32_derivation,
241            user_address,
242            proprietary,
243        })
244    }
245}
246
247impl Zip32Derivation {
248    /// Parses a ZIP 32 derivation path from its component parts.
249    ///
250    /// Returns an error if any of the derivation path indices are non-hardened (which
251    /// this crate does not support, even though Sapling does).
252    pub fn parse(
253        seed_fingerprint: [u8; 32],
254        derivation_path: Vec<u32>,
255    ) -> Result<Self, ParseError> {
256        Ok(Self {
257            seed_fingerprint,
258            derivation_path: derivation_path
259                .into_iter()
260                .map(|i| ChildIndex::from_index(i).ok_or(ParseError::InvalidZip32Derivation))
261                .collect::<Result<_, _>>()?,
262        })
263    }
264}
265
266/// Errors that can occur while parsing a PCZT bundle.
267#[derive(Debug)]
268#[non_exhaustive]
269pub enum ParseError {
270    /// An invalid `anchor` was provided.
271    InvalidAnchor,
272    /// An invalid `bsk` was provided.
273    InvalidBindingSignatureSigningKey,
274    /// An invalid `dummy_ask` was provided.
275    InvalidDummySpendAuthorizingKey,
276    /// An invalid `enc_ciphertext` was provided.
277    InvalidEncCiphertext,
278    /// An invalid `cmu` was provided.
279    InvalidExtractedNoteCommitment,
280    /// An invalid `rcm` was provided.
281    InvalidNoteCommitRandomness,
282    /// An invalid `out_ciphertext` was provided.
283    InvalidOutCiphertext,
284    /// An invalid `proof_generation_key` was provided.
285    InvalidProofGenerationKey,
286    /// An invalid `rk` was provided.
287    InvalidRandomizedKey,
288    /// An invalid `recipient` was provided.
289    InvalidRecipient,
290    /// An invalid `alpha` was provided.
291    InvalidSpendAuthRandomizer,
292    /// An invalid `cv` was provided.
293    InvalidValueCommitment,
294    /// An invalid `rcv` was provided.
295    InvalidValueCommitTrapdoor,
296    /// An invalid `witness` was provided.
297    InvalidWitness,
298    /// An invalid `zip32_derivation` was provided.
299    InvalidZip32Derivation,
300    /// Both `rcm` and `rseed` were provided for a Spend.
301    MixedNoteCommitRandomnessAndRseed,
302}
303
304impl fmt::Display for ParseError {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        match self {
307            ParseError::InvalidAnchor => write!(f, "invalid anchor"),
308            ParseError::InvalidBindingSignatureSigningKey => write!(f, "invalid `bsk`"),
309            ParseError::InvalidDummySpendAuthorizingKey => write!(f, "invalid `dummy_ask`"),
310            ParseError::InvalidEncCiphertext => write!(f, "invalid `enc_ciphertext`"),
311            ParseError::InvalidExtractedNoteCommitment => write!(f, "invalid `cmu`"),
312            ParseError::InvalidNoteCommitRandomness => write!(f, "invalid `rcm`"),
313            ParseError::InvalidOutCiphertext => write!(f, "invalid `out_ciphertext`"),
314            ParseError::InvalidProofGenerationKey => write!(f, "invalid `proof_generation_key`"),
315            ParseError::InvalidRandomizedKey => write!(f, "invalid `rk`"),
316            ParseError::InvalidRecipient => write!(f, "invalid `recipient`"),
317            ParseError::InvalidSpendAuthRandomizer => write!(f, "invalid `alpha`"),
318            ParseError::InvalidValueCommitment => write!(f, "invalid `cv`"),
319            ParseError::InvalidValueCommitTrapdoor => write!(f, "invalid `rcv`"),
320            ParseError::InvalidWitness => write!(f, "invalid `witness`"),
321            ParseError::InvalidZip32Derivation => write!(f, "invalid `zip32_derivation`"),
322            ParseError::MixedNoteCommitRandomnessAndRseed => {
323                write!(f, "both `rcm` and `rseed` were provided for a spend")
324            }
325        }
326    }
327}
328
329#[cfg(feature = "std")]
330impl std::error::Error for ParseError {}