Skip to content

Commit 78e4d8a

Browse files
sinsemilla::merkle.rs: Add test for MerkleChip.
1 parent 63e5d5c commit 78e4d8a

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed

src/circuit/gadget/sinsemilla/merkle.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,226 @@ impl<C: CurveAffine, const PATH_LENGTH: usize, const K: usize, const MAX_WORDS:
545545
SinsemillaChip::<C, K, MAX_WORDS>::extract(point)
546546
}
547547
}
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

Comments
 (0)