Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
use std::ops::Range;
|
|
|
|
|
|
2023-03-31 18:12:38 -04:00
|
|
|
use hashbrown::HashMap;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use itertools::Itertools;
|
|
|
|
|
use plonky2::field::extension::Extendable;
|
2023-01-15 00:06:08 -08:00
|
|
|
use plonky2::fri::FriParams;
|
2023-01-03 15:46:59 -08:00
|
|
|
use plonky2::gates::noop::NoopGate;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use plonky2::hash::hash_types::RichField;
|
2022-11-22 08:48:48 -05:00
|
|
|
use plonky2::hash::hashing::HashConfig;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use plonky2::iop::challenger::RecursiveChallenger;
|
2023-01-03 15:46:59 -08:00
|
|
|
use plonky2::iop::target::{BoolTarget, Target};
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
|
|
|
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
2023-01-15 00:06:08 -08:00
|
|
|
use plonky2::plonk::circuit_data::{
|
|
|
|
|
CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget,
|
|
|
|
|
};
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher};
|
|
|
|
|
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
2023-01-03 15:46:59 -08:00
|
|
|
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
|
2023-03-31 18:12:38 -04:00
|
|
|
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
use plonky2::util::timing::TimingTree;
|
2023-01-03 15:46:59 -08:00
|
|
|
use plonky2_util::log2_ceil;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
|
|
|
|
|
use crate::all_stark::{all_cross_table_lookups, AllStark, Table, NUM_TABLES};
|
|
|
|
|
use crate::config::StarkConfig;
|
|
|
|
|
use crate::cpu::cpu_stark::CpuStark;
|
|
|
|
|
use crate::cross_table_lookup::{verify_cross_table_lookups_circuit, CrossTableLookup};
|
|
|
|
|
use crate::generation::GenerationInputs;
|
|
|
|
|
use crate::keccak::keccak_stark::KeccakStark;
|
|
|
|
|
use crate::keccak_sponge::keccak_sponge_stark::KeccakSpongeStark;
|
|
|
|
|
use crate::logic::LogicStark;
|
|
|
|
|
use crate::memory::memory_stark::MemoryStark;
|
|
|
|
|
use crate::permutation::{get_grand_product_challenge_set_target, GrandProductChallengeSet};
|
|
|
|
|
use crate::proof::StarkProofWithMetadata;
|
|
|
|
|
use crate::prover::prove;
|
|
|
|
|
use crate::recursive_verifier::{
|
|
|
|
|
add_common_recursion_gates, recursive_stark_circuit, PlonkWrapperCircuit, PublicInputs,
|
|
|
|
|
StarkWrapperCircuit,
|
|
|
|
|
};
|
|
|
|
|
use crate::stark::Stark;
|
|
|
|
|
|
|
|
|
|
/// The recursion threshold. We end a chain of recursive proofs once we reach this size.
|
|
|
|
|
const THRESHOLD_DEGREE_BITS: usize = 13;
|
|
|
|
|
|
|
|
|
|
/// Contains all recursive circuits used in the system. For each STARK and each initial
|
|
|
|
|
/// `degree_bits`, this contains a chain of recursive circuits for shrinking that STARK from
|
|
|
|
|
/// `degree_bits` to a constant `THRESHOLD_DEGREE_BITS`. It also contains a special root circuit
|
|
|
|
|
/// for combining each STARK's shrunk wrapper proof into a single proof.
|
2023-04-01 09:34:13 -04:00
|
|
|
pub struct AllRecursiveCircuits<F, C, const D: usize>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
|
|
|
|
[(); C::HCO::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
2023-01-03 15:46:59 -08:00
|
|
|
/// The EVM root circuit, which aggregates the (shrunk) per-table recursive proofs.
|
2023-04-01 09:34:13 -04:00
|
|
|
pub root: RootCircuitData<F, C, D>,
|
|
|
|
|
pub aggregation: AggregationCircuitData<F, C, D>,
|
2023-01-15 00:06:08 -08:00
|
|
|
/// The block circuit, which verifies an aggregation root proof and a previous block proof.
|
2023-04-01 09:34:13 -04:00
|
|
|
pub block: BlockCircuitData<F, C, D>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
/// Holds chains of circuits for each table and for each initial `degree_bits`.
|
2023-04-01 09:34:13 -04:00
|
|
|
by_table: [RecursiveCircuitsForTable<F, C, D>; NUM_TABLES],
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-03 15:46:59 -08:00
|
|
|
/// Data for the EVM root circuit, which is used to combine each STARK's shrunk wrapper proof
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
/// into a single proof.
|
2023-04-01 09:34:13 -04:00
|
|
|
pub struct RootCircuitData<F, C, const D: usize>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
2023-04-01 09:34:13 -04:00
|
|
|
circuit: CircuitData<F, C, D>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
proof_with_pis: [ProofWithPublicInputsTarget<D>; NUM_TABLES],
|
|
|
|
|
/// For each table, various inner circuits may be used depending on the initial table size.
|
|
|
|
|
/// This target holds the index of the circuit (within `final_circuits()`) that was used.
|
|
|
|
|
index_verifier_data: [Target; NUM_TABLES],
|
2023-01-03 15:46:59 -08:00
|
|
|
/// Public inputs used for cyclic verification. These aren't actually used for EVM root
|
|
|
|
|
/// proofs; the circuit has them just to match the structure of aggregation proofs.
|
|
|
|
|
cyclic_vk: VerifierCircuitTarget,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Data for the aggregation circuit, which is used to compress two proofs into one. Each inner
|
|
|
|
|
/// proof can be either an EVM root proof or another aggregation proof.
|
2023-04-01 09:34:13 -04:00
|
|
|
pub struct AggregationCircuitData<F, C, const D: usize>
|
2023-01-03 15:46:59 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
2023-01-03 15:46:59 -08:00
|
|
|
{
|
2023-04-01 09:34:13 -04:00
|
|
|
circuit: CircuitData<F, C, D>,
|
2023-01-03 15:46:59 -08:00
|
|
|
lhs: AggregationChildTarget<D>,
|
|
|
|
|
rhs: AggregationChildTarget<D>,
|
|
|
|
|
cyclic_vk: VerifierCircuitTarget,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct AggregationChildTarget<const D: usize> {
|
|
|
|
|
is_agg: BoolTarget,
|
|
|
|
|
agg_proof: ProofWithPublicInputsTarget<D>,
|
|
|
|
|
evm_proof: ProofWithPublicInputsTarget<D>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
pub struct BlockCircuitData<F, C, const D: usize>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
2023-01-15 00:06:08 -08:00
|
|
|
{
|
2023-04-01 09:34:13 -04:00
|
|
|
circuit: CircuitData<F, C, D>,
|
2023-01-15 00:06:08 -08:00
|
|
|
has_parent_block: BoolTarget,
|
|
|
|
|
parent_block_proof: ProofWithPublicInputsTarget<D>,
|
|
|
|
|
agg_root_proof: ProofWithPublicInputsTarget<D>,
|
|
|
|
|
cyclic_vk: VerifierCircuitTarget,
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
impl<F, C, const D: usize> AllRecursiveCircuits<F, C, D>
|
2023-01-15 00:06:08 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F> + 'static,
|
|
|
|
|
C::Hasher: AlgebraicHasher<F, C::HCO>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
[(); C::Hasher::HASH_SIZE]:,
|
|
|
|
|
[(); CpuStark::<F, D>::COLUMNS]:,
|
|
|
|
|
[(); KeccakStark::<F, D>::COLUMNS]:,
|
|
|
|
|
[(); KeccakSpongeStark::<F, D>::COLUMNS]:,
|
|
|
|
|
[(); LogicStark::<F, D>::COLUMNS]:,
|
|
|
|
|
[(); MemoryStark::<F, D>::COLUMNS]:,
|
2023-04-01 09:34:13 -04:00
|
|
|
[(); C::HCO::WIDTH]:,
|
|
|
|
|
[(); C::HCI::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
|
|
|
|
/// Preprocess all recursive circuits used by the system.
|
|
|
|
|
pub fn new(
|
|
|
|
|
all_stark: &AllStark<F, D>,
|
|
|
|
|
degree_bits_range: Range<usize>,
|
|
|
|
|
stark_config: &StarkConfig,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let cpu = RecursiveCircuitsForTable::new(
|
|
|
|
|
Table::Cpu,
|
|
|
|
|
&all_stark.cpu_stark,
|
|
|
|
|
degree_bits_range.clone(),
|
|
|
|
|
&all_stark.cross_table_lookups,
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
let keccak = RecursiveCircuitsForTable::new(
|
|
|
|
|
Table::Keccak,
|
|
|
|
|
&all_stark.keccak_stark,
|
|
|
|
|
degree_bits_range.clone(),
|
|
|
|
|
&all_stark.cross_table_lookups,
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
let keccak_sponge = RecursiveCircuitsForTable::new(
|
|
|
|
|
Table::KeccakSponge,
|
|
|
|
|
&all_stark.keccak_sponge_stark,
|
|
|
|
|
degree_bits_range.clone(),
|
|
|
|
|
&all_stark.cross_table_lookups,
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
let logic = RecursiveCircuitsForTable::new(
|
|
|
|
|
Table::Logic,
|
|
|
|
|
&all_stark.logic_stark,
|
|
|
|
|
degree_bits_range.clone(),
|
|
|
|
|
&all_stark.cross_table_lookups,
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
let memory = RecursiveCircuitsForTable::new(
|
|
|
|
|
Table::Memory,
|
|
|
|
|
&all_stark.memory_stark,
|
|
|
|
|
degree_bits_range,
|
|
|
|
|
&all_stark.cross_table_lookups,
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let by_table = [cpu, keccak, keccak_sponge, logic, memory];
|
|
|
|
|
let root = Self::create_root_circuit(&by_table, stark_config);
|
2023-01-03 15:46:59 -08:00
|
|
|
let aggregation = Self::create_aggregation_circuit(&root);
|
2023-01-15 00:06:08 -08:00
|
|
|
let block = Self::create_block_circuit(&aggregation);
|
2023-01-03 15:46:59 -08:00
|
|
|
Self {
|
|
|
|
|
root,
|
|
|
|
|
aggregation,
|
2023-01-15 00:06:08 -08:00
|
|
|
block,
|
2023-01-03 15:46:59 -08:00
|
|
|
by_table,
|
|
|
|
|
}
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_root_circuit(
|
2023-04-01 09:34:13 -04:00
|
|
|
by_table: &[RecursiveCircuitsForTable<F, C, D>; NUM_TABLES],
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
stark_config: &StarkConfig,
|
2023-04-01 09:34:13 -04:00
|
|
|
) -> RootCircuitData<F, C, D> {
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
let inner_common_data: [_; NUM_TABLES] =
|
2023-01-30 08:51:33 -08:00
|
|
|
core::array::from_fn(|i| &by_table[i].final_circuits()[0].common);
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
|
|
|
|
|
let mut builder = CircuitBuilder::new(CircuitConfig::standard_recursion_config());
|
|
|
|
|
let recursive_proofs =
|
2023-02-25 08:55:55 -08:00
|
|
|
core::array::from_fn(|i| builder.add_virtual_proof_with_pis(inner_common_data[i]));
|
2023-01-30 08:51:33 -08:00
|
|
|
let pis: [_; NUM_TABLES] = core::array::from_fn(|i| {
|
2023-04-01 09:34:13 -04:00
|
|
|
PublicInputs::<Target, C::HCO>::from_vec(
|
|
|
|
|
&recursive_proofs[i].public_inputs,
|
|
|
|
|
stark_config,
|
|
|
|
|
)
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
});
|
2023-01-30 08:51:33 -08:00
|
|
|
let index_verifier_data = core::array::from_fn(|_i| builder.add_virtual_target());
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
let mut challenger = RecursiveChallenger::<F, C::HCO, C::Hasher, D>::new(&mut builder);
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
for pi in &pis {
|
|
|
|
|
for h in &pi.trace_cap {
|
|
|
|
|
challenger.observe_elements(h);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let ctl_challenges = get_grand_product_challenge_set_target(
|
|
|
|
|
&mut builder,
|
|
|
|
|
&mut challenger,
|
|
|
|
|
stark_config.num_challenges,
|
|
|
|
|
);
|
|
|
|
|
// Check that the correct CTL challenges are used in every proof.
|
|
|
|
|
for pi in &pis {
|
|
|
|
|
for i in 0..stark_config.num_challenges {
|
|
|
|
|
builder.connect(
|
|
|
|
|
ctl_challenges.challenges[i].beta,
|
|
|
|
|
pi.ctl_challenges.challenges[i].beta,
|
|
|
|
|
);
|
|
|
|
|
builder.connect(
|
|
|
|
|
ctl_challenges.challenges[i].gamma,
|
|
|
|
|
pi.ctl_challenges.challenges[i].gamma,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let state = challenger.compact(&mut builder);
|
2023-04-01 09:34:13 -04:00
|
|
|
for k in 0..C::HCO::WIDTH {
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
builder.connect(state[k], pis[0].challenger_state_before[k]);
|
|
|
|
|
}
|
|
|
|
|
// Check that the challenger state is consistent between proofs.
|
|
|
|
|
for i in 1..NUM_TABLES {
|
2023-04-01 09:34:13 -04:00
|
|
|
for k in 0..C::HCO::WIDTH {
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
builder.connect(
|
|
|
|
|
pis[i].challenger_state_before[k],
|
|
|
|
|
pis[i - 1].challenger_state_after[k],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the CTL checks.
|
2023-02-10 21:47:51 -08:00
|
|
|
verify_cross_table_lookups_circuit::<F, D>(
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
&mut builder,
|
|
|
|
|
all_cross_table_lookups(),
|
|
|
|
|
pis.map(|p| p.ctl_zs_last),
|
|
|
|
|
stark_config,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (i, table_circuits) in by_table.iter().enumerate() {
|
|
|
|
|
let final_circuits = table_circuits.final_circuits();
|
|
|
|
|
for final_circuit in &final_circuits {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
&final_circuit.common, inner_common_data[i],
|
|
|
|
|
"common_data mismatch"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let mut possible_vks = final_circuits
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|c| builder.constant_verifier_data(&c.verifier_only))
|
|
|
|
|
.collect_vec();
|
|
|
|
|
// random_access_verifier_data expects a vector whose length is a power of two.
|
|
|
|
|
// To satisfy this, we will just add some duplicates of the first VK.
|
|
|
|
|
while !possible_vks.len().is_power_of_two() {
|
|
|
|
|
possible_vks.push(possible_vks[0].clone());
|
|
|
|
|
}
|
|
|
|
|
let inner_verifier_data =
|
|
|
|
|
builder.random_access_verifier_data(index_verifier_data[i], possible_vks);
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
builder.verify_proof::<C>(
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
&recursive_proofs[i],
|
|
|
|
|
&inner_verifier_data,
|
|
|
|
|
inner_common_data[i],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 15:46:59 -08:00
|
|
|
// We want EVM root proofs to have the exact same structure as aggregation proofs, so we add
|
|
|
|
|
// public inputs for cyclic verification, even though they'll be ignored.
|
|
|
|
|
let cyclic_vk = builder.add_verifier_data_public_inputs();
|
|
|
|
|
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
RootCircuitData {
|
2023-04-01 09:34:13 -04:00
|
|
|
circuit: builder.build::<C>(),
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
proof_with_pis: recursive_proofs,
|
|
|
|
|
index_verifier_data,
|
2023-01-03 15:46:59 -08:00
|
|
|
cyclic_vk,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_aggregation_circuit(
|
2023-04-01 09:34:13 -04:00
|
|
|
root: &RootCircuitData<F, C, D>,
|
|
|
|
|
) -> AggregationCircuitData<F, C, D> {
|
2023-01-03 15:46:59 -08:00
|
|
|
let mut builder = CircuitBuilder::<F, D>::new(root.circuit.common.config.clone());
|
|
|
|
|
let cyclic_vk = builder.add_verifier_data_public_inputs();
|
|
|
|
|
let lhs = Self::add_agg_child(&mut builder, root);
|
|
|
|
|
let rhs = Self::add_agg_child(&mut builder, root);
|
|
|
|
|
|
|
|
|
|
// Pad to match the root circuit's degree.
|
|
|
|
|
while log2_ceil(builder.num_gates()) < root.circuit.common.degree_bits() {
|
|
|
|
|
builder.add_gate(NoopGate, vec![]);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
let circuit = builder.build::<C>();
|
2023-01-03 15:46:59 -08:00
|
|
|
AggregationCircuitData {
|
|
|
|
|
circuit,
|
|
|
|
|
lhs,
|
|
|
|
|
rhs,
|
|
|
|
|
cyclic_vk,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_agg_child(
|
|
|
|
|
builder: &mut CircuitBuilder<F, D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
root: &RootCircuitData<F, C, D>,
|
2023-01-03 15:46:59 -08:00
|
|
|
) -> AggregationChildTarget<D> {
|
|
|
|
|
let common = &root.circuit.common;
|
|
|
|
|
let root_vk = builder.constant_verifier_data(&root.circuit.verifier_only);
|
|
|
|
|
let is_agg = builder.add_virtual_bool_target_safe();
|
2023-02-25 08:55:55 -08:00
|
|
|
let agg_proof = builder.add_virtual_proof_with_pis(common);
|
|
|
|
|
let evm_proof = builder.add_virtual_proof_with_pis(common);
|
2023-01-03 15:46:59 -08:00
|
|
|
builder
|
2023-04-01 09:34:13 -04:00
|
|
|
.conditionally_verify_cyclic_proof::<C>(
|
2023-01-03 15:46:59 -08:00
|
|
|
is_agg, &agg_proof, &evm_proof, &root_vk, common,
|
|
|
|
|
)
|
|
|
|
|
.expect("Failed to build cyclic recursion circuit");
|
|
|
|
|
AggregationChildTarget {
|
|
|
|
|
is_agg,
|
|
|
|
|
agg_proof,
|
|
|
|
|
evm_proof,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
fn create_block_circuit(agg: &AggregationCircuitData<F, C, D>) -> BlockCircuitData<F, C, D> {
|
2023-01-15 00:06:08 -08:00
|
|
|
// The block circuit is similar to the agg circuit; both verify two inner proofs.
|
|
|
|
|
// We need to adjust a few things, but it's easier than making a new CommonCircuitData.
|
|
|
|
|
let expected_common_data = CommonCircuitData {
|
|
|
|
|
fri_params: FriParams {
|
|
|
|
|
degree_bits: 14,
|
|
|
|
|
..agg.circuit.common.fri_params.clone()
|
|
|
|
|
},
|
|
|
|
|
..agg.circuit.common.clone()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut builder = CircuitBuilder::<F, D>::new(CircuitConfig::standard_recursion_config());
|
|
|
|
|
let has_parent_block = builder.add_virtual_bool_target_safe();
|
2023-02-25 08:55:55 -08:00
|
|
|
let parent_block_proof = builder.add_virtual_proof_with_pis(&expected_common_data);
|
|
|
|
|
let agg_root_proof = builder.add_virtual_proof_with_pis(&agg.circuit.common);
|
2023-01-15 00:06:08 -08:00
|
|
|
|
|
|
|
|
let cyclic_vk = builder.add_verifier_data_public_inputs();
|
|
|
|
|
builder
|
2023-04-01 09:34:13 -04:00
|
|
|
.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
2023-01-15 00:06:08 -08:00
|
|
|
has_parent_block,
|
|
|
|
|
&parent_block_proof,
|
|
|
|
|
&expected_common_data,
|
|
|
|
|
)
|
|
|
|
|
.expect("Failed to build cyclic recursion circuit");
|
|
|
|
|
|
|
|
|
|
let agg_verifier_data = builder.constant_verifier_data(&agg.circuit.verifier_only);
|
2023-04-01 09:34:13 -04:00
|
|
|
builder.verify_proof::<C>(&agg_root_proof, &agg_verifier_data, &agg.circuit.common);
|
2023-01-15 00:06:08 -08:00
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
let circuit = builder.build::<C>();
|
2023-01-15 00:06:08 -08:00
|
|
|
BlockCircuitData {
|
|
|
|
|
circuit,
|
|
|
|
|
has_parent_block,
|
|
|
|
|
parent_block_proof,
|
|
|
|
|
agg_root_proof,
|
|
|
|
|
cyclic_vk,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
/// Create a proof for each STARK, then combine them, eventually culminating in a root proof.
|
|
|
|
|
pub fn prove_root(
|
|
|
|
|
&self,
|
|
|
|
|
all_stark: &AllStark<F, D>,
|
|
|
|
|
config: &StarkConfig,
|
|
|
|
|
generation_inputs: GenerationInputs,
|
|
|
|
|
timing: &mut TimingTree,
|
2023-04-01 09:34:13 -04:00
|
|
|
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
|
|
|
|
|
let all_proof = prove::<F, C, D>(all_stark, config, generation_inputs, timing)?;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
let mut root_inputs = PartialWitness::new();
|
2023-01-03 15:46:59 -08:00
|
|
|
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
for table in 0..NUM_TABLES {
|
|
|
|
|
let stark_proof = &all_proof.stark_proofs[table];
|
|
|
|
|
let original_degree_bits = stark_proof.proof.recover_degree_bits(config);
|
|
|
|
|
let table_circuits = &self.by_table[table];
|
|
|
|
|
let shrunk_proof = table_circuits.by_stark_size[&original_degree_bits]
|
|
|
|
|
.shrink(stark_proof, &all_proof.ctl_challenges)?;
|
|
|
|
|
let index_verifier_data = table_circuits
|
|
|
|
|
.by_stark_size
|
|
|
|
|
.keys()
|
|
|
|
|
.position(|&size| size == original_degree_bits)
|
|
|
|
|
.unwrap();
|
|
|
|
|
root_inputs.set_target(
|
|
|
|
|
self.root.index_verifier_data[table],
|
|
|
|
|
F::from_canonical_usize(index_verifier_data),
|
|
|
|
|
);
|
|
|
|
|
root_inputs.set_proof_with_pis_target(&self.root.proof_with_pis[table], &shrunk_proof);
|
|
|
|
|
}
|
2023-01-03 15:46:59 -08:00
|
|
|
|
2023-01-03 17:45:47 -08:00
|
|
|
root_inputs.set_verifier_data_target(
|
|
|
|
|
&self.root.cyclic_vk,
|
|
|
|
|
&self.aggregation.circuit.verifier_only,
|
|
|
|
|
);
|
2023-01-03 15:46:59 -08:00
|
|
|
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
self.root.circuit.prove(root_inputs)
|
|
|
|
|
}
|
2023-01-03 15:46:59 -08:00
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
pub fn verify_root(&self, agg_proof: ProofWithPublicInputs<F, C, D>) -> anyhow::Result<()> {
|
2023-01-04 14:50:15 -08:00
|
|
|
self.root.circuit.verify(agg_proof)
|
2023-01-03 15:46:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn prove_aggregation(
|
|
|
|
|
&self,
|
|
|
|
|
lhs_is_agg: bool,
|
2023-04-01 09:34:13 -04:00
|
|
|
lhs_proof: &ProofWithPublicInputs<F, C, D>,
|
2023-01-03 15:46:59 -08:00
|
|
|
rhs_is_agg: bool,
|
2023-04-01 09:34:13 -04:00
|
|
|
rhs_proof: &ProofWithPublicInputs<F, C, D>,
|
|
|
|
|
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
|
2023-01-03 15:46:59 -08:00
|
|
|
let mut agg_inputs = PartialWitness::new();
|
|
|
|
|
|
|
|
|
|
agg_inputs.set_bool_target(self.aggregation.lhs.is_agg, lhs_is_agg);
|
|
|
|
|
agg_inputs.set_proof_with_pis_target(&self.aggregation.lhs.agg_proof, lhs_proof);
|
|
|
|
|
agg_inputs.set_proof_with_pis_target(&self.aggregation.lhs.evm_proof, lhs_proof);
|
|
|
|
|
|
|
|
|
|
agg_inputs.set_bool_target(self.aggregation.rhs.is_agg, rhs_is_agg);
|
|
|
|
|
agg_inputs.set_proof_with_pis_target(&self.aggregation.rhs.agg_proof, rhs_proof);
|
|
|
|
|
agg_inputs.set_proof_with_pis_target(&self.aggregation.rhs.evm_proof, rhs_proof);
|
|
|
|
|
|
|
|
|
|
agg_inputs.set_verifier_data_target(
|
|
|
|
|
&self.aggregation.cyclic_vk,
|
|
|
|
|
&self.aggregation.circuit.verifier_only,
|
|
|
|
|
);
|
2023-01-04 14:50:15 -08:00
|
|
|
|
2023-01-03 15:46:59 -08:00
|
|
|
self.aggregation.circuit.prove(agg_inputs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn verify_aggregation(
|
|
|
|
|
&self,
|
2023-04-01 09:34:13 -04:00
|
|
|
agg_proof: &ProofWithPublicInputs<F, C, D>,
|
2023-01-03 15:46:59 -08:00
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
self.aggregation.circuit.verify(agg_proof.clone())?;
|
|
|
|
|
check_cyclic_proof_verifier_data(
|
|
|
|
|
agg_proof,
|
|
|
|
|
&self.aggregation.circuit.verifier_only,
|
|
|
|
|
&self.aggregation.circuit.common,
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-01-15 00:06:08 -08:00
|
|
|
|
|
|
|
|
pub fn prove_block(
|
|
|
|
|
&self,
|
2023-04-01 09:34:13 -04:00
|
|
|
opt_parent_block_proof: Option<&ProofWithPublicInputs<F, C, D>>,
|
|
|
|
|
agg_root_proof: &ProofWithPublicInputs<F, C, D>,
|
|
|
|
|
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
|
2023-01-15 00:06:08 -08:00
|
|
|
let mut block_inputs = PartialWitness::new();
|
|
|
|
|
|
|
|
|
|
block_inputs.set_bool_target(
|
|
|
|
|
self.block.has_parent_block,
|
|
|
|
|
opt_parent_block_proof.is_some(),
|
|
|
|
|
);
|
|
|
|
|
if let Some(parent_block_proof) = opt_parent_block_proof {
|
|
|
|
|
block_inputs
|
|
|
|
|
.set_proof_with_pis_target(&self.block.parent_block_proof, parent_block_proof);
|
2023-03-31 18:12:38 -04:00
|
|
|
} else {
|
|
|
|
|
block_inputs.set_proof_with_pis_target(
|
|
|
|
|
&self.block.parent_block_proof,
|
|
|
|
|
&cyclic_base_proof(
|
|
|
|
|
&self.block.circuit.common,
|
|
|
|
|
&self.block.circuit.verifier_only,
|
|
|
|
|
HashMap::new(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2023-01-15 00:06:08 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
block_inputs.set_proof_with_pis_target(&self.block.agg_root_proof, agg_root_proof);
|
|
|
|
|
|
|
|
|
|
block_inputs
|
|
|
|
|
.set_verifier_data_target(&self.block.cyclic_vk, &self.block.circuit.verifier_only);
|
|
|
|
|
|
|
|
|
|
self.block.circuit.prove(block_inputs)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
pub fn verify_block(&self, block_proof: &ProofWithPublicInputs<F, C, D>) -> anyhow::Result<()> {
|
2023-01-15 00:06:08 -08:00
|
|
|
self.block.circuit.verify(block_proof.clone())?;
|
|
|
|
|
check_cyclic_proof_verifier_data(
|
|
|
|
|
block_proof,
|
|
|
|
|
&self.block.circuit.verifier_only,
|
|
|
|
|
&self.block.circuit.common,
|
|
|
|
|
)
|
|
|
|
|
}
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
struct RecursiveCircuitsForTable<F, C, const D: usize>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
|
|
|
|
[(); C::HCO::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
|
|
|
|
/// A map from `log_2(height)` to a chain of shrinking recursion circuits starting at that
|
|
|
|
|
/// height.
|
2023-04-01 09:34:13 -04:00
|
|
|
by_stark_size: BTreeMap<usize, RecursiveCircuitsForTableSize<F, C, D>>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
impl<F, C, const D: usize> RecursiveCircuitsForTable<F, C, D>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
|
|
|
|
C::Hasher: AlgebraicHasher<F, C::HCO>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
[(); C::Hasher::HASH_SIZE]:,
|
2023-04-01 09:34:13 -04:00
|
|
|
[(); C::HCO::WIDTH]:,
|
|
|
|
|
[(); C::HCI::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
|
|
|
|
fn new<S: Stark<F, D>>(
|
|
|
|
|
table: Table,
|
|
|
|
|
stark: &S,
|
|
|
|
|
degree_bits_range: Range<usize>,
|
|
|
|
|
all_ctls: &[CrossTableLookup<F>],
|
|
|
|
|
stark_config: &StarkConfig,
|
|
|
|
|
) -> Self
|
|
|
|
|
where
|
|
|
|
|
[(); S::COLUMNS]:,
|
|
|
|
|
{
|
|
|
|
|
let by_stark_size = degree_bits_range
|
|
|
|
|
.map(|degree_bits| {
|
|
|
|
|
(
|
|
|
|
|
degree_bits,
|
|
|
|
|
RecursiveCircuitsForTableSize::new::<S>(
|
|
|
|
|
table,
|
|
|
|
|
stark,
|
|
|
|
|
degree_bits,
|
|
|
|
|
all_ctls,
|
|
|
|
|
stark_config,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
Self { by_stark_size }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// For each initial `degree_bits`, get the final circuit at the end of that shrinking chain.
|
|
|
|
|
/// Each of these final circuits should have degree `THRESHOLD_DEGREE_BITS`.
|
2023-04-01 09:34:13 -04:00
|
|
|
fn final_circuits(&self) -> Vec<&CircuitData<F, C, D>> {
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
self.by_stark_size
|
|
|
|
|
.values()
|
|
|
|
|
.map(|chain| {
|
|
|
|
|
chain
|
|
|
|
|
.shrinking_wrappers
|
|
|
|
|
.last()
|
|
|
|
|
.map(|wrapper| &wrapper.circuit)
|
|
|
|
|
.unwrap_or(&chain.initial_wrapper.circuit)
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A chain of shrinking wrapper circuits, ending with a final circuit with `degree_bits`
|
|
|
|
|
/// `THRESHOLD_DEGREE_BITS`.
|
2023-04-01 09:34:13 -04:00
|
|
|
struct RecursiveCircuitsForTableSize<F, C, const D: usize>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
|
|
|
|
[(); C::HCO::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
2023-04-01 09:34:13 -04:00
|
|
|
initial_wrapper: StarkWrapperCircuit<F, C, D>,
|
|
|
|
|
shrinking_wrappers: Vec<PlonkWrapperCircuit<F, C, D>>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 09:34:13 -04:00
|
|
|
impl<F, C, const D: usize> RecursiveCircuitsForTableSize<F, C, D>
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
where
|
|
|
|
|
F: RichField + Extendable<D>,
|
2023-04-01 09:34:13 -04:00
|
|
|
C: GenericConfig<D, F = F>,
|
|
|
|
|
C::Hasher: AlgebraicHasher<F, C::HCO>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
[(); C::Hasher::HASH_SIZE]:,
|
2023-04-01 09:34:13 -04:00
|
|
|
[(); C::HCO::WIDTH]:,
|
|
|
|
|
[(); C::HCI::WIDTH]:,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
{
|
|
|
|
|
fn new<S: Stark<F, D>>(
|
|
|
|
|
table: Table,
|
|
|
|
|
stark: &S,
|
|
|
|
|
degree_bits: usize,
|
|
|
|
|
all_ctls: &[CrossTableLookup<F>],
|
|
|
|
|
stark_config: &StarkConfig,
|
|
|
|
|
) -> Self
|
|
|
|
|
where
|
|
|
|
|
[(); S::COLUMNS]:,
|
|
|
|
|
{
|
|
|
|
|
let initial_wrapper = recursive_stark_circuit(
|
|
|
|
|
table,
|
|
|
|
|
stark,
|
|
|
|
|
degree_bits,
|
|
|
|
|
all_ctls,
|
|
|
|
|
stark_config,
|
|
|
|
|
&shrinking_config(),
|
|
|
|
|
THRESHOLD_DEGREE_BITS,
|
|
|
|
|
);
|
|
|
|
|
let mut shrinking_wrappers = vec![];
|
|
|
|
|
|
|
|
|
|
// Shrinking recursion loop.
|
|
|
|
|
loop {
|
|
|
|
|
let last = shrinking_wrappers
|
|
|
|
|
.last()
|
2023-04-01 09:34:13 -04:00
|
|
|
.map(|wrapper: &PlonkWrapperCircuit<F, C, D>| &wrapper.circuit)
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
.unwrap_or(&initial_wrapper.circuit);
|
|
|
|
|
let last_degree_bits = last.common.degree_bits();
|
|
|
|
|
assert!(last_degree_bits >= THRESHOLD_DEGREE_BITS);
|
|
|
|
|
if last_degree_bits == THRESHOLD_DEGREE_BITS {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut builder = CircuitBuilder::new(shrinking_config());
|
2023-02-25 08:55:55 -08:00
|
|
|
let proof_with_pis_target = builder.add_virtual_proof_with_pis(&last.common);
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
let last_vk = builder.constant_verifier_data(&last.verifier_only);
|
2023-04-01 09:34:13 -04:00
|
|
|
builder.verify_proof::<C>(&proof_with_pis_target, &last_vk, &last.common);
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
builder.register_public_inputs(&proof_with_pis_target.public_inputs); // carry PIs forward
|
|
|
|
|
add_common_recursion_gates(&mut builder);
|
2023-04-01 09:34:13 -04:00
|
|
|
let circuit = builder.build::<C>();
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
circuit.common.degree_bits() < last_degree_bits,
|
|
|
|
|
"Couldn't shrink to expected recursion threshold of 2^{}; stalled at 2^{}",
|
|
|
|
|
THRESHOLD_DEGREE_BITS,
|
|
|
|
|
circuit.common.degree_bits()
|
|
|
|
|
);
|
|
|
|
|
shrinking_wrappers.push(PlonkWrapperCircuit {
|
|
|
|
|
circuit,
|
|
|
|
|
proof_with_pis_target,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
initial_wrapper,
|
|
|
|
|
shrinking_wrappers,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn shrink(
|
|
|
|
|
&self,
|
2023-04-01 09:34:13 -04:00
|
|
|
stark_proof_with_metadata: &StarkProofWithMetadata<F, C, D>,
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
ctl_challenges: &GrandProductChallengeSet<F>,
|
2023-04-01 09:34:13 -04:00
|
|
|
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
let mut proof = self
|
|
|
|
|
.initial_wrapper
|
|
|
|
|
.prove(stark_proof_with_metadata, ctl_challenges)?;
|
|
|
|
|
for wrapper_circuit in &self.shrinking_wrappers {
|
|
|
|
|
proof = wrapper_circuit.prove(&proof)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(proof)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 11:23:28 -08:00
|
|
|
/// Our usual recursion threshold is 2^12 gates, but for these shrinking circuits, we use a few more
|
|
|
|
|
/// gates for a constant inner VK and for public inputs. This pushes us over the threshold to 2^13.
|
|
|
|
|
/// As long as we're at 2^13 gates, we might as well use a narrower witness.
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
fn shrinking_config() -> CircuitConfig {
|
|
|
|
|
CircuitConfig {
|
|
|
|
|
num_routed_wires: 40,
|
|
|
|
|
..CircuitConfig::standard_recursion_config()
|
|
|
|
|
}
|
|
|
|
|
}
|