diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 9cde750c..511f1b7a 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -9,6 +9,7 @@ use pasta_curves::arithmetic::{CurveAffine, FieldExt}; use std::{convert::TryInto, fmt::Debug}; pub mod chip; +pub mod merkle; mod message; /// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget. diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index fcffc2ba..372b6234 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -72,6 +72,13 @@ pub struct SinsemillaConfig { pub(super) lookup_config_4: LookupRangeCheckConfig, } +impl SinsemillaConfig { + /// Returns an array of all advice columns in this config, in arbitrary order. + pub(super) fn advices(&self) -> [Column; 5] { + [self.x_a, self.x_p, self.bits, self.lambda_1, self.lambda_2] + } +} + #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChip { config: SinsemillaConfig, diff --git a/src/circuit/gadget/sinsemilla/merkle.rs b/src/circuit/gadget/sinsemilla/merkle.rs new file mode 100644 index 00000000..91883281 --- /dev/null +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -0,0 +1,141 @@ +use halo2::{ + circuit::{Chip, Layouter}, + plonk::Error, +}; +use pasta_curves::arithmetic::CurveAffine; + +use super::{HashDomains, SinsemillaInstructions}; + +use crate::{ + circuit::gadget::utilities::{cond_swap::CondSwapInstructions, UtilitiesInstructions}, + spec::i2lebsp, +}; +use std::{convert::TryInto, iter}; + +// mod chip; + +/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`. +/// The hash function used is a Sinsemilla instance with `K`-bit words. +/// The hash function can process `MAX_WORDS` words. +pub trait MerkleInstructions< + C: CurveAffine, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, +>: + SinsemillaInstructions + + CondSwapInstructions + + UtilitiesInstructions + + Chip +{ + /// Compute MerkleCRH for a given `layer`. The root is at `layer 0`, and the + /// leaves are at `layer MERKLE_DEPTH_ORCHARD` = `layer 32`. + #[allow(non_snake_case)] + fn hash_layer( + &self, + layouter: impl Layouter, + Q: C, + l_star: usize, + left: Self::Var, + right: Self::Var, + ) -> Result; +} + +#[derive(Clone, Debug)] +pub struct MerklePath< + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, +> where + MerkleChip: MerkleInstructions + Clone, +{ + chip_1: MerkleChip, + chip_2: MerkleChip, + domain: MerkleChip::HashDomains, + leaf_pos: Option, + path: Option<[C::Base; PATH_LENGTH]>, +} + +#[allow(non_snake_case)] +impl< + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + > MerklePath +where + MerkleChip: MerkleInstructions + Clone, +{ + /// Calculates the root of the tree containing the given leaf at this Merkle path. + fn calculate_root( + &self, + mut layouter: impl Layouter, + leaf: MerkleChip::Var, + ) -> Result { + // A Sinsemilla chip uses 5 advice columns, but the full Orchard action circuit + // uses 10 advice columns. We distribute the path hashing across two Sinsemilla + // chips to make better use of the available circuit area. + let chips = iter::empty() + .chain(iter::repeat(self.chip_1.clone()).take(PATH_LENGTH / 2)) + .chain(iter::repeat(self.chip_2.clone())); + + let path = if let Some(path) = self.path { + path.iter() + .map(|node| Some(*node)) + .collect::>() + .try_into() + .unwrap() + } else { + [None; PATH_LENGTH] + }; + + // Get position as a PATH_LENGTH-bit bitstring (little-endian bit order). + let pos: [Option; PATH_LENGTH] = { + let pos: Option<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64)); + let pos: [Option; PATH_LENGTH] = if let Some(pos) = pos { + pos.iter() + .map(|pos| Some(*pos)) + .collect::>() + .try_into() + .unwrap() + } else { + [None; PATH_LENGTH] + }; + pos + }; + + let Q = self.domain.Q(); + + let mut node = leaf; + for (l_star, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() { + // `l_star` = MERKLE_DEPTH_ORCHARD - layer - 1, which is the index obtained from + // enumerating this Merkle path (going from leaf to root). + // For example, when `layer = 31` (the first sibling on the Merkle path), + // we have `l_star` = 32 - 31 - 1 = 0. + // On the other hand, when `layer = 0` (the final sibling on the Merkle path), + // we have `l_star` = 32 - 0 - 1 = 31. + let pair = { + let pair = (node, *sibling); + + // Swap node and sibling if needed + chip.swap(layouter.namespace(|| "node position"), pair, *pos)? + }; + + // Each `hash_layer` consists of 52 Sinsemilla words: + // - l_star (10 bits) = 1 word + // - left (255 bits) || right (255 bits) = 51 words (510 bits) + node = chip.hash_layer( + layouter.namespace(|| format!("hash l_star {}", l_star)), + Q, + l_star, + pair.0, + pair.1, + )?; + } + + Ok(node) + } +}