Wormhole Signature Bypass (2022)
Smart contract security audit by TierZero · 2023-05-15
Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.
Findings summary
Independent research — not a commissioned audit.
What the protocol did
Wormhole is a generic message-passing bridge connecting Solana to Ethereum and other chains. Its Solana program is the settlement point for token transfers: when a user locks or burns an asset on one chain, a set of off-chain "guardian" nodes observe the event and co-sign a message — a Verified Action Approval (VAA) — attesting that the event happened. The Solana program's job is to check that a VAA carries enough valid guardian signatures before it mints a wrapped asset, in this case wETH backed 1:1 by ETH locked on Ethereum.
That signature check is the entire trust boundary. If a program can be convinced a VAA is properly signed when it isn't, it will mint wrapped assets against collateral that was never deposited. That is exactly what happened on February 2, 2022: an attacker forged guardian approval for a deposit that never occurred and minted roughly 120,000 wETH on Solana, worth close to $325 million at the time — one of the largest exploits in DeFi history at that point. Jump Crypto, Wormhole's backer, replenished the funds days later; the bug itself was a smart-contract logic flaw, not a private-key compromise.
The vulnerability
Solana programs verify secp256k1 (Ethereum-style) signatures by relying on a separate, native secp256k1 program instruction placed earlier in the same transaction, then reading that instruction's data back via the runtime's Instructions sysvar. This is a standard, documented pattern — but it only works if the program confirms the account it's reading from is genuinely the sysvar, and not an arbitrary account the caller supplied.
Wormhole's verify_signatures instruction used the deprecated load_instruction_at sysvar helper instead of load_instruction_at_checked. The deprecated function parses instruction data out of whatever account is passed in — it does not assert that the account's public key equals the well-known Instructions sysvar address (Sysvar1nstructions1111111111111111111111). The account was accepted as a plain, unconstrained input.
// simplified, pre-patch pattern
let ix_sysvar_acc = &ctx.accounts.instruction_acc; // caller-supplied, NOT constrained
let secp_ix = solana_program::sysvar::instructions::load_instruction_at(
secp_ix_index,
&ix_sysvar_acc.data.borrow(),
)?; // trusts whatever data is in this account
// program then treats secp_ix as proof that secp256k1 verification ran
The missing constraint is a single line — assert_eq!(ix_sysvar_acc.key, &sysvar::instructions::id()) — but its absence collapses the entire signature-verification model. This is the same family of bug our Solana program security audit checklist for trading systems flags as a top-line item: any sysvar, PDA, or oracle account read for a security decision needs an explicit key check, not an implicit one inferred from naming or convention.
How the exploit worked
- The attacker created their own Solana account and populated it with fabricated instruction data shaped to look like a completed secp256k1 signature-verification instruction — without any real guardian signatures behind it.
- They called Wormhole's
verify_signaturesinstruction, passing their spoofed account in place of the genuine Instructions sysvar. - Because the check only parsed the account's contents and never confirmed its identity, the program accepted the forged data as evidence that valid guardian signatures had been verified by the runtime's secp256k1 program.
- With signature verification "passed," the attacker submitted a VAA claiming a deposit of roughly 120,000 ETH had been locked on the Ethereum side — no such deposit existed.
- The Solana Token Bridge program minted the corresponding wETH and credited it to the attacker's account.
- A portion was bridged back out to Ethereum and swapped for ETH before the team could react; the remainder stayed as wrapped tokens on Solana.
Notably, this exact bug had been identified by a third-party security firm and reported to Wormhole beforehand; a fix was in progress but had not yet reached the deployed mainnet program when the attacker found and used it independently. That gap between disclosure and deployment is its own lesson, one we return to in our piece on upgradeable Solana programs and BPF loader risk — a known fix sitting unshipped is functionally no different from an unknown bug.
How an audit catches this
This class of bug is mechanical to catch with the right checklist, not luck:
- Sysvar identity checks: grep every use of
load_instruction_at,load_current_index, or raw sysvar account reads and confirm each one validatesaccount.keyagainst the canonical sysvar address, or uses the_checkedvariant. - Trust-boundary tracing: for every instruction that gates a mint, withdrawal, or state transition on an external attestation (signature, oracle price, cross-program result), trace the account back to its source and confirm it cannot be substituted by the caller. This is the same discipline we apply to cross-program invocation trust assumptions in our CPI risk analysis for Solana DeFi.
- Negative testing: write a test that supplies a attacker-controlled account in place of every sysvar or program-derived account the instruction reads, and assert the instruction rejects it.
- Deprecation sweep: flag any use of functions the Solana SDK marks deprecated — deprecation is often the ecosystem's way of quietly retiring an unsafe API after exactly this kind of incident.
Remediation
Wormhole's actual fix, and the durable pattern, is to use load_instruction_at_checked, which validates the sysvar account's key internally before parsing, or to add an explicit require_keys_eq!-style assertion against sysvar::instructions::id() before trusting any data read from it. More broadly: any account whose identity — not just its contents — carries security meaning must be pinned to a known address or derived and verified via PDA seeds, never accepted implicitly by position or naming convention.
This incident is a clean argument for treating account-validation review as a first-class line item in smart contract audits, not an afterthought to logic review — the bug here was one unchecked account reference sitting inside otherwise well-designed cryptographic verification.
Need your contracts audited?
Manual review + tooling across TON, Solana and EVM — certificate and full report included.
Get an audit