Skip to content
Merged
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
16 changes: 16 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/vm_runner",
"crates/privacy_prove",
"crates/privacy_circuit_verify",
"crates/circuit_params",
]
resolver = "2"

Expand Down Expand Up @@ -81,6 +82,7 @@ circuit-serialize = { git = "https://github.com/starkware-libs/stwo-circuits", r

# local crates
cairo-program-runner-lib = { path = "crates/cairo-program-runner-lib", version = "1.0.0" }
leaf-prover = { path = "crates/leaf_prover" }
leaf-proof-format = { path = "crates/leaf_proof_format" }
privacy-circuit-verify = { path = "crates/privacy_circuit_verify", version = "1.0.0" }
stwo-run-and-prove-common = { path = "crates/stwo_run_and_prove_common", version = "1.2.2" }
Expand Down
20 changes: 20 additions & 0 deletions crates/circuit_params/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "circuit-params"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Compute the leaf-prover verifier circuit's per-component sizes for a range of trace sizes"

[dependencies]
clap.workspace = true
indexmap = { version = "2.10.0", default-features = false, features = ["std"] }

leaf-prover.workspace = true

cairo-air.workspace = true
circuit-cairo-verifier.workspace = true
circuits-stark-verifier.workspace = true
circuit-common.workspace = true
stwo-cairo-common.workspace = true
stwo-cairo-prover.workspace = true
stwo-cairo-utils.workspace = true
153 changes: 153 additions & 0 deletions crates/circuit_params/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//! Computes the leaf-prover verifier circuit's per-component sizes for the CANONICAL preprocessed
//! trace config, across a range of verified trace sizes, and writes them as text.

use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::Arc;

use cairo_air::verifier::INTERACTION_POW_BITS;
use circuit_cairo_verifier::privacy::get_pcs_config;
use circuit_cairo_verifier::statement::MEMORY_VALUES_LIMBS;
use circuit_cairo_verifier::verify::{CairoVerifierConfig, build_cairo_verifier_circuit};
use circuit_common::finalize::{ComponentSizes, compute_padded_sizes};
use circuits_stark_verifier::proof::ProofConfig;
use clap::Parser;
use leaf_prover::consts::DISABLED_COMPONENTS_CANONICAL_PREPROCESSED;
use leaf_prover::prove_leaf::{LeafVerifierComponents, leaf_verifier_components};
use stwo_cairo_common::preprocessed_columns::preprocessed_trace::PreProcessedTraceVariant;
use stwo_cairo_common::prover_types::cpu::M31;
use stwo_cairo_utils::binary_utils::run_binary;

#[derive(Parser)]
struct Args {
/// Path to write the output to. If omitted, prints to stdout.
#[clap(long)]
output_path: Option<PathBuf>,
/// Smallest verified trace log size to measure (inclusive). A canonical Cairo trace commits
/// its preprocessed sequence columns at `MAX_SEQUENCE_LOG_SIZE = 25`, so a real canonical
/// leaf proof has `log_trace_size >= 25`.
#[clap(long)]
min_trace_log_size: u32,
/// Largest verified trace log size to measure (inclusive).
#[clap(long)]
max_trace_log_size: u32,
Comment thread
cursor[bot] marked this conversation as resolved.
/// Log blowup factor of the verified Cairo proof (1, 2, or 3), passed to `get_pcs_config`.
#[clap(long = "log_blowup_factor", default_value_t = 1)]
log_blowup_factor: u32,
}

/// The fraction (as a percentage) of the padded (power-of-two) component that is actually used.
fn usage_percent(size: usize, padded_size: usize) -> f64 {
100.0 * size as f64 / padded_size as f64
}

/// The raw (non-padded) row count of each AIR component.
struct RawSizes {
eq: usize,
qm31_ops: usize,
m31_to_u32: usize,
triple_xor: usize,
blake_g_gate: usize,
}

/// One line with each component's padded log size and usage (fraction of the padded power-of-two
/// that the non-padded rows fill).
fn format_sizes(raw: &RawSizes, padded: &ComponentSizes) -> String {
let component = |name: &str, raw_size: usize, padded_size: usize| {
format!(
"{name}:(log: {}, usage = {:.0}%)",
padded_size.ilog2(),
usage_percent(raw_size, padded_size),
)
};
format!(
"{} {} {} {} {}",
component("eq", raw.eq, padded.eq),
component("qm31_ops", raw.qm31_ops, padded.qm31_ops),
component("m31_to_u32", raw.m31_to_u32, padded.m31_to_u32),
component("triple_xor", raw.triple_xor, padded.triple_xor),
component("blake_g_gate", raw.blake_g_gate, padded.blake_g_gate),
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Output omits raw row counts

Medium Severity

The binary computes RawSizes for each AIR component but format_sizes only prints padded log and usage. The PR and crate docs describe emitting non-padded row counts per component, so stdout/file output is missing a primary reported field.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ce5906e. Configure here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we care about this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I fixed the doc, I think the usage percentage is more useful than the actual number.


/// Builds the leaf-prover verifier circuit topology (with the CANONICAL preprocessed trace and its
/// component set) for a verified Cairo proof whose trace has the given log size, and returns the
/// non-padded row counts and padded sizes of its AIR components.
fn leaf_verifier_component_sizes(
trace_log_size: u32,
log_blowup_factor: u32,
) -> (RawSizes, ComponentSizes) {
let preprocessed_trace_variant = PreProcessedTraceVariant::Canonical;

// The Cairo-proof PCS config the leaf prover uses (canonical preprocessed).
let pcs_config = get_pcs_config(trace_log_size, log_blowup_factor);

let LeafVerifierComponents { components: cairo_components, enabled_bits } =
leaf_verifier_components(&DISABLED_COMPONENTS_CANONICAL_PREPROCESSED);

let proof_config = ProofConfig::new(
&cairo_components,
preprocessed_trace_variant.n_columns(),
&pcs_config,
INTERACTION_POW_BITS,
);

// Program length and output count are held fixed for this measurement; only the trace size
// varies. The preprocessed root value is irrelevant for the [NoValue] topology.
let program: Arc<[[M31; MEMORY_VALUES_LIMBS]]> =
std::iter::repeat_n([M31::from(0u32); MEMORY_VALUES_LIMBS], 128).collect();

let verifier_config = CairoVerifierConfig {
proof_config,
enabled_bits,
program,
n_outputs: 1,
preprocessed_root: [0u32; 8].into(),
preprocessed_trace_variant,
};

let context = build_cairo_verifier_circuit(&verifier_config);
let padded = compute_padded_sizes(&context);

// Non-padded row counts, mirroring `compute_padded_sizes` before its power-of-two rounding.
let circuit = context.circuit();
// TODO(ilya): Use `qm31_ops_n_rows` instead of counting the operations manually.
let qm31_ops = circuit.add.len()
+ circuit.sub.len()
+ circuit.mul.len()
+ circuit.pointwise_mul.len()
+ circuit.permutation.iter().map(|p| p.inputs.len() + p.outputs.len()).sum::<usize>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong qm31_ops usage metric

Medium Severity

The qm31_ops usage percentage pairs a hand-rolled raw count (sums of add/sub/mul/pointwise_mul lengths plus permutation input/output counts) with padded.qm31_ops from compute_padded_sizes, which uses the library’s row model (see the nearby TODO for qm31_ops_n_rows). That mismatch makes reported qm31_ops usage and log lines unreliable for sizing decisions.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f546396. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

both compute qm31_ops_n_rows in the same way

let raw = RawSizes {
eq: circuit.eq.len(),
qm31_ops,
m31_to_u32: circuit.m31_to_u32.len(),
triple_xor: circuit.triple_xor.len(),
blake_g_gate: circuit.blake_g_gate.len(),
};

(raw, padded)
}

fn main() -> ExitCode {
run_binary(run, "circuit_params")
}

fn run() -> Result<(), String> {
let args = Args::parse();

let output = (args.min_trace_log_size..=args.max_trace_log_size)
.map(|trace_log_size| {
let (raw, padded) =
leaf_verifier_component_sizes(trace_log_size, args.log_blowup_factor);
format!("{}: {}", trace_log_size, format_sizes(&raw, &padded))
})
.collect::<Vec<_>>()
.join("\n");

match args.output_path {
Some(path) => std::fs::write(&path, format!("{output}\n"))
.map_err(|err| format!("Cannot write output to {}: {err}", path.display()))?,
None => println!("{output}"),
}
Ok(())
}
24 changes: 24 additions & 0 deletions crates/circuit_params/tests/cli_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::process::Command;

/// Runs the `circuit-params` binary for a single trace log size and checks it prints one info line.
#[test]
fn run_circuit_params_binary() {
let binary = env!("CARGO_BIN_EXE_circuit-params");
let output = Command::new(binary)
.args(["--min-trace-log-size", "25", "--max-trace-log-size", "25"])
.output()
.expect("Cannot run circuit-params");

assert!(
output.status.success(),
"binary exited with status {}: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
);

let stdout = String::from_utf8(output.stdout).expect("stdout is not valid UTF-8");
assert!(
stdout.contains("25: eq:(log:") && stdout.contains("blake_g_gate:(log:"),
"unexpected output: {stdout}"
);
}
43 changes: 28 additions & 15 deletions crates/leaf_prover/src/prove_leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,6 @@ pub fn prove_leaf(

let preprocessed_root = proof.extended_stark_proof.proof.commitments[PREPROCESSED_TRACE_IDX];

// Create the component list and enabled bits for the circuit that verifies the Cairo proof.
// The set of components is constant (all possible components for the given preprocessed trace)
// to keep the verifier circuit stable. The trace is expected to contain all the components
// in this set.
let mut cairo_components: IndexMap<&'static str, Box<dyn CircuitEval<_>>> = IndexMap::default();
let mut enabled_bits = vec![];

let disabled_components: &[&str] = match cairo_prover_parameters.preprocessed_trace {
PreProcessedTraceVariant::Canonical => &DISABLED_COMPONENTS_CANONICAL_PREPROCESSED,
PreProcessedTraceVariant::CanonicalSmall => &DISABLED_COMPONENTS_SMALL_PREPROCESSED,
Expand All @@ -106,14 +99,8 @@ pub fn prove_leaf(
cairo_prover_parameters.preprocessed_trace
),
};
for (name, component) in all_components::<QM31>() {
if disabled_components.contains(&name) {
enabled_bits.push(false);
} else {
cairo_components.insert(name, component);
enabled_bits.push(true);
}
}
let LeafVerifierComponents { components: cairo_components, enabled_bits } =
leaf_verifier_components(disabled_components);

// Set min_lifting_log_size from the cairo proof.
let mut pcs_config = cairo_prover_parameters.pcs_config;
Expand Down Expand Up @@ -204,6 +191,32 @@ pub fn prove_leaf(
SerializedLeafProof { circuit_preprocessed_root, proof: proof_bytes }
}

/// The components of the circuit that verifies the Cairo proof.
pub struct LeafVerifierComponents {
/// Map from component name to the circuit evaluator that verifies it.
pub components: IndexMap<&'static str, Box<dyn CircuitEval<QM31>>>,
/// One bit per possible component: `true` if the component is enabled (present).
pub enabled_bits: Vec<bool>,
}

/// Creates the component list and enabled bits for the circuit that verifies the Cairo proof.
/// The set of components is constant (all possible components for the given preprocessed trace,
/// minus `disabled_components`) to keep the verifier circuit stable. The trace is expected to
/// contain all the components in this set.
pub fn leaf_verifier_components(disabled_components: &[&str]) -> LeafVerifierComponents {
let mut components: IndexMap<&'static str, Box<dyn CircuitEval<QM31>>> = IndexMap::default();
let mut enabled_bits = vec![];
for (name, component) in all_components::<QM31>() {
if disabled_components.contains(&name) {
enabled_bits.push(false);
} else {
components.insert(name, component);
enabled_bits.push(true);
}
}
LeafVerifierComponents { components, enabled_bits }
}

fn program_felts(program: &Program) -> Vec<[M31; MEMORY_VALUES_LIMBS]> {
let mut program_felts = vec![];
for value in program.iter_data() {
Expand Down
Loading