-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
773d143
commit bd9007c
Showing
3 changed files
with
268 additions
and
2 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,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<C: CurveAffine>( | ||
region: &mut Region<'_, C::Base>, | ||
config: SinsemillaConfig, | ||
Q: C, | ||
message: &[MessagePiece<C::Base>], | ||
) -> Result<EccPoint<C>, 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::<C>(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<bool> = message | ||
.iter() | ||
.map(|piece: &MessagePiece<C::Base>| { | ||
piece | ||
.cell | ||
.value | ||
.unwrap() | ||
.to_le_bits() | ||
.into_iter() | ||
.take(K * piece.length) | ||
.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), | ||
}) | ||
} | ||
|
||
// Hash a message piece containing `piece.length` number of `K`-bit words. | ||
fn hash_piece<C: CurveAffine>( | ||
region: &mut Region<'_, C::Base>, | ||
config: SinsemillaConfig, | ||
offset: usize, | ||
piece: &MessagePiece<C::Base>, | ||
x_a: CellValue<C::Base>, | ||
y_a: Option<C::Base>, | ||
) -> Result<(CellValue<C::Base>, Option<C::Base>), 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<Vec<bool>> = piece.cell.value.map(|value| { | ||
value | ||
.to_le_bits() | ||
.into_iter() | ||
.take(K * piece.len()) | ||
.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.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<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.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<Option<(C::Base, C::Base)>> = 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)) | ||
} |
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