Abstract: most bridge failures are validation-semantics failures

The most dangerous cross-chain bridge bugs rarely come from breaking the cryptographic primitive itself. They come from verifier contracts accepting bytes that look like proofs without proving the right thing: the right source chain, the right bridge contract, the right validator set, the right state root, the right target chain, and the right message type. Signature forgery, Merkle proof fraud, empty witnesses, validator-set timing errors, and non-canonical signature parsing all reduce a bridge from cryptographic security to implementation luck. The central conclusion is simple: a bridge verifier must bind four domains at once. The message must bind to source-chain state. The signature must bind to the correct validator set. The Merkle leaf must bind to the tree domain. The validator update must bind to a time window. If one binding is missing, an attacker may not need to break a hash function or steal a private key to make a fraudulent withdrawal look valid on the target chain.

Scope: defensive forensics, not exploitation

This article discusses bridge verification as a defensive engineering problem. It does not provide reusable exploit payloads, target-selection logic, forged proof templates, or private-key bypass methods. The system roles are source-chain events, target-chain verifier contracts, relayers, validator or guardian sets, Merkle or vector-commitment proofs, signature parsing libraries, governance upgrades, and cross-chain route engines.

The adversary is modeled as someone who can submit arbitrary message bytes, replay old validator context, stress parser boundaries, exploit default values, or select proof objects that the contract may interpret incorrectly. The adversary is not assumed to break secure hash functions or compromise honest validator private keys.

Bridge trust roots vary: multisig, light clients, IBC-style vector commitments, ZK light clients, TEEs, MPC, and optimistic fraud proofs. This article focuses on multisig and proof-verifier bridges because many historical bridge losses came from the target-chain verifier accepting a proof it should have rejected. For non-custodial cross-chain exchange, this risk affects release, refund attribution, and route pricing.

Bridge verifier state machine: a proof must bind to full context

A cross-chain release message can be modeled as:

`M = (srcChainId, srcBlock, emitter, nonce, token, amount, recipient, dstChainId, action)`

The target-chain verifier should not accept a naked message. It should accept `Proof(M, C)`, where `C` is context: source state root, validator-set ID, signature domain, chain ID, verifier contract address, message type, and expiry window. A defensive verifier looks like this:

```text verify_bridge_message(M, proof, ctx): require(ctx.dstChainId == localChainId) require(ctx.verifierAddress == this) require(valid_validator_set(ctx.validatorSetId, ctx.time)) require(verify_signatures(hash_domain(M, ctx), proof.signatures, ctx.validatorSet)) require(verify_inclusion(M, proof.merkle, ctx.stateRoot)) require(!consumed(M.nonce, M.emitter, M.srcChainId)) mark_consumed(...) execute_release_or_mint(M) ```

The order and binding are the point. Signatures should not cover only `amount + recipient`. A Merkle proof should not only prove that some leaf exists in some tree. The validator set should not be selected by the relayer. The nonce should not be local to the target chain. Each field should answer one question: which chain produced this message, which bridge contract emitted it, under which state root, confirmed by which validator set, consumed under which replay domain, and intended for which target chain.

Merkle empty witnesses and leaf ambiguity

Merkle proof safety depends on tree semantics, not only on hashing. The verifier must know the leaf encoding, sorting rule, tree height, path direction, empty-value semantics, and domain separation between leaves and internal nodes. Empty witness risk appears when a verifier treats a missing path, default zero hash, empty sibling list, or uninitialized root as a valid inclusion proof. One lesson from the Nomad incident class is that default values and initialization state can become verification conditions if the contract does not distinguish them.

A defensive Merkle verifier should enforce three invariants:

`leaf = H(LEAF_DOMAIN || canonical_encode(M))`

`node = H(NODE_DOMAIN || left || right)`

`root == recompute(leaf, siblings, path, tree_height)`

`LEAF_DOMAIN` and `NODE_DOMAIN` must differ. `canonical_encode` must be unique. `tree_height`, path direction, and sibling count must match the claimed tree. If the proof system supports non-existence proofs, existence and non-existence must have different proof types. An empty witness must not automatically fall into the existence path.

Second-preimage risk in bridge code is often not a practical break of the hash function. It is an encoding ambiguity. If `H(a || b)` has no length prefix, the boundary between `a` and `b` may be ambiguous. If leaves are not domain-separated, an internal-node hash may be interpreted as a leaf. If a sorted Merkle tree does not handle duplicate leaves strictly, membership proofs and position proofs can be confused. OpenZeppelin's MerkleProof documentation warns about leaf-length ambiguity for exactly this class of issue. Bridge verifiers should be more conservative than generic application code because they release assets across trust domains.

