1use alloc::vec::Vec;
16
17use serde::{Deserialize, Serialize};
18
19use super::SpendAuthSignature;
20use crate::Pczt;
21
22pub const VERSION: u32 = 1;
24
25const REQUEST_MAGIC_BYTES: &[u8; 4] = b"PCZB"; const RESPONSE_MAGIC_BYTES: &[u8; 4] = b"PCZS"; #[derive(Clone, Debug)]
34pub struct BatchSignRequest {
35 pczts: Vec<Pczt>,
36}
37
38impl BatchSignRequest {
39 pub fn new(pczts: Vec<Pczt>) -> Self {
41 Self { pczts }
42 }
43
44 pub fn pczts(&self) -> &[Pczt] {
46 &self.pczts
47 }
48
49 pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
56 let body = parse_version(bytes, REQUEST_MAGIC_BYTES)?;
57 let (pczt_version, body) = parse_pczt_version(body)?;
58 match pczt_version {
59 crate::PCZT_VERSION_1 => {
60 Ok(parse_body::<v1::BatchSignRequest<crate::v1::Pczt>>(body)?.into())
61 }
62 crate::PCZT_VERSION_2 => {
63 Self::try_from(parse_body::<v1::BatchSignRequest<crate::v2::Pczt>>(body)?)
64 }
65 _ => Err(ParseError::UnknownPcztVersion(pczt_version)),
66 }
67 }
68
69 pub fn serialize(&self) -> Result<Vec<u8>, EncodingError> {
76 Ok(serialize_request(
77 crate::PCZT_VERSION_2,
78 &v1::BatchSignRequest::<crate::v2::Pczt>::try_from(self)?,
79 ))
80 }
81}
82
83#[derive(Clone, Debug, Eq, PartialEq)]
89pub struct BatchSignResponse {
90 signatures: Vec<Vec<SpendAuthSignature>>,
91}
92
93impl BatchSignResponse {
94 pub fn new(signatures: Vec<Vec<SpendAuthSignature>>) -> Self {
96 Self { signatures }
97 }
98
99 pub fn signatures(&self) -> &[Vec<SpendAuthSignature>] {
102 &self.signatures
103 }
104
105 pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
107 let body = parse_version(bytes, RESPONSE_MAGIC_BYTES)?;
108 let response = parse_body::<v1::BatchSignResponse>(body)?;
109 Self::try_from(response)
110 }
111
112 pub fn serialize(&self) -> Result<Vec<u8>, EncodingError> {
114 Ok(serialize_versioned(
115 RESPONSE_MAGIC_BYTES,
116 &v1::BatchSignResponse::try_from(self)?,
117 ))
118 }
119}
120
121#[derive(Debug)]
123#[non_exhaustive]
124pub enum EncodingError {
125 ActionIndexOutOfRange,
127 PcztEncoding(crate::EncodingError),
129}
130
131#[derive(Debug)]
133#[non_exhaustive]
134pub enum ParseError {
135 Invalid(postcard::Error),
137 InvalidMagic,
139 ActionIndexOutOfRange,
141 PcztParse(crate::ParseError),
143 TrailingData,
145 TooShort,
147 UnknownPcztVersion(u32),
149 UnknownVersion(u32),
151}
152
153fn parse_version<'a>(bytes: &'a [u8], magic: &[u8; 4]) -> Result<&'a [u8], ParseError> {
154 let (version, body) = crate::parse_header(bytes, magic).map_err(|e| match e {
155 crate::HeaderParseError::InvalidMagic => ParseError::InvalidMagic,
156 crate::HeaderParseError::TooShort => ParseError::TooShort,
157 })?;
158 if version != VERSION {
159 return Err(ParseError::UnknownVersion(version));
160 }
161 Ok(body)
162}
163
164fn parse_pczt_version(bytes: &[u8]) -> Result<(u32, &[u8]), ParseError> {
165 let version = bytes
166 .get(..4)
167 .ok_or(ParseError::TooShort)
168 .map(|version| u32::from_le_bytes(version.try_into().unwrap()))?;
169 Ok((version, &bytes[4..]))
170}
171
172fn parse_body<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, ParseError> {
173 let (value, remaining) = postcard::take_from_bytes(bytes).map_err(ParseError::Invalid)?;
174 if !remaining.is_empty() {
175 return Err(ParseError::TrailingData);
176 }
177 Ok(value)
178}
179
180fn serialize_request<T: Serialize>(pczt_version: u32, value: &T) -> Vec<u8> {
181 let mut bytes = crate::serialize_header(REQUEST_MAGIC_BYTES, VERSION);
182 bytes.extend_from_slice(&pczt_version.to_le_bytes());
183 postcard::to_extend(value, bytes).expect("can serialize into memory")
184}
185
186fn serialize_versioned<T: Serialize>(magic: &[u8; 4], value: &T) -> Vec<u8> {
187 let bytes = crate::serialize_header(magic, VERSION);
188 postcard::to_extend(value, bytes).expect("can serialize into memory")
189}
190
191mod v1 {
192 use super::*;
193 use serde_with::serde_as;
194
195 #[derive(Deserialize, Serialize)]
196 pub(super) struct BatchSignRequest<P> {
197 pub(super) pczts: Vec<P>,
198 }
199
200 #[derive(Deserialize, Serialize)]
201 pub(super) struct BatchSignResponse {
202 pub(super) signatures: Vec<Vec<SpendAuthSignature>>,
203 }
204
205 #[derive(Deserialize, Serialize)]
206 pub(super) enum ValuePool {
207 Orchard,
208 Ironwood,
209 }
210
211 #[serde_as]
212 #[derive(Deserialize, Serialize)]
213 pub(super) struct SpendAuthSignature {
214 pub(super) value_pool: ValuePool,
215 pub(super) action_index: u32,
216 #[serde_as(as = "[_; 64]")]
217 pub(super) signature: [u8; 64],
218 }
219
220 impl TryFrom<&super::BatchSignRequest> for BatchSignRequest<crate::v2::Pczt> {
221 type Error = EncodingError;
222
223 fn try_from(request: &super::BatchSignRequest) -> Result<Self, Self::Error> {
224 Ok(Self {
225 pczts: request
226 .pczts
227 .iter()
228 .cloned()
229 .map(|pczt| {
230 crate::v2::Pczt::try_from(pczt).map_err(EncodingError::PcztEncoding)
231 })
232 .collect::<Result<_, _>>()?,
233 })
234 }
235 }
236
237 impl From<BatchSignRequest<crate::v1::Pczt>> for super::BatchSignRequest {
238 fn from(request: BatchSignRequest<crate::v1::Pczt>) -> Self {
239 Self {
240 pczts: request.pczts.into_iter().map(Into::into).collect(),
241 }
242 }
243 }
244
245 impl TryFrom<BatchSignRequest<crate::v2::Pczt>> for super::BatchSignRequest {
246 type Error = ParseError;
247
248 fn try_from(request: BatchSignRequest<crate::v2::Pczt>) -> Result<Self, Self::Error> {
249 Ok(Self {
250 pczts: request
251 .pczts
252 .into_iter()
253 .map(|pczt| pczt.into_logical().map_err(ParseError::PcztParse))
254 .collect::<Result<_, _>>()?,
255 })
256 }
257 }
258
259 impl TryFrom<&super::BatchSignResponse> for BatchSignResponse {
260 type Error = EncodingError;
261
262 fn try_from(response: &super::BatchSignResponse) -> Result<Self, Self::Error> {
263 Ok(Self {
264 signatures: response
265 .signatures
266 .iter()
267 .map(|signatures| {
268 signatures
269 .iter()
270 .map(SpendAuthSignature::try_from)
271 .collect::<Result<_, _>>()
272 })
273 .collect::<Result<_, EncodingError>>()?,
274 })
275 }
276 }
277
278 impl TryFrom<BatchSignResponse> for super::BatchSignResponse {
279 type Error = ParseError;
280
281 fn try_from(response: BatchSignResponse) -> Result<Self, Self::Error> {
282 Ok(Self {
283 signatures: response
284 .signatures
285 .into_iter()
286 .map(|signatures| {
287 signatures
288 .into_iter()
289 .map(super::SpendAuthSignature::try_from)
290 .collect::<Result<_, _>>()
291 })
292 .collect::<Result<_, ParseError>>()?,
293 })
294 }
295 }
296
297 impl TryFrom<&super::SpendAuthSignature> for SpendAuthSignature {
298 type Error = EncodingError;
299
300 fn try_from(signature: &super::SpendAuthSignature) -> Result<Self, Self::Error> {
301 Ok(Self {
302 value_pool: match signature.value_pool() {
303 orchard::ValuePool::Orchard => ValuePool::Orchard,
304 orchard::ValuePool::Ironwood => ValuePool::Ironwood,
305 },
306 action_index: u32::try_from(signature.action_index())
307 .map_err(|_| EncodingError::ActionIndexOutOfRange)?,
308 signature: *signature.signature(),
309 })
310 }
311 }
312
313 impl TryFrom<SpendAuthSignature> for super::SpendAuthSignature {
314 type Error = ParseError;
315
316 fn try_from(signature: SpendAuthSignature) -> Result<Self, Self::Error> {
317 let value_pool = match signature.value_pool {
318 ValuePool::Orchard => orchard::ValuePool::Orchard,
319 ValuePool::Ironwood => orchard::ValuePool::Ironwood,
320 };
321 let action_index = usize::try_from(signature.action_index)
322 .map_err(|_| ParseError::ActionIndexOutOfRange)?;
323 Ok(Self::from_parts(
324 value_pool,
325 action_index,
326 signature.signature,
327 ))
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use serde_with::serde_as;
336 use zcash_protocol::consensus::BranchId;
337
338 use crate::roles::creator::Creator;
339
340 fn empty_pczt(expiry_height: u32) -> Pczt {
341 Creator::new(BranchId::Nu6_3.into(), expiry_height, 133, None, None)
342 .unwrap()
343 .build()
344 .unwrap()
345 }
346
347 fn v5_pczt(expiry_height: u32) -> Pczt {
348 Creator::new(
349 BranchId::Nu6.into(),
350 expiry_height,
351 133,
352 Some([0; 32]),
353 Some([0; 32]),
354 )
355 .unwrap()
356 .build()
357 .unwrap()
358 }
359
360 #[test]
361 fn request_round_trip() {
362 let request = BatchSignRequest::new(vec![empty_pczt(10_000_000), empty_pczt(10_000_001)]);
363
364 let expected_pczts = request
365 .pczts()
366 .iter()
367 .cloned()
368 .map(|pczt| pczt.serialize().unwrap())
369 .collect::<Vec<_>>();
370 let expected_body = postcard::to_allocvec(
371 &v1::BatchSignRequest::<crate::v2::Pczt>::try_from(&request).unwrap(),
372 )
373 .unwrap();
374 let encoded = request.serialize().unwrap();
375 let decoded = BatchSignRequest::parse(&encoded).unwrap();
376
377 assert_eq!(&encoded[..4], REQUEST_MAGIC_BYTES);
378 assert_eq!(
379 u32::from_le_bytes(encoded[4..8].try_into().unwrap()),
380 VERSION
381 );
382 assert_eq!(
383 u32::from_le_bytes(encoded[8..12].try_into().unwrap()),
384 crate::PCZT_VERSION_2
385 );
386 assert_eq!(&encoded[12..], expected_body);
387 assert_eq!(decoded.pczts().len(), expected_pczts.len());
388 for (decoded, expected) in decoded.pczts().iter().zip(expected_pczts) {
389 assert_eq!(decoded.clone().serialize().unwrap(), expected);
390 }
391 }
392
393 #[test]
394 fn request_parse_accepts_v1_pczt_payloads() {
395 let pczt = v5_pczt(10_000_000);
396 let expected = crate::v1::Pczt::try_from(pczt.clone()).unwrap().serialize();
397 let wire = v1::BatchSignRequest {
398 pczts: vec![crate::v1::Pczt::try_from(pczt).unwrap()],
399 };
400
401 let encoded = serialize_request(crate::PCZT_VERSION_1, &wire);
402 let decoded = BatchSignRequest::parse(&encoded).unwrap();
403
404 assert_eq!(decoded.pczts().len(), 1);
405 assert_eq!(
406 crate::v1::Pczt::try_from(decoded.pczts()[0].clone())
407 .unwrap()
408 .serialize(),
409 expected
410 );
411 }
412
413 #[test]
414 fn request_parse_rejects_invalid_body() {
415 let mut encoded = crate::serialize_header(REQUEST_MAGIC_BYTES, VERSION);
416 encoded.extend_from_slice(&crate::PCZT_VERSION_2.to_le_bytes());
417 encoded.push(1);
418
419 assert!(matches!(
420 BatchSignRequest::parse(&encoded),
421 Err(ParseError::Invalid(_))
422 ));
423 }
424
425 #[test]
426 fn response_round_trip() {
427 let response = BatchSignResponse::new(vec![
428 vec![SpendAuthSignature::from_parts(
429 orchard::ValuePool::Orchard,
430 0,
431 [0x11; 64],
432 )],
433 vec![SpendAuthSignature::from_parts(
434 orchard::ValuePool::Ironwood,
435 12,
436 [0x22; 64],
437 )],
438 ]);
439
440 let encoded = response.serialize().unwrap();
441
442 assert_eq!(&encoded[..4], RESPONSE_MAGIC_BYTES);
443 assert_eq!(
444 u32::from_le_bytes(encoded[4..8].try_into().unwrap()),
445 VERSION
446 );
447 assert_eq!(BatchSignResponse::parse(&encoded).unwrap(), response);
448 }
449
450 #[test]
451 fn parse_rejects_unknown_version() {
452 let encoded = crate::serialize_header(REQUEST_MAGIC_BYTES, VERSION + 1);
453 assert!(matches!(
454 BatchSignRequest::parse(&encoded),
455 Err(ParseError::UnknownVersion(2))
456 ));
457 }
458
459 #[test]
460 fn request_parse_rejects_unknown_pczt_version_before_parsing_body() {
461 let mut encoded = crate::serialize_header(REQUEST_MAGIC_BYTES, VERSION);
462 encoded.extend_from_slice(&(crate::PCZT_VERSION_2 + 1).to_le_bytes());
463 encoded.extend_from_slice(b"not a Postcard body");
464
465 assert!(matches!(
466 BatchSignRequest::parse(&encoded),
467 Err(ParseError::UnknownPcztVersion(3))
468 ));
469 }
470
471 #[test]
472 fn request_and_response_use_distinct_magic_bytes() {
473 let request = BatchSignRequest::new(vec![]).serialize().unwrap();
474 let response = BatchSignResponse::new(vec![]).serialize().unwrap();
475
476 assert!(matches!(
477 BatchSignResponse::parse(&request),
478 Err(ParseError::InvalidMagic)
479 ));
480 assert!(matches!(
481 BatchSignRequest::parse(&response),
482 Err(ParseError::InvalidMagic)
483 ));
484 }
485
486 #[test]
487 fn parse_rejects_short_header() {
488 assert!(matches!(
489 BatchSignRequest::parse(&[0; 7]),
490 Err(ParseError::TooShort)
491 ));
492 }
493
494 #[test]
495 fn parse_rejects_trailing_data() {
496 let mut encoded = BatchSignRequest::new(vec![]).serialize().unwrap();
497 encoded.push(0);
498 assert!(matches!(
499 BatchSignRequest::parse(&encoded),
500 Err(ParseError::TrailingData)
501 ));
502 }
503
504 #[test]
505 fn response_parse_rejects_unknown_value_pool() {
506 #[derive(Serialize)]
507 struct RawBatchSignResponse {
508 signatures: Vec<Vec<RawSpendAuthSignature>>,
509 }
510
511 #[serde_as]
512 #[derive(Serialize)]
513 struct RawSpendAuthSignature {
514 value_pool: u8,
515 action_index: u32,
516 #[serde_as(as = "[_; 64]")]
517 signature: [u8; 64],
518 }
519
520 let wire = RawBatchSignResponse {
521 signatures: vec![vec![RawSpendAuthSignature {
522 value_pool: 2,
523 action_index: 0,
524 signature: [0; 64],
525 }]],
526 };
527 let encoded = serialize_versioned(RESPONSE_MAGIC_BYTES, &wire);
528 assert!(matches!(
529 BatchSignResponse::parse(&encoded),
530 Err(ParseError::Invalid(_))
531 ));
532 }
533}