From 1ac3541505357bef2531ad44d6d331b3e53d8aaa Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:51:01 +0800 Subject: [PATCH 01/13] Add spec::i2lebsp and constants::MERKLE_DEPTH_ORCHARD --- src/primitives/sinsemilla.rs | 5 ++-- src/spec.rs | 58 +++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) 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); + } + } } From d090da0159fbc0a9926097c760bcce5d942d77e0 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:53:09 +0800 Subject: [PATCH 02/13] sinsemilla::merkle.rs: Add MerkleInstructions. This has three const generic parameters: PATH_LENGTH, K, MAX_WORDS. PATH_LENGTH is the length of the Merkle path being hashed. K and MAX_WORDS parameterize the internal Sinsemilla instance used in hashing the path. --- src/circuit/gadget/sinsemilla.rs | 1 + src/circuit/gadget/sinsemilla/chip.rs | 7 ++ src/circuit/gadget/sinsemilla/merkle.rs | 141 ++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/circuit/gadget/sinsemilla/merkle.rs 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..372b6234b 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 000000000..91883281d --- /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) + } +} From 68878d88b18f0b699a354d25a10dd9d63ee9201a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:54:10 +0800 Subject: [PATCH 03/13] sinsemilla::merkle.rs: Add MerkleChip --- src/circuit/gadget/sinsemilla/merkle.rs | 2 +- src/circuit/gadget/sinsemilla/merkle/chip.rs | 53 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/circuit/gadget/sinsemilla/merkle/chip.rs diff --git a/src/circuit/gadget/sinsemilla/merkle.rs b/src/circuit/gadget/sinsemilla/merkle.rs index 91883281d..9d5e05c2d 100644 --- a/src/circuit/gadget/sinsemilla/merkle.rs +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -12,7 +12,7 @@ use crate::{ }; use std::{convert::TryInto, iter}; -// mod chip; +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. diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs new file mode 100644 index 000000000..d10b6048f --- /dev/null +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -0,0 +1,53 @@ +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, + lookup_range_check::LookupRangeCheckConfig, + CellValue, UtilitiesInstructions, Var, + }, + constants::MERKLE_DEPTH_ORCHARD, + primitives::sinsemilla, +}; +use ff::{PrimeField, PrimeFieldBits}; +use std::{array, convert::TryInto}; + +#[derive(Clone, Debug)] +pub struct MerkleConfig { + advices: [Column; 5], + l_star_plus1: Column, + perm: Permutation, + lookup_config: LookupRangeCheckConfig, + 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 { + &() + } +} From 6976e2baebff668f94bf6eecd241588ec57b4a74 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:57:29 +0800 Subject: [PATCH 04/13] sinsemilla::merkle.rs: Derive SinsemillaInstructions, CondSwapInstructions for MerkleChip --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 103 +++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index d10b6048f..af5e9e0c3 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -51,3 +51,106 @@ impl Chip for MerkleChip { &() } } + +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 { + let bits = &field_elem + .to_le_bits() + .iter() + .by_val() + .take(pallas::Base::NUM_BITS as usize) + .collect::>()[bitrange]; + let bits: Vec = bits + .iter() + .cloned() + .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() +} From 569eb4baa6695a2365e7256ca92e15d8ac6913c7 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:58:11 +0800 Subject: [PATCH 05/13] sinsemilla::merkle.rs: Configure MerkleChip MerkleChip::configure() takes a SinsemillaConfig as input. --- src/circuit/gadget/sinsemilla/chip.rs | 2 +- src/circuit/gadget/sinsemilla/merkle/chip.rs | 130 +++++++++++++++---- 2 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index 372b6234b..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. diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index af5e9e0c3..b1bd1276c 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -21,7 +21,7 @@ use crate::{ constants::MERKLE_DEPTH_ORCHARD, primitives::sinsemilla, }; -use ff::{PrimeField, PrimeFieldBits}; +use ff::PrimeFieldBits; use std::{array, convert::TryInto}; #[derive(Clone, Debug)] @@ -52,6 +52,113 @@ impl Chip for MerkleChip { } } +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()); + let lookup_config = LookupRangeCheckConfig::configure( + meta, + advices[0], + sinsemilla_config.constants, + sinsemilla_config.generator_table.table_idx, + 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_star_plus1 = 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. + // + meta.create_gate("Decomposition check", |meta| { + let l_star_plus1_whole = meta.query_fixed(l_star_plus1, 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_star_plus1_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) + // + // 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()); + // 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(); + let b0_shifted = b_0 * two_pow_240; + let b1_shifted = b_1 * two_pow_240 * two_pow_10; + a_1 + b0_shifted + b1_shifted + }; + reconstructed - left_node + }; + + // Check that right = b_2 (5 bits) || c (250 bits) + let right_check = b_2 + c_whole * two_pow_5 - right_node; + + array::IntoIter::new([l_star_check, left_check, right_check]) + .map(move |poly| l_star_plus1_whole.clone() * poly) + }); + + MerkleConfig { + advices, + l_star_plus1, + perm: sinsemilla_config.perm.clone(), + cond_swap_config, + lookup_config, + sinsemilla_config, + } + } + + pub fn construct(config: MerkleConfig) -> Self { + MerkleChip { config } + } +} + impl UtilitiesInstructions for MerkleChip { type Var = CellValue; } @@ -133,24 +240,3 @@ impl SinsemillaInstructions) -> pallas::Base { - let bits = &field_elem - .to_le_bits() - .iter() - .by_val() - .take(pallas::Base::NUM_BITS as usize) - .collect::>()[bitrange]; - let bits: Vec = bits - .iter() - .cloned() - .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() -} From f30de79fc64a8232604a9854e03650f8430acd12 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 16:50:11 +0800 Subject: [PATCH 06/13] sinsemilla::merkle.rs: Implement MerkleInstructions for MerkleChip. Co-authored-by: Daira Hopwood Co-authored-by: Jack Grigg --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 268 ++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index b1bd1276c..f2f113080 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -18,7 +18,7 @@ use crate::{ lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions, Var, }, - constants::MERKLE_DEPTH_ORCHARD, + constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}, primitives::sinsemilla, }; use ff::PrimeFieldBits; @@ -159,6 +159,252 @@ impl MerkleChip { } } +impl MerkleInstructions + for MerkleChip +{ + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + l_star: usize, + left: >::Var, + right: >::Var, + ) -> Result<>::Var, Error> { + 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` || (bits 0..=239 of `left`) + let a = { + let a = { + // a_0 = l_star + let a_0 = bitrange_subset(pallas::Base::from_u64(l_star 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_3"), + 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!("l_star {}", l_star)), + 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. + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l_star_plus1` to the current l_star + 1. + let l_star_plus1 = (l_star as u64) + 1; + region.assign_fixed( + || format!("l_star_plus1 {}", l_star_plus1), + config.l_star_plus1, + 0, + || Ok(pallas::Base::from_u64(l_star_plus1)), + )?; + + // 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, + || "a_0", + config.advices[0], + 1, + &z1_a, + &config.perm, + )?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + copy( + &mut region, + || "b_0", + 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_star 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; } @@ -240,3 +486,23 @@ impl SinsemillaInstructions) -> 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() +} From db45c81ea6496eddd117ea4c365e1a893d12387f Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 16:51:21 +0800 Subject: [PATCH 07/13] sinsemilla::merkle.rs: Add test for MerkleChip. --- src/circuit/gadget/sinsemilla/merkle.rs | 212 ++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/src/circuit/gadget/sinsemilla/merkle.rs b/src/circuit/gadget/sinsemilla/merkle.rs index 9d5e05c2d..20df5a547 100644 --- a/src/circuit/gadget/sinsemilla/merkle.rs +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -139,3 +139,215 @@ where 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_star, (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_star 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(())) + } +} From 32e564a963f14b76c6d8f1ceb21f71a2c6536fb0 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 25 Jun 2021 20:22:51 +0800 Subject: [PATCH 08/13] Constrain b_1 + 2^5 b_2 = z1_b in decomposition gate. --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index f2f113080..fc8aa0cf1 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -123,6 +123,8 @@ impl MerkleChip { 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); @@ -140,7 +142,7 @@ impl MerkleChip { // Check that right = b_2 (5 bits) || c (250 bits) let right_check = b_2 + c_whole * two_pow_5 - right_node; - array::IntoIter::new([l_star_check, left_check, right_check]) + array::IntoIter::new([l_star_check, left_check, right_check, b1_b2_check]) .map(move |poly| l_star_plus1_whole.clone() * poly) }); From d68eb6583d7807b69bf75431cf7df7f103153fbd Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 28 Jun 2021 17:06:39 +0800 Subject: [PATCH 09/13] Docfixes, variable renames, cleanups Co-authored-by: Daira Hopwood --- src/circuit/gadget/sinsemilla/merkle.rs | 55 ++++++++------------ src/circuit/gadget/sinsemilla/merkle/chip.rs | 22 ++++---- src/circuit/gadget/utilities.rs | 15 ++++++ 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/merkle.rs b/src/circuit/gadget/sinsemilla/merkle.rs index 20df5a547..85db6c220 100644 --- a/src/circuit/gadget/sinsemilla/merkle.rs +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -7,10 +7,12 @@ use pasta_curves::arithmetic::CurveAffine; use super::{HashDomains, SinsemillaInstructions}; use crate::{ - circuit::gadget::utilities::{cond_swap::CondSwapInstructions, UtilitiesInstructions}, + circuit::gadget::utilities::{ + cond_swap::CondSwapInstructions, transpose_option_array, UtilitiesInstructions, + }, spec::i2lebsp, }; -use std::{convert::TryInto, iter}; +use std::iter; mod chip; @@ -28,14 +30,15 @@ pub trait MerkleInstructions< + 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`. + /// 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_star: usize, + l: usize, left: Self::Var, right: Self::Var, ) -> Result; @@ -55,6 +58,7 @@ pub struct MerklePath< chip_2: MerkleChip, domain: MerkleChip::HashDomains, leaf_pos: Option, + // The Merkle path is ordered from leaves to root. path: Option<[C::Base; PATH_LENGTH]>, } @@ -82,41 +86,26 @@ where .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] - }; + // 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)); - 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 + transpose_option_array(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 + 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_star` = 32 - 31 - 1 = 0. + // we have `l` = 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. + // we have `l` = 32 - 0 - 1 = 31. let pair = { let pair = (node, *sibling); @@ -125,12 +114,12 @@ where }; // Each `hash_layer` consists of 52 Sinsemilla words: - // - l_star (10 bits) = 1 word + // - l (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)), + layouter.namespace(|| format!("hash l {}", l)), Q, - l_star, + l, pair.0, pair.1, )?; @@ -296,14 +285,14 @@ pub mod tests { // Compute the root let mut node = leaf; - for (l_star, (sibling, pos)) in path.iter().zip(pos_bool.iter()).enumerate() { + 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_star as u64); + let l_star = i2lebsp::<10>(l as u64); let left: Vec<_> = left .to_le_bits() .iter() diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index fc8aa0cf1..a0ce10e97 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -27,7 +27,7 @@ use std::{array, convert::TryInto}; #[derive(Clone, Debug)] pub struct MerkleConfig { advices: [Column; 5], - l_star_plus1: Column, + l_plus_1: Column, perm: Permutation, lookup_config: LookupRangeCheckConfig, pub(super) cond_swap_config: CondSwapConfig, @@ -74,7 +74,7 @@ impl MerkleChip { // - Disabling the entire decomposition gate (when set to zero) // (i.e. replacing a Selector). - let l_star_plus1 = meta.fixed_column(); + let l_plus_1 = meta.fixed_column(); // Check that pieces have been decomposed correctly for Sinsemilla hash. // @@ -88,7 +88,7 @@ impl MerkleChip { // 250 bits, 20 bits, and 250 bits respectively. // meta.create_gate("Decomposition check", |meta| { - let l_star_plus1_whole = meta.query_fixed(l_star_plus1, Rotation::cur()); + 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(); @@ -111,7 +111,7 @@ impl MerkleChip { // 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_star_plus1_whole.clone() - Expression::Constant(pallas::Base::one())); + 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) @@ -143,12 +143,12 @@ impl MerkleChip { let right_check = b_2 + c_whole * two_pow_5 - right_node; array::IntoIter::new([l_star_check, left_check, right_check, b1_b2_check]) - .map(move |poly| l_star_plus1_whole.clone() * poly) + .map(move |poly| l_plus_1_whole.clone() * poly) }); MerkleConfig { advices, - l_star_plus1, + l_plus_1, perm: sinsemilla_config.perm.clone(), cond_swap_config, lookup_config, @@ -268,13 +268,13 @@ impl MerkleInstructions( + option_array: Option<[T; LEN]>, +) -> [Option; LEN] { + if let Some(arr) = option_array { + arr.iter() + .map(|el| Some(*el)) + .collect::>() + .try_into() + .unwrap() + } else { + [None; LEN] + } +} From 3806a9d6f07a747a715ac8c3b89f9b4729f0809d Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 29 Jun 2021 09:39:37 +0800 Subject: [PATCH 10/13] Further cleanups and docfixes. Co-authored-by: Daira Hopwood --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 68 +++++++++++++------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index a0ce10e97..b67f197b2 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -71,13 +71,13 @@ impl MerkleChip { // 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) + // - 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 @@ -87,6 +87,13 @@ impl MerkleChip { // 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()); @@ -115,6 +122,8 @@ impl MerkleChip { // 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) @@ -132,18 +141,23 @@ impl MerkleChip { let left_check = { let reconstructed = { let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); - let b0_shifted = b_0 * two_pow_240; - let b1_shifted = b_1 * two_pow_240 * two_pow_10; - a_1 + b0_shifted + b1_shifted + 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, left_check, right_check, b1_b2_check]) - .map(move |poly| l_plus_1_whole.clone() * poly) + 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 { @@ -169,13 +183,14 @@ impl MerkleInstructions, Q: pallas::Affine, - l_star: usize, - left: >::Var, - right: >::Var, - ) -> Result<>::Var, Error> { + // 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. // @@ -184,11 +199,11 @@ impl MerkleInstructions(l_star as u64); + let l_star = i2lebsp::<10>(l as u64); let left: Vec<_> = left .to_le_bits() .iter() From 8dfcd7d49b639c76e0405ea470a8bc77a7d8c333 Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 29 Jun 2021 22:41:01 +0100 Subject: [PATCH 11/13] Remove unused lookup_config in MerkleConfig --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index b67f197b2..c416d1318 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -15,7 +15,6 @@ use crate::{ circuit::gadget::utilities::{ cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, copy, - lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions, Var, }, constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}, @@ -29,7 +28,6 @@ pub struct MerkleConfig { advices: [Column; 5], l_plus_1: Column, perm: Permutation, - lookup_config: LookupRangeCheckConfig, pub(super) cond_swap_config: CondSwapConfig, pub(super) sinsemilla_config: SinsemillaConfig, } @@ -60,13 +58,6 @@ impl MerkleChip { let advices = sinsemilla_config.advices(); let cond_swap_config = CondSwapChip::configure(meta, advices, sinsemilla_config.perm.clone()); - let lookup_config = LookupRangeCheckConfig::configure( - meta, - advices[0], - sinsemilla_config.constants, - sinsemilla_config.generator_table.table_idx, - sinsemilla_config.perm.clone(), - ); // This fixed column serves two purposes: // - Fixing the value of l* for rows in which a Merkle path layer @@ -165,7 +156,6 @@ impl MerkleChip { l_plus_1, perm: sinsemilla_config.perm.clone(), cond_swap_config, - lookup_config, sinsemilla_config, } } From cbded2b8218fd1e395163ad549aa461e660c298b Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 29 Jun 2021 22:43:50 +0100 Subject: [PATCH 12/13] Optimize transpose_option_array --- src/circuit/gadget/utilities.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/circuit/gadget/utilities.rs b/src/circuit/gadget/utilities.rs index a343fa4c7..e493b0afd 100644 --- a/src/circuit/gadget/utilities.rs +++ b/src/circuit/gadget/utilities.rs @@ -3,7 +3,7 @@ use halo2::{ plonk::{Advice, Column, Error, Permutation}, }; use pasta_curves::arithmetic::FieldExt; -use std::convert::TryInto; +use std::array; pub(crate) mod cond_swap; pub(crate) mod enable_flag; @@ -89,13 +89,11 @@ where pub fn transpose_option_array( option_array: Option<[T; LEN]>, ) -> [Option; LEN] { + let mut ret = [None; LEN]; if let Some(arr) = option_array { - arr.iter() - .map(|el| Some(*el)) - .collect::>() - .try_into() - .unwrap() - } else { - [None; LEN] + for (entry, value) in ret.iter_mut().zip(array::IntoIter::new(arr)) { + *entry = Some(value); + } } + ret } From 7c38f149ac6876d0f4469e5f986ca7037194411e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 29 Jun 2021 22:46:07 +0100 Subject: [PATCH 13/13] rustfmt --- src/circuit/gadget/sinsemilla/merkle/chip.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index c416d1318..e86d85a8a 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -14,8 +14,7 @@ use super::MerkleInstructions; use crate::{ circuit::gadget::utilities::{ cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, - copy, - CellValue, UtilitiesInstructions, Var, + copy, CellValue, UtilitiesInstructions, Var, }, constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}, primitives::sinsemilla,