Skip to content

Commit

Permalink
sinsemilla::hash_to_point.rs: Return message decomposition from hash_…
Browse files Browse the repository at this point in the history
…to_point

A cumulative sum z is used to decompose a Sinsemilla message. It
produced intermediate values for each word in the message, such
that z_next = (z_cur - word_next) / 2^K.

These intermediate values are useful for range checks on subsets
of the Sinsemilla message. Sinsemilla messages in the Orchard
protocol are composed of field elements, and we need to check
the canonicity of the field element encodings in certain cases.
  • Loading branch information
therealyingtong committed Jun 6, 2021
1 parent 42eadef commit 6eba421
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 71 deletions.
25 changes: 22 additions & 3 deletions src/circuit/gadget/sinsemilla.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Gadget and chips for the Sinsemilla hash function.
use crate::circuit::gadget::ecc::{self, EccInstructions};
use crate::circuit::gadget::{
ecc::{self, EccInstructions},
utilities::Var,
};
use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error};
use std::fmt::Debug;

Expand All @@ -13,6 +16,9 @@ pub use chip::{SinsemillaChip, SinsemillaConfig};
/// in each word accepted by the Sinsemilla hash, and `MAX_WORDS`, the maximum
/// number of words that a single hash instance can process.
pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS: usize> {
/// A variable in the circuit.
type CellValue: Var<C::Base>;

/// A message composed of [`MessagePiece`]s.
type Message: From<Vec<Self::MessagePiece>>;

Expand Down Expand Up @@ -87,13 +93,26 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
) -> Result<Self::MessagePiece, Error>;

/// Hashes a message to an ECC curve point.
/// This returns both the resulting point, as well as the message
/// decomposition in the form of intermediate values in a cumulative
/// sum.
///
/// A cumulative sum `z` is used to decompose a Sinsemilla message. It
/// produced intermediate values for each word in the message, such
/// that `z_next` = (`z_cur` - `word_next`) / `2^K`.
///
/// These intermediate values are useful for range checks on subsets
/// of the Sinsemilla message. Sinsemilla messages in the Orchard
/// protocol are composed of field elements, and we need to check
/// the canonicity of the field element encodings in certain cases.
///
#[allow(non_snake_case)]
fn hash_to_point(
&self,
layouter: impl Layouter<C::Base>,
Q: C,
message: Self::Message,
) -> Result<Self::Point, Error>;
) -> Result<(Self::Point, Vec<Self::CellValue>), Error>;

/// Extracts the x-coordinate of the output of a Sinsemilla hash.
fn extract(point: &Self::Point) -> &Self::X;
Expand Down Expand Up @@ -194,7 +213,7 @@ where
assert_eq!(self.sinsemilla_chip, message.chip);
self.sinsemilla_chip
.hash_to_point(layouter, self.Q, message.inner)
.map(|point| ecc::Point::from_inner(self.ecc_chip.clone(), point))
.map(|(point, _)| ecc::Point::from_inner(self.ecc_chip.clone(), point))
}

