mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-10 01:33:07 +00:00
Merge branch 'main' into fix_z_check
# Conflicts: # src/circuit_builder.rs
This commit is contained in:
commit
525db6c461
@ -21,8 +21,6 @@ fn main() {
|
||||
}
|
||||
|
||||
fn bench_prove<F: Field + Extendable<D>, const D: usize>() {
|
||||
let gmimc_gate = GMiMCGate::<F, D, GMIMC_ROUNDS>::with_automatic_constants();
|
||||
|
||||
let config = CircuitConfig {
|
||||
num_wires: 134,
|
||||
num_routed_wires: 27,
|
||||
@ -32,7 +30,7 @@ fn bench_prove<F: Field + Extendable<D>, const D: usize>() {
|
||||
fri_config: FriConfig {
|
||||
proof_of_work_bits: 1,
|
||||
rate_bits: 3,
|
||||
reduction_arity_bits: vec![1],
|
||||
reduction_arity_bits: vec![2, 2, 2, 2, 2],
|
||||
num_query_rounds: 1,
|
||||
},
|
||||
};
|
||||
|
||||
@ -37,7 +37,7 @@ pub struct CircuitBuilder<F: Extendable<D>, const D: usize> {
|
||||
/// The next available index for a public input.
|
||||
public_input_index: usize,
|
||||
|
||||
/// The next available index for a VirtualAdviceTarget.
|
||||
/// The next available index for a `VirtualTarget`.
|
||||
virtual_target_index: usize,
|
||||
|
||||
copy_constraints: Vec<(Target, Target)>,
|
||||
@ -78,22 +78,18 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
(0..n).map(|_i| self.add_public_input()).collect()
|
||||
}
|
||||
|
||||
/// Adds a new "virtual" advice target. This is not an actual wire in the witness, but just a
|
||||
/// target that help facilitate witness generation. In particular, a generator can assign a
|
||||
/// values to a virtual target, which can then be copied to other (virtual or concrete) targets
|
||||
/// via `generate_copy`. When we generate the final witness (a grid of wire values), these
|
||||
/// virtual targets will go away.
|
||||
///
|
||||
/// Since virtual targets are not part of the actual permutation argument, they cannot be used
|
||||
/// with `assert_equal`.
|
||||
pub fn add_virtual_advice_target(&mut self) -> Target {
|
||||
/// Adds a new "virtual" target. This is not an actual wire in the witness, but just a target
|
||||
/// that help facilitate witness generation. In particular, a generator can assign a values to a
|
||||
/// virtual target, which can then be copied to other (virtual or concrete) targets. When we
|
||||
/// generate the final witness (a grid of wire values), these virtual targets will go away.
|
||||
pub fn add_virtual_target(&mut self) -> Target {
|
||||
let index = self.virtual_target_index;
|
||||
self.virtual_target_index += 1;
|
||||
Target::VirtualAdviceTarget { index }
|
||||
Target::VirtualTarget { index }
|
||||
}
|
||||
|
||||
pub fn add_virtual_advice_targets(&mut self, n: usize) -> Vec<Target> {
|
||||
(0..n).map(|_i| self.add_virtual_advice_target()).collect()
|
||||
pub fn add_virtual_targets(&mut self, n: usize) -> Vec<Target> {
|
||||
(0..n).map(|_i| self.add_virtual_target()).collect()
|
||||
}
|
||||
|
||||
pub fn add_gate_no_constants(&mut self, gate_type: GateRef<F, D>) -> usize {
|
||||
@ -260,7 +256,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
/// polynomials (which are opened at only one location) and for the Z polynomials (which are
|
||||
/// opened at two).
|
||||
fn blinding_counts(&self) -> (usize, usize) {
|
||||
let num_gates = self.gates.len();
|
||||
let num_gates = self.gate_instances.len();
|
||||
let mut degree_estimate = 1 << log2_ceil(num_gates);
|
||||
|
||||
loop {
|
||||
@ -281,48 +277,52 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
}
|
||||
|
||||
fn blind_and_pad(&mut self) {
|
||||
// let (regular_poly_openings, z_openings) = self.blinding_counts();
|
||||
//
|
||||
// let num_routed_wires = self.config.num_routed_wires;
|
||||
// let num_wires = self.config.num_wires;
|
||||
//
|
||||
// // For each "regular" blinding factor, we simply add a no-op gate, and insert a random value
|
||||
// // for each wire.
|
||||
// for _ in 0..regular_poly_openings {
|
||||
// let gate = self.add_gate_no_constants(NoopGate::get());
|
||||
// for w in 0..num_wires {
|
||||
// self.add_generator(RandomValueGenerator {
|
||||
// target: Target::Wire(Wire { gate, input: w }),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // For each z poly blinding factor, we add two new gates with the same random value, and
|
||||
// // enforce a copy constraint between them.
|
||||
// // See https://mirprotocol.org/blog/Adding-zero-knowledge-to-Plonk-Halo
|
||||
// for _ in 0..z_openings {
|
||||
// let gate_1 = self.add_gate_no_constants(NoopGate::get());
|
||||
// let gate_2 = self.add_gate_no_constants(NoopGate::get());
|
||||
//
|
||||
// for w in 0..num_routed_wires {
|
||||
// self.add_generator(RandomValueGenerator {
|
||||
// target: Target::Wire(Wire {
|
||||
// gate: gate_1,
|
||||
// input: w,
|
||||
// }),
|
||||
// });
|
||||
// self.add_generator(CopyGenerator {
|
||||
// src: Target::Wire(Wire {
|
||||
// gate: gate_1,
|
||||
// input: w,
|
||||
// }),
|
||||
// dst: Target::Wire(Wire {
|
||||
// gate: gate_2,
|
||||
// input: w,
|
||||
// }),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
let (regular_poly_openings, z_openings) = self.blinding_counts();
|
||||
info!(
|
||||
"Adding {} blinding terms for witness polynomials, and {}*2 for Z polynomials",
|
||||
regular_poly_openings, z_openings
|
||||
);
|
||||
|
||||
let num_routed_wires = self.config.num_routed_wires;
|
||||
let num_wires = self.config.num_wires;
|
||||
|
||||
// For each "regular" blinding factor, we simply add a no-op gate, and insert a random value
|
||||
// for each wire.
|
||||
for _ in 0..regular_poly_openings {
|
||||
let gate = self.add_gate_no_constants(NoopGate::get());
|
||||
for w in 0..num_wires {
|
||||
self.add_generator(RandomValueGenerator {
|
||||
target: Target::Wire(Wire { gate, input: w }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For each z poly blinding factor, we add two new gates with the same random value, and
|
||||
// enforce a copy constraint between them.
|
||||
// See https://mirprotocol.org/blog/Adding-zero-knowledge-to-Plonk-Halo
|
||||
for _ in 0..z_openings {
|
||||
let gate_1 = self.add_gate_no_constants(NoopGate::get());
|
||||
let gate_2 = self.add_gate_no_constants(NoopGate::get());
|
||||
|
||||
for w in 0..num_routed_wires {
|
||||
self.add_generator(RandomValueGenerator {
|
||||
target: Target::Wire(Wire {
|
||||
gate: gate_1,
|
||||
input: w,
|
||||
}),
|
||||
});
|
||||
self.generate_copy(
|
||||
Target::Wire(Wire {
|
||||
gate: gate_1,
|
||||
input: w,
|
||||
}),
|
||||
Target::Wire(Wire {
|
||||
gate: gate_2,
|
||||
input: w,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
while !self.gate_instances.len().is_power_of_two() {
|
||||
self.add_gate_no_constants(NoopGate::get());
|
||||
@ -369,7 +369,11 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
}
|
||||
|
||||
for index in 0..self.public_input_index {
|
||||
target_partitions.add_partition(Target::PublicInput { index })
|
||||
target_partitions.add_partition(Target::PublicInput { index });
|
||||
}
|
||||
|
||||
for index in 0..self.virtual_target_index {
|
||||
target_partitions.add_partition(Target::VirtualTarget { index });
|
||||
}
|
||||
|
||||
for &(a, b) in &self.copy_constraints {
|
||||
@ -384,12 +388,12 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
pub fn build(mut self) -> CircuitData<F, D> {
|
||||
let start = Instant::now();
|
||||
info!(
|
||||
"degree before blinding & padding: {}",
|
||||
"Degree before blinding & padding: {}",
|
||||
self.gate_instances.len()
|
||||
);
|
||||
self.blind_and_pad();
|
||||
let degree = self.gate_instances.len();
|
||||
info!("degree after blinding & padding: {}", degree);
|
||||
info!("Degree after blinding & padding: {}", degree);
|
||||
|
||||
let gates = self.gates.iter().cloned().collect();
|
||||
let (gate_tree, max_filtered_constraint_degree, num_constants) = Tree::from_gates(gates);
|
||||
|
||||
@ -39,7 +39,7 @@ impl Default for CircuitConfig {
|
||||
fri_config: FriConfig {
|
||||
proof_of_work_bits: 1,
|
||||
rate_bits: 1,
|
||||
reduction_arity_bits: vec![1],
|
||||
reduction_arity_bits: vec![1, 1, 1, 1],
|
||||
num_query_rounds: 1,
|
||||
},
|
||||
}
|
||||
@ -61,7 +61,7 @@ impl CircuitConfig {
|
||||
fri_config: FriConfig {
|
||||
proof_of_work_bits: 1,
|
||||
rate_bits: 3,
|
||||
reduction_arity_bits: vec![1],
|
||||
reduction_arity_bits: vec![1, 1, 1, 1],
|
||||
num_query_rounds: 1,
|
||||
},
|
||||
}
|
||||
|
||||
@ -136,6 +136,8 @@ impl Debug for CrandallField {
|
||||
}
|
||||
|
||||
impl Field for CrandallField {
|
||||
type PrimeField = Self;
|
||||
|
||||
const ZERO: Self = Self(0);
|
||||
const ONE: Self = Self(1);
|
||||
const TWO: Self = Self(2);
|
||||
@ -143,6 +145,7 @@ impl Field for CrandallField {
|
||||
|
||||
const ORDER: u64 = 18446744071293632513;
|
||||
const TWO_ADICITY: usize = 28;
|
||||
const CHARACTERISTIC: u64 = Self::ORDER;
|
||||
|
||||
const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(5);
|
||||
const POWER_OF_TWO_GENERATOR: Self = Self(10281950781551402419);
|
||||
|
||||
@ -43,11 +43,14 @@ impl From<<Self as FieldExtension<2>>::BaseField> for QuadraticCrandallField {
|
||||
}
|
||||
|
||||
impl Field for QuadraticCrandallField {
|
||||
type PrimeField = CrandallField;
|
||||
|
||||
const ZERO: Self = Self([CrandallField::ZERO; 2]);
|
||||
const ONE: Self = Self([CrandallField::ONE, CrandallField::ZERO]);
|
||||
const TWO: Self = Self([CrandallField::TWO, CrandallField::ZERO]);
|
||||
const NEG_ONE: Self = Self([CrandallField::NEG_ONE, CrandallField::ZERO]);
|
||||
|
||||
const CHARACTERISTIC: u64 = CrandallField::ORDER;
|
||||
// Does not fit in 64-bits.
|
||||
const ORDER: u64 = 0;
|
||||
const TWO_ADICITY: usize = 29;
|
||||
|
||||
@ -50,6 +50,8 @@ impl From<<Self as FieldExtension<4>>::BaseField> for QuarticCrandallField {
|
||||
}
|
||||
|
||||
impl Field for QuarticCrandallField {
|
||||
type PrimeField = CrandallField;
|
||||
|
||||
const ZERO: Self = Self([CrandallField::ZERO; 4]);
|
||||
const ONE: Self = Self([
|
||||
CrandallField::ONE,
|
||||
@ -70,6 +72,7 @@ impl Field for QuarticCrandallField {
|
||||
CrandallField::ZERO,
|
||||
]);
|
||||
|
||||
const CHARACTERISTIC: u64 = CrandallField::ORDER;
|
||||
// Does not fit in 64-bits.
|
||||
const ORDER: u64 = 0;
|
||||
const TWO_ADICITY: usize = 30;
|
||||
|
||||
370
src/field/fft.rs
370
src/field/fft.rs
@ -1,142 +1,315 @@
|
||||
use std::option::Option;
|
||||
|
||||
use crate::field::field::Field;
|
||||
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
|
||||
use crate::util::{log2_ceil, log2_strict};
|
||||
use crate::util::{log2_strict, reverse_index_bits};
|
||||
|
||||
/// Permutes `arr` such that each index is mapped to its reverse in binary.
|
||||
fn reverse_index_bits<T: Copy>(arr: Vec<T>) -> Vec<T> {
|
||||
let n = arr.len();
|
||||
let n_power = log2_strict(n);
|
||||
// TODO: Should really do some "dynamic" dispatch to handle the
|
||||
// different FFT algos rather than C-style enum dispatch.
|
||||
enum FftStrategy {
|
||||
Classic,
|
||||
Unrolled,
|
||||
}
|
||||
|
||||
let mut result = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
result.push(arr[reverse_bits(i, n_power)]);
|
||||
const FFT_STRATEGY: FftStrategy = FftStrategy::Classic;
|
||||
|
||||
type FftRootTable<F: Field> = Vec<Vec<F>>;
|
||||
|
||||
fn fft_classic_root_table<F: Field>(n: usize) -> FftRootTable<F> {
|
||||
let lg_n = log2_strict(n);
|
||||
// bases[i] = g^2^i, for i = 0, ..., lg_n - 1
|
||||
let mut bases = Vec::with_capacity(lg_n);
|
||||
let mut base = F::primitive_root_of_unity(lg_n);
|
||||
bases.push(base);
|
||||
for _ in 1..lg_n {
|
||||
base = base.square(); // base = g^2^_
|
||||
bases.push(base);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn reverse_bits(n: usize, num_bits: usize) -> usize {
|
||||
let mut result = 0;
|
||||
for i in 0..num_bits {
|
||||
let i_rev = num_bits - i - 1;
|
||||
result |= (n >> i & 1) << i_rev;
|
||||
let mut root_table = Vec::with_capacity(lg_n);
|
||||
for lg_m in 1..=lg_n {
|
||||
let half_m = 1 << (lg_m - 1);
|
||||
let base = bases[lg_n - lg_m];
|
||||
let root_row = base.powers().take(half_m.max(2)).collect();
|
||||
root_table.push(root_row);
|
||||
}
|
||||
result
|
||||
root_table
|
||||
}
|
||||
|
||||
pub(crate) struct FftPrecomputation<F: Field> {
|
||||
/// For each layer index i, stores the cyclic subgroup corresponding to the evaluation domain of
|
||||
/// layer i. The indices within these subgroup vectors are bit-reversed.
|
||||
subgroups_rev: Vec<Vec<F>>,
|
||||
fn fft_unrolled_root_table<F: Field>(n: usize) -> FftRootTable<F> {
|
||||
// Precompute a table of the roots of unity used in the main
|
||||
// loops.
|
||||
|
||||
// Suppose n is the size of the outer vector and g is a primitive nth
|
||||
// root of unity. Then the [lg(m) - 1][j] element of the table is
|
||||
// g^{ n/2m * j } for j = 0..m-1
|
||||
|
||||
let lg_n = log2_strict(n);
|
||||
// bases[i] = g^2^i, for i = 0, ..., lg_n - 2
|
||||
let mut bases = Vec::with_capacity(lg_n);
|
||||
let mut base = F::primitive_root_of_unity(lg_n);
|
||||
bases.push(base);
|
||||
// NB: If n = 1, then lg_n is zero, so we can't do 1..(lg_n-1) here
|
||||
for _ in 2..lg_n {
|
||||
base = base.square(); // base = g^2^(_-1)
|
||||
bases.push(base);
|
||||
}
|
||||
|
||||
let mut root_table = Vec::with_capacity(lg_n);
|
||||
for lg_m in 1..lg_n {
|
||||
let m = 1 << lg_m;
|
||||
let base = bases[lg_n - lg_m - 1];
|
||||
let root_row = base.powers().take(m.max(2)).collect();
|
||||
root_table.push(root_row);
|
||||
}
|
||||
root_table
|
||||
}
|
||||
|
||||
impl<F: Field> FftPrecomputation<F> {
|
||||
pub fn size(&self) -> usize {
|
||||
self.subgroups_rev.last().unwrap().len()
|
||||
#[inline]
|
||||
fn fft_dispatch<F: Field>(
|
||||
input: Vec<F>,
|
||||
zero_factor: Option<usize>,
|
||||
root_table: Option<FftRootTable<F>>,
|
||||
) -> Vec<F> {
|
||||
let n = input.len();
|
||||
match FFT_STRATEGY {
|
||||
FftStrategy::Classic => fft_classic(
|
||||
input,
|
||||
zero_factor.unwrap_or(0),
|
||||
root_table.unwrap_or_else(|| fft_classic_root_table(n)),
|
||||
),
|
||||
FftStrategy::Unrolled => fft_unrolled(
|
||||
input,
|
||||
zero_factor.unwrap_or(0),
|
||||
root_table.unwrap_or_else(|| fft_unrolled_root_table(n)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fft<F: Field>(poly: PolynomialCoeffs<F>) -> PolynomialValues<F> {
|
||||
let precomputation = fft_precompute(poly.len());
|
||||
fft_with_precomputation_power_of_2(poly, &precomputation)
|
||||
fft_with_options(poly, None, None)
|
||||
}
|
||||
|
||||
pub(crate) fn fft_precompute<F: Field>(degree: usize) -> FftPrecomputation<F> {
|
||||
let degree_log = log2_ceil(degree);
|
||||
|
||||
let mut subgroups_rev = Vec::new();
|
||||
let mut subgroup = F::two_adic_subgroup(degree_log);
|
||||
for _i in 0..=degree_log {
|
||||
let subsubgroup = subgroup.iter().step_by(2).copied().collect();
|
||||
let subgroup_rev = reverse_index_bits(subgroup);
|
||||
subgroups_rev.push(subgroup_rev);
|
||||
subgroup = subsubgroup;
|
||||
#[inline]
|
||||
pub fn fft_with_options<F: Field>(
|
||||
poly: PolynomialCoeffs<F>,
|
||||
zero_factor: Option<usize>,
|
||||
root_table: Option<FftRootTable<F>>,
|
||||
) -> PolynomialValues<F> {
|
||||
let PolynomialCoeffs { coeffs } = poly;
|
||||
PolynomialValues {
|
||||
values: fft_dispatch(coeffs, zero_factor, root_table),
|
||||
}
|
||||
subgroups_rev.reverse();
|
||||
|
||||
FftPrecomputation { subgroups_rev }
|
||||
}
|
||||
|
||||
pub(crate) fn ifft_with_precomputation_power_of_2<F: Field>(
|
||||
#[inline]
|
||||
pub fn ifft<F: Field>(poly: PolynomialValues<F>) -> PolynomialCoeffs<F> {
|
||||
ifft_with_options(poly, None, None)
|
||||
}
|
||||
|
||||
pub fn ifft_with_options<F: Field>(
|
||||
poly: PolynomialValues<F>,
|
||||
precomputation: &FftPrecomputation<F>,
|
||||
zero_factor: Option<usize>,
|
||||
root_table: Option<FftRootTable<F>>,
|
||||
) -> PolynomialCoeffs<F> {
|
||||
let n = poly.len();
|
||||
let n_inv = F::from_canonical_usize(n).try_inverse().unwrap();
|
||||
let lg_n = log2_strict(n);
|
||||
let n_inv = F::inverse_2exp(lg_n);
|
||||
|
||||
let PolynomialValues { values } = poly;
|
||||
let PolynomialValues { values: mut result } =
|
||||
fft_with_precomputation_power_of_2(PolynomialCoeffs { coeffs: values }, precomputation);
|
||||
let mut coeffs = fft_dispatch(values, zero_factor, root_table);
|
||||
|
||||
// We reverse all values except the first, and divide each by n.
|
||||
result[0] *= n_inv;
|
||||
result[n / 2] *= n_inv;
|
||||
coeffs[0] *= n_inv;
|
||||
coeffs[n / 2] *= n_inv;
|
||||
for i in 1..(n / 2) {
|
||||
let j = n - i;
|
||||
let result_i = result[j] * n_inv;
|
||||
let result_j = result[i] * n_inv;
|
||||
result[i] = result_i;
|
||||
result[j] = result_j;
|
||||
let coeffs_i = coeffs[j] * n_inv;
|
||||
let coeffs_j = coeffs[i] * n_inv;
|
||||
coeffs[i] = coeffs_i;
|
||||
coeffs[j] = coeffs_j;
|
||||
}
|
||||
PolynomialCoeffs { coeffs: result }
|
||||
PolynomialCoeffs { coeffs }
|
||||
}
|
||||
|
||||
pub(crate) fn fft_with_precomputation_power_of_2<F: Field>(
|
||||
poly: PolynomialCoeffs<F>,
|
||||
precomputation: &FftPrecomputation<F>,
|
||||
) -> PolynomialValues<F> {
|
||||
debug_assert_eq!(
|
||||
poly.len(),
|
||||
precomputation.subgroups_rev.last().unwrap().len(),
|
||||
"Number of coefficients does not match size of subgroup in precomputation"
|
||||
);
|
||||
/// FFT implementation based on Section 32.3 of "Introduction to
|
||||
/// Algorithms" by Cormen et al.
|
||||
///
|
||||
/// The parameter r signifies that the first 1/2^r of the entries of
|
||||
/// input may be non-zero, but the last 1 - 1/2^r entries are
|
||||
/// definitely zero.
|
||||
pub(crate) fn fft_classic<F: Field>(
|
||||
input: Vec<F>,
|
||||
r: usize,
|
||||
root_table: FftRootTable<F>,
|
||||
) -> Vec<F> {
|
||||
let mut values = reverse_index_bits(input);
|
||||
|
||||
let half_degree = poly.len() >> 1;
|
||||
let degree_log = poly.log_len();
|
||||
let n = values.len();
|
||||
let lg_n = log2_strict(n);
|
||||
|
||||
// In the base layer, we're just evaluating "degree 0 polynomials", i.e. the coefficients
|
||||
// themselves.
|
||||
let PolynomialCoeffs { coeffs } = poly;
|
||||
let mut evaluations = reverse_index_bits(coeffs);
|
||||
if root_table.len() != lg_n {
|
||||
panic!(
|
||||
"Expected root table of length {}, but it was {}.",
|
||||
lg_n,
|
||||
root_table.len()
|
||||
);
|
||||
}
|
||||
|
||||
for i in 1..=degree_log {
|
||||
// In layer i, we're evaluating a series of polynomials, each at 2^i points. In practice
|
||||
// we evaluate a pair of points together, so we have 2^(i - 1) pairs.
|
||||
let points_per_poly = 1 << i;
|
||||
let pairs_per_poly = 1 << (i - 1);
|
||||
|
||||
let mut new_evaluations = Vec::new();
|
||||
for pair_index in 0..half_degree {
|
||||
let poly_index = pair_index / pairs_per_poly;
|
||||
let pair_index_within_poly = pair_index % pairs_per_poly;
|
||||
|
||||
let child_index_0 = poly_index * points_per_poly + pair_index_within_poly;
|
||||
let child_index_1 = child_index_0 + pairs_per_poly;
|
||||
|
||||
let even = evaluations[child_index_0];
|
||||
let odd = evaluations[child_index_1];
|
||||
|
||||
let point_0 = precomputation.subgroups_rev[i][pair_index_within_poly * 2];
|
||||
let product = point_0 * odd;
|
||||
new_evaluations.push(even + product);
|
||||
new_evaluations.push(even - product);
|
||||
// After reverse_index_bits, the only non-zero elements of values
|
||||
// are at indices i*2^r for i = 0..n/2^r. The loop below copies
|
||||
// the value at i*2^r to the positions [i*2^r + 1, i*2^r + 2, ...,
|
||||
// (i+1)*2^r - 1]; i.e. it replaces the 2^r - 1 zeros following
|
||||
// element i*2^r with the value at i*2^r. This corresponds to the
|
||||
// first r rounds of the FFT when there are 2^r zeros at the end
|
||||
// of the original input.
|
||||
if r > 0 {
|
||||
// if r == 0 then this loop is a noop.
|
||||
let mask = !((1 << r) - 1);
|
||||
for i in 0..n {
|
||||
values[i] = values[i & mask];
|
||||
}
|
||||
evaluations = new_evaluations;
|
||||
}
|
||||
|
||||
// Reorder so that evaluations' indices correspond to (g_0, g_1, g_2, ...)
|
||||
let values = reverse_index_bits(evaluations);
|
||||
PolynomialValues { values }
|
||||
let mut m = 1 << (r + 1);
|
||||
for lg_m in (r + 1)..=lg_n {
|
||||
let half_m = m / 2;
|
||||
for k in (0..n).step_by(m) {
|
||||
for j in 0..half_m {
|
||||
let omega = root_table[lg_m - 1][j];
|
||||
let t = omega * values[k + half_m + j];
|
||||
let u = values[k + j];
|
||||
values[k + j] = u + t;
|
||||
values[k + half_m + j] = u - t;
|
||||
}
|
||||
}
|
||||
m *= 2;
|
||||
}
|
||||
values
|
||||
}
|
||||
|
||||
pub(crate) fn ifft<F: Field>(poly: PolynomialValues<F>) -> PolynomialCoeffs<F> {
|
||||
let precomputation = fft_precompute(poly.len());
|
||||
ifft_with_precomputation_power_of_2(poly, &precomputation)
|
||||
/// FFT implementation inspired by Barretenberg's (but with extra unrolling):
|
||||
/// https://github.com/AztecProtocol/barretenberg/blob/master/barretenberg/src/aztec/polynomials/polynomial_arithmetic.cpp#L58
|
||||
/// https://github.com/AztecProtocol/barretenberg/blob/master/barretenberg/src/aztec/polynomials/evaluation_domain.cpp#L30
|
||||
///
|
||||
/// The parameter r signifies that the first 1/2^r of the entries of
|
||||
/// input may be non-zero, but the last 1 - 1/2^r entries are
|
||||
/// definitely zero.
|
||||
fn fft_unrolled<F: Field>(input: Vec<F>, r_orig: usize, root_table: FftRootTable<F>) -> Vec<F> {
|
||||
let n = input.len();
|
||||
let lg_n = log2_strict(input.len());
|
||||
|
||||
let mut values = reverse_index_bits(input);
|
||||
|
||||
// FFT of a constant polynomial (including zero) is itself.
|
||||
if n < 2 {
|
||||
return values;
|
||||
}
|
||||
|
||||
// The 'm' corresponds to the specialisation from the 'm' in the
|
||||
// main loop (m >= 4) below.
|
||||
|
||||
// (See comment in fft_classic near same code.)
|
||||
let mut r = r_orig;
|
||||
let mut m = 1 << r;
|
||||
if r > 0 {
|
||||
// if r == 0 then this loop is a noop.
|
||||
let mask = !((1 << r) - 1);
|
||||
for i in 0..n {
|
||||
values[i] = values[i & mask];
|
||||
}
|
||||
}
|
||||
|
||||
// m = 1
|
||||
if m == 1 {
|
||||
for k in (0..n).step_by(2) {
|
||||
let t = values[k + 1];
|
||||
values[k + 1] = values[k] - t;
|
||||
values[k] += t;
|
||||
}
|
||||
r += 1;
|
||||
m *= 2;
|
||||
}
|
||||
|
||||
if n == 2 {
|
||||
return values;
|
||||
}
|
||||
|
||||
if root_table.len() != (lg_n - 1) {
|
||||
panic!(
|
||||
"Expected root table of length {}, but it was {}.",
|
||||
lg_n,
|
||||
root_table.len()
|
||||
);
|
||||
}
|
||||
|
||||
// m = 2
|
||||
if m <= 2 {
|
||||
for k in (0..n).step_by(4) {
|
||||
// NB: Grouping statements as is done in the main loop below
|
||||
// does not seem to help here (worse by a few millis).
|
||||
let omega_0 = root_table[0][0];
|
||||
let tmp_0 = omega_0 * values[k + 2 + 0];
|
||||
values[k + 2 + 0] = values[k + 0] - tmp_0;
|
||||
values[k + 0] += tmp_0;
|
||||
|
||||
let omega_1 = root_table[0][1];
|
||||
let tmp_1 = omega_1 * values[k + 2 + 1];
|
||||
values[k + 2 + 1] = values[k + 1] - tmp_1;
|
||||
values[k + 1] += tmp_1;
|
||||
}
|
||||
r += 1;
|
||||
m *= 2;
|
||||
}
|
||||
|
||||
// m >= 4
|
||||
for lg_m in r..lg_n {
|
||||
for k in (0..n).step_by(2 * m) {
|
||||
// Unrolled the commented loop by groups of 4 and
|
||||
// rearranged the lines. Improves runtime by about
|
||||
// 10%.
|
||||
/*
|
||||
for j in (0..m) {
|
||||
let omega = root_table[lg_m - 1][j];
|
||||
let tmp = omega * values[k + m + j];
|
||||
values[k + m + j] = values[k + j] - tmp;
|
||||
values[k + j] += tmp;
|
||||
}
|
||||
*/
|
||||
for j in (0..m).step_by(4) {
|
||||
let off1 = k + j;
|
||||
let off2 = k + m + j;
|
||||
|
||||
let omega_0 = root_table[lg_m - 1][j + 0];
|
||||
let omega_1 = root_table[lg_m - 1][j + 1];
|
||||
let omega_2 = root_table[lg_m - 1][j + 2];
|
||||
let omega_3 = root_table[lg_m - 1][j + 3];
|
||||
|
||||
let tmp_0 = omega_0 * values[off2 + 0];
|
||||
let tmp_1 = omega_1 * values[off2 + 1];
|
||||
let tmp_2 = omega_2 * values[off2 + 2];
|
||||
let tmp_3 = omega_3 * values[off2 + 3];
|
||||
|
||||
values[off2 + 0] = values[off1 + 0] - tmp_0;
|
||||
values[off2 + 1] = values[off1 + 1] - tmp_1;
|
||||
values[off2 + 2] = values[off1 + 2] - tmp_2;
|
||||
values[off2 + 3] = values[off1 + 3] - tmp_3;
|
||||
values[off1 + 0] += tmp_0;
|
||||
values[off1 + 1] += tmp_1;
|
||||
values[off1 + 2] += tmp_2;
|
||||
values[off1 + 3] += tmp_3;
|
||||
}
|
||||
}
|
||||
m *= 2;
|
||||
}
|
||||
values
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::field::crandall_field::CrandallField;
|
||||
use crate::field::fft::{fft, ifft};
|
||||
use crate::field::fft::{fft, fft_with_options, ifft};
|
||||
use crate::field::field::Field;
|
||||
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
|
||||
use crate::util::{log2_ceil, log2_strict};
|
||||
@ -162,6 +335,15 @@ mod tests {
|
||||
for i in degree..degree_padded {
|
||||
assert_eq!(interpolated_coefficients.coeffs[i], F::ZERO);
|
||||
}
|
||||
|
||||
for r in 0..4 {
|
||||
// expand ceofficients by factor 2^r by filling with zeros
|
||||
let zero_tail = coefficients.clone().lde(r);
|
||||
assert_eq!(
|
||||
fft(zero_tail.clone()),
|
||||
fft_with_options(zero_tail, Some(r), None)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_naive<F: Field>(coefficients: &PolynomialCoeffs<F>) -> PolynomialValues<F> {
|
||||
|
||||
@ -32,11 +32,14 @@ pub trait Field:
|
||||
+ Send
|
||||
+ Sync
|
||||
{
|
||||
type PrimeField: Field;
|
||||
|
||||
const ZERO: Self;
|
||||
const ONE: Self;
|
||||
const TWO: Self;
|
||||
const NEG_ONE: Self;
|
||||
|
||||
const CHARACTERISTIC: u64;
|
||||
const ORDER: u64;
|
||||
const TWO_ADICITY: usize;
|
||||
|
||||
@ -101,6 +104,31 @@ pub trait Field:
|
||||
x_inv
|
||||
}
|
||||
|
||||
/// Compute the inverse of 2^exp in this field.
|
||||
#[inline]
|
||||
fn inverse_2exp(exp: usize) -> Self {
|
||||
let p = Self::CHARACTERISTIC;
|
||||
|
||||
if exp <= Self::PrimeField::TWO_ADICITY {
|
||||
// The inverse of 2^exp is p-(p-1)/2^exp when char(F) = p and exp is
|
||||
// at most the TWO_ADICITY of the prime field.
|
||||
//
|
||||
// NB: PrimeFields fit in 64 bits => TWO_ADICITY < 64 =>
|
||||
// exp < 64 => this shift amount is legal.
|
||||
Self::from_canonical_u64(p - ((p - 1) >> exp))
|
||||
} else {
|
||||
// In the general case we compute 1/2 = (p+1)/2 and then exponentiate
|
||||
// by exp to get 1/2^exp. Costs about log_2(exp) operations.
|
||||
let half = Self::from_canonical_u64((p + 1) >> 1);
|
||||
half.exp(exp as u64)
|
||||
|
||||
// TODO: Faster to combine several high powers of 1/2 using multiple
|
||||
// applications of the trick above. E.g. if the 2-adicity is v, then
|
||||
// compute 1/2^(v^2 + v + 13) with 1/2^((v + 1) * v + 13), etc.
|
||||
// (using the v-adic expansion of m). Costs about log_v(exp) operations.
|
||||
}
|
||||
}
|
||||
|
||||
fn primitive_root_of_unity(n_log: usize) -> Self {
|
||||
assert!(n_log <= Self::TWO_ADICITY);
|
||||
let mut base = Self::POWER_OF_TWO_GENERATOR;
|
||||
|
||||
@ -315,6 +315,20 @@ macro_rules! test_arithmetic {
|
||||
assert_eq!(x, F::ONE);
|
||||
assert_eq!(F::ZERO - x, F::NEG_ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverse_2exp() {
|
||||
// Just check consistency with try_inverse()
|
||||
type F = $field;
|
||||
|
||||
let v = <F as Field>::PrimeField::TWO_ADICITY;
|
||||
|
||||
for e in [0, 1, 2, 3, 4, v - 2, v - 1, v, v + 1, v + 2, 123 * v] {
|
||||
let x = F::TWO.exp(e as u64).inverse();
|
||||
let y = F::inverse_2exp(e);
|
||||
assert_eq!(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::field::extension_field::Extendable;
|
||||
use crate::target::Target;
|
||||
use crate::util::bits_u64;
|
||||
|
||||
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
/// Computes `-x`.
|
||||
|
||||
@ -6,7 +6,7 @@ use num::Integer;
|
||||
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::field::extension_field::target::{ExtensionAlgebraTarget, ExtensionTarget};
|
||||
use crate::field::extension_field::{Extendable, FieldExtension, OEF};
|
||||
use crate::field::extension_field::{Extendable, OEF};
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::arithmetic::ArithmeticExtensionGate;
|
||||
use crate::generator::SimpleGenerator;
|
||||
|
||||
@ -1,9 +1,42 @@
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::field::extension_field::target::ExtensionTarget;
|
||||
use crate::field::extension_field::Extendable;
|
||||
use crate::generator::NonzeroTestGenerator;
|
||||
use crate::target::Target;
|
||||
|
||||
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
/// Evaluates to 0 if `x` equals zero, 1 otherwise.
|
||||
/// From section 2 of https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf,
|
||||
/// based on an idea from https://eprint.iacr.org/2012/598.pdf.
|
||||
pub fn is_nonzero(&mut self, x: Target) -> Target {
|
||||
// Dummy variable.
|
||||
let m = self.add_virtual_target();
|
||||
|
||||
// The prover sets this the dummy variable to 1/x if x != 0, or to an arbitrary value if
|
||||
// x == 0.
|
||||
self.add_generator(NonzeroTestGenerator {
|
||||
to_test: x,
|
||||
dummy: m,
|
||||
});
|
||||
|
||||
// Evaluates to (0) * (0) = 0 if x == 0 and (x) * (1/x) = 1 otherwise.
|
||||
let y = self.mul(x, m);
|
||||
|
||||
// Enforce that (1 - y) * x == 0.
|
||||
let prod = self.arithmetic(F::NEG_ONE, x, y, F::ONE, x);
|
||||
self.assert_zero(prod);
|
||||
|
||||
y
|
||||
}
|
||||
|
||||
/// Evaluates to 1 if `x` and `y` are equal, 0 otherwise.
|
||||
pub fn is_equal(&mut self, x: Target, y: Target) -> Target {
|
||||
let difference = self.sub(x, y);
|
||||
let not_equal = self.is_nonzero(difference);
|
||||
let one = self.one();
|
||||
self.sub(one, not_equal)
|
||||
}
|
||||
|
||||
/// Inserts a `Target` in a vector at a non-deterministic index. This is done by rotating to the
|
||||
/// left, inserting at 0 and then rotating to the right.
|
||||
/// Note: `index` is not range-checked.
|
||||
@ -13,9 +46,29 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
element: ExtensionTarget<D>,
|
||||
v: Vec<ExtensionTarget<D>>,
|
||||
) -> Vec<ExtensionTarget<D>> {
|
||||
let mut v = self.rotate_left(index, &v);
|
||||
v.insert(0, element);
|
||||
self.rotate_right(index, &v)
|
||||
let mut already_inserted = self.zero();
|
||||
let mut new_list = Vec::new();
|
||||
|
||||
for i in 0..v.len() {
|
||||
let one = self.one();
|
||||
|
||||
let cur_index = self.constant(F::from_canonical_usize(i));
|
||||
let insert_here = self.is_equal(cur_index, index);
|
||||
|
||||
let mut new_item = self.zero_extension();
|
||||
new_item = self.scalar_mul_add_extension(insert_here, element, new_item);
|
||||
if i > 0 {
|
||||
new_item = self.scalar_mul_add_extension(already_inserted, v[i - 1], new_item);
|
||||
}
|
||||
already_inserted = self.add(already_inserted, insert_here);
|
||||
|
||||
let not_already_inserted = self.sub(one, already_inserted);
|
||||
new_item = self.scalar_mul_add_extension(not_already_inserted, v[i], new_item);
|
||||
|
||||
new_list.push(new_item);
|
||||
}
|
||||
|
||||
new_list
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
||||
@ -9,14 +9,14 @@ use crate::wire::Wire;
|
||||
use crate::witness::PartialWitness;
|
||||
|
||||
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
|
||||
/// Split the given integer into a list of virtual advice targets, where each one represents a
|
||||
/// bit of the integer, with little-endian ordering.
|
||||
/// Split the given integer into a list of virtual targets, where each one represents a bit of
|
||||
/// the integer, with little-endian ordering.
|
||||
///
|
||||
/// Note that this only handles witness generation; it does not enforce that the decomposition
|
||||
/// is correct. The output should be treated as a "purported" decomposition which must be
|
||||
/// enforced elsewhere.
|
||||
pub(crate) fn split_le_virtual(&mut self, integer: Target, num_bits: usize) -> Vec<Target> {
|
||||
let bit_targets = self.add_virtual_advice_targets(num_bits);
|
||||
let bit_targets = self.add_virtual_targets(num_bits);
|
||||
self.add_generator(SplitGenerator {
|
||||
integer,
|
||||
bits: bit_targets.clone(),
|
||||
|
||||
@ -86,7 +86,7 @@ impl<F: Extendable<D>, const D: usize> Tree<GateRef<F, D>> {
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"Found tree with max degree {} and {} constants wires in {}s.",
|
||||
"Found tree with max degree {} and {} constants wires in {:.4}s.",
|
||||
best_degree,
|
||||
best_num_constants,
|
||||
timer.elapsed().as_secs_f32()
|
||||
|
||||
@ -130,3 +130,27 @@ impl<F: Field> SimpleGenerator<F> for RandomValueGenerator {
|
||||
PartialWitness::singleton_target(self.target, random_value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A generator for testing if a value equals zero
|
||||
pub(crate) struct NonzeroTestGenerator {
|
||||
pub(crate) to_test: Target,
|
||||
pub(crate) dummy: Target,
|
||||
}
|
||||
|
||||
impl<F: Field> SimpleGenerator<F> for NonzeroTestGenerator {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
vec![self.to_test]
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
let to_test_value = witness.get_target(self.to_test);
|
||||
|
||||
let dummy_value = if to_test_value == F::ZERO {
|
||||
F::ONE
|
||||
} else {
|
||||
to_test_value.inverse()
|
||||
};
|
||||
|
||||
PartialWitness::singleton_target(self.dummy, dummy_value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ impl TargetPartitions {
|
||||
}
|
||||
|
||||
pub fn to_wire_partitions(&self) -> WirePartitions {
|
||||
// Here we just drop all CircuitInputs, leaving all GateInputs.
|
||||
// Here we keep just the Wire targets, filtering out everything else.
|
||||
let mut partitions = Vec::new();
|
||||
let mut indices = HashMap::new();
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
use std::cmp::max;
|
||||
use std::iter::Sum;
|
||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
use crate::field::extension_field::Extendable;
|
||||
use crate::field::fft::{fft, ifft};
|
||||
use crate::field::fft::{fft, fft_with_options, ifft};
|
||||
use crate::field::field::Field;
|
||||
use crate::util::log2_strict;
|
||||
|
||||
@ -55,7 +56,7 @@ impl<F: Field> PolynomialValues<F> {
|
||||
|
||||
pub fn lde(self, rate_bits: usize) -> Self {
|
||||
let coeffs = ifft(self).lde(rate_bits);
|
||||
fft(coeffs)
|
||||
fft_with_options(coeffs, Some(rate_bits), None)
|
||||
}
|
||||
|
||||
pub fn degree(&self) -> usize {
|
||||
|
||||
@ -7,8 +7,15 @@ use crate::wire::Wire;
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum Target {
|
||||
Wire(Wire),
|
||||
PublicInput { index: usize },
|
||||
VirtualAdviceTarget { index: usize },
|
||||
PublicInput {
|
||||
index: usize,
|
||||
},
|
||||
/// A target that doesn't have any inherent location in the witness (but it can be copied to
|
||||
/// another target that does). This is useful for representing intermediate values in witness
|
||||
/// generation.
|
||||
VirtualTarget {
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Target {
|
||||
@ -20,7 +27,7 @@ impl Target {
|
||||
match self {
|
||||
Target::Wire(wire) => wire.is_routable(config),
|
||||
Target::PublicInput { .. } => true,
|
||||
Target::VirtualAdviceTarget { .. } => false,
|
||||
Target::VirtualTarget { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,13 +50,13 @@ pub(crate) fn transpose<T: Clone>(matrix: &[Vec<T>]) -> Vec<Vec<T>> {
|
||||
}
|
||||
|
||||
/// Permutes `arr` such that each index is mapped to its reverse in binary.
|
||||
pub(crate) fn reverse_index_bits<T: Clone>(arr: Vec<T>) -> Vec<T> {
|
||||
pub(crate) fn reverse_index_bits<T: Copy>(arr: Vec<T>) -> Vec<T> {
|
||||
let n = arr.len();
|
||||
let n_power = log2_strict(n);
|
||||
|
||||
let mut result = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
result.push(arr[reverse_bits(i, n_power)].clone());
|
||||
result.push(arr[reverse_bits(i, n_power)]);
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -74,12 +74,13 @@ pub(crate) fn reverse_index_bits_in_place<T>(arr: &mut Vec<T>) {
|
||||
}
|
||||
|
||||
pub(crate) fn reverse_bits(n: usize, num_bits: usize) -> usize {
|
||||
let mut result = 0;
|
||||
for i in 0..num_bits {
|
||||
let i_rev = num_bits - i - 1;
|
||||
result |= (n >> i & 1) << i_rev;
|
||||
}
|
||||
result
|
||||
// NB: The only reason we need overflowing_shr() here as opposed
|
||||
// to plain '>>' is to accommodate the case n == num_bits == 0,
|
||||
// which would become `0 >> 64`. Rust thinks that any shift of 64
|
||||
// bits causes overflow, even when the argument is zero.
|
||||
n.reverse_bits()
|
||||
.overflowing_shr(usize::BITS - num_bits as u32)
|
||||
.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -7,7 +7,7 @@ macro_rules! timed {
|
||||
|
||||
let timer = Instant::now();
|
||||
let res = $a;
|
||||
info!("{:.3}s {}", timer.elapsed().as_secs_f32(), $msg);
|
||||
info!("{:.4}s {}", timer.elapsed().as_secs_f32(), $msg);
|
||||
res
|
||||
}};
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ impl<F: Field> Witness<F> {
|
||||
F: Extendable<D>,
|
||||
{
|
||||
for &(a, b) in copy_constraints {
|
||||
// TODO: Take care of public inputs once they land.
|
||||
// TODO: Take care of public inputs once they land, and virtual targets.
|
||||
if let (
|
||||
Target::Wire(Wire {
|
||||
gate: a_gate,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user