Bridge Proof-State Sentinel Separation and Default-Value Discipline

  • Name: bridge proof-state sentinel separation and default-value discipline
  • Category: bridge reference / verifier-state design note / initialization-safety checklist / default-value hardening
  • Summary: Bridge verifiers should never let uninitialized, unproven, legacy default, or bootstrap placeholder alias to acceptable proof state. Healthy designs keep runtime proof admission, initialization constants, default mapping values, and execution eligibility in disjoint domains, then force every bridge message to cross those domains explicitly. This note exists to preserve the positive design target behind Nomad and adjacent verifier-state failures: the bridge should not be able to turn nothing has been proven yet into this message may release custody just because zero, empty, or legacy state was treated as convenient shorthand.
  • Why this note exists:
    • bridge-verification-failure preserves the broad negative family where a bridge accepts forged bridge truth.
    • nomad-bridge-invalid-proof-acceptance-2022-08-01 is the canonical case where default storage value and acceptable committed root collapsed into the same runtime meaning.
    • What deserves its own positive-design note is the narrower verifier-state lesson: how bridge code should represent not proven, proven, acceptable root, legacy migration state, and bootstrap configuration so that one omitted write or one zero-valued placeholder cannot authorize withdrawals.

The core design rule

No bridge release path should be reachable through a state value that can also arise from absence, default initialization, legacy migration residue, or zero-filled storage.

A healthy bridge verifier should make all of the following independently legible:

  • no proof has been recorded for this message
  • a proof was recorded but is not yet acceptable for execution
  • a root or header is configured as trusted bootstrap state
  • a message is proven against that trusted state
  • a message is actually executable right now

If two of those meanings can share the same runtime value, the bridge is already carrying counterfeit-release risk.

Three verifier-state promotions healthy bridges should not collapse

  1. Some verifier-shaped state exists
    • A mapping slot, root field, status word, timestamp, or proof record-shaped value is present somewhere in storage.
    • This is only the weakest fact: some bytes or defaults can be read.
  2. That state counts as canonical proof existence for this exact message
    • The harder question is whether the bridge has actually crossed from storage has a value into this message was deliberately proven under one exact trusted object.
    • Nomad collapsed this boundary: a missing message entry inherited a default bytes32(0) root, and the verifier treated that default-shaped value as if it were a recorded proof reference.
  3. That accepted proof state deserves custody-moving execution authority right now
    • Even an explicitly recorded proof should still need separate checks for trust-anchor acceptance, freshness, replay status, challenge-window state, and route-local execution eligibility.
    • Bridges fail catastrophically when they flatten proof record exists and release may execute now into the same predicate.

Healthy review gets materially better once teams stop calling all three steps message was proven.

What this note means by sentinel separation

  • Sentinel means a value used to signal a special state such as unset, none, not proven yet, legacy, or bootstrap.
  • Default-value discipline means treating language and storage defaults — 0, bytes32(0), empty arrays, zero enum discriminants, false booleans, missing map entries — as adversarial inputs unless proven otherwise.
  • Proof-state separation means the bridge cannot infer acceptable for execution from raw stored bytes alone without first crossing explicit state checks.
  • The analytical habit is simple: if the verifier would behave differently when an absent entry and a real proof artifact are confused, those states must not share representation.

The five separations healthy bridge verifiers should preserve

1. Uninitialized state versus proven state

  • Message has never been proven must not serialize to the same runtime object as message was proven under root X.
  • Mapping absence should stay semantically empty, not double as a root, status flag, or hash placeholder that can later satisfy execution checks.
  • If the bridge wants proof state by root, it should usually store status + root or exists + root, not only root and hope zero means the right thing forever.

2. Bootstrap trust versus runtime proof admission

  • Initialization often needs a trusted committed root, genesis header, checkpoint, or signer set.
  • That bootstrap object should not live in the same acceptance lane as per-message runtime proof state unless the code can still distinguish bridge starts with trusted anchor A from this specific message proved inclusion under A.
  • Healthy code keeps trusted root inventory separate from message proof inventory and crosses the boundary explicitly.

3. Stored artifact versus execution eligibility

  • Even when a message stores a real root or proof reference, execution should depend on explicit predicates such as exists, not spent, challenge window elapsed, root currently trusted, and message leaf bound to that root.
  • A raw equality or acceptance check over one field is usually too weak if that field can be stale, default, or migration-shaped.
  • The bridge should ask is this exact message executable now?, not merely does one field look acceptable?

