The bridge verified withdrawals with an IAVL Merkle proof from the Cosmos library. The verifier confirmed that the proof's computed root matched the trusted root — but never confirmed that every leaf inside the proof was actually part of that computation. The attacker appended a forged leaf node that the hash walk simply never visited. Root matched. Payload was his. He minted himself 1,000,000 BNB, twice. The same flawed library sat under a large part of the Cosmos ecosystem.
func (proof *RangeProof) Verify(root []byte) error { rootHash, err := proof.computeRootHash() // walks only the nodes it needs if err != nil { return err } if !bytes.Equal(rootHash, root) { return ErrInvalidRoot } return nil // ❌ never asserts that every leaf in proof.Leaves was consumed // by the walk — an extra, unvisited leaf changes nothing about // the root hash, but the caller reads it as "proven". } // The forged proof: leaves = [ real_leaf , forged_leaf ] // forged_leaf: "send 1,000,000 BNB to me" computeRootHash(leaves) == trusted_root // ✅ passes → result: 2,000,000 BNB released against a proof of nothing
Entries in the SAFE database that describe this failure. These share its failure class.
Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.
Input validation is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.