Your first proof, without the mystery.

Clone the workspace, run the maintained example, then see how the circuit, prover, and verifier fit together.

Prerequisites

You need a current Rust toolchain, Git, and Make. Cryptographic examples and tests should be run in release mode; the root Makefile carries the supported feature combinations.

Development workspaceThe imported crates are coordinated here, but the repository is not published as one combined package. Start by cloning the workspace rather than copying dependency versions from these pages.

Run the maintained examples

terminal
git clone https://github.com/dusk-network/zk-tools.git
cd zk-tools
make examples

This builds and runs both proof-system examples in release mode with the supported BLST backend. The examples prove the same basic idea through different compilation paths.

Write one reusable circuit

A circuit implements Circuit generically over ComposerBackend. Private values are witnesses; values the verifier is expected to know are public inputs.

product.rs
use dusk_zk_composer::prelude::*;

#[derive(Default)]
struct ProductCircuit {
    left: BlsScalar,
    right: BlsScalar,
    result: BlsScalar,
}

impl Circuit for ProductCircuit {
    fn circuit<B: ComposerBackend>(
        &self,
        composer: &mut Composer<B>,
    ) -> Result<(), CircuitError> {
        let left = composer.append_witness(self.left);
        let right = composer.append_witness(self.right);
        let product = composer.gate_mul(
            Constraint::new().mult(1).a(left).b(right),
        );
        let result = composer.append_public(self.result);
        composer.assert_equal(product, result);
        Ok(())
    }
}

This statement means: “I know left and right whose product equals the public result.” The Rust values are not the proof by themselves—the constraints are.

Choose a proof system

Groth16

Groth16 uses a circuit-specific trusted setup. Setup compiles ProductCircuit::default(), binds its canonical R1CS shape, and creates a matched prover and verifier.

groth16.rs
let (prover, verifier) =
    dusk_groth16::Compiler::trusted_setup::<ProductCircuit, _>(&mut OsRng)?;

let (proof, public_inputs) = prover.prove(&mut OsRng, &circuit)?;
let expected_public_inputs = [BlsScalar::from(42u64)];
assert_eq!(public_inputs.as_slice(), expected_public_inputs.as_slice());
verifier.verify(&proof, &expected_public_inputs)?;
Trust assumptionThe current Groth16 setup is single-party. Generate its randomness securely and destroy that randomness after setup. Multi-party contribution and verification flows are not implemented.

PLONK

PLONK compiles the circuit against reusable KZG public parameters and binds a transcript label to the compiled circuit.

plonk.rs
let pp = PublicParameters::setup(1 << 8, &mut OsRng)?;
let (prover, verifier) =
    dusk_plonk::Compiler::compile::<ProductCircuit>(&pp, b"product-circuit")?;

let (proof, public_inputs) = prover.prove(&mut OsRng, &circuit)?;
let expected_public_inputs = [BlsScalar::from(42u64)];
assert_eq!(public_inputs.as_slice(), expected_public_inputs.as_slice());
verifier.verify(&proof, &expected_public_inputs)?;
Validate the public statementThe verifier must construct expected public inputs from trusted application state. Here, both snippets verify the ordered input [BlsScalar::from(42u64)]; the assertion also checks that the circuit emitted the order the protocol expects.

Where to go next