Signature parsing: threshold means unique valid weight, not signature count

A multisig bridge may require `k-of-n` signatures. Weak implementations reduce this to “number of valid signatures >= k.” That is not enough. A secure threshold function should be closer to:

`weight = Σ votingPower(v_i) for unique v_i where Verify(pk_i, domain_hash(M, ctx), sig_i)`

and then:

`weight >= threshold(ctx.validatorSetId)`

If a validator signature can be counted twice, or if the same key can appear under different encodings, the threshold is weakened. If ECDSA malleability boundaries are not handled, signer deduplication can be polluted. If Ed25519 or DER parsers accept non-canonical lengths, leading zeros, empty keys, or unclean memory, a verifier may treat malformed input as a real identity.

EIP-712 matters for bridges because it gives structured domain separation: chain ID, verifying contract, message type, and version. EIP-2's ECDSA malleability constraints show why one mathematical signature with several encodings can be dangerous for deduplication. RFC 8032 defines EdDSA boundaries that should be respected when Ed25519 verification is ported into contracts or precompile-based systems. The bridge should reject non-canonical encoding at the verification boundary, not after business logic begins.

Implementation should separate parsing from membership. A signature library returning a recovered address or a valid curve check only proves that the bytes satisfy a curve relation. The bridge still has to check that the signer belongs to `validatorSetId`, is valid for the message height, has not already been counted, has voting weight, and is not removed by governance. Useful states are `decoded`, `curveValid`, `memberValid`, `uniqueSigner`, and `thresholdReached`. A failure in any state should stop before Merkle verification or execution.

Variable-length arrays also deserve conservative handling. Signature count, signature order, validator-index bitmap, public-key array, and weight array must have consistent length rules. If the relayer can provide signatures in arbitrary order, the verifier should deduplicate by validator index. If a bitmap is used, it must not exceed the set length. If weights are used, they must come from on-chain validator-set state, not from proof input.

Validator-set updates: old sets must not be resurrected

Validator-set updates are a bridge state machine, not an admin detail. Suppose validator set `V_e` is valid for source height interval `[h_e_start, h_e_end)`. The target verifier must not simply use the current set, and it must not let the relayer choose an old set. A correct predicate looks like:

`validSet(M, V_e) := h_e_start <= M.srcBlock < h_e_end and updateLog(e) finalized before proofTime`

If update logs are not atomic, several errors appear. An old validator set may sign a new-height message. A new validator set may retroactively confirm an old message. A relayer may choose the epoch that helps a fraudulent proof. “Historical validator resurrection” does not require every old private key to be compromised. It only requires the protocol to let expired sets participate in current messages.

Defenses include monotonic epochs, mandatory source height or event sequence in every message, rejection of expired sets for new intervals, update logs confirmed by the same or stronger trust root, and deterministic ordering between validator updates and message execution. High-value bridges can separate validator updates from large releases with a delay window so governance change and asset movement do not share the same risk window.

A stricter bridge can treat validator updates as two-phase commit. Phase one records a pending set and activation height. Phase two activates it after enough source-chain finality. This increases update latency and can slow emergency rotation, but it makes the timing window explicit. High-value releases can require longer validator-set finality than small messages.

Auditors should inspect three logs together: validator-set update logs, message proof logs, and consumption logs. They must be monotonic and linkable. If update logs skip epochs, message proofs omit source height, and consumed keys are local to the target chain, incident response becomes guesswork.

Proof schema: bridge security needs machine-checkable meaning

Many bridge proofs are compact byte strings interpreted by SDKs, relayers, and contracts. That is dangerous if field meaning is not enforced by schema. A robust proof object should include:

- `proofType`: multisig, Merkle inclusion, IBC commitment, ZK proof, or optimistic proof; - `sourceDomain`: source chain, source contract, source height, message type; - `targetDomain`: target chain, verifier contract, action type; - `validatorContext`: validator-set ID, active interval, threshold rule; - `stateContext`: state root, tree height, path rules, leaf domain; - `consumptionKey`: complete anti-replay key.

These fields do not all need to be user-facing, but they must be verified. If `targetDomain` is absent, a proof may be replayed across target chains. If `proofType` is absent, a non-existence proof may be routed through an existence verifier. If `stateContext` is absent, the same Merkle path can be interpreted under different tree rules. If `validatorContext` is absent, signature threshold becomes relayer-provided context.

