@@ -406,3 +406,116 @@ impl fmt::Display for ParseError {
406406
407407#[ cfg( feature = "std" ) ]
408408impl std:: error:: Error for ParseError { }
409+
410+ #[ cfg( all( test, feature = "hybrid-kem" ) ) ]
411+ mod tests {
412+ use super :: * ;
413+ use crate :: memo:: ZcashMemo ;
414+
415+ /// Helper: constructs minimal valid arguments for `Output::parse` (hybrid-kem mode).
416+ /// The cmx, enc_ciphertext, and out_ciphertext are syntactically valid but semantically
417+ /// meaningless — sufficient to reach the PQ field validation.
418+ fn valid_output_args ( ) -> (
419+ Nullifier ,
420+ [ u8 ; 32 ] ,
421+ [ u8 ; 32 ] ,
422+ Vec < u8 > ,
423+ Vec < u8 > ,
424+ [ u8 ; 1088 ] ,
425+ [ u8 ; 11 ] ,
426+ ) {
427+ use rand:: rngs:: OsRng ;
428+ let nf = Nullifier :: dummy ( & mut OsRng ) ;
429+ // Zero is a valid Pallas base field element
430+ let cmx_bytes = [ 0u8 ; 32 ] ;
431+ let ephemeral_key = [ 0u8 ; 32 ] ;
432+ let enc_ciphertext = vec ! [ 0u8 ; 580 ] ; // ZcashMemo NoteCiphertextBytes size
433+ let out_ciphertext = vec ! [ 0u8 ; 112 ] ; // hybrid out_ciphertext size
434+ let ct_pq = [ 0u8 ; 1088 ] ;
435+ let diversifier_hint = [ 0u8 ; 11 ] ;
436+ (
437+ nf,
438+ cmx_bytes,
439+ ephemeral_key,
440+ enc_ciphertext,
441+ out_ciphertext,
442+ ct_pq,
443+ diversifier_hint,
444+ )
445+ }
446+
447+ #[ test]
448+ fn output_parse_missing_pq_ciphertext ( ) {
449+ let ( nf, cmx, epk, enc, out, _ct_pq, div_hint) = valid_output_args ( ) ;
450+ let result = Output :: < ZcashMemo > :: parse (
451+ nf,
452+ cmx,
453+ epk,
454+ enc,
455+ out,
456+ None , // ct_pq missing
457+ Some ( div_hint) ,
458+ None ,
459+ None ,
460+ None ,
461+ None ,
462+ None ,
463+ None ,
464+ BTreeMap :: new ( ) ,
465+ ) ;
466+ assert ! (
467+ matches!( result, Err ( ParseError :: MissingPqCiphertext ) ) ,
468+ "parse must fail with MissingPqCiphertext when ct_pq is None"
469+ ) ;
470+ }
471+
472+ #[ test]
473+ fn output_parse_missing_diversifier_hint ( ) {
474+ let ( nf, cmx, epk, enc, out, ct_pq, _div_hint) = valid_output_args ( ) ;
475+ let result = Output :: < ZcashMemo > :: parse (
476+ nf,
477+ cmx,
478+ epk,
479+ enc,
480+ out,
481+ Some ( ct_pq) ,
482+ None , // diversifier_hint missing
483+ None ,
484+ None ,
485+ None ,
486+ None ,
487+ None ,
488+ None ,
489+ BTreeMap :: new ( ) ,
490+ ) ;
491+ assert ! (
492+ matches!( result, Err ( ParseError :: MissingDiversifierHint ) ) ,
493+ "parse must fail with MissingDiversifierHint when diversifier_hint is None"
494+ ) ;
495+ }
496+
497+ #[ test]
498+ fn output_parse_succeeds_with_all_pq_fields ( ) {
499+ let ( nf, cmx, epk, enc, out, ct_pq, div_hint) = valid_output_args ( ) ;
500+ let result = Output :: < ZcashMemo > :: parse (
501+ nf,
502+ cmx,
503+ epk,
504+ enc,
505+ out,
506+ Some ( ct_pq) ,
507+ Some ( div_hint) ,
508+ None ,
509+ None ,
510+ None ,
511+ None ,
512+ None ,
513+ None ,
514+ BTreeMap :: new ( ) ,
515+ ) ;
516+ assert ! (
517+ result. is_ok( ) ,
518+ "parse must succeed when all PQ fields are provided"
519+ ) ;
520+ }
521+ }
0 commit comments