diff --git a/rfcs/starknet/fri.html b/rfcs/starknet/fri.html index e6494b4..29ed9f2 100644 --- a/rfcs/starknet/fri.html +++ b/rfcs/starknet/fri.html @@ -395,8 +395,14 @@
The FRI protocol is globally parameterized according to the following variables which from the protocol making use of FRI. For a real-world example, check the Starknet STARK verifier specification.
n_verifier_friendly_commitment_layers
. The number of layers (starting from the bottom) that make use of the circuit-friendly hash.
proof_of_work_n_bits
. The number of bits required for the proof of work. This value should be between 20 and 50.
The protocol as implemented accepts proofs created using different parameters. This allows provers to decide on the trade-offs between proof size, prover time and space complexity, and verifier time and space complexity.
A FRI layer reduction can be configured with the following fields:
table_n_columns
. The number of values committed in each leaf of the Merkle tree. As explained in the overview, each FRI reduction makes predictible related queries to each layer, as such related points are grouped together to reduce multiple related queries to a single one.
A FRI configuration contains the following fields:
log_input_size
. The size of the input layer to FRI (the number of evaluations committed). (TODO: double check)
n_layers
. The number of layers or folding that will occur as part of the FRI proof.
log_expected_input_degree = sum_of_step_sizes + log_last_layer_degree_bound
Commitments of polynomials are done using Merkle trees. The Merkle trees can be configured to hash some parameterized number of the lower layers using a circuit-friendly hash function (Poseidon).
@@ -555,21 +565,38 @@FRI queries are generated once, and then refined through each reduction of the FRI protocol.
-The generation of each FRI query goes through the same process:
+FRI queries are generated once, and then refined through each reduction of the FRI protocol. The number of queries that is randomly generated is based on configuration.
+Each FRI query is composed of the following fields:
index
: the index of the query in the layer's evaluations. Note that this value needs to be shifted before being used as a path in a Merkle tree commitment.y_value
: the evaluation of the layer's polynomial at the queried point.x_inv_value
: the inverse of the point at which the layer's polynomial is evaluated. This value is derived from the index
as explained in the next subsection.Finally, when all FRI queries have been generated, they are sorted in ascending order.
-TODO: refer to the section on the first layer evaluation stuff (external dependency)
-Besides the first layer, each layer verification of a query happens by simply decommitng a layer's queries.
+That is, we should have for each FRI query for the layer the following identity:
+ + +Or in terms of commitment, that the decommitment at path the path behind index
is y_value
.
Each reduction will produce queries to the next layer, which will expect specific evaluations.
-The next queries are derived as:
-The next evaluations expected at the queried layers are derived as:
-Queries between layers verify that the next layer is computed correctly based on the currently layer .
+
+
+TODO: link to section on merkle tree
+
+#### Computing the next layer's queries
+
+Each reduction will produce queries to the next layer, which will expect specific evaluations.
+
+The next queries are derived as:
+
+* index: index / coset_size
+* point: point^2
+* value: FRI formula below
+
+##### FRI formula
+
+The next evaluations expected at the queried layers are derived as:
+
+Queries between layers verify that the next layer is computed correctly based on the currently layer .
The next layer is either the direct next layer or a layer further away if the configuration allows layers to be skipped.
-Specifically, each reduction is allowed to skip 0, 1, 2, or 3 layers (see the MAX_FRI_STEP
constant).
TODO: why MAX_FRI_STEP=3?
-no skipping:
-1 skipping with the generator of the 4-th roots of unity (such that ):
-as you can see, this requires 4 evaluations of p_{i} at , , , .
-2 skippings with the generator of the 8-th roots of unity (such that and ):
-as you can see, this requires 8 evaluations of p_{i} at , , , , , , , .
-3 skippings with the generator of the 16-th roots of unity (such that , , and ):
-as you can see, this requires 16 evaluations of p_{i} at , , , , , , , , , , , , , , , .
-TODO: reconcile with section on the differences with vanilla FRI
-TODO: reconcile with constants used for elements and inverses chosen in subgroups of order (the s)
-The FRI flow is split into three main functions. The reason is that verification of FRI proofs are computationally intensive, and programs might want to verify a FRI proof in multiple calls (for example, if calls have a cost limit). The three main functions are:
-fri_verify_initial
which returns the initial set of queries.fri_verify_step
which takes a set of queries and returns another set of queries.fri_verify_final
which takes the final set of queries and the last layer coefficients and returns the final result.fn proof_of_work_commit(
+ ref channel: Channel, unsent_commitment: ProofOfWorkUnsentCommitment, config: ProofOfWorkConfig
+) {
+ verify_proof_of_work(channel.digest.into(), config.n_bits, unsent_commitment.nonce);
+ channel.read_uint64_from_prover(unsent_commitment.nonce);
+}
+
+fn verify_proof_of_work(digest: u256, n_bits: u8, nonce: u64) {
+ // Compute the initial hash.
+ // Hash(0x0123456789abcded || digest || n_bits )
+ // 8 bytes || 32 bytes || 1 byte
+ // Total of 0x29 = 41 bytes.
+
+ let mut init_hash_data = ArrayTrait::new(); // u8 with blake, u64 with keccak
+ init_hash_data.append_big_endian(MAGIC);
+ init_hash_data.append_big_endian(digest);
+ let init_hash = hash_n_bytes(init_hash_data, n_bits.into(), true).flip_endianness();
+
+ // Compute Hash(init_hash || nonce )
+ // 32 bytes || 8 bytes
+ // Total of 0x28 = 40 bytes.
+
+ let mut hash_data = ArrayTrait::new(); // u8 with blake, u64 with keccak
+ hash_data.append_big_endian(init_hash);
+ hash_data.append_big_endian(nonce);
+ let hash = hash_n_bytes(hash_data, 0, false).flip_endianness();
+
+ let work_limit = pow(2, 128 - n_bits.into());
+ assert(
+ Into::<u128, u256>::into(hash.high) < Into::<felt252, u256>::into(work_limit),
+ 'proof of work failed'
+ )
+}
+
+
+
+### Full Protocol
+
+The FRI flow is split into four main functions. The only reason for doing this is that verification of FRI proofs can be computationally intensive, and users of this specification might want to split the verification of a FRI proof in multiple calls.
+
+The four main functions are:
+
+1. `fri_commit`, which returns the commitment to every layers of the FRI proof.
+1. `fri_verify_initial`, which returns the initial set of queries.
+1. `fri_verify_step`, which takes a set of queries and returns another set of queries.
+1. `fri_verify_final`, which takes the final set of queries and the last layer coefficients and returns the final result.
+
+To retain context, functions pass around two objects:
+
+
+struct FriVerificationStateConstant {
+ // the number of layers in the FRI proof (including skipped layers) (TODO: not the first)
+ n_layers: u32,
+ // commitments to each layer (excluding the first, last, and any skipped layers)
+ commitment: Span<TableCommitment>,
+ // verifier challenges used to produce each (non-skipped) layer polynomial (except the first)
+ eval_points: Span<felt252>,
+ // the number of layers to skip for each reduction
+ step_sizes: Span<felt252>,
+ // the hash of the polynomial of the last layer
+ last_layer_coefficients_hash: felt252,
+}
+struct FriVerificationStateVariable {
+ // a counter representing the current layer being verified
+ iter: u32,
+ // the FRI queries for each (non-skipped) layer
+ queries: Span<FriLayerQuery>,
+}
+
+
+
-We give more detail to each function below.
-fri_verify_initial(queries, fri_commitment, decommitment)
.
gather_first_layer_queries
(TODO: how?) <-- this only happens for the first layer (
FriVerificationStateConstant {
n_layers: (commitment.config.n_layers - 1).try_into().unwrap(),
@@ -677,20 +798,23 @@ Full Protocol
)
}
-fri_verify_step(stateConstant, stateVariable, witness, settings)
.
stateVariable.iter <= stateConstant.n_layers
iter
counterfri_verify_final(stateConstant, stateVariable, last_layer_coefficients)
.
iter == n_layers
)fn fri_verify_final(
stateConstant: FriVerificationStateConstant,
stateVariable: FriVerificationStateVariable,
@@ -710,20 +834,21 @@ Full Protocol
)
}
+
+
+
+## Test Vectors
+
+TKTK
+
+## Security Considerations
+
+* number of queries?
+* size of domain?
+* proof of work stuff?
+
+security bits: `n_queries * log_n_cosets + proof_of_work_bits`
TKTK
-security bits: n_queries * log_n_cosets + proof_of_work_bits