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 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 #[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 #[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 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#[derive(Debug)]
268#[non_exhaustive]
269pub enum ParseError {
270 InvalidAnchor,
272 InvalidBindingSignatureSigningKey,
274 InvalidDummySpendAuthorizingKey,
276 InvalidEncCiphertext,
278 InvalidExtractedNoteCommitment,
280 InvalidNoteCommitRandomness,
282 InvalidOutCiphertext,
284 InvalidProofGenerationKey,
286 InvalidRandomizedKey,
288 InvalidRecipient,
290 InvalidSpendAuthRandomizer,
292 InvalidValueCommitment,
294 InvalidValueCommitTrapdoor,
296 InvalidWitness,
298 InvalidZip32Derivation,
300 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 {}