Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Sinsemilla private init point #19

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion halo2_gadgets/src/ecc/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl NonIdentityEccPoint {
///
/// This is an internal API that we only use where we know we have a valid non-identity
/// curve point.
pub(crate) fn from_coordinates_unchecked(
pub fn from_coordinates_unchecked(
x: AssignedCell<Assigned<pallas::Base>, pallas::Base>,
y: AssignedCell<Assigned<pallas::Base>, pallas::Base>,
) -> Self {
Expand Down
60 changes: 60 additions & 0 deletions halo2_gadgets/src/sinsemilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), 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.
///
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point_with_private_init(
&self,
layouter: impl Layouter<C::Base>,
Q: &Self::NonIdentityPoint,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error>;

/// Extracts the x-coordinate of the output of a Sinsemilla hash.
fn extract(point: &Self::NonIdentityPoint) -> Self::X;
}
Expand Down Expand Up @@ -329,6 +343,23 @@ where
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
}

#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
pub fn hash_to_point_with_private_init(
&self,
layouter: impl Layouter<C::Base>,
Q: &<SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
) -> Result<(ecc::NonIdentityPoint<C, EccChip>, Vec<SinsemillaChip::RunningSum>), Error> {
assert_eq!(self.sinsemilla_chip, message.chip);
self.sinsemilla_chip
.hash_to_point_with_private_init(layouter, Q, message.inner)
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
}

/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
Expand Down Expand Up @@ -431,6 +462,35 @@ where
self.M.hash_to_point(layouter, message)
}

#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub fn hash_with_private_init(
&self,
layouter: impl Layouter<C::Base>,
Q: &<SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
) -> Result<
(
ecc::NonIdentityPoint<C, EccChip>,
Vec<SinsemillaChip::RunningSum>,
),
Error,
> {
assert_eq!(self.M.sinsemilla_chip, message.chip);
self.M.hash_to_point_with_private_init(layouter, Q, message)
}

#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub fn q_init(&self) -> C {
self.M.Q
}

#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
Expand Down
37 changes: 35 additions & 2 deletions halo2_gadgets/src/sinsemilla/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ where
/// q_sinsemilla2 is used to define a synthetic selector,
/// q_sinsemilla3 = (q_sinsemilla2) ⋅ (q_sinsemilla2 - 1)
/// Simple selector used to constrain hash initialization to be consistent with
/// the y-coordinate of the domain $Q$.
/// the y-coordinate of the domain $Q$ when $y_Q$ is a public constant.
q_sinsemilla4: Selector,
/// Simple selector used to constrain hash initialization to be consistent with
/// the y-coordinate of the domain $Q$ when $y_Q$ is a private value.
q_sinsemilla4_private: Selector,
/// Fixed column used to load the y-coordinate of the domain $Q$.
fixed_y_q: Column<Fixed>,
/// Logic specific to merged double-and-add.
Expand Down Expand Up @@ -165,6 +168,7 @@ where
q_sinsemilla1: meta.complex_selector(),
q_sinsemilla2: meta.fixed_column(),
q_sinsemilla4: meta.selector(),
q_sinsemilla4_private: meta.selector(),
fixed_y_q,
double_and_add: DoubleAndAdd {
x_a: advices[0],
Expand Down Expand Up @@ -201,7 +205,7 @@ where

// Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q.
// https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial
meta.create_gate("Initial y_Q", |meta| {
meta.create_gate("Initial y_Q (public)", |meta| {
let q_s4 = meta.query_selector(config.q_sinsemilla4);
let y_q = meta.query_fixed(config.fixed_y_q);

Expand All @@ -214,6 +218,21 @@ where
Constraints::with_selector(q_s4, Some(("init_y_q_check", init_y_q_check)))
});

// Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q.
// https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial
meta.create_gate("Initial y_Q (private)", |meta| {
let q_s4 = meta.query_selector(config.q_sinsemilla4_private);
let y_q = meta.query_advice(config.double_and_add.x_p, Rotation::prev());

// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
let Y_A_cur = Y_A(meta, Rotation::cur());

// 2 * y_q - Y_{A,0} = 0
let init_y_q_check = y_q * two - Y_A_cur;

Constraints::with_selector(q_s4, Some(("init_y_q_check", init_y_q_check)))
});

// https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial
meta.create_gate("Sinsemilla gate", |meta| {
let q_s1 = meta.query_selector(config.q_sinsemilla1);
Expand Down Expand Up @@ -321,6 +340,20 @@ where
)
}

#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point_with_private_init(
&self,
mut layouter: impl Layouter<pallas::Base>,
Q: &Self::NonIdentityPoint,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error> {
layouter.assign_region(
|| "hash_to_point",
|mut region| self.hash_message_with_private_init(&mut region, Q, &message),
)
}

fn extract(point: &Self::NonIdentityPoint) -> Self::X {
point.x()
}
Expand Down
138 changes: 138 additions & 0 deletions halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,144 @@ where
))
}

/// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial).
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
pub(super) fn hash_message_with_private_init(
&self,
region: &mut Region<'_, pallas::Base>,
Q: &NonIdentityEccPoint,
message: &<Self as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::Message,
) -> Result<
(
NonIdentityEccPoint,
Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>>,
),
Error,
> {
let config = self.config().clone();
let mut offset = 0;

// Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4
// selector.
let mut y_a: Y<pallas::Base> = {
// Enable `q_sinsemilla4_private` on the first row.
config.q_sinsemilla4_private.enable(region, offset + 1)?;
let q_y: AssignedCell<Assigned<pallas::Base>, pallas::Base> = Q.y().into();
let y_a: AssignedCell<Assigned<pallas::Base>, pallas::Base> =
q_y.copy_advice(|| "fixed y_q", region, config.double_and_add.x_p, offset)?;

y_a.value_field().into()
};
offset += 1;

// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
let mut x_a: X<pallas::Base> = {
let q_x: AssignedCell<Assigned<pallas::Base>, pallas::Base> = Q.x().into();
let x_a = q_x.copy_advice(|| "fixed x_q", region, config.double_and_add.x_a, offset)?;

x_a.into()
};

let mut zs_sum: Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>> = Vec::new();

// Hash each piece in the message.
for (idx, piece) in message.iter().enumerate() {
let final_piece = idx == message.len() - 1;

// The value of the accumulator after this piece is processed.
let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?;

// 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 value.
x_a = x;
y_a = y;
zs_sum.push(zs);
}

// Assign the final y_a.
let y_a = {
// Assign the final y_a.
let y_a_cell =
region.assign_advice(|| "y_a", config.double_and_add.lambda_1, offset, || y_a.0)?;

// Assign lambda_2 and x_p zero values since they are queried
// in the gate. (The actual values do not matter since they are
// multiplied by zero.)
{
region.assign_advice(
|| "dummy lambda2",
config.double_and_add.lambda_2,
offset,
|| Value::known(pallas::Base::zero()),
)?;
region.assign_advice(
|| "dummy x_p",
config.double_and_add.x_p,
offset,
|| Value::known(pallas::Base::zero()),
)?;
}

y_a_cell
};

#[cfg(test)]
#[allow(non_snake_case)]
// Check equivalence to result from primitives::sinsemilla::hash_to_point
{
use crate::sinsemilla::primitives::{K, S_PERSONALIZATION};

use group::{prime::PrimeCurveAffine, Curve};
use pasta_curves::arithmetic::CurveExt;

let field_elems: Value<Vec<_>> = message
.iter()
.map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words())))
.collect();

field_elems
.zip(x_a.value().zip(y_a.value()))
.zip(Q.point())
.assert_if_known(|((field_elems, (x_a, y_a)), Q)| {
// Get message as a bitstring.
let bitstring: Vec<bool> = field_elems
.iter()
.flat_map(|(elem, num_words)| {
elem.to_le_bits().into_iter().take(K * num_words)
})
.collect();

let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());

// We can use complete addition here because it differs from
// incomplete addition with negligible probability.
let expected_point = bitstring
.chunks(K)
.fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc);
let actual_point =
pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap();
expected_point.to_affine() == actual_point
});
}

x_a.value()
.zip(y_a.value())
.error_if_known_and(|(x_a, y_a)| x_a.is_zero_vartime() || y_a.is_zero_vartime())?;
Ok((
NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a),
zs_sum,
))
}

#[allow(clippy::type_complexity)]
/// Hashes a message piece containing `piece.length` number of `K`-bit words.
///
Expand Down
13 changes: 13 additions & 0 deletions halo2_gadgets/src/sinsemilla/merkle/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,19 @@ where
chip.hash_to_point(layouter, Q, message)
}

#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point_with_private_init(
&self,
layouter: impl Layouter<pallas::Base>,
Q: &Self::NonIdentityPoint,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Vec<Self::CellValue>>), Error> {
let config = self.config().sinsemilla_config.clone();
let chip = SinsemillaChip::<Hash, Commit, F>::construct(config);
chip.hash_to_point_with_private_init(layouter, Q, message)
}

fn extract(point: &Self::NonIdentityPoint) -> Self::X {
SinsemillaChip::<Hash, Commit, F>::extract(point)
}
Expand Down