
Material type: scientific and technical article for WordPress.
Primary source: V8 JavaScript Engine Team (Google), official V8 blog, December 2015.
Object of analysis: the implementation transition Math.random() in V8 from MWC1616 to xorshift128+ and its cryptanalytic significance.
Annotation
This article examines the historical algorithm transition Math.random() in the V8 JavaScript engine: from the MWC1616 generator, used until late 2015, to xorshift128+. It is shown that increasing the internal state from 64 to 128 bits, increasing the period to 128 bits, 2^128 - 1 and passing the TestU01 statistical tests improves modeling quality and eliminates obvious structural defects of the previous implementation, but does not transform the API into a cryptographically secure source of randomness.
The main cryptanalytic conclusion is the distinction between statistical “randomness” and adversarial unpredictability. Linear XOR generators and finite-state generators can be reversible or recoverable from their outputs; keys, nonces, tokens, salts, and signature materials require a CSPRNG, accessible in the browser via crypto.getRandomValues().
Keywords: V8, JavaScript, Math.random, MWC1616, xorshift128+, PRNG, CSPRNG, entropy, generator state, TestU01, cryptanalysis, nonce, ECDSA.
Statement of the problem
The ECMAScript specification defines only the external contract: Math.random() return a floating-point number from a half-interval [0, 1) with an approximately uniform distribution; the choice of algorithm is left to the implementation. Therefore, the same JavaScript code could historically use different generators in different browsers and engine versions.
For a typical simulation, game, or random sort, reasonable statistics are sufficient. In cryptography, the requirements are more stringent: the observer must not be able to distinguish a future output from a random one, reconstruct the state from observations, or try the initial state with a feasible cost.
PRNG model
A pseudorandom generator is defined by a state, a deterministic transition function, and a yield function. If the initial state is fixed, the entire sequence is fixed; randomness is present only in the way the state is initialized.
s_(i+1) = F(s_i) r_i = G(s_i) where s_i is the internal state, F is the state transition, G is the transformation of the state into the observed output r_i.
For a state size of n bits, the maximum cycle length does not exceed 2^n, but this is only a theoretical upper bound. Actual cycles can be significantly shorter due to the structure of the transition function and “poor” initial states.
Cryptanalytic interpretation
The security of a PRNG isn’t determined solely by its period. A generator with a huge period can remain predictable if its state is reconstructed algebraically or from a small number of outputs. In particular, linearity over the field GF(2) makes XOR-shift constructions convenient for fast computations, but requires caution: linear recurrence relations are often used precisely in state reconstruction attacks.
A historical example: the Debian OpenSSL vulnerability (CVE-2008-0166) demonstrated the practical effect of predictable randomness: the generated cryptographic material could be guessed, and keys used for SSH, OpenVPN, DNSSEC, X.509, and TLS sessions were considered vulnerable. The bug wasn’t an “RSA break” or an “AES break,” but rather a violation of the assumption of an unpredictable source of randomness.
MWC1616 in V8
Prior to V8 4.9.40, MWC1616 (multiply-with-carry) was used, combining two 16-bit carry branches. In simplified form, each branch updates a 32-bit variable: the low-order 16 bits are multiplied by a constant, and the high-order 16 bits are added as a carry.
uint32_t state0 = 1;
uint32_t state1 = 2;
uint32_t mwc1616() {
state0 = 18030 * (state0 & 0xFFFF) + (state0 >> 16);
state1 = 30903 * (state1 & 0xFFFF) + (state1 >> 16);
return (state0 << 16) + (state1 & 0xFFFF);
}
x_(t+1) = a · (x_t mod 2^16) + floor(x_t / 2^16) y_(t+1) = b · (y_t mod 2^16) + floor(y_t / 2^16) R_t = ((x_(t+1) mod 2^16) << 16) + (y_(t+1) mod 2^16)
In the V8 publication, the code is presented as a schematic description; in C/C++, when reproducing, the parentheses in the shift and addition expressions must be explicitly included. This is important for auditing: cryptographic forensics must analyze the precise semantics of the source code, types, overflows, and order of operations, not just pseudocode.
MWC1616 defects
| Property | V8 Observation | Cryptanalytic investigation |
|---|---|---|
| Internal state | 64 bits, two branches of 32 bits. | The state size alone does not guarantee 64 bits of unpredictability; output structure and leakage are critical. |
| Exit space | Only about 2^32 distinct values are available instead of up to 2^52 representable fractional double values in the range [0,1). | Granularity decreases and structural heterogeneity of the outputs appears. |
| Most significant bits | The senior half of the result is almost completely determined state0. | The independence of bit components is violated; observations can further narrow the state space. |
| Period | No more than 2^32; many short cycles, in case of an unsuccessful state – less than 40 million values. | Repetitions and the state driven into a small cycle are unacceptable for long processes and are especially dangerous for secret values. |
| Statistical tests | Fails multiple TestU01 tests. | The observed patterns refute the suitability even for a number of high-quality non-cryptographic problems. |
Practical example
Consider a 2015 web application that generates a password recovery link as Math.random().toString(36). Even if the format doesn’t expose the entire internal output, security depends on the predictability of the state, the execution environment, and the number of observable tokens. MWC1616’s limited output space and structural dependency of the high-order bits introduce additional risk, so this scheme shouldn’t be considered an authentication mechanism.
Transition to xorshift128+
The V8 team replaced MWC1616 with xorshift128+; the change was included in V8 4.9.41.0 and became available with Chrome 49. The algorithm uses 128-bit state and has a period 2^128 - 1 for all non-zero states, and passes the TestU01 benchmark suite specified in the original publication.
// Схематическая форма перехода xorshift128+.
uint64_t s1 = state0;
uint64_t s0 = state1;
state0 = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 17;
s1 ^= s0;
s1 ^= s0 >> 26;
state1 = s1;
// Вариант выдачи зависит от версии реализации.
output = state0 + state1;
s_1 <- s_1 xor (s_1 << 23) s_1 <- s_1 xor (s_1 >> 17) s_1 <- s_1 xor s_0 s_1 <- s_1 xor (s_0 >> 26) Period: P = 2^128 – 1, with (state0, state1) != (0, 0).
The “+” sign in the name xorshift128+ refers to the additive output transformation used to improve the observed quality of the sequence. However, the output transformation does not make the internal transition a cryptographic primitive: the transition relies on XOR and shifts, and thus has a strong algebraic structure.
What has improved?
- The state was increased to 128 bits, which radically increased the maximum period and reduced the likelihood of early repetition in typical non-cryptographic scenarios.
- The transition eliminated specific defects of MWC1616: 32-bit limitation of output diversity, skew of high-order bits and many short cycles.
- The implementation has become better aligned with the capabilities of the double number, which carries up to 52 significant binary digits of the fractional part in the range under consideration.
Why isn’t this a CSPRNG?
Statistical tests answer the question “how similar is a sequence to randomness based on a set of statistics,” but they do not prove resistance against an adaptive adversary. A CSPRNG must maintain the unpredictability of future values even when the attacker knows the algorithm, has observed the outputs, and has significant computing resources.
| Criterion | Regular PRNG | CSPRNG |
|---|---|---|
| The main goal | Speed, reproducibility, acceptable statistics. | Computational indistinguishability from a random sequence. |
| Knowledge of the algorithm | Often admitted, but predictability of the state is not the intended protection. | It is assumed according to Kerckhoffs’ principle; the secret remains the state and entropy. |
| Restoring the state | It may be practically feasible with yields or weak seeds. | Should be computationally infeasible when implemented correctly. |
| Application | Games, Monte Carlo, interface effects, sampling. | Keys, nonce, IV, tokens, salts, padding, signatures. |
Critical Bound: The period 2^128 - 1 is not equal to “128 bits of cryptographic strength.” The period describes the cycle length, while cryptographic strength describes the difficulty of predicting and restoring the state in an attack model.
Cryptanalytic scenario
For an XOR-shifted generator, state transitions are expressed by linear operations on bits. If the application reveals sufficiently informative sequential values or their bits, a cryptanalyst can construct a system of equations over GF(2), reconstruct a valid state, and then emulate the generator. For partially observable double outputs, the task is complicated by the transformation format and bit discarding, but this does not create a cryptographic guarantee: security cannot be based on the complexity of a specific leakage format.
For the linear transition model: S_(t+1) = A · S_t over GF(2) Observations are given by: O_t = B · S_t Accumulating a sufficient number of independent equations may allow S_t to be found using linear algebra methods.
A historical analogy: the 2008 Debian OpenSSL compromise illustrates a general class of failure: when the real entropy space is small or the generator is predictable, correct cryptographic primitives produce weak keys. The consequence extends to all protocols using such values, not to a single library.
Precision and display of double
Math.random() returns an IEEE 754 double, not the generator’s raw 64-bit word. Therefore, the sequence undergoes a word-to-number mapping layer in [0,1). In the historical implementation of MWC1616, the 32-bit output limited the number of achievable values to approximately 2^32, although the double format is capable of resolving up to 2^52 fractional values of appropriate precision.
u = k / 2^m, where 0 <= k < 2^m. For a grid of 52 binary digits: m = 52, the number of possible values is 2^52.
In cryptographic auditing, this distinction is important for two reasons: discretization can reduce the entropy of the output value, and float transformations should not be mistaken for protection against prediction. The protection is determined by the properties of the random source before serialization and transformations.
Historical chronology
| Period | Event | Meaning |
|---|---|---|
| Until the end of 2015 | V8 uses MWC1616 up to version 4.9.40. | A fast, compact, but statistically weak implementation Math.random(). |
| November–December 2015 | The V8 is getting xorshift128+. | Transition to 128-bit state and period 2^128 - 1. |
| Chrome 49 | The updated implementation is becoming available to browser users. | The behavior change depends on the engine version, not the JavaScript source code. |
| V8 7.1 | The implementation has been adjusted; the V8 publication refers to the source code for details. | You can’t impose cryptographic assumptions on an API name: the version and the specific implementation matter. |
Safe JavaScript Practices
For secret values, the web application must obtain bytes from the Web Crypto API. This method crypto.getRandomValues() is designed for cryptographically strong random values; the bytes are then encoded into the required format without losing control over length and distribution.
// 256 бит случайности для токена или симметрического ключевого материала.
function secureRandomHex(byteLength = 32) {
const bytes = new Uint8Array(byteLength);
crypto.getRandomValues(bytes);
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
}
const token = secureRandomHex(32); // 64 hex-символа, 256 случайных бит
H_min(X) = -log_2(max_x Pr[X = x]) For a uniform n-bit value: H_min(X) = n.
The example above produces 32 bytes. If the source works correctly, this value represents the space 2^256 of possible strings; the hex encoding is just a representation of the bytes and should not be replaced by generating “random” characters using Math.random().
Nonce for ECDSA
In ECDSA, the one-time secret k must be unpredictable and unique. If the same one k is used for two signatures with different hashes, the private key can be recovered from the public signature data; this is a classic example of how a generator flaw destroys the security of elliptic curve cryptography.
s = k^(-1)(z + r d) mod n For two signatures with the same k: k = (z_1 – z_2)(s_1 – s_2)^(-1) mod nd = (s_1 k – z_1)r^(-1) mod n
Here d , is the private key, z is the message hash, (r,s) are the signature components, n and is the group order. Therefore, Math.random() cannot be used for generating either kprivate keys or nonces in signature schemes.
Audit checklist
- Consider any use
Math.random()in generating tokens, password reset links, keys, nonces, IVs, salts, and sessions a potential vulnerability until proven otherwise. - Check not only the function name, but also the engine version, browser version, runtime version, initialization method, and all output byte transformations.
- Use
crypto.getRandomValues()in the browser; on the server, use the system CSPRNG and proven cryptographic APIs. - Do not substitute requirements for unpredictability with statistical tests, long periods, or the absence of visible repetitions.
- If you detect historical use of a weak generator, revoke and reissue secrets: replacing the code is not enough if keys and tokens were already generated from weak randomness.
Conclusion
The 2015 V8 update was a significant engineering improvement: xorshift128+ fixed the low quality and short cycles of MWC1616 for its intended non-cryptographic use Math.random(). However, the cryptanalytic criterion remains unchanged: a deterministic PRNG with observable outputs and unprotected state should not be a root of trust for secrets.
The practical formula is simple: Math.random() – for modeling and interface randomness; crypto.getRandomValues() – for everything on which confidentiality, authentication, non-forgery, or monetary value depends.
Sources
PLEASE SELECT TWO DISTINCT LANGUAGES: https://nordai.ru/please-select-two-distinct-languages/
- V8 JavaScript Engine Team. There’s Math.random(), and then there’s Math.random() . Official V8 blog, December 2015. https://v8.dev/blog/math-random
- V8 repository. Commit
085fed0, Implement xorshift128+ for Math.random , November 23, 2015. https://github.com/v8/v8/commit/085fed0fb5c3b0136827b5d7c190b4bd1c23a23e - CERT/CC. Vulnerability Note VU#925211: Debian and Ubuntu OpenSSL packages contain a predictable random number generator , 2008. https://www.kb.cert.org/vuls/id/925211/
- NIST National Vulnerability Database. CVE-2008-0166. https://nvd.nist.gov/vuln/detail/CVE-2008-0166
