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:

  1. Obtain the current sync committee's public keys

  2. Verify signatures using BLS signature aggregation

  3. 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:

  1. Bootstrap Request: The verifier requests bootstrap data for the trusted checkpoint from the Beacon API:

    GET /eth/v1/beacon/light_client/bootstrap/{block_root}
  2. Bootstrap Verification: The response contains:

    • header - The beacon block header matching the trusted checkpoint

    • currentSyncCommittee - The 512 validator public keys active at that checkpoint

    • currentSyncCommitteeBranch - Merkle proof linking the sync committee to the beacon state

  3. 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_root

    • Verifying the Merkle branch against header.beacon.stateRoot

    • The gindex depends on the fork (54 for Deneb, 86 for Electra)

  4. 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 initialization

Process:

  1. Finality Checkpoint Query: The verifier requests the latest finalized checkpoint using the standard Beacon API endpoint:

    GET /eth/v1/beacon/states/head/finality_checkpoints

    Response format (compatible with both Beacon APIs and Checkpointz servers):

    {
      "data": {
        "finalized": {
          "epoch": "400504",
          "root": "0x40ed0506ff33aed37aae7a4678cdc84de1429306ac1138b7aba46dd8490f1a59"
        }
      }
    }
  2. Checkpoint Trust Establishment: The verifier sets this root as a trusted checkpoint internally

  3. 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 checkpointz servers OR beacon_apis for checkpoint fetching

  • Automatic 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:

  1. Light Client Update Request: Fetch updates from the Beacon API:

    GET /eth/v1/beacon/light_client/updates?start_period={period}&count={n}
  2. Update Structure: Each LightClientUpdate contains:

    • attestedHeader - A recent beacon block header signed by the old sync committee

    • nextSyncCommittee - The public keys for the new sync committee

    • nextSyncCommitteeBranch - Merkle proof for the new committee

    • finalizedHeader - A finalized beacon block header

    • finalityBranch - Merkle proof for finalized header

    • syncAggregate - BLS signature from the old committee

    • signatureSlot - The slot when the signature was created

  3. 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)
  4. 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:

  1. Check if highest_period == requested_period + 1

  2. If yes, fetch the light client update for requested_period

  3. Extract nextSyncCommittee and compute its hash_tree_root

  4. Compare against the stored previous_sync_committee_root from Period P+1

  5. If 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 required

WSP Validation Process

  1. Checkpoint Query: Request block root for the last finalized slot from Checkpointz:

    GET /eth/v1/beacon/blocks/{finality_slot}/root
  2. Comparison: Compare the Checkpointz-provided root with the locally stored finalized root

  3. 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:

  1. 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, ...]

  2. 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 committee

Normal Operation:

Need Period N+k → Fetch k light client updates → Verify chain → Store each period

WSP Exceeded:

Gap too large → Checkpointz validation → Match: continue | Mismatch: clear all state

Storage 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: uint64

Merkle Branch Depths:

  • Deneb: nextSyncCommitteeBranch = 5, currentSyncCommitteeBranch = 5, finalityBranch = 6

  • Electra: 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:

  1. Extract participating validator indices from syncAggregate.syncCommitteeBits

  2. Aggregate public keys of participating validators

  3. Compute the signing root with domain separation

  4. Verify the aggregated signature against the aggregated public key

Further Reading

Last updated