diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index b8dddbebf..019453629 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -24,6 +24,8 @@ mod generator_table; pub use generator_table::get_s_by_idx; 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 @@ -350,7 +352,11 @@ impl SinsemillaInstructions for SinsemillaChip { Q: C, message: Vec, ) -> Result { - todo!() + let config = self.config(); + layouter.assign_region( + || "hash_to_point", + |mut region| hash_to_point::hash_message::(&mut region, config.clone(), Q, &message), + ) } } diff --git a/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs b/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs new file mode 100644 index 000000000..5a9cda214 --- /dev/null +++ b/src/circuit/gadget/sinsemilla/chip/hash_to_point.rs @@ -0,0 +1,260 @@ +use super::{get_s_by_idx, CellValue, EccPoint, MessagePiece, SinsemillaConfig}; +use crate::primitives::sinsemilla::{lebs2ip_k, K}; +use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::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> { + let x_q = Q.coordinates().unwrap().x().clone(); + let y_q = Q.coordinates().unwrap().y().clone(); + + // Assign the `x`-coordinate of our starting `Q` base. + let x_q_cell = region.assign_advice(|| "x_q", config.x_a, 0, || Ok(x_q))?; + + let mut x_a = CellValue::new(x_q_cell, Some(x_q)); + let mut y_a = Some(y_q); + + // a, b, c, .. , h + let mut offset = 0; + for piece in message.iter() { + let a = hash_piece::(region, config.clone(), offset, piece, x_a, y_a)?; + offset = offset + piece.length; + x_a = a.0; + y_a = a.1; + } + + // Finalize + // 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)] + #[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.length) + .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), + }) +} + +// 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.length - 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.cell.value.map(|value| { + value + .to_le_bits() + .into_iter() + .take(K * piece.len()) + .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 + .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.length + 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 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.length] + }; + + // 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.length] + }; + + 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), + )?; + + // 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), + )?; + + // 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), + )?; + + x_a = x_a_new; + } + + Ok((CellValue::new(x_a_cell, x_a), y_a)) +} diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 685b76997..ffe827e9e 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -13,7 +13,7 @@ mod constants; mod sinsemilla_s; pub use constants::*; -fn lebs2ip_k(bits: &[bool]) -> u32 { +pub fn lebs2ip_k(bits: &[bool]) -> u32 { assert!(bits.len() == K); bits.iter() .enumerate()