On August 12, 2026, Harmony Protocol suffered an unauthorized mint of approximately 4B ONE (~26% of the ~15.01B supply) in the first confirmed wave - ~3.01T ONE forged in total across the reconstructed attack (@harmonyprotocol). At the pre-incident price (~$0.001183) the forged amount carries a nominal value of ~$3.56B, roughly 200x ONE’s total supply and far beyond its market cap - neither realizable nor a loss figure, and no verified realized USD loss exists: the team patched the flaw and rolled the network back to its pre-attack state (@harmonyprotocol).
Harmony is a sharded, proof-of-stake Layer-1 written in Go, with a beacon chain (Shard 0) and three execution shards, and ONE as the native token. The attack did not touch a smart contract: it forged cross-shard receipts at the consensus layer, crediting attacker addresses with native ONE without any matching source debit.
FBFT and BLS aggregated signatures
Harmony’s consensus is FBFT (Fast Byzantine Fault Tolerance), a PBFT variant that compresses communication with BLS (Boneh-Lynn-Shacham) aggregate multi-signatures: validators send signed PREPARE messages to the leader only, and the leader aggregates >= 2f+1 valid signatures (> 2/3 voting power) into a single BLS signature, with a signer bitmap recording which committee members actually signed.
Each chain runs its own instance of this protocol with its own leader and committee: the beacon chain (Shard 0) for staking, randomness, and epoch transitions, and each execution shard (Shards 1-3) for user transactions - the Consensus struct is scoped per shard via its ShardID (consensus/consensus.go @ v2026.1.0).
For staking epochs, quorum voting power is staked tokens per validator. For pre-staking epochs, every node has uniform voting power and the uniformVerifier applies - a historical path used today only to verify old headers, such as the source headers of cross-shard receipts.
The uniform verifier: IsQuorumAchievedByMask()
IsQuorumAchievedByMask(): decides whether a signer bitmap plus aggregate BLS signature constitutes quorum for a pre-staking committee. The vulnerable implementation at release v2026.1.0 (”Bloom”):
consensus/quorum/verifier.go:73-86 (v2026.1.0)
mask.Publics is the full committee public-key list - not the set of validators that actually signed. The committee size always exceeds pubKeyCnt*2/3 + 1, so the check always returned true, regardless of the bitmap. An all-zero bitmap with an all-zero (identity) aggregate BLS signature could therefore satisfy “quorum” - a fabricated pre-staking header authenticated without any real validator signature (evidenced by the fix and its regression tests in commit 7515262f).
Cross-shard (CX) receipts, Merkle proofs, and IsSpent()
Value moves between shards via CX (cross-shard) transactions: a transaction on the source shard produces a receipt, which the destination shard credits through a CXReceiptsProof containing:
the receipts to apply (recipient, amount),
a MerkleProof proving inclusion in the source block’s receipt tree,
the source block’s Header,
the source committee’s CommitSig/CommitBitmap - the aggregate BLS signature over the header only.
To prevent double-crediting, the destination shard marks applied proofs as spent. ValidateCXReceiptsProof(): the destination-shard entry point that verifies a submitted CX proof - signature, quorum, Merkle inclusion, and spent status - before applying its receipts. IsSpent(): checks whether a proof was already consumed, keyed by shard ID and block number:
core/blockchain_impl.go:2726-2735 (v2026.1.0) The catch: the BLS signature covers the Header, but MerkleProof.ShardID and MerkleProof.BlockNum are separate, unsigned copies.
The Bloom fork (epoch 2964)
The Bloom hard fork (mainnet epoch 2964, July 13, 2026; shipped in release v2026.1.0) hardened CX replay protection by keying the spent marker off the signed header (PR #5053). But both IsSpent() and WriteCXReceiptsProofSpent() (core/blockchain_impl.go:2709-2724 (v2026.1.0)) applied the strict path only when IsCXMerkleProofReplayFixEpoch(cxp.Header.Epoch()) - the epoch inside the proof’s own header, not the chain’s current epoch. Every genuine pre-fork header still took the weak legacy path, keyed off the mutable, unsigned MerkleProof fields (see the legacy code path above, at v2026.1.0).
Key information
Attacker addresses (freeze list, published by Harmony at 05:25 UTC Aug 12):
one1uap8dx2z0qsjxqthm5flgcxkeepsz3gsrghnfn (0xe7427699427821230177dd13f460d6ce43014510)
one17u300a40ll5wphd8kj5hktryhdjq3ml9f4phy4 (0xf722f7f6afffe8e0dda7b4a97b2c64bb6408efe5)
one1a5hur07z5vtvzhr35zkw8tfqedemkz8t88xgd7 (0xed2fc1bfc2a316c15c71a0ace3ad20cb73bb08eb)
one1h56hkxmua0uzfv07fu04cudvtrl35u96pq47vy (0xbd357b1b7cebf824b1fe4f1f5c71ac58ff1a70ba)
Attack transactions (on-chain data via explorer.harmony.one; note the rollback discarded these blocks, so the links may no longer resolve):
Exploit Analysis
The intended CX flow:
A user submits a cross-shard transfer on the source shard; the block is finalized by a BLS-quorum of that shard’s own committee (one FBFT instance per shard, as above).
The user assembles a CXReceiptsProof - receipts, Merkle proof, source Header, CommitSig/CommitBitmap.
The destination shard verifies the aggregate signature and quorum against the committee of the source header’s epoch, checks IsSpent(), then ApplyIncomingReceipt credits the recipient’s balance via AddBalance, and the proof is written as spent.
The attack, reconstructed from the vulnerable code above and the on-chain transactions below:
The attacker takes a genuine, already-applied CXReceiptsProof from a pre-Bloom source block - chain history supplies these freely.
Keeps Header and CommitSig byte-identical, so the signature still verifies; with the quorum flaw, even a fabricated pre-staking header could pass without any real validator signature.
Mutates only MerkleProof.ShardID and MerkleProof.BlockNum. Each mutation yields a fresh key to the legacy IsSpent() lookup - the replayed proof reads as unspent.
The destination shard credits the receipts again with no source-side debit: native ONE inflation. The block’s IncomingReceipts field moves balances even in otherwise empty blocks - the likely origin of early “minted via empty blocks” framing (Juiceberg).
Loop with new mutations for unlimited mints.
The patch, merged within hours, addresses both gaps directly. Commit 7515262f replaces the committee-size count with a count of bitmap bits actually enabled:
consensus/quorum/verifier.go (v2026.1.1)
And commit 61afbf65 drops the epoch gate so the spent marker is always keyed off the signed Header:
core/blockchain_impl.go (v2026.1.1)
Harmony confirmed the replay-protection failure as root cause but has not confirmed which of the two flaws the attacker relied on: the quorum flaw in IsQuorumAchievedByMask() and the replay path in the receipt stack are complementary gaps in the same verifier/receipt stack (per Harmony’s official incident updates).
Chronologically:
Before 23:25:37 UTC, Aug 11 - the last known pre-attack state; the exact first exploited block has not been published.
~01:00 UTC, Aug 12 - ONE price begins declining on OKX, ~30 minutes before any public claim.
01:42 UTC - analyst Juiceberg posts the first alert: ~4B ONE minted via two empty-block transactions (1B + 3B), ~2.8B ONE already moved to CEX deposit wallets with roughly 115M ONE left to sell on-chain (Juiceberg, independent on-chain observation; Harmony’s first official statement followed at 04:26 UTC); the totalSupply endpoint did not reflect the new tokens, making the inflation invisible to standard feeds.
04:26 UTC - Harmony confirms and coordinates exchange freezes; patch and rollback options in progress (@harmonyprotocol).
05:25 UTC - four-address freeze list published, asking exchanges to block and freeze funds tracing back to it.
06:09-06:30 UTC - fix commits authored and merged via PR #5101; signed release v2026.1.1 published.
06:39 UTC - bridge.harmony.one paused.
08:07 UTC - third-party reports put the forged total above 3 trillion ONE from six abnormal blocks (individual block numbers never published); Harmony’s own reconstruction later confirmed ~3.01T ONE forged (@harmonyprotocol).
09:44 UTC - ONE trades down ~36% on secondary markets.
Aug 17-18 - after evaluating targeted burns, blacklisting, selective replay, and token migration, Harmony rolls back: Shard 0 to block 92,730,034 and Shard 1 to block 94,978,278 (state of 23:25:37 UTC Aug 11) (@harmonyprotocol), discarding 141,628 Shard-0 blocks including 109,126 legitimate user transactions. 477 of 534 attempted attacker transfers of 5B ONE each (~2.385T ONE) had succeeded in a 106-second window (@harmonyprotocol); >99.9% of forged flows were traced, with law enforcement and exchanges coordinating. Amounts frozen by exchanges were never disclosed, and a rollback cannot reverse off-chain trades - no verified realized-loss figure exists.
Conclusion
Unbound Merkle proof identity: MerkleProof.ShardID/BlockNum are unsigned duplicates of fields in the signed header, and ValidateCXReceiptsProof() never binds them together for pre-Bloom epochs - a byte-identical signed header paired with mutated proof identity reads as unspent.
Consensus-level replay across the Bloom fork: the stricter header-keyed spent marker was gated on the epoch inside the proof’s own header, so every genuine pre-fork receipt stayed on the weak legacy path - an unbounded native-ONE mint one month after the “hardening” fork shipped. Fixed in 61afbf65 by always keying off the signed header.
Committee-size-counted quorum: IsQuorumAchievedByMask() compared len(mask.Publics) - the full committee key list - against the threshold instead of counting bits set in the signer bitmap, so an all-zero bitmap with an identity aggregate signature satisfied “quorum” for a pre-staking committee. Fixed in 7515262f.
Detection blind spot: the totalSupply endpoint never reflected the minted ONE while tokens were already moving to exchanges, delaying recognition of a supply-inflation attack.
Consensus-critical verification logic - quorum counting and replay bookkeeping - deserves the same adversarial review and regression-test rigor as bridge contracts, and any hardening gated on a message-supplied field must treat that gate as attacker-controlled. The regression tests added alongside v2026.1.1 (TestUniformVerifierQuorumByMask, TestIsSpentIgnoresMutatedMerkleProofIdentity) encode exactly these lessons.





