diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 27d0c2af6..bc5b6db5c 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -4,6 +4,8 @@ use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; use std::fmt::Debug; pub mod chip; +mod message; + pub use chip::{SinsemillaChip, SinsemillaConfig}; /// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget. @@ -11,6 +13,9 @@ pub use chip::{SinsemillaChip, SinsemillaConfig}; /// in each word accepted by the Sinsemilla hash, and `MAX_WORDS`, the maximum /// number of words that a single hash instance can process. pub trait SinsemillaInstructions { + /// A message composed of [`MessagePiece`]s. + type Message: From>; + /// A piece in a message containing a number of `K`-bit words. /// A [`MessagePiece`] fits in a single base field element, /// which means it can only contain up to `N` words, where @@ -20,9 +25,6 @@ pub trait SinsemillaInstructions, message: Vec>, num_words: usize, - ) -> Result, Error>; + ) -> Result; /// Witness a message piece given a bitstring. Returns a [`MessagePiece`] /// encoding the given message. @@ -84,17 +86,13 @@ pub trait SinsemillaInstructions Result; - /// Prepare a message piece given a [`CellValue`] and the number of words - /// encoded in the contained base field element. - fn prepare_message_piece(cell: &Self::CellValue, num_words: usize) -> Self::MessagePiece; - /// Hashes a message to an ECC curve point. #[allow(non_snake_case)] fn hash_to_point( &self, layouter: impl Layouter, Q: C, - message: Vec, + message: Self::Message, ) -> Result; /// Extracts the x-coordinate of the output of a Sinsemilla hash. @@ -110,7 +108,7 @@ where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { chip: SinsemillaChip, - inner: Vec, + inner: SinsemillaChip::Message, } impl @@ -132,24 +130,9 @@ where fn from_pieces(chip: SinsemillaChip, pieces: Vec) -> Self { Self { chip, - inner: pieces, + inner: pieces.into(), } } - - /// Return the `MessagePiece`s contained in this `Message`. - fn pieces(&self) -> &[SinsemillaChip::MessagePiece] { - &self.inner - } - - /// Construct a `MessagePiece` given a vector of `CellValue`s and the - /// number of words encoded in the contained base field elements. - fn new_piece( - _chip: SinsemillaChip, - cell: &SinsemillaChip::CellValue, - num_words: usize, - ) -> SinsemillaChip::MessagePiece { - SinsemillaChip::prepare_message_piece(cell, num_words) - } } #[allow(non_snake_case)] @@ -337,9 +320,12 @@ mod tests { SinsemillaInstructions, }; - use crate::circuit::gadget::ecc::{ - chip::{EccChip, EccConfig}, - ScalarFixed, + use crate::circuit::gadget::{ + ecc::{ + chip::{EccChip, EccConfig}, + ScalarFixed, + }, + utilities::Var, }; use std::convert::TryInto; @@ -432,7 +418,11 @@ mod tests { )?; let left = merkle_crh.hash_to_point(layouter.namespace(|| "left"), left)?; let left = left.extract_p(); - Message::new_piece(chip1.clone(), left.inner(), 25) + chip1.witness_message_piece_field( + layouter.namespace(|| "witness left piece"), + left.inner().value(), + 25, + )? }; // Right leaf @@ -447,7 +437,11 @@ mod tests { )?; let right = merkle_crh.hash_to_point(layouter.namespace(|| "right"), right)?; let right = right.extract_p(); - Message::new_piece(chip1.clone(), right.inner(), 25) + chip1.witness_message_piece_field( + layouter.namespace(|| "witness left piece"), + right.inner().value(), + 25, + )? }; // Layer 0 diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index f73cbbf68..e4fc605a6 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -1,5 +1,11 @@ -use super::super::ecc::chip::{CellValue, EccPoint}; -use super::{CommitDomains, HashDomains, SinsemillaInstructions}; +use super::{ + message::{Message, MessagePiece}, + CommitDomains, HashDomains, SinsemillaInstructions, +}; +use crate::circuit::gadget::{ + ecc::chip::EccPoint, + utilities::{CellValue, Var}, +}; use crate::constants::OrchardFixedBasesFull; use crate::primitives::sinsemilla::{ @@ -23,27 +29,6 @@ use generator_table::{GeneratorTableChip, GeneratorTableConfig}; mod hash_to_point; -/// A [`MessagePiece`] of some bitlength. -/// -/// The piece must fit within a cell, which means its length cannot exceed -/// the base field's `NUM_BITS`. -#[derive(Clone, Debug)] -pub struct MessagePiece { - cell: CellValue, - // The number of K-bit words in this message piece. - num_words: usize, -} - -impl MessagePiece { - fn new(cell: CellValue, num_words: usize) -> Self { - Self { cell, num_words } - } - - fn num_words(&self) -> usize { - self.num_words - } -} - /// TODO: Configuration for the Sinsemilla hash chip #[derive(Clone, Debug)] pub struct SinsemillaConfig { @@ -199,8 +184,8 @@ impl SinsemillaChip SinsemillaInstructions for SinsemillaChip { - type MessagePiece = MessagePiece; - type CellValue = CellValue; + type Message = Message; + type MessagePiece = MessagePiece; type X = CellValue; type Point = EccPoint; @@ -215,7 +200,7 @@ impl SinsemillaInstructi mut layouter: impl Layouter, message: Vec>, num_words: usize, - ) -> Result, Error> { + ) -> Result { // Message must have at most `MAX_WORDS` words. assert!(num_words <= MAX_WORDS); @@ -224,9 +209,9 @@ impl SinsemillaInstructi // Message piece must be at most `ceil(C::Base::NUM_BITS / 10)` bits let piece_num_words = C::Base::NUM_BITS as usize / K; - message + let pieces: Result, _> = message .chunks(piece_num_words * K) - .map(|piece| { + .map(|piece| -> Result { let num_words = (piece.len() + K - 1) / K; self.witness_message_piece_bitstring( layouter.namespace(|| "message piece"), @@ -234,7 +219,9 @@ impl SinsemillaInstructi num_words, ) }) - .collect() + .collect(); + + pieces.map(|pieces| pieces.into()) } #[allow(non_snake_case)] @@ -282,10 +269,7 @@ impl SinsemillaInstructi 0, || piece_value.ok_or(Error::SynthesisError), )?; - Ok(MessagePiece::new( - CellValue::new(cell, piece_value), - num_words, - )) + Ok(MessagePiece::new(cell, piece_value, num_words)) }, ) } @@ -309,14 +293,7 @@ impl SinsemillaInstructi ) }, )?; - Ok(Self::prepare_message_piece( - &CellValue::new(cell, value), - num_words, - )) - } - - fn prepare_message_piece(cell: &Self::CellValue, length: usize) -> Self::MessagePiece { - MessagePiece::new(cell.clone(), length) + Ok(MessagePiece::new(cell, value, num_words)) } #[allow(non_snake_case)] @@ -324,19 +301,11 @@ impl SinsemillaInstructi &self, mut layouter: impl Layouter, Q: C, - message: Vec, + message: Self::Message, ) -> Result { - let config = self.config(); layouter.assign_region( || "hash_to_point", - |mut region| { - hash_to_point::hash_message::( - &mut region, - config.clone(), - Q, - &message, - ) - }, + |mut region| self.hash_message(&mut region, Q, &message), ) } diff --git a/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs b/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs index 64b735e55..025244cc5 100644 --- a/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs +++ b/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs @@ -1,268 +1,276 @@ -use super::{get_s_by_idx, CellValue, EccPoint, MessagePiece, SinsemillaConfig}; +use super::super::SinsemillaInstructions; +use super::{get_s_by_idx, CellValue, EccPoint, SinsemillaChip, Var}; use crate::primitives::sinsemilla::lebs2ip_k; use halo2::{ arithmetic::{CurveAffine, FieldExt}, - circuit::Region, + circuit::{Chip, Region}, plonk::Error, }; use ff::{Field, PrimeField}; use group::Curve; -#[allow(non_snake_case)] -pub(super) fn hash_message( - region: &mut Region<'_, C::Base>, - config: SinsemillaConfig, - Q: C, - message: &[MessagePiece], -) -> Result, Error> { - // Get the `x`- and `y`-coordinates of the starting `Q` base. - let x_q = *Q.coordinates().unwrap().x(); - let y_q = *Q.coordinates().unwrap().y(); - - // Assign the `x`-coordinate of the starting `Q` base. - let x_q_cell = region.assign_advice(|| "x_q", config.x_a, 0, || Ok(x_q))?; - - // Initialize the accumulator to `Q`. - let mut x_a = CellValue::new(x_q_cell, Some(x_q)); - let mut y_a = Some(y_q); - - // Hash each piece in the message. - let mut offset = 0; - for piece in message.iter() { - // The value of the hash after this piece is processed. - let a = hash_piece::(region, config.clone(), offset, piece, x_a, y_a)?; - - // Since each message word takes one row to process, we increase - // the offset by `piece.num_words` on each iteration. - offset += piece.num_words; - - // Update the accumulator to the latest hash value. - x_a = a.0; - y_a = a.1; - } - - // Assign the final `y_a`. - let y_a_cell = region.assign_advice( - || "y_a", - config.bits, - offset + 1, - || y_a.ok_or(Error::SynthesisError), - )?; - - #[cfg(test)] +impl SinsemillaChip { #[allow(non_snake_case)] - // Check equivalence to result from primitives::sinsemilla::hash_to_point - { - use crate::primitives::sinsemilla::S_PERSONALIZATION; - use halo2::arithmetic::CurveExt; - - // Get message as a bitstring. - let bitstring: Vec = message - .iter() - .map(|piece: &MessagePiece| { - piece - .cell - .value - .unwrap() - .to_le_bits() - .into_iter() - .take(K * piece.num_words) - .collect::>() - }) - .flatten() - .collect(); - - let hasher_S = C::CurveExt::hash_to_curve(S_PERSONALIZATION); - let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); - - let expected_point = bitstring - .chunks(K) - .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); - let actual_point = C::from_xy(x_a.value.unwrap(), y_a.unwrap()).unwrap(); - assert_eq!(expected_point.to_affine(), actual_point); - } - - // Enable `Sinsemilla` selector on the last double-and-add row for expr1 - config.q_sinsemilla1.enable(region, offset - 1)?; - - Ok(EccPoint { - x: x_a, - y: CellValue::new(y_a_cell, y_a), - }) -} - -#[allow(clippy::type_complexity)] -// Hash a message piece containing `piece.length` number of `K`-bit words. -fn hash_piece( - region: &mut Region<'_, C::Base>, - config: SinsemillaConfig, - offset: usize, - piece: &MessagePiece, - x_a: CellValue, - y_a: Option, -) -> Result<(CellValue, Option), Error> { - // Enable `Sinsemilla` selectors for expr1 and expr2 - for row in 0..(piece.num_words - 1) { - config.q_sinsemilla1.enable(region, offset + row)?; - config.q_sinsemilla2.enable(region, offset + row)?; - } + pub(super) fn hash_message( + &self, + region: &mut Region<'_, C::Base>, + Q: C, + message: &>::Message, + ) -> Result, Error> { + let config = self.config().clone(); + + // Get the `x`- and `y`-coordinates of the starting `Q` base. + let x_q = *Q.coordinates().unwrap().x(); + let y_q = *Q.coordinates().unwrap().y(); + + // Assign the `x`-coordinate of the starting `Q` base. + let x_q_cell = region.assign_advice(|| "x_q", config.x_a, 0, || Ok(x_q))?; + + // Initialize the accumulator to `Q`. + let mut x_a = CellValue::new(x_q_cell, Some(x_q)); + let mut y_a = Some(y_q); + + // Hash each piece in the message. + let mut offset = 0; + for piece in message.0.iter() { + // The value of the hash after this piece is processed. + let a = self.hash_piece(region, offset, piece, x_a, y_a)?; + + // Since each message word takes one row to process, we increase + // the offset by `piece.num_words` on each iteration. + offset += piece.num_words(); + + // Update the accumulator to the latest hash value. + x_a = a.0; + y_a = a.1; + } + + // Assign the final `y_a`. + let y_a_cell = region.assign_advice( + || "y_a", + config.bits, + offset + 1, + || y_a.ok_or(Error::SynthesisError), + )?; - // Message piece as K * piece.length bitstring - let bitstring: Option> = piece.cell.value.map(|value| { - value - .to_le_bits() - .into_iter() - .take(K * piece.num_words) - .collect() - }); - - let words: Option> = bitstring.map(|bitstring| { - bitstring - .chunks_exact(K) - .map(|word| lebs2ip_k(word)) - .collect() - }); - - // Get (x_p, y_p) for each word. We precompute this here so that we can use `batch_normalize()`. - let generators_projective: Option> = words - .clone() - .map(|words| words.iter().map(|word| get_s_by_idx::(*word)).collect()); - let generators: Option> = - generators_projective.map(|generators_projective| { - let mut generators = vec![C::default(); generators_projective.len()]; - C::Curve::batch_normalize(&generators_projective, &mut generators); - generators + #[cfg(test)] + #[allow(non_snake_case)] + // Check equivalence to result from primitives::sinsemilla::hash_to_point + { + use crate::primitives::sinsemilla::S_PERSONALIZATION; + use crate::circuit::gadget::sinsemilla::message::MessagePiece; + use halo2::arithmetic::CurveExt; + + // Get message as a bitstring. + let bitstring: Vec = message + .0 .iter() - .map(|gen| { - let point = gen.coordinates().unwrap(); - (*point.x(), *point.y()) + .map(|piece: &MessagePiece| { + piece + .field_elem() + .unwrap() + .to_le_bits() + .into_iter() + .take(K * piece.num_words()) + .collect::>() }) + .flatten() + .collect(); + + let hasher_S = C::CurveExt::hash_to_curve(S_PERSONALIZATION); + let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); + + let expected_point = bitstring + .chunks(K) + .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); + let actual_point = C::from_xy(x_a.value().unwrap(), y_a.unwrap()).unwrap(); + assert_eq!(expected_point.to_affine(), actual_point); + } + + // Enable `Sinsemilla` selector on the last double-and-add row for expr1 + config.q_sinsemilla1.enable(region, offset - 1)?; + + Ok(EccPoint { + x: x_a, + y: CellValue::new(y_a_cell, y_a), + }) + } + + #[allow(clippy::type_complexity)] + // Hash a message piece containing `piece.length` number of `K`-bit words. + fn hash_piece( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + piece: &>::MessagePiece, + x_a: CellValue, + y_a: Option, + ) -> Result<(CellValue, Option), Error> { + let config = self.config().clone(); + + // Enable `Sinsemilla` selectors for expr1 and expr2 + for row in 0..(piece.num_words() - 1) { + config.q_sinsemilla1.enable(region, offset + row)?; + config.q_sinsemilla2.enable(region, offset + row)?; + } + + // Message piece as K * piece.length bitstring + let bitstring: Option> = piece.field_elem().map(|value| { + value + .to_le_bits() + .into_iter() + .take(K * piece.num_words()) .collect() }); - // Closure to decompose a message piece into `K`-bit pieces with a running sum `z`. - let decompose_message = - |region: &mut Region<'_, C::Base>, words: Vec>| -> Result<(), Error> { - let mut zs = Vec::with_capacity(piece.num_words + 1); - - // Copy message and initialize running sum `z` to decompose message in-circuit - let cell = region.assign_advice( - || "copy message", - config.bits, - offset, - || piece.cell.value.ok_or(Error::SynthesisError), - )?; - region.constrain_equal(&config.perm, piece.cell.cell, cell)?; - zs.push(CellValue::new(cell, piece.cell.value)); - - // Assign cumulative sum such that - // z_i = 2^10 * z_{i + 1} + m_{i + 1} - // => z_{i + 1} = (z_i - m_{i + 1}) / 2^10 - // - // For a message m = m_1 + 2^K m_2 + ... + 2^{Kl} m_l}, initialize z_0 = m. - // We end up with z_{l} = 0. - let mut z = piece.cell.value; - let inv_2_k = C::Base::from_u64(1 << K).invert().unwrap(); // TODO: Constant - for (idx, word) in words.iter().enumerate() { - // z_next = (z_cur - m_next) / 2^K - z = z - .zip(*word) - .map(|(z, word)| (z - C::Base::from_u64(word as u64)) * inv_2_k); + let words: Option> = bitstring.map(|bitstring| { + bitstring + .chunks_exact(K) + .map(|word| lebs2ip_k(word)) + .collect() + }); + + // Get (x_p, y_p) for each word. We precompute this here so that we can use `batch_normalize()`. + let generators_projective: Option> = words + .clone() + .map(|words| words.iter().map(|word| get_s_by_idx::(*word)).collect()); + let generators: Option> = + generators_projective.map(|generators_projective| { + let mut generators = vec![C::default(); generators_projective.len()]; + C::Curve::batch_normalize(&generators_projective, &mut generators); + generators + .iter() + .map(|gen| { + let point = gen.coordinates().unwrap(); + (*point.x(), *point.y()) + }) + .collect() + }); + + // Closure to decompose a message piece into `K`-bit pieces with a running sum `z`. + let decompose_message = + |region: &mut Region<'_, C::Base>, words: Vec>| -> Result<(), Error> { + let mut zs = Vec::with_capacity(piece.num_words() + 1); + + // Copy message and initialize running sum `z` to decompose message in-circuit let cell = region.assign_advice( - || format!("z_{:?}", idx + 1), + || "copy message", config.bits, - offset + idx + 1, - || z.ok_or(Error::SynthesisError), + offset, + || piece.field_elem().ok_or(Error::SynthesisError), )?; - zs.push(CellValue::new(cell, z)) - } + region.constrain_equal(&config.perm, piece.cell(), cell)?; + zs.push(CellValue::new(cell, piece.field_elem())); + + // Assign cumulative sum such that + // z_i = 2^10 * z_{i + 1} + m_{i + 1} + // => z_{i + 1} = (z_i - m_{i + 1}) / 2^10 + // + // For a message m = m_1 + 2^K m_2 + ... + 2^{Kl} m_l}, initialize z_0 = m. + // We end up with z_{l} = 0. + let mut z = piece.field_elem(); + let inv_2_k = C::Base::from_u64(1 << K).invert().unwrap(); // TODO: Constant + for (idx, word) in words.iter().enumerate() { + // z_next = (z_cur - m_next) / 2^K + z = z + .zip(*word) + .map(|(z, word)| (z - C::Base::from_u64(word as u64)) * inv_2_k); + let cell = region.assign_advice( + || format!("z_{:?}", idx + 1), + config.bits, + offset + idx + 1, + || z.ok_or(Error::SynthesisError), + )?; + zs.push(CellValue::new(cell, z)) + } + + Ok(()) + }; + + // Convert `words` from `Option>` to `Vec>` + let words: Vec> = if let Some(words) = words { + words.into_iter().map(Some).collect() + } else { + vec![None; piece.num_words()] + }; + + // Decompose message into words using a running sum. + decompose_message(region, words)?; + + let mut x_a_cell = x_a.cell(); + let mut x_a = x_a.value(); + let mut y_a = y_a; - Ok(()) + let generators: Vec> = if let Some(generators) = generators { + generators.into_iter().map(Some).collect() + } else { + vec![None; piece.num_words()] }; - // Convert `words` from `Option>` to `Vec>` - let words: Vec> = if let Some(words) = words { - words.into_iter().map(Some).collect() - } else { - vec![None; piece.num_words] - }; - - // Decompose message into words using a running sum. - decompose_message(region, words)?; - - let mut x_a_cell = x_a.cell; - let mut x_a = x_a.value; - let mut y_a = y_a; - - let generators: Vec> = if let Some(generators) = generators { - generators.into_iter().map(Some).collect() - } else { - vec![None; piece.num_words] - }; - - for (row, gen) in generators.iter().enumerate() { - let x_p = gen.map(|gen| gen.0); - let y_p = gen.map(|gen| gen.1); - - // Assign `x_p` - region.assign_advice( - || "x_p", - config.x_p, - offset + row, - || x_p.ok_or(Error::SynthesisError), - )?; + for (row, gen) in generators.iter().enumerate() { + let x_p = gen.map(|gen| gen.0); + let y_p = gen.map(|gen| gen.1); - // Compute and assign `lambda1, lambda2` - let lambda1 = x_a - .zip(y_a) - .zip(x_p) - .zip(y_p) - .map(|(((x_a, y_a), x_p), y_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); - let x_r = lambda1 - .zip(x_a) - .zip(x_p) - .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); - let lambda2 = x_a - .zip(y_a) - .zip(x_r) - .zip(lambda1) - .map(|(((x_a, y_a), x_r), lambda1)| { - C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 - }); - region.assign_advice( - || "lambda1", - config.lambda.0, - offset + row, - || lambda1.ok_or(Error::SynthesisError), - )?; - region.assign_advice( - || "lambda2", - config.lambda.1, - offset + row, - || lambda2.ok_or(Error::SynthesisError), - )?; + // Assign `x_p` + region.assign_advice( + || "x_p", + config.x_p, + offset + row, + || x_p.ok_or(Error::SynthesisError), + )?; - // Compute and assign `x_a` for the next row - let x_a_new = lambda2 - .zip(x_a) - .zip(x_r) - .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); - y_a = lambda2 - .zip(x_a) - .zip(x_a_new) - .zip(y_a) - .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); - - x_a_cell = region.assign_advice( - || "x_a", - config.x_a, - offset + row + 1, - || x_a_new.ok_or(Error::SynthesisError), - )?; + // Compute and assign `lambda1, lambda2` + let lambda1 = x_a + .zip(y_a) + .zip(x_p) + .zip(y_p) + .map(|(((x_a, y_a), x_p), y_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + let x_r = lambda1 + .zip(x_a) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + let lambda2 = x_a + .zip(y_a) + .zip(x_r) + .zip(lambda1) + .map(|(((x_a, y_a), x_r), lambda1)| { + C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda1", + config.lambda.0, + offset + row, + || lambda1.ok_or(Error::SynthesisError), + )?; + region.assign_advice( + || "lambda2", + config.lambda.1, + offset + row, + || lambda2.ok_or(Error::SynthesisError), + )?; - x_a = x_a_new; - } + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = lambda2 + .zip(x_a) + .zip(x_a_new) + .zip(y_a) + .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); + + x_a_cell = region.assign_advice( + || "x_a", + config.x_a, + offset + row + 1, + || x_a_new.ok_or(Error::SynthesisError), + )?; - Ok((CellValue::new(x_a_cell, x_a), y_a)) + x_a = x_a_new; + } + + Ok((CellValue::new(x_a_cell, x_a), y_a)) + } } diff --git a/src/circuit/gadget/sinsemilla/message.rs b/src/circuit/gadget/sinsemilla/message.rs new file mode 100644 index 000000000..7b3300cd4 --- /dev/null +++ b/src/circuit/gadget/sinsemilla/message.rs @@ -0,0 +1,130 @@ +//! Gadget and chips for the Sinsemilla hash function. +use crate::circuit::gadget::utilities::{CellValue, Var}; +use halo2::{arithmetic::FieldExt, circuit::Cell}; +use std::convert::TryInto; +use std::fmt::Debug; + +/// A [`Message`] composed of several [`MessagePiece`]s. +#[derive(Clone, Debug)] +pub struct Message( + pub Vec>, +); + +impl From>> + for Message +{ + fn from(pieces: Vec>) -> Self { + // A message cannot contain more than `MAX_WORDS` words. + assert!(pieces.iter().map(|piece| piece.num_words()).sum::() < MAX_WORDS); + Message(pieces) + } +} + +/// A [`MessagePiece`] of some bitlength. +/// +/// The piece must fit within a base field element, which means its length +/// cannot exceed the base field's `NUM_BITS`. +#[derive(Copy, Clone, Debug)] +pub struct MessagePiece { + cell: Cell, + field_elem: Option, + // The number of K-bit words in this message piece. + num_words: usize, +} + +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 { + Self { + cell, + field_elem, + num_words, + } + } + + pub fn num_words(&self) -> usize { + self.num_words + } + + pub fn cell(&self) -> Cell { + self.cell + } + + pub fn field_elem(&self) -> Option { + self.field_elem + } + + // Assembles a field element encoding a few subpieces and the number of + // words encoded by them. + pub fn from_subpieces(subpieces: &[MessageSubPiece], num_words: usize) -> Option { + // The number of words in the message piece should be consistent with the number of bits used + // in the subpieces. + let total_num_bits = subpieces + .iter() + .map(|subpiece| subpiece.bit_range.len()) + .sum::(); + assert_eq!(num_words * K, total_num_bits); + + // The number of words in the message piece must not exceed the field element's capacity. + assert!(num_words <= F::NUM_BITS as usize / K); + + let bits: Vec> = subpieces.iter().fold(Vec::new(), |mut acc, subpiece| { + let bits = if let Some(value) = subpiece.field_elem { + value + .to_le_bits() + .iter() + .by_val() + .take(F::NUM_BITS as usize) + .map(Some) + .collect() + } else { + vec![None; total_num_bits] + }; + + let bits = bits[subpiece.bit_range.clone()].to_vec(); + acc.extend_from_slice(&bits); + acc + }); + + let bits: Option> = bits.into_iter().collect(); + + bits.map(|bits| bits_to_field_elem(&bits)) + } +} + +// Closure to parse a bitstring (little-endian) into a field element. +pub fn bits_to_field_elem(bits: &[bool]) -> F { + // Pad to 256 bits + let pad_len = 256 - bits.len(); + let mut bits = bits.to_vec(); + bits.extend_from_slice(&vec![false; pad_len]); + + let bytearray: Vec = bits + .chunks_exact(8) + .map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8)) + .collect(); + + F::from_bytes(&bytearray.try_into().unwrap()).unwrap() +} + +#[derive(Clone, Debug)] +pub struct MessageSubPiece { + field_elem: Option, + bit_range: std::ops::Range, +} + +impl From<(Option, std::ops::Range)> + for MessageSubPiece +{ + fn from(field_elem_bit_range: (Option, std::ops::Range)) -> Self { + Self { + field_elem: field_elem_bit_range.0, + bit_range: field_elem_bit_range.1, + } + } +}