Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions crates/circuit_registry/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ impl From<&ComponentSizes> for LogSizes {
}
}

impl From<&LogSizes> for ComponentSizes {
fn from(log: &LogSizes) -> Self {
ComponentSizes {
eq: 1 << log.eq,
qm31_ops: 1 << log.qm31_ops,
m31_to_u32: 1 << log.m31_to_u32,
triple_xor: 1 << log.triple_xor,
blake_g_gate: 1 << log.blake_g_gate,
}
}
}

fn log_size(size: usize) -> u32 {
size.next_power_of_two().ilog2()
}
Expand All @@ -54,6 +66,17 @@ impl<'de> Deserialize<'de> for RootHex {
}
}

impl RootHex {
/// The root as 32 little-endian bytes, matching a proof's `circuit_preprocessed_root`.
pub fn to_le_bytes(&self) -> [u8; 32] {
let mut bytes = [0u8; 32];
for (word, chunk) in self.0.iter().zip(bytes.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_le_bytes());
}
bytes
}
}

/// A circuit configuration: the (circuit-prover) log blowup factor and padded component log sizes a
/// circuit is proven with. Circuits sharing a config produce proofs a common AIR can verify.
#[derive(Serialize, Deserialize)]
Expand Down Expand Up @@ -94,3 +117,22 @@ pub struct CircuitRegistry {
pub leaf_verifiers: Vec<LeafVerifier>,
pub multiverifiers: Vec<Multiverifier>,
}

impl CircuitRegistry {
/// The supported leaf verifier for a Cairo proof with the given trace log size and log blowup
/// factor, or `None` if the registry doesn't list it.
pub fn leaf_verifier(
&self,
trace_log_size: u32,
log_blowup_factor: u32,
) -> Option<&LeafVerifier> {
self.leaf_verifiers.iter().find(|leaf| {
leaf.trace_log_size == trace_log_size && leaf.log_blowup_factor == log_blowup_factor
})
}

/// The component-size pad target of the given config, or `None` if the config is unknown.
pub fn config_pad_target(&self, config: &str) -> Option<ComponentSizes> {
self.circuit_configs.get(config).map(|config| (&config.component_log_sizes).into())
}
}
1 change: 1 addition & 0 deletions crates/leaf_prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sonic-rs.workspace = true
tracing.workspace = true
indexmap = { version = "2.10.0", default-features = false, features = ["std"] }

circuit-registry.workspace = true
leaf-proof-format.workspace = true

circuit-cairo-verifier.workspace = true
Expand Down
23 changes: 21 additions & 2 deletions crates/leaf_prover/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::path::PathBuf;
use std::process::ExitCode;

use cairo_program_runner_lib::utils::{get_program, get_program_input_from_path};
use circuit_registry::CircuitRegistry;
use clap::Parser;
use leaf_prover::prove_leaf::prove_leaf;
use stwo_cairo_utils::binary_utils::run_binary;
Expand All @@ -28,6 +29,12 @@ struct Args {
cairo_prover_params_json: PathBuf,
#[clap(long, help = "JSON file containing the circuit prover parameters.")]
circuit_prover_params_json: PathBuf,
#[clap(
long,
help = "JSON file containing the circuit registry; the leaf circuit is padded to its \
component-size target."
)]
circuit_registry_json: PathBuf,
#[clap(long, help = "Path to write the output file")]
output_path: PathBuf,
}
Expand Down Expand Up @@ -65,8 +72,20 @@ fn run() -> Result<(), String> {
});
let circuit_prover_pcs_config = sonic_rs::from_str(&circuit_prover_pcs_config).unwrap();

let output =
prove_leaf(&program, program_input, cairo_prover_parameters, circuit_prover_pcs_config);
let registry = read_to_string(&args.circuit_registry_json).unwrap_or_else(|err| {
panic!("Cannot get circuit registry from {}: {err}", args.circuit_registry_json.display())
});
let registry: CircuitRegistry = serde_json::from_str(&registry).unwrap_or_else(|err| {
panic!("Cannot parse circuit registry from {}: {err}", args.circuit_registry_json.display())
});

let output = prove_leaf(
&program,
program_input,
cairo_prover_parameters,
circuit_prover_pcs_config,
&registry,
);

