Added in-circuit reverse_bits and exp.

This commit is contained in:
wborgeaud 2021-06-09 17:39:45 +02:00
parent 9adf5bb43f
commit 89761ef22a
6 changed files with 163 additions and 38 deletions

View File

@ -21,8 +21,15 @@ use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place};
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity
/// and P' is the FRI reduced polynomial.
fn compute_evaluation(&mut self) {
todo!();
fn compute_evaluation(
&mut self,
x: Target,
old_x_index: Target,
arity_bits: usize,
last_evals: &[ExtensionTarget<D>],
beta: ExtensionTarget<D>,
) -> ExtensionTarget<D> {
todo!()
// debug_assert_eq!(last_evals.len(), 1 << arity_bits);
//
// let g = F::primitive_root_of_unity(arity_bits);
@ -272,10 +279,11 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// TODO: The verifier will need to check these constants at some point (out of circuit).
let g = self.constant(F::MULTIPLICATIVE_GROUP_GENERATOR);
let phi = self.constant(F::primitive_root_of_unity(log_n));
// TODO: Gate for this.
let reversed_x = self.reverse_bits(x_index, log_n);
let reversed_x = self.reverse_bits::<2>(x_index, log_n);
let phi = self.exp(phi, reversed_x);
let mut subgroup_x = self.mul(g, phi);
for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() {
let arity = 1 << arity_bits;
let next_domain_size = domain_size >> arity_bits;

View File

@ -172,6 +172,21 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
product
}
// TODO: Optimize this, maybe with a new gate.
pub fn exp(&mut self, base: Target, exponent: Target) -> Target {
let mut current = base;
let one = self.one();
let mut product = one;
let exponent_bits = self.split_le(exponent);
for bit in exponent_bits.into_iter() {
product = self.mul_many(&[bit, current, product]);
current = self.mul(current, current);
}
product
}
/// Computes `q = x / y` by witnessing `q` and requiring that `q * y = x`. This can be unsafe in
/// some cases, as it allows `0 / 0 = <anything>`.
pub fn div_unsafe(&mut self, x: Target, y: Target) -> Target {

View File

@ -4,20 +4,17 @@ use crate::field::field::Field;
use crate::gates::base_sum::{BaseSplitGenerator, BaseSumGate};
use crate::generator::{SimpleGenerator, WitnessGenerator};
use crate::target::Target;
use crate::util::ceil_div_usize;
use crate::util::{ceil_div_usize, log2_strict};
use crate::wire::Wire;
use crate::witness::PartialWitness;
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Split the given element into a list of 11 targets, where each one represents a
/// base-64 limb of the element, with little-endian ordering.
/// Split the given element into a list of targets, where each one represents a
/// base-B limb of the element, with little-endian ordering.
pub(crate) fn split_le_base<const B: usize>(&mut self, x: Target) -> Vec<Target> {
let num_limbs = num_limbs_to_check(64, B);
let gate = self.add_gate(BaseSumGate::<B>::new(num_limbs), vec![]);
let sum = Target::Wire(Wire {
gate,
input: BaseSumGate::<B>::WIRE_SUM,
});
let sum = Target::wire(gate, BaseSumGate::<B>::WIRE_SUM);
self.route(x, sum);
Target::wires_from_range(
@ -38,12 +35,21 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
self.assert_zero(limbs[i]);
}
}
pub(crate) fn reverse_bits<const B: usize>(&mut self, x: Target, num_limbs: usize) -> Target {
let gate = self.add_gate(BaseSumGate::<B>::new(num_limbs), vec![]);
let sum = Target::wire(gate, BaseSumGate::<B>::WIRE_SUM);
self.route(x, sum);
Target::wire(gate, BaseSumGate::<B>::WIRE_REVERSED_SUM)
}
}
/// Returns `k` such that any number with `k` trailing zeros in base `base` has at least
/// `n` trailing zeros in base 2.
fn num_limbs_to_check(n: u32, base: usize) -> usize {
(n as f64 * (2.0_f64.log(base as f64))).ceil() as usize
const fn num_limbs_to_check(n: u32, base: usize) -> usize {
assert_eq!(base % 2, 0, "Base should be even.");
ceil_div_usize(n as usize, base.trailing_zeros() as usize)
}
#[cfg(test)]

View File

@ -1,8 +1,10 @@
use crate::circuit_builder::CircuitBuilder;
use crate::field::extension_field::Extendable;
use crate::field::field::Field;
use crate::gates::base_sum::BaseSumGate;
use crate::generator::{SimpleGenerator, WitnessGenerator};
use crate::target::Target;
use crate::util::ceil_div_usize;
use crate::wire::Wire;
use crate::witness::PartialWitness;
@ -21,6 +23,48 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
});
bit_targets
}
/// Split the given integer into a list of wires, where each one represents a
/// bit of the integer, with little-endian ordering.
/// Verifies that the decomposition is correct by using `k` `BaseSum<2>` gates
/// with `k` such that `k*num_routed_bits>=64`.
pub(crate) fn split_le(&mut self, integer: Target) -> Vec<Target> {
let num_limbs = self.config.num_routed_wires - BaseSumGate::WIRE_LIMBS_START;
let k = ceil_div_usize(64, num_limbs);
let gates = (0..k)
.map(|_| self.add_gate_no_constants(BaseSumGate::new(num_limbs)))
.collect::<Vec<_>>();
let mut bits = Vec::with_capacity(64);
for &gate in &gates {
bits.extend(Target::wires_from_range(
gate,
BaseSumGate::WIRE_LIMBS_START..BaseSumGate::WIRE_LIMBS_START + num_limbs,
));
}
let zero = self.zero();
let mut acc = zero;
for &gate in gates.iter().rev() {
let sum = Target::wire(gate, BaseSumGate::WIRE_SUM);
acc = self.arithmetic(
F::from_canonical_usize(1 << num_limbs),
acc,
zero,
F::ONE,
sum,
);
}
self.assert_equal(acc, integer);
self.add_generator(WireSplitGenerator {
integer,
gates,
num_limbs,
});
bits
}
}
/// Generator for a little-endian split.
@ -79,3 +123,39 @@ impl<F: Field> SimpleGenerator<F> for SplitGenerator {
result
}
}
#[derive(Debug)]
struct WireSplitGenerator {
integer: Target,
gates: Vec<usize>,
num_limbs: usize,
}
impl<F: Field> SimpleGenerator<F> for WireSplitGenerator {
fn dependencies(&self) -> Vec<Target> {
vec![self.integer]
}
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
let mut integer_value = witness.get_target(self.integer).to_canonical_u64();
let mut result = PartialWitness::new();
for gate in self.gates {
let sum = Target::wire(gate, BaseSumGate::WIRE_SUM);
result.set_target(
sum,
F::from_canonical_u64(integer_value & ((1 << self.num_limbs) - 1)),
);
integer_value >> self.num_limbs;
}
debug_assert_eq!(
integer_value,
0,
"Integer too large to fit in {} many `BaseSumGate`s",
self.gates.len()
);
result
}
}

