diff --git a/crates/halo2_gadgets/benches/primitives.rs b/crates/halo2_gadgets/benches/primitives.rs index 64b1c2a6..ccb7e426 100644 --- a/crates/halo2_gadgets/benches/primitives.rs +++ b/crates/halo2_gadgets/benches/primitives.rs @@ -1,6 +1,8 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use ff::Field; +use group::{Curve, Group}; use halo2_gadgets::{ + ecc::chip::{NUM_WINDOWS, compute_lagrange_coeffs}, poseidon::primitives::{self as poseidon, ConstantLength, P128Pow5T3}, sinsemilla::primitives as sinsemilla, }; @@ -58,6 +60,15 @@ fn bench_primitives(c: &mut Criterion) { }); } } + + { + let mut group = c.benchmark_group("ECC fixed-base preprocessing"); + group.sample_size(10); + let base = pallas::Point::generator().to_affine(); + group.bench_function("full-width Lagrange coefficients", |b| { + b.iter(|| compute_lagrange_coeffs(base, NUM_WINDOWS)) + }); + } } #[cfg(unix)] diff --git a/crates/halo2_gadgets/src/ecc/chip/constants.rs b/crates/halo2_gadgets/src/ecc/chip/constants.rs index ef9d9d37..0f4b215b 100644 --- a/crates/halo2_gadgets/src/ecc/chip/constants.rs +++ b/crates/halo2_gadgets/src/ecc/chip/constants.rs @@ -39,38 +39,41 @@ pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; /// [the Halo 2 book](https://zcash.github.io/halo2/design/gadgets/ecc/fixed-base-scalar-mul.html#load-fixed-base). fn compute_window_table(base: C, num_windows: usize) -> Vec<[C; H]> { let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows); + let h = C::Scalar::from(H as u64); + let mut window_scale = C::Scalar::ONE; // Generate window table entries for all windows but the last. // For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B. // Here, w ranges from [0..`num_windows - 1`) - for w in 0..(num_windows - 1) { + for _ in 0..(num_windows - 1) { window_table.push( (0..H) .map(|k| { // scalar = (k+2)*(8^w) - let scalar = C::Scalar::from(k as u64 + 2) - * C::Scalar::from(H as u64).pow([w as u64, 0, 0, 0]); + let scalar = C::Scalar::from(k as u64 + 2) * window_scale; (base * scalar).to_affine() }) .collect::>() .into_inner() .unwrap(), ); + window_scale *= h; } // Generate window table entries for the last window, w = `num_windows - 1`. // For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined // as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1} - let sum = (0..(num_windows - 1)).fold(C::Scalar::ZERO, |acc, j| { - acc + C::Scalar::from(2).pow([FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, 0, 0, 0]) - }); + let mut offset = C::Scalar::from(2); + let mut sum = C::Scalar::ZERO; + for _ in 0..(num_windows - 1) { + sum += offset; + offset *= h; + } window_table.push( (0..H) .map(|k| { // scalar = k * (2^3)^w - sum, where w = `num_windows - 1` - let scalar = C::Scalar::from(k as u64) - * C::Scalar::from(H as u64).pow([(num_windows - 1) as u64, 0, 0, 0]) - - sum; + let scalar = C::Scalar::from(k as u64) * window_scale - sum; (base * scalar).to_affine() }) .collect::>() diff --git a/crates/halo2_proofs/src/plonk.rs b/crates/halo2_proofs/src/plonk.rs index a06949da..765a131d 100644 --- a/crates/halo2_proofs/src/plonk.rs +++ b/crates/halo2_proofs/src/plonk.rs @@ -39,7 +39,11 @@ pub use keygen::*; pub use prover::*; pub use verifier::*; -use std::{io, sync::Arc}; +use std::{ + any::{Any as StdAny, TypeId}, + io, + sync::Arc, +}; fn commit_instance(params: &Params, instance: &[C::Scalar]) -> C::Curve { let mut commitment = C::Curve::from(params.w); @@ -47,6 +51,53 @@ fn commit_instance(params: &Params, instance: &[C::Scalar]) - commitment } +/// Computes `base^(2^exponent)` using the public exponent directly. +fn pow_by_power_of_two(base: F, exponent: u32) -> F { + if TypeId::of::() == TypeId::of::() { + let value = (&base as &dyn StdAny) + .downcast_ref::() + .expect("the field type was checked"); + let result = pasta_curves::arithmetic::square_fp_n(value, exponent); + return *(&result as &dyn StdAny) + .downcast_ref::() + .expect("the field type was checked"); + } + if TypeId::of::() == TypeId::of::() { + let value = (&base as &dyn StdAny) + .downcast_ref::() + .expect("the field type was checked"); + let result = pasta_curves::arithmetic::square_fq_n(value, exponent); + return *(&result as &dyn StdAny) + .downcast_ref::() + .expect("the field type was checked"); + } + + let mut base = base; + for _ in 0..exponent { + base = base.square(); + } + base +} + +#[cfg(test)] +mod power_of_two_tests { + use super::pow_by_power_of_two; + use pasta_curves::{Fp, Fq}; + + #[test] + fn matches_repeated_field_squaring() { + let fp = Fp::from(0x9e37_79b9_7f4a_7c15); + let fq = Fq::from(0x0123_4567_89ab_cdef); + + for exponent in [0, 1, 2, 11, 64] { + let expected_fp = (0..exponent).fold(fp, |value, _| value.square()); + let expected_fq = (0..exponent).fold(fq, |value, _| value.square()); + assert_eq!(pow_by_power_of_two(fp, exponent), expected_fp); + assert_eq!(pow_by_power_of_two(fq, exponent), expected_fq); + } + } +} + /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. #[derive(Clone, Debug)] diff --git a/crates/halo2_proofs/src/plonk/prover.rs b/crates/halo2_proofs/src/plonk/prover.rs index a6c7c640..05c84ad7 100644 --- a/crates/halo2_proofs/src/plonk/prover.rs +++ b/crates/halo2_proofs/src/plonk/prover.rs @@ -1295,7 +1295,7 @@ where } let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); - let xn = x.pow([params.n, 0, 0, 0]); + let xn = super::pow_by_power_of_two(*x, params.k); let polynomial_evaluator = PolynomialEvaluator::new( [ *x, diff --git a/crates/halo2_proofs/src/plonk/verifier.rs b/crates/halo2_proofs/src/plonk/verifier.rs index 0274ed71..7820439c 100644 --- a/crates/halo2_proofs/src/plonk/verifier.rs +++ b/crates/halo2_proofs/src/plonk/verifier.rs @@ -288,7 +288,7 @@ fn verify_proof_with_instance_commitments< // commitments open to the correct values. let vanishing = { // x^n - let xn = x.pow([params.n, 0, 0, 0]); + let xn = super::pow_by_power_of_two(*x, params.k); let blinding_factors = vk.cs.blinding_factors(); let l_evals = vk diff --git a/crates/orchard/src/circuit/commit_ivk.rs b/crates/orchard/src/circuit/commit_ivk.rs index 1032d65f..7ec2cd3f 100644 --- a/crates/orchard/src/circuit/commit_ivk.rs +++ b/crates/orchard/src/circuit/commit_ivk.rs @@ -7,7 +7,7 @@ use core::iter; -use group::ff::{Field, PrimeField}; +use group::ff::PrimeField; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Value}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, @@ -130,7 +130,9 @@ impl CommitIvkChip { // Check that nk = b_2 (5 bits) || c (240 bits) || d_0 (9 bits) || d_1 (1 bit) let nk_decomposition_check = { - let two_pow_245 = pallas::Base::from(1 << 49).pow([5, 0, 0, 0]); + let two_pow_49 = pallas::Base::from(1 << 49); + let two_pow_245 = + pasta_curves::arithmetic::square_fp_n(&two_pow_49, 2) * two_pow_49; b_2.clone() + c.clone() * two_pow_5 diff --git a/crates/pasta_curves/benches/fp.rs b/crates/pasta_curves/benches/fp.rs index 00fcd4b1..e6043081 100644 --- a/crates/pasta_curves/benches/fp.rs +++ b/crates/pasta_curves/benches/fp.rs @@ -1,11 +1,14 @@ ///! Benchmarks for the Fp field. -use criterion::{Bencher, Criterion, criterion_group, criterion_main}; +use criterion::{Bencher, Criterion, black_box, criterion_group, criterion_main}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; use ff::{Field, PrimeField}; use pasta_curves::Fp; +use pasta_curves::arithmetic::square_fp_n; + +const PROVER_DOMAIN_EXPONENT: u32 = 11; fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Fp"); @@ -22,6 +25,18 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_function("from_repr", bench_fp_from_repr); group.bench_function("eq/equal", |b| bench_fp_eq(b, true)); group.bench_function("eq/unequal", |b| bench_fp_eq(b, false)); + group.bench_function("pow 2^11/constant-time", bench_fp_pow_power_of_two); + group.bench_function("pow 2^11/repeated-squaring", bench_fp_square_n); +} + +fn bench_fp_pow_power_of_two(b: &mut Bencher) { + let value = Fp::from(0x9e37_79b9_7f4a_7c15); + b.iter(|| black_box(value).pow([1 << PROVER_DOMAIN_EXPONENT, 0, 0, 0])); +} + +fn bench_fp_square_n(b: &mut Bencher) { + let value = Fp::from(0x9e37_79b9_7f4a_7c15); + b.iter(|| square_fp_n(black_box(&value), PROVER_DOMAIN_EXPONENT)); } fn bench_fp_eq(b: &mut Bencher, equal: bool) { diff --git a/crates/pasta_curves/benches/fq.rs b/crates/pasta_curves/benches/fq.rs index 17c5124e..5720be4e 100644 --- a/crates/pasta_curves/benches/fq.rs +++ b/crates/pasta_curves/benches/fq.rs @@ -1,11 +1,14 @@ ///! Benchmarks for the Fq field. -use criterion::{Bencher, Criterion, criterion_group, criterion_main}; +use criterion::{Bencher, Criterion, black_box, criterion_group, criterion_main}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; use ff::{Field, PrimeField}; use pasta_curves::Fq; +use pasta_curves::arithmetic::square_fq_n; + +const PROVER_DOMAIN_EXPONENT: u32 = 11; fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Fq"); @@ -22,6 +25,18 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_function("from_repr", bench_fq_from_repr); group.bench_function("eq/equal", |b| bench_fq_eq(b, true)); group.bench_function("eq/unequal", |b| bench_fq_eq(b, false)); + group.bench_function("pow 2^11/constant-time", bench_fq_pow_power_of_two); + group.bench_function("pow 2^11/repeated-squaring", bench_fq_square_n); +} + +fn bench_fq_pow_power_of_two(b: &mut Bencher) { + let value = Fq::from(0x9e37_79b9_7f4a_7c15); + b.iter(|| black_box(value).pow([1 << PROVER_DOMAIN_EXPONENT, 0, 0, 0])); +} + +fn bench_fq_square_n(b: &mut Bencher) { + let value = Fq::from(0x9e37_79b9_7f4a_7c15); + b.iter(|| square_fq_n(black_box(&value), PROVER_DOMAIN_EXPONENT)); } fn bench_fq_eq(b: &mut Bencher, equal: bool) { diff --git a/crates/pasta_curves/src/arithmetic.rs b/crates/pasta_curves/src/arithmetic.rs index b07e70c6..f6ee34f4 100644 --- a/crates/pasta_curves/src/arithmetic.rs +++ b/crates/pasta_curves/src/arithmetic.rs @@ -27,3 +27,48 @@ pub fn mul_fp_by_inverse_power_of_two(value: &crate::Fp, exponent: u32) -> crate pub fn mul_fq_by_inverse_power_of_two(value: &crate::Fq, exponent: u32) -> crate::Fq { value.mul_by_inverse_power_of_two(exponent) } + +/// Squares an [`Fp`](crate::Fp) element `count` times. +/// +/// This is an internal cross-crate bridge for Halo 2 and Orchard. +#[doc(hidden)] +#[inline] +pub fn square_fp_n(value: &crate::Fp, count: u32) -> crate::Fp { + if count == 0 { + *value + } else { + SqrtTableHelpers::sqr_n(value, count) + } +} + +/// Squares an [`Fq`](crate::Fq) element `count` times. +/// +/// This is an internal cross-crate bridge for Halo 2 and Orchard. +#[doc(hidden)] +#[inline] +pub fn square_fq_n(value: &crate::Fq, count: u32) -> crate::Fq { + if count == 0 { + *value + } else { + SqrtTableHelpers::sqr_n(value, count) + } +} + +#[cfg(test)] +mod tests { + use super::{square_fp_n, square_fq_n}; + use crate::{Fp, Fq}; + + #[test] + fn repeated_squaring_bridges_match_field_squaring() { + let fp = Fp::from(0x9e37_79b9_7f4a_7c15); + let fq = Fq::from(0x0123_4567_89ab_cdef); + + for count in [0, 1, 2, 11, 64] { + let expected_fp = (0..count).fold(fp, |value, _| value.square()); + let expected_fq = (0..count).fold(fq, |value, _| value.square()); + assert_eq!(square_fp_n(&fp, count), expected_fp); + assert_eq!(square_fq_n(&fq, count), expected_fq); + } + } +} diff --git a/docs/changelog/unreleased/333.md b/docs/changelog/unreleased/333.md new file mode 100644 index 00000000..6f5b9dab --- /dev/null +++ b/docs/changelog/unreleased/333.md @@ -0,0 +1,29 @@ +## zakura-pasta-curves + +### Added + +- Added the doc-hidden `arithmetic::{square_fp_n, square_fq_n}` cross-crate + bridges for invoking the dedicated repeated-squaring implementation + ([#333](https://github.com/zakura-core/common/pull/333)). + +## zakura-halo2-proofs + +### Changed + +- Changed the public-domain `x^(2^k)` calculation used by proof creation and + verification to call Pasta's dedicated repeated-squaring implementation. + At `k = 11`, the isolated calculation was 63.6x faster on Apple M4 and + 64.9x faster on x86_64 Linux; proof bytes and transcripts are unchanged + ([#333](https://github.com/zakura-core/common/pull/333)). + +## zakura-halo2-gadgets + +### Changed + +- Changed generic fixed-base window preprocessing to advance public window + scales iteratively instead of recomputing each power with constant-time + exponentiation, reducing full-width Lagrange-coefficient generation by + 14.7% on Apple M4 and 16.3% on x86_64 Linux. Orchard uses precomputed + coefficient tables, so these measurements do not represent an Orchard + key-generation speedup + ([#333](https://github.com/zakura-core/common/pull/333)).