4. Migration compatibility versus current safety invariants

  • Upgrades often preserve storage layouts, legacy sentinels, or prior root semantics for compatibility.
  • That is exactly where dangerous aliasing appears: old code interpreted zero as not proven; new code interprets the same slot as root pointer or accepted root.
  • A safe migration proves that old-state values cannot accidentally satisfy new execution predicates.

5. Acceptable root inventory versus acceptable message inventory

  • A bridge may quite reasonably track trusted roots in one table and message -> proven root in another.
  • The dangerous bug appears when a missing message entry can still inherit a root from the trusted-root table because both maps use the same sentinel value or the execution path forgets to prove message entry exists first.
  • Healthy code forces the verifier to prove both: this message has a recorded root and that recorded root is currently acceptable.

What sound design looks like

  • A healthy bridge keeps three different claims separate all the way through the verifier: some storage value exists, this exact message has canonical proof existence, and that accepted proof state is executable bridge truth right now. If those three promotions collapse into one convenience check, untouched storage and live custody authority are already too close together.

1. Explicit proof-state encoding

  • Represent proof state with explicit enums, booleans, or structured records instead of overloading one raw field.
  • Good shapes include enum { None, Proven, Executed }, or { exists, root, timestamp, spent }, rather than bytes32 root alone.
  • The target property is that absence cannot impersonate proof, and that recorded proof still cannot impersonate executable authority without crossing separate gates.

2. Separate stores for trusted checkpoints and message proofs

  • Trusted roots, updater checkpoints, light-client headers, and guardian-set metadata should be stored separately from message-level proof records.
  • The execution path should bridge those stores deliberately: lookup message proof → assert proof exists → lookup referenced trusted root → assert root acceptable now.
  • If a missing message entry can skip the first step, the bridge is already half-broken.

3. Non-default executable states

  • Executable proof states should require writes that cannot be synthesized by untouched storage.
  • Domain-separated hashes, explicit nonzero discriminants, and impossible-by-default state tuples are healthier than zero unless something wrote a real value.
  • If the happy path is observationally identical to untouched storage, auditing and monitoring both get weaker.

4. Migration assertions and shadow checks

  • Upgrades should assert invariants over live storage before enabling new verifier semantics.
  • Examples: no trusted root equals sentinel, no legacy entry can decode as executable, message-proof store has explicit existence bits, new acceptance predicate rejects untouched slots.
  • The safest migrations shadow-validate old and new semantics on production snapshots before flipping execution rights.

5. Fail-closed execution predicates

  • process, execute, mint, or withdraw paths should fail closed when any part of the proof record is missing, stale, ambiguous, or default-shaped.
  • The bridge should prefer an explicit revert on ambiguous proof state to any attempt at graceful interpretation.
  • Bridge liveness is recoverable; counterfeit release is not.

Negative exemplar cluster: what no sentinel separation looks like

  • Nomad — the clean canonical case: messages[_messageHash] defaulted to bytes32(0) for unproven messages, confirmAt[bytes32(0)] was initialized as acceptable, and the execution path asked only whether that root was acceptable.
  • bridge-verification-failure preserves the broader family where this kind of state aliasing becomes counterfeit bridge truth rather than a mere storage bug.
  • The recurring lesson is not zero is bad. It is special states must not inherit authority from storage defaults or bootstrap placeholders.

Red flags that usually mean the verifier is one refactor away from a Nomad-style failure

  • Proof status represented only as bytes32 root or uint256 timestamp with no explicit existence bit
  • Initializers that pre-approve roots, headers, or checkpoints without asserting they cannot equal sentinel values
  • Execution code that checks acceptableRoot(messageRoot) before checking whether messageRoot was ever recorded for this message
  • Upgrades that change the semantic meaning of an existing storage slot without a full-state migration assertion
  • Legacy compatibility comments like zero means old-style proven or empty value falls back to committed root
  • Tests that cover valid proofs and invalid proofs but not untouched storage, zero-filled slots, or historical state carried across upgrades
  • Monitoring that watches accepted roots but not the impossible condition missing message entry still passes executable predicate