For a cross-chain router, schema is also a risk input. Multisig proofs are scored by signer concentration and threshold rules. Merkle proofs are scored by root finality and proof schema. Light-client proofs are scored by source consensus assumptions and verification cost. ZK proofs are scored by circuit coverage and public-input binding. Without schema, a router can only score by bridge brand. With schema, it can score by actual path semantics.

Schema versioning should be explicit as well. A verifier upgrade that changes leaf encoding, signature domain, path sorting, or validator-weight rules is not a harmless SDK change. It changes the meaning of historical proofs. A bridge should keep `proofSchemaVersion` in the signed or committed context and reject proofs whose schema version is not valid for the source height. Otherwise, a relayer may submit an old proof object through a new parser, or a new proof object through an old parser, and the contract may validate bytes under the wrong semantics.

Failure modes: how verifier mistakes turn into asset loss

The first failure mode is accepting default roots. An uninitialized root, zero value, or empty proof is treated as an accepted state. Detection signals include repeated short proofs, many messages under the same default root, and abnormal releases after initialization or upgrade.

The second failure mode is leaf ambiguity. Two different messages map to the same leaf encoding, or an internal node hash is accepted as a leaf. Detection signals include variable-length fields, missing domain tags, duplicate leaves, and non-fixed path length. Defenses are canonical encoding, length prefixes, leaf/node domain separation, and fixed tree rules.

The third failure mode is signer deduplication failure. Repeated signatures, duplicate public keys, malleable signatures, or different encodings of the same signer are counted more than once. Detection signals include signer bitmaps near threshold, repeated signer indices, and signatures that verify but do not map to unique validator membership. Defenses include unique validator indexes, canonical signature rules, and on-chain weight lookup.

The fourth failure mode is validator-set timing mismatch. Old sets sign new messages, new sets retroactively sign old messages, or relayers choose a favorable epoch. Detection signals include source height outside validator-set interval, missing update logs, and large releases across epoch boundaries. Defenses are monotonic epochs, message-height binding, and atomic update logs.

The fifth failure mode is incomplete consumption state. A message verifies but is not marked consumed under a complete key. If the key omits source chain, emitter, nonce, action, or target chain, the same proof can be replayed across assets, chains, or actions. Defenses are complete nonce domains and marking consumption before release.

The sixth failure mode is coarse pause logic. A bridge may only have global pause, blocking refunds and exits, or only narrow pause, failing to stop the active proof type. Better designs pause by proof type, source chain, target chain, asset, or amount tier.

AllSwap relevance: bridge verification risk must enter route and refund models

AllSwap-style cross-chain routing should not treat all bridge paths as equal infrastructure. The trust root, validator-update frequency, Merkle schema, signature threshold, replay domain, and incident response design all affect route safety. A simple bridge-risk score is:

`bridgeRisk = validatorSetRisk + proofFormatRisk + updateTimingRisk + replayRisk + incidentResponseRisk`

`validatorSetRisk` comes from signer concentration, unique-signer rules, and voting weight. `proofFormatRisk` comes from Merkle domain separation, empty-witness handling, and canonical encoding. `updateTimingRisk` comes from validator-set transition windows. `replayRisk` comes from consumed-key design. `incidentResponseRisk` comes from pause, limit, and refund mechanisms. A cheaper bridge path with weaker verification semantics may be worse than a slightly more expensive route with stronger proof binding.

Route status should also be more precise. `proofObserved` means a source event and proof appeared. `signatureVerified` means the threshold check passed. `proofVerified` means Merkle or state proof passed. `releaseExecuted` means target-chain release happened. `challengeOrPause` means risk controls triggered. `refundPending` means the fallback path is active. Users do not need to understand empty witnesses, but they do need accurate waiting and refund state.

AllSwap can model bridge paths as `normal`, `elevatedRisk`, `proofDegraded`, `paused`, and `refundOnly`. If a bridge shows suspicious proof behavior, validator-update delay, or large releases near epoch boundaries, routing does not need to shut down everything. It can lower limits, require stricter quote checks, allow refund-only paths, or switch to a stronger but more expensive route.

The practical implementation detail is that these states should not be inferred from marketing labels such as "audited bridge" or "institutional validator set." They should come from telemetry that is close to the verifier boundary. A route engine can observe proof length distribution, guardian or validator-set epoch, number of unique signers, source-height freshness, target-chain execution delay, failed proof submissions, paused proof types, and refund queue growth. None of these signals proves that a bridge is unsafe on its own. Together, they provide a useful early-warning model. A sudden cluster of short Merkle proofs, many releases under a recently changed validator set, or repeated target-chain verification failures should raise the route cost even before a public incident report exists.

