Skip to content

Commit

Permalink
Refactor Composer into trait
Browse files Browse the repository at this point in the history
A composer is a set of functionalities that can be extended into
different implementators, such as debuggers or even PLONKup.

This commit introduces the composer as trait, and targets to reduce the
complexity of the implementation of multiple components of the composer.

With this simplification, we achieved significant performance boost for
public inputs evaluation.

We also removed some of the exported types that are leaked internals.
The user shouldn't be aware of public inputs indexes, kzg commitments,
keys or any permutation argument. Instead, he should work with proofs,
public inputs, public parameters, labels, circuits, provers and
verifiers.

The proof generation was largely simplified, reducing its arguments to a
random number generator for the proof blinders, and a circuit instance
for its witnesses.

The proof verification was simplified to take only the proof and its
public inputs. The public inputs are not encoded into the proof because
they are often used from external sources, such as blockchain payload.

A debugger was introduced and it will output CDF files if the feature
flag is on, and the `CDF_OUTPUT` environment variable is set. These CDF
files are expected to be read from the TCDB debugger - a CLI application
inspired in gdb, for zk-SNARKS Plonk circuits.

Finally, a type-safe constraint was introduced, binding the prover
and verifier with their concrete circuit implementation. This allowed
great simplification of the verification process since the user don't
need to manage verification keys anymore.
  • Loading branch information
vlopes11 committed Aug 15, 2022
1 parent 8bb8586 commit a107f2f
Show file tree
Hide file tree
Showing 52 changed files with 4,324 additions and 4,745 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ cfg-if = "1.0"
canonical = {version = "0.7", optional = true}
canonical_derive = {version = "0.7", optional = true}
rkyv = {version = "0.7", optional = true}
backtrace = {version = "0.3", optional = true}
dusk-cdf = {version = "0.2", optional = true}

[dev-dependencies]
criterion = "0.3"
tempdir = "0.3"
rand = "0.8"

[[bench]]
name = "plonk"
Expand All @@ -51,8 +54,7 @@ std = [
"rayon"
]
alloc = ["dusk-bls12_381/alloc"]
trace = []
trace-print = ["trace"]
debug = ["dusk-cdf", "backtrace"]
canon = ["dusk-bls12_381/canon", "dusk-jubjub/canon", "canonical", "canonical_derive"]
rkyv-impl = ["dusk-bls12_381/rkyv", "dusk-jubjub/rkyv-impl", "rkyv"]

Expand Down
95 changes: 26 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,16 @@ pub struct TestCircuit {
}

