Skip to content

Commit

Permalink
Implement JitterRng, based on jitterentropy-library.
Browse files Browse the repository at this point in the history
This is a pretty direct translation from C to Rust.

Some notes per function:

### `random_loop_cnt` (`jent_loop_shuffle`)
Because the `min` argument was always `0`, I removed that argument.

The C code did not seem to fold the time optimally. When `bits` is set to `7`,
as `mem_access` does, it will fold `64 / 7 = 9` times, leaving 1 bit unused. It
is minor, but we use `folds = (64 + n_bits - 1) / n_bits;`, so all is used.

We do not add 1 to the resulting loop count, this should be done in the calling
code. `memaccess` already adds 128 anyway. For `lfsr_time` we use the loop count
on a `throw_away` value, and then run the real calculation once.


### `lfsr_time` (`jent_lfsr_time`)
We do not allow overriding `loop_cnt`, and also do not return the actual
`loop_cnt` used. This only had some use in testing the C code, but was 'not
needed during runtime'.

Only the last round of the outer loop (in C) effect `self.data`. In Rust the
inner loop is part of the `lfsr` helper function. All but the last round operate
on a `throw_away` function, that does not get reset between the loop rounds.

### `memaccess` (`jent_memaccess`)
We do not allow overriding `loop_cnt`, and also do not return the actual
`loop_cnt` used. This only had some use in testing the C code, but was 'not
needed during runtime'.

We do not do NULL pointer checks, and running `JitterRng` without the Memory
Access noise source is (currently) not supported.

We (currently) do not support changing `memblocksize` and `memblocks` except by
changing the constants `MEMORY_BLOCKS` and `MEMORY_BLOCKSIZE`. So instead of
recalculating `wrap`, we can just re-use MEMORY_SIZE.

Instead of a `memlocation` pointer we use indexing. The `index` is calculated
before accessing `self.mem[index] instead of after. This convinces the compiler
indexing is safe, and eliminates bounds checks.


### `stuck` (`jent_stuck`)
For all delta's we use an `i64`, instead of an `u64` for the first delta in the
C implementation. This handles a clock that may not be entirely monotonic (for
example due to NTP) slightly better.

Also, we return a `bool` instead of an `u64`.


### `measure_jitter` (`jent_measure_jitter`)
It seemed clearer here to not return an `u64` or `bool`, but `Option<()>`.
`Some` and `None` indicate clearly whether we have been able to add some
entropy.

For `current_delta` we use an `i64` instead of an `u64`. It is cast back to an
`u64` for `lfsr_time`, which only cares about bits.


### `stir_pool` (`jent_stir_pool`)
The C code does something difficult with initializing an `u64` with two `u32`'s
in an `union`. The numbers it uses for initialization are from SHA-1, and the
order does not really matter. The Rust code just sets the `u64`'s directly, and
uses the constants in the order they appear in FIPS 180-4 section 5.3.1.

The function tries to be constant time to prevent leaking timing information
about the generated random number. Using a `trow_away` value like it does is
optimised out, and I don't trust branches to be constant time. I used a bit mask
trick instead, and verified the assembly does not include anything conditional.
Not sure it matters anything, we just went through a lot of effort to have as
much timing variance as possible to generate the random number.


### `gen_entropy` (`jent_gen_entropy`)
We do not support oversampling, so no need to repeat the loop more times.

`self.memaccess()` in `measure_jitter` is easily optimised out, because LLVM
recognises we never read the results. Therefore we do a single read from
`self.mem`, hidden to the optimizer with `black_box()`.

We return `self.data`  instead of requiring the calling code to read from it.

### (not included) `jent_read_entropy`
Here we have the convienent `fill_bytes_via_u64` for.

The C code calls `jent_gen_entropy` one last time after filling the buffer, to
make sure that something reading the processes memory can not read the last
generated random number. 'This call reduces the speed of the RNG by up to half`.
It seems to me setting it to 0 is just as good.

I did not bother with this. As an alternative a user caring very much about this
can just call `next_u64` after receiving a result.


### `entropy_init` (`jent_entropy_init`)
Wrap `lfsr_time` in the loop in a `black_box` to make sure it is not optimised
out.

For delta we use an `i64` instead of an `u64`.

Instead of `lowdelta` we just use `delta`. It seems a hack to compile on some
32-bit architectures.
  • Loading branch information
pitdicker committed Oct 30, 2017
1 parent 819a282 commit 4f08d57
Show file tree
Hide file tree
Showing 3 changed files with 616 additions and 6 deletions.
25 changes: 19 additions & 6 deletions benches/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const BYTES_LEN: usize = 1024;
use std::mem::size_of;
use test::{black_box, Bencher};

use rand::{Rng, NewSeeded, SeedFromRng, StdRng, OsRng, Rand, Default};
use rand::{Rng, NewSeeded, SeedFromRng, StdRng, OsRng, JitterRng, Rand, Default};
use rand::prng::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng};

macro_rules! gen_bytes {
Expand Down Expand Up @@ -59,15 +59,22 @@ gen_usize!(gen_usize_chacha, ChaChaRng);
gen_usize!(gen_usize_std, StdRng);
gen_usize!(gen_usize_os, OsRng);

#[bench]
fn gen_usize_jitter(b: &mut Bencher) {
let mut rng = JitterRng::new().unwrap();
b.iter(|| {
black_box(usize::rand(&mut rng, Default));
});
b.bytes = size_of::<usize>() as u64 * RAND_BENCH_N;
}

macro_rules! init_gen {
($fnn:ident, $gen:ident) => {
#[bench]
fn $fnn(b: &mut Bencher) {
let mut rng = XorShiftRng::new().unwrap();
let mut rng = OsRng::new().unwrap();
b.iter(|| {
for _ in 0..RAND_BENCH_N {
black_box($gen::from_rng(&mut rng).unwrap());
}
black_box($gen::from_rng(&mut rng).unwrap());
});
}
}
Expand All @@ -77,4 +84,10 @@ init_gen!(init_xorshift, XorShiftRng);
init_gen!(init_isaac, IsaacRng);
init_gen!(init_isaac64, Isaac64Rng);
init_gen!(init_chacha, ChaChaRng);
init_gen!(init_std, StdRng);

#[bench]
fn init_jitter(b: &mut Bencher) {
b.iter(|| {
black_box(JitterRng::new().unwrap());
});
}
Loading

0 comments on commit 4f08d57

Please sign in to comment.