use std::fmt::Debug;
use midnight_proofs::{circuit::Layouter, plonk::Error};
use crate::{
types::{AssignedVector, InnerValue, Vectorizable},
CircuitField,
};
pub trait HashCPU<Input, Output>: Clone + Debug {
fn hash(inputs: &[Input]) -> Output;
}
pub trait HashInstructions<F, Input, Output>: HashCPU<Input::Element, Output::Element>
where
F: CircuitField,
Input: InnerValue,
Output: InnerValue,
{
fn hash(&self, layouter: &mut impl Layouter<F>, inputs: &[Input]) -> Result<Output, Error>;
}
pub trait VarHashInstructions<F, const MAX_LEN: usize, Input, Output, const A: usize>:
HashCPU<<Input as InnerValue>::Element, Output::Element>
where
F: CircuitField,
Input: Vectorizable,
Output: InnerValue,
{
fn varhash(
&self,
layouter: &mut impl Layouter<F>,
inputs: &AssignedVector<F, Input, MAX_LEN, A>,
) -> Result<Output, Error>;
}
#[cfg(any(test, feature = "testing"))]
pub(crate) mod tests {
use std::{fmt::Debug, marker::PhantomData};
use midnight_proofs::{
circuit::{SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem},
};
use rand::SeedableRng;
use rand_chacha::ChaCha12Rng;
use super::*;
#[cfg(test)]
use crate::{
field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
vec::vector_gadget::VectorGadget,
};
use crate::{
instructions::{AssertionInstructions, AssignmentInstructions},
testing_utils::{FromScratch, Sampleable},
utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
};
#[derive(Clone, Debug, Default)]
struct TestCircuit<F, Input, Output, HashChip, AssignChip>
where
Input: InnerValue,
Output: InnerValue,
{
input: Vec<Value<Input::Element>>,
expected_output: Output::Element,
_marker: PhantomData<(F, Output, HashChip, AssignChip)>,
}
impl<F, Input, Output, HashChip, AssignChip> Circuit<F>
for TestCircuit<F, Input, Output, HashChip, AssignChip>
where
F: CircuitField,
Input: InnerValue,
Output: InnerValue,
HashChip: HashInstructions<F, Input, Output> + FromScratch<F>,
AssignChip:
AssignmentInstructions<F, Input> + AssertionInstructions<F, Output> + FromScratch<F>,
{
type Config = (
<HashChip as FromScratch<F>>::Config,
<AssignChip as FromScratch<F>>::Config,
);
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let committed_instance_column = meta.instance_column();
let instance_column = meta.instance_column();
let instance_columns = [committed_instance_column, instance_column];
let mut advice_columns = vec![];
let mut fixed_columns = vec![];
(
HashChip::configure_from_scratch(
meta,
&mut advice_columns,
&mut fixed_columns,
&instance_columns,
),
AssignChip::configure_from_scratch(
meta,
&mut advice_columns,
&mut fixed_columns,
&instance_columns,
),
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let chip = HashChip::new_from_scratch(&config.0);
let assign_chip = AssignChip::new_from_scratch(&config.1);
let inputs = assign_chip.assign_many(&mut layouter, &self.input)?;
cost_measure_start(&mut layouter);
let output = chip.hash(&mut layouter, &inputs)?;
cost_measure_end(&mut layouter);
assign_chip.assert_equal_to_fixed(
&mut layouter,
&output,
self.expected_output.clone(),
)?;
chip.load_from_scratch(&mut layouter)?;
assign_chip.load_from_scratch(&mut layouter)
}
}
pub fn test_hash<F, Input, Output, HashChip, AssignChip>(
cost_model: bool,
chip_name: &str,
size: usize,
) where
F: CircuitField + ff::FromUniformBytes<64> + Ord,
Input: InnerValue + Sampleable,
Output: InnerValue,
HashChip: HashInstructions<F, Input, Output> + FromScratch<F>,
AssignChip:
AssignmentInstructions<F, Input> + AssertionInstructions<F, Output> + FromScratch<F>,
{
let mut rng = ChaCha12Rng::seed_from_u64(0xf007ba11);
let input = (0..size).map(|_| Input::sample_inner(&mut rng)).collect::<Vec<_>>();
let expected_output = <HashChip as HashCPU<Input::Element, Output::Element>>::hash(&input);
println!(
"[{}] Preimage circuit test on input {:?}",
chip_name,
input.clone()
);
let circuit = TestCircuit::<F, Input, Output, HashChip, AssignChip> {
input: input.into_iter().map(Value::known).collect(),
expected_output,
_marker: PhantomData,
};
MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
println!("\n... succeeded!\n");
if cost_model {
circuit_to_json(chip_name, "hash", circuit);
}
}
#[cfg(test)]
type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
#[cfg(test)]
#[derive(Clone, Debug, Default)]
struct TestVarHashCircuit<F, Input, Output, VarHashChip, const M: usize, const A: usize>
where
Input: Vectorizable,
Output: InnerValue,
{
input: Value<Vec<Input::Element>>,
expected_output: Output::Element,
_marker: PhantomData<(F, Output, VarHashChip)>,
}
#[cfg(test)]
impl<F, Input, Output, VarHashChip, const M: usize, const A: usize> Circuit<F>
for TestVarHashCircuit<F, Input, Output, VarHashChip, M, A>
where
F: CircuitField,
Input: Vectorizable,
Output: InnerValue,
VarHashChip: VarHashInstructions<F, M, Input, Output, A> + FromScratch<F>,
VectorGadget<F>: AssignmentInstructions<F, AssignedVector<F, Input, M, A>>,
NG<F>: AssignmentInstructions<F, Input> + AssertionInstructions<F, Output>,
{
type Config = (
<VarHashChip as FromScratch<F>>::Config,
<VectorGadget<F> as FromScratch<F>>::Config,
);
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let committed_instance_column = meta.instance_column();
let instance_column = meta.instance_column();
let instance_columns = [committed_instance_column, instance_column];
let mut advice_columns = vec![];
let mut fixed_columns = vec![];
(
VarHashChip::configure_from_scratch(
meta,
&mut advice_columns,
&mut fixed_columns,
&instance_columns,
),
VectorGadget::configure_from_scratch(
meta,
&mut advice_columns,
&mut fixed_columns,
&instance_columns,
),
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let chip = VarHashChip::new_from_scratch(&config.0);
let ng = <NG<F>>::new_from_scratch(&config.1);
let vg = VectorGadget::new(&ng);
let assigned_input: AssignedVector<_, _, M, A> =
vg.assign(&mut layouter, self.input.clone())?;
cost_measure_start(&mut layouter);
let output = chip.varhash(&mut layouter, &assigned_input)?;
cost_measure_end(&mut layouter);
ng.assert_equal_to_fixed(&mut layouter, &output, self.expected_output.clone())?;
chip.load_from_scratch(&mut layouter)?;
ng.load_from_scratch(&mut layouter)
}
}
#[cfg(test)]
pub fn test_varhash<F, Input, Output, VarHashChip, const M: usize, const A: usize>(
cost_model: bool,
chip_name: &str,
size: usize,
) where
F: CircuitField + ff::FromUniformBytes<64> + Ord,
Input: Vectorizable + Sampleable,
Output: InnerValue,
VarHashChip: VarHashInstructions<F, M, Input, Output, A> + FromScratch<F>,
VectorGadget<F>: AssignmentInstructions<F, AssignedVector<F, Input, M, A>>,
NG<F>: AssignmentInstructions<F, Input> + AssertionInstructions<F, Output>,
{
let mut rng = ChaCha12Rng::seed_from_u64(0xf007ba11);
let input = (0..size).map(|_| Input::sample_inner(&mut rng)).collect::<Vec<_>>();
let expected_output =
<VarHashChip as HashCPU<Input::Element, Output::Element>>::hash(&input);
let circuit = TestVarHashCircuit::<F, Input, Output, VarHashChip, M, A> {
input: Value::known(input),
expected_output,
_marker: PhantomData,
};
MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
if cost_model {
circuit_to_json(chip_name, "hash", circuit);
}
}
}