Sync Committee
Overview
Colibri uses Ethereum's Sync Committee mechanism to verify beacon chain data without maintaining the full chain state. This document explains how initialization, synchronization, and verification work in the verifier.
Sync Committee Basics
A Sync Committee consists of 512 validators who are responsible for signing beacon block headers. The committee rotates approximately every 27 hours (256 epochs on mainnet). To verify beacon chain data, Colibri must:
Obtain the current sync committee's public keys
Verify signatures using BLS signature aggregation
Update to new committees as they rotate
Initialization Strategies
Colibri supports two initialization modes: with or without a trusted checkpoint.
Initialization with Trusted Checkpoint
When you provide explicit a trusted_checkpoint:
const client = new Colibri({
trusted_checkpoint: "0x4232db57354ddacec40adda0a502f7732ede19ba0687482a1e15ad20e5e7d1e7"
});Process:
Bootstrap Request: The verifier requests bootstrap data for the trusted checkpoint from the Beacon API:
GET /eth/v1/beacon/light_client/bootstrap/{block_root}Bootstrap Verification: The response contains:
header- The beacon block header matching the trusted checkpointcurrentSyncCommittee- The 512 validator public keys active at that checkpointcurrentSyncCommitteeBranch- Merkle proof linking the sync committee to the beacon state
Merkle Proof Validation: The verifier validates that the sync committee is correctly included in the beacon state by:
Computing
hash_tree_root(currentSyncCommittee)→sync_committee_rootVerifying the Merkle branch against
header.beacon.stateRootThe gindex depends on the fork (54 for Deneb, 86 for Electra)
Storage: The verified sync committee public keys are stored locally, indexed by period number
Security: This approach is maximally secure when you control the source of the trusted checkpoint (e.g., from your own node, a hardware wallet, or a trusted out-of-band source).
Initialization without Trusted Checkpoint
When no trusted_checkpoint are provided, Colibri automatically bootstraps:
const client = new Colibri(); // Automatic secure initializationProcess:
Finality Checkpoint Query: The verifier requests the latest finalized checkpoint using the standard Beacon API endpoint:
GET /eth/v1/beacon/states/head/finality_checkpointsResponse format (compatible with both Beacon APIs and Checkpointz servers):
{ "data": { "finalized": { "epoch": "400504", "root": "0x40ed0506ff33aed37aae7a4678cdc84de1429306ac1138b7aba46dd8490f1a59" } } }Checkpoint Trust Establishment: The verifier sets this root as a trusted checkpoint internally
Bootstrap Flow: Continues with the same bootstrap process as above using the fetched root
Server Flexibility:
Beacon API Compatible: The endpoint works with standard Beacon API nodes
Checkpointz Servers: Also works with dedicated Checkpointz servers
Configuration Choice: Hosts can configure either
checkpointzservers ORbeacon_apisfor checkpoint fetchingAutomatic Fallback: Multiple URLs enable redundancy
Security Considerations:
a Multiple Servers: Colibri queries multiple configured servers for consensus
Trusted Infrastructure Options:
Use your own Beacon node for maximum trust
Use public Checkpointz services (beaconcha.in, ethstaker.cc, etc.)
Use public Beacon APIs as a fallback
Weak Subjectivity: This approach relies on the weak subjectivity assumption - as long as initialization happens within the weak subjectivity period (~2 weeks for Ethereum mainnet), the network's honesty assumption holds
Trade-off: Convenience vs. complete trustlessness - suitable for most applications but not for maximum security scenarios
Sync Committee Period Transitions
Every ~27 hours, the sync committee changes. Colibri handles this automatically using Light Client Updates.
Standard Period Transition
When verification requires a newer sync committee than currently stored:
Light Client Update Request: Fetch updates from the Beacon API:
GET /eth/v1/beacon/light_client/updates?start_period={period}&count={n}Update Structure: Each
LightClientUpdatecontains:attestedHeader- A recent beacon block header signed by the old sync committeenextSyncCommittee- The public keys for the new sync committeenextSyncCommitteeBranch- Merkle proof for the new committeefinalizedHeader- A finalized beacon block headerfinalityBranch- Merkle proof for finalized headersyncAggregate- BLS signature from the old committeesignatureSlot- The slot when the signature was created
Verification Steps:
a. Signature Verification:
- Compute: hash_tree_root(attestedHeader.beacon) → attested_blockhash - Compute: signing_root = hash_tree_root(SigningData(attested_blockhash, domain)) - Verify: BLS_aggregate_verify(old_sync_committee_keys, signing_root, syncAggregate.signature)b. Next Committee Merkle Proof:
- Compute: hash_tree_root(nextSyncCommittee) → next_committee_root - Verify: merkle_proof(nextSyncCommitteeBranch, next_committee_root, gindex=55, attestedHeader.stateRoot)c. Finality Merkle Proof:
- Compute: hash_tree_root(finalizedHeader.beacon) → finalized_header_root - Verify: merkle_proof(finalityBranch, finalized_header_root, gindex=105, attestedHeader.stateRoot)Storage: The new sync committee is stored, indexed by its period (
period = finalized_slot >> (5 + 8)on mainnet)
Edge Case: Delayed Finality
Sometimes finality is not reached at the exact start of a new period due to network conditions. Colibri handles this edge case:
Scenario: Period P+1 sync committee is known, but Period P is requested for verification
Solution:
Check if
highest_period == requested_period + 1If yes, fetch the light client update for
requested_periodExtract
nextSyncCommitteeand compute itshash_tree_rootCompare against the stored
previous_sync_committee_rootfrom Period P+1If the match is confirmed, store the keys for Period P without full signature verification
This optimization allows verification even when blocks are signed by the previous period's committee after a new period has started.
Previous Sync Committee Root Tracking
To support the edge case above, Colibri stores an additional 32 bytes with each sync committee:
Storage Format:
[validator_pubkeys: 512 * 48 bytes] [previous_sync_committee_root: 32 bytes]This previous_sync_committee_root is the hash_tree_root of the previous period's sync committee, allowing backward verification when needed.
Weak Subjectivity Period (WSP) Validation
Ethereum's security model requires periodic checkpoint validation when syncing across long time gaps.
WSP Configuration
Each chain has a configured weak subjectivity period (in epochs):
Mainnet: 3,682 epochs (~2 weeks)
Sepolia: 3,682 epochs
Gnosis: ~1,500 epochs
Chiado: ~1,500 epochs
When WSP Validation Triggers
When fetching sync committees across multiple periods:
epoch_gap = (target_period - stored_period) * 256
if epoch_gap > weak_subjectivity_epochs:
# WSP validation requiredWSP Validation Process
Checkpoint Query: Request block root for the last finalized slot from Checkpointz:
GET /eth/v1/beacon/blocks/{finality_slot}/rootComparison: Compare the Checkpointz-provided root with the locally stored finalized root
Result:
✅ Match: Sync continues normally
❌ Mismatch: All local state is cleared, forcing re-initialization
Purpose: Prevents long-range attacks where an attacker attempts to create an alternative history by controlling old sync committees.
Storage and State Management
Persistent Storage
Colibri stores minimal state using a plugin storage system:
Chain State (
states_{chain_id}):An array of trusted blocks with their periods
Last checkpoint slot for WSP validation
Format:
[c4_trusted_block_t, c4_trusted_block_t, ...]
Sync Committee State (
sync_{chain_id}_{period}):Validator public keys (512 * 48 = 24,576 bytes)
Optional: Deserialized BLS keys (512 * 96 = 49,152 bytes)
Previous sync committee root (32 bytes)
Total: ~24KB per period (or ~49KB with deserialization)
State Lifecycle
Initialization:
No state → Checkpointz query → Bootstrap → Store Period N sync committeeNormal Operation:
Need Period N+k → Fetch k light client updates → Verify chain → Store each periodWSP Exceeded:
Gap too large → Checkpointz validation → Match: continue | Mismatch: clear all stateStorage Cleanup
Colibri automatically manages storage by keeping only the most recent periods (configurable limit). Oldest periods are deleted when the limit is exceeded, but the earliest and latest periods are always preserved.
Security Properties
Trust Assumptions
With Trusted Checkpoint:
✅ Fully trustless after initial checkpoint
✅ No dependency on external services
⚠️ Requires secure out-of-band checkpoint source
Without Trusted Checkpoint:
✅ Convenient automatic initialization
✅ Multiple Checkpointz servers provide redundancy
⚠️ Relies on the weak subjectivity assumption
⚠️ Initial trust in Checkpointz infrastructure
Verification Guarantees
Once initialized, Colibri provides:
Cryptographic proofs for all beacon chain data
BLS signature verification for sync committee attestations
Merkle proofs for state inclusion
Automatic period transitions with full verification
WSP validation prevents long-range attacks
Attack Resistance
Sync Committee Compromise: An attacker controlling 2/3 of a sync committee can sign invalid blocks, but:
Cannot forge signatures for future periods
Limited to their committee's 27-hour window
WSP validation catches attempts to fork history
Long-Range Attacks: Prevented by WSP validation requiring external checkpoint validation
Eclipse Attacks: Multiple Checkpointz servers and Beacon APIs provide redundancy
Implementation Details
SSZ Types
LightClientBootstrap:
Container:
header: LightClientHeader
currentSyncCommittee: SyncCommittee
currentSyncCommitteeBranch: Vector[Bytes32, depth]LightClientUpdate:
Container:
attestedHeader: LightClientHeader
nextSyncCommittee: SyncCommittee
nextSyncCommitteeBranch: Vector[Bytes32, depth]
finalizedHeader: LightClientHeader
finalityBranch: Vector[Bytes32, depth]
syncAggregate: SyncAggregate
signatureSlot: uint64Merkle Branch Depths:
Deneb:
nextSyncCommitteeBranch= 5,currentSyncCommitteeBranch= 5,finalityBranch= 6Electra:
nextSyncCommitteeBranch= 6,currentSyncCommitteeBranch= 6,finalityBranch= 7
Period Calculation
// Mainnet configuration:
// slots_per_epoch = 32 (5 bits)
// epochs_per_period = 256 (8 bits)
uint32_t period = slot >> (5 + 8); // slot / (32 * 256)BLS Signature Aggregation
Colibri uses BLS signature aggregation to efficiently verify that 2/3 of the sync committee signed a block:
Extract participating validator indices from
syncAggregate.syncCommitteeBitsAggregate public keys of participating validators
Compute the signing root with domain separation
Verify the aggregated signature against the aggregated public key
Further Reading
Last updated