@@ -545,3 +545,226 @@ impl<C: CurveAffine, const PATH_LENGTH: usize, const K: usize, const MAX_WORDS:
545
545
SinsemillaChip :: < C , K , MAX_WORDS > :: extract ( point)
546
546
}
547
547
}
548
+
549
+ #[ cfg( test) ]
550
+ pub mod tests {
551
+ use super :: { MerkleChip , MerkleConfig , MerkleInstructions } ;
552
+
553
+ use crate :: circuit:: gadget:: {
554
+ sinsemilla:: chip:: SinsemillaChip ,
555
+ utilities:: { UtilitiesInstructions , Var } ,
556
+ } ;
557
+ use crate :: constants:: {
558
+ util:: i2lebsp, L_ORCHARD_BASE , MERKLE_CRH_PERSONALIZATION , MERKLE_DEPTH_ORCHARD ,
559
+ } ;
560
+ use crate :: primitives:: sinsemilla:: { HashDomain , C , K } ;
561
+
562
+ use ff:: PrimeField ;
563
+ use halo2:: {
564
+ arithmetic:: { CurveAffine , FieldExt } ,
565
+ circuit:: { layouter:: SingleChipLayouter , Layouter } ,
566
+ dev:: MockProver ,
567
+ pasta:: pallas,
568
+ plonk:: { Assignment , Circuit , ConstraintSystem , Error } ,
569
+ } ;
570
+
571
+ use rand:: random;
572
+ use std:: { convert:: TryInto , marker:: PhantomData } ;
573
+
574
+ struct MyCircuit <
575
+ C : CurveAffine ,
576
+ const PATH_LENGTH : usize ,
577
+ const K : usize ,
578
+ const MAX_WORDS : usize ,
579
+ > {
580
+ leaf : ( Option < C :: Base > , Option < u32 > ) ,
581
+ merkle_path : Vec < Option < C :: Base > > ,
582
+ _marker : PhantomData < C > ,
583
+ }
584
+
585
+ impl < const PATH_LENGTH : usize , const K : usize , const MAX_WORDS : usize > Circuit < pallas:: Base >
586
+ for MyCircuit < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS >
587
+ {
588
+ type Config = (
589
+ MerkleConfig < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > ,
590
+ MerkleConfig < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > ,
591
+ ) ;
592
+
593
+ fn configure ( meta : & mut ConstraintSystem < pallas:: Base > ) -> Self :: Config {
594
+ let advices = [
595
+ meta. advice_column ( ) ,
596
+ meta. advice_column ( ) ,
597
+ meta. advice_column ( ) ,
598
+ meta. advice_column ( ) ,
599
+ meta. advice_column ( ) ,
600
+ meta. advice_column ( ) ,
601
+ meta. advice_column ( ) ,
602
+ meta. advice_column ( ) ,
603
+ meta. advice_column ( ) ,
604
+ meta. advice_column ( ) ,
605
+ ] ;
606
+
607
+ let perm = meta. permutation (
608
+ & advices
609
+ . iter ( )
610
+ . map ( |advice| ( * advice) . into ( ) )
611
+ . collect :: < Vec < _ > > ( ) ,
612
+ ) ;
613
+
614
+ // Fixed columns for the Sinsemilla generator lookup table
615
+ let lookup = (
616
+ meta. fixed_column ( ) ,
617
+ meta. fixed_column ( ) ,
618
+ meta. fixed_column ( ) ,
619
+ ) ;
620
+
621
+ let sinsemilla_config_1 = SinsemillaChip :: < pallas:: Affine , K , MAX_WORDS > :: configure (
622
+ meta,
623
+ advices[ ..5 ] . try_into ( ) . unwrap ( ) ,
624
+ lookup,
625
+ perm. clone ( ) ,
626
+ ) ;
627
+ let config1 = MerkleChip :: < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > :: configure (
628
+ meta,
629
+ sinsemilla_config_1,
630
+ ) ;
631
+
632
+ let sinsemilla_config_2 = SinsemillaChip :: < pallas:: Affine , K , MAX_WORDS > :: configure (
633
+ meta,
634
+ advices[ ..5 ] . try_into ( ) . unwrap ( ) ,
635
+ lookup,
636
+ perm,
637
+ ) ;
638
+ let config2 = MerkleChip :: < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > :: configure (
639
+ meta,
640
+ sinsemilla_config_2,
641
+ ) ;
642
+
643
+ ( config1, config2)
644
+ }
645
+
646
+ fn synthesize (
647
+ & self ,
648
+ cs : & mut impl Assignment < pallas:: Base > ,
649
+ config : Self :: Config ,
650
+ ) -> Result < ( ) , Error > {
651
+ let mut layouter = SingleChipLayouter :: new ( cs) ?;
652
+
653
+ // Load generator table (shared across both configs)
654
+ SinsemillaChip :: < pallas:: Affine , K , MAX_WORDS > :: load (
655
+ config. 0 . sinsemilla_config . clone ( ) ,
656
+ & mut layouter,
657
+ ) ?;
658
+
659
+ // Construct Merkle chips which will be placed side-by-side in the circuit.
660
+ let merkle_chip_1 = MerkleChip :: < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > :: construct (
661
+ config. 0 . clone ( ) ,
662
+ ) ;
663
+ let merkle_chip_2 = MerkleChip :: < pallas:: Affine , PATH_LENGTH , K , MAX_WORDS > :: construct (
664
+ config. 1 . clone ( ) ,
665
+ ) ;
666
+
667
+ // Process lo half of the Merkle path from leaf to intermediate root.
668
+ let leaf = merkle_chip_1. load_private (
669
+ layouter. namespace ( || "" ) ,
670
+ config. 0 . cond_swap_config . x ,
671
+ self . leaf . 0 ,
672
+ ) ?;
673
+ let pos_lo = self . leaf . 1 . map ( |pos| pos & ( ( 1 << PATH_LENGTH ) - 1 ) ) ;
674
+
675
+ let intermediate_root = merkle_chip_1. merkle_path_check (
676
+ layouter. namespace ( || "" ) ,
677
+ 0 ,
678
+ ( leaf, pos_lo) ,
679
+ self . merkle_path [ 0 ..PATH_LENGTH ] . to_vec ( ) ,
680
+ ) ?;
681
+
682
+ // Process hi half of the Merkle path from intermediate root to root.
683
+ let pos_hi = self . leaf . 1 . map ( |pos| pos >> ( PATH_LENGTH ) ) ;
684
+
685
+ let computed_final_root = merkle_chip_2. merkle_path_check (
686
+ layouter. namespace ( || "" ) ,
687
+ PATH_LENGTH ,
688
+ ( intermediate_root, pos_hi) ,
689
+ self . merkle_path [ PATH_LENGTH ..] . to_vec ( ) ,
690
+ ) ?;
691
+
692
+ // The expected final root
693
+ let pos_bool = i2lebsp :: < 32 > ( self . leaf . 1 . unwrap ( ) ) ;
694
+ let path: Option < Vec < pallas:: Base > > = self . merkle_path . to_vec ( ) . into_iter ( ) . collect ( ) ;
695
+ let final_root = hash_path ( 0 , self . leaf . 0 . unwrap ( ) , & pos_bool, & path. unwrap ( ) ) ;
696
+
697
+ // Check the computed final root against the expected final root.
698
+ assert_eq ! ( computed_final_root. value( ) . unwrap( ) , final_root) ;
699
+
700
+ Ok ( ( ) )
701
+ }
702
+ }
703
+
704
+ fn hash_path (
705
+ offset : usize ,
706
+ leaf : pallas:: Base ,
707
+ pos_bool : & [ bool ] ,
708
+ path : & [ pallas:: Base ] ,
709
+ ) -> pallas:: Base {
710
+ let domain = HashDomain :: new ( MERKLE_CRH_PERSONALIZATION ) ;
711
+
712
+ // Compute the root
713
+ let mut node = leaf;
714
+ for ( l_star, ( sibling, pos) ) in path. iter ( ) . zip ( pos_bool. iter ( ) ) . enumerate ( ) {
715
+ let l_star = l_star + offset;
716
+
717
+ let ( left, right) = if * pos {
718
+ ( * sibling, node)
719
+ } else {
720
+ ( node, * sibling)
721
+ } ;
722
+
723
+ let l_star = i2lebsp :: < 10 > ( l_star as u32 ) ;
724
+ let left: Vec < _ > = left
725
+ . to_le_bits ( )
726
+ . iter ( )
727
+ . by_val ( )
728
+ . take ( L_ORCHARD_BASE )
729
+ . collect ( ) ;
730
+ let right: Vec < _ > = right
731
+ . to_le_bits ( )
732
+ . iter ( )
733
+ . by_val ( )
734
+ . take ( L_ORCHARD_BASE )
735
+ . collect ( ) ;
736
+
737
+ let mut message = l_star. to_vec ( ) ;
738
+ message. extend_from_slice ( & left) ;
739
+ message. extend_from_slice ( & right) ;
740
+
741
+ node = domain. hash ( message. into_iter ( ) ) . unwrap ( ) ;
742
+ }
743
+ node
744
+ }
745
+
746
+ #[ test]
747
+ fn merkle_chip ( ) {
748
+ // Choose a random leaf and position
749
+ let leaf = pallas:: Base :: rand ( ) ;
750
+ let pos = random :: < u32 > ( ) ;
751
+ let pos_bool = i2lebsp :: < 32 > ( pos) ;
752
+
753
+ // Choose a path of random inner nodes
754
+ let path: Vec < _ > = ( 0 ..( MERKLE_DEPTH_ORCHARD ) )
755
+ . map ( |_| pallas:: Base :: rand ( ) )
756
+ . collect ( ) ;
757
+
758
+ // This root is provided as a public input in the Orchard circuit.
759
+ let _root = hash_path ( 0 , leaf, & pos_bool, & path) ;
760
+
761
+ let circuit = MyCircuit :: < pallas:: Affine , { MERKLE_DEPTH_ORCHARD / 2 } , K , C > {
762
+ leaf : ( Some ( leaf) , Some ( pos) ) ,
763
+ merkle_path : path. into_iter ( ) . map ( Some ) . collect ( ) ,
764
+ _marker : PhantomData ,
765
+ } ;
766
+
767
+ let prover = MockProver :: run ( 11 , & circuit, vec ! [ ] ) . unwrap ( ) ;
768
+ assert_eq ! ( prover. verify( ) , Ok ( ( ) ) )
769
+ }
770
+ }
0 commit comments