Continuity Chain Validation
Chain validation confirms the receipt sequence is complete, properly ordered, and tamper-free. This page covers the validation algorithm and how to handle anomalies.
What is the validation algorithm?
Chain validation follows a strict sequence. Each check must pass before proceeding.
function validateChain(receipts, chain_head):
if receipts.length == 0:
return FAIL("EMPTY_CHAIN")
// Sort by counter
receipts.sortBy(r => r.counter)
// Check first receipt
if receipts[0].chain.prev_receipt_hash != "0".repeat(64):
return FAIL("INVALID_GENESIS")
prev = null
for receipt in receipts:
// Counter continuity
if prev != null:
if receipt.counter != prev.counter + 1:
return FAIL("COUNTER_GAP", receipt.counter)
// Hash linkage
if prev != null:
if receipt.chain.prev_receipt_hash != prev.chain.this_receipt_hash:
return FAIL("CHAIN_PREV_HASH_MISMATCH", receipt.counter)
// Signature validity
if !verifySignature(receipt):
return FAIL("RECEIPT_SIGNATURE_INVALID", receipt.counter)
// Hash integrity
if !verifyReceiptHash(receipt):
return FAIL("RECEIPT_HASH_MISMATCH", receipt.counter)
prev = receipt
// Chain head verification
last = receipts[receipts.length - 1]
if chain_head.head_receipt_hash != last.chain.this_receipt_hash:
return FAIL("CHAIN_HEAD_MISMATCH")
if chain_head.receipt_count != receipts.length:
return FAIL("RECEIPT_COUNT_MISMATCH")
return PASSHow are gaps detected?
Gaps occur when receipts are missing from the sequence. The validator detects this through counter discontinuity.
Valid chain: [1] → [2] → [3] → [4] → [5]
✓ ✓ ✓ ✓ ✓
Gap detected: [1] → [2] → [4] → [5]
✓ ✓ ✗ COUNTER_GAP(expected: 3, got: 4)
// Gap information includes:
{
"error": "COUNTER_GAP",
"expected_counter": 3,
"received_counter": 4,
"gap_size": 1,
"prev_receipt": "receipts/0002.json",
"next_receipt": "receipts/0004.json"
}What are chain forks?
A fork occurs when two valid chains diverge from a common ancestor. This can happen if the signing key creates conflicting histories.
┌─▶ [3a] → [4a] → [5a] Chain A
│
[1] → [2] ──────────┤
│
└─▶ [3b] → [4b] Chain B
// Both chains are cryptographically valid
// Both have same receipts 1-2
// They diverge at receipt 3
// This indicates a compromised key or malicious boundaryHow are forks detected?
Fork detection requires comparing against external anchors or checkpoint commitments:
- ■Checkpoint comparison: If two bundles claim different chain states at the same checkpoint, one is forked
- ■External anchor mismatch: If chain state differs from anchored commitment, fork detected
- ■Same counter, different hash: Two receipts with same counter but different hashes indicate fork
How do I handle validation failures?
Each failure type has specific remediation:
COUNTER_GAPReceipts missing. Retrieve from source if available. If not, chain custody is broken for that period.
CHAIN_PREV_HASH_MISMATCHReceipt modified or wrong order. Re-fetch receipts. If persists, indicates tampering.
RECEIPT_SIGNATURE_INVALIDReceipt was modified after signing or wrong public key used. Critical security event—investigate immediately.
CHAIN_HEAD_MISMATCHBundle manifest doesn't match actual chain. Bundle may be incomplete or corrupted.
What are ordering guarantees?
The chain provides these ordering guarantees:
- ■Total order: All receipts have a defined position (counter)
- ■Causal order: Each receipt depends on the previous (hash link)
- ■Append-only: Cannot insert in middle without breaking links
- ■No reorder: Cannot swap positions without breaking signatures
Frequently asked questions
Can validation be parallelized?
Signature verification can be parallel. Hash link verification must be sequential (each depends on previous). In practice, signature verification dominates so parallelization helps significantly.
What if validation is too slow?
Use checkpoint verification. Verify from the last trusted checkpoint instead of genesis. Each checkpoint commits to chain state, reducing full-chain verification to segment verification.
How do I validate partial chains?
Partial chains can be validated with caveats. The verifier notes the starting point isn't genesis and emits PASS_WITH_CAVEATS. Useful for incremental verification of ongoing runs.