halo2/halo2_proofs/examples/shuffle.rs

362 lines
12 KiB
Rust

use ff::BatchInvert;
use halo2_proofs::{
arithmetic::{CurveAffine, FieldExt},
circuit::{floor_planner::V1, Layouter, Value},
dev::{metadata, FailureLocation, MockProver, VerifyFailure},
halo2curves::pasta::EqAffine,
plonk::*,
poly::{
commitment::ParamsProver,
ipa::{
commitment::{IPACommitmentScheme, ParamsIPA},
multiopen::{ProverIPA, VerifierIPA},
strategy::AccumulatorStrategy,
},
Rotation, VerificationStrategy,
},
transcript::{
Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer,
},
};
use rand_core::{OsRng, RngCore};
use std::iter;
fn rand_2d_array<F: FieldExt, R: RngCore, const W: usize, const H: usize>(
rng: &mut R,
) -> [[F; H]; W] {
[(); W].map(|_| [(); H].map(|_| F::random(&mut *rng)))
}
fn shuffled<F: FieldExt, R: RngCore, const W: usize, const H: usize>(
original: [[F; H]; W],
rng: &mut R,
) -> [[F; H]; W] {
let mut shuffled = original;
for row in (1..H).rev() {
let rand_row = (rng.next_u32() as usize) % row;
for column in shuffled.iter_mut() {
column.swap(row, rand_row);
}
}
shuffled
}
#[derive(Clone)]
struct MyConfig<const W: usize> {
q_shuffle: Selector,
q_first: Selector,
q_last: Selector,
original: [Column<Advice>; W],
shuffled: [Column<Advice>; W],
theta: Challenge,
gamma: Challenge,
z: Column<Advice>,
}
impl<const W: usize> MyConfig<W> {
fn configure<F: FieldExt>(meta: &mut ConstraintSystem<F>) -> Self {
let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector());
// First phase
let original = [(); W].map(|_| meta.advice_column_in(FirstPhase));
let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase));
let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase));
// Second phase
let z = meta.advice_column_in(SecondPhase);
meta.create_gate("z should start with 1", |meta| {
let q_first = meta.query_selector(q_first);
let z = meta.query_advice(z, Rotation::cur());
let one = Expression::Constant(F::one());
vec![q_first * (one - z)]
});
meta.create_gate("z should end with 1", |meta| {
let q_last = meta.query_selector(q_last);
let z = meta.query_advice(z, Rotation::cur());
let one = Expression::Constant(F::one());
vec![q_last * (one - z)]
});
meta.create_gate("z should have valid transition", |meta| {
let q_shuffle = meta.query_selector(q_shuffle);
let original = original.map(|advice| meta.query_advice(advice, Rotation::cur()));
let shuffled = shuffled.map(|advice| meta.query_advice(advice, Rotation::cur()));
let [theta, gamma] = [theta, gamma].map(|challenge| meta.query_challenge(challenge));
let [z, z_w] =
[Rotation::cur(), Rotation::next()].map(|rotation| meta.query_advice(z, rotation));
// Compress
let original = original
.iter()
.cloned()
.reduce(|acc, a| acc * theta.clone() + a)
.unwrap();
let shuffled = shuffled
.iter()
.cloned()
.reduce(|acc, a| acc * theta.clone() + a)
.unwrap();
vec![q_shuffle * (z * (original + gamma.clone()) - z_w * (shuffled + gamma))]
});
Self {
q_shuffle,
q_first,
q_last,
original,
shuffled,
theta,
gamma,
z,
}
}
}
#[derive(Clone, Default)]
struct MyCircuit<F: FieldExt, const W: usize, const H: usize> {
original: Value<[[F; H]; W]>,
shuffled: Value<[[F; H]; W]>,
}
impl<F: FieldExt, const W: usize, const H: usize> MyCircuit<F, W, H> {
fn rand<R: RngCore>(rng: &mut R) -> Self {
let original = rand_2d_array::<F, _, W, H>(rng);
let shuffled = shuffled(original, rng);
Self {
original: Value::known(original),
shuffled: Value::known(shuffled),
}
}
}
impl<F: FieldExt, const W: usize, const H: usize> Circuit<F> for MyCircuit<F, W, H> {
type Config = MyConfig<W>;
type FloorPlanner = V1;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
MyConfig::configure(meta)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let theta = layouter.get_challenge(config.theta);
let gamma = layouter.get_challenge(config.gamma);
layouter.assign_region(
|| "Shuffle original into shuffled",
|mut region| {
// Keygen
config.q_first.enable(&mut region, 0)?;
config.q_last.enable(&mut region, H)?;
for offset in 0..H {
config.q_shuffle.enable(&mut region, offset)?;
}
// First phase
for (idx, (&column, values)) in config
.original
.iter()
.zip(self.original.transpose_array().iter())
.enumerate()
{
for (offset, &value) in values.transpose_array().iter().enumerate() {
region.assign_advice(
|| format!("original[{}][{}]", idx, offset),
column,
offset,
|| value,
)?;
}
}
for (idx, (&column, values)) in config
.shuffled
.iter()
.zip(self.shuffled.transpose_array().iter())
.enumerate()
{
for (offset, &value) in values.transpose_array().iter().enumerate() {
region.assign_advice(
|| format!("shuffled[{}][{}]", idx, offset),
column,
offset,
|| value,
)?;
}
}
// Second phase
let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map(
|(((original, shuffled), theta), gamma)| {
let mut product = vec![F::zero(); H];
for (idx, product) in product.iter_mut().enumerate() {
let mut compressed = F::zero();
for value in shuffled.iter() {
compressed *= theta;
compressed += value[idx];
}
*product = compressed + gamma
}
product.iter_mut().batch_invert();
for (idx, product) in product.iter_mut().enumerate() {
let mut compressed = F::zero();
for value in original.iter() {
compressed *= theta;
compressed += value[idx];
}
*product *= compressed + gamma
}
#[allow(clippy::let_and_return)]
let z = iter::once(F::one())
.chain(product)
.scan(F::one(), |state, cur| {
*state *= &cur;
Some(*state)
})
.collect::<Vec<_>>();
#[cfg(feature = "sanity-checks")]
assert_eq!(F::one(), *z.last().unwrap());
z
},
);
for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() {
region.assign_advice(
|| format!("z[{}]", offset),
config.z,
offset,
|| value,
)?;
}
Ok(())
},
)
}
}
fn test_mock_prover<F: FieldExt, const W: usize, const H: usize>(
k: u32,
circuit: MyCircuit<F, W, H>,
expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>,
) {
let prover = MockProver::run::<_>(k, &circuit, vec![]).unwrap();
match (prover.verify(), expected) {
(Ok(_), Ok(_)) => {}
(Err(err), Err(expected)) => {
assert_eq!(
err.into_iter()
.map(|failure| match failure {
VerifyFailure::ConstraintNotSatisfied {
constraint,
location,
..
} => (constraint, location),
_ => panic!("MockProver::verify has result unmatching expected"),
})
.collect::<Vec<_>>(),
expected
)
}
(_, _) => panic!("MockProver::verify has result unmatching expected"),
};
}
fn test_prover<C: CurveAffine, const W: usize, const H: usize>(
k: u32,
circuit: MyCircuit<C::Scalar, W, H>,
expected: bool,
) {
let params = ParamsIPA::<C>::new(k);
let vk = keygen_vk(&params, &circuit).unwrap();
let pk = keygen_pk(&params, vk, &circuit).unwrap();
let proof = {
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
create_proof::<IPACommitmentScheme<C>, ProverIPA<C>, _, _, _, _>(
&params,
&pk,
&[circuit],
&[&[]],
OsRng,
&mut transcript,
)
.expect("proof generation should not fail");
transcript.finalize()
};
let accepted = {
let strategy = AccumulatorStrategy::new(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
verify_proof::<IPACommitmentScheme<C>, VerifierIPA<C>, _, _, _>(
&params,
pk.get_vk(),
strategy,
&[&[]],
&mut transcript,
)
.map(|strategy| strategy.finalize())
.unwrap_or_default()
};
assert_eq!(accepted, expected);
}
fn main() {
const W: usize = 4;
const H: usize = 32;
const K: u32 = 8;
let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng);
{
test_mock_prover(K, circuit.clone(), Ok(()));
test_prover::<EqAffine, W, H>(K, circuit.clone(), true);
}
#[cfg(not(feature = "sanity-checks"))]
{
use std::ops::IndexMut;
let mut circuit = circuit.clone();
circuit.shuffled = circuit.shuffled.map(|mut shuffled| {
shuffled.index_mut(0).swap(0, 1);
shuffled
});
test_mock_prover(
K,
circuit.clone(),
Err(vec![(
((1, "z should end with 1").into(), 0, "").into(),
FailureLocation::InRegion {
region: (0, "Shuffle original into shuffled").into(),
offset: 32,
},
)]),
);
test_prover::<EqAffine, W, H>(K, circuit, false);
}
}