owned this note changed 15 days ago
Published Linked with GitHub

People: Josh Triplett, Diggory, TC, Amanieu

Notes

JoshT: My expectation: the trait should exist in core. The concept of generating random numbers doesn't require OS. But the (default) random source could be in std and getrandom could be in std.

Diggory: This might require initialisation.

Josh:

  • trait RandomSource - in core, infallible
  • struct DefaultRandomSource - in std
  • fn getrandom(...) -> io::Result<...> - in std, fallible
    • Or: impl Read for DefaultRandomSource

TC: need for a non-blocking API. Alternative:

  • fn is_getrandom_ready(): yes, not yet, other error

Amanieu:

fn secure_random_source() -> Result<DefaultRandomSource, Error>;

Josh:

struct DefaultRandomSource;
// May fail:
impl Read for DefaultRandomSource { .. }
// Can't fail (would panic instead)
impl RandomSource for DefaultRandomSource { .. }

Diggory: in Rand we have two traits one with error handling TryRngCore and one withoutRngCore. And we have a wrapper for converting one from the other.

Amanieu: Do people use TryRngCore?

Amanieu: Feel strongly that the trait in core should be without error handling.

Josh: We can add the "try" version later in the future. If you want something that has the possibility of failing, you can call getrandom yourself.

TC: We were talking about the random source implementing Read. And Read is fallible.

Amanieu: That was an alternative proposal instead of the getrandom function.

Josh: Could we use Read as the implementation of the random source. It's fallible and couldn't possible be put in core (and not clear path forward). We can't put io::Error into core. We might be able to put io::Error into alloc but core should be able to have the random trait.

TC: Why wouldn't we implement Read for DefaultRandomSource? That seems extremely useful.

Josh: That is potentially a workable idea. Can we just provide impl Read for DefaultRandomSource could you then call call read/read_buf and have that be your source?

Diggory: Yes.

Josh: TC, would that (impl Read for default random source?)?

TC: Have we solved reading into uninitialized buffers?

Josh: Read supports that but we need to stabilize it.

(Someone): See also:

https://blog.sunfishcode.online/writingintouninitializedbuffersinrust/

TC: I think user should use that most of the time. I still like the idea to stabilize the unsafe primitive as well we'll need to implement that anyway.

Diggory: would the DefaultRandomSource implement both Read and the RandomSource trait?

Josh: Yes, you would get "fill a buffer" from read and the higher-level functionality like get i32.

Josh: Why does rng::Source implement so many different ways (next_u32, next_u64, fill_bytes) of getting individual values?

Diggory: Some rngs are faster doing that.

Josh: Is there a reason why all these are required and some of them can't be implemented (by default) in terms of the other ones?

Diggory: We provide implementations people can call, but different RNGs want to provide a different interface; block-based generators would provide fill_buf, word-based interfaces would provide next_u64.

Josh: We're working on something in the language that would help here: say that these things all have default implementations that depend on each other but you'd need to implement at least one.

TC: Usecase for why non-blocking is important: you're writing an SSH implementation in rust. You want to write this using async rust (you have executors and all that). You need to generate server-side keys. That's a difficult problem SSH is one of the first things the OS starts. OS might start you early before it's ready to generate randomness. So you want to wait until the OS is ready. But you're also running an async executor. You can't call "getrandom blocking" because that can block you. That's a correctness problem. I'd prefer to call a non-blocking mode so it returns an error and then could signal to the OS that I'm not ready to start up yet. And try again later. If I can't do that I'm not sure how to write the server correctly.

Josh: Sounds like you're ready to rely on OS's "once it's ready, it'll never be not ready".

TC: For the time being yes.

Josh: If we're going to provide a "is_random_ready" function, would that work?

TC: Yes.

TC: Writing an SSH server is something that's not obscure something we really should support. So we really need to nail this solution.

Josh: No objection to having an is_random_ready function. An OS that doesn't have it always fails. An OS that has it ready always can return true. Just necessarily ready to stabilize this at the same time.

Diggory: Sounds like that could go against "not easy to misuse"?

Josh: You can just not call it, and still use the

TC+Josh: Need to document that (a) once it returns success, DefaultRandomSource won't block, and (b) that if a source is ready it won't stop being ready.

Josh: getrandom would only block when the OS's randomness is not ready yet.

Amanieu: for something like Tokio you need to wake up once something is ready. Typically you'd open /dev/random and wait for it to be ready.

Josh: Is is_random_ready something that could go to the std. But something that asynchronously watches the OS randomness to be ready is not something we're not ready add yet (because we don't have any executor etc. to add it).

Josh: If we had this set of interfaces (possibly is_random_ready), DefaultRandomSource that implements Read and Rand. What should the RandomSource have?

Diggory: Depends on applications. Is this to be used as a replacement for Rand?

Josh: What is the performance delta of using a word-based generation to do fill_buf compared to having it implement multiple functions instead of one.

Diggore: Maybe like 20% loss or something? Depends on your application.

Josh: Definitely non-trivial.

Amanieuu: Are you converting a single word into multiple bytes?

Diggory: Yes, you're using the same input.

Amanieu: That should still optimize away.

Diggory: If you know the length & alignment. If you don't, maybe not.

Josh: What we want teh properties of the interface be? DefaultRandomSource: OS-based and secure. We haven't talked about any sources for insecure randomness.

Diggory: The hashmap initialisation uses the insecure implementation already.

