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(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 #[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 #[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 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#[derive(Debug)]
267#[non_exhaustive]
268pub enum ParseError {
269 InvalidAnchor,
271 InvalidBindingSignatureSigningKey,
273 InvalidDummySpendAuthorizingKey,
275 InvalidEncCiphertext,
277 InvalidExtractedNoteCommitment,
279 InvalidNoteCommitRandomness,
281 InvalidOutCiphertext,
283 InvalidProofGenerationKey,
285 InvalidRandomizedKey,
287 InvalidRecipient,
289 InvalidSpendAuthRandomizer,
291 InvalidValueCommitment,
293 InvalidValueCommitTrapdoor,
295 InvalidWitness,
297 InvalidZip32Derivation,
299 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 {}