Merge 96ce399bb5405c9b52eb39467f1d357ff7ffd4bb into bdfb86b46e8f9749acc8bb8233cc78ee7eb6f4a5

This commit is contained in:
M Alghazwi 2025-03-17 15:31:37 +01:00 committed by GitHub
commit c58122678f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 687 additions and 49 deletions

View File

@ -97,6 +97,10 @@ harness = false
name = "reverse_index_bits"
harness = false
[[bench]]
name = "pi_gate_v2"
harness = false
# Display math equations properly in documentation
[package.metadata.docs.rs]
rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"]

View File

@ -0,0 +1,116 @@
use std::any::type_name;
use anyhow::{anyhow, Result};
use criterion::{criterion_group, criterion_main, Criterion};
use plonky2::gates::noop::NoopGate;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::CircuitConfig;
use plonky2::plonk::config::{GenericConfig, Poseidon2BN254Config, PoseidonGoldilocksConfig};
use plonky2_field::extension::Extendable;
use plonky2_field::goldilocks_field::GoldilocksField;
/// Benchmark for building, proving, and verifying the Plonky2 circuit.
fn bench_circuit<F: RichField + Extendable<D>, const D:usize, C: GenericConfig<D, F = F>,>(c: &mut Criterion, circuit_size: usize, num_of_pi: usize) -> Result<()>{
// Create the circuit configuration
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let num_dummy_gates = match circuit_size {
0 => return Err(anyhow!("size must be at least 1")),
1 => 0,
2 => 1,
n => (1 << (n - 1)) + 1,
};
for _ in 0..num_dummy_gates {
builder.add_gate(NoopGate, vec![]);
}
// The public inputs
let mut t_list= vec![];
for _ in 0..num_of_pi {
let t = builder.add_virtual_public_input();
t_list.push(t);
}
// Benchmark Group
let mut group = c.benchmark_group(format!("Circuit size = {}, pi size = {}, and Hasher = {}", circuit_size, num_of_pi, type_name::<C::Hasher>()));
// Benchmark the Circuit Building Phase
group.bench_function("Build Circuit", |b| {
b.iter(|| {
let config = CircuitConfig::standard_recursion_config();
let mut local_builder = CircuitBuilder::<F, D>::new(config);
for _ in 0..num_dummy_gates {
local_builder.add_gate(NoopGate, vec![]);
}
let _data = local_builder.build_unhashed_pi::<C>();
})
});
let data = builder.build_unhashed_pi::<C>();
println!("Circuit size (degree bits): {:?}", data.common.degree_bits());
// Create a PartialWitness
let mut pw = PartialWitness::new();
for i in 0..num_of_pi {
pw.set_target(t_list[i], F::ZERO)?;
}
// Benchmark the Proving Phase
group.bench_function("Prove Circuit", |b| {
b.iter(|| {
let local_pw = pw.clone();
data.prove_unhashed_pi(local_pw).expect("Failed to prove circuit")
})
});
// Generate the proof once for verification benchmarking
let proof_with_pis = data.prove_unhashed_pi(pw.clone()).expect("Failed to prove circuit");
let verifier_data = data.verifier_data();
println!("Proof size: {} bytes", proof_with_pis.to_bytes().len());
println!("num of pi = {}", proof_with_pis.public_inputs.len());
// Benchmark the Verifying Phase
group.bench_function("Verify Proof", |b| {
b.iter(|| {
verifier_data.verify_unhashed_pi(proof_with_pis.clone()).expect("Failed to verify proof");
})
});
group.finish();
Ok(())
}
fn bench_multiple_pi_values(c: &mut Criterion){
const D: usize = 2;
type C1 = PoseidonGoldilocksConfig;
type C2 = Poseidon2BN254Config;
type F = GoldilocksField;
// bench_circuit::<F,D,C1>(c, 12, 10).expect("failed");
// bench_circuit::<F,D,C2>(c, 12, 10).expect("failed");
//
// bench_circuit::<F,D,C1>(c, 12, 50).expect("failed");
// bench_circuit::<F,D,C2>(c, 12, 50).expect("failed");
//
// bench_circuit::<F,D,C1>(c, 12, 100).expect("failed");
// bench_circuit::<F,D,C2>(c, 12, 100).expect("failed");
// bench_circuit::<F,D,C1>(c, 12, 500).expect("failed");
// bench_circuit::<F,D,C2>(c, 12, 500).expect("failed");
bench_circuit::<F,D,C1>(c, 12, 1000).expect("failed");
bench_circuit::<F,D,C2>(c, 12, 1000).expect("failed");
}
/// Criterion benchmark group
criterion_group!{
name = prove_verify_benches;
config = Criterion::default().sample_size(10);
targets = bench_multiple_pi_values
}
criterion_main!(prove_verify_benches);

View File

@ -247,6 +247,7 @@ where
let prover_opts = ProverOptions {
export_witness: Some(format!("{}_witness.json",name)),
print_hash_statistics: HashStatisticsPrintLevel::Summary, // ::None,
hash_public_input: true,
};
let mut timing = TimingTree::new("prove", Level::Debug);
@ -265,6 +266,7 @@ where
let verifier_opts = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::Summary,
hash_public_input: true,
};
data.verify_with_options(proof.clone(), &verifier_opts)?;

View File

