Stage 1 Draft / May 13, 2025

Seeded Random

Introduction

This is the preliminary formal specification for a proposed SeededPRNG class in JavaScript. It modifies the original ECMAScript specification with several new or revised clauses. See the proposal's explainer for the proposal's background, motivation, and usage examples.

1 SeededPRNG Object

A Seeded PRNG is an object representing a Pseudo-Random Number Generator; it holds a complex internal binary "state", and uses that to generate pseudo-random numbers on command, mutating its state as it does so. Two SeededPRNGs with the same "state" will generate the same sequence of numbers, allowing for reproducible "randomness".

Seeded PRNGs defined by this specification generate random numbers using the ChaCha 12 algorithm.

1.1 The SeededPRNG Constructor

The SeededPRNG constructor:

  • is %SeededPRNG%.
  • is the initial value of the "SeededPRNG" property of the global object.
  • creates and initializes a new Seeded PRNG when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.

1.1.1 SeededPRNG ( init )

This function performs the following steps when called.

  1. If NewTarget is undefined, throw a TypeError exception.
  2. Let result be ?OrdinaryCreateFromConstructor(NewTarget, "%SeededPRNG.prototype%", « [[PRNGState]]»).
  3. If init is a Uint8Array:
    1. If init has length 112, set result.[[PRNGState]] to SeededPRNGStateFromState(init).
    2. Otherwise, if init has length 32, set result.[[PRNGState]] to SeededPRNGStateFromSeed(init).
    3. Otherwise, throw a TypeError.
  4. Otherwise, if init is a Number, set result.[[PRNGState]] to SeededPRNGStateFromNumber(init).
  5. Otherwise, throw a TypeError.
  6. Return result.
Editor's Note
// Seed is 32 bytes
// State is 64 (state) + 32 (seed) + 16 (counter) = 112 bytes
// Number spams its float64 representation four times into a seed.
Note

SeededPRNG objects can be initialized in three ways:

  1. Directly from a state value, which is 112 bytes long (represented by a Uint8Array). All possible states are valid, although naively constructing one manually is unlikely to give the proper pseudo-random behavior.
  2. From a seed value, which is 32 bytes long (represented by a Uint8Array). All possible seeds are valid and reasonable to use.
  3. For convenience, directly from a Number. This just initializes a seed directly from the Number's binary representation; a Number is represented as a 64-bit (8 byte) float, so four copies of its bit pattern can fill out a seed.

1.1.2 SeededPRNGStateFromState(state)

  1. Return a copy of state.

1.1.3 SeededPRNGStateFromSeed(seed)

  1. Create a state from seed, however ChaCha specifies that works.
  2. Return state.

1.1.4 SeededPRNGStateFromNumber(number)

  1. Let seed be a 32-byte array.
  2. Fill seed with four contiguous copies of number's binary representation.
  3. Return SeededPRNGStateFromSeed(seed).

1.1.5 Properties of the SeededPRNG Constructor

The SeededPRNG constructor:

  • has a [[Prototype]] internal slot whose value is %Function.prototype%.

1.2 Properties of the SeededPRNG Prototype Object

The SeededPRNG prototype object:

  • is %SeededPRNG.prototype%.
  • is an ordinary object.
  • is not a SeededPRNG object; it does not have a [[PRNGState]] internal slot.
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.

Unless explicitly stated otherwise, the methods of the SeededPRNG prototype object defined below are not generic and the this value passed to them must be a SeededPRNG value.

1.2.1 SeededPRNG.prototype.constructor

The initial value of SeededPRNG.prototype.constructor is %SeededPRNG%.

1.2.2 SeededPRNG.prototype.random ()

The random() method returns a pseudo-random value between 0 and 1 (including 0 but excluding 1), similar to Math.random().

It performs the following steps when called:

  1. Let « nextState, bits64 » be GetPRNGValue( this.[[PRNGState]] ).
  2. Set this.[[PRNGState]] to nextState.
  3. Set int53 to the result of right-shifting bits64 by 11, then interpreting the result as a binary integer.
  4. Let divisor be the mathematical value 1/(253).
  5. Let randVal be the product of int53 and divisor.
  6. Return 𝔽( randVal ).

1.2.3 GetPRNGValue( state )

The GetPRNGValue() abstract operation takes a SeededPRNG [[PRNGState]] value, and returns a pair of values, consisting of the next [[PRNGState]] value, and a pseudo-random 64-bit value.

It performs the following steps when called:

  1. Whatever ChaCha 12 does.

1.2.4 SeededPRNG.prototype.randomSeed ()

The randomSeed() method returns a random seed value, suitable for initializing another SeededPRNG object with. This method's return values cover the full possible range of 2128 seeds, unlike something like new SeededPRNG(prng.random()), which only produces 253 possible seeds.

It performs the following steps when called:

  1. Let seed be a Uint8Array with length 16.
  2. Let nextState be this.[[PRNGState]].
  3. For the integers 0-3 inclusive i:
    1. Let « nextState, bits64 » be GetPRNGValue( nextState ).
    2. Write bits64 into seed, starting with the byte 8 * i.
  4. Set this.[[PRNGState]] to nextState.
  5. Return seed.

1.2.5 SeededPRNG.prototype.state ()

The state() method returns the internal state of the SeededPRNG object, suitable for initializing another SeededPRNG which will then produce an identical sequence of pseudo-random values.

It performs the following steps when called:

  1. Return a copy of this.[[PRNGState]].

1.2.6 SeededPRNG.prototype.setState ( bytes )

The setState() method overwrites the internal state of the SeededPRNG object with a new state.

It performs the following steps when called:

  1. If bytes is a Uint8Array of length 112, set this.[[PRNGState]] to SeededPRNGStateFromState( bytes ).
  2. Otherwise, throw a TypeError.
  3. Return this.