Josh: You're talking about calling getrandom without the grnd flag? Effectively using random rather than urandom?

Josh: in the spirit of hard to misuse we should give you secure random. And maybe require you to call into another library for insecure random number.

Diggory: Should RandomSource have the implication be that it should provide secure random bytes?

Amanieu: No.

Diggory: Should we have a marker trait that declares this to be secure?

Josh: There's different meanings of "secure" here.

Amanieu: I'd expect anyone who needs secure random data to just use the DefaultRandomSource directly.

Josh: There is an argument for why someone building a cryptographic library to use a non-default source: testing and fuzzing e.g. getting a seedable source (to verify you get the same key out).

Amanieu: In that case you wouldn't require an secure trait bound, just any random trait bound. Either you allow a different source for testing. Or you allow for

Josh: Seedable doesn't imply insecure.

Amanieu: Don't see much use for the marker trait. We should ship without it.

Josh: We should ship without it first. We need to see what people expect. Not clear whether we want to provide a generic argument for seeding. There are usecases for having a marker saying this is definitely CSRNG.

Josh: We may want to consider having a crypto random source. But we don't want to provide a way for someone to fake out a crypto random source for testing. Would be nice to dodge those questions.

Amanieu: Can be added later if needed.

Amanieu: Good to have 2 sources: (1) default random source: give me the best random source you can provide (always succeeds). (2) Secure rando source: the platform will fail on a platform that doesn't have a secure random source.

Josh: Concern about the naming ("default" implying possibly an insecure source)

Amaniou: There's a random source that always succeeds and one that always fails.

Josh: Why wouldn't we provide best effort at all? Is there anything other than wasm that we don't want people to copy?

Josh: Are you saying that the "insecure" source is PRNG?

Amanieu: There's a value in having a "thread RNG" and secure RNG.

Diggory: Thread RNG in rand is secure. Don't see necessarily a lot of value.

Josh: The concept of an insecure source makes sense for a different reason: the insecure one doesn't have to have its algorithm changed in the future. So it can always to stay the same and then have it be the source for fuzzing, games etc.

Meeting 2025-06-09 - Random number generation in the standard library

Josh:
The goal of the original library ACP was to get

  1. An interface in core for random sources, and
  2. A default random source in std based on system randomness.
  3. Eventually, to build some basic functionality atop that, like "choose an item from an iterator" or "roll a random number in a range". The goal isn't to have all the functionality of rand for every one of its many use cases, but to have enough of the functionality to satisfy two common use cases: "I want to build a simple game" and "I want to get randomness to use for an API key or similar".

dhardy:

  • A random source (getrandom). Probably needs to have error handling of some kind. Probably needs to support settings a custom source in some cases, but without letting some library in the dependency tree override the default with an insecure source.
  • A default RNG (thread-local or direct fast OS interface); a rand::rng replacement. Probably wants a trait like rand::RngCore.
  • This is just choosing parts of rand to incorporate and possibly simplify (e.g. maybe drop portable reproducibility guarantees, or not). Less important since with (1) and (2) the rand crate could potentially drop all dependencies and be decently small.

TC:

For getrandom, agree we'll want error handling, and we'll also want to talk about how to support use cases that need its blocking behavior and those that need its nonblocking one (i.e. GRND_NONBLOCK).

Personally, I'd also like to see us eventually stabilize something that works without std (maybe, e.g., in core::arch, in the spirit of breakpoint) that uses the features of the target CPU (maybe it'd only be available on such targets). These instructions are now widespread. I'd see this function as being low level and e.g. only returning a single word.


Random source requirements:

  • Secure
  • Trait based? May help with no_std compatibility, but may not be useful.
  • Error reporting. Or not: https://github.com/golang/go/issues/66821. Do the error values matter? Likely only for debugging. Can code safely recover? Can it be useful in selecting between multiple sources? BrianSmith says "Not everybody can use an API that abort()s or panics, as some coding standards do not allow any abort() or (reachable) panic at all."
  • Filling an uninitialized buffer? Motivation in getrandom is partly about using Miri's initialization proof for randomization.
  • Generation of u32 / u64?
  • Potentially-insecure seeding?
  • Option to specify a custom source, maybe like #[global_allocator] or using RFC 3632? But libraries in the dep tree should not be able to replace the source for security reasons. See this comment and the next

Default random source requirements (trait based):

  • Secure
  • Fast?
  • Is this the same as above, or a user-space PRNG over it? jstarks says "please never do this", yet rand::rng does because it cannot guarantee the perf. of the system RNG.
  • Platform-specific?
  • Byte-fill and next-word interfaces?
  • No error reporting?
  • Trait-based, with alternatives?

Alternative RNGs:

  • Do these belong in core?
  • Allow seeding with expectations of reproducibility

newpavlov suggests (at least) three sources in std: SecureRandomSource, InsecureRandomSource, ThreadRng


"Easy to use" vs "hard to misuse" in API design, and tradeoffs like interfaces with error handling vs interfaces that are harder to misuse.

Trait design and error reporting.

Entropy source (e.g. getrandom) vs default random source.

dhardy: "PRNGs like PCG or Xoshiro are much smaller and somewhat faster than CSPRNGs like ChaCha, so there's incentive for both to exist."

Lower-priority topics:

  • Seedability: traits, vs RNG-specific interfaces. Do we want this in core?
  • RNG jump support?
Select a repo