sapling_crypto/pczt/
parse.rs

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