Merkle Epochs

Merkle Epochs

PoC 2 introduces merkle tree epochs -- a compression layer that groups heartbeats into fixed-size epochs, each summarised by a single 32-byte merkle root.

#Concept

Instead of storing every heartbeat on-chain, heartbeats are grouped into epochs. Each epoch is a merkle tree of heartbeat hashes, yielding a compact root hash. Epochs themselves chain together, forming a verifiable timeline.

diagram
Epoch 0                    Epoch 1                    Epoch 2┌──────────────────┐      ┌──────────────────┐      ┌──────────────────┐│ H0  H1  ... H59  │      │ H60 H61 ... H119 │      │ H120 H121 ...    ││       │          │      │       │          │      │       │          ││   merkle tree    │      │   merkle tree    │      │   merkle tree    ││       │          │      │       │          │      │       │          ││   epoch root     │──────│   epoch root     │──────│   epoch root     │└──────────────────┘      └──────────────────┘      └──────────────────┘                   prevEpochHash           prevEpochHash

With 60 heartbeats per epoch (one per minute), each epoch covers 1 hour of uptime.

#Merkle Tree Construction

Heartbeat hashes are arranged as leaves of a binary merkle tree. The tree is built bottom-up:

text
         Root        /    \      H01     H23     /   \   /   \    H0   H1 H2   H3

Each internal node is the SHA-256 hash of its two children concatenated:

text
parent = SHA-256(left || right)

#Selective Disclosure

The key property of merkle trees is selective disclosure: you can prove a specific heartbeat existed within an epoch without revealing any other heartbeats.

#Example: Proving Heartbeat #37

To prove heartbeat #37 existed in an epoch of 60 heartbeats:

  1. Provide heartbeat #37 (the leaf).
  2. Provide the merkle proof path -- the sibling hashes needed to reconstruct the root.
  3. The verifier hashes heartbeat #37, then combines with each sibling hash up the tree, arriving at the root.
  4. If the computed root matches the published epoch root, the heartbeat is proven.
diagram
            Root ← verifier arrives here           /    \         ...    ...        /          \      ...          H_sibling  ← provided in proof     /   H_sibling       ← provided in proof  /H37 ← start here

The proof path contains O(log n) sibling hashes -- logarithmic in the number of heartbeats per epoch.

#Proof Sizes

Heartbeats per EpochTree DepthProof HashesProof Size
60 (1 hour)66192 bytes
1,440 (1 day)1111352 bytes
43,200 (30 days)1616512 bytes

#Epoch Structure

go
type Epoch struct {    LeaseID       string   // lease block hash    EpochIndex    uint64   // monotonic epoch counter    StartSeq      uint64   // first heartbeat sequence in this epoch    EndSeq        uint64   // last heartbeat sequence in this epoch    MerkleRoot    []byte   // root of heartbeat merkle tree    PrevEpochHash []byte   // SHA-256 of previous epoch (zero for first)    Timestamp     int64    // unix nanos when epoch was sealed    Hash          []byte   // SHA-256 of this epoch's fields}

#Epoch Chaining

Each epoch includes a prevEpochHash field -- the hash of the previous epoch. This creates a chain of epochs analogous to the heartbeat chain itself:

diagram
E0 ← E1 ← E2 ← E3 ← ... ← En

#Properties

Ordering is immutable. You cannot reorder epochs without breaking the chain of prevEpochHash references.

Insertion is impossible. Inserting a new epoch between two existing ones would require recomputing the prevEpochHash of all subsequent epochs.

Deletion is detectable. Removing an epoch creates a gap: the next epoch's prevEpochHash will not match the preceding epoch's hash.

#Verification

#Verify Epoch Chain Integrity

text
for each epoch Ei where i > 0:    assert Ei.prevEpochHash == SHA-256(Ei-1)    assert Ei.startSeq == Ei-1.endSeq + 1    assert Ei.epochIndex == Ei-1.epochIndex + 1

#Verify Heartbeat Membership

text
1. Compute leaf = SHA-256(heartbeat)2. Walk proof path: for each (sibling, direction) in proof:     if direction == left:         current = SHA-256(sibling || current)     else:         current = SHA-256(current || sibling)3. Assert current == epoch.merkleRoot

#Compression Ratio

For a 24-hour lease with 1-minute heartbeats:

LayerDataSize
Raw heartbeats1,440 x ~250 bytes360 KB
Epoch roots24 x 32 bytes768 bytes
Single claim root1 x 32 bytes32 bytes

The epoch layer alone achieves a 480x compression from raw heartbeats. Combined with the claim layer, the total compression reaches 3,681x.