@ -44,6 +44,7 @@ fn main() -> Result<()> {
let prover_opts = ProverOptions {
export_witness: Some(String::from("fibonacci_witness.json")),
print_hash_statistics: HashStatisticsPrintLevel::Info,
hash_public_input: true,
};
let proof = data.prove_with_options(pw, &prover_opts)?;
@ -55,6 +56,7 @@ fn main() -> Result<()> {
let verifier_opts = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::Summary,
hash_public_input: true,
};
data.verify_with_options(proof, &verifier_opts)

View File

@ -145,6 +145,7 @@ fn main() -> Result<()> {
let prover_opts = ProverOptions {
export_witness: Some(String::from("lookup_witness.json")),
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: true,
};
let proof = data.prove_with_options(pw, &prover_opts)?;

View File

@ -89,6 +89,7 @@ fn main() -> Result<()> {
let prover_opts = ProverOptions {
export_witness: Some(String::from("multi_lookup_witness.json")),
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: true,
};
let proof = data.prove_with_options(pw, &prover_opts)?;

View File

@ -0,0 +1,74 @@
use std::fs;
use anyhow::Result;
use plonky2::field::types::Field;
use plonky2::gates::noop::NoopGate;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::CircuitConfig;
use plonky2::plonk::config::{Poseidon2BN254Config, PoseidonGoldilocksConfig};
use plonky2::plonk::prover::ProverOptions;
use plonky2::plonk::verifier::{HashStatisticsPrintLevel, VerifierOptions};
use plonky2_field::goldilocks_field::GoldilocksField;
/// An example of using Plonky2 to prove a circuit with size S
/// and with P number of public inputs
/// uses the `PublicInputGateV2` which doesn't hash the public input
fn main() -> Result<()> {
const D: usize = 2;
type C1 = PoseidonGoldilocksConfig;
type C2 = Poseidon2BN254Config;
type F = GoldilocksField;
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
const S: usize = 5;
let num_dummy_gates = (1 << (S - 1)) + 1;
for _ in 0..num_dummy_gates {
builder.add_gate(NoopGate, vec![]);
}
const P: usize = 1000;
// The public inputs
let mut t_list= vec![];
for _ in 0..P {
let t = builder.add_virtual_public_input();
t_list.push(t);
}
// Provide initial values.
let mut pw = PartialWitness::new();
for i in 0..P {
pw.set_target(t_list[i], F::ZERO)?;
}
let data = builder.build_unhashed_pi::<C1>();
println!("circuit size = {}", data.common.degree_bits());
let prover_opts = ProverOptions {
export_witness: Some(String::from("pi_gate_v2_witness.json")),
print_hash_statistics: HashStatisticsPrintLevel::Info,
hash_public_input: false,
};
let proof = data.prove_with_options(pw, &prover_opts)?;
println!("num pi = {}", proof.public_inputs.len());
let verifier_opts = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::Summary,
hash_public_input: false,
};
let common_circuit_data_serialized = serde_json::to_string(&data.common).unwrap();
let verifier_only_circuit_data_serialized = serde_json::to_string(&data.verifier_only).unwrap();
let proof_serialized = serde_json::to_string(&proof).unwrap();
fs::write("pi_gate_v2_common.json", common_circuit_data_serialized ).expect("Unable to write file");
fs::write("pi_gate_v2_vkey.json" , verifier_only_circuit_data_serialized ).expect("Unable to write file");
fs::write("pi_gate_v2_proof.json" , proof_serialized ).expect("Unable to write file");
assert!(data.verify_with_options(proof, &verifier_opts).is_ok());
Ok(())
}

View File

@ -43,6 +43,7 @@ fn main() -> Result<()> {
let verifier_opts = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::Summary,
hash_public_input: true,
};
assert!(data.verify_with_options(proof, &verifier_opts).is_ok());

View File

@ -0,0 +1,87 @@
use anyhow::Result;
use plonky2::field::types::Field;
use plonky2::gates::noop::NoopGate;
use plonky2::iop::target::Target;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::CircuitConfig;
use plonky2::plonk::config::{Poseidon2BN254Config, PoseidonGoldilocksConfig};
use plonky2_field::goldilocks_field::GoldilocksField;
/// An example of using Plonky2 to prove a proof in-circuit
/// and with P number of public inputs
/// uses the `PublicInputGateV2` which doesn't hash the public input
fn main() -> Result<()> {
const D: usize = 2;
type C1 = PoseidonGoldilocksConfig;
type C2 = Poseidon2BN254Config;
type F = GoldilocksField;
//---------- inner layer ------------
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
const S: usize = 5;
let num_dummy_gates = (1 << (S - 1)) + 1;
for _ in 0..num_dummy_gates {
builder.add_gate(NoopGate, vec![]);
}
const P: usize = 150;
// The public inputs
let mut t_list= vec![];
for _ in 0..P {
let t = builder.add_virtual_public_input();
t_list.push(t);
}
// Provide initial values.
let mut pw = PartialWitness::new();
for i in 0..P {
pw.set_target(t_list[i], F::ZERO)?;
}
let data = builder.build::<C1>();
println!("inner layer: circuit size = {}", data.common.degree_bits());
let proof = data.prove(pw)?;
println!("inner layer: num pi = {}", proof.public_inputs.len());
assert!(data.verify(proof.clone()).is_ok());
//------------ outer layer ----------------
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let proof_t = builder.add_virtual_proof_with_pis(&data.common);
let verifier_data_t = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height);
builder.verify_proof::<C1>(&proof_t, &verifier_data_t, &data.common);
let mut t_list: Vec<Target> = vec![];
for (i,pi) in proof_t.public_inputs.iter().enumerate() {
let t = builder.add_virtual_public_input();
builder.connect(*pi,t.clone());
t_list.push(t);
}
// Provide initial values.
let mut pw = PartialWitness::new();
for i in 0..P {
pw.set_target(t_list[i], F::ZERO)?;
}
pw.set_proof_with_pis_target(&proof_t,&proof);
pw.set_verifier_data_target(&verifier_data_t,&data.verifier_only);
let data = builder.build_unhashed_pi::<C1>();
println!("outer layer: circuit size = {}", data.common.degree_bits());
let proof = data.prove_unhashed_pi(pw)?;
println!("outer layer: num pi = {}", proof.public_inputs.len());
assert!(data.verify_unhashed_pi(proof.clone()).is_ok());
Ok(())
}

View File

@ -864,6 +864,7 @@ mod tests {
local_constants: &[],
local_wires: &get_wires(shift, values, eval_point),
public_inputs_hash: &HashOut::rand(),
public_inputs: &[F::rand()],
};
assert!(

View File

@ -430,6 +430,7 @@ mod tests {
local_constants: &[],
local_wires: &get_wires(base, power as u64),
public_inputs_hash: &HashOut::rand(),
public_inputs: &[F::rand()],
};
assert!(
gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()),

View File

@ -99,10 +99,12 @@ pub trait Gate<F: RichField + Extendable<D>, const D: usize>: 'static + Send + S
.map(|w| F::Extension::from_basefield(*w))
.collect::<Vec<_>>();
let public_inputs_hash = &vars_base.public_inputs_hash;
let public_inputs = &vars_base.public_inputs;
let vars = EvaluationVars {
local_constants,
local_wires,
public_inputs_hash,
public_inputs,
};
let values = self.eval_unfiltered(vars);

View File

@ -28,6 +28,7 @@ pub fn test_low_degree<F: RichField + Extendable<D>, G: Gate<F, D>, const D: usi
let constant_ldes = random_low_degree_matrix::<F::Extension>(gate.num_constants(), rate_bits);
assert_eq!(wire_ldes.len(), constant_ldes.len());
let public_inputs_hash = &HashOut::rand();
let pi_rand = F::rand_vec(4);
let constraint_evals = wire_ldes
.iter()
@ -36,6 +37,7 @@ pub fn test_low_degree<F: RichField + Extendable<D>, G: Gate<F, D>, const D: usi
local_constants,
local_wires,
public_inputs_hash,
public_inputs: &pi_rand,
})
.map(|vars| gate.eval_unfiltered(vars))
.collect::<Vec<_>>();
@ -107,13 +109,16 @@ pub fn test_eval_fns<
.collect::<Vec<_>>();
let public_inputs_hash = HashOut::rand();
let rand_pi = F::rand_vec(4);
// Batch of 1.
let vars_base_batch =
EvaluationVarsBaseBatch::new(1, &constants_base, &wires_base, &public_inputs_hash);
EvaluationVarsBaseBatch::new(1, &constants_base, &wires_base, &public_inputs_hash, &rand_pi);
let vars = EvaluationVars {
local_constants: &constants,
local_wires: &wires,
public_inputs_hash: &public_inputs_hash,
public_inputs: &rand_pi
};
let evals_base = gate.eval_unfiltered_base_batch(vars_base_batch);
@ -146,6 +151,7 @@ pub fn test_eval_fns<
local_constants: &constants,
local_wires: &wires,
public_inputs_hash: &public_inputs_hash,
public_inputs: &rand_pi,
};
let evals = gate.eval_unfiltered(vars);