fs::write(&args.output_path, serde_json::to_string_pretty(&output).unwrap()).unwrap_or_else(
|err| panic!("Cannot write output to {}: {err}", args.output_path.display()),
Expand Down
30 changes: 29 additions & 1 deletion crates/leaf_prover/src/prove_leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ use circuit_cairo_verifier::verify::{
CairoVerifierConfig, build_and_fill_cairo_verifier_circuit,
prepare_cairo_proof_for_circuit_verifier,
};
use circuit_common::finalize::pad_to_targets;
use circuit_common::preprocessed::PreprocessedCircuit;
use circuit_prover::prover::{
BaseColumnPool, prepare_circuit_proof_for_circuit_verifier, prove_circuit_assignment,
};
use circuit_registry::CircuitRegistry;
use circuit_serialize::serialize::CircuitSerialize;
use circuits_stark_verifier::constraint_eval::CircuitEval;
use circuits_stark_verifier::proof::ProofConfig;
Expand All @@ -40,6 +42,7 @@ pub fn prove_leaf(
program_input: Option<ProgramInput>,
cairo_prover_parameters: ProverParameters,
circuit_prover_pcs_config: PcsConfig,
registry: &CircuitRegistry,
) -> SerializedLeafProof {
assert!(
cairo_prover_parameters.include_all_preprocessed_columns,
Expand Down Expand Up @@ -119,6 +122,23 @@ pub fn prove_leaf(
let mut pcs_config = cairo_prover_parameters.pcs_config;
pcs_config.min_lifting_log_size = proof.extended_stark_proof.proof.config.min_lifting_log_size;

// Look up this leaf in the registry by the verified Cairo proof's trace log size and blowup.
// `get_pcs_config` sets `min_lifting_log_size = trace_log_size + log_blowup_factor`, so we
// invert it here. The matched entry gives the component-size target to pad to and the
// preprocessed root the resulting circuit must produce.
let log_blowup_factor = pcs_config.fri_config.log_blowup_factor;
let trace_log_size = pcs_config.min_lifting_log_size - log_blowup_factor;
let leaf_verifier =
registry.leaf_verifier(trace_log_size, log_blowup_factor).unwrap_or_else(|| {
panic!(
"No leaf verifier in the registry for trace_log_size={trace_log_size}, \
log_blowup_factor={log_blowup_factor}"
)
});
let pad_target = registry.config_pad_target(&leaf_verifier.config).unwrap_or_else(|| {
panic!("Registry has no config {:?} for the matched leaf verifier", leaf_verifier.config)
});

let proof_config = ProofConfig::new(
&cairo_components,
cairo_prover_parameters.preprocessed_trace.n_columns(),
Expand Down Expand Up @@ -158,7 +178,7 @@ pub fn prove_leaf(
output_hash,
);

// TODO: Pad to multiverifier size.
pad_to_targets(&mut context, pad_target);

info!(
"Verifier config:
Expand Down Expand Up @@ -193,6 +213,14 @@ pub fn prove_leaf(
circuit_proof.stark_proof.proof.commitments[PREPROCESSED_TRACE_IDX].0;
info!("Circuit preprocessed root: {:?}", circuit_preprocessed_root);

// The circuit we built and proved must be the one the registry describes for this leaf.
let expected_root = leaf_verifier.preprocessed_root.to_le_bytes();
assert!(
circuit_preprocessed_root == expected_root,
"Circuit preprocessed root {circuit_preprocessed_root:?} does not match the registry's \
leaf verifier root {expected_root:?}"
);

// Convert the proof to our output format.

let (proof_qm31s, _public_data) = prepare_circuit_proof_for_circuit_verifier(circuit_proof);
Expand Down
2 changes: 2 additions & 0 deletions crates/leaf_prover/tests/cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ fn run_leaf_prover_binary() {
.arg(test_data_dir.join("cairo_prover_params_canonical_small.json"))
.arg("--circuit-prover-params-json")
.arg(test_data_dir.join("circuit_prover_params_canonical_small.json"))
.arg("--circuit-registry-json")
.arg(test_data_dir.join("registry.json"))
.arg("--output-path")
.arg(&output_path)
.status()
Expand Down
32 changes: 32 additions & 0 deletions crates/leaf_prover/tests/data/registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"circuit_configs": {
"default": {
"log_blowup_factor": 1,
"component_log_sizes": {
"eq": 20,
"qm31_ops": 23,
"m31_to_u32": 20,
"triple_xor": 19,
"blake_g_gate": 23
}
}
},
"leaf_verifiers": [
{
"config": "default",
"trace_log_size": 20,
"log_blowup_factor": 1,
"preprocessed_root": [
"0x6de807ae",
"0x0bf50621",
"0x05cbd9f4",
"0x87b08e05",
"0x12453193",
"0x89e82e30",
"0x25a779de",
"0x7da05a04"
]
}
],
"multiverifiers": []
}
38 changes: 38 additions & 0 deletions crates/leaf_prover/tests/registry_fixture_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Guards the `cli_test` registry fixture: it must list the leaf the slow end-to-end test proves,
//! resolve to the expected pad target, and carry the same preprocessed root as the committed
//! expected output. Fast (no proving), so it runs on every test job unlike `cli_test`.

use std::fs;
use std::path::PathBuf;

use circuit_registry::CircuitRegistry;
use leaf_proof_format::SerializedLeafProof;

#[test]
fn registry_fixture_matches_expected_leaf() {
let data_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data");

let registry: CircuitRegistry =
serde_json::from_str(&fs::read_to_string(data_dir.join("registry.json")).unwrap())
.expect("Cannot parse registry fixture");

// The fixture lists exactly the one leaf the e2e test proves, found by its identity.
assert_eq!(registry.leaf_verifiers.len(), 1);
let leaf = &registry.leaf_verifiers[0];
assert!(registry.leaf_verifier(leaf.trace_log_size, leaf.log_blowup_factor).is_some());

// Its config resolves to the pad target the leaf prover expects.
let pad = registry.config_pad_target(&leaf.config).expect("leaf verifier config missing");
assert_eq!(pad.eq, 1 << 20);
assert_eq!(pad.qm31_ops, 1 << 23);
assert_eq!(pad.m31_to_u32, 1 << 20);
assert_eq!(pad.triple_xor, 1 << 19);
assert_eq!(pad.blake_g_gate, 1 << 23);

// Its preprocessed root equals the root in the committed expected e2e output, so a matching
// leaf passes the prover's root check.
let expected: SerializedLeafProof =
serde_json::from_str(&fs::read_to_string(data_dir.join("expected_output.json")).unwrap())
.expect("Cannot parse expected output");
assert_eq!(leaf.preprocessed_root.to_le_bytes(), expected.circuit_preprocessed_root);
}
Loading