In this document we specify the STARK verifier used in Starknet.
draft
In this section we give a brief overview of the Starknet STARK protocol. While the protocol used is designed to verify Cairo programs, we provide an agnostic specification. The instantiation of this protocol with Cairo should be the object of a different specification.
Before we delve into the details, let's look at the protocol from a high-level protocol diagram point of view. The Starknet STARK protocol is divided in three main phases:
We illustrate the flow in the following diagram:
In the next sections we review the different phases.
But first, we quickly remind the reader that the Starknet STARK protocol allows a prover to convince a verifier that an AIR (Algebraic Intermediate Representation) arithmetization is satisfied by their witness. This is generally augmented to also include a public input, usually via a public memory extension.
AIR is essentially two things:
The indexing of the table is chosen as the elements of the smallest subgroup of power that can index the table.
Furthermore, the columns of a table can be grouped, which allows the prover to fill the table group by group, using challenges from the verifier in-between. This is useful in order to perform an interactive arithmetization where parts of the encoded circuit need verifier randomness to be computed.
We give the example of two "original" columns and one "interaction" column, indexed using the multiplicative subgroup of the 16-th roots of unity:
The first phase of the Starknet STARK protocol is to iteratively construct the trace tables (what we previously called interactive arithmetization). The prover sends commitments to parts of the table, and receives verifier challenges in between.
The role of the verifier is now to verify constraints of the form of polynomials on the trace column polynomials, applied on a domain (a list of all the indexes on which the constraint applies).
As with our example above, we can imagine a list of constraints that need to vanish on a list of associated domains described by their domain polynomials .
By definition, this can be reduced to checking that you can write each as for some quotient polynomial of degree .
While protocols based on polynomial commitments like KZG would commit to the quotient polynomial and then prove the relation at a random point (using Schwartz-Zippel), the Starknet STARK protocol uses a different approach: it uses a FRI check to prove that the commitment to the evaluations of correctly represents a polynomial of low degree.
As such, the role of the verifier is to verify that all the quotient polynomials associated with all the constraints exist and are of low-degree.
TODO: define low-degree better
As we want to avoid having to go through many FRI checks, the verifier sends a challenge which the prover can use to aggregate all of the constraint quotient polynomials into a composition polynomial .
This composition polynomial is quite big, so the prover provides a commitment to chunks or columns of the composition polynomials, interpreting as .
Finally, to allow the verifier to check that has correctly been committed, Schwartz-Zippel is used with a random verifier challenge called the "oods point". Specifically, the verifier evaluates the following and check that they match:
Of course, the verifier cannot evaluate both sides without the help of the prover! The left-hand side involves evaluations of the trace polynomials at the oods point (and potentially shifted oods points), and the right-hand side involves evaluations of the composition column polynomials at the oods point as well.
As such, the prover sends the needed evaluations to the verifier so that the verifier can perform the check. (These evaluations are often referred to as the "mask" values.)
Notice that this "oods check" cannot happen in the domain used to index the trace polynomials. This is because the left-hand side involves divisions by domain polynomials , which might lead to divisions by zero.
This is why the oods point is called "out-of-domain sampling". Although nothing special is done when sampling this point, but the probability that it ends up in the trace domain is very low.
TODO: explain what parts does the term "DEEP" refer to in this protocol.
The verifier now has to:
TODO: the second point also should have the effect of proving that the commitments to the trace column polynomials are correct (as they will also act as FRI checks)
In order to avoid running multiple instances of the FRI protocol, the FRI Aggregation technique is used as described in the Aggregating Multiple FRI Proofs section of the Starknet FRI Verifier specification. The verifier sends a challenge called oods_alpha
which is used to aggregate all of the first layer of the previously discussed FRI proofs.
Finally, the FRI protocol is run as described in the Starknet FRI Verifier specification.
In this section we list all of the dependencies and interfaces this standard relies on.
While this specification was written with Cairo in mind, it should be usable with any AIR arithmetization that can be verified using the STARK protocol.
A protocol that wants to use this specification should provide the following:
interactive arithmetization. A description of the interactive arithmetization step, which should include in what order the different tables are committed and what verifier challenges are sent in-between.
eval_composition_polynomial
. A function that takes all of the commitments, all of the evaluations, and a number of Merkle tree witnesses sent by the prover and produces an evaluation of the composition polynomial at the oods point. (This evaluation will depend heavily on the number of trace columns and the constraints of the given AIR arithmetization.) The function is expected to verify any decommitment (via the Merkle tree witnesses) that it uses.
eval_oods_polynomial
. A function that takes all of the commitments, all of the evaluations, and a number of Merkle tree witnesses sent by the prover and produces a list of evaluations of the first layer polynomial of the FRI check at a list of queried points. The function is expected to verify any decommitment (via the Merkle tree witnesses) that it uses.
We refer to the Merkle Tree Polynomial Commitments specification on how to verify decommitments.
We use the following constants throughout the protocol.
STARKNET_PRIME = 3618502788666131213697322783095070105623107215331596699973092056135872020481
. The Starknet prime ().
FIELD_GENERATOR = 3
. The generator for the main multiplicative subgroup of the Starknet field. This is also used as coset factor to produce the coset used in the first layer's evaluation.
See the Channel specification for more details.
See the Starknet FRI Verifier specification.
Specifically, we expose the following functions:
fri_commit
fri_verify_initial
fri_verify_step
fri_verify_final
as well as the two objects FriVerificationStateConstant
, FriVerificationStateVariable
defined in that specification.
struct StarkConfig {
traces: TracesConfig,
composition: TableCommitmentConfig,
fri: FriConfig,
proof_of_work: ProofOfWorkConfig,
// Log2 of the trace domain size.
log_trace_domain_size: felt252,
// Number of queries to the last component, FRI.
n_queries: felt252,
// Log2 of the number of cosets composing the evaluation domain, where the coset size is the
// trace length.
log_n_cosets: felt252,
// Number of layers that use a verifier friendly hash in each commitment.
n_verifier_friendly_commitment_layers: felt252,
}
To validate:
log_trace_domain_size
, and there is cosets, then the evaluation domain size is expected to be (TODO: explain why we talk about cosets here)The verifier accepts the following proof as argument:
struct StarkProof {
config: StarkConfig,
public_input: PublicInput,
unsent_commitment: StarkUnsentCommitment,
witness: StarkWitness,
}
struct StarkUnsentCommitment {
traces: TracesUnsentCommitment,
composition: felt252,
// n_oods_values elements. The i-th value is the evaluation of the i-th mask item polynomial at
// the OODS point, where the mask item polynomial is the interpolation polynomial of the
// corresponding column shifted by the corresponding row_offset.
oods_values: Span<felt252>,
fri: FriUnsentCommitment,
proof_of_work: ProofOfWorkUnsentCommitment,
}
We assume that the public input is instantiated and verified by the parent protocol, and thus is out of scope of this standard.
Commitments to all the polynomials, before the FRI protocol, are done on evaluations of polynomials in the evaluation domain as defined in the Domains and Commitments section of the Starknet FRI Verifier specification.
Commitments to all the polynomials, before the FRI protocol, are done using table commitments as described in the Table Commitments section of the Merkle Tree Polynomial Commitments specification.
The goal of the STARK commit is to process all of the commitments produced by the prover during the protocol (including the FRI commitments), as well as produce the verifier challenges.
interaction_after_composition
).eval_composition_polynomial
function defined in the AIR Arithmetization Dependency section.fri_commit
.The goal of STARK verify is to verify evaluation queries (by checking that evaluations exist in the committed polynomials) and the FRI queries (by running the FRI verification).
To do this, we simply call the fri_verify_initial
function contained in the FRI specification and give it the values computed by eval_oods_polynomial
(as defined in the AIR Arithmetization Dependency section) as first evaluations associated with the queried points. It should return two FRI objects.
The protocol is split into 3 core functions:
verify_initial
as defined below.verify_step
is a wrapper around fri_verify_step
(see the FRI section).verify_final
is a wrapper around fri_verify_final
(see the FRI section).The verify initial function is defined as:
get_public_input_hash(public_input, cfg.n_verifier_friendly_commitment_layers, settings)
(TODO: define external function for that).One can successively call them in the following order to verify a proof:
verify_initial
on the proof and return:FriVerificationStateConstant
objectFriVerificationStateVariable
objectlast_layer_coefficients
n_layers
of them according to the StateConstant returned) and pass the FriVerificationStateVariable in between each callsn_layers + 1