Hardware-based Random Number Generator (RDRAND)

class randomgen.rdrand.RDRAND(seed=None, *, retries=10)

Container for the hardware RDRAND random number generator.

Parameters:
seedNone

Must be None. Raises if any other value is passed.

retriesint

The number of times to retry. On CPUs with many cores it is possible for RDRAND to fail if heavily utilized. retries sets the number of retries before a RuntimeError is raised. Each retry issues a pause instruction which waits a CPU-specific number of cycles (140 on Skylake [1]). The default value of 10 is recommended by Intel ([2]). You can set any value up-to the maximum integer size on your platform if you have issues with errors, although the practical maximum is less than 100. See Notes for more on the error state.

Raises:
RuntimeError

If RDRAND is not available on the platform.

Notes

RDRAND is a hardware random number generator that is available on Intel processors from the Ivy Bridge line (2012) or later, and AMD processors starting in 2015.

RDRAND has been audited and is reported to be a secure generator. It is much slower than software BitGenerators and so is only useful in applications where security is more important than performance.

State and Seeding

Results from RDRAND are NOT reproducible.

RDRAND uses a hardware generated seed and so cannot be seeded. The state contains a single integer value status that takes the value 1 if all calls have succeeded and 0 if any fail. A failure of a call to RDRAND does not propagate, and so users much check this value to determine if results are random.

Parallel Features

RDRAND is stateless and so multiple instances can be used in parallel.

>>> from numpy.random import Generator
>>> from randomgen import RDRAND
>>> rg = [Generator(RDRAND()) for _ in range(10)]

Exceptions

Bit generators are designed to run as quickly as possible to produce deterministic but chaotic sequences. With the exception of RDRAND, all other bit generators cannot fail (short of a massive CPU issue). RDRAND can fail to produce a random value if many threads are all utilizing the same random generator, and so it is necessary to check a flag to ensure that the instruction has succeeded. When it does not exceed, an exception should be raised. However, bit generators operate without the Python GIL which means that they cannot directly raise. Instead, if an error is detected when producing random values using RDRAND, the Python error flag is set with a RuntimError. This error must then be checked for. In most applications this happens automatically since the Lock attached to this instance will check the error state when exiting and raise RuntimError.

If you write custom code that uses lower-level function, e.g., the PyCapsule, you will either need to check the status flag in the state structure, or use PyErr_Occurred to see if an error occurred during generation.

To see the exception you will generate, you can run this invalid code

>>> from numpy.random import Generator
>>> from randomgen import RDRAND
>>> bitgen = RDRAND()
>>> state = bitgen.state
>>> state["retries"] = -1  # Ensure always fails
>>> bitgen.state = state
>>> gen = Generator(bitgen)

The next command will always raise RuntimeError.

>>> gen.standard_normal(size=10)

The RDRAND-provided function random_raw also checks for success and will raise if not able to use RDRAND

>>> bitgen.random_raw()

Note that random_raw has been customized for the needs to RDRAND and does not rely on the Lock to raise. Instead it checks the status directly and raises if the status is invalid.

Finally, you can directly check if there have been any errors by inspecting the success property

>>> bitgen = RDRAND()
>>> assert bitgen.success  # True
>>> bitgen.random_raw(10)
>>> assert bitgen.success  # Still true

You will only ever see an AssertionError if the RDRAND has failed. Since you will first see a RuntimeError, the second assert will not execute without some manual intervention.

No Compatibility Guarantee

RDRAND is hardware dependent and not reproducible, and so there is no stream guarantee.

References

[1]

Software.intel.com. 2020. Intel® Intrinsics Guide. [online] Available at: <https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_pause&expand=4141> [Accessed 10 July 2020].

[2]

Intel. 2020. Intel® Digital Random Number Generator (DRNG) Software Implementation. (online) Available at: <https://software.intel.com/content/www/us/en/develop/articles/intel-digital-random-number-generator-drng-software-implementation-guide.html> [Accessed 10 July 2020].

Examples

>>> from numpy.random import Generator
>>> from randomgen import RDRAND
>>> rg = Generator(RDRAND())
>>> rg.standard_normal()
0.123  # random
Attributes:
lockthreading.Lock

Lock instance that is shared so that the same bit git generator can be used in multiple Generators without corrupting the state. Code that generates values from a bit generator should hold the bit generator’s lock.

seed_seqNone

Always None since RDRAND cannot be seeded.

Seeding and State

seed([seed])

Compatibility function.

state

Get or set the PRNG state

success

Gets the flag indicating that all calls to RDRAND succeeded

Parallel generation

jumped([iter])

Returns a new bit generator

Extending

cffi

CFFI interface

ctypes

ctypes interface

Testing

random_raw([size, output])

Return randoms as generated by the underlying BitGenerator

Custom Lock

class randomgen.rdrand.RaisingLock

A Lock that wraps threading.Lock can can raise errors.

Raises the last exception set while the lock was held, if any. It clears the error when the lock is acquired.

Notes

This class has been specially designed for issues unique to RDRAND.