Reference
Configuration
Every configuration value StateSync-GKR reads, with its default, accepted range and the error returned at each limit, plus the cryptographic parameters that are fixed in the source.
A prover's behavior is set by one StateSyncGkrConfig value. Its fields are plain values. Changing smt or layer_strategy changes the compiled circuit identity, its commitment and every proof made under it; batching only affects callers that drive batching::BatchProver. This page lists each value with its default and limits, then the parameters that are fixed in the source.
StateSyncGkrConfig#
use statesync_gkr::StateSyncGkrConfig;
use statesync_gkr::batching::BatchPolicy;
use statesync_gkr::compiler::{LayerStrategy, SmtParams};
fn main() {
let config = StateSyncGkrConfig {
smt: SmtParams {
depth: 24,
leaf_max_fields: 31,
},
layer_strategy: LayerStrategy::A,
batching: BatchPolicy::default(),
};
// The explicit values above are the defaults.
assert_eq!(config.smt, SmtParams::default());
assert_eq!(config.layer_strategy, LayerStrategy::default());
println!("{config:?}");
}| Field | Type | Default | Read by |
|---|---|---|---|
smt | compiler::SmtParams | depth 24, leaf_max_fields 31 | Compiler, witness generation, verifier, circuit commitment, encoding |
layer_strategy | compiler::LayerStrategy | A | Compiler, circuit commitment, encoding |
batching | batching::BatchPolicy | 32, 200 ms, 2 | Only callers that drive batching::BatchProver; the facade's methods do not read it |
Build PreparedSync after you fix the configuration, because it encodes that configuration's circuit. Do not change prover.config while you reuse prepared state; build a new preparation instead.
Tree depth#
SmtParams::depth is the number of tree levels, which is also the number of siblings in every path.
| Property | Value |
|---|---|
| Type | u32 |
| Default | 24, which gives 2^24 slots |
| Minimum | 1. Depth 0 makes compilation fail with CompileError::UnsupportedConfig and the reason "tree depth must be positive" |
| Key range | asset_id must be below 2^depth while depth is below 64 |
| Path length | Exactly depth siblings |
| Measured profiles | Depths 24, 28 and 32 |
| Maximum | None enforced. From depth 64 upward the key-range check is disabled; such depths are untested |
A larger depth widens the circuit and increases witness, proving and verification work. In the measured profiles the layer count stayed at 118 and the encoded membership proof grew about 2.93% from depth 24 to depth 32; Circuits and layers explains why. Each depth compiles to a different circuit with a different commitment and prepared state, so trees of different depths are separate instances.
Leaf bound#
SmtParams::leaf_max_fields is the maximum length of a leaf encoding in field elements, tag included.
| Property | Value |
|---|---|
| Type | u32 |
| Default | 31 (primitives::hash::DEFAULT_LEAF_MAX_FIELDS) |
| Minimum | 10: the tag plus the nine identity limbs of an occupied leaf. Smaller values make compilation fail with CompileError::UnsupportedConfig and the reason "leaf_max_fields must be >= 10 (tag + 9 keccak limbs)" |
| Encoding limit | 65,535. The circuit identity stores the bound as a u16; above this, circuit_identity and encode_sync_result fail with EncodeError::CountOverflow and verify_encoded_sync_op returns false |
| State fields per occupied leaf | At most leaf_max_fields - 10, which is 21 at the default |
| Leaf pre-image width | leaf_max_fields + 1 rounded up to a multiple of 8: 32 lanes, absorbed in four sponge blocks, at the default |
The bound is part of the circuit identity and the circuit commitment, so any change produces a different circuit identity and commitment. A change that alters the pre-image width also changes every leaf digest, and with it every root, so treat the bound like the depth: fixed for the life of a tree. The 118-layer schedule was measured at the default bound; a bound with a different pre-image width changes the leaf hash circuit, and that measurement no longer applies.
Layer strategy#
LayerStrategy selects how tree levels map to GKR layers.
| Variant | Meaning | Result |
|---|---|---|
A | One data-parallel instance per tree level | Default; compiles |
B { merge_k: u32 } | Merge k adjacent tree levels into one layer | CompileError::UnsupportedConfig |
C | Split each level's hash rounds into thinner layers | CompileError::UnsupportedConfig |
The unsupported variants fail with the reason "v0.1 implements strategy A (one data-parallel instance per tree level) only". The inner-proof encoding accepts only the strategy identifier of A.
Batch policy#
batching::BatchPolicy holds the knobs of the in-process deadline scheduler.
| Field | Type | Default | Meaning |
|---|---|---|---|
max_batch_size | usize | 32 | Upper bound on one batch |
deadline | Duration | 200 ms | Budget of one scheduling round |
min_batch_size | usize | 2 | Queue length below which the scheduler takes whatever is queued |
DeadlineScheduler::decide_batch_size(queue_len, remaining) returns 0 for an empty queue and the whole queue when it is shorter than min_batch_size. Otherwise it takes up to max_batch_size jobs, and when remaining is below deadline it scales that size down in proportion, never below min_batch_size.
The defaults are provisional values for this scheduler. The measured serving settings (worker count, batch cap and maximum wait) belong to a separate benchmark caller and are covered in Performance tuning.
Fixed parameters#
These values are compiled in. There is no runtime API to select a different field, hash or transcript.
| Parameter | Value | Where |
|---|---|---|
| Base field | KoalaBear, p = 2^31 - 2^24 + 1 = 2130706433 | primitives::BaseField |
| Challenge field | Degree-four binomial extension of KoalaBear | primitives::ChallengeField, CHALLENGE_EXT_DEGREE |
| Permutation | Poseidon2, width 16, x^3 S-box, 4 + 4 external and 20 internal rounds, Plonky3 0.4.3 constants | primitives::poseidon2_arith |
| Digest width | 8 field elements | primitives::hash::DIGEST_WIDTH |
| Leaf sponge rate | 8 lanes per permutation | primitives::hash::LEAF_SPONGE_RATE |
| Transcript | Poseidon2 duplex sponge, rate 8 | primitives::Transcript |
| Transcript domain tag | statesync-gkr/v0.1 | DOMAIN_TAG_V01 |
| Commitment domain tag | ssgkr/circuit-commitment/v1 | wrap::commitment::CIRCUIT_COMMITMENT_DOMAIN_V1 |
| Round degree bound | 4 | gkr::reduce::LAYER_ROUND_DEGREE |
| Inner-proof versions | Encoding 1, protocol 1, circuit 1, leaf encoding 1 | wrap::encoding |
| Plonky3 crates | Exact =0.4.3 pins | Workspace Cargo.toml |
Changing the cryptographic profile#
The generic traits, HashGadget, WiringOracle, SumcheckOracle and the Field bounds of the sumcheck and circuit types, are extension points. The shipped proving and verification functions, however, use the fixed aliases above. Replacing the field, the hash, the transcript or the compiler is a new implementation and a new verification task:
- proof bytes, circuit commitments and inner-proof compatibility change, and the transcript change requires a new
protocol_version; - the existing proof evidence and formal results do not transfer automatically.
Custom frontends describes what a new frontend over the existing field and hash must supply.
Limits at a glance#
| Limit | Enforced by | Result |
|---|---|---|
depth is 0 | Compilation (prepare, prove_sync_op, verify_sync_op) | CompileError::UnsupportedConfig; verify_sync_op returns false |
leaf_max_fields below 10 | Compilation | CompileError::UnsupportedConfig |
Strategy other than A | Compilation | CompileError::UnsupportedConfig |
Path length differs from depth | MerklePath::compute_root, witness generation, verification | SmtError::PathLengthMismatch; verification returns false |
Key at or above 2^depth, depth below 64 | MerklePath::compute_root, verification | SmtError::KeyOutOfRange; verification returns false. Witness generation does not check it |
Leaf encoding empty or longer than leaf_max_fields | Leaf hashing | HashError::EmptyEncoding or EncodingTooLong, wrapped as SmtError::LeafEncoding |
leaf_max_fields above 65,535 | circuit_identity, encode_sync_result | EncodeError::CountOverflow; verify_encoded_sync_op returns false |
Encoded message longer than u32::MAX bytes | wrap::encoding::encode_inner_proof | EncodeError::MessageTooLong |
Errors describes every error type and how to handle it.