rust-lang / libs-team

The home of the library team
Apache License 2.0
110 stars 18 forks source link

Simple seedable insecure random number generation, stable across Rust versions #394

Open joshtriplett opened 3 weeks ago

joshtriplett commented 3 weeks ago

API design partially based on discussions with @BartMassey.

Proposal

Problem statement

People regularly want to generate random numbers for a variety of use cases. Doing so currently requires using a crate from crates.io. rand is the 6th most downloaded crate on crates.io, and fastrand is quite popular as well. Many, many people over the years have expressed frustration that random number generation is not available in the standard library (despite the standard library using randomness internally for HashMap).

There are multiple reasons why we have not historically added this capability. Primarily, there are three capabilities people want, and those capabilities seem to present a "pick any two" constraint:

These constraints arise from the possibility of a secure random number generator potentially requiring updates for security reasons. Changing the random number generator would result in different sequences for the same seed.

In addition to that primary constraint, there have also been design difficulties: there are numerous pieces of additional functionality people may want surrounding random number generation, which makes any proposal for it subject to massive scope creep and bikeshed painting. Most notably: users of random numbers may want to represent the state of the RNG explicitly as something they can pass around, or implicitly as global state for simplicity.

This ACP proposes a solution that aims to be as simple as possible, satisfy all the stated constraints on the problem, and allow for future expansion if desired. This ACP handles the "pick any two" constraint above by explicitly marking seeding as insecure. This will allow us to keep the insecure seedable generator the same across Rust versions and targets.

Separately, ACP 393 proposes an RNG that is secure and seedable but does not guarantee identical seeding across Rust versions.

Motivating examples or use cases

Solution sketch

All of these would live in a new std::random module.

/// Trait for any type that can be generated via random number generation.
#[sealed]
trait Random { ... }

/// A seeded, insecure random number generator.
struct InsecureRng { ... }

impl InsecureRng {
    /// Create a seeded random number generator.
    ///
    /// The resulting generator is not cryptographically secure.
    pub fn new_seeded_insecure(seed: u128) -> Self;

    /// Generate a random value, uniformly distributed over possible values.
    pub fn random<R: Random>(&mut self) -> R;
}

The trait Random (shared with ACP 393) will initially be implemented for all iN and uN integer types, isize/usize, and bool, as well as arrays and tuples of such values.

Notably, Random will not initially be implemented for floating-point values (to avoid questions of distribution), strings/paths/etc (to avoid questions of length and character selection), or char (to avoid questions of what subset of values to generate). We can consider such additions in the future; see the section on future work below.

This ACP proposes initially using the same RNG for both secure and insecure random number generation, but if we ever need to change the secure RNG, we would keep the insecure seedable RNG as a separate implementation that keeps the same behavior.

The seeded insecure random number generator, given the same seed, will provide the same sequence of random numbers, on all targets.

Alternatives

We could do nothing, and continue to refer people to external crates like rand and fastrand.

We could avoid providing seeded random number generation at all, and refer people who need seeded random number generation to external crates.

Future work

We could consider, in the future, introducing random float generation, or random character generation. We should probably leave random String generation to crates, though, since that would have many more aspects to customize.

We could consider supporting random generation in ranges (e.g. random_range(1..6)). Providing a correct implementation will steer people away from the most common open-coded implementation (using %), which introduces bias.

We could unseal the Random trait to allow implementing it. This would require defining an interface for it, and committing to the stability of that interface.

We could provide functions to fill an existing value rather than creating a new one.

We could provide a function to fill an array/slice with randomness.

We could allow #[derive(Random)] on structures or enums containing only types that implement Random.

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution: