Skip to content

Commit

Permalink
chip::hash_to_point.rs: Implement hash_to_point instruction.
Browse files Browse the repository at this point in the history
  • Loading branch information
therealyingtong committed Jun 6, 2021
1 parent 91e7409 commit 1865fc4
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 3 deletions.
57 changes: 55 additions & 2 deletions src/circuit/gadget/sinsemilla/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod generator_table;
pub use generator_table::get_s_by_idx;
use generator_table::GeneratorTableConfig;

// mod hash_to_point;
mod hash_to_point;

/// Configuration for the Sinsemilla hash chip
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -114,6 +114,56 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
let generator_table =
GeneratorTableConfig::configure(meta, q_sinsemilla1, lookup, word, x_a, x_p, lambda);

// Create custom gates
meta.create_gate("Sinsemilla gate 1", |meta| {
// (λ_{2,i})^2 − (x_{A,i+1} + (λ_{1,i}^2 − x_{A,i} − x_{P,i}) + x_{A,i})
let expr1 = {
let q_sinsemilla1 = meta.query_selector(q_sinsemilla1, Rotation::cur());
let lambda1 = meta.query_advice(lambda.0, Rotation::cur());
let lambda1_sq = lambda1.clone() * lambda1;
let lambda2 = meta.query_advice(lambda.1, Rotation::cur());
let lambda2_sq = lambda2.clone() * lambda2;
let x_a_next = meta.query_advice(x_a, Rotation::next());
let x_p_cur = meta.query_advice(x_p, Rotation::cur());

q_sinsemilla1 * (lambda2_sq - (x_a_next + (lambda1_sq - x_p_cur)))
};
vec![expr1]
});

meta.create_gate("Sinsemilla gate 2", |meta| {
// 2⋅λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − (y_{A,i} + y_{A,i+1})
// where y_{A,j} = [(λ_{1,j} + λ_{2,j})⋅(x_{A,j} − (λ_{1,j}^2 − x_{A,j} − x_{P,j}))] / 2
let expr2 = {
let q_sinsemilla2 = meta.query_selector(q_sinsemilla2, Rotation::cur());
let lambda1_cur = meta.query_advice(lambda.0, Rotation::cur());
let lambda1_cur_sq = lambda1_cur.clone() * lambda1_cur.clone();
let lambda1_next = meta.query_advice(lambda.0, Rotation::next());
let lambda1_next_sq = lambda1_next.clone() * lambda1_next.clone();
let lambda2_cur = meta.query_advice(lambda.1, Rotation::cur());
let lambda2_next = meta.query_advice(lambda.1, Rotation::next());
let x_a_cur = meta.query_advice(x_a, Rotation::cur());
let x_a_next = meta.query_advice(x_a, Rotation::next());
let x_p_cur = meta.query_advice(x_p, Rotation::cur());
let x_p_next = meta.query_advice(x_p, Rotation::next());

// y_{A,i} = [(λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i}))] / 2
let y_a_cur = (lambda1_cur + lambda2_cur.clone())
* (x_a_cur.clone() - (lambda1_cur_sq - x_a_cur.clone() - x_p_cur))
* C::Base::TWO_INV;

// y_{A,i+1} = [(λ_{1,i+1} + λ_{2,i+1})⋅(x_{A,i+1} − (λ_{1,i+1}^2 − x_{A,i+1} − x_{P,i+1}))] / 2
let y_a_next = (lambda1_next + lambda2_next)
* (x_a_next.clone() - (lambda1_next_sq - x_a_next.clone() - x_p_next))
* C::Base::TWO_INV;

// 2⋅λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − (y_{A,i} + y_{A,i+1})
q_sinsemilla2 * (lambda2_cur * (x_a_cur - x_a_next) - (y_a_cur + y_a_next))
};

vec![expr2]
});

SinsemillaConfig {
q_sinsemilla1,
q_sinsemilla2,
Expand Down Expand Up @@ -250,7 +300,10 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaInstructi
Q: C,
message: Self::Message,
) -> Result<Self::Point, Error> {
todo!()
layouter.assign_region(
|| "hash_to_point",
|mut region| self.hash_message(&mut region, Q, &message),
)
}

fn extract(point: &Self::Point) -> &Self::X {
Expand Down
292 changes: 292 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,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))
}
}
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 1865fc4

Please sign in to comment.