plonky2/src/gates/gmimc.rs

305 lines
9.6 KiB
Rust
Raw Normal View History

2021-02-24 13:07:22 -08:00
use std::sync::Arc;
2021-02-24 12:25:13 -08:00
use crate::circuit_builder::CircuitBuilder;
2021-02-09 21:25:21 -08:00
use crate::field::field::Field;
use crate::gates::gate::{Gate, GateRef};
use crate::generator::{SimpleGenerator, WitnessGenerator};
2021-04-02 15:29:21 -07:00
use crate::gmimc::gmimc_automatic_constants;
use crate::target::Target;
2021-04-12 10:38:07 +02:00
use crate::vars::{EvaluationTargets, EvaluationVars};
use crate::wire::Wire;
2021-03-30 20:16:20 -07:00
use crate::witness::PartialWitness;
2021-02-09 21:25:21 -08:00
/// The width of the permutation, in field elements.
const W: usize = 12;
/// Evaluates a full GMiMC permutation with 12 state elements, and writes the output to the next
/// gate's first `width` wires (which could be the input of another `GMiMCGate`).
2021-04-12 10:38:07 +02:00
///
/// This also has some extra features to make it suitable for efficiently verifying Merkle proofs.
/// It has a flag which can be used to swap the first four inputs with the next four, for ordering
/// sibling digests. It also has an accumulator that computes the weighted sum of these flags, for
/// computing the index of the leaf based on these swap bits.
2021-02-09 21:25:21 -08:00
#[derive(Debug)]
pub struct GMiMCGate<F: Field, const R: usize> {
2021-02-24 13:15:21 -08:00
constants: Arc<[F; R]>,
2021-02-09 21:25:21 -08:00
}
impl<F: Field, const R: usize> GMiMCGate<F, R> {
2021-02-24 13:15:21 -08:00
pub fn with_constants(constants: Arc<[F; R]>) -> GateRef<F> {
let gate = GMiMCGate::<F, R> { constants };
GateRef::new(gate)
2021-02-24 13:15:21 -08:00
}
pub fn with_automatic_constants() -> GateRef<F> {
2021-04-02 15:29:21 -07:00
let constants = Arc::new(gmimc_automatic_constants::<F, R>());
Self::with_constants(constants)
2021-02-09 21:25:21 -08:00
}
2021-02-26 13:18:41 -08:00
2021-04-12 10:38:07 +02:00
/// The wire index for the `i`th input to the permutation.
2021-02-26 13:18:41 -08:00
pub fn wire_input(i: usize) -> usize {
2021-03-01 12:35:02 -08:00
i
2021-02-26 13:18:41 -08:00
}
2021-04-12 10:38:07 +02:00
/// The wire index for the `i`th output to the permutation.
2021-02-26 13:18:41 -08:00
pub fn wire_output(i: usize) -> usize {
2021-04-12 10:38:07 +02:00
W + i
2021-03-01 12:35:02 -08:00
}
2021-04-12 10:38:07 +02:00
/// Used to incrementally compute the index of the leaf based on a series of swap bits.
pub const WIRE_INDEX_ACCUMULATOR_OLD: usize = 2 * W;
pub const WIRE_INDEX_ACCUMULATOR_NEW: usize = 2 * W + 1;
/// If this is set to 1, the first four inputs will be swapped with the next four inputs. This
/// is useful for ordering hashes in Merkle proofs. Otherwise, this should be set to 0.
pub const WIRE_SWAP: usize = 2 * W + 2;
/// A wire which stores the input to the `i`th cubing.
fn wire_cubing_input(i: usize) -> usize {
2021-04-12 10:38:07 +02:00
2 * W + 3 + i
2021-02-26 13:18:41 -08:00
}
2021-02-09 21:25:21 -08:00
}
impl<F: Field, const R: usize> Gate<F> for GMiMCGate<F, R> {
2021-02-09 21:25:21 -08:00
fn id(&self) -> String {
2021-02-26 13:18:41 -08:00
// TODO: This won't include generic params?
2021-02-09 21:25:21 -08:00
format!("{:?}", self)
}
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F> {
let mut constraints = Vec::with_capacity(W + R);
2021-02-24 13:07:22 -08:00
2021-04-12 10:38:07 +02:00
// Assert that `swap` is binary.
let swap = vars.local_wires[Self::WIRE_SWAP];
constraints.push(swap * (swap - F::ONE));
let old_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_OLD];
let new_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_NEW];
let computed_new_index_acc = F::TWO * old_index_acc + swap;
constraints.push(computed_new_index_acc - new_index_acc);
2021-03-01 12:35:02 -08:00
let mut state = Vec::with_capacity(12);
2021-02-26 13:18:41 -08:00
for i in 0..4 {
let a = vars.local_wires[i];
let b = vars.local_wires[i + 4];
2021-04-12 10:38:07 +02:00
state.push(a + swap * (b - a));
2021-02-26 13:18:41 -08:00
}
for i in 0..4 {
let a = vars.local_wires[i + 4];
let b = vars.local_wires[i];
2021-04-12 10:38:07 +02:00
state.push(a + swap * (b - a));
2021-02-26 13:18:41 -08:00
}
for i in 8..12 {
state.push(vars.local_wires[i]);
}
2021-04-12 10:38:07 +02:00
// Value that is implicitly added to each element.
// See https://affine.group/2020/02/starkware-challenge
let mut addition_buffer = F::ZERO;
for r in 0..R {
let active = r % W;
let cubing_input = state[active] + addition_buffer + self.constants[r];
let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)];
constraints.push(cubing_input - cubing_input_wire);
let f = cubing_input_wire.cube();
addition_buffer += f;
state[active] -= f;
}
for i in 0..W {
state[i] += addition_buffer;
2021-04-12 10:38:07 +02:00
constraints.push(state[i] - vars.local_wires[Self::wire_output(i)]);
}
constraints
}
fn eval_unfiltered_recursively(
&self,
builder: &mut CircuitBuilder<F>,
vars: EvaluationTargets,
) -> Vec<Target> {
unimplemented!()
}
fn generators(
&self,
gate_index: usize,
_local_constants: &[F],
_next_constants: &[F],
) -> Vec<Box<dyn WitnessGenerator<F>>> {
let gen = GMiMCGenerator {
gate_index,
constants: self.constants.clone(),
};
vec![Box::new(gen)]
}
fn num_wires(&self) -> usize {
W + 1 + R
}
fn num_constants(&self) -> usize {
0
}
fn degree(&self) -> usize {
3
}
fn num_constraints(&self) -> usize {
2021-04-12 10:38:07 +02:00
R + W + 2
}
}
#[derive(Debug)]
struct GMiMCGenerator<F: Field, const R: usize> {
gate_index: usize,
constants: Arc<[F; R]>,
}
impl<F: Field, const R: usize> SimpleGenerator<F> for GMiMCGenerator<F, R> {
fn dependencies(&self) -> Vec<Target> {
2021-04-12 10:38:07 +02:00
let mut dep_input_indices = Vec::with_capacity(W + 2);
for i in 0..W {
dep_input_indices.push(GMiMCGate::<F, R>::wire_input(i));
}
dep_input_indices.push(GMiMCGate::<F, R>::WIRE_SWAP);
dep_input_indices.push(GMiMCGate::<F, R>::WIRE_INDEX_ACCUMULATOR_OLD);
dep_input_indices.into_iter()
.map(|input| Target::Wire(Wire { gate: self.gate_index, input }))
.collect()
}
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
let mut result = PartialWitness::new();
let mut state = (0..W)
.map(|i| witness.get_wire(Wire {
gate: self.gate_index,
input: GMiMCGate::<F, R>::wire_input(i),
}))
.collect::<Vec<_>>();
2021-04-12 10:38:07 +02:00
let swap_value = witness.get_wire(Wire {
gate: self.gate_index,
2021-04-12 10:38:07 +02:00
input: GMiMCGate::<F, R>::WIRE_SWAP,
});
2021-04-12 10:38:07 +02:00
debug_assert!(swap_value == F::ZERO || swap_value == F::ONE);
if swap_value == F::ONE {
for i in 0..4 {
state.swap(i, 4 + i);
}
2021-02-26 13:18:41 -08:00
}
2021-04-12 10:38:07 +02:00
// Update the index accumulator.
let old_index_acc_value = witness.get_wire(Wire {
gate: self.gate_index,
input: GMiMCGate::<F, R>::WIRE_INDEX_ACCUMULATOR_OLD,
});
let new_index_acc_value = F::TWO * old_index_acc_value + swap_value;
result.set_wire(
Wire {
gate: self.gate_index,
input: GMiMCGate::<F, R>::WIRE_INDEX_ACCUMULATOR_NEW,
},
new_index_acc_value);
2021-02-24 13:07:22 -08:00
// Value that is implicitly added to each element.
// See https://affine.group/2020/02/starkware-challenge
let mut addition_buffer = F::ZERO;
2021-02-24 13:07:22 -08:00
for r in 0..R {
let active = r % W;
let cubing_input = state[active] + addition_buffer + self.constants[r];
result.set_wire(
Wire {
gate: self.gate_index,
input: GMiMCGate::<F, R>::wire_cubing_input(r),
},
cubing_input);
let f = cubing_input.cube();
addition_buffer += f;
state[active] -= f;
2021-02-24 13:07:22 -08:00
}
for i in 0..W {
state[i] += addition_buffer;
result.set_wire(
Wire {
2021-04-12 10:38:07 +02:00
gate: self.gate_index,
input: GMiMCGate::<F, R>::wire_output(i),
},
state[i]);
2021-02-24 13:07:22 -08:00
}
result
2021-02-24 12:25:13 -08:00
}
2021-02-09 21:25:21 -08:00
}
2021-02-26 23:30:22 -08:00
#[cfg(test)]
mod tests {
2021-03-01 13:40:05 -08:00
use std::convert::TryInto;
2021-02-26 23:30:22 -08:00
use std::sync::Arc;
2021-03-01 12:35:02 -08:00
use crate::circuit_data::CircuitConfig;
2021-02-26 23:30:22 -08:00
use crate::field::crandall_field::CrandallField;
use crate::field::field::Field;
use crate::gates::gmimc::{GMiMCGate, W};
2021-03-01 13:40:05 -08:00
use crate::generator::generate_partial_witness;
use crate::gmimc::gmimc_permute_naive;
use crate::wire::Wire;
use crate::witness::PartialWitness;
2021-02-26 23:30:22 -08:00
2021-03-01 12:35:02 -08:00
#[test]
fn generated_output() {
type F = CrandallField;
const R: usize = 101;
let constants = Arc::new([F::TWO; R]);
type Gate = GMiMCGate::<F, R>;
2021-03-01 13:40:05 -08:00
let gate = Gate::with_constants(constants.clone());
2021-03-01 12:35:02 -08:00
let config = CircuitConfig {
num_wires: 200,
num_routed_wires: 200,
..Default::default()
2021-03-01 12:35:02 -08:00
};
2021-03-01 13:40:05 -08:00
let permutation_inputs = (0..W)
.map(F::from_canonical_usize)
.collect::<Vec<_>>();
let mut witness = PartialWitness::new();
2021-04-12 10:38:07 +02:00
witness.set_wire(
Wire { gate: 0, input: Gate::WIRE_INDEX_ACCUMULATOR_OLD },
F::from_canonical_usize(7));
witness.set_wire(
Wire { gate: 0, input: Gate::WIRE_SWAP },
F::ZERO);
2021-03-01 13:40:05 -08:00
for i in 0..W {
witness.set_wire(
Wire { gate: 0, input: Gate::wire_input(i) },
permutation_inputs[i]);
}
let generators = gate.0.generators(0, &[], &[]);
2021-03-21 11:17:00 -07:00
generate_partial_witness(&mut witness, &generators);
2021-03-01 13:40:05 -08:00
let expected_outputs: [F; W] = gmimc_permute_naive(
permutation_inputs.try_into().unwrap(),
constants);
for i in 0..W {
let out = witness.get_wire(
2021-04-12 10:38:07 +02:00
Wire { gate: 0, input: Gate::wire_output(i) });
2021-03-01 13:40:05 -08:00
assert_eq!(out, expected_outputs[i]);
}
2021-04-12 10:38:07 +02:00
let acc_new = witness.get_wire(
Wire { gate: 0, input: Gate::WIRE_INDEX_ACCUMULATOR_NEW });
assert_eq!(acc_new, F::from_canonical_usize(7 * 2));
2021-02-26 23:30:22 -08:00
}
}