diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 79e5032ab..8920ddcd1 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -1,5 +1,8 @@ //! Gadget and chips for the Sinsemilla hash function. -use crate::circuit::gadget::ecc::{self, EccInstructions}; +use crate::circuit::gadget::{ + ecc::{self, EccInstructions}, + utilities::Var, +}; use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; use std::fmt::Debug; @@ -13,6 +16,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 variable in the circuit. + type CellValue: Var; + /// A message composed of [`MessagePiece`]s. type Message: From>; @@ -87,13 +93,26 @@ pub trait SinsemillaInstructions Result; /// Hashes a message to an ECC curve point. + /// This returns both the resulting point, as well as the message + /// decomposition in the form of intermediate values in a cumulative + /// sum. + /// + /// A cumulative sum `z` is used to decompose a Sinsemilla message. It + /// produced intermediate values for each word in the message, such + /// that `z_next` = (`z_cur` - `word_next`) / `2^K`. + /// + /// These intermediate values are useful for range checks on subsets + /// of the Sinsemilla message. Sinsemilla messages in the Orchard + /// protocol are composed of field elements, and we need to check + /// the canonicity of the field element encodings in certain cases. + /// #[allow(non_snake_case)] fn hash_to_point( &self, layouter: impl Layouter, Q: C, message: Self::Message, - ) -> Result; + ) -> Result<(Self::Point, Vec), Error>; /// Extracts the x-coordinate of the output of a Sinsemilla hash. fn extract(point: &Self::Point) -> &Self::X; @@ -194,7 +213,7 @@ where assert_eq!(self.sinsemilla_chip, message.chip); self.sinsemilla_chip .hash_to_point(layouter, self.Q, message.inner) - .map(|point| ecc::Point::from_inner(self.ecc_chip.clone(), point)) + .map(|(point, _)| ecc::Point::from_inner(self.ecc_chip.clone(), point)) } /// $\mathsf{SinsemillaHash}$ from [ยง 5.4.1.9][concretesinsemillahash]. diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index 340c63e85..1bdc9cd19 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -164,6 +164,8 @@ impl SinsemillaChip SinsemillaInstructions for SinsemillaChip { + type CellValue = CellValue; + type Message = Message; type MessagePiece = MessagePiece; @@ -282,7 +284,7 @@ impl SinsemillaInstructi mut layouter: impl Layouter, Q: C, message: Self::Message, - ) -> Result { + ) -> Result<(Self::Point, Vec), Error> { layouter.assign_region( || "hash_to_point", |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 f50020306..9874ca0ca 100644 --- a/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs +++ b/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs @@ -11,6 +11,8 @@ use halo2::{ use ff::{Field, PrimeField}; use group::Curve; +use std::ops::Deref; + impl SinsemillaChip { #[allow(non_snake_case)] pub(super) fn hash_message( @@ -18,7 +20,7 @@ impl SinsemillaChip, Q: C, message: &>::Message, - ) -> Result, Error> { + ) -> Result<(EccPoint, Vec>), Error> { let config = self.config().clone(); // Get the `x`- and `y`-coordinates of the starting `Q` base. @@ -29,22 +31,24 @@ impl SinsemillaChip = CellValue::new(x_q_cell, Some(x_q)).into(); + let mut y_a: Y = Some(y_q).into(); + let mut zs_sum: Vec> = Vec::new(); // 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)?; + let (x, y, zs) = 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; + x_a = x; + y_a = y; + zs_sum.extend_from_slice(&zs); } // Assign the final `y_a`. @@ -97,10 +101,13 @@ impl SinsemillaChip SinsemillaChip, offset: usize, piece: &>::MessagePiece, - x_a: CellValue, - y_a: Option, - ) -> Result<(CellValue, Option), Error> { + x_a: X, + y_a: Y, + ) -> Result<(X, Y, Vec>), Error> { let config = self.config().clone(); // Enable `Sinsemilla` selectors for expr1 and expr2 @@ -154,46 +161,6 @@ impl SinsemillaChip, 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.word, - offset, - || piece.field_elem().ok_or(Error::SynthesisError), - )?; - 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.word, - 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() @@ -201,8 +168,44 @@ impl SinsemillaChip 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.word, + offset + idx + 1, + || z.ok_or(Error::SynthesisError), + )?; + zs.push(CellValue::new(cell, z)) + } + + zs + }; // Copy in the accumulator x-coordinate let x_a = copy( @@ -210,7 +213,7 @@ impl SinsemillaChip SinsemillaChip SinsemillaChip SinsemillaChip SinsemillaChip(CellValue); + +impl From> for X { + fn from(cell_value: CellValue) -> Self { + X(cell_value) + } +} + +impl Deref for X { + type Target = CellValue; + + fn deref(&self) -> &CellValue { + &self.0 + } +} + +// The y-coordinate of the accumulator in a Sinsemilla hash instance. +// This is never actually witnessed until the last round, since it +// can be derived from other variables. Thus it only exists as a field +// element, not a `CellValue`. +struct Y(Option); + +impl From> for Y { + fn from(value: Option) -> Self { + Y(value) + } +} + +impl Deref for Y { + type Target = Option; + + fn deref(&self) -> &Option { + &self.0 } }