This also changes how limits should be designed. A bridge limit that only caps total daily volume is too coarse for verifier-semantics risk. More useful limits are scoped by source chain, target chain, asset, proof type, validator-set epoch, and amount tier. For example, a router may keep small transfers open while moving large transfers to a slower light-client path. It may keep same-asset refunds enabled while disabling new release messages. It may require a longer quote validity window when source finality is delayed. The user-facing result is not a cryptography lecture; it is a more accurate route status, a clearer expected wait, and fewer ambiguous failure states.

Refund accounting should use the same context binding as message verification. A refund key that only stores user address and amount is not enough. It should bind the original source chain, target chain, asset, quote ID, bridge path, message nonce, and release attempt. Otherwise, bridge failure, route retry, and user refund can become three disconnected ledgers. In a non-custodial exchange flow, the worst incident response is not always the bridge exploit itself. It is the inability to prove which user intent corresponds to which stuck proof after the bridge has paused. Strong verifier semantics make incident response auditable.

This article should link to AllSwap's cross-chain exchange page, fee page, asset pages, ZK light-client research, and omnichain supply-invariant research. The product reader learns why bridge choice is not only about price. The technical reader sees how proof semantics affect non-custodial route execution and refund certainty.

Open problems: bridge security needs composable verification standards

First, bridge proof systems lack a common description language. Multisig VAA, Merkle inclusion, IBC commitment proof, and ZK light client proof all have different semantics. Routers need a common risk interface.

Second, validator updates and message execution remain hard to compose. Upgrade, rotation, pause, and recovery actions may overlap with large releases, creating human-created risk windows.

Third, on-chain signature libraries differ by curve and encoding. ECDSA, Ed25519, BLS, and Schnorr have different malleability and canonicalization boundaries. Porting verification into contracts increases parser risk.

Fourth, existence and non-existence proofs are often mixed. Verifiers need explicit proof types rather than letting empty values flow through default paths.

Fifth, user-facing bridge risk remains hard to explain. A good interface should not expose every cryptographic term, but it should make route risk, wait reason, refund certainty, and pause state visible.

References

[1] Wormhole documentation, Guardians, https://wormhole.com/docs/protocol/infrastructure/guardians/

[2] Wormhole generic message passing whitepaper, https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0001_generic_message_passing.md

[3] Cosmos IBC, ICS-23 vector commitments, https://github.com/cosmos/ibc/tree/main/spec/core/ics-023-vector-commitments

[4] Cosmos ICS23 implementation and proofs, https://github.com/cosmos/ics23

[5] OpenZeppelin Contracts, MerkleProof, https://docs.openzeppelin.com/contracts/5.x/api/utils#MerkleProof

[6] OpenZeppelin Contracts, ECDSA, https://docs.openzeppelin.com/contracts/5.x/api/utils#ECDSA

[7] EIP-712: Typed structured data hashing and signing, https://eips.ethereum.org/EIPS/eip-712

[8] EIP-2: Homestead hard-fork changes and ECDSA malleability, https://eips.ethereum.org/EIPS/eip-2

[9] RFC 8032: Edwards-Curve Digital Signature Algorithm, https://datatracker.ietf.org/doc/html/rfc8032

[10] Chainlink, Cross-chain bridge vulnerabilities, https://blog.chain.link/cross-chain-bridge-vulnerabilities/

[11] SlowMist, Root cause analysis of Poly Network hack, https://slowmist.medium.com/the-root-cause-of-poly-network-being-hacked-ec2ee1b0c68f

[12] CertiK, Nomad bridge exploit incident analysis, https://www.certik.com/resources/blog/28fMavD63CpZJOKOjb9DX3-nomad-bridge-exploit-incident-analysis

FAQ

Does bridge signature forgery always mean private keys were stolen?

No. Many bridge failures come from verifier semantics: duplicate signer counting, missing signature domain separation, validator-set version mismatch, or non-canonical encoding. A private key does not need to be broken for a bad proof to pass.

Why are Merkle empty witnesses dangerous?

If a verifier treats an empty sibling list, default zero root, or uninitialized state as a valid inclusion proof, a non-existent message can be accepted. Defensive verifiers need fixed tree rules, domain separation, canonical encoding, and explicit proof types.

Why does validator-set timing matter for bridge safety?

If an old validator set can sign new messages, or a new set can retroactively confirm old messages, relayers can choose favorable context. Safe designs bind message height, validatorSetId, and update logs atomically.

Why does AllSwap care about bridge verifier details?

Cross-chain swaps depend on bridge release and refund paths. A cheaper bridge route with weak signature thresholds, Merkle semantics, or replay domains may create higher release-failure or pause risk.

Sources & references