Skip to content
Dark abstract illustration of a dense teal machined register plate whose rows of milled cells are indexed from two opposite edges, with one cell duplicated in a single row and its second copy raised and lit red
exploitsAugust 6, 20263 min read

Verus Bridge $7.5M Exploit: When Two Chains Read the Same Bytes Differently

Dmitry Serdyuk
Dmitry SerdyukCo-Founder & CDO

Updated on August 6, 2026

TL;DR

On July 23, 2026 at 03:45:59 UTC, an attacker drained roughly $7.5 million from the Verus Ethereum Bridge in a single submitImports call, taking 1,137.45 ETH plus tBTC, USDC, USDT, EURC, MKR and scrvUSD. Every key and every signature involved was legitimate. The attacker embedded duplicate state-root entries inside otherwise legitimate Verus notarizations, and the two chains parsed those duplicates in opposite directions: Verus kept the first entry, Ethereum kept the last. Eleven honest notaries signed bytes that meant one thing on their own chain and something else on Ethereum. This was the second drain of the same bridge in nine weeks, after $11.58 million on May 18. Verus recovered most of that money and put it back into the same contract on July 8. Hardened replacement contracts went on-chain two days later, the pull request holding that work is still unmerged, and the bridge was drained anyway.


What Actually Happened on July 23?

The bridge was emptied in 22 minutes of Ethereum activity, after about four hours of setup on Verus.

CertiK's incident analysis reconstructs the sequence, and the Ethereum half is directly verifiable. The transaction history of the Verus Delegator contract shows three calls from the same brand-new address, 0xBda71b58cEc0b1C20A8f87cCD52FA0679747855c: a setLatestData at 03:23:35 UTC, a second at 03:41:47, then submitImports at 03:45:59. That last call is the drain transaction, and Etherscan labels its counterparty the Verus Bridge Exploiter Wallet.

It burned 1,108,209 gas at 0.1 gwei, a fee of 0.00011 ETH. Etherscan prices that at 21 cents, for seven and a half million dollars.

The setup ran on Verus first. Starting at 23:49 UTC on July 22, the attacker got three consecutive notarizations signed, each carrying extra state-root entries alongside the genuine ones. At 01:48 UTC they opened a real cross-chain export worth 0.01 VRSC.

Every signature on that payload was valid. What they attested to was that eleven notaries had seen the same bytes, which is a weaker claim than the Ethereum side was relying on.


How Did the Verus Bridge Exploit Work?

The attack turned on a parser disagreement, and two primitives need defining first.

A notarization is Verus's cross-chain checkpoint. Notaries agree on a set of proof roots, sign the serialized result, and that signed blob becomes the reference Ethereum trusts when deciding whether a claimed Verus transaction really happened. The Ethereum contract checks an 8-of-15 notary signature threshold, a Merkle proof, and a transaction hash binding, per The Crypto Times.

A Cross-Chain Export, or CCE, is the receipt for value leaving Verus. It records which transfers were batched, the block heights, and a hash binding the batch together. Ethereum reads the CCE, verifies it against the notarized state root, and pays out. The CCE is the instruction; the state root is the authority that makes the instruction credible.

Now the bug. A notarization carries a list of proof roots, one per connected system. Verus deserializes that list into a vector and then inserts the entries into a map keyed by system ID, and a map insert keeps the first value written for a key. The Ethereum contract loops over the same entries and assigns each match to a single stateRoot variable, and a loop that overwrites keeps the last.

Same bytes. Opposite results.

The attacker put the genuine root first, so every notary validating the blob saw a correct checkpoint and signed in good faith. The same blob then went to Ethereum, where the trailing malicious root, 20f2…d30a in CertiK's reconstruction, won the assignment and became the state Ethereum trusted. A fabricated CCE could then be made to prove against it. CertiK notes the attacker modified the batch hash to match the draining transfer, set numInputs to 8 and firstinput to 0, and omitted the totalamounts, totalfees and totalburned fields entirely.

The Attack, Step by Step

