Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion tutorials/math/random_number_generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ Godot.

After giving you a brief overview of useful functions that generate random
numbers, you will learn how to get random elements from arrays, dictionaries,
and how to use a noise generator in GDScript.
and how to use a noise generator in GDScript. Lastly, we'll take a look at
cryptographically secure random number generation and how it differs from
typical random number generation.

.. note::

Computers cannot generate "true" random numbers. Instead, they rely on
`pseudorandom number generators
<https://en.wikipedia.org/wiki/Pseudorandom_number_generator>`__ (PRNGs).

Godot internally uses the `PCG Family <https://www.pcg-random.org/>`__
of pseudorandom number generators.

Global scope versus RandomNumberGenerator class
-----------------------------------------------

Expand All @@ -34,6 +39,14 @@ the RandomNumberGenerator class.
The randomize() method
----------------------

.. note::

Since Godot 4.0, the random seed is automatically set to a random value when
the project starts. This means you don't need to call ``randomize()`` in
``_ready()`` anymore to ensure that results are random across project runs.
However, you can still use ``randomize()`` if you want to use a specific
seed number, or generate it using a different method.

In global scope, you can find a :ref:`randomize()
<class_@GlobalScope_method_randomize>` method. **This method should be called only
once when your project starts to initialize the random seed.** Calling it
Expand Down Expand Up @@ -473,3 +486,48 @@ terrain. Godot provides :ref:`class_fastnoiselite` for this, which supports
GD.Print(_noise.GetNoise1D(i));
}
}

Cryptographically secure pseudorandom number generation
-------------------------------------------------------

So far, the approaches mentioned above are **not** suitable for
*cryptographically secure* pseudorandom number generation (CSPRNG). This is fine
for games, but this is not sufficient for scenarios where encryption,
authentication or signing is involved.

Godot offers a :ref:`class_Crypto` class for this. This class can perform
asymmetric key encryption/decryption, signing/verification, while also
generating cryptographically secure random bytes, RSA keys, HMAC digests, and
self-signed :ref:`class_X509Certificate`\ s.

The downside of :abbr:`CSPRNG (Cryptographically secure pseudorandom number generation)`
is that it's much slower than standard pseudorandom number generation. Its API
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generated 10 million random ints:

const COUNT = 10 * 1000 * 1000

func test_randi():
	var arr: Array[int] = []
	arr.resize(COUNT)
	
	var t0 = Time.get_ticks_msec()
	for i in range(COUNT):
		arr[i] = randi()
	var t1 = Time.get_ticks_msec()
	prints("test_randi:", t1-t0, "ms")
	return t1-t0
	

func test_random_bytes():
	
	var arr: Array[int] = []
	arr.resize(COUNT)
	
	var t0 = Time.get_ticks_msec()
	var crypto := Crypto.new()
	var byte_array := crypto.generate_random_bytes(COUNT * 4)
	for i in range(COUNT):
		arr[i] = byte_array.decode_u32(i * 4)
	var t1 = Time.get_ticks_msec()
	prints("test_random_bytes:", t1-t0, "ms")
	return t1-t0

Result:

test_randi: 381 ms
test_random_bytes: 550 ms

So the crypto.generate_random_bytes() takes only about 50% more time. In my opinion that is not much slower, just slower. But is this something that could be dependent on operating system? Also, I'm pretty sure that the pseudo rng (randi()) always takes a constant time to generate a new random number, but how about the CSPRNG? If it does use operating system as a randomness source (does it?), generating even a single random byte might take non-constant time?

Copy link
Member

@AThousandShips AThousandShips Oct 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50% is a lot slower though generally speaking, so this is still a valid statement

It is also very hardware and OS dependent

is also less convenient to use. As a result,
:abbr:`CSPRNG (Cryptographically secure pseudorandom number generation)`
should be avoided for gameplay elements.

Example of using the Crypto class to generate 2 random integers between ``0``
and ``2^32 - 1`` (inclusive):

::

var crypto := Crypto.new()
# Request as many bytes as you need, but try to minimize the amount
# of separate requests to improve performance.
# Each 32-bit integer requires 4 bytes, so we request 8 bytes.
var byte_array := crypto.generate_random_bytes(8)

# Use the ``decode_u32()`` method from PackedByteArray to decode a 32-bit unsigned integer
# from the beginning of `byte_array`. This method doesn't modify `byte_array`.
var random_int_1 := byte_array.decode_u32(0)
# Do the same as above, but with an offset of 4 bytes since we've already decoded
# the first 4 bytes previously.
var random_int_2 := byte_array.decode_u32(4)

prints("Random integers:", random_int_1, random_int_2)

.. seealso::

See :ref:`class_PackedByteArray`'s documentation for other methods you can
use to decode the generated bytes into various types of data, such as
integers or floats.