use ff::{BatchInvert, FromUniformBytes}; use halo2_axiom as halo2_proofs; use halo2_proofs::{ arithmetic::Field, circuit::{Layouter, SimpleFloorPlanner, Value}, dev::{metadata, FailureLocation, MockProver}, plonk::*, poly::{ commitment::ParamsProver, kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, multiopen::{ProverSHPLONK, VerifierSHPLONK}, strategy::SingleStrategy, }, }, transcript::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; use halo2curves::bn256::{Bn256, Fr}; use rand_core::{OsRng, RngCore}; use std::iter; fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) } fn shuffled( 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 { q_shuffle: Selector, q_first: Selector, q_last: Selector, original: [Column; W], shuffled: [Column; W], theta: Challenge, gamma: Challenge, z: Column, } impl MyConfig { fn configure(meta: &mut ConstraintSystem) -> 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", |_| { let one = Expression::Constant(F::ONE); vec![q_first.expr() * (one - z.cur())] }); meta.create_gate("z should end with 1", |_| { let one = Expression::Constant(F::ONE); vec![q_last.expr() * (one - z.cur())] }); meta.create_gate("z should have valid transition", |_| { let q_shuffle = q_shuffle.expr(); let original = original.map(|advice| advice.cur()); let shuffled = shuffled.map(|advice| advice.cur()); let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); // 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.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] }); Self { q_shuffle, q_first, q_last, original, shuffled, theta, gamma, z, } } } #[derive(Clone, Default)] struct MyCircuit { original: Value<[[F; H]; W]>, shuffled: Value<[[F; H]; W]>, } impl MyCircuit { fn rand(rng: &mut R) -> Self { let original = rand_2d_array::(rng); let shuffled = shuffled(original, rng); Self { original: Value::known(original), shuffled: Value::known(shuffled), } } } impl Circuit for MyCircuit { type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { Self::default() } fn configure(meta: &mut ConstraintSystem) -> Self::Config { MyConfig::configure(meta) } fn synthesize( &self, config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { println!("Calling synthesize function"); 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(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(column, offset, value); } } region.next_phase(); let theta = region.get_challenge(config.theta); let gamma = region.get_challenge(config.gamma); // 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::>(); #[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(config.z, offset, value); } Ok(()) }, ) } } fn test_mock_prover, const W: usize, const H: usize>( k: u32, circuit: MyCircuit, 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::>(), expected )*/ } (_, _) => panic!("MockProver::verify has result unmatching expected"), }; } fn test_prover( k: u32, circuit: MyCircuit, expected: bool, ) { let params = ParamsKZG::::new(k); let vk = keygen_vk(¶ms, &circuit).unwrap(); let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); let proof = { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); println!("Begin create proof"); create_proof::, ProverSHPLONK, _, _, _, _>( ¶ms, &pk, &[circuit], &[&[]], OsRng, &mut transcript, ) .expect("proof generation should not fail"); println!("End create proof"); transcript.finalize() }; let accepted = { let strategy = SingleStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); verify_proof::, VerifierSHPLONK, _, _, _>( ¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript, ) .is_ok() }; 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::(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::(K, circuit, false); } }