impl Circuit for TestCircuit {
const CIRCUIT_ID: [u8; 32] = [0xff; 32];
fn gadget(
&mut self,
composer: &mut TurboComposer,
) -> Result<(), Error> {
fn circuit<C>(&self, composer: &mut C) -> Result<(), Error>
where
C: Composer,
{
let a = composer.append_witness(self.a);
let b = composer.append_witness(self.b);

// Make first constraint a + b = c
let constraint = Constraint::new()
.left(1)
.right(1)
.public(-self.c)
.a(a)
.b(b);
let constraint =
Constraint::new().left(1).right(1).public(-self.c).a(a).b(b);

composer.append_gate(constraint);

Expand All @@ -59,71 +54,38 @@ impl Circuit for TestCircuit {
composer.component_range(b, 1 << 5);

// Make second constraint a * b = d
let constraint = Constraint::new()
.mult(1)
.public(-self.d)
.a(a)
.b(b);
let constraint =
Constraint::new().mult(1).public(-self.d).a(a).b(b);

composer.append_gate(constraint);

let e = composer.append_witness(self.e);
let scalar_mul_result = composer
.component_mul_generator(e, dusk_jubjub::GENERATOR_EXTENDED);
.component_mul_generator(e, dusk_jubjub::GENERATOR_EXTENDED)?;

// Apply the constraint
composer.assert_equal_public_point(scalar_mul_result, self.f);

Ok(())
}
}

fn public_inputs(&self) -> Vec<PublicInputValue> {
vec![self.c.into(), self.d.into(), self.f.into()]
}
let label = b"transcript-arguments";
let pp = PublicParameters::setup(1 << 12, &mut OsRng)
.expect("failed to setup");

fn padded_gates(&self) -> usize {
1 << 11
}
}
let (prover, verifier) = Compiler::compile::<TestCircuit>(&pp, label)
.expect("failed to compile circuit");

// Generate the proof and its public inputs
let (proof, public_inputs) = prover
.prove(&mut OsRng, &TestCircuit::default())
.expect("failed to prove");

// Now let's use the Circuit we've just implemented!

let pp = PublicParameters::setup(1 << 12, &mut OsRng).unwrap();
// Initialize the circuit
let mut circuit = TestCircuit::default();
// Compile/preproces the circuit
let (pk, vd) = circuit.compile(&pp).unwrap();

// Prover POV
let proof = {
let mut circuit = TestCircuit {
a: BlsScalar::from(20u64),
b: BlsScalar::from(5u64),
c: BlsScalar::from(25u64),
d: BlsScalar::from(100u64),
e: JubJubScalar::from(2u64),
f: JubJubAffine::from(
dusk_jubjub::GENERATOR_EXTENDED * JubJubScalar::from(2u64),
),
};
circuit.prove(&pp, &pk, b"Test", &mut OsRng).unwrap()
};

// Verifier POV
let public_inputs: Vec<PublicInputValue> = vec![
BlsScalar::from(25u64).into(),
BlsScalar::from(100u64).into(),
JubJubAffine::from(
dusk_jubjub::GENERATOR_EXTENDED * JubJubScalar::from(2u64),
)
.into(),
];
TestCircuit::verify(
&pp,
&vd,
&proof,
&public_inputs,
b"Test",
).unwrap();
// Verify the generated proof
verifier
.verify(&proof, &public_inputs)
.expect("failed to verify proof");
```

### Features
Expand All @@ -135,12 +97,7 @@ This crate includes a variety of features which will briefly be explained below:
- `std`: Enables `std` usage as well as `rayon` parallelization in some proving and verifying ops.
It also uses the `std` versions of the elliptic curve deps, which utilizes the `parallel` feature
from `dusk-bls12-381`. By default, this is the feature that comes enabled with the crate.
- `trace`: Enables the Circuit debugger tooling. This is essentially the capability of using the
`TurboComposer::check_circuit_satisfied` function. The function will output information about each circuit gate until
one of the gates does not satisfy the equation, or there are no more gates. If there is an unsatisfied gate
equation, the function will panic and return the gate number.
- `trace-print`: Goes a step further than `trace` and prints each `gate` component data, giving a clear overview of all the
values which make up the circuit that we're constructing.
- `debug`: Enables the runtime debugger backend. Will output [CDF](https://crates.io/crates/dusk-cdf) files to the path defined in the `CDF_OUTPUT` environment variable. If used, the binary must be compiled with `debug = true`. For more info, check the [cargo book](https://doc.rust-lang.org/cargo/reference/profiles.html#debug).
__The recommended method is to derive the std output, and the std error, and then place them in text file
which can be used to efficiently analyse the gates.__
- `canon`: Enables `canonical` serialization for particular data structures, which is very useful in integrating this library within the rest of the Dusk stack - especially for storage purposes.
Expand Down
204 changes: 105 additions & 99 deletions benches/plonk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,127 +8,133 @@

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dusk_plonk::prelude::*;
use rand_core::OsRng;

#[derive(Debug, Clone, Copy)]
struct BenchCircuit {
degree: usize,
struct BenchCircuit<const DEGREE: usize> {
a: BlsScalar,
b: BlsScalar,
x: BlsScalar,
y: JubJubScalar,
z: JubJubExtended,
}

impl<T> From<T> for BenchCircuit
where
T: Into<usize>,
{
fn from(degree: T) -> Self {
impl<const DEGREE: usize> Default for BenchCircuit<DEGREE> {
fn default() -> Self {
Self {
degree: 1 << degree.into(),
a: BlsScalar::from(2u64),
b: BlsScalar::from(3u64),
x: BlsScalar::from(6u64),
y: JubJubScalar::from(7u64),
z: dusk_jubjub::GENERATOR_EXTENDED * &JubJubScalar::from(7u64),
}
}
}

impl Circuit for BenchCircuit {
const CIRCUIT_ID: [u8; 32] = [0xff; 32];

fn gadget(&mut self, composer: &mut TurboComposer) -> Result<(), Error> {
let mut a = BlsScalar::from(2u64);
let mut b = BlsScalar::from(3u64);
let mut c;

while composer.gates() < self.padded_gates() {
a += BlsScalar::one();
b += BlsScalar::one();
c = a * b + a + b + BlsScalar::one();

let x = composer.append_witness(a);
let y = composer.append_witness(b);
let z = composer.append_witness(c);

let constraint = Constraint::new()
.mult(1)
.left(1)
.right(1)
.output(-BlsScalar::one())
.constant(1)
.a(x)
.b(y)
.o(z);

composer.append_gate(constraint);
impl<const DEGREE: usize> Circuit for BenchCircuit<DEGREE> {
fn circuit<C>(&self, composer: &mut C) -> Result<(), Error>
where
C: Composer,
{
let w_a = composer.append_witness(self.a);
let w_b = composer.append_witness(self.b);
let w_x = composer.append_witness(self.x);
let w_y = composer.append_witness(self.y);
let w_z = composer.append_point(self.z);

let mut diff = 0;
let mut prev = composer.constraints();

while prev + diff < DEGREE {
let r_w =
composer.gate_mul(Constraint::new().mult(1).a(w_a).b(w_b));

composer.append_constant(15);
composer.append_constant_point(self.z);

composer.assert_equal(w_x, r_w);
composer.assert_equal_point(w_z, w_z);

composer.gate_add(Constraint::new().left(1).right(1).a(w_a).b(w_b));

composer.component_add_point(w_z, w_z);
composer.append_logic_and(w_a, w_b, 254);
composer.append_logic_xor(w_a, w_b, 254);
composer.component_boolean(C::ONE);
composer.component_decomposition::<254>(w_a);
composer.component_mul_generator(
w_y,
dusk_jubjub::GENERATOR_EXTENDED,
)?;
composer.component_mul_point(w_y, w_z);
composer.component_range(w_a, 254);
composer.component_select(C::ONE, w_a, w_b);
composer.component_select_identity(C::ONE, w_z);
composer.component_select_one(C::ONE, w_a);
composer.component_select_point(C::ONE, w_z, w_z);
composer.component_select_zero(C::ONE, w_a);

diff = composer.constraints() - prev;
prev = composer.constraints();
}

Ok(())
}

fn public_inputs(&self) -> Vec<PublicInputValue> {
vec![]
}

fn padded_gates(&self) -> usize {
self.degree
}
}

fn constraint_system_prove(
circuit: &mut BenchCircuit,
fn run<const DEGREE: usize>(
c: &mut Criterion,
pp: &PublicParameters,
pk: &ProverKey,
label: &'static [u8],
) -> Proof {
circuit
.prove(pp, pk, label, &mut OsRng)
.expect("Failed to prove bench circuit!")
) {
let (prover, verifier) =
Compiler::compile::<BenchCircuit<DEGREE>>(&pp, label)
.expect("failed to compile circuit");

// sanity run
let (proof, public_inputs) = prover
.prove(&mut rand_core::OsRng, &Default::default())
.expect("failed to prove");

verifier
.verify(&proof, &public_inputs)
.expect("failed to verify proof");

let power = (DEGREE as f64).log2() as usize;
let description = format!("Prove 2^{} = {} gates", power, DEGREE);

c.bench_function(description.as_str(), |b| {
b.iter(|| {
black_box(prover.prove(&mut rand_core::OsRng, &Default::default()))
})
});

let description = format!("Verify 2^{} = {} gates", power, DEGREE);

c.bench_function(description.as_str(), |b| {
b.iter(|| verifier.verify(black_box(&proof), black_box(&public_inputs)))
});
}

fn constraint_system_benchmark(c: &mut Criterion) {
let initial_degree = 5;
let final_degree = 17;
const MAX_DEGREE: usize = 17;

let rng = &mut rand_core::OsRng;
let label = b"dusk-network";
let pp = PublicParameters::setup(1 << final_degree, rng)
.expect("Failed to create PP");

let data: Vec<(BenchCircuit, ProverKey, VerifierData, Proof)> =
(initial_degree..=final_degree)
.map(|degree| {
let mut circuit = BenchCircuit::from(degree as usize);
let (pk, vd) =
circuit.compile(&pp).expect("Failed to compile circuit!");

let proof =
constraint_system_prove(&mut circuit, &pp, &pk, label);

BenchCircuit::verify(&pp, &vd, &proof, &[], label)
.expect("Failed to verify bench circuit");

(circuit, pk, vd, proof)
})
.collect();

data.iter().for_each(|(mut circuit, pk, _, _)| {
let size = circuit.padded_gates();
let power = (size as f64).log2() as usize;
let description = format!("Prove 2^{} = {} gates", power, size);

c.bench_function(description.as_str(), |b| {
b.iter(|| {
constraint_system_prove(black_box(&mut circuit), &pp, pk, label)
})
});
});

data.iter().for_each(|(circuit, _, vd, proof)| {
let size = circuit.padded_gates();
let power = (size as f64).log2() as usize;
let description = format!("Verify 2^{} = {} gates", power, size);

c.bench_function(description.as_str(), |b| {
b.iter(|| {
BenchCircuit::verify(&pp, vd, black_box(proof), &[], label)
.expect("Failed to verify bench circuit!");
})
});
});
let pp = PublicParameters::setup(1 << MAX_DEGREE, &mut rand_core::OsRng)
.expect("failed to generate pp");

run::<{ 1 << 5 }>(c, &pp, label);
run::<{ 1 << 6 }>(c, &pp, label);
run::<{ 1 << 7 }>(c, &pp, label);
run::<{ 1 << 8 }>(c, &pp, label);
run::<{ 1 << 9 }>(c, &pp, label);
run::<{ 1 << 10 }>(c, &pp, label);
run::<{ 1 << 11 }>(c, &pp, label);
run::<{ 1 << 12 }>(c, &pp, label);
run::<{ 1 << 13 }>(c, &pp, label);
run::<{ 1 << 14 }>(c, &pp, label);
run::<{ 1 << 15 }>(c, &pp, label);
run::<{ 1 << 16 }>(c, &pp, label);
run::<{ 1 << 17 }>(c, &pp, label);
}

criterion_group! {
Expand Down
Loading

0 comments on commit a107f2f

Please sign in to comment.