From 6fdee7166798046438dda8603b76d05809f8b905 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 21 Jun 2021 23:27:13 +0800 Subject: [PATCH 1/5] Adjustments to APIs in sinsemilla::chip and sinsemilla::message. --- src/circuit/gadget/sinsemilla/chip.rs | 12 +++++++++--- src/circuit/gadget/sinsemilla/message.rs | 22 +++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index ae70da58c..1f3be1fcf 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -60,13 +60,19 @@ pub struct SinsemillaConfig { lambda_2: Column, /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ /// generators of the Sinsemilla hash. - generator_table: GeneratorTableConfig, + pub(super) generator_table: GeneratorTableConfig, /// 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, - /// Permutation over all advice columns and the `constants` fixed column. - perm: Permutation, + // Permutation over all advice columns and the `constants` fixed column. + pub(super) perm: Permutation, +} + +impl SinsemillaConfig { + pub fn advices(&self) -> [Column; 5] { + [self.bits, self.lambda_1, self.lambda_2, self.x_a, self.x_p] + } } #[derive(Eq, PartialEq, Clone, Debug)] diff --git a/src/circuit/gadget/sinsemilla/message.rs b/src/circuit/gadget/sinsemilla/message.rs index 20616123e..ac2926d9c 100644 --- a/src/circuit/gadget/sinsemilla/message.rs +++ b/src/circuit/gadget/sinsemilla/message.rs @@ -34,25 +34,17 @@ impl std:: /// cannot exceed the base field's `NUM_BITS`. #[derive(Copy, Clone, Debug)] pub struct MessagePiece { - cell: Cell, - field_elem: Option, + cell_value: CellValue, /// The number of K-bit words in this message piece. num_words: usize, } -#[allow(clippy::from_over_into)] -impl Into> for MessagePiece { - fn into(self) -> CellValue { - CellValue::new(self.cell(), self.field_elem()) - } -} - impl MessagePiece { pub fn new(cell: Cell, field_elem: Option, num_words: usize) -> Self { assert!(num_words * K < F::NUM_BITS as usize); + let cell_value = CellValue::new(cell, field_elem); Self { - cell, - field_elem, + cell_value, num_words, } } @@ -62,10 +54,14 @@ impl MessagePiece { } pub fn cell(&self) -> Cell { - self.cell + self.cell_value.cell() } pub fn field_elem(&self) -> Option { - self.field_elem + self.cell_value.value() + } + + pub fn cell_value(&self) -> CellValue { + self.cell_value } } From bdcdb8ac134a4f8582e5a4402ebd2153c9da21ac Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 24 Jun 2021 16:29:55 +0800 Subject: [PATCH 2/5] Move witness_message() and witness_message_piece_bitstring() to gadget level These instructions were not making any assignments; instead, they were calling through to witness_message_piece_field(). This PR also renames the witness_message_piece_field() instruction to witness_message_piece(). --- src/circuit/gadget/sinsemilla.rs | 154 ++++++++++++++++++-------- src/circuit/gadget/sinsemilla/chip.rs | 69 +----------- 2 files changed, 110 insertions(+), 113 deletions(-) diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index bc06c8ea1..2b4c67353 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -3,8 +3,10 @@ use crate::circuit::gadget::{ ecc::{self, EccInstructions}, utilities::Var, }; -use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; -use std::fmt::Debug; +use ff::PrimeField; +use halo2::{circuit::Layouter, plonk::Error}; +use pasta_curves::arithmetic::{CurveAffine, FieldExt}; +use std::{convert::TryInto, fmt::Debug}; pub mod chip; mod message; @@ -27,7 +29,7 @@ pub trait SinsemillaInstructions; - /// Witness a message in the given bitstring. - /// Returns a vector of [`Self::MessagePiece`]s encoding the given message. - /// - /// # Panics - /// - /// Panics if the message length is not a multiple of `K`. - /// - /// Panics if the message length exceeds `K * MAX_WORDS`. - fn witness_message( - &self, - layouter: impl Layouter, - message: Vec>, - ) -> Result; - - /// Witnesses a message piece given a field element and the intended number of `K`-bit - /// words it contains. - /// - /// Returns a [`Self::MessagePiece`] encoding the given message. - /// - /// # Panics - /// - /// Panics if the message length is not a multiple of `K`. - /// - /// Panics if the message length exceeds the maximum number of words - /// that can fit in a field element. - fn witness_message_piece_bitstring( - &self, - layouter: impl Layouter, - message: &[Option], - ) -> Result; - /// Witness a message piece given a field element. Returns a [`Self::MessagePiece`] /// encoding the given message. /// @@ -75,7 +46,7 @@ pub trait SinsemillaInstructions, value: Option, @@ -130,24 +101,112 @@ where { fn from_bitstring( chip: SinsemillaChip, - layouter: impl Layouter, + mut layouter: impl Layouter, bitstring: Vec>, ) -> Result { - let inner = chip.witness_message(layouter, bitstring)?; - Ok(Self { chip, inner }) + // Message must be composed of `K`-bit words. + assert_eq!(bitstring.len() % K, 0); + + // Message must have at most `MAX_WORDS` words. + assert!(bitstring.len() / K <= MAX_WORDS); + + // Message piece must be at most `ceil(C::NUM_BITS / K)` bits + let piece_num_words = C::Base::NUM_BITS as usize / K; + let pieces: Result, _> = bitstring + .chunks(piece_num_words * K) + .enumerate() + .map( + |(i, piece)| -> Result, Error> { + MessagePiece::from_bitstring( + chip.clone(), + layouter.namespace(|| format!("message piece {}", i)), + piece, + ) + }, + ) + .collect(); + + pieces.map(|pieces| Self::from_pieces(chip, pieces)) } /// Constructs a message from a vector of [`MessagePiece`]s. /// /// [`MessagePiece`]: SinsemillaInstructions::MessagePiece - fn from_pieces(chip: SinsemillaChip, pieces: Vec) -> Self { + fn from_pieces( + chip: SinsemillaChip, + pieces: Vec>, + ) -> Self { Self { chip, - inner: pieces.into(), + inner: pieces + .iter() + .map(|piece| piece.inner.clone()) + .collect::>() + .into(), } } } +#[derive(Clone, Debug)] +pub struct MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + chip: SinsemillaChip, + inner: SinsemillaChip::MessagePiece, +} + +impl + MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + fn from_bitstring( + chip: SinsemillaChip, + layouter: impl Layouter, + bitstring: &[Option], + ) -> Result { + // Message must be composed of `K`-bit words. + assert_eq!(bitstring.len() % K, 0); + let num_words = bitstring.len() / K; + + // Message piece must be at most `ceil(C::Base::NUM_BITS / K)` bits + let piece_max_num_words = C::Base::NUM_BITS as usize / K; + assert!(num_words <= piece_max_num_words as usize); + + // Closure to parse a bitstring (little-endian) into a base field element. + let to_base_field = |bits: &[Option]| -> Option { + assert!(bits.len() <= C::Base::NUM_BITS as usize); + + let bits: Option> = bits.iter().cloned().collect(); + let bytes: Option> = bits.map(|bits| { + // Pad bits to 256 bits + let pad_len = 256 - bits.len(); + let mut bits = bits; + bits.extend_from_slice(&vec![false; pad_len]); + + bits.chunks_exact(8) + .map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8)) + .collect() + }); + bytes.map(|bytes| C::Base::from_bytes(&bytes.try_into().unwrap()).unwrap()) + }; + + let piece_value = to_base_field(bitstring); + Self::from_field_elem(chip, layouter, piece_value, num_words) + } + + fn from_field_elem( + chip: SinsemillaChip, + layouter: impl Layouter, + field_elem: Option, + num_words: usize, + ) -> Result { + let inner = chip.witness_message_piece(layouter, field_elem, num_words)?; + Ok(Self { chip, inner }) + } +} + /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can /// be used. #[allow(non_snake_case)] @@ -242,7 +301,7 @@ mod tests { use super::{ chip::SinsemillaHashDomains, chip::{SinsemillaChip, SinsemillaConfig}, - HashDomain, Message, SinsemillaInstructions, + HashDomain, Message, MessagePiece, }; use crate::{ @@ -337,13 +396,17 @@ mod tests { // Layer 31, l = MERKLE_DEPTH_ORCHARD - 1 - layer = 0 let l_bitstring = vec![Some(false); K]; - let l = chip1 - .witness_message_piece_bitstring(layouter.namespace(|| "l"), &l_bitstring)?; + let l = MessagePiece::from_bitstring( + chip1.clone(), + layouter.namespace(|| "l"), + &l_bitstring, + )?; // Left leaf let left_bitstring: Vec> = (0..250).map(|_| Some(rand::random::())).collect(); - let left = chip1.witness_message_piece_bitstring( + let left = MessagePiece::from_bitstring( + chip1.clone(), layouter.namespace(|| "left"), &left_bitstring, )?; @@ -351,7 +414,8 @@ mod tests { // Right leaf let right_bitstring: Vec> = (0..250).map(|_| Some(rand::random::())).collect(); - let right = chip1.witness_message_piece_bitstring( + let right = MessagePiece::from_bitstring( + chip1.clone(), layouter.namespace(|| "right"), &right_bitstring, )?; diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index 1f3be1fcf..d4b80b66c 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -12,7 +12,6 @@ use crate::{ }, }; -use ff::PrimeField; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::{Chip, Layouter}, @@ -24,8 +23,6 @@ use halo2::{ }; use pasta_curves::pallas; -use std::convert::TryInto; - mod generator_table; pub use generator_table::get_s_by_idx; use generator_table::GeneratorTableConfig; @@ -231,71 +228,7 @@ impl SinsemillaInstructions, - message: Vec>, - ) -> Result { - // Message must be composed of `K`-bit words. - assert_eq!(message.len() % sinsemilla::K, 0); - - // Message must have at most `sinsemilla::C` words. - assert!(message.len() / sinsemilla::K <= sinsemilla::C); - - // Message piece must be at most `ceil(pallas::Base::NUM_BITS / sinsemilla::K)` bits - let piece_num_words = pallas::Base::NUM_BITS as usize / sinsemilla::K; - let pieces: Result, _> = message - .chunks(piece_num_words * sinsemilla::K) - .enumerate() - .map(|(i, piece)| -> Result { - self.witness_message_piece_bitstring( - layouter.namespace(|| format!("message piece {}", i)), - piece, - ) - }) - .collect(); - - pieces.map(|pieces| pieces.into()) - } - - #[allow(non_snake_case)] - fn witness_message_piece_bitstring( - &self, - layouter: impl Layouter, - message_piece: &[Option], - ) -> Result { - // Message must be composed of `K`-bit words. - assert_eq!(message_piece.len() % sinsemilla::K, 0); - let num_words = message_piece.len() / sinsemilla::K; - - // Message piece must be at most `ceil(C::Base::NUM_BITS / sinsemilla::K)` bits - let piece_max_num_words = pallas::Base::NUM_BITS as usize / sinsemilla::K; - assert!(num_words <= piece_max_num_words as usize); - - // Closure to parse a bitstring (little-endian) into a base field element. - let to_base_field = |bits: &[Option]| -> Option { - assert!(bits.len() <= pallas::Base::NUM_BITS as usize); - - let bits: Option> = bits.iter().cloned().collect(); - let bytes: Option> = bits.map(|bits| { - // Pad bits to 256 bits - let pad_len = 256 - bits.len(); - let mut bits = bits; - bits.extend_from_slice(&vec![false; pad_len]); - - bits.chunks_exact(8) - .map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8)) - .collect() - }); - bytes.map(|bytes| pallas::Base::from_bytes(&bytes.try_into().unwrap()).unwrap()) - }; - - let piece_value = to_base_field(message_piece); - self.witness_message_piece_field(layouter, piece_value, num_words) - } - - fn witness_message_piece_field( + fn witness_message_piece( &self, mut layouter: impl Layouter, field_elem: Option, From 2ec30943b32611a6603dfc25350fcc050699ebbf Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 25 Jun 2021 11:04:13 +0800 Subject: [PATCH 3/5] Configure each Sinsemilla advice column for use with a K-bit lookup. Inputs to Sinsemilla often need to be decomposed and range-constrained. --- src/circuit/gadget/sinsemilla/chip.rs | 49 ++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index d4b80b66c..e274e1199 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ circuit::gadget::{ ecc::chip::EccPoint, - utilities::{CellValue, Var}, + utilities::{lookup_range_check::LookupRangeCheckConfig, CellValue, Var}, }, primitives::sinsemilla::{ self, Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, Q_NOTE_COMMITMENT_M_GENERATOR, @@ -62,8 +62,14 @@ pub struct SinsemillaConfig { /// x-coordinate of the domain $Q$, which is then constrained to equal the /// initial $x_a$. constants: Column, - // Permutation over all advice columns and the `constants` fixed 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. + pub(super) lookup_config_0: LookupRangeCheckConfig, + pub(super) lookup_config_1: LookupRangeCheckConfig, + pub(super) lookup_config_2: LookupRangeCheckConfig, + pub(super) lookup_config_3: LookupRangeCheckConfig, + pub(super) lookup_config_4: LookupRangeCheckConfig, } impl SinsemillaConfig { @@ -109,7 +115,7 @@ impl SinsemillaChip { meta: &mut ConstraintSystem, advices: [Column; 5], lookup: (Column, Column, Column), - constants: Column, + constants: [Column; 6], // TODO: replace with public inputs API perm: Permutation, ) -> >::Config { let config = SinsemillaConfig { @@ -126,7 +132,42 @@ impl SinsemillaChip { table_x: lookup.1, table_y: lookup.2, }, - constants, + constants: constants[5], + lookup_config_0: LookupRangeCheckConfig::configure( + meta, + advices[0], + constants[0], + lookup.0, + perm.clone(), + ), + lookup_config_1: LookupRangeCheckConfig::configure( + meta, + advices[1], + constants[1], + lookup.0, + perm.clone(), + ), + lookup_config_2: LookupRangeCheckConfig::configure( + meta, + advices[2], + constants[2], + lookup.0, + perm.clone(), + ), + lookup_config_3: LookupRangeCheckConfig::configure( + meta, + advices[3], + constants[3], + lookup.0, + perm.clone(), + ), + lookup_config_4: LookupRangeCheckConfig::configure( + meta, + advices[4], + constants[4], + lookup.0, + perm.clone(), + ), perm, }; From 9b47bd0db40ea95e82e3e4ba69696b8779d6a060 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 25 Jun 2021 13:39:37 +0800 Subject: [PATCH 4/5] sinsemilla::tests: Use separate constants columns for chips. To be replaced by the public inputs API. --- src/circuit/gadget/sinsemilla.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 2b4c67353..48f559c9f 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -337,12 +337,32 @@ mod tests { meta.advice_column(), ]; - let constants = meta.fixed_column(); + // 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 constants_3 = meta.fixed_column(); + let perm = meta.permutation( &advices .iter() .map(|advice| (*advice).into()) - .chain(Some(constants.into())) + .chain(Some(constants_3.into())) + .chain(constants_1.iter().map(|fixed| (*fixed).into())) + .chain(constants_2.iter().map(|fixed| (*fixed).into())) .collect::>(), ); @@ -359,14 +379,14 @@ mod tests { meta, advices[..5].try_into().unwrap(), lookup, - constants, + constants_1, perm.clone(), ); let config2 = SinsemillaChip::configure( meta, advices[5..].try_into().unwrap(), lookup, - constants, + constants_2, perm, ); (ecc_config, config1, config2) From 12cef1755984137430ffe7df04bd368f7b487f10 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 29 Jun 2021 12:02:54 +0800 Subject: [PATCH 5/5] Cleanups and minor refactors. Co-authored-by: Jack Grigg --- src/circuit/gadget/sinsemilla.rs | 6 ++---- src/circuit/gadget/sinsemilla/chip.rs | 6 ------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 48f559c9f..9cde750c3 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -139,8 +139,8 @@ where Self { chip, inner: pieces - .iter() - .map(|piece| piece.inner.clone()) + .into_iter() + .map(|piece| piece.inner) .collect::>() .into(), } @@ -354,13 +354,11 @@ mod tests { meta.fixed_column(), meta.fixed_column(), ]; - let constants_3 = meta.fixed_column(); let perm = meta.permutation( &advices .iter() .map(|advice| (*advice).into()) - .chain(Some(constants_3.into())) .chain(constants_1.iter().map(|fixed| (*fixed).into())) .chain(constants_2.iter().map(|fixed| (*fixed).into())) .collect::>(), diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index e274e1199..fcffc2ba4 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -72,12 +72,6 @@ pub struct SinsemillaConfig { pub(super) lookup_config_4: LookupRangeCheckConfig, } -impl SinsemillaConfig { - pub fn advices(&self) -> [Column; 5] { - [self.bits, self.lambda_1, self.lambda_2, self.x_a, self.x_p] - } -} - #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChip { config: SinsemillaConfig,