On August 1, DeFi bridge Nomad was hacked for over $190M.

After a frenzied hack from hundreds of wallets, the bridge’s TVL dropped from $190,740,000 to $1,794 in mere hours. The hack involved a total of 960 transactions with 1,175 individual withdrawals from the bridge.

Nomad is a cross-chain bridge used for swapping tokens between Ethereum, Avalanche, Evmos, Milkomeda C1, and Moonbeam. Nomad confirmed on Twitter that the vulnerability was not exploited on any chain except for Ethereum, and confirmed that only Ethereum-based assets were involved in the hack. 

The Frenzy Begins

The first transactions started at Ethereum block 15259101 on August 1, 21:32:31 UTC. There were four relevant transactions within this same block, at indices 0, 1, 3, and 124. Each of these transactions drained 100 WBTC from the bridge.

On August 1, a hacker found a vulnerability in Nomad’s code that failed to properly validate that a message was approved before processing it. When one hacker started taking out funds, a swarm of copycats joined the party.

A perplexing aspect of this vulnerability was that all users had to do to hack bridge funds was copy the original hacker’s transaction calldata, replace the original address with a personal one, and the tx would succeed! Easy as CTRL-C, CTRL-V.

0xfoobar, DeFi dev & founder

Because of the nature of the bug, many wallets were taking out the same exact amount: $202,440 worth of USDC.  Crypto-twitter was a rush of emotions: fear, panic, and greed.

A $190M Bug

According to Nomad’s post-mortem, an implementation bug in a June 21 smart contract upgrade caused the Replica contract to fail to authenticate messages properly.  This issue meant that any message could be forged as long as it had not already been processed.   

As a result, contracts relying on the Replica for authentication of inbound messages suffered security failures.  From there, this authentication failure resulted in fraudulent messages being passed to the Nomad BridgeRouter contract. 

Nomad commits to cross-chain messages in a Merkle tree (message tree), whose root is propagated to remote chains via the optimistic mechanism.  In their post-mortem, Nomad explains Acceptable Root as follows:  

The Replica contract tracks roots from other chains using a mapping(bytes32 => uint256). This maps roots to the timestamp at which they become valid. Messages may not be processed before the root’s optimistic timer has elapsed. When reading a mapping, if the entry has not been set, the default value (also called the 0 value) is read instead. The default value of a uint256 is 0. Any root that has not been attested to will therefore have a 0 timestamp in this mapping.

After a root is propagated to another chain, messages inclusion in the tree is proven by Merkle proof. The root under which a message is proven is stored in a mapping(bytes32 => bytes32) in the Replica contract. This maps the hash of the message to the root under which it was proven. In this case, the default value of a bytes32 is bytes32(0). As such, any message that has not been proven will have a root of bytes32(0) in this mapping.

When a message is submitted to the process function, the protocol reads the root from the mapping, and checks whether the acceptableRoot function returns true. This function is intended to return true if and only if a valid root’s optimistic timeout period has finished. This function checks for special legacy values of root (from an older system version), as well as for roots that have not been attested to (have a 0 timestamp in the roots mapping). It ensures that a root’s timer has elapsed by checking it against the block timestamp return block.timestamp >= _time;.

The relevant lines of acceptableRoot are as follows:

uint256 _time = confirmAt[_root];
if (_time == 0) {
   return false;
}
return block.timestamp >= _time;

First, confirmAt[_root] is loaded into a variable named _time. For unknown messages (including forged messages) this _root is equal to bytes32(0). On any Replica that was initialized with _committedRoot set to bytes32(0) , the value of confirmAt[bytes32(0)] was set at initialization equal to 1.  

Because _time is 1, we skip this block and any valid blockstamp will be greater than or equal to 1.  As a result, acceptableRoot(bytes32(0)); always returns true for these Replica contracts.

This allowed unproven messages to pass the following check in the process function — meaning the messages could be processed without first having been proven.

function process(bytes memory _message) public returns (bool _success) {
   // ...
   require(acceptableRoot(messages[_messageHash]), "!proven");
   // ...
}

Moving Forward

As a result of this vulnerability, over $190M was taken from the Nomad bridge.  “It’s been a heartbreaking week”, Nomad co-founder Pranay Mohan tweeted. “I haven’t communicated much publicly because we’ve been spending every available moment on recovering funds and figuring out a plan to reboot the Nomad bridge.”

Mohan and his team put up a request for a return of funds. As of August 15, 2022, over $33M has been returned to Nomad’s wallet by whitehat hackers.  

Hacks like these can change the directory of crypto careers.  You can become nihilistic and lose hope in a decentralized system — or move forward with strength and optimism. When looking towards the future, sometimes it helps to look at the past.

It’s good to remember that in every budding industry, there are crashes and flames.  The first ship was not built without a few shipwrecks.  We would not have today’s airport system if it was not for the original pilots and inventors risking their lives.  To build a decentralized future, we need to fail repeatedly.  When you zoom out, you can see this is just part of the process of building great lasting things.

“The word ‘community’ gets thrown around a lot in crypto. In this crisis, I’m realizing that it’s not just a buzzword, but a real bedrock of friends and partners playing the infinite game with us,” Mohan wrote in a tweet.  Keep on building your web3 community — it’s what will get you through tough times like these.

The Nomad Bridge Hack: A Deeper Dive
Rob Behnke
08.15.2022