View File

@ -38,6 +38,7 @@ pub mod packed_util;
pub mod poseidon;
pub mod poseidon_mds;
pub mod public_input;
pub mod public_input_v2;
pub mod random_access;
pub mod reducing;
pub mod reducing_extension;

View File

@ -0,0 +1,170 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::ops::Range;
use crate::field::extension::Extendable;
use crate::field::packed::PackedField;
use crate::gates::gate::Gate;
use crate::gates::packed_util::PackedEvaluableBase;
use crate::gates::util::StridedConstraintConsumer;
use crate::hash::hash_types::RichField;
use crate::iop::ext_target::ExtensionTarget;
use crate::iop::generator::WitnessGeneratorRef;
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::CommonCircuitData;
use crate::plonk::vars::{
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
EvaluationVarsBasePacked,
};
use crate::util::serialization::{Buffer, IoResult, Read, Write};
/// A gate which enforces that each wire matches a corresponding public-input element.
///
/// Specifically, if this gate has `num_pub_inputs` wires, then for each wire i in
/// [0..num_pub_inputs):
///
/// local_wires[i] == public_inputs[i]
///
/// If the circuit has more public inputs than the circuit config's `num_wires` you'll need multiple gates
#[derive(Debug)]
pub struct PublicInputGateV2 {
/// How many public inputs are enforced by this gate.
pub num_pub_inputs: usize,
/// start index from which we take the public input
pub index: usize,
}
impl PublicInputGateV2 {
/// careful with this fn, you must ensure `num_pub_inputs` <= the circuit config's `num_wires`.
pub fn new(num_pub_inputs: usize, index: usize) -> Self {
Self {
num_pub_inputs,
index
}
}
}
impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for PublicInputGateV2 {
fn id(&self) -> String {
"PublicInputGateV2".into()
}
fn short_id(&self) -> String {
"PublicInputGateV2".into()
}
fn serialize(
&self,
dst: &mut Vec<u8>,
_common_data: &CommonCircuitData<F, D>,
) -> IoResult<()> {
dst.write_usize(self.num_pub_inputs)?;
dst.write_usize(self.index)
}
fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData<F, D>) -> IoResult<Self> {
let num_pub_inputs = src.read_usize()?;
let index = src.read_usize()?;
Ok(Self {
num_pub_inputs,
index
})
}
fn eval_unfiltered(&self, vars: EvaluationVars<F, D>) -> Vec<F::Extension> {
// For each i in [0..num_pub_inputs], the constraint is: local_wires[i] - public_inputs[i]
// That must be 0 if the public input is correct.
(0..self.num_pub_inputs)
.map(|i| {
let wire_value = vars.local_wires[i];
let pi_value = vars.public_inputs[self.index + i].into();
wire_value - pi_value
})
.collect()
}
fn eval_unfiltered_base_one(
&self,
_vars: EvaluationVarsBase<F>,
_yield_constr: StridedConstraintConsumer<F>,
) {
panic!("use eval_unfiltered_base_packed instead");
}
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
self.eval_unfiltered_base_batch_packed(vars_base)
}
fn eval_unfiltered_circuit(
&self,
builder: &mut CircuitBuilder<F, D>,
vars: EvaluationTargets<D>,
) -> Vec<ExtensionTarget<D>> {
todo!()
// // Circuit-level version of the same logic.
// (0..self.num_pub_inputs)
// .map(|i| {
// // local_wires[i] - public_inputs[i]
// let pi_part_ex = builder.convert_to_ext(vars.public_inputs[self.index + i]);
// builder.sub_extension(vars.local_wires[i], pi_part_ex)
// })
// .collect()
}
fn generators(&self, _row: usize, _local_constants: &[F]) -> Vec<WitnessGeneratorRef<F, D>> {
Vec::new()
}
fn num_wires(&self) -> usize {
self.num_pub_inputs
}
fn num_constants(&self) -> usize {
0
}
fn degree(&self) -> usize {
1
}
fn num_constraints(&self) -> usize {
self.num_pub_inputs
}
}
impl<F: RichField + Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for PublicInputGateV2 {
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
&self,
vars: EvaluationVarsBasePacked<P>,
mut yield_constr: StridedConstraintConsumer<P>,
) {
yield_constr.many(
(0..self.num_pub_inputs).map(|i| vars.local_wires[i] - vars.public_inputs[self.index + i]),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::goldilocks_field::GoldilocksField;
use crate::gates::gate_testing::{test_eval_fns, test_low_degree};
use crate::gates::public_input_v2::PublicInputGateV2;
use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
#[test]
fn pi_v2_low_degree() {
test_low_degree::<GoldilocksField, _, 4>(PublicInputGateV2{num_pub_inputs:4,index:0})
}
#[test]
fn pi_v2_eval_fns() -> anyhow::Result<()> {
todo!()
// const D: usize = 2;
// type C = PoseidonGoldilocksConfig;
// type F = <C as GenericConfig<D>>::F;
// test_eval_fns::<F, C, _, D>(PublicInputGateV2{num_pub_inputs:4,index:0})
}
}

View File

@ -526,6 +526,7 @@ mod tests {
&constants,
),
public_inputs_hash: &HashOut::rand(),
public_inputs: &[F::rand()],
};
let bad_claimed_elements = F::rand_vec(4);
let bad_vars = EvaluationVars {
@ -538,6 +539,7 @@ mod tests {
&constants,
),
public_inputs_hash: &HashOut::rand(),
public_inputs: &[F::rand()],
};
assert!(

View File

@ -36,6 +36,8 @@ pub fn test_gate_constraints<F: RichField + Extendable<D>, const D: usize>() {
[ make_fext::<F,D>(666)
, make_fext::<F,D>(77)
];
let pi = F::rand_vec(4);
let input_hash = HashOut{ elements:
[ F::from_canonical_u64(101)
, F::from_canonical_u64(102)
@ -47,7 +49,8 @@ pub fn test_gate_constraints<F: RichField + Extendable<D>, const D: usize>() {
let vars = EvaluationVars
{ local_constants: &loc_constants
, local_wires: &loc_wires
, public_inputs_hash: &input_hash
, public_inputs_hash: &input_hash
, public_inputs: &pi
};
let circuit_config: CircuitConfig = CircuitConfig::standard_recursion_config();

View File

@ -30,6 +30,7 @@ use crate::gates::lookup::{Lookup, LookupGate};
use crate::gates::lookup_table::LookupTable;
use crate::gates::noop::NoopGate;
use crate::gates::public_input::PublicInputGate;
use crate::gates::public_input_v2::PublicInputGateV2;
use crate::gates::selectors::{selector_ends_lookups, selector_polynomials, selectors_lookup};
use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField};
use crate::hash::merkle_proofs::MerkleProofTarget;
@ -1061,8 +1062,9 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
pub fn build_with_options<C: GenericConfig<D, F = F>>(
self,
commit_to_sigma: bool,
hash_public_input: bool,
) -> CircuitData<F, C, D> {
let (circuit_data, success) = self.try_build_with_options(commit_to_sigma);
let (circuit_data, success) = self.try_build_with_options(commit_to_sigma, hash_public_input);
if !success {
panic!("Failed to build circuit");
}
@ -1072,6 +1074,7 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
pub fn try_build_with_options<C: GenericConfig<D, F = F>>(
mut self,
commit_to_sigma: bool,
hash_public_input: bool,
) -> (CircuitData<F, C, D>, bool) {
let mut timing = TimingTree::new("preprocess", Level::Trace);
@ -1085,21 +1088,52 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// Hash the public inputs, and route them to a `PublicInputGate` which will enforce that
// those hash wires match the claimed public inputs.
let num_public_inputs = self.public_inputs.len();
let public_inputs_hash =
self.hash_n_to_hash_no_pad::<C::InnerHasher>(self.public_inputs.clone());
let pi_gate = self.add_gate(PublicInputGate, vec![]);
for (&hash_part, wire) in public_inputs_hash
.elements
.iter()
.zip(PublicInputGate::wires_public_inputs_hash())
{
self.connect(hash_part, Target::wire(pi_gate, wire))
}
// See <https://github.com/0xPolygonZero/plonky2/issues/456>
// however randomization makes debugging harder as runs are not deterministic
if self.config.randomize_unused_wires {
self.randomize_unused_pi_wires(pi_gate);
// only hash public input if flag is set
if hash_public_input {
let public_inputs_hash =
self.hash_n_to_hash_no_pad::<C::InnerHasher>(self.public_inputs.clone());
let pi_gate = self.add_gate(PublicInputGate, vec![]);
for (&hash_part, wire) in public_inputs_hash
.elements
.iter()
.zip(PublicInputGate::wires_public_inputs_hash())
{
self.connect(hash_part, Target::wire(pi_gate, wire))
}
// See <https://github.com/0xPolygonZero/plonky2/issues/456>
// however randomization makes debugging harder as runs are not deterministic
if self.config.randomize_unused_wires {
self.randomize_unused_pi_wires(pi_gate);
}
} else {
let num_of_routed_wires = self.config.num_routed_wires;
let num_of_pi_gates = num_public_inputs.div_ceil(num_of_routed_wires);
let mut pi_gates = vec![];
let all_pi = self.public_inputs.clone();
for i in 0..num_of_pi_gates{
// start index
let start_i = i*num_of_routed_wires;
// make sure we do not go past the number of public inputs.
let end_i = core::cmp::min(start_i + num_of_routed_wires, num_public_inputs);
// the number of public inputs in this gate.
let num_pi_in_gate = end_i - start_i;
let pi_gate = self.add_gate(
PublicInputGateV2::new(num_pi_in_gate, start_i)
, vec![]);
// connect
let wire_range = 0..num_pi_in_gate;
for (&pi_part, wire) in all_pi[start_i .. end_i]
.iter()
.zip(wire_range)
{
self.connect(pi_part, Target::wire(pi_gate, wire))
}
pi_gates.push(pi_gate);
}
}
// Place LUT-related gates.
@ -1329,11 +1363,17 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Builds a "full circuit", with both prover and verifier data.
pub fn build<C: GenericConfig<D, F = F>>(self) -> CircuitData<F, C, D> {
self.build_with_options(true)
self.build_with_options(true, true)
}
/// Builds a "full circuit", with both prover and verifier data.
/// the public input are not hashed when calling this function
pub fn build_unhashed_pi<C: GenericConfig<D, F = F>>(self) -> CircuitData<F, C, D> {
self.build_with_options(true, false)
}
pub fn mock_build<C: GenericConfig<D, F = F>>(self) -> MockCircuitData<F, C, D> {
let circuit_data = self.build_with_options(false);
let circuit_data = self.build_with_options(false, true);
MockCircuitData {
prover_only: circuit_data.prover_only,
common: circuit_data.common,

View File

@ -46,8 +46,8 @@ use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::config::{GenericConfig, Hasher};
use crate::plonk::plonk_common::PlonkOracle;
use crate::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs};
use crate::plonk::prover::{prove, prove_with_options, ProverOptions};
use crate::plonk::verifier::{verify, verify_with_options, VerifierOptions};
use crate::plonk::prover::{prove, prove_unhashed_pi, prove_with_options, ProverOptions};
use crate::plonk::verifier::{verify, verify_with_options, VerifierOptions, verify_unhashed_pi};
use crate::util::serialization::{
Buffer, GateSerializer, IoResult, Read, WitnessGeneratorSerializer, Write,
};
@ -199,6 +199,15 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
)
}
pub fn prove_unhashed_pi(&self, inputs: PartialWitness<F>) -> Result<ProofWithPublicInputs<F, C, D>> {
prove_unhashed_pi::<F, C, D>(
&self.prover_only,
&self.common,
inputs,
&mut TimingTree::default(),
)
}
pub fn prove_with_options(&self, inputs: PartialWitness<F>, opts: &ProverOptions) -> Result<ProofWithPublicInputs<F, C, D>> {
prove_with_options::<F, C, D>(
&self.prover_only,
@ -213,6 +222,10 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
verify::<F, C, D>(proof_with_pis, &self.verifier_only, &self.common)
}
pub fn verify_unhashed_pi(&self, proof_with_pis: ProofWithPublicInputs<F, C, D>) -> Result<()> {
verify_unhashed_pi::<F, C, D>(proof_with_pis, &self.verifier_only, &self.common)
}
pub fn verify_with_options(&self, proof_with_pis: ProofWithPublicInputs<F, C, D>, verifier_options: &VerifierOptions) -> Result<()> {
verify_with_options::<F, C, D>(proof_with_pis, &self.verifier_only, &self.common, verifier_options)
}
@ -302,6 +315,15 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
buffer.read_prover_circuit_data(gate_serializer, generator_serializer)
}
pub fn prove_unhashed_pi(&self, inputs: PartialWitness<F>) -> Result<ProofWithPublicInputs<F, C, D>> {
prove_unhashed_pi::<F, C, D>(
&self.prover_only,
&self.common,
inputs,
&mut TimingTree::default(),
)
}
pub fn prove(&self, inputs: PartialWitness<F>) -> Result<ProofWithPublicInputs<F, C, D>> {
prove::<F, C, D>(
&self.prover_only,
@ -354,6 +376,10 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
verify::<F, C, D>(proof_with_pis, &self.verifier_only, &self.common)
}
pub fn verify_unhashed_pi(&self, proof_with_pis: ProofWithPublicInputs<F, C, D>) -> Result<()> {
verify_unhashed_pi::<F, C, D>(proof_with_pis, &self.verifier_only, &self.common)
}
pub fn verify_compressed(
&self,
compressed_proof_with_pis: CompressedProofWithPublicInputs<F, C, D>,

View File

@ -15,7 +15,7 @@ use crate::iop::challenger::{Challenger, RecursiveChallenger};
use crate::iop::target::Target;
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::CommonCircuitData;
use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher};
use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, IntoGenericFieldVec};
use crate::plonk::proof::{
CompressedProof, CompressedProofWithPublicInputs, FriInferredElements, OpeningSet,
OpeningSetTarget, Proof, ProofChallenges, ProofChallengesTarget, ProofTarget,
@ -24,6 +24,7 @@ use crate::plonk::proof::{
use crate::util::reverse_bits;
fn get_challenges<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
public_inputs: Option<Vec<F>>,
public_inputs_hash: <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash,
wires_cap: &MerkleCap<F, C::Hasher>,
plonk_zs_partial_products_cap: &MerkleCap<F, C::Hasher>,
@ -34,6 +35,7 @@ fn get_challenges<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, cons
pow_witness: F,
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
hash_public_input: bool,
) -> anyhow::Result<ProofChallenges<F, D>> {
let config = &common_data.config;
let num_challenges = config.num_challenges;
@ -43,7 +45,12 @@ fn get_challenges<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, cons
// Observe the instance.
challenger.observe_hash::<C::Hasher>(*circuit_digest);
challenger.observe_hash::<C::InnerHasher>(public_inputs_hash);
if hash_public_input {
challenger.observe_hash::<C::InnerHasher>(public_inputs_hash);
} else {
let pi_felts = public_inputs.unwrap().into_generic_field_vec();
challenger.observe_elements(&pi_felts);
}
challenger.observe_cap::<C::Hasher>(wires_cap);
let plonk_betas = challenger.get_n_challenges(num_challenges);
@ -98,9 +105,11 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
&self,
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
hash_public_input: bool,
) -> anyhow::Result<Vec<usize>> {
let pi = if hash_public_input { None } else {Some(self.public_inputs.clone())};
Ok(self
.get_challenges(self.get_public_inputs_hash(), circuit_digest, common_data)?
.get_challenges(pi, self.get_public_inputs_hash(), circuit_digest, common_data, hash_public_input)?
.fri_challenges
.fri_query_indices)
}
@ -108,9 +117,11 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
/// Computes all Fiat-Shamir challenges used in the Plonk proof.
pub fn get_challenges(
&self,
public_inputs: Option<Vec<F>>,
public_inputs_hash: <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash,
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
hash_public_input: bool,
) -> anyhow::Result<ProofChallenges<F, D>> {
let Proof {
wires_cap,
@ -126,19 +137,25 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
},
} = &self.proof;
get_challenges::<F, C, D>(
public_inputs_hash,
wires_cap,
plonk_zs_partial_products_cap,
quotient_polys_cap,
openings,
commit_phase_merkle_caps,
final_poly,
*pow_witness,
circuit_digest,
common_data,
)
let challenges =
get_challenges::<F, C, D>(
public_inputs,
public_inputs_hash,
wires_cap,
plonk_zs_partial_products_cap,
quotient_polys_cap,
openings,
commit_phase_merkle_caps,
final_poly,
*pow_witness,
circuit_digest,
common_data,
hash_public_input
);
challenges
}
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
@ -166,6 +183,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
} = &self.proof;
get_challenges::<F, C, D>(
None,
public_inputs_hash,
wires_cap,
plonk_zs_partial_products_cap,
@ -176,6 +194,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
*pow_witness,
circuit_digest,
common_data,
true,
)
}

View File

@ -93,7 +93,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<CompressedProofWithPublicInputs<F, C, D>> {
let indices = self.fri_query_indices(circuit_digest, common_data)?;
let indices = self.fri_query_indices(circuit_digest, common_data, true)?;
let compressed_proof = self.proof.compress(&indices, &common_data.fri_params);
Ok(CompressedProofWithPublicInputs {
public_inputs: self.public_inputs,
@ -224,6 +224,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
.decompress(&challenges, fri_inferred_elements, &common_data.fri_params);
verify_with_challenges::<F, C, D>(
decompressed_proof,
self.public_inputs,
public_inputs_hash,
challenges,
verifier_data,

View File

@ -22,7 +22,7 @@ use crate::fri::oracle::PolynomialBatch;
use crate::gates::lookup::LookupGate;
use crate::gates::lookup_table::LookupTableGate;
use crate::gates::selectors::{LookupSelectors};
use crate::hash::hash_types::RichField;
use crate::hash::hash_types::{HashOut, RichField};
use crate::hash::hashing::*;
use crate::iop::challenger::Challenger;
use crate::iop::generator::generate_partial_witness;
@ -123,11 +123,19 @@ pub fn set_lookup_wires<
pub struct ProverOptions {
pub export_witness: Option<String>, // export the full witness into the given file
pub print_hash_statistics: HashStatisticsPrintLevel,
pub hash_public_input: bool,
}
pub const DEFAULT_PROVER_OPTIONS: ProverOptions = ProverOptions {
export_witness: None,
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: true,
};
pub const UNHASHED_PI_PROVER_OPTIONS: ProverOptions = ProverOptions {
export_witness: None,
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: false,
};
// things we want to export to be used by third party tooling
@ -201,6 +209,21 @@ fn collect_things_to_export<F: RichField + Extendable<D>, C: GenericConfig<D, F
//------------------------------------------------------------------------------
pub fn prove_unhashed_pi<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
prover_data: &ProverOnlyCircuitData<F, C, D>,
common_data: &CommonCircuitData<F, D>,
inputs: PartialWitness<F>,
timing: &mut TimingTree,
) -> Result<ProofWithPublicInputs<F, C, D>> {
prove_with_options(
prover_data,
common_data,
inputs,
timing,
&UNHASHED_PI_PROVER_OPTIONS,
)
}
pub fn prove<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
prover_data: &ProverOnlyCircuitData<F, C, D>,
common_data: &CommonCircuitData<F, D>,
@ -269,8 +292,13 @@ where
set_lookup_wires(prover_data, common_data, &mut partition_witness)?;
let public_inputs = partition_witness.get_targets(&prover_data.public_inputs);
let pi_felts: Vec<GenericField<F>> = public_inputs.clone().into_generic_field_vec();
let public_inputs_hash = C::InnerHasher::hash_no_pad(&pi_felts);
let public_inputs_hash: <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash =
if prover_options.hash_public_input {
let pi_felts: Vec<GenericField<F>> = public_inputs.clone().into_generic_field_vec();
C::InnerHasher::hash_no_pad(&pi_felts)
}else{
HashOut::<F>::default()
};
let witness = timed!(
timing,
@ -320,7 +348,12 @@ where
// Observe the instance.
challenger.observe_hash::<C::Hasher>(prover_data.circuit_digest);
challenger.observe_hash::<C::InnerHasher>(public_inputs_hash);
if prover_options.hash_public_input {
challenger.observe_hash::<C::InnerHasher>(public_inputs_hash);
} else{
let pi_felts = public_inputs.clone().into_generic_field_vec();
challenger.observe_elements(&pi_felts);
}
challenger.observe_cap::<C::Hasher>(&wires_commitment.merkle_tree.cap);
@ -397,6 +430,7 @@ where
compute_quotient_polys::<F, C, D>(
common_data,
prover_data,
&public_inputs,
&public_inputs_hash,
&wires_commitment,
&partial_products_zs_and_lookup_commitment,
@ -757,6 +791,7 @@ fn compute_quotient_polys<
>(
common_data: &CommonCircuitData<F, D>,
prover_data: &'a ProverOnlyCircuitData<F, C, D>,
public_inputs: &[F],
public_inputs_hash: &<<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash,
wires_commitment: &'a PolynomialBatch<F, C, D>,
zs_partial_products_and_lookup_commitment: &'a PolynomialBatch<F, C, D>,
@ -913,12 +948,14 @@ fn compute_quotient_polys<
}
}
let vars_batch = EvaluationVarsBaseBatch::new(
xs_batch.len(),
&local_constants_batch,
&local_wires_batch,
public_inputs_hash,
);
let vars_batch =
EvaluationVarsBaseBatch::new(
xs_batch.len(),
&local_constants_batch,
&local_wires_batch,
&public_inputs_hash,
public_inputs,
);
let mut quotient_values_batch = eval_vanishing_poly_base_batch::<F, D>(
common_data,

View File

@ -15,6 +15,7 @@ pub struct EvaluationVars<'a, F: RichField + Extendable<D>, const D: usize> {
pub local_constants: &'a [F::Extension],
pub local_wires: &'a [F::Extension],
pub public_inputs_hash: &'a HashOut<F>,
pub public_inputs: &'a [F],
}
/// A batch of evaluation vars, in the base field.
@ -26,6 +27,7 @@ pub struct EvaluationVarsBaseBatch<'a, F: Field> {
pub local_constants: &'a [F],
pub local_wires: &'a [F],
pub public_inputs_hash: &'a HashOut<F>,
pub public_inputs: &'a [F],
}
/// A view into `EvaluationVarsBaseBatch` for a particular evaluation point. Does not copy the data.
@ -34,6 +36,7 @@ pub struct EvaluationVarsBase<'a, F: Field> {
pub local_constants: PackedStridedView<'a, F>,
pub local_wires: PackedStridedView<'a, F>,
pub public_inputs_hash: &'a HashOut<F>,
pub public_inputs: &'a [F],
}
/// Like `EvaluationVarsBase`, but packed.
@ -44,6 +47,7 @@ pub struct EvaluationVarsBasePacked<'a, P: PackedField> {
pub local_constants: PackedStridedView<'a, P>,
pub local_wires: PackedStridedView<'a, P>,
pub public_inputs_hash: &'a HashOut<P::Scalar>,
pub public_inputs: &'a [P::Scalar],
}
impl<F: RichField + Extendable<D>, const D: usize> EvaluationVars<'_, F, D> {
@ -67,6 +71,7 @@ impl<'a, F: Field> EvaluationVarsBaseBatch<'a, F> {
local_constants: &'a [F],
local_wires: &'a [F],
public_inputs_hash: &'a HashOut<F>,
public_inputs: &'a [F],
) -> Self {
assert_eq!(local_constants.len() % batch_size, 0);
assert_eq!(local_wires.len() % batch_size, 0);
@ -75,6 +80,7 @@ impl<'a, F: Field> EvaluationVarsBaseBatch<'a, F> {
local_constants,
local_wires,
public_inputs_hash,
public_inputs
}
}
@ -99,6 +105,7 @@ impl<'a, F: Field> EvaluationVarsBaseBatch<'a, F> {
local_constants,
local_wires,
public_inputs_hash: self.public_inputs_hash,
public_inputs: self.public_inputs,
}
}
@ -196,6 +203,7 @@ impl<'a, P: PackedField> Iterator for EvaluationVarsBaseBatchIterPacked<'a, P> {
local_constants,
local_wires,
public_inputs_hash: self.vars_batch.public_inputs_hash,
public_inputs: self.vars_batch.public_inputs,
};
self.i += P::WIDTH;
Some(res)

View File

@ -5,7 +5,7 @@ use anyhow::{ensure, Result};
use crate::field::extension::Extendable;
use crate::field::types::Field;
use crate::fri::verifier::verify_fri_proof;
use crate::hash::hash_types::RichField;
use crate::hash::hash_types::{HashOut, RichField};
use crate::hash::hashing::*;
use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
use crate::plonk::config::{GenericConfig, Hasher};
@ -27,10 +27,17 @@ pub enum HashStatisticsPrintLevel {
#[derive(Debug,Clone)]
pub struct VerifierOptions {
pub print_hash_statistics: HashStatisticsPrintLevel,
pub hash_public_input: bool,
}
pub const DEFAULT_VERIFIER_OPTIONS: VerifierOptions = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: true,
};
pub const UNHASHED_PI_VERIFIER_OPTIONS: VerifierOptions = VerifierOptions {
print_hash_statistics: HashStatisticsPrintLevel::None,
hash_public_input: false,
};
pub(crate) fn verify<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
@ -45,6 +52,20 @@ pub(crate) fn verify<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, c
&DEFAULT_VERIFIER_OPTIONS,
)
}
pub(crate) fn verify_unhashed_pi<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
proof_with_pis: ProofWithPublicInputs<F, C, D>,
verifier_data: &VerifierOnlyCircuitData<C, D>,
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
verify_with_options(
proof_with_pis,
verifier_data,
common_data,
&UNHASHED_PI_VERIFIER_OPTIONS,
)
}
pub(crate) fn verify_with_options<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
proof_with_pis: ProofWithPublicInputs<F, C, D>,
verifier_data: &VerifierOnlyCircuitData<C, D>,
@ -56,16 +77,25 @@ pub(crate) fn verify_with_options<F: RichField + Extendable<D>, C: GenericConfig
validate_proof_with_pis_shape(&proof_with_pis, common_data)?;
let public_inputs_hash = proof_with_pis.get_public_inputs_hash();
let public_inputs_hash: HashOut<F> =
if verifier_options.hash_public_input {
proof_with_pis.get_public_inputs_hash()
}else {
HashOut::<F>::default()
};
if verifier_options.print_hash_statistics >= HashStatisticsPrintLevel::Info {
print_hash_counters("after PI");
}
let pi = if verifier_options.hash_public_input { None } else {Some(proof_with_pis.public_inputs.clone())};
let challenges = proof_with_pis.get_challenges(
pi,
public_inputs_hash,
&verifier_data.circuit_digest,
common_data,
verifier_options.hash_public_input,
)?;
if verifier_options.print_hash_statistics >= HashStatisticsPrintLevel::Info {
@ -74,6 +104,7 @@ pub(crate) fn verify_with_options<F: RichField + Extendable<D>, C: GenericConfig
let result = verify_with_challenges::<F, C, D>(
proof_with_pis.proof,
proof_with_pis.public_inputs,
public_inputs_hash,
challenges,
verifier_data,
@ -93,6 +124,7 @@ pub(crate) fn verify_with_challenges<
const D: usize,
>(
proof: Proof<F, C, D>,
public_inputs: Vec<F>,
public_inputs_hash: <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash,
challenges: ProofChallenges<F, D>,
verifier_data: &VerifierOnlyCircuitData<C, D>,
@ -105,6 +137,7 @@ pub(crate) fn verify_with_challenges<
local_constants,
local_wires,
public_inputs_hash: &public_inputs_hash,
public_inputs: &public_inputs,
};
let local_zs = &proof.openings.plonk_zs;
let next_zs = &proof.openings.plonk_zs_next;