Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circuit dynamic configure #2

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion halo2_proofs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ blake2b_simd = "1"
maybe-rayon = {version = "0.1.0", default-features = false}

# Developer tooling dependencies
plotters = { version = "0.3.0", default-features = false, optional = true }
plotters = { version = "0.3.0", optional = true }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I turned on the default features to generate a test circuit layout, the PSE fork has the defaults on too.

tabbycat = { version = "0.1", features = ["attributes"], optional = true }

# Legacy circuit compatibility
Expand Down Expand Up @@ -82,6 +82,7 @@ gadget-traces = ["backtrace"]
sanity-checks = []
batch = ["rand_core/getrandom"]
floor-planner-v1-legacy-pdqsort = ["halo2_legacy_pdqsort"]
circuit-params = ["dev-graph"]

# In-development features
# See https://zcash.github.io/halo2/dev/features.html
Expand Down
5 changes: 5 additions & 0 deletions halo2_proofs/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,12 @@ impl<F: Field + Ord> MockProver<F> {
let n = 1 << k;

let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let cs = cs;

if n < cs.minimum_rows() {
Expand Down
5 changes: 5 additions & 0 deletions halo2_proofs/src/dev/cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,12 @@ impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
pub fn measure(k: u32, circuit: &ConcreteCircuit) -> Self {
// Collect the layout details.
let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let mut layout = Layout::new(k, 1 << k, cs.num_selectors);
ConcreteCircuit::FloorPlanner::synthesize(
&mut layout,
Expand Down
5 changes: 5 additions & 0 deletions halo2_proofs/src/dev/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ pub fn circuit_dot_graph<F: Field, ConcreteCircuit: Circuit<F>>(
) -> String {
// Collect the graph details.
let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let mut graph = Graph::default();
ConcreteCircuit::FloorPlanner::synthesize(&mut graph, circuit, config, cs.constants).unwrap();

Expand Down
5 changes: 5 additions & 0 deletions halo2_proofs/src/dev/graph/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ impl CircuitLayout {
let n = 1 << k;
// Collect the layout details.
let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let mut layout = Layout::new(k, n, cs.num_selectors);
ConcreteCircuit::FloorPlanner::synthesize(
&mut layout,
Expand Down
1 change: 1 addition & 0 deletions halo2_proofs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![deny(unsafe_code)]
#![feature(associated_type_defaults)]
parazyd marked this conversation as resolved.
Show resolved Hide resolved

pub mod arithmetic;
pub mod circuit;
Expand Down
7 changes: 6 additions & 1 deletion halo2_proofs/src/plonk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,13 @@ where
pub fn read<R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
reader: &mut R,
params: &Params<C>,
#[cfg(feature = "circuit-params")] circuit_params: ConcreteCircuit::Params,
) -> io::Result<Self> {
let (domain, cs, _) = keygen::create_domain::<C, ConcreteCircuit>(params);
let (domain, cs, _) = keygen::create_domain::<C, ConcreteCircuit>(
params,
#[cfg(feature = "circuit-params")]
circuit_params,
);
let mut num_fixed_columns_be_bytes = [0u8; 4];
reader.read_exact(&mut num_fixed_columns_be_bytes)?;
let num_fixed_columns = u32::from_be_bytes(num_fixed_columns_be_bytes);
Expand Down
20 changes: 20 additions & 0 deletions halo2_proofs/src/plonk/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,14 +470,34 @@ pub trait Circuit<F: Field> {
/// `Circuit` trait because its behaviour is circuit-critical.
type FloorPlanner: FloorPlanner;

/// Runtime parameters to configure the circuit.
#[cfg(feature = "circuit-params")]
type Params: Default = ();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Associated type default: rust-lang/rust#29661

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@parazyd Would turning on this unstable feature - associated type default - be a good idea?

I think tradeoff is:

the pro:

  • won't break any circuits implementing the Circuit trait from zcash/halo2, which seems nice, one can use zcash/halo2 Circuit implementing circuits with this fork directly

the cons:

  • potential corner cases (though the usage is simple here)
  • need to migrate if and when Rust team introduces a breaking change (but the alternative is breaking the circuits now)

other misc points:

  • Using an unstable feature requires depending on nightly which we already do in CI, so I think it isn't an issue.


/// Returns a copy of this circuit with no witness values (i.e. all witnesses set to
/// `None`). For most circuits, this will be equal to `Self::default()`.
fn without_witnesses(&self) -> Self;

/// For defining runtime configuration paramters.
#[cfg(feature = "circuit-params")]
fn params(&self) -> Self::Params {
Self::Params::default()
}

/// The circuit is given an opportunity to describe the exact gate
/// arrangement, column arrangement, etc.
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config;

/// Same as configure but takes params.
/// Credit: https://github.com/privacy-scaling-explorations/halo2/pull/168
#[cfg(feature = "circuit-params")]
fn configure_with_params(
meta: &mut ConstraintSystem<F>,
_params: Self::Params,
) -> Self::Config {
Self::configure(meta)
}

/// Given the provided `cs`, synthesize the circuit. The concrete type of
/// the caller will be different depending on the context, and they may or
/// may not expect to have a witness present.
Expand Down
15 changes: 14 additions & 1 deletion halo2_proofs/src/plonk/keygen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{

pub(crate) fn create_domain<C, ConcreteCircuit>(
params: &Params<C>,
#[cfg(feature = "circuit-params")] circuit_params: ConcreteCircuit::Params,
) -> (
EvaluationDomain<C::Scalar>,
ConstraintSystem<C::Scalar>,
Expand All @@ -34,6 +35,10 @@ where
ConcreteCircuit: Circuit<C::Scalar>,
{
let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit_params);
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let degree = cs.degree();
Expand Down Expand Up @@ -195,7 +200,11 @@ where
C::Scalar: FromUniformBytes<64>,
ConcreteCircuit: Circuit<C::Scalar>,
{
let (domain, cs, config) = create_domain::<C, ConcreteCircuit>(params);
let (domain, cs, config) = create_domain::<C, ConcreteCircuit>(
params,
#[cfg(feature = "circuit-params")]
circuit.params(),
);

if (params.n as usize) < cs.minimum_rows() {
return Err(Error::not_enough_rows_available(params.k));
Expand Down Expand Up @@ -255,6 +264,10 @@ where
ConcreteCircuit: Circuit<C::Scalar>,
{
let mut cs = ConstraintSystem::default();

#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut cs);

let cs = cs;
Expand Down
120 changes: 99 additions & 21 deletions halo2_proofs/src/plonk/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ pub fn create_proof<

let domain = &pk.vk.domain;
let mut meta = ConstraintSystem::default();
// Since there is 1 proving key for all the circuits, the circuits should share the same configuration.
#[cfg(feature = "circuit-params")]
let config = ConcreteCircuit::configure_with_params(&mut meta, circuits[0].params());
#[cfg(not(feature = "circuit-params"))]
let config = ConcreteCircuit::configure(&mut meta);

// Selector optimizations cannot be applied here; use the ConstraintSystem
Expand Down Expand Up @@ -724,63 +728,137 @@ pub fn create_proof<
multiopen::create_proof(params, rng, transcript, instances).map_err(|_| Error::Opening)
}

#[cfg(feature = "circuit-params")]
#[test]
fn test_create_proof() {
use crate::{
circuit::SimpleFloorPlanner,
pasta::pallas,
plonk::{keygen_pk, keygen_vk},
transcript::{Blake2bWrite, Challenge255},
};
use pasta_curves::EqAffine;
use rand_core::OsRng;

#[derive(Clone, Copy)]
struct MyCircuit;
#[derive(Clone, Default)]
struct MyCircuit {
vals: Vec<Value<pallas::Base>>,
}

#[derive(Clone)]
struct MyConfig {
advices: Vec<Column<Advice>>,
primary: Column<Instance>,
}

fn assign_free_advice<V: Copy>(
mut layouter: impl crate::circuit::Layouter<pallas::Base>,
column: Column<Advice>,
value: Value<V>,
) -> Result<crate::circuit::AssignedCell<V, pallas::Base>, crate::plonk::Error>
where
for<'v> Assigned<pallas::Base>: From<&'v V>,
{
layouter.assign_region(
|| "load private",
|mut region| region.assign_advice(|| "load private", column, 0, || value),
)
}

impl<F: Field> Circuit<F> for MyCircuit {
type Config = ();
impl Circuit<pallas::Base> for MyCircuit {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case that should be helpful to see the flow.

type Config = MyConfig;

type FloorPlanner = SimpleFloorPlanner;

type Params = Self;

fn without_witnesses(&self) -> Self {
*self
self.clone()
}

fn params(&self) -> Self::Params {
self.clone()
}

fn configure(_meta: &mut ConstraintSystem<F>) -> Self::Config {}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
Self::Config {
advices: vec![],
primary: meta.instance_column(),
}
}

fn configure_with_params(
meta: &mut ConstraintSystem<pallas::Base>,
params: Self::Params,
) -> Self::Config {
let advices = vec![meta.advice_column(); params.vals.len()];
let primary = meta.instance_column();

for advice in advices.iter() {
meta.enable_equality(*advice);
}
meta.enable_equality(primary);
Self::Config { advices, primary }
}

fn synthesize(
&self,
_config: Self::Config,
_layouter: impl crate::circuit::Layouter<F>,
config: Self::Config,
mut layouter: impl crate::circuit::Layouter<pallas::Base>,
) -> Result<(), Error> {
let mut witnessed = vec![];
for (i, val) in self.vals.iter().enumerate() {
let witness = assign_free_advice(
layouter.namespace(|| format!("witness {}", i)),
config.advices[i],
*val,
)?;
witnessed.push(witness);
}
for (i, witness) in witnessed.iter().enumerate() {
layouter.constrain_instance(witness.cell(), config.primary, i)?
}
Ok(())
}
}

let params: Params<EqAffine> = Params::new(3);
let vk = keygen_vk(&params, &MyCircuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &MyCircuit).expect("keygen_pk should not fail");
let params: Params<EqAffine> = Params::new(5);
let vals = vec![
Value::known(pallas::Base::from(1)),
Value::known(pallas::Base::from(2)),
Value::known(pallas::Base::from(3)),
];

let circuit = MyCircuit { vals };
// Render circuit digram
use crate::dev::CircuitLayout;
use plotters::prelude::*;
let root = BitMapBackend::new("../target/layout.png", (3840, 2160)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root.titled("Circuit Layout", ("sans-serif", 60)).unwrap();
CircuitLayout::default().render(5, &circuit, &root).unwrap();

let vk = keygen_vk(&params, &circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &circuit).expect("keygen_pk should not fail");
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);

// Create proof with wrong number of instances
let proof = create_proof(
&params,
&pk,
&[MyCircuit, MyCircuit],
&[circuit.clone(), circuit.clone()],
&[],
OsRng,
&mut transcript,
);
assert!(matches!(proof.unwrap_err(), Error::InvalidInstances));

let instance = vec![
pallas::Base::from(1),
pallas::Base::from(2),
pallas::Base::from(3),
];
// Create proof with correct number of instances
create_proof(
&params,
&pk,
&[MyCircuit, MyCircuit],
&[&[], &[]],
OsRng,
&mut transcript,
)
.expect("proof generation should not fail");
let prover = crate::dev::MockProver::run(5, &circuit, vec![instance]).unwrap();
prover.assert_satisfied();
}
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.60.0"
channel = "nightly"