Design questions worth asking first

  • Which values can appear in untouched storage for each verifier map, and what would execution do if it saw them right now?
  • Does the bridge represent message proven explicitly, or does it infer proof existence from one field that can also be empty or legacy-shaped?
  • Are trusted roots, checkpoints, or bootstrap anchors stored separately from per-message proof records?
  • Could an initializer, migration, or rollback ever make a sentinel value look like an acceptable root or header?
  • Does the execution predicate require both message proof exists and referenced proof artifact is acceptable, or only the latter?
  • What invariant proves that no storage value reachable without a successful proof write can authorize custody movement?
  • During upgrades, who checks that old-state encodings cannot satisfy new proof semantics accidentally?
  • Which tests and runtime alerts would catch untouched slot became executable before attackers do?

Failure-prevention checklist

  • Encode message proof existence explicitly. Make slot has some value, message is actually proven, and message is executable now separate states; do not infer proven from raw root, hash, timestamp, or status values that can arise from untouched storage.
  • Keep trusted-root inventory separate from message-proof inventory, and force execution to prove membership in both lanes independently.
  • Reserve impossible-by-default executable states: nonzero discriminants, structured records, or domain-separated tags that untouched storage cannot imitate.
  • Assert during initialization and upgrade that bootstrap roots, legacy values, and sentinel constants are disjoint from runtime proof-state sentinels.
  • Review migrations against production snapshots, not only local fixtures, before enabling new verifier semantics.
  • Test untouched storage, zero values, stale entries, rolled-back upgrades, partially initialized replicas, and empty mappings as hostile inputs.
  • Require process / execute paths to fail closed on absent proof records, ambiguous status, or default-valued artifacts even if a related trusted root is present elsewhere.
  • Monitor for impossible state combinations such as message entry absent but execution predicate true, trusted root equals sentinel, or sudden execution success without preceding proof-write events.
  • Document verifier-state invariants in the codebase and incident runbooks so future refactors do not reintroduce aliasing through convenience shortcuts.
  • Treat storage default semantics as part of the custody boundary. If auditors cannot explain how absence, legacy state, and executable proof differ, the bridge is not ready.

Audit prompts

  • At which promotion boundary does each verifier check operate: some verifier-shaped state exists, this exact message is proven, or this message is executable right now?
  • For every verifier mapping, what concrete value does an untouched slot return, and could any execution predicate treat that as meaningful proof state?
  • Are there any code paths where acceptable root, known root, or trusted checkpoint is checked before message proof exists?
  • Does initialization or migration ever write trust anchors that can collide with sentinel values, especially 0, bytes32(0), or default enum variants?
  • Can old storage written under prior verifier semantics decode into executable state after an upgrade?
  • Which fields must be non-default before a message can execute, and are those constraints enforced structurally or only by convention?
  • If the bridge stores message -> root, what proves that a missing message cannot inherit authority from the root-acceptance table?
  • Are challenge-window timestamps, replay markers, or spent flags also vulnerable to default-value aliasing in the same flow?
  • Do tests and monitors cover absence pretending to be proof, or only malformed proofs pretending to be proof?
  • Can any emergency recovery or manual repair script accidentally set verifier storage to values that the execution path interprets as already acceptable?
  • What single invariant would you show a reviewer to prove that untouched storage can never release custody?

Lessons that should already be standard

  • Some verifier-shaped state exists, that state counts as canonical proof existence, and that accepted proof state may release custody now are three different claims.
  • Default storage is protocol state. If its semantics are not intentionally designed, the bridge is outsourcing custody policy to language defaults.
  • Bootstrap trust and runtime proof trust are different things. Conflating them is how initialization data becomes withdrawal authority.
  • Explicit existence beats clever sentinel reuse. Storage efficiency is not worth ambiguous authenticity semantics.
  • Upgrade compatibility is not safety unless old values are proven unable to satisfy new execution predicates.
  • A verifier should ask was this message proven? before it asks is the referenced root acceptable?
  • If an untouched slot can look one branch away from executable, the bridge is living on refactor luck rather than design discipline.

How this note complements the exploit corpus

  • Start with bridge-verification-failure when the sharper question is how did forged bridge truth cross the destination-side verifier at all?
  • Use this note when the sharper design question becomes how should a healthy bridge represent proof state so default, bootstrap, and legacy values cannot impersonate execution authority?
  • Pair with bridge-truth-security-object when the next question is what exact source-side object the bridge should authenticate once state representation is sound.
  • Pair with bridge-bounded-release-design when the next question is how to keep one verifier-state mistake from turning into uncapped custody loss.
  • Keep nomad-bridge-invalid-proof-acceptance-2022-08-01 as the canonical negative case study.

Internal linkages

Control surface