/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
Expand Down
4 changes: 3 additions & 1 deletion src/circuit/gadget/sinsemilla/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaInstructions<C, K, MAX_WORDS>
for SinsemillaChip<C, K, MAX_WORDS>
{
type CellValue = CellValue<C::Base>;

type Message = Message<C::Base, K, MAX_WORDS>;
type MessagePiece = MessagePiece<C::Base, K>;

Expand Down Expand Up @@ -299,7 +301,7 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaInstructi
mut layouter: impl Layouter<C::Base>,
Q: C,
message: Self::Message,
) -> Result<Self::Point, Error> {
) -> Result<(Self::Point, Vec<Self::CellValue>), Error> {
layouter.assign_region(
|| "hash_to_point",
|mut region| self.hash_message(&mut region, Q, &message),
Expand Down
175 changes: 108 additions & 67 deletions src/circuit/gadget/sinsemilla/chip/hash_to_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ use halo2::{
use ff::{Field, PrimeField};
use group::Curve;

use std::ops::Deref;

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> {
) -> Result<(EccPoint<C>, Vec<CellValue<C::Base>>), Error> {
let config = self.config().clone();

// Get the `x`- and `y`-coordinates of the starting `Q` base.
Expand All @@ -29,22 +31,24 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
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);
let mut x_a: X<C::Base> = CellValue::new(x_q_cell, Some(x_q)).into();
let mut y_a: Y<C::Base> = Some(y_q).into();
let mut zs_sum: Vec<CellValue<C::Base>> = Vec::new();

// 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)?;
let (x, y, zs) = 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;
x_a = x;
y_a = y;
zs_sum.extend_from_slice(&zs);
}

// Assign the final `y_a`.
Expand Down Expand Up @@ -97,10 +101,13 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
// 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),
})
Ok((
EccPoint {
x: x_a.0,
y: CellValue::new(y_a_cell, y_a.0),
},
zs_sum,
))
}

#[allow(clippy::type_complexity)]
Expand All @@ -110,9 +117,9 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
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> {
x_a: X<C::Base>,
y_a: Y<C::Base>,
) -> Result<(X<C::Base>, Y<C::Base>, Vec<CellValue<C::Base>>), Error> {
let config = self.config().clone();

// Enable `Sinsemilla` selectors for expr1 and expr2
Expand Down Expand Up @@ -154,63 +161,59 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
.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)?;
// Decompose message into `K`-bit pieces with a running sum `z`.
let zs = {
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))
}

zs
};

// Copy in the accumulator x-coordinate
let x_a = copy(
region,
|| "Initialize accumulator x-coordinate",
config.x_a,
offset,
&x_a,
&x_a.0,
&config.perm,
)?;

Expand Down Expand Up @@ -238,21 +241,21 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K

// Compute and assign `lambda1, lambda2`
let lambda1 = x_a
.zip(y_a)
.zip(y_a.0)
.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
});
let lambda2 =
x_a.zip(y_a.0)
.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,
Expand All @@ -271,11 +274,12 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
.zip(x_a)
.zip(x_r)
.map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r);
y_a = lambda2
let y_a_val = lambda2
.zip(x_a)
.zip(x_a_new)
.zip(y_a)
.zip(y_a.0)
.map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a);
y_a = y_a_val.into();

x_a_cell = region.assign_advice(
|| "x_a",
Expand All @@ -287,6 +291,43 @@ impl<C: CurveAffine, const K: usize, const MAX_WORDS: usize> SinsemillaChip<C, K
x_a = x_a_new;
}

Ok((CellValue::new(x_a_cell, x_a), y_a))
Ok((CellValue::new(x_a_cell, x_a).into(), y_a.into(), zs))
}
}

// The x-coordinate of the accumulator in a Sinsemilla hash instance.
struct X<F: FieldExt>(CellValue<F>);

impl<F: FieldExt> From<CellValue<F>> for X<F> {
fn from(cell_value: CellValue<F>) -> Self {
X(cell_value)
}
}

impl<F: FieldExt> Deref for X<F> {
type Target = CellValue<F>;

fn deref(&self) -> &CellValue<F> {
&self.0
}
}

// The y-coordinate of the accumulator in a Sinsemilla hash instance.
// This is never actually witnessed until the last round, since it
// can be derived from other variables. Thus it only exists as a field
// element, not a `CellValue`.
struct Y<F: FieldExt>(Option<F>);

impl<F: FieldExt> From<Option<F>> for Y<F> {
fn from(value: Option<F>) -> Self {
Y(value)
}
}

impl<F: FieldExt> Deref for Y<F> {
type Target = Option<F>;

fn deref(&self) -> &Option<F> {
&self.0
}
}

0 comments on commit 6eba421

Please sign in to comment.