#Time (UTC)ChainAction
1Jul 22, 23:17VerusA genuine notarization lands, giving the attacker a valid template
2Jul 22, 23:49 onwardVerusThree consecutive notarizations signed carrying duplicate malicious proof roots
3Jul 22 to 23VerusSignatures harvested from 11 notaries via getnotarizationdata()
4Jul 23, 01:48 to 01:51VerusA fresh address opens a real 0.01 VRSC export to create a valid-looking batch
5Jul 23, 03:23:35EthereumFirst setLatestData() relays the poisoned notarization; the loop installs the malicious root
6Jul 23, 03:41:47EthereumSecond setLatestData() call
7Jul 23, 03:45:59EthereumsubmitImports() with a forged CCE; reserves pay out
8Jul 23, 04:23:47EthereumThe address that deployed the July 10 replacement contracts calls revokeWithMainAddress, 38 minutes after the drain

Rows 1 through 4 follow CertiK's reconstruction. Rows 5 through 8 are read from the bridge contract's own transaction history, which is where the response timing comes from; CertiK's timeline stops at the drain.

The two-chain interplay is invisible in a diagram of either chain alone:

rendering diagram…

Eleven Honest Notaries Signed the Malicious State Root

From where the notaries stood, nothing was wrong.

This is CWE-436, Interpretation Conflict, which MITRE defines as one product handling inputs differently than another, causing it to act incorrectly on its perception of the other's state. Bridges are a near-perfect habitat, because a bridge is two implementations of one format, written in two languages, by people optimizing for different constraints.

The notary set did its job. The threshold held. The cryptography was sound. What failed was the assumption that a signature over a byte string carries a shared understanding of what it says. Nobody had run both parsers against a malformed-but-well-signed input, so nobody had asked what happens when a system ID appears twice.

An audit certifies the code you showed it, in the language you showed it in. A reviewer reading deserializeNotarization() in isolation finds a loop that assigns on match, which is entirely reasonable Solidity. You only see it by running that loop against the Verus deserializer, which is rarely in scope.


How July Differed From the May Hack

The two drains shared a contract and an entry path, then parted company on root cause.

Blockaid flagged the July drain and said it appeared related to May, citing the same bridge contract, same entry path, and same bug class. That framing was repeated widely, including in reporting that the flaw remained unfixed. It is right about the contract and the entry path, imprecise about the bug class, and Blockaid said at the time that root cause was still under investigation.

May 18 was an economic validation gap. Halborn's analysis puts it plainly: neither side verified that the amount committed on Verus matched the payout released on Ethereum. The attacker forged an import that passed every cryptographic check while committing essentially zero value, turning about $10 in fees into $11.58 million. Merkle Science sized the fix at roughly 10 lines of Solidity in the checkCCEValues function, and characterized it as an economic-logic gap rather than a cryptographic or parsing failure.

July was a parser bug. The attacker subverted the authority the value check relies on, making Ethereum trust a state root Verus never agreed to. CertiK treats the missing payout-versus-export validation as a secondary contributing factor and names cross-chain semantic consistency as the root cause.

Both readings converge on one operational fact: the same contract paid out unbacked value twice. A team that patched only the May bug would still be exposed to July, and the reverse holds too.


How Much Was Actually Taken?

The dollar figures disagree. The token quantities do not, and the quantities are what to trust.

CertiK puts the loss at $7.44 million. Blockaid and most subsequent reporting put it at $7.54 million. That gap is about 1.3 percent, a pricing-snapshot artifact: a mixed basket values differently depending on which block you price it at.

CoinCentral's breakdown lists 1,137.45 ETH, 71.50 tBTC v2, 149,275 USDC, 78,300 USDT, 31,475 EURC, 59.43 MKR and 92,784 scrvUSD. The ETH leg is confirmed by Etherscan's own description of the drain transaction.

