Skip to content

Commit ca15012

Browse files
Add test for MerkleChip
1 parent 5762e89 commit ca15012

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed

src/circuit/gadget/sinsemilla/merkle.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,225 @@ impl<C: CurveAffine, const PATH_LENGTH: usize, const K: usize, const MAX_WORDS:
589589
SinsemillaChip::<C, K, MAX_WORDS>::extract(point)
590590
}
591591
}
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

Comments
 (0)