View File

@ -12,7 +12,7 @@ use crate::wire::Wire;
use crate::witness::PartialWitness;
use std::ops::Range;
/// A gate which can sum base W limbs.
/// A gate which can sum base W limbs and the reversed limbs.
#[derive(Debug)]
pub struct BaseSumGate<const B: usize> {
num_limbs: usize,
@ -24,7 +24,8 @@ impl<const B: usize> BaseSumGate<B> {
}
pub const WIRE_SUM: usize = 0;
pub const WIRE_LIMBS_START: usize = 1;
pub const WIRE_REVERSED_SUM: usize = 1;
pub const WIRE_LIMBS_START: usize = 2;
/// Returns the index of the `i`th limb wire.
pub fn limbs(&self) -> Range<usize> {
@ -39,9 +40,13 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
fn eval_unfiltered(&self, vars: EvaluationVars<F, D>) -> Vec<F::Extension> {
let sum = vars.local_wires[Self::WIRE_SUM];
let limbs = vars.local_wires[self.limbs()].to_vec();
let reversed_sum = vars.local_wires[Self::WIRE_REVERSED_SUM];
let mut limbs = vars.local_wires[self.limbs()].to_vec();
let computed_sum = reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B));
let mut constraints = vec![computed_sum - sum];
limbs.reverse();
let computed_reversed_sum =
reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B));
let mut constraints = vec![computed_sum - sum, computed_reversed_sum - reversed_sum];
for limb in limbs {
constraints.push(
(0..B)
@ -59,10 +64,15 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
) -> Vec<ExtensionTarget<D>> {
let base = builder.constant(F::from_canonical_usize(B));
let sum = vars.local_wires[Self::WIRE_SUM];
let limbs = vars.local_wires[self.limbs()].to_vec();
let computed_sum =
reduce_with_powers_recursive(builder, &vars.local_wires[self.limbs()], base);
let mut constraints = vec![builder.sub_extension(computed_sum, sum)];
let reversed_sum = vars.local_wires[Self::WIRE_REVERSED_SUM];
let mut limbs = vars.local_wires[self.limbs()].to_vec();
let computed_sum = reduce_with_powers_recursive(builder, &limbs, base);
limbs.reverse();
let reversed_computed_sum = reduce_with_powers_recursive(builder, &limbs, base);
let mut constraints = vec![
builder.sub_extension(computed_sum, sum),
builder.sub_extension(reversed_computed_sum, computed_sum),
];
for limb in limbs {
constraints.push({
let mut acc = builder.one_extension();
@ -89,20 +99,23 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
vec![Box::new(gen)]
}
// 2 for the sum and reversed sum, then `num_limbs` for the limbs.
fn num_wires(&self) -> usize {
self.num_limbs + 1
self.num_limbs + 2
}
fn num_constants(&self) -> usize {
0
}
// Bounded by the range-check (x-0)*(x-1)*...*(x-B+1).
fn degree(&self) -> usize {
B
}
// 2 for checking the sum and reversed sum, then `num_limbs` for range-checking the limbs.
fn num_constraints(&self) -> usize {
1 + self.num_limbs
2 + self.num_limbs
}
}
@ -121,26 +134,29 @@ impl<F: Field, const B: usize> SimpleGenerator<F> for BaseSplitGenerator<B> {
}
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
let mut sum_value = witness
.get_target(Target::Wire(Wire {
gate: self.gate_index,
input: BaseSumGate::<B>::WIRE_SUM,
}))
let sum_value = witness
.get_target(Target::wire(self.gate_index, BaseSumGate::<B>::WIRE_SUM))
.to_canonical_u64() as usize;
let limbs = (BaseSumGate::<B>::WIRE_LIMBS_START
..BaseSumGate::<B>::WIRE_LIMBS_START + self.num_limbs)
.map(|i| {
Target::Wire(Wire {
gate: self.gate_index,
input: i,
})
});
.map(|i| Target::wire(self.gate_index, i));
let limbs_value = (0..self.num_limbs)
.scan(sum_value, |acc, _| {
let tmp = *acc % B;
*acc /= B;
Some(tmp)
})
.collect::<Vec<_>>();
let reversed_sum = limbs_value.iter().rev().fold(0, |acc, &x| acc * B + x);
let mut result = PartialWitness::new();
for b in limbs {
let b_value = sum_value % B;
result.set_target(
Target::wire(self.gate_index, BaseSumGate::<B>::WIRE_REVERSED_SUM),
F::from_canonical_usize(reversed_sum),
);
for (b, b_value) in limbs.zip(limbs_value) {
result.set_target(b, F::from_canonical_usize(b_value));
sum_value /= B;
}
debug_assert_eq!(

View File

@ -7,7 +7,7 @@ pub(crate) fn bits_u64(n: u64) -> usize {
(64 - n.leading_zeros()) as usize
}
pub(crate) fn ceil_div_usize(a: usize, b: usize) -> usize {
pub(crate) const fn ceil_div_usize(a: usize, b: usize) -> usize {
(a + b - 1) / b
}