The proceeds reconcile cleanly. CertiK observed the attacker convert the token basket into 2,778.8662 ETH through the Relay DEX. Other reporting describes 3,916 ETH consolidated before the funds moved into Tornado Cash. The two figures measure different legs of the same haul: 1,137.45 ETH taken natively plus 2,778.87 ETH bought with the tokens gives 3,916.3 ETH.

We headline this at $7.5 million. The defensible unit is 3,916 ETH, because the quantity is fixed and only the exchange rate is arguable.

The same arithmetic validates the May reporting. Per The Crypto Times and CoinCentral, that attacker returned 4,052.4 ETH under a settlement and kept 1,350 ETH as a bounty. Those sum to 5,402.4 ETH, exactly the drainer balance Merkle Science recorded in May.


The Real Hole: A Remediation That Never Finished

The most uncomfortable finding in this incident sits in the project's public GitHub rather than in any of the security reports.

The Verus-Ethereum-Contracts repository has one branch, and the most recent commit on it is dated February 26, 2026, three months before the May hack and five before July. Every line of remediation written since May lives in pull request #59, opened July 10, last updated July 13, still open and unmerged. We read these figures from the GitHub API on August 6, 2026; anyone can re-run the check.

The PR is clearly the real fix. It carries 33 commits across 32 files, adding 4,069 lines and removing 734. Its first commit is dated May 22, four days after the May hack, titled "Add fixes for CCE, CompactInt, Proof storage." VerusProof.sol, the contract validating the proofs at the center of the July attack, is rewritten with 592 additions and 243 deletions.

The engineering was done. It started four days after the first drain and ran eight weeks.

Two facts sit on either side of it. Blockscout puts the replacement contracts on mainnet at 11:13 UTC on July 10, eight hours before the PR opened, and they are source-verified: the new VerusProof sits at 0x54E03a1682fd0bB065B669f6296f97028DcFD4Ce, and CertiK's July analysis names that same address, indicating at least part of the upgrade was live during the attack. And on July 8, per on-chain records cited by CoinDesk, Verus redeposited the recovered May funds into the same bridge.

Read the ordering again. The money went back in on July 8. The hardened contracts were deployed July 10. The pull request opened later that day. On July 23 the bridge was drained anyway.

Part of the fix was on-chain, and CertiK places the new VerusProof inside the July incident. So either the upgrade was only partially activated behind the Delegator, or the hardened validation did not cover the duplicate-entry divergence the attack used. Public data does not settle which.

That ordering carries a second-order risk. PR #59 has been public since July 10, and its diff shows precisely which validations were being hardened. Reading a published patch to locate the bug it fixes is ordinary practice against systems not yet updated, and here the patch sat in the open for thirteen days while the old path kept taking traffic. The exposure was there either way.

Reporting after the second drain relayed Merkle Science's guidance: avoid the bridge until the faulty validation is fixed and independently audited. We found no publicly confirmed independent audit of the Verus Ethereum contracts, before May or since.

Attribution stays open. The wallet Etherscan labels the Verus Bridge Exploiter Wallet has 68 transactions, all of them on July 23, the earliest at 03:57:59 UTC, twelve minutes after the drain. The address that called the bridge was equally new. Fresh infrastructure on both ends leaves address reuse silent, and no public clustering analysis ties July's operator to May's.

Verus has not published a postmortem or public statement on the July incident that we can find, so that is where the record stops. What it supports is narrower: eight weeks of security work never reached a finished state, and the treasury was refilled before it got there.

The May settlement returned most of the money to a contract whose fix was still in review.


What Does This Generalize To?

Any system where two independent implementations must agree on the meaning of one serialized structure carries a latent consensus bug. Cross-chain bridges are the densest concentration of that pattern in production today.

The lineage runs well outside crypto. Request smuggling works because a proxy and a back-end server disagree about which Content-Length header counts. The control functions correctly against its own reading, and the attacker routes each reading to the party it fools.

Bridges inherit this by design. The source serializer is typically C++ or Rust; the destination verifier is Solidity, written later, by different people, often from the reference implementation rather than a written standard. Duplicate keys, field ordering, absent optionals, integer edge cases: each is a reasonable choice on one side and an exploitable divergence on the other.

