diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 9cde750c3..511f1b7a1 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 fcffc2ba4..b1452baff 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -61,7 +61,7 @@ pub struct SinsemillaConfig { /// Fixed column shared by the whole circuit. This is used to load the /// x-coordinate of the domain $Q$, which is then constrained to equal the /// initial $x_a$. - constants: Column, + pub(super) constants: Column, /// Permutation over all advice columns and the `constants` fixed column. pub(super) perm: Permutation, /// Configure each advice column to be able to perform lookup range checks. @@ -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 000000000..85db6c220 --- /dev/null +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -0,0 +1,342 @@ +use halo2::{ + circuit::{Chip, Layouter}, + plonk::Error, +}; +use pasta_curves::arithmetic::CurveAffine; + +use super::{HashDomains, SinsemillaInstructions}; + +use crate::{ + circuit::gadget::utilities::{ + cond_swap::CondSwapInstructions, transpose_option_array, UtilitiesInstructions, + }, + spec::i2lebsp, +}; +use std::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 hash that computes the root + /// is at layer 0, and the hashes that are applied to two leaves are at + /// layer `MERKLE_DEPTH_ORCHARD - 1` = layer 31. + #[allow(non_snake_case)] + fn hash_layer( + &self, + layouter: impl Layouter, + Q: C, + l: 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, + // The Merkle path is ordered from leaves to root. + 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())); + + // The Merkle path is ordered from leaves to root, which is consistent with the + // little-endian representation of `pos` below. + let path = transpose_option_array(self.path); + + // 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)); + transpose_option_array(pos) + }; + + let Q = self.domain.Q(); + + let mut node = leaf; + for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() { + // `l` = 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` = 32 - 31 - 1 = 0. + // On the other hand, when `layer = 0` (the final sibling on the Merkle path), + // we have `l` = 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 (10 bits) = 1 word + // - left (255 bits) || right (255 bits) = 51 words (510 bits) + node = chip.hash_layer( + layouter.namespace(|| format!("hash l {}", l)), + Q, + l, + pair.0, + pair.1, + )?; + } + + Ok(node) + } +} + +#[cfg(test)] +pub mod tests { + use super::{ + chip::{MerkleChip, MerkleConfig}, + MerklePath, + }; + + use crate::{ + circuit::gadget::{ + sinsemilla::chip::{SinsemillaChip, SinsemillaHashDomains}, + utilities::{UtilitiesInstructions, Var}, + }, + constants::{L_ORCHARD_BASE, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + primitives::sinsemilla::HashDomain, + spec::i2lebsp, + }; + + use ff::PrimeFieldBits; + use halo2::{ + arithmetic::FieldExt, + circuit::{layouter::SingleChipLayouter, Layouter}, + dev::MockProver, + pasta::pallas, + plonk::{Assignment, Circuit, ConstraintSystem, Error}, + }; + + use rand::random; + use std::convert::TryInto; + + struct MyCircuit { + leaf: Option, + leaf_pos: Option, + merkle_path: Option<[pallas::Base; MERKLE_DEPTH_ORCHARD]>, + } + + impl Circuit for MyCircuit { + type Config = (MerkleConfig, MerkleConfig); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + // Shared fixed column for loading constants + // TODO: Replace with public inputs API + let constants_1 = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + let constants_2 = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + + let perm = meta.permutation( + &advices + .iter() + .map(|advice| (*advice).into()) + .chain(constants_1.iter().map(|fixed| (*fixed).into())) + .chain(constants_2.iter().map(|fixed| (*fixed).into())) + .collect::>(), + ); + + // Fixed columns for the Sinsemilla generator lookup table + let lookup = ( + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ); + + let sinsemilla_config_1 = SinsemillaChip::configure( + meta, + advices[5..].try_into().unwrap(), + lookup, + constants_1, + perm.clone(), + ); + let config1 = MerkleChip::configure(meta, sinsemilla_config_1); + + let sinsemilla_config_2 = SinsemillaChip::configure( + meta, + advices[..5].try_into().unwrap(), + lookup, + constants_2, + perm, + ); + let config2 = MerkleChip::configure(meta, sinsemilla_config_2); + + (config1, config2) + } + + fn synthesize( + &self, + cs: &mut impl Assignment, + config: Self::Config, + ) -> Result<(), Error> { + let mut layouter = SingleChipLayouter::new(cs)?; + + // Load generator table (shared across both configs) + SinsemillaChip::load(config.0.sinsemilla_config.clone(), &mut layouter)?; + + // Construct Merkle chips which will be placed side-by-side in the circuit. + let chip_1 = MerkleChip::construct(config.0.clone()); + let chip_2 = MerkleChip::construct(config.1.clone()); + + let leaf = chip_1.load_private( + layouter.namespace(|| ""), + config.0.cond_swap_config.a, + self.leaf, + )?; + + let path = MerklePath { + chip_1, + chip_2, + domain: SinsemillaHashDomains::MerkleCrh, + leaf_pos: self.leaf_pos, + path: self.merkle_path, + }; + + let computed_final_root = + path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?; + + // The expected final root + let pos_bool = i2lebsp::<32>(self.leaf_pos.unwrap() as u64); + let path: Option> = self.merkle_path.map(|path| path.to_vec()); + let final_root = hash_path(self.leaf.unwrap(), &pos_bool, &path.unwrap()); + + // Check the computed final root against the expected final root. + assert_eq!(computed_final_root.value().unwrap(), final_root); + + Ok(()) + } + } + + fn hash_path(leaf: pallas::Base, pos_bool: &[bool], path: &[pallas::Base]) -> pallas::Base { + let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); + + // Compute the root + let mut node = leaf; + for (l, (sibling, pos)) in path.iter().zip(pos_bool.iter()).enumerate() { + let (left, right) = if *pos { + (*sibling, node) + } else { + (node, *sibling) + }; + + let l_star = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_BASE) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_BASE) + .collect(); + + let mut message = l_star.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + node = domain.hash(message.into_iter()).unwrap(); + } + node + } + + #[test] + fn merkle_chip() { + // Choose a random leaf and position + let leaf = pallas::Base::rand(); + let pos = random::(); + let pos_bool = i2lebsp::<32>(pos as u64); + + // Choose a path of random inner nodes + let path: Vec<_> = (0..(MERKLE_DEPTH_ORCHARD)) + .map(|_| pallas::Base::rand()) + .collect(); + + // This root is provided as a public input in the Orchard circuit. + let _root = hash_path(leaf, &pos_bool, &path); + + let circuit = MyCircuit { + leaf: Some(leaf), + leaf_pos: Some(pos), + merkle_path: Some(path.try_into().unwrap()), + }; + + let prover = MockProver::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } +} diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs new file mode 100644 index 000000000..e86d85a8a --- /dev/null +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -0,0 +1,523 @@ +use halo2::{ + circuit::{Chip, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation}, + poly::Rotation, +}; +use pasta_curves::{arithmetic::FieldExt, pallas}; + +use super::super::{ + chip::{SinsemillaChip, SinsemillaConfig}, + SinsemillaInstructions, +}; +use super::MerkleInstructions; + +use crate::{ + circuit::gadget::utilities::{ + cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, + copy, CellValue, UtilitiesInstructions, Var, + }, + constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}, + primitives::sinsemilla, +}; +use ff::PrimeFieldBits; +use std::{array, convert::TryInto}; + +#[derive(Clone, Debug)] +pub struct MerkleConfig { + advices: [Column; 5], + l_plus_1: Column, + perm: Permutation, + pub(super) cond_swap_config: CondSwapConfig, + pub(super) sinsemilla_config: SinsemillaConfig, +} + +#[derive(Clone, Debug)] +pub struct MerkleChip { + config: MerkleConfig, +} + +impl Chip for MerkleChip { + type Config = MerkleConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl MerkleChip { + pub fn configure( + meta: &mut ConstraintSystem, + sinsemilla_config: SinsemillaConfig, + ) -> MerkleConfig { + let advices = sinsemilla_config.advices(); + let cond_swap_config = + CondSwapChip::configure(meta, advices, sinsemilla_config.perm.clone()); + + // This fixed column serves two purposes: + // - Fixing the value of l* for rows in which a Merkle path layer + // is decomposed. + // - Disabling the entire decomposition gate when set to zero + // (i.e. replacing a Selector). + + let l_plus_1 = meta.fixed_column(); + + // Check that pieces have been decomposed correctly for Sinsemilla hash. + // + // + // a = a_0||a_1 = l_star || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be + // 250 bits, 20 bits, and 250 bits respectively. + // + /* + The pieces and subpieces are arranged in the following configuration: + | A_0 | A_1 | A_2 | A_3 | A_4 | l_plus_1 | + ---------------------------------------------------- + | a | b | c | left | right | l + 1 | + | z1_a | z1_b | b_1 | b_2 | | | + */ + meta.create_gate("Decomposition check", |meta| { + let l_plus_1_whole = meta.query_fixed(l_plus_1, Rotation::cur()); + + let two_pow_5 = pallas::Base::from_u64(1 << 5); + let two_pow_10 = two_pow_5.square(); + + // a_whole is constrained by Sinsemilla to be 250 bits. + let a_whole = meta.query_advice(advices[0], Rotation::cur()); + // b_whole is constrained by Sinsemilla to be 20 bits. + let b_whole = meta.query_advice(advices[1], Rotation::cur()); + // c_whole is constrained by Sinsemilla to be 250 bits. + let c_whole = meta.query_advice(advices[2], Rotation::cur()); + let left_node = meta.query_advice(advices[3], Rotation::cur()); + let right_node = meta.query_advice(advices[4], Rotation::cur()); + + // a = a_0||a_1 = l_star || (bits 0..=239 of left) + // Check that a_0 = l_star + // + // z_1 of SinsemillaHash(a) = a_1 + let z1_a = meta.query_advice(advices[0], Rotation::next()); + let a_1 = z1_a; + // a_0 = a - (a_1 * 2^10) + let a_0 = a_whole - a_1.clone() * pallas::Base::from_u64(1 << 10); + let l_star_check = + a_0 - (l_plus_1_whole.clone() - Expression::Constant(pallas::Base::one())); + + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // The Orchard specification allows this representation to be non-canonical. + // + // + // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 + // => b_0 = b - (z1_b * 2^10) + let z1_b = meta.query_advice(advices[1], Rotation::next()); + // b_1 has been constrained to be 5 bits outside this gate. + let b_1 = meta.query_advice(advices[2], Rotation::next()); + // b_2 has been constrained to be 5 bits outside this gate. + let b_2 = meta.query_advice(advices[3], Rotation::next()); + // Constrain b_1 + 2^5 b_2 = z1_b + let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); + // Derive b_0 (constrained by SinsemillaHash to be 10 bits) + let b_0 = b_whole - (z1_b * two_pow_10); + + // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) + let left_check = { + let reconstructed = { + let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); + a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 + }; + reconstructed - left_node + }; + + // Check that right = b_2 (5 bits) || c (250 bits) + // The Orchard specification allows this representation to be non-canonical. + // + let right_check = b_2 + c_whole * two_pow_5 - right_node; + + array::IntoIter::new([ + ("l_star_check", l_star_check), + ("left_check", left_check), + ("right_check", right_check), + ("b1_b2_check", b1_b2_check), + ]) + .map(move |(name, poly)| (name, l_plus_1_whole.clone() * poly)) + }); + + MerkleConfig { + advices, + l_plus_1, + perm: sinsemilla_config.perm.clone(), + cond_swap_config, + sinsemilla_config, + } + } + + pub fn construct(config: MerkleConfig) -> Self { + MerkleChip { config } + } +} + +impl MerkleInstructions + for MerkleChip +{ + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + // l = MERKLE_DEPTH_ORCHARD - layer - 1 + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config().clone(); + + // + // We need to hash `l_star || left || right`, where `l_star` is a 10-bit value. + // We allow `left` and `right` to be non-canonical 255-bit encodings. + // + // a = a_0||a_1 = l_star || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + + // `a = a_0||a_1` = `l_star` || (bits 0..=239 of `left`) + let a = { + let a = { + // a_0 = l_star + let a_0 = bitrange_subset(pallas::Base::from_u64(l as u64), 0..10); + + // a_1 = (bits 0..=239 of `left`) + let a_1 = left.value().map(|value| bitrange_subset(value, 0..240)); + + a_1.map(|a_1| a_0 + a_1 * pallas::Base::from_u64(1 << 10)) + }; + + self.witness_message_piece(layouter.namespace(|| "Witness a = a_0 || a_1"), a, 25)? + }; + + // b = b_0 || b_1 || b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + let (b_1, b_2, b) = { + // b_0 = (bits 240..=249 of `left`) + let b_0 = left.value().map(|value| bitrange_subset(value, 240..250)); + + // b_1 = (bits 250..=254 of `left`) + // Constrain b_1 to 5 bits. + let b_1 = { + let b_1 = left + .value() + .map(|value| bitrange_subset(value, 250..L_ORCHARD_BASE)); + + config + .sinsemilla_config + .lookup_config_0 + .witness_short_check(layouter.namespace(|| "Constrain b_1 to 5 bits"), b_1, 5)? + }; + + // b_2 = (bits 0..=4 of `right`) + // Constrain b_2 to 5 bits. + let b_2 = { + let b_2 = right.value().map(|value| bitrange_subset(value, 0..5)); + + config + .sinsemilla_config + .lookup_config_1 + .witness_short_check(layouter.namespace(|| "Constrain b_2 to 5 bits"), b_2, 5)? + }; + + let b = { + let b = b_0 + .zip(b_1.value()) + .zip(b_2.value()) + .map(|((b_0, b_1), b_2)| { + b_0 + b_1 * pallas::Base::from_u64(1 << 10) + + b_2 * pallas::Base::from_u64(1 << 15) + }); + self.witness_message_piece( + layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), + b, + 2, + )? + }; + + (b_1, b_2, b) + }; + + let c = { + // `c = bits 5..=254 of `right` + let c = right + .value() + .map(|value| bitrange_subset(value, 5..L_ORCHARD_BASE)); + self.witness_message_piece(layouter.namespace(|| "Witness c"), c, 25)? + }; + + let (point, zs) = self.hash_to_point( + layouter.namespace(|| format!("hash at l = {}", l)), + Q, + vec![a, b, c].into(), + )?; + let z1_a = zs[0][1]; + let z1_b = zs[1][1]; + + // Check that the pieces have been decomposed properly. + /* + The pieces and subpieces are arranged in the following configuration: + | A_0 | A_1 | A_2 | A_3 | A_4 | l_plus_1 | + ---------------------------------------------------- + | a | b | c | left | right | l + 1 | + | z1_a | z1_b | b_1 | b_2 | | | + */ + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l_plus_1` to the current l + 1. + // Recall that l = MERKLE_DEPTH_ORCHARD - layer - 1. + // The layer with 2^n nodes is called "layer n". + let l_plus_1 = (l as u64) + 1; + region.assign_fixed( + || format!("l_plus_1 {}", l_plus_1), + config.l_plus_1, + 0, + || Ok(pallas::Base::from_u64(l_plus_1)), + )?; + + // Offset 0 + // Copy and assign `a` at the correct position. + copy( + &mut region, + || "copy a", + config.advices[0], + 0, + &a.cell_value(), + &config.perm, + )?; + // Copy and assign `b` at the correct position. + copy( + &mut region, + || "copy b", + config.advices[1], + 0, + &b.cell_value(), + &config.perm, + )?; + // Copy and assign `c` at the correct position. + copy( + &mut region, + || "copy c", + config.advices[2], + 0, + &c.cell_value(), + &config.perm, + )?; + // Copy and assign the left node at the correct position. + copy( + &mut region, + || "left", + config.advices[3], + 0, + &left, + &config.perm, + )?; + // Copy and assign the right node at the correct position. + copy( + &mut region, + || "right", + config.advices[4], + 0, + &right, + &config.perm, + )?; + + // Offset 1 + // Copy and assign z_1 of SinsemillaHash(a) = a_1 + copy( + &mut region, + || "z1_a", + config.advices[0], + 1, + &z1_a, + &config.perm, + )?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + copy( + &mut region, + || "z1_b", + config.advices[1], + 1, + &z1_b, + &config.perm, + )?; + // Copy `b_1`, which has been constrained to be a 5-bit value + copy( + &mut region, + || "b_1", + config.advices[2], + 1, + &b_1, + &config.perm, + )?; + // Copy `b_2`, which has been constrained to be a 5-bit value + copy( + &mut region, + || "b_2", + config.advices[3], + 1, + &b_2, + &config.perm, + )?; + + Ok(()) + }, + )?; + } + + let result = Self::extract(&point); + + // Check layer hash output against Sinsemilla primitives hash + #[cfg(test)] + { + use crate::{ + constants::MERKLE_CRH_PERSONALIZATION, primitives::sinsemilla::HashDomain, + spec::i2lebsp, + }; + + if let (Some(left), Some(right)) = (left.value(), right.value()) { + let l_star = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_BASE) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_BASE) + .collect(); + let merkle_crh = HashDomain::new(MERKLE_CRH_PERSONALIZATION); + + let mut message = l_star.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + let expected = merkle_crh.hash(message.into_iter()).unwrap(); + + assert_eq!(expected.to_bytes(), result.value().unwrap().to_bytes()); + } + } + + Ok(result) + } +} + +impl UtilitiesInstructions for MerkleChip { + type Var = CellValue; +} + +impl CondSwapInstructions for MerkleChip { + #[allow(clippy::type_complexity)] + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Option), + swap: Option, + ) -> Result<(Self::Var, Self::Var), Error> { + let config = self.config().cond_swap_config.clone(); + let chip = CondSwapChip::::construct(config); + chip.swap(layouter, pair, swap) + } +} + +impl SinsemillaInstructions for MerkleChip { + type CellValue = >::CellValue; + + type Message = >::Message; + type MessagePiece = >::MessagePiece; + + type X = >::X; + type Point = >::Point; + + type HashDomains = >::HashDomains; + + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Option, + num_words: usize, + ) -> Result { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChip::construct(config); + chip.witness_message_piece(layouter, value, num_words) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::Point, Vec>), Error> { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChip::construct(config); + chip.hash_to_point(layouter, Q, message) + } + + fn extract(point: &Self::Point) -> Self::X { + SinsemillaChip::extract(point) + } +} + +fn bitrange_subset(field_elem: pallas::Base, bitrange: std::ops::Range) -> pallas::Base { + assert!(bitrange.end <= L_ORCHARD_BASE); + + let bits: Vec = field_elem + .to_le_bits() + .iter() + .by_val() + .skip(bitrange.start) + .take(bitrange.end - bitrange.start) + .chain(std::iter::repeat(false)) + .take(256) + .collect(); + let bytearray: Vec = bits + .chunks_exact(8) + .map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8)) + .collect(); + + pallas::Base::from_bytes(&bytearray.try_into().unwrap()).unwrap() +} diff --git a/src/circuit/gadget/utilities.rs b/src/circuit/gadget/utilities.rs index ce01eb222..e493b0afd 100644 --- a/src/circuit/gadget/utilities.rs +++ b/src/circuit/gadget/utilities.rs @@ -3,6 +3,7 @@ use halo2::{ plonk::{Advice, Column, Error, Permutation}, }; use pasta_curves::arithmetic::FieldExt; +use std::array; pub(crate) mod cond_swap; pub(crate) mod enable_flag; @@ -84,3 +85,15 @@ where Ok(CellValue::new(cell, copy.value)) } + +pub fn transpose_option_array( + option_array: Option<[T; LEN]>, +) -> [Option; LEN] { + let mut ret = [None; LEN]; + if let Some(arr) = option_array { + for (entry, value) in ret.iter_mut().zip(array::IntoIter::new(arr)) { + *entry = Some(value); + } + } + ret +} diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 0922f17c2..a2d6bc340 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -4,8 +4,7 @@ use halo2::arithmetic::CurveExt; use pasta_curves::pallas; use subtle::CtOption; -use crate::constants::util::gen_const_array; -use crate::spec::extract_p_bottom; +use crate::spec::{extract_p_bottom, i2lebsp}; mod addition; use self::addition::IncompletePoint; @@ -25,7 +24,7 @@ pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { /// up to `2^K` - 1. pub fn i2lebsp_k(int: usize) -> [bool; K] { assert!(int < (1 << K)); - gen_const_array(|mask: usize| (int & (1 << mask)) != 0) + i2lebsp(int as u64) } /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a diff --git a/src/spec.rs b/src/spec.rs index 34f8a5ddc..44e19c480 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -11,7 +11,7 @@ use pasta_curves::pallas; use subtle::{ConditionallySelectable, CtOption}; use crate::{ - constants::L_ORCHARD_BASE, + constants::{util::gen_const_array, L_ORCHARD_BASE}, primitives::{poseidon, sinsemilla}, }; @@ -252,11 +252,26 @@ pub fn lebs2ip(bits: &[bool; L]) -> u64 { .fold(0u64, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) } +/// The sequence of bits representing a u64 in little-endian order. +/// +/// # Panics +/// +/// Panics if the expected length of the sequence `NUM_BITS` exceeds +/// 64. +pub fn i2lebsp(int: u64) -> [bool; NUM_BITS] { + assert!(NUM_BITS <= 64); + gen_const_array(|mask: usize| (int & (1 << mask)) != 0) +} + #[cfg(test)] mod tests { + use super::{i2lebsp, lebs2ip}; + use group::Group; use halo2::arithmetic::CurveExt; use pasta_curves::pallas; + use rand::{rngs::OsRng, RngCore}; + use std::convert::TryInto; #[test] fn diversify_hash_substitution() { @@ -264,4 +279,45 @@ mod tests { pallas::Point::hash_to_curve("z.cash:Orchard-gd")(&[]).is_identity() )); } + + #[test] + fn lebs2ip_round_trip() { + let mut rng = OsRng; + { + let int = rng.next_u64(); + assert_eq!(lebs2ip::<64>(&i2lebsp(int)), int); + } + + assert_eq!(lebs2ip::<64>(&i2lebsp(0)), 0); + assert_eq!( + lebs2ip::<64>(&i2lebsp(0xFFFFFFFFFFFFFFFF)), + 0xFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn i2lebsp_round_trip() { + { + let bitstring = (0..64).map(|_| rand::random()).collect::>(); + assert_eq!( + i2lebsp::<64>(lebs2ip::<64>(&bitstring.clone().try_into().unwrap())).to_vec(), + bitstring + ); + } + + { + let bitstring = [false; 64]; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + + { + let bitstring = [true; 64]; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + + { + let bitstring = []; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + } }