diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 5c209f03..196e3ba3 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -16,15 +16,6 @@ pub trait FieldExt: /// Inverse of `ROOT_OF_UNITY` const ROOT_OF_UNITY_INV: Self; - /// The value $(2^S)^{-1} \mod t$. - const UNROLL_T_EXPONENT: [u64; 4]; - - /// Represents $t$ where $2^S \cdot t = p - 1$ with $t$ odd. - const T_EXPONENT: [u64; 4]; - - /// The value $t^{-1} \mod 2^S$. - const UNROLL_S_EXPONENT: u64; - /// Generator of the $t-order$ multiplicative subgroup const DELTA: Self; @@ -67,68 +58,6 @@ pub trait FieldExt: /// byte representation of an integer. fn from_bytes_wide(bytes: &[u8; 64]) -> Self; - /// Returns a square root of this element, if it exists and this element is - /// nonzero. Always returns the same square root, and it is efficient to - /// check that it has done so using `extract_radix2_vartime`. - fn deterministic_sqrt(&self) -> Option { - let sqrt = self.sqrt(); - if bool::from(sqrt.is_none()) { - return None; - } - let sqrt = sqrt.unwrap(); - let extracted = sqrt.extract_radix2_vartime()?; - - if extracted.1 >> (Self::S - 1) == 1 { - Some(-sqrt) - } else { - Some(sqrt) - } - } - - /// Returns an element $a$ of multiplicative order $t$ together with an - /// integer `s` such that `self` is the square of $a \cdot \omega^{s}$ if - /// indeed `self` is a square. - fn extract_radix2_vartime(&self) -> Option<(Self, u64)> { - if bool::from(self.ct_is_zero()) { - return None; - } - - // TODO: these can probably be simplified - let t = self.pow_vartime(&[1 << Self::S, 0, 0, 0]); - let t = t.pow_vartime(&Self::UNROLL_T_EXPONENT); - let t = t.pow_vartime(&Self::UNROLL_T_EXPONENT); - let s = self.pow_vartime(&Self::T_EXPONENT); - let mut s = s.pow_vartime(&[Self::UNROLL_S_EXPONENT, 0, 0, 0]); - - let mut m = Self::S; - let mut c = Self::ROOT_OF_UNITY_INV; - - let mut extract: u64 = 0; - - let mut cur = 1; - while s != Self::one() { - let mut i = 1; - { - let mut s2i = s; - s2i = s2i.square(); - while s2i != Self::one() { - i += 1; - s2i = s2i.square(); - } - } - - for _ in 0..(m - i) { - c = c.square(); - cur <<= 1; - } - extract |= cur; - s *= c; - m = i; - } - - Some((t, extract)) - } - /// Exponentiates `self` by `by`, where `by` is a little-endian order /// integer exponent. fn pow(&self, by: &[u64; 4]) -> Self { diff --git a/src/pasta/fields.rs b/src/pasta/fields.rs index 5b0647d4..e19389e1 100644 --- a/src/pasta/fields.rs +++ b/src/pasta/fields.rs @@ -6,21 +6,3 @@ mod fq; pub use fp::*; pub use fq::*; - -#[cfg(test)] -use ff::{Field, PrimeField}; - -#[cfg(test)] -use crate::arithmetic::FieldExt; - -#[test] -fn test_extract() { - let a = Fq::rand(); - let a = a.square(); - let (t, s) = a.extract_radix2_vartime().unwrap(); - assert_eq!( - t.pow_vartime(&[1 << Fq::S, 0, 0, 0]) * Fq::ROOT_OF_UNITY.pow_vartime(&[s, 0, 0, 0]), - a - ); - assert_eq!(a.deterministic_sqrt().unwrap().square(), a); -} diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 2cba5c36..c28182e1 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -643,20 +643,7 @@ impl FieldExt for Fp { 0xb4ed8e647196dad1, 0x2cd5282c53116b5c, ]); - const UNROLL_T_EXPONENT: [u64; 4] = [ - 0x955a0a417453113c, - 0x0000000022016b89, - 0xc000000000000000, - 0x3f7ed4c6, - ]; - const T_EXPONENT: [u64; 4] = [ - 0x094cf91b992d30ed, - 0x00000000224698fc, - 0x0000000000000000, - 0x40000000, - ]; const DELTA: Self = DELTA; - const UNROLL_S_EXPONENT: u64 = 0x204ace5; const TWO_INV: Self = Fp::from_raw([ 0xcc96987680000001, 0x11234c7e04a67c8d, @@ -791,13 +778,6 @@ fn test_sqrt() { assert!(v == Fp::TWO_INV || (-v) == Fp::TWO_INV); } -#[test] -fn test_deterministic_sqrt() { - // NB: TWO_INV is standing in as a "random" field element - let v = (Fp::TWO_INV).square().deterministic_sqrt().unwrap(); - assert!(v == Fp::TWO_INV || (-v) == Fp::TWO_INV); -} - #[test] fn test_zeta() { assert_eq!( diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 3ffbc322..6c3c9890 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -643,20 +643,7 @@ impl FieldExt for Fq { 0xf4c8f353124086c1, 0x2235e1a7415bf936, ]); - const UNROLL_T_EXPONENT: [u64; 4] = [ - 0xcc771cc2ac1e1664, - 0x00000000062dfe9e, - 0xc000000000000000, - 0xb89e9c7, - ]; - const T_EXPONENT: [u64; 4] = [ - 0x0994a8dd8c46eb21, - 0x00000000224698fc, - 0x0000000000000000, - 0x40000000, - ]; const DELTA: Self = DELTA; - const UNROLL_S_EXPONENT: u64 = 0xd1d858e1; const TWO_INV: Self = Fq::from_raw([ 0xc623759080000001, 0x11234c7e04ca546e, @@ -791,13 +778,6 @@ fn test_sqrt() { assert!(v == Fq::TWO_INV || (-v) == Fq::TWO_INV); } -#[test] -fn test_deterministic_sqrt() { - // NB: TWO_INV is standing in as a "random" field element - let v = (Fq::TWO_INV).square().deterministic_sqrt().unwrap(); - assert!(v == Fq::TWO_INV || (-v) == Fq::TWO_INV); -} - #[test] fn test_zeta() { assert_eq!( diff --git a/src/poly/commitment/prover.rs b/src/poly/commitment/prover.rs index 12afe08f..ef51f108 100644 --- a/src/poly/commitment/prover.rs +++ b/src/poly/commitment/prover.rs @@ -3,8 +3,8 @@ use ff::Field; use super::super::{Coeff, Polynomial}; use super::{Blind, Params}; use crate::arithmetic::{ - best_multiexp, compute_inner_product, eval_polynomial, parallelize, small_multiexp, Curve, - CurveAffine, FieldExt, + best_multiexp, compute_inner_product, eval_polynomial, parallelize, Curve, CurveAffine, + FieldExt, }; use crate::transcript::{Challenge, ChallengeScalar, TranscriptWrite}; use std::io; @@ -84,9 +84,8 @@ pub fn create_proof>( } } - // Initialize the vector `G` from the SRS. We'll be progressively - // collapsing this vector into smaller and smaller vectors until it is - // of length 1. + // Initialize the vector `G` from the SRS. We'll be progressively collapsing + // this vector into smaller and smaller vectors until it is of length 1. let mut g = params.g.clone(); // Perform the inner product argument, round by round. @@ -98,76 +97,42 @@ pub fn create_proof>( // TODO: If we modify multiexp to take "extra" bases, we could speed // this piece up a bit by combining the multiexps. metrics::counter!("multiexp", 2, "val" => "l/r", "size" => format!("{}", half)); - let l = best_multiexp(&a[0..half], &g[half..]); - let r = best_multiexp(&a[half..], &g[0..half]); - let value_l = compute_inner_product(&a[0..half], &b[half..]); - let value_r = compute_inner_product(&a[half..], &b[0..half]); - let mut l_randomness = C::Scalar::rand(); + let l = best_multiexp(&a[half..], &g[0..half]); + let r = best_multiexp(&a[0..half], &g[half..]); + let value_l = compute_inner_product(&a[half..], &b[0..half]); + let value_r = compute_inner_product(&a[0..half], &b[half..]); + let l_randomness = C::Scalar::rand(); let r_randomness = C::Scalar::rand(); metrics::counter!("multiexp", 2, "val" => "l/r", "size" => "2"); let l = l + &best_multiexp(&[value_l * &z, l_randomness], &[params.u, params.h]); let r = r + &best_multiexp(&[value_r * &z, r_randomness], &[params.u, params.h]); - let mut l = l.to_affine(); + let l = l.to_affine(); let r = r.to_affine(); - let challenge = loop { - // We'll fork the transcript and adjust our randomness - // until the challenge is a square. - let mut transcript = transcript.fork(); - - // Feed L and R into the cloned transcript. - // We expect these to not be points at infinity due to the randomness. - transcript.write_point(l)?; - transcript.write_point(r)?; - - // ... and get the squared challenge. - let challenge_sq_packed = Challenge::get(&mut transcript); - let challenge_sq = *ChallengeScalar::::from(challenge_sq_packed); - - // There might be no square root, in which case we'll fork the - // transcript. - let challenge = challenge_sq.deterministic_sqrt(); - if let Some(challenge) = challenge { - break challenge; - } else { - // Try again, with slightly different randomness - l = (l + params.h).to_affine(); - l_randomness += &C::Scalar::one(); - } - }; - - // Challenge is unlikely to be zero. - let challenge_inv = challenge.invert().unwrap(); - let challenge_sq_inv = challenge_inv.square(); - let challenge_sq = challenge.square(); - // Feed L and R into the real transcript transcript.write_point(l)?; transcript.write_point(r)?; - // And obtain the challenge, even though we already have it, since - // squeezing affects the transcript. - { - let challenge_sq_expected = ChallengeScalar::<_, ()>::get(transcript); - assert_eq!(challenge_sq, *challenge_sq_expected); - } + let challenge_packed = Challenge::get(transcript); + let challenge = *ChallengeScalar::::from(challenge_packed); + let challenge_inv = challenge.invert().unwrap(); // TODO, bubble this up // Collapse `a` and `b`. // TODO: parallelize for i in 0..half { - a[i] = (a[i] * &challenge) + &(a[i + half] * &challenge_inv); - b[i] = (b[i] * &challenge_inv) + &(b[i + half] * &challenge); + a[i] = a[i] + &(a[i + half] * &challenge_inv); + b[i] = b[i] + &(b[i + half] * &challenge); } a.truncate(half); b.truncate(half); // Collapse `G` - parallel_generator_collapse(&mut g, challenge, challenge_inv); + parallel_generator_collapse(&mut g, challenge); g.truncate(half); // Update randomness (the synthetic blinding factor at the end) - blind += &(l_randomness * &challenge_sq); - blind += &(r_randomness * &challenge_sq_inv); + blind += &(l_randomness * &challenge_inv); + blind += &(r_randomness * &challenge); } // We have fully collapsed `a`, `b`, `G` @@ -180,20 +145,16 @@ pub fn create_proof>( Ok(()) } -fn parallel_generator_collapse( - g: &mut [C], - challenge: C::Scalar, - challenge_inv: C::Scalar, -) { +fn parallel_generator_collapse(g: &mut [C], challenge: C::Scalar) { let len = g.len() / 2; let (mut g_lo, g_hi) = g.split_at_mut(len); - metrics::counter!("multiexp", len as u64, "size" => "2", "fn" => "parallel_generator_collapse"); + metrics::counter!("scalar_multiplication", len as u64, "fn" => "parallel_generator_collapse"); parallelize(&mut g_lo, |g_lo, start| { let g_hi = &g_hi[start..]; let mut tmp = Vec::with_capacity(g_lo.len()); for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { - tmp.push(small_multiexp(&[challenge_inv, challenge], &[*g_lo, *g_hi])); + tmp.push(g_lo.to_projective() + &(*g_hi * challenge)); } C::Projective::batch_to_affine(&tmp, g_lo); }); diff --git a/src/poly/commitment/verifier.rs b/src/poly/commitment/verifier.rs index 0adf9ba8..1d956c64 100644 --- a/src/poly/commitment/verifier.rs +++ b/src/poly/commitment/verifier.rs @@ -4,16 +4,15 @@ use super::super::Error; use super::{Params, MSM}; use crate::transcript::{Challenge, ChallengeScalar, TranscriptRead}; -use crate::arithmetic::{best_multiexp, Curve, CurveAffine, FieldExt}; +use crate::arithmetic::{best_multiexp, BatchInvert, Curve, CurveAffine}; /// A guard returned by the verifier #[derive(Debug, Clone)] pub struct Guard<'a, C: CurveAffine> { msm: MSM<'a, C>, neg_a: C::Scalar, - allinv: C::Scalar, - challenges_sq: Vec, - challenges_sq_packed: Vec, + challenges: Vec, + challenges_packed: Vec, } /// An accumulator instance consisting of an evaluation claim and a proof. @@ -24,14 +23,14 @@ pub struct Accumulator { /// A vector of 128-bit challenges sampled by the verifier, to be used in /// computing g. - pub challenges_sq_packed: Vec, + pub challenges_packed: Vec, } impl<'a, C: CurveAffine> Guard<'a, C> { /// Lets caller supply the challenges and obtain an MSM with updated /// scalars and points. pub fn use_challenges(mut self) -> MSM<'a, C> { - let s = compute_s(&self.challenges_sq, self.allinv * &self.neg_a); + let s = compute_s(&self.challenges, self.neg_a); self.msm.add_to_g_scalars(&s); self.msm.add_to_h_scalar(self.neg_a); @@ -45,7 +44,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> { let accumulator = Accumulator { g, - challenges_sq_packed: self.challenges_sq_packed, + challenges_packed: self.challenges_packed, }; (self.msm, accumulator) @@ -53,7 +52,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> { /// Computes G + H, where G = ⟨s, params.g⟩ and H is used for blinding pub fn compute_g(&self) -> C { - let s = compute_s(&self.challenges_sq, self.allinv); + let s = compute_s(&self.challenges, C::Scalar::one()); metrics::increment_counter!("multiexp", "size" => format!("{}", s.len()), "fn" => "compute_g"); let mut tmp = best_multiexp(&s, &self.msm.params.g); @@ -84,46 +83,37 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead>( let z = *ChallengeScalar::::get(transcript); - // Data about the challenges from each of the rounds. - let mut challenges = Vec::with_capacity(k); - let mut challenges_inv = Vec::with_capacity(k); - let mut challenges_sq = Vec::with_capacity(k); - let mut challenges_sq_packed: Vec = Vec::with_capacity(k); - let mut allinv = C::Scalar::one(); - + let mut rounds = vec![]; for _ in 0..k { // Read L and R from the proof and write them to the transcript let l = transcript.read_point().map_err(|_| Error::OpeningError)?; let r = transcript.read_point().map_err(|_| Error::OpeningError)?; - let challenge_sq_packed = Challenge::get(transcript); - let challenge_sq = *ChallengeScalar::::from(challenge_sq_packed); + let challenge_packed = Challenge::get(transcript); + let challenge = *ChallengeScalar::::from(challenge_packed); - let challenge = challenge_sq.deterministic_sqrt(); - if challenge.is_none() { - // We didn't sample a square. - return Err(Error::OpeningError); - } - let challenge = challenge.unwrap(); + rounds.push(( + l, + r, + challenge, + /* to be inverted */ challenge, + challenge_packed, + )); + } - let challenge_inv = challenge.invert(); - if bool::from(challenge_inv.is_none()) { - // We sampled zero for some reason, unlikely to happen by - // chance. - return Err(Error::OpeningError); - } - let challenge_inv = challenge_inv.unwrap(); - allinv *= &challenge_inv; + rounds + .iter_mut() + .map(|&mut (_, _, _, ref mut challenge, _)| challenge) + .batch_invert(); - let challenge_sq_inv = challenge_inv.square(); - - msm.append_term(challenge_sq, l); - msm.append_term(challenge_sq_inv, r); + let mut challenges = Vec::with_capacity(k); + let mut challenges_packed: Vec = Vec::with_capacity(k); + for (l, r, challenge, challenge_inv, challenge_packed) in rounds { + msm.append_term(challenge_inv, l); + msm.append_term(challenge, r); challenges.push(challenge); - challenges_inv.push(challenge_inv); - challenges_sq.push(challenge_sq); - challenges_sq_packed.push(challenge_sq_packed); + challenges_packed.push(challenge_packed); } // Our goal is to open @@ -142,7 +132,7 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead>( let a = transcript.read_scalar().map_err(|_| Error::SamplingError)?; let neg_a = -a; let h = transcript.read_scalar().map_err(|_| Error::SamplingError)?; - let b = compute_b(x, &challenges, &challenges_inv); + let b = compute_b(x, &challenges); msm.add_to_u_scalar(neg_a * &b * &z); msm.add_to_h_scalar(a - &h); @@ -150,42 +140,43 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead>( let guard = Guard { msm, neg_a, - allinv, - challenges_sq, - challenges_sq_packed, + challenges, + challenges_packed, }; Ok(guard) } -fn compute_b(x: F, challenges: &[F], challenges_inv: &[F]) -> F { - assert!(!challenges.is_empty()); - assert_eq!(challenges.len(), challenges_inv.len()); - if challenges.len() == 1 { - *challenges_inv.last().unwrap() + *challenges.last().unwrap() * x - } else { - (*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x) - * compute_b( - x.square(), - &challenges[0..(challenges.len() - 1)], - &challenges_inv[0..(challenges.len() - 1)], - ) +/// Computes $\prod\limits_{i=0}^{k-1} (1 + u_i x^{2^i})$. +fn compute_b(x: F, challenges: &[F]) -> F { + let mut tmp = F::one(); + let mut cur = x; + for challenge in challenges.iter().rev() { + tmp *= F::one() + &(*challenge * &cur); + cur *= cur; } + tmp } -// TODO: parallelize -fn compute_s(challenges_sq: &[F], allinv: F) -> Vec { - let lg_n = challenges_sq.len(); - let n = 1 << lg_n; +/// Computes the coefficients of $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_i X^{2^i})$. +fn compute_s(challenges: &[F], init: F) -> Vec { + assert!(challenges.len() > 0); + let mut v = vec![F::zero(); 1 << challenges.len()]; + v[0] = init; - let mut s = Vec::with_capacity(n); - s.push(allinv); - for i in 1..n { - let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize; - let k = 1 << lg_i; - let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i]; - s.push(s[i - k] * u_lg_i_sq); + for (len, challenge) in challenges + .iter() + .rev() + .enumerate() + .map(|(i, challenge)| (1 << i, challenge)) + { + let (left, right) = v.split_at_mut(len); + let right = &mut right[0..len]; + right.copy_from_slice(&left); + for v in right { + *v *= challenge; + } } - s + v } diff --git a/src/transcript.rs b/src/transcript.rs index 537556c5..389ae779 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -32,18 +32,11 @@ pub trait TranscriptRead: Transcript { /// Transcript view from the perspective of a prover that has access to an /// output stream of messages from the prover to the verifier. pub trait TranscriptWrite: Transcript { - /// Forked transcript that does not write to the proof structure. - type ForkedTranscript: TranscriptWrite; - /// Write a curve point to the proof and the transcript. fn write_point(&mut self, point: C) -> io::Result<()>; /// Write a scalar to the proof and the transcript. fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()>; - - /// Fork the transcript, creating a variant of this `TranscriptWrite` which - /// does not output anything to the writer. - fn fork(&self) -> Self::ForkedTranscript; } /// This is just a simple (and completely broken) transcript reader @@ -161,8 +154,6 @@ impl DummyHashWrite { } impl TranscriptWrite for DummyHashWrite { - type ForkedTranscript = DummyHashWrite; - fn write_point(&mut self, point: C) -> io::Result<()> { self.common_point(point)?; let compressed = point.to_bytes(); @@ -175,14 +166,6 @@ impl TranscriptWrite for DummyHashWrite { let data = scalar.to_bytes(); self.writer.write_all(&data[..]) } - fn fork(&self) -> Self::ForkedTranscript { - DummyHashWrite { - base_state: self.base_state, - scalar_state: self.scalar_state, - written_scalar: self.written_scalar, - writer: io::sink(), - } - } } impl Transcript for DummyHashWrite {