Cryptographic soundness and semantic agreement are separate properties, and only the first one gets audited.

The same day made the point twice more. AFX Trade lost $24.15 million from its Arbitrum bridge and B2 Network lost $3.86 million to an upgrade-authority compromise, both within seven hours of the Verus drain. It echoes one LayerZero DVN parameter putting $292 million at risk and Aztec Connect's settlement path accepting a proof it should have rejected. The verifier ran, the verifier was satisfied, and it was answering a narrower question than anyone thought.


Six Controls Worth Adopting

  1. Differentially test both parsers against the same adversarial corpus. If you serialize a structure on chain A and deserialize it on chain B, fuzz both implementations with identical bytes and assert they produce identical objects. Duplicate keys, reordered fields, omitted optionals, oversized integers. This test class would have caught the Verus divergence, and no single-language audit would have.

  2. Make duplicate entries a hard revert, not a resolution rule. When a deserializer meets two entries for one key, the correct behavior in a security-critical parser is to reject the message. Choosing first-wins or last-wins is choosing a policy the other implementation may not share.

  3. Validate value conservation independently of proof validity. Both drains ended with the bridge releasing more than was committed. A ceiling comparing released value against locked value, enforced regardless of what the proof says, would have capped both near zero. Treat it as a circuit breaker, not a validation step.

  4. Alert on first-time callers of privileged entry points. The attacker's address had never called setLatestData before 03:23:35 UTC and drained the bridge at 03:45:59. A rule as simple as "state-root submission from an address with no prior history" would have fired 22 minutes ahead. The address that deployed their replacement contracts reacted at 04:23:47, 38 minutes after the money left.

  5. Treat state-root submissions as reconcilable, not just verifiable. A monitor that independently reads the canonical Verus notarization and compares it against what was installed on Ethereum catches the mismatch in that same window, without needing to understand the parser bug.

  6. Never refill a contract before the fix is merged, reviewed and deployed. Treat fund recovery and incident closure as separate milestones. Gate it explicitly: recovered funds return only after the remediation is on the default branch, independently reviewed, and confirmed live on-chain.

Points 1 through 3 are audit work, and a review scoped across both implementations would have earned its fee here. Points 4 and 5 are runtime work, and the ceiling deserves stating honestly: monitoring would not have prevented this drain. It would have bought 22 minutes and a chance at an automated pause, and whether anyone can use 22 minutes at 3am UTC depends entirely on whether the response is automated. Tripwire is the layer built to act inside a window that short.


Frequently Asked Questions

How much did the Verus bridge lose in July 2026? Roughly $7.5 million. CertiK calculated $7.44 million while Blockaid and most reporting cited $7.54 million; the difference is a pricing snapshot on a mixed basket. The attacker consolidated 3,916 ETH before moving funds into Tornado Cash.

Was the Verus bridge hacked twice by the same bug? Not exactly. Both drains went through submitImports on the same contract, but May 18 was a missing source-amount validation in checkCCEValues, while July 23 was a parser divergence in how the two chains deserialize duplicate proof-root entries. The missing amount validation was a contributing factor in July rather than the primary cause.

Had Verus fixed the May vulnerability before July? Partly. The remediation code was written starting four days after the May hack, and replacement contracts were deployed to mainnet on July 10, but the pull request containing that work, #59, remains open and unmerged as of August 6, 2026. The recovered May funds went back into the bridge on July 8, and the July 23 drain succeeded regardless.

Would a smart contract audit have caught this? An audit of the Solidity alone probably would not have. Nothing in the Ethereum contract is wrong on its own terms; the divergence surfaces only when you run the same bytes through the Verus deserializer and diff the results. A review scoped to include differential testing across both would have found it.


Sources / References

Dmitry Serdyuk
Dmitry Serdyuk

Co-Founder & CDO

Full-Stack Operator | Building across security, AI, and digital infrastructure.