TL;DR
Between 23:42 UTC on October 17 and 01:26 UTC on October 18, 2026, an attacker drained 1,404 Qubic wallets of about 2.94 trillion QU, worth roughly $1.3M to $1.4M depending on the hour you price it. Qubic says the seeds behind those wallets had insufficient randomness, and only seeds made on iPhones and iPads, in Safari on a Mac, or in its older iOS app were affected. The fix commit shows the cause: the web wallet built every 55-letter seed from JavaScript's Math.random(), and it had done so since the repository's first commit in February 2024. SlowMist showed that the same random stream also leaked into network packet headers. Our reading of the engine source explains the Apple-only victim list: WebKit seeds its Math.random() generator from a single 32-bit number, so a seed made in Safari had at most about 4.3 billion possible values. Validators halted the chain within seven minutes of the last drain, moved the stolen funds into escrow, and restarted 42 hours later with a claims portal. Qubic has not published its technical report yet. On this reading, the strength of a browser-generated key depended on which engine the user ran, which no wallet team controls.
What Happened to Qubic Wallets on October 17?
Qubic is a layer-1 chain whose validators are called Computors. Its incident thread on X gives the core facts. Over a window of about 104 minutes, 1,404 wallets were emptied, totalling around 2.94 trillion QU. Qubic placed the issue in seed generation, with the protocol itself unaffected, and listed the affected seeds as those created:
- on an iPhone or iPad, in any browser, before October 18, 2026
- on a Mac using Safari, before October 18, 2026
- in the Qubic iOS app, before July 2025
Computors coordinated a halt at tick 85,371,949. Qubic's own RPC stamps that tick at 01:32:52 UTC on October 18, within seven minutes of the last drain. According to Qubic, 87.1% of the drained QU never left the network and stayed frozen in place. The other 12.9% went to MEXC, which froze the receiving account within about an hour, and exchanges suspended QUBIC deposits and withdrawals.
How Did the Qubic Web Wallet Generate Seeds?
A Qubic seed is 55 lowercase letters. SlowMist's analysis traces the derivation: the letters are mapped to values, hashed with K12 into a 32-byte private key, and SchnorrQ produces the public key. Nothing else goes into it. No password, no device key, no second entropy source. Whoever can predict the 55 letters holds the wallet.
The wallet picked those letters like this, in the create-vault component as it stood in the initial commit:
private seedGen(): string {
const letters = 'abcdefghijklmnopqrstuvwxyz';
const letterSize = letters.length;
let seed = '';
for (let i = 0; i < 55; i++) {
seed += letters[Math.floor(Math.random() * letterSize)];
}
return seed;
}On paper that is 26^55, about 258 bits. In practice the ceiling is whatever state sits behind Math.random(). MDN's reference is blunt about it: the function does not provide cryptographically secure random numbers, do not use it for anything related to security, use Crypto.getRandomValues() instead. The class has a name, CWE-338, use of a cryptographically weak PRNG.
The fix replaced both copies of seedGen() with a shared generateSeed() that calls crypto.getRandomValues(), rejects byte values 234 to 255 to avoid modulo bias, and throws if Web Crypto is missing. It shipped as version 4.26.4 in the wallet's changelog, tagged on October 18 (Qubic's restart thread dates the release to October 19). It is a good patch, and like any generator fix it only protects seeds made after it.
Why Were Only Apple Devices Affected?
Qubic named the platforms without saying why. The JavaScript engines suggest an answer.
Chrome and Node run V8. V8 moved Math.random() to xorshift128+, a generator with 128 bits of internal state, while warning that it is still not cryptographically secure. SlowMist notes that V8 derives that state from a single 64-bit value, so a V8 stream has at most 2^64 starting points. That is weak for key material and still far too many to search blindly.
Safari runs JavaScriptCore, and its generator is WebKit's WeakRandom. The constructor in WeakRandom.h takes an unsigned seed, and setSeed() copies that one 32-bit value into both halves of the 128-bit state before advancing it. JavaScriptCore's Math.random returns the global object's weakRandomNumber(), which reads from that generator. A page's entire Math.random() stream is fixed by one 32-bit number, which leaves 4,294,967,296 possible streams.
Now map that onto Qubic's list. On iPhone and iPad, every browser runs WebKit. Apple only lets apps use browser engines other than WebKit in the European Union, and only on recent OS versions. On a Mac, only Safari uses WebKit, and Chrome brings V8. "Any browser on iOS, Safari on Mac" is a precise description of where JavaScriptCore generated the seed.
This is our inference from public engine source and Qubic's scoping, not a finding Qubic has published. It also covers only the two browser entries. The third, the Qubic iOS app before July 2025, is a native app, and its seed helper has called Dart's Random.secure() since April 2024. We could not find what changed in July 2025, and Qubic has not said. A 2^32 space is searchable offline. Derive the key for every possible stream at each plausible call offset, compare the resulting identities against those holding QU, and sweep the matches. Milk Sad showed in 2023 that attackers will enumerate exactly this size: Libbitcoin Explorer's Mersenne Twister had at most 2^32 starting positions, and over $900,000 was moved.
The drain pattern fits: 1,404 wallets in 104 minutes, about 13 a minute, looks like a precomputed hit list being swept.
What Did SlowMist Find in the Packet Headers?
SlowMist found a second path, and it is the more unusual bug. The Qubic TypeScript library stamps every request header with a dejaVu value from randomizeDejaVu(), which is Math.floor(Math.random() * 2147483647). That draws from the same stream as seedGen().
The wallet talks to a default bridge at wss://webbridge.qubic.li. Per SlowMist, the bridge terminates TLS and forwards the raw Qubic packets to peers over plaintext TCP on port 21841. Tick requests draw a fresh dejaVu every two seconds, and balance requests every 30 seconds, carrying the user's public key too. Anyone at the bridge, at a peer, or on that plaintext hop gets truncated generator outputs plus an oracle to check guesses against.
SlowMist encoded the interval constraints and the xorshift128+ transition in the Z3 solver. Four or five observations left several candidate states. Six consecutive dejaVu values recovered the full 128-bit state uniquely, in about 15 milliseconds of solving time. From the state, the 55-letter seed follows, forward or backward.
SlowMist is explicit that this was a local proof of concept on V8, and that real-world success depends on bridge and peer trust boundaries and on call alignment. It shows a network observer could, in principle, reach seeds generated even in Chrome. It does not explain why the confirmed victims were all on Apple engines.
The Attack, Step by Step
Steps marked "inferred" are our reconstruction. Qubic has not published the attacker's method.
| Step | What happened | Status |
|---|---|---|
| 1 | Users on iOS browsers and Mac Safari create wallets; seedGen() draws 55 letters from JavaScriptCore's Math.random() | Code in repo since Feb 2024 |
| 2 | Attacker enumerates the 2^32 possible generator seeds and the likely call offsets, deriving a key for each candidate | Inferred |
| 3 | Candidate public identities are compared against identities holding QU, producing a hit list | Inferred |
| 4 | From 23:42 UTC on Oct 17 to 01:26 UTC on Oct 18, 1,404 wallets are drained of about 2.94 trillion QU | Confirmed by Qubic |
| 5 | 12.9% of the haul reaches MEXC, which freezes the receiving account within about an hour | Confirmed by Qubic |
| 6 | Computors halt the chain at tick 85,371,949, at 01:32:52 UTC | Confirmed on-chain |
| 7 | Wallet 4.26.4 replaces Math.random() with crypto.getRandomValues() | Confirmed in GitHub |
How Much Was Actually Lost?
The token amount is fixed at about 2.94 trillion QU. The dollar figure depends on when you price it. investingLive reported roughly $1.3M, and Cointelegraph and most other outlets used $1.4M.
We priced it ourselves. CoinGecko's hourly QU prices across the drain window run from about $0.000000474 at 23:00 UTC to $0.000000477 at 00:00 and $0.000000457 at 01:00. Multiplied out, that is $1.39M, $1.40M and $1.34M. The gap between the two reported figures is the token sliding while it was being stolen. We use $1.4M because it reflects the price when the wallets were emptied, before the news reached the market.
How Did a Chain Halt Become a Refund?
The chain stopped at 01:32 UTC on October 18 and stayed stopped. The first tick after the halt, 85,371,952, is stamped 20:00:08 UTC on October 19, about 42 hours later.
Qubic's restart thread says that while the network was down, the affected funds were moved into a protected escrow, the Computor quorum approved the recovery, and the first funds have already gone back to verified owners. Claims run through the official reclaim portal at weak-seed-reclaim.qubic.org until May 1, 2027. Cointelegraph's report carries Qubic's instruction to treat any pre-4.26.4 web wallet as insecure even if it was never touched, create a new one, and move funds out.
For the 1,404 victims, this is a good outcome. It also confirms that a validator quorum on Qubic can move funds that no key signed for (the same lever that saved this money is one any quorum could pull). The restart thread does not say what happens to the 12.9% frozen at MEXC, and replies under it are asking.
Why Does Weak Wallet Entropy Keep Coming Back?
This is the third weak-entropy wallet drain we have written up since August. Ill Bloom was a CryptoJS function seeded from Math.random(). Coldcard was a firmware build that routed seed generation to a software PRNG. Further back, Randstorm traced vulnerable BitcoinJS wallets from 2011 to 2015 to the JSBN library's SecureRandom() combined with weak browser Math.random() implementations.
Qubic is the bluntest of them. In Ill Bloom, the weak call was three dependencies deep. In Qubic, it was nine lines in a component file, readable on GitHub for two and a half years. The usual audit-complacency argument is that an audit only covers the code you showed it. Qubic's problem came earlier: the key generation path of a web wallet is often never put in scope at all, because the contracts get the review budget and the frontend is treated as presentation. In a self-custody web wallet, the frontend is the code that creates the private key.
The engine dependency is the part teams miss. You can test a seed generator in Chrome, see 55 well-shuffled letters, and ship. Its strength is still set by whichever engine each user happens to run, and your Safari users get Safari's generator.
What Should Wallet Teams and Holders Do Now?
- Grep your key path for
Math.random()today. Anything that touches seeds, mnemonics, private keys, nonces or salts must callcrypto.getRandomValues()or the platform CSPRNG directly. Qubic's fix is a good template: rejection sampling to avoid modulo bias, and a hard failure if the secure source is missing. - Treat every non-cryptographic random draw as public. SlowMist's
dejaVufinding is a reminder that a "harmless" request ID drawn from the same stream as a key leaks the key's generator. Use a separate CSPRNG source for anything that ends up on the wire, or a counter. - Test key generation on every engine your users run. At minimum JavaScriptCore, V8, SpiderMonkey and every WebView your app embeds. Outside the EU, every iPhone browser is WebKit.
- Plan the migration before you need it. A patched generator does not repair an existing seed. Know which users made keys on the vulnerable build, and have a guided path to move them to fresh wallets.
- Holders: if you created a Qubic web wallet before 4.26.4, move now. Generate a new wallet on 4.26.4 or later and transfer everything, even if your balance looks untouched. If you were drained, use only the official reclaim portal.
Nothing on-chain could flag a weak seed before it was used. Once the sweep began, it was loud: 1,404 unrelated wallets emptied in 104 minutes, about 13 a minute, with part of the haul heading straight to an exchange deposit. A rule watching for many unrelated wallets draining to the same few destinations fires within the first minutes. Here the halt landed about 111 minutes after the first drain, so faster detection would have cut the loss without preventing it. Prevention sat in the wallet code, and a full-stack dApp audit puts exactly that path in scope: where the seed is generated, which random source feeds it, which engines run it, and what else draws from the same stream. A Math.random() call inside seedGen() is a first-day finding for that review, and fixing it would have kept these 1,404 wallets out of any attacker's search space.
Frequently Asked Questions
What caused the Qubic exploit?
Weak wallet seeds. The Qubic web wallet generated seeds with JavaScript's Math.random(), which is not a cryptographically secure generator. On Apple's JavaScriptCore engine, that generator's whole output stream is fixed by a single 32-bit seed, which is the likeliest reason only Apple-generated seeds were drained. Qubic has confirmed the weak randomness but not this mechanism, and says the protocol itself was not at fault.
How much was stolen from Qubic wallets?
About 2.94 trillion QU from 1,404 wallets. Priced at the hourly rate during the drain, that is $1.34M to $1.40M, which is why reports range between $1.3M and $1.4M. Most of it was frozen on-chain by the halt, and the MEXC portion was frozen by the exchange.
Is my Qubic wallet affected if it was never drained?
Yes, if you created it in the Qubic web wallet before version 4.26.4. Qubic says to treat such wallets as insecure even if funds were never touched. The highest-risk seeds were made on iPhone or iPad browsers, Safari on Mac, or the iOS app before July 2025, but Qubic's guidance covers every pre-4.26.4 web wallet.
Were Chrome users safe?
No, not fully. Qubic did not list them, and V8's 64-bit seed space is far harder to enumerate. But SlowMist's proof of concept recovered the generator state from six observed header values. Migrate on any browser.
Can Qubic victims get their funds back?
Yes, for funds in escrow. The Computor quorum approved a recovery, the first refunds have gone out, and claims are open at weak-seed-reclaim.qubic.org until May 1, 2027. Qubic has not said how the portion frozen at MEXC will be returned.
Sources / References
- Qubic on X: "Security incident update. Between 23:42 UTC on 17 October and 01:26 UTC on 18 October, an attacker drained 1,404 Qubic wallets, totalling around 2.94 trillion QU."
- Qubic on X: "The Qubic network is live again. The Computor quorum approved the recovery, the reclaim platform is open, and the first funds have already gone back to verified owners."
- SlowMist: Recovering Private Keys from 6 Observations: Qubic Wallet Vulnerability Analysis
- qubic/wallet commit 09dd288: fix: use cryptographically secure seed generation
- qubic/wallet initial commit: create-vault.component.ts
- qubic/wallet-app: lib/helpers/random.dart
- qubic/wallet CHANGELOG.md
- WebKit: Source/WTF/wtf/WeakRandom.h
- WebKit: Source/JavaScriptCore/runtime/MathObject.cpp
- V8: There's Math.random(), and then there's Math.random()
- MDN: Math.random()
- CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
- Apple Developer: Using alternative browser engines in the European Union
- Qubic RPC: tick 85,371,949 (halt)
- Qubic RPC: tick 85,371,952 (restart)
- CoinGecko API: QU hourly price, October 17 to 18, 2026
- investingLive: Crypto: Qubic halts network after weak wallet seeds let attacker drain 1,404 wallets
- Cointelegraph: Qubic restarts blockchain, opens recovery claims after $1.4M hack
- Milk Sad: /bx full write-up
- Unciphered: Randstorm: You Can't Patch a House of Cards
- SigIntZero: Ill Bloom: A 12-Year-Old CryptoJS PRNG Bug Drained $5.7M in Guessable Seeds
- SigIntZero: Coldcard's $88.6M Entropy Failure: Five Years of Guessable Bitcoin Seeds



