|
| 1 | +//! Top-level Cairo verifier input. |
| 2 | +//! |
| 3 | +//! Mirrors the Cairo verifier's `main(proof: CircuitProof, config: CircuitVerifierConfig)` |
| 4 | +//! signature. Field order MUST match the Cairo `CircuitProof` struct in |
| 5 | +//! `stwo-cairo/stwo_cairo_verifier/crates/circuit_air/src/lib.cairo`. |
| 6 | +//! |
| 7 | +//! The serializer is asymmetric on `queried_values`: the prover stores them in tree-major |
| 8 | +//! order, the Cairo verifier reads them sorted by column size and transposed (see |
| 9 | +//! `cairo_air::utils::sort_and_transpose_queried_values`). The conversion in |
| 10 | +//! [`prepare_cairo_verifier_input`] applies the sort before the derive emits felts. |
| 11 | +
|
| 12 | +use cairo_air::utils::sort_and_transpose_queried_values; |
| 13 | +use circuit_prover::prover::CircuitProof as CircuitProverOutput; |
| 14 | +use starknet_ff::FieldElement; |
| 15 | +use stwo::core::ColumnVec; |
| 16 | +use stwo::core::pcs::TreeVec; |
| 17 | +use stwo::core::pcs::quotients::CommitmentSchemeProof; |
| 18 | +use stwo::core::proof::StarkProof; |
| 19 | +use stwo::core::vcs::blake2_hash::Blake2sHash; |
| 20 | +use stwo::core::vcs_lifted::blake2_merkle::Blake2sM31MerkleHasher; |
| 21 | +use stwo::core::vcs_lifted::merkle_hasher::MerkleHasherLifted; |
| 22 | +use stwo_cairo_serialize::{CairoDeserialize, CairoSerialize}; |
| 23 | + |
| 24 | +use crate::CairoCircuitVerifierConfig; |
| 25 | +use crate::claim::{CairoCircuitClaim, CairoCircuitInteractionClaim}; |
| 26 | + |
| 27 | +/// Owned mirror of the Cairo `CircuitProof` struct, with `queried_values` already sorted |
| 28 | +/// and transposed into the layout the Cairo verifier expects. |
| 29 | +/// |
| 30 | +/// Symmetric `CairoSerialize`/`CairoDeserialize` derive — both directions read fields in |
| 31 | +/// declaration order, so this round-trips cleanly. |
| 32 | +// Note: cannot derive `PartialEq`/`Eq` because `FriProof`/`MerkleDecommitmentLifted` do |
| 33 | +// not implement them. Roundtrip tests compare serialized bytes instead. |
| 34 | +#[derive(Clone, Debug, CairoSerialize, CairoDeserialize)] |
| 35 | +pub struct CairoCircuitProof<H: MerkleHasherLifted<Hash = Blake2sHash> = Blake2sM31MerkleHasher> { |
| 36 | + pub claim: CairoCircuitClaim, |
| 37 | + pub interaction_pow: u64, |
| 38 | + pub interaction_claim: CairoCircuitInteractionClaim, |
| 39 | + pub stark_proof: CairoStarkProofForCircuit<H>, |
| 40 | + pub channel_salt: u32, |
| 41 | +} |
| 42 | + |
| 43 | +/// Owned counterpart of `CommitmentSchemeProof` with `queried_values` already in the |
| 44 | +/// 2D sorted-and-transposed layout (one `Vec<BaseField>` per tree, concatenated across |
| 45 | +/// queries) that the Cairo verifier deserializes. |
| 46 | +#[derive(Clone, Debug, CairoSerialize, CairoDeserialize)] |
| 47 | +pub struct CairoStarkProofForCircuit< |
| 48 | + H: MerkleHasherLifted<Hash = Blake2sHash> = Blake2sM31MerkleHasher, |
| 49 | +> { |
| 50 | + pub config: stwo::core::pcs::PcsConfig, |
| 51 | + pub commitments: Vec<Blake2sHash>, |
| 52 | + pub sampled_values: Vec<ColumnVec<Vec<stwo::core::fields::qm31::QM31>>>, |
| 53 | + pub decommitments: Vec<stwo::core::vcs_lifted::verifier::MerkleDecommitmentLifted<H>>, |
| 54 | + /// Sorted+transposed queried values (per tree). |
| 55 | + pub queried_values: Vec<Vec<stwo::core::fields::m31::M31>>, |
| 56 | + pub proof_of_work: u64, |
| 57 | + pub fri_proof: stwo::core::fri::FriProof<H>, |
| 58 | +} |
| 59 | + |
| 60 | +impl<H: MerkleHasherLifted<Hash = Blake2sHash>> CairoStarkProofForCircuit<H> { |
| 61 | + /// Builds the Cairo-ready stark proof from a Rust `StarkProof` plus per-tree column |
| 62 | + /// log sizes for the [trace, interaction] trees (used to sort `queried_values`). |
| 63 | + pub fn from_stark_proof( |
| 64 | + proof: &StarkProof<H>, |
| 65 | + trace_and_interaction_trace_log_sizes: &[&[u32]; 2], |
| 66 | + ) -> Self { |
| 67 | + let CommitmentSchemeProof { |
| 68 | + config, |
| 69 | + commitments, |
| 70 | + sampled_values, |
| 71 | + decommitments, |
| 72 | + queried_values, |
| 73 | + proof_of_work, |
| 74 | + fri_proof, |
| 75 | + } = &proof.0; |
| 76 | + |
| 77 | + let sorted = sort_and_transpose_queried_values( |
| 78 | + queried_values, |
| 79 | + trace_and_interaction_trace_log_sizes.to_vec(), |
| 80 | + ); |
| 81 | + |
| 82 | + Self { |
| 83 | + config: *config, |
| 84 | + commitments: (**commitments).clone(), |
| 85 | + sampled_values: (**sampled_values).clone(), |
| 86 | + decommitments: (**decommitments).clone(), |
| 87 | + queried_values: (*sorted).clone(), |
| 88 | + proof_of_work: *proof_of_work, |
| 89 | + fri_proof: fri_proof.clone(), |
| 90 | + } |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl<H: MerkleHasherLifted<Hash = Blake2sHash>> CairoCircuitProof<H> { |
| 95 | + /// Builds from the live circuit prover output. Errors if the prover failed. |
| 96 | + pub fn from_prover_output( |
| 97 | + prover_output: &CircuitProverOutput<H>, |
| 98 | + ) -> Result<Self, &'static str> { |
| 99 | + let extended = prover_output |
| 100 | + .stark_proof |
| 101 | + .as_ref() |
| 102 | + .map_err(|_| "circuit prover failed to produce a stark proof")?; |
| 103 | + let stark_proof = &extended.proof; |
| 104 | + |
| 105 | + let log_sizes = column_log_sizes_by_tree(prover_output); |
| 106 | + let stark_proof = CairoStarkProofForCircuit::<H>::from_stark_proof( |
| 107 | + stark_proof, |
| 108 | + &[log_sizes[0].as_slice(), log_sizes[1].as_slice()], |
| 109 | + ); |
| 110 | + |
| 111 | + Ok(Self { |
| 112 | + claim: CairoCircuitClaim::from(&prover_output.claim), |
| 113 | + interaction_pow: prover_output.interaction_pow_nonce, |
| 114 | + interaction_claim: CairoCircuitInteractionClaim::from(&prover_output.interaction_claim), |
| 115 | + stark_proof, |
| 116 | + channel_salt: prover_output.channel_salt, |
| 117 | + }) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +/// Builds the felt252 input stream for the Cairo circuit verifier from a live prover |
| 122 | +/// output and a verifier config. |
| 123 | +pub fn prepare_cairo_verifier_input<H: MerkleHasherLifted<Hash = Blake2sHash>>( |
| 124 | + prover_output: &CircuitProverOutput<H>, |
| 125 | + config: &CairoCircuitVerifierConfig, |
| 126 | +) -> Result<Vec<FieldElement>, &'static str> { |
| 127 | + let proof = CairoCircuitProof::<H>::from_prover_output(prover_output)?; |
| 128 | + let mut bytes = Vec::new(); |
| 129 | + CairoSerialize::serialize(&proof, &mut bytes); |
| 130 | + CairoSerialize::serialize(config, &mut bytes); |
| 131 | + Ok(bytes) |
| 132 | +} |
| 133 | + |
| 134 | +/// `[trace_log_sizes, interaction_log_sizes]` from the prover output's components, in |
| 135 | +/// the order in which the prover committed columns. Each component contributes |
| 136 | +/// `n_trace_columns` cells for tree 1 and `n_interaction_columns` for tree 2, all |
| 137 | +/// tagged with that component's `log_size`. |
| 138 | +fn column_log_sizes_by_tree<H: MerkleHasherLifted<Hash = Blake2sHash>>( |
| 139 | + prover_output: &CircuitProverOutput<H>, |
| 140 | +) -> [Vec<u32>; 2] { |
| 141 | + let mut trace = Vec::new(); |
| 142 | + let mut interaction = Vec::new(); |
| 143 | + for component in &prover_output.components { |
| 144 | + let bounds: TreeVec<ColumnVec<u32>> = component.trace_log_degree_bounds(); |
| 145 | + if let Some(t) = bounds.get(1) { |
| 146 | + trace.extend_from_slice(t); |
| 147 | + } |
| 148 | + if let Some(i) = bounds.get(2) { |
| 149 | + interaction.extend_from_slice(i); |
| 150 | + } |
| 151 | + } |
| 152 | + [trace, interaction] |
| 153 | +} |
0 commit comments