diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index bc06c8ea1..9cde750c3 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 + .into_iter() + .map(|piece| piece.inner) + .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::{ @@ -278,12 +337,30 @@ 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 perm = meta.permutation( &advices .iter() .map(|advice| (*advice).into()) - .chain(Some(constants.into())) + .chain(constants_1.iter().map(|fixed| (*fixed).into())) + .chain(constants_2.iter().map(|fixed| (*fixed).into())) .collect::>(), ); @@ -300,14 +377,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) @@ -337,13 +414,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 +432,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 ae70da58c..fcffc2ba4 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -5,14 +5,13 @@ 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, }, }; -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; @@ -60,13 +57,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, + 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, } #[derive(Eq, PartialEq, Clone, Debug)] @@ -106,7 +109,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 { @@ -123,7 +126,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, }; @@ -225,71 +263,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, 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 } }