Skip to content

Commit

Permalink
Implement hash_to_point
Browse files Browse the repository at this point in the history
  • Loading branch information
therealyingtong committed Jun 2, 2021
1 parent 773d143 commit bd9007c
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 2 deletions.
8 changes: 7 additions & 1 deletion src/circuit/gadget/sinsemilla/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -350,7 +352,11 @@ impl<C: CurveAffine> SinsemillaInstructions<C> for SinsemillaChip<C> {
Q: C,
message: Vec<Self::MessagePiece>,
) -> Result<Self::Point, Error> {
todo!()
let config = self.config();
layouter.assign_region(
|| "hash_to_point",
|mut region| hash_to_point::hash_message::<C>(&mut region, config.clone(), Q, &message),
)
}
}

Expand Down
260 changes: 260 additions & 0 deletions src/circuit/gadget/sinsemilla/chip/hash_to_point.rs
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))
}
2 changes: 1 addition & 1 deletion src/primitives/sinsemilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit bd9007c

Please sign in to comment.