-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chip::hash_to_point.rs: Implement hash_to_point instruction.
- Loading branch information
1 parent
91e7409
commit 1865fc4
Showing
3 changed files
with
348 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
use super::super::SinsemillaInstructions; | ||
use super::{get_s_by_idx, CellValue, EccPoint, SinsemillaChip, Var}; | ||
use crate::circuit::gadget::utilities::copy; | ||
use crate::primitives::sinsemilla::lebs2ip_k; | ||
use halo2::{ | ||
arithmetic::{CurveAffine, FieldExt}, | ||
circuit::{Chip, Region}, | ||
plonk::Error, | ||
}; | ||
|
||
use ff::{Field, PrimeField}; | ||
use group::Curve; | ||
|
||
impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K, MAX_WORDS> { | ||
#[allow(non_snake_case)] | ||
pub(super) fn hash_message( | ||
&self, | ||
region: &mut Region<'_, C::Base>, | ||
Q: C, | ||
message: &<Self as SinsemillaInstructions<C, K, MAX_WORDS>>::Message, | ||
) -> Result<EccPoint<C>, 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.word, | ||
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::circuit::gadget::sinsemilla::message::MessagePiece; | ||
use crate::primitives::sinsemilla::S_PERSONALIZATION; | ||
use halo2::arithmetic::CurveExt; | ||
|
||
let field_elems: Option<Vec<C::Base>> = | ||
message.0.iter().map(|piece| piece.field_elem()).collect(); | ||
|
||
if let Some(_) = field_elems { | ||
// Get message as a bitstring. | ||
let bitstring: Vec<bool> = message | ||
.0 | ||
.iter() | ||
.map(|piece: &MessagePiece<C::Base, K>| { | ||
piece | ||
.field_elem() | ||
.unwrap() | ||
.to_le_bits() | ||
.into_iter() | ||
.take(K * piece.num_words()) | ||
.collect::<Vec<_>>() | ||
}) | ||
.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: &<Self as SinsemillaInstructions<C, K, MAX_WORDS>>::MessagePiece, | ||
x_a: CellValue<C::Base>, | ||
y_a: Option<C::Base>, | ||
) -> Result<(CellValue<C::Base>, Option<C::Base>), 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<Vec<bool>> = piece.field_elem().map(|value| { | ||
value | ||
.to_le_bits() | ||
.into_iter() | ||
.take(K * piece.num_words()) | ||
.collect() | ||
}); | ||
|
||
let words: Option<Vec<u32>> = 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<Vec<C::CurveExt>> = words | ||
.clone() | ||
.map(|words| words.iter().map(|word| get_s_by_idx::<C>(*word)).collect()); | ||
let generators: Option<Vec<(C::Base, C::Base)>> = | ||
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<Option<u32>>| -> 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<Vec<u32>>` to `Vec<Option<u32>>` | ||
let words: Vec<Option<u32>> = 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)?; | ||
|
||
// Copy in the accumulator x-coordinate | ||
let x_a = copy( | ||
region, | ||
|| "Initialize accumulator x-coordinate", | ||
config.x_a, | ||
offset, | ||
&x_a, | ||
&config.perm, | ||
)?; | ||
|
||
let mut x_a_cell = x_a.cell(); | ||
let mut x_a = x_a.value(); | ||
let mut y_a = y_a; | ||
|
||
let generators: Vec<Option<(C::Base, C::Base)>> = 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), | ||
)?; | ||
|
||
// 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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters