Skip to content
Open
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
5 changes: 4 additions & 1 deletion crates/halo2_proofs/src/plonk/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for SingleVerifie
) -> Result<Self::Output, Error> {
let guard = f(self.msm)?;
let msm = guard.use_challenges();
if msm.eval() {
// The IPA challenge expansion normally produces random-like,
// full-width generator coefficients. Correctness does not depend on
// their density.
if msm.eval_full_width() {
Ok(())
} else {
Err(Error::ConstraintSystemFailure)
Expand Down
8 changes: 7 additions & 1 deletion crates/halo2_proofs/src/plonk/verifier/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,13 @@ where
);

match final_msm {
Ok(msm) => msm.eval(),
// Each contribution passed through the IPA challenge expansion,
// which normally produces random-like, full-width coefficients,
// before being scaled into the batch. The batch coefficients are
// fresh, sampled after every proof equation is fixed, and discarded
// after this check, so batch verification explicitly accepts the
// variable-time MSM's timing leakage.
Ok(msm) => msm.eval_full_width(),
Err(_) => false,
}
}
Expand Down
89 changes: 82 additions & 7 deletions crates/halo2_proofs/src/poly/commitment/msm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::Params;
use crate::arithmetic::{CurveAffine, best_multiexp};
use crate::arithmetic::{CurveAffine, CurveExt, best_multiexp};
use ff::{Field, PrimeField};
use group::Group;

Expand Down Expand Up @@ -79,6 +79,12 @@ type OtherTerms<C> = BTreeMap<
(<C as CurveAffine>::ScalarExt, <C as CurveAffine>::Base),
>;

#[derive(Clone, Copy, Eq, PartialEq)]
enum ScalarProfile {
General,
FullWidth,
}

/// A multiscalar multiplication in the polynomial commitment scheme
#[derive(Debug, Clone)]
pub struct MSM<'a, C: CurveAffine> {
Expand Down Expand Up @@ -283,7 +289,7 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
self.u_scalar = self.u_scalar.map(|a| a * &factor);
}

fn multiexp(self) -> C::Curve {
fn multiexp_terms(self) -> (Vec<C::Scalar>, Vec<C>) {
let Self {
params,
g_scalars,
Expand Down Expand Up @@ -340,12 +346,27 @@ impl<'a, C: CurveAffine> MSM<'a, C> {

assert_eq!(scalars.len(), len);

(scalars, bases)
}

fn multiexp(self) -> C::Curve {
let (scalars, bases) = self.multiexp_terms();
best_multiexp(&scalars, &bases)
}

/// Perform multiexp and check that it results in zero
/// Perform multiexp and check that it results in zero.
pub fn eval(self) -> bool {
self.eval_with_profile(ScalarProfile::General)
}

/// Evaluates a verifier MSM whose dominant generator-scalar vector is
/// expected to be random-like and full-width.
pub(crate) fn eval_full_width(self) -> bool {
self.eval_with_profile(ScalarProfile::FullWidth)
}

#[cfg_attr(not(feature = "orbits"), allow(unused_mut))]
pub fn eval(mut self) -> bool {
fn eval_with_profile(mut self, scalar_profile: ScalarProfile) -> bool {
// A prepared fixed-base zero-check over [g..., w, u] (built by
// `Params::prepare_zero_checks`, under the opt-in `orbits`
// feature) evaluates the identity test
Expand Down Expand Up @@ -417,7 +438,17 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
}
}

bool::from(self.multiexp().is_identity())
if scalar_profile == ScalarProfile::General {
return bool::from(self.multiexp().is_identity());
}

let (scalars, bases) = self.multiexp_terms();
if let Some(is_identity) =
C::CurveExt::try_multiexp_full_width_is_identity_vartime(&scalars, &bases)
{
return is_identity;
}
bool::from(best_multiexp(&scalars, &bases).is_identity())
}

/// The MSM's accumulated terms, exposed for capturing the verifier fingerprint without consuming
Expand Down Expand Up @@ -456,8 +487,12 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
mod tests {
use super::CanonicalFieldKey;
use crate::poly::commitment::{MSM, Params};
use group::Curve;
use pasta_curves::{EpAffine, EqAffine, Fp, Fq, arithmetic::CurveAffine};
use ff::Field;
use group::{Curve, Group};
use pasta_curves::{
EpAffine, EqAffine, Fp, Fq,
arithmetic::{CurveAffine, CurveExt},
};

fn assert_cached_key_order<F: ff::PrimeField + Ord>(values: &[F]) {
for left in values {
Expand Down Expand Up @@ -491,6 +526,46 @@ mod tests {
assert_cached_key_order(&vesta_xs);
}

fn cancelling_full_width_msm(params: &Params<EpAffine>) -> MSM<'_, EpAffine> {
let g_scalars = (0..params.n)
.map(|index| Fq::from(index + 2).invert().unwrap())
.collect::<Vec<_>>();
let mut msm = MSM::new(params);
msm.add_to_g_scalars(&g_scalars);
let sum = msm.clone().multiexp();
assert!(!bool::from(sum.is_identity()));
msm.append_term(-Fq::ONE, sum.to_affine());
msm
}

#[test]
fn eval_full_width_is_exact() {
let params = Params::<EpAffine>::new(8);
let valid = cancelling_full_width_msm(&params);
assert!(valid.clone().eval_full_width());

let mut invalid = valid;
invalid.append_term(Fq::ONE, params.w);
assert!(!invalid.eval_full_width());
}

#[test]
fn eval_full_width_falls_back_below_glv_threshold() {
let params = Params::<EpAffine>::new(4);
let valid = cancelling_full_width_msm(&params);
let (scalars, bases) = valid.clone().multiexp_terms();
assert!(
<<EpAffine as CurveAffine>::CurveExt as CurveExt>::
try_multiexp_full_width_is_identity_vartime(&scalars, &bases)
.is_none()
);
assert!(valid.clone().eval_full_width());

let mut invalid = valid;
invalid.append_term(Fq::ONE, params.w);
assert!(!invalid.eval_full_width());
}

#[test]
fn msm_arithmetic() {
// Once plain, once with the prepared fixed-base zero-check armed
Expand Down
28 changes: 28 additions & 0 deletions crates/pasta_curves/src/arithmetic/curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,34 @@ pub trait CurveExt:
None
}

/// Attempts an optimized variable-time identity test for a multiscalar
/// multiplication whose nonzero scalars are expected to be random-like
/// and full-width.
///
/// The full-width profile is a performance hint only. The result must be
/// exact for every scalar value, including zero and sparse or small
/// nonzero scalars. [`Some(true)`](Some) means the multiscalar
/// multiplication is the identity, [`Some(false)`](Some) means it is not,
/// and [`None`] means that this implementation declined the optimization.
/// Implementations without a specialized backend return [`None`].
///
/// # Security
///
/// This method may run in variable time with respect to `scalars` and
/// `bases`. Inputs should be public unless the caller explicitly accepts
/// timing leakage from secret material.
///
/// # Panics
///
/// Implementations may panic if `scalars` and `bases` have different
/// lengths.
fn try_multiexp_full_width_is_identity_vartime(
_scalars: &[Self::ScalarExt],
_bases: &[Self::AffineExt],
) -> Option<bool> {
None
}

/// Attempts an affine, variable-time FFT specialized for this curve.
///
/// `input` and `output` must have the same power-of-two length, equal to
Expand Down
8 changes: 8 additions & 0 deletions crates/pasta_curves/src/curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ macro_rules! impl_multiexp_vartime {
) -> Option<Self> {
crate::glv::try_multiexp::<$name>(scalars, bases)
}

#[cfg(feature = "glv")]
fn try_multiexp_full_width_is_identity_vartime(
scalars: &[Self::ScalarExt],
bases: &[Self::AffineExt],
) -> Option<bool> {
crate::glv::try_multiexp_full_width_is_identity::<$name>(scalars, bases)
}
};
(native, $name:ident) => {};
}
Expand Down
Loading
Loading