Expand description
§Midnight ZK Standard Library
The Midnight ZK Standard Library (midnight-zk-stdlib) provides a high-level abstraction for building zero-knowledge circuits using the midnight-circuits and midnight-proofs crates.
WARNING: This library has not been audited. Use it at your own risk.
§Overview
ZkStdLib encapsulates the functionality required by Midnight and serves as an abstraction layer, allowing developers to focus on circuit logic rather than the configuration and chip creation. Developers only need to implement the Relation trait, avoiding the boilerplate of Halo2’s Circuit trait.
The architecture of ZkStdLib is configurable via the following structure:
pub struct ZkStdLibArch {
pub jubjub: bool,
pub poseidon: bool,
pub sha2_256: bool,
pub sha2_512: bool,
pub sha3_256: bool,
pub keccak_256: bool,
pub blake2b: bool,
pub secp256k1: bool,
pub bls12_381: bool,
pub base64: bool,
pub automaton: bool,
pub nr_pow2range_cols: u8,
}The configuration can be defined via the Relation trait with the used_chips function. The default architecture activates only JubJub, Poseidon and sha256, and uses a single column for the pow2range chip. The maximum number of columns accepted for the pow2range chip is currently 4.
§Example: Proving Knowledge of a SHA-256 Preimage
Here is a complete example showing how to build a circuit for proving knowledge of a SHA-256 preimage.
Given a public input x, we will prove that we know w ∈ {0,1}^192 such that x = SHA-256(w).
use midnight_circuits::{
instructions::{AssignmentInstructions, PublicInputInstructions},
types::{AssignedByte, Instantiable},
};
use midnight_zk_stdlib::{utils::plonk_api::filecoin_srs, Relation, ZkStdLib, ZkStdLibArch};
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use rand::{rngs::OsRng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use sha2::Digest;
type F = midnight_curves::Fq;
#[derive(Clone, Default)]
struct ShaPreImageCircuit;
impl Relation for ShaPreImageCircuit {
// When defining a circuit, one must clearly state the instance and the witness
// of the underlying NP-relation.
type Instance = [u8; 32]; // x ∈ {0, 1}^256
type Witness = [u8; 24]; // w ∈ {0, 1}^192 (192 = 24 * 8)
type Error = Error;
// We must specify how the instance, which can be any Rust type, is converted
// into raw field elements to be processed by the prover/verifier. The order
// here must be consistent with the order in which public inputs are
// constrained/assigned in [circuit].
fn format_instance(instance: &Self::Instance) -> Result<Vec<F>, Error> {
Ok(instance
.iter()
.flat_map(AssignedByte::<F>::as_public_input)
.collect())
}
// Define the logic of the NP-relation being proved.
fn circuit(
&self,
std_lib: &ZkStdLib,
layouter: &mut impl Layouter<F>,
_instance: Value<Self::Instance>,
witness: Value<Self::Witness>,
) -> Result<(), Error> {
let assigned_input = std_lib.assign_many(layouter, &witness.transpose_array())?;
let output = std_lib.sha2_256(layouter, &assigned_input)?;
output
.iter()
.try_for_each(|b| std_lib.constrain_as_public_input(layouter, b))
}
fn used_chips(&self) -> ZkStdLibArch {
ZkStdLibArch {
sha2_256: true,
..ZkStdLibArch::default()
}
}
fn write_relation<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
Ok(())
}
fn read_relation<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> {
Ok(ShaPreImageCircuit)
}
}
// The SRS (Structured Reference String) must match the circuit size exactly.
// `k` is the log2 of the circuit size (i.e. the circuit has 2^k rows).
// We can load an SRS that is larger than needed and downsize it automatically.
const K: u32 = 14;
let mut srs = filecoin_srs(K);
let relation = ShaPreImageCircuit;
// Compute the optimal k for the circuit and downsize the SRS to match.
let k = midnight_zk_stdlib::optimal_k(&relation);
srs.downsize(k);
// If you already know the exact k for your circuit, you can skip the above
// and load an SRS of the right size directly.
let vk = midnight_zk_stdlib::setup_vk(&srs, &relation);
let pk = midnight_zk_stdlib::setup_pk(&relation, &vk);
// Sample a random preimage as the witness.
let mut rng = ChaCha8Rng::from_entropy();
let witness: [u8; 24] = core::array::from_fn(|_| rng.gen());
let instance = sha2::Sha256::digest(witness).into();
let proof = midnight_zk_stdlib::prove::<ShaPreImageCircuit, blake2b_simd::State>(
&srs, &pk, &relation, &instance, witness, OsRng,
)
.expect("Proof generation should not fail");
assert!(
midnight_zk_stdlib::verify::<ShaPreImageCircuit, blake2b_simd::State>(
&srs.verifier_params(),
&vk,
&instance,
None,
&proof
)
.is_ok()
)You can find more examples in the examples directory.
§Versioning
We use Semantic Versioning. To capture the changes that do not affect the API, do not add any new functionality, but are breaking changes, we increment the MAJOR version. This happens when the circuit is modified for performance or bug fixes; the modification of the verification keys break backwards compatibility.
- MAJOR: Incremented when you make incompatible API or VK changes
- MINOR: Incremented when you add functionality in a backward-compatible manner
- PATCH: Incremented when you make backward-compatible bug fixes
§Implementation Details
This library uses a fixed configuration, meaning that regardless of what one uses, it will always consist of the same columns, lookups, permutation enabled columns, or gates. The motivation for this is twofold:
-
It facilitates recursion (we always aggregate circuits that have the same verification logic).
-
We could optimise the verifier, who can store part of the circuit description in memory and does not need to reproduce it everytime it receives a new proof.
Modules§
- utils
- Utilities and helper functions for zk-stdlib.
Structs§
- Midnight
Circuit - Circuit structure which is used to create any circuit that can be compiled into keys using the ZK standard library.
- MidnightPK
- A proving key of a Midnight circuit.
- MidnightVK
- A verifier key of a Midnight circuit.
- ZkStd
Lib - The
ZkStdLibexposes all tools that are used in circuit generation. - ZkStd
LibArch - Architecture of the standard library. Specifies what chips need to be configured.
- ZkStd
LibConfig - Configured chips for ZkStdLib.
Traits§
- Relation
- Helper trait, used to abstract the circuit developer from Halo2’s boilerplate.
Functions§
- batch_
verify - Verifies a batch of proofs with respect to their corresponding vk.
This method does not need to know the
Relationthe proofs are associated to and, indeed, it can verify proofs from differentRelations. For that, this function does not takeinstances, but public inputs in raw format (Vec<F>). - cost_
model - Cost model of the given relation for the given
k.kis the log2 of the circuit size. IfNone, the optimal value is computed automatically. - optimal_
k - Finds the optimal
k(log2 of the circuit size) for the given relation. Tries different values ofk(9..=25) and picks the smallest one where the circuit fits. The pow2range table usesmax_bit_len = k - 1. - prove
- Produces a proof of relation
Rfor the given instance (using the given proving key and witness). - setup_
pk - Generates a proving key for a
MidnightCircuit<R>circuit. - setup_
vk - Generates a verifying key for a
MidnightCircuit<R>circuit. - verify
- Verifies the given proof of relation
Rwith respect to the given instance. ReturnsOk(())if the proof is valid.