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