
Scientific and technical article. Subject: cryptanalytic fact of entropy reduction during BIP39 mnemonic generation.
Annotation
GHSA-rg76-677x-56q9 (CVE-2026-71851) describes a critical historical vulnerability in [ CryptoJS.lib.WordArray.random()unclear]: in affected versions of CryptoJS, a function formally requesting cryptographically significant random bytes generated them using a variant of the Multiply-With-Carry (MWC) pseudorandom generator, initially dependent on [ Math.random()unclear]. For a number of downstream wallets, this became the source of entropy for BIP39 mnemonics. The cryptanalytic consequence is not a “hashing weakness” or a secp256k1 flaw, but a sharp reduction in the set of achievable initial secrets: subsequent PBKDF2, hash functions, and the deterministic HD tree are unable to produce entropy that was missing from the input.
Ill Bloom’s investigation linked this flaw to the practical recovery of mnemonics, derivation of keys via relevant derivation paths, and matching of the resulting addresses with public blockchain data. This incident illustrates a fundamental principle of applied cryptography: the strength of a composition is limited by its weakest link, and for generating a long-term secret, the quality of the random source is more important than the nominal length of the result.
Status and scope
| Parameter | Meaning |
|---|---|
| Identifiers | GHSA-rg76-677x-56q9; CVE-2026-71851. |
| Plastic bag | crypto-js (npm). |
| Affected versions | < 4.0.0, but the operational significance depends on the actual usage WordArray.random() for security-sensitive values. |
| Corrected versions | >= 4.0.0; generation has been transferred to the platform’s native cryptographic API. |
| Weakness classes | CWE-331 (insufficient entropy), CWE-334 (small random space), CWE-338 (cryptographically weak PRNG). |
| Risk | Critical for secrets generated in a vulnerable way: mnemonics, keys, tokens, salts, or other long-lived values. |
Assertion boundary. The presence of a dependency crypto-js < 4.0.0 alone does not prove a compromise. A causal chain is required: a specific historical version of the application must call the vulnerable function precisely when the sensitive secret is created.
Technical reason
A historically vulnerable implementation appeared in the 3.x line as an attempt to improve the previous random generation behavior. Instead of the system CSPRNG, it used a custom MWC construct seeded with the value Math.random(). MWC may be acceptable for simulation or non-critical tasks, but it does not provide cryptographic unpredictability with this initialization method.
Conceptually, MWC maintains an internal state and combines the lower part of the product with a carry at each step. One typical form is written as follows:
t_i = a x_i + c_i
x_{i+1} = t_i mod 2^w
c_{i+1} = floor(t_i / 2^w)
Here a , is the multiplier, w is the word width, x_i is the state, and c_i is the carry. This formula does not specify the exact implementation of a specific version of CryptoJS; it illustrates the principle: the output is determined by a small state. If the initial state comes from a source with limited or predictable entropy, the length of the resulting byte array does not increase the number of independent state variants.
Why Math.random() is unusable
ECMAScript doesn’t require Math.random() a CSPRNG, doesn’t define a portable cryptographic strength level, and doesn’t intend it for keys or mnemonics. Browser and engine implementations have historically varied, and the algorithm, state, and initialization may have changed. Therefore, code relying on it to create a secret violates the cryptographic threat model already at the API boundary.
// Небезопасно: пример анти-паттерна
const entropy = CryptoJS.lib.WordArray.random(32);
// Нельзя использовать entropy как источник seed, ключа, nonce или токена.
Historical context. This family of bugs is well known: the Debian OpenSSL incident (2006–2008) made the keyspace predictably small due to a flaw in PRNG initialization; in the crypto wallet ecosystem, the Milk Sad incident and the Trust Wallet extension vulnerability, also related to weak randomness, had similar consequences. In all these cases, the mathematical security of RSA, ECDSA, or BIP39 was not directly “broken”: the attacker brute-forced an insufficient initial secret space.
Cryptanalytic model
Let’s say an application requests n bytes, or nominally 8n bits. An ideal generator would yield a space of 2^(8n) equally probable outcomes. If the vulnerable design is effectively bounded by the effective entropy h, the number of practically achievable variants is close to:
|S_eff| ≈ 2^h, H_min(X) = -log_2(max_x Pr[X = x])
For the implementation under discussion, the advisory specifies approximately h ≈ 39 128 bits for the nominal 128 bits and h ≈ 47 256 bits for the nominal 256 bits. Therefore, increasing the length of the requested array from 16 to 32 bytes does not yield the expected 128 additional independent bits: the attacker explores the state space and deterministically obtains candidate outputs.
| Nominal request | Expected CSPRNG model | Ill Bloom Rating | Cryptanalytic effect |
|---|---|---|---|
| 128-bit entropy | 2^128 options | near 2^39 | Transition from astronomical space to a finite space suitable for specialized enumeration. |
| 256-bit entropy | 2^256 options | near 2^47 | The added bytes are a function of the small state, not new independent random bits. |
For an equiprobable space, the average number of tests before success is approximately:
E[T] ≈ 2^(h-1)
The actual cost depends on mnemonic generation, PBKDF2, networks, dictionary languages, HD derivation paths, batch processing, and the ability to filter out candidates using known addresses. Therefore, this h is a key parameter, but not a complete attack time estimate.
Wallet chain
In BIP39, the original entropy ENT is supplemented by a checksum CScalculated from the hash, after which the bits are encoded as word indices. For the standard 12-word mnemonic ENT = 128, CS = 4, and , the total is 132 bits, divided into 12 groups of 11 bits:
CS = first_ENT/32_bits(SHA256(ENT)) MS = ENT || CS N_words = (ENT + ENT/32) / 11
The checksum verifies the integrity of the representation but does not add any secret entropy. Therefore, from each weak PRNG candidate, a syntactically correct BIP39 phrase can be constructed, and then the seed can be obtained using PBKDF2-HMAC-SHA512:
seed = PBKDF2-HMAC-SHA512(password = mnemonic,
salt = "mnemonic" || passphrase,
iterations = 2048,
dkLen = 64)
Next, a BIP32/BIP44-like derivation transforms keys and addresses into a deterministic seed function. The observed blockchain address becomes a verification oracle: if the derived address matches a historical address, the candidate seed is identified with a high probability.
(k_i, c_i) = CKDpriv((k_parent, c_parent), index_i) P_i = point(k_i) address_i = Encode(Hash/Script(P_i))
Here, cryptanalysis doesn’t solve ECDLP or reverse SHA-256. It exploits the fact that the search begins in a greatly reduced set of valid addresses ENT, after which standard public derivation algorithms reproduce the addresses.
Practical verification
According to Ill Bloom’s description, the researchers replicated the generator, enumerated the achievable outputs, converted them into mnemonics, deduced keys and addresses across various paths and networks, and then compared them with public blockchain activity. The process doesn’t require entering someone else’s seed phrase: public addresses and locally generated candidates are used for the search.
# Псевдокод исследовательского конвейера (не инструмент извлечения средств)
for state in feasible_prng_states:
ent = vulnerable_prng_output(state, nbytes)
mnemonic = bip39_encode(ent, language)
seed = bip39_seed(mnemonic, passphrase="")
for path in relevant_derivation_paths:
for index in address_range:
address = derive_address(seed, path, index)
if address in public_exposure_or_chain_dataset:
record_for_responsible_validation(address, path)
An archived example of the method. In the brainwallet address studies, the attackers didn’t “crack” secp256k1: they brute-forced predictable passwords, hashed them into key material, and compared the resulting public addresses with the blockchain. Ill Bloom is structurally similar; the source of the small space is different—a weak PRNG instead of a human-readable phrase.
Observed exploitation
Coinspect reports that the investigation began in May 2026 amid coordinated writedowns; Ill Bloom places the first confirmed on-chain commit on May 27, 2026. The study describes several waves affecting Bitcoin, Ethereum, Polygon, Rootstock, and Tron, and the advisory places a measured lower-end estimate of the stolen assets at approximately $5 million as of July 13, 2026.
The research website lists five confirmed applications: RRWallet, Bexo, NanChat, Bitcoin Libre, and Milo; this list is not exhaustive. Attribution requires a combination of on-chain data, historical source code, dependency graphs, old builds, and reverse engineering, as the address alone does not reveal the application that generated the seed.
Why KDF doesn’t save
A common mistake is to think that PBKDF2 or SHA-256 “strengthen” weak randomness. KDF increases the cost of verifying each candidate, but its output remains a deterministic function of the input. If X it only accepts 2^h feasible values, then the set F(X) contains at most 2^h values:
|Im(F(X))| ≤ |Supp(X)| ≤ 2^h
PBKDF2 may increase the time of a single search, but it doesn’t transform approximately 2^39 or 2^47 possible sources into 2^128 independent 2^256 secrets. Similarly, the BIP39 checksum discards incorrect encodings but doesn’t expand the original entropy space.
Persistence of compromise
Updating the package fixes future generation but does not change the seed already generated by the weak trajectory. Importing the old mnemonic into an updated or hardware wallet also preserves the same deterministic root and the same potentially brute-forceable keys.
The only meaningful remediation for such a secret is to create a new wallet with a new mnemonic from a trusted CSPRNG and transfer assets to the addresses of the new seed. It is necessary to consider all assets and networks, as the same BIP39 phrase can generate addresses in multiple ecosystems and via multiple paths.
Audit for developers
- Inventory direct and transitive dependencies, lock files, build archives, CDN bundles, Electron/mobile releases, and forks for
crypto-js < 4.0.0. - Search not only for the package version, but also for calls
CryptoJS.lib.WordArray.random(), wrappers around it, and places where their results are sent to generate keys, mnemonics, seeds, reset tokens, API keys, session identifiers, or salts. - Recover the timeline: commit, release, publication date, distribution channels, and versions in which the vulnerable path was reachable during the initial wallet initialization.
- Verify the actual data flow: A test or reverse engineering should confirm that the weak generator was used to generate the secret, and not just present in the bundle.
- For affected secrets, perform a managed rotation: issue new values, migrate funds, revoke old tokens, notify users, and document residual risk.
# Примеры безопасной генерации: браузер и Node.js
// Browser
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
// Node.js (ESM)
import { randomBytes } from 'node:crypto';
const bytesNode = randomBytes(32);
Operational example. For a web wallet, a secure pipeline looks like this: system CSPRNG → BIP39 entropy → mnemonic → user backup → deterministic keys. An audit should record the origin of the first element, not just the validity of the mnemonic in subsequent steps.
User actions
- Do not enter your seed phrase, private key, password, or backup into verification sites or support forms.
- If a public address is found in the Ill Bloom seed, consider the seed compromised and immediately create a new wallet with a new phrase, then transfer the assets.
- Verify more than just the main network: a single seed can control addresses across multiple networks, accounts, and derivation paths.
- Do not consider a negative public checker result as proof of security: published address sets are not required to cover all applications, mnemonic languages, paths, and index ranges.
- For new long-term reserves, consider a hardware wallet or a software product that uses the platform’s native CSPRNG and has been independently audited.
Scientific findings
Ill Bloom is an empirically significant example of implementation cryptanalysis. The attack does not invalidate the security of BIP39, PBKDF2, HMAC-SHA512, BIP32, or the elliptic curve secp256k1 in their target models. It demonstrates that these algorithms do not compensate for the violation of the high-min-entropy initial secret assumption.
Methodologically, this case highlights the need for long-term supply chain analysis: dangerous code may disappear from the current version of the library but remain in older wallet releases, forks, minified bundles, and already generated user backups. For cryptographic applications, the audit should cover the origin of entropy, the secret lifecycle, historical artifacts, and possible on-chain verification of consequences.
Sources
GHSA-rg76-677x-56q9: Ill Bloom Cryptanalytic Analysis: https://investprice.ru/ghsa-rg76-677x-56q9-ill-bloom-cryptanalytic-analysis/
- GitHub Security Advisory GHSA-rg76-677x-56q9, CryptoJS.lib.WordArray.random() uses a weak PRNG (Ill Bloom) , CVE-2026-71851.
- Coinspect, Ill Bloom: Investigating a Wallet Generation Vulnerability During Active Exploitation , 2026.
- Ill Bloom Research, Crypto Wallet Vulnerability : Technical Disclosure, Address Set Methodology, and FAQ, 2026.
- BIP-39: Mnemonic code for generating deterministic keys; BIP-32: Hierarchical Deterministic Wallets.
- NIST SP 800-90A Rev. 1: Recommendation for Random Number Generation Using Deterministic Random Bit Generators.
