A cross-chain bridge lost 199,916.3 XRP on Sunday. Every payment that took it out was correctly signed.
I pulled the account. 94 payments, all successful, 19:26:30 to 20:53:50 UTC on 9 August. Every one of the 94 carries exactly 17 signatures. The bridge's signer list on the XRP Ledger reads: quorum 17, 28 keys, each weighted 1.
So the multisig worked. Seventeen relayers looked at a deposit, agreed it was real, and released the money. Ninety-four times in a row.
The relayer code is public. `relayer/processes/xrpl_to_coreum.go`, in the Coreum bridge repo, and the whole thing hangs on one function that decides whether an XRPL transaction is a deposit.
What that function checks, in order: the result is success, the type is Payment, the memo parses, the amount converts, the amount isn't zero.
What it never checks: who the payment was sent to.
Grep the file for `Destination`. Zero hits. The bridge's own address is used once in it, on line 100, where it gets compared against the *sender* to work out whether the bridge sent the transaction. Didn't send it? Then it's incoming. That's the whole test.
Now the part that makes this exploitable rather than merely sloppy. The relayer scans one account's transaction history, the bridge's own. On the XRP Ledger, an issuer's history includes every payment that rippled through it. So if the bridge once issued you a token, and you turn around and send that token from one of your wallets to another, your transaction lands in the bridge's feed next to the real deposits and looks exactly like them under every test the code actually runs.
I counted. In the four hours around the attack, 4,423 transactions hit that feed. 4,312 of them ran between two parties, neither of which was the bridge. That's 97.5%.
That's the feed 28 relayers trusted. And the memo they read to find the recipient is unauthenticated JSON, `{"type":"coreumbridge-xrpl-v1","coreum_recipient":"core1..."}`, which anyone can staple to any payment.
So: hold a bridge-issued token. Send it to yourself. Attach that memo. Seventeen relayers see a successful payment carrying a valid bridge memo for a non-zero amount, and not one of them asks where the money actually went, because the code all of them run was never written to ask.
Two wallets opened trust lines to the bridge's token at 17:57:50 and 17:57:52 that afternoon, two seconds apart. An hour and a half later the XRP started moving. It stopped at 493.543894 XRP, which is still the balance today.
The number I keep coming back to is the 17. It's a real control and it did its job. A 17-of-28 threshold means an attacker needs seventeen keys, and this attacker had none. It's the right defence against a stolen key, against a bribed operator, against eleven relayers going bad at once, and against every other threat where the danger is that somebody signs who shouldn't.
What it cannot defend against is 28 honest relayers running the same code and asking the same wrong question. Nobody was fooled about who signed. They were fooled about what they were signing for, all of them at once, because the check that would have caught it was never written.
Splitting a signature 28 ways doesn't split the reasoning. Every relayer runs the same test on the same input and gets the same answer, so a 17-key quorum is one decision wearing 28 hats. Agreement isn't verification.
Tx, which runs the bridge, halted it, says it has fixed the code, hired forensics people, filed an FBI complaint. Fast, and fair enough. But the public repository hasn't taken a commit since 5 September 2025, and that function is still sitting on `master` today with no destination check in it.
Go read it yourself. Being open is the whole point of it being open.