Embedded-hal: [Discussion] rand::RngCore for blocking RNG trait

Created on 2 Mar 2019  路  10Comments  路  Source: rust-embedded/embedded-hal

The RngCore trait seems like a good candidate for a blocking RNG trait. It is minimal, already in fairly wide use and no-std.

discussion help wanted needs-impl

Most helpful comment

In my opinion though RngCore makes the mistake of not making up its mind whether fill_bytes/try_fill_bytes can fail or not (the documentation's example of "reading a file of entropy" seems both contrived and misguided). By definition, a CSPRNG is supposed to transform a good entropic seed (the goodness of which is untestable) into random bits "forever", whereas a hardware TRNG typically can perform a self-test on startup (so the fallibility would be in the constructor - which if anything would be a separate trait NewRng) and then doesn't really have a reason to suddenly start failing. I understand that the RngCore trait wants to "offer both", but what's a driver supposed to use or rely on?

The TRNG of the i.MX RT106x (and probably the similar chips) can fail at any time, and it does not (AFAIK) do any self-test on startup. It applies various statistical tests to its output and sets the error flag if any of them fail. Data will still be generated but is presumably not truly random (assuming tests are running properly; they are configurable). NXP's official SDK will not write the data to the out-pointer in this case.

I don't believe I've seen a failure with its default configuration options but it might happen. NXP does not publicly provide documentation for the security features of the chip, so it's hard for me to know. (My current support for the TRNG just waits for good data forever.)

I'll also note here that the TRNG of the 106x generates 512 bits at a time.

I agree the mix of infallible/fallible methods of RngCore is odd.

All 10 comments

Good question.
Here the alternatives:

  1. We could reexport (some of) the traits from rand_core:

    • :+1: Widely used traits, seems mostly stable.

    • :+1: Maintained by Rust team itself.

    • :-1: External dependency.

    • :-1: rand_core is not stable yet so it would hold back embedded-hal 1.0.

    • :question: Too much complexity?

    • :question: Reexport everything or only a subset?

    • :question: Remove embedded_hal::rng?

  2. We encourage direct dependency on rand_core for HAL/HWRNG impls like we do with e.g. embedded-dma:

    • :+1: Flexibility for HAL impls to choose rand_core version.

    • :-1: Fragmentation in rand_core versions across ecosystem.

    • :question: Too much complexity for HAL/HWRNG impls?

    • :question: Remove embedded_hal::rng?

  3. Keep embedded_hal::rng as-is:

    • :+1: No dependencies.

    • :-1: Separate RNG trait to what the Rust community is already using detriments interoperability.

    • :+1: Much simpler than rand_core traits.

    • :question: Too simple?

I am unaware about how many implementations of embedded_hal::rng::Read there are. As of 0.2.x it is marked as unproven.
Does somebody know if it is being used?

I think I like option 2 the most for now, including removing embedded_hal::rng if there are no users. Then in a future release _maybe_ do option 1.

Opinions from hardware RNG implementers would be helpful here.

Funny enough I had the exact same trait definition and implementation in my nrf51-hal which seems to have gotten dropped by the consolidation of all nRF HALs. This one is implementing rand_core now: https://github.com/nrf-rs/nrf-hal/blob/master/nrf-hal-common/src/rng.rs

stm32l4xx-hal does implement embedded_hal::rng::Read. See here
cc @nickray

hmm, all interesting options! it's definitely neat to have consistent rand everywhere. i am also perhaps a little cautious about the weird breakages we might see trying to match libraries with different rand_core versions (like, e-h versions is already a constraint, e-h and rand versions with the hal is going to make things even trickier, and it looks like they don't semver trick the rand-core crate...

in related things there's also getrandom which is great for, making rand just work but not so good for binding in peripherals, and i ran into a bunch of problems actually using the rng that ended in rand-facade, it'd be neat to discuss / document how to effectively use rngs as part of any improvements.

No strong opinions; I did also implement this unproven trait in our LPC55 HAL, just to not reinvent the wheel. Practically, all I need is a method to fill byte buffers with entropy, with the ability to ignore errors.

Might it be possible to nudge rand_core towards a v1? I do fear a bit of a mess and tangle for what's essentially filling a byte buffer. Personally, despite "doing cryptography" I've avoided the rand ecosystem so far.

In my opinion though RngCore makes the mistake of not making up its mind whether fill_bytes/try_fill_bytes can fail or not (the documentation's example of "reading a file of entropy" seems both contrived and misguided). By definition, a CSPRNG is supposed to transform a good entropic seed (the goodness of which is untestable) into random bits "forever", whereas a hardware TRNG typically can perform a self-test on startup (so the fallibility would be in the constructor - which if anything would be a separate trait NewRng) and then doesn't really have a reason to suddenly start failing. I understand that the RngCore trait wants to "offer both", but what's a driver supposed to use or rely on?

I'd tend to prefer an infallible method for these reasons, but certainly someone is going to come up with some valid corner case on embedded Linux or something :sweat_smile:.

In any case, if the consensus is for a fallible method, then https://github.com/rust-embedded/embedded-hal/issues/229 seems applicable, I don't think NonZeroU32 without additional interpretation is a particularly useful error type.

cc @tarcieri as it would be great if the eventual trait integrated nicely with RustCrypto.

Outside the embedded space, rand_core::OsRng provides an infallible wrapper for the fallible getrandom crate. That approach seems to work well enough.

We've adopted rand_core pretty ubiquitously in the https://github.com/RustCrypto crates. I also wish it were 1.0 and it's been something of a complication in other 1.0 crates (e.g. curve25519-dalek), but we've managed to live with that so far.

In yesterdays meeting we mostly hovered around going option 2 with the possible path to go option 1 in the future if things get too wild.

Personally I think if there's a perfectly fine universal solution like rand_core it doesn't really make sense to re-export it unless we manage to create a good chunk of value-add on top of it. It certainly makes sense to nudge the maintainers into stabilizing it for good to get rid of the moving target.

This is basically waiting for someone to PR the removal of embedded_hal::rng traits and adding some documentation/pointers on how to use rand_core instead.

I think you'll be a lot better off not re-exporting it, as it's one of the most common triggers of this bug:

https://github.com/rust-lang/cargo/issues/7916

The std feature of rand_core is activated by all sorts of commonly used dev-dependencies (e.g. criterion, proptest), and until features=dev_dep (rust-lang/cargo#7916) is stable it's a giant headache.

In my opinion though RngCore makes the mistake of not making up its mind whether fill_bytes/try_fill_bytes can fail or not (the documentation's example of "reading a file of entropy" seems both contrived and misguided). By definition, a CSPRNG is supposed to transform a good entropic seed (the goodness of which is untestable) into random bits "forever", whereas a hardware TRNG typically can perform a self-test on startup (so the fallibility would be in the constructor - which if anything would be a separate trait NewRng) and then doesn't really have a reason to suddenly start failing. I understand that the RngCore trait wants to "offer both", but what's a driver supposed to use or rely on?

The TRNG of the i.MX RT106x (and probably the similar chips) can fail at any time, and it does not (AFAIK) do any self-test on startup. It applies various statistical tests to its output and sets the error flag if any of them fail. Data will still be generated but is presumably not truly random (assuming tests are running properly; they are configurable). NXP's official SDK will not write the data to the out-pointer in this case.

I don't believe I've seen a failure with its default configuration options but it might happen. NXP does not publicly provide documentation for the security features of the chip, so it's hard for me to know. (My current support for the TRNG just waits for good data forever.)

I'll also note here that the TRNG of the 106x generates 512 bits at a time.

I agree the mix of infallible/fallible methods of RngCore is odd.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eisterman picture eisterman  路  13Comments

therealprof picture therealprof  路  13Comments

amiraeva picture amiraeva  路  7Comments

TheZoq2 picture TheZoq2  路  10Comments

David-OConnor picture David-OConnor  路  8Comments