And to show my objections are not just theoretical I wrote a little program to check:
#include <time.h>
#include <stdio.h>
static int estimate_entropy(long l) {
int bits = 1; /* for the sign bit */
if (l < 0) l = -l;
while (l > 0) {
++bits;
l >>= 1;
}
return bits;
}
int main() {
struct timespec ts;
if (clock_getres(CLOCK_REALTIME, &ts) != 0) {
perror("clock_getres");
return 1;
}
printf("Clock resolution: %ld.%09ld\n", (long) ts.tv_sec, (long) ts.tv_nsec);
#define N 50 /* number of samples */
struct timespec samples[N];
for (int i = 0; i < N; ++i) {
clock_gettime(CLOCK_REALTIME, &samples[i]);
}
printf("Deltas (ns):");
long deltas[N - 1];
for (int i = 0; i < N - 1; ++i) {
deltas[i] =
(samples[i + 1].tv_sec - samples[i].tv_sec)*1000000000L
+ (samples[i + 1].tv_nsec - samples[i].tv_nsec);
printf(" %4ld", deltas[i]);
}
printf("\n");
long entropy = 0;
printf("Deltas of deltas: ");
for (int i = 0; i < N - 2; ++i) {
long dd = deltas[i + 1] - deltas[i];
printf(" %4ld", dd);
entropy += estimate_entropy(dd);
}
printf("\n");
printf("Maximum entropy: %lld\n", entropy);
}
On my system this prints: Clock resolution: 0.000000001
Deltas (ns): 55 51 23 23 25 24 24 24 24 24 25 25 24 24 24 24 24 25 24 24 24 25 25 24 24 23 25 24 24 25 24 23 25 25 26 23 25 24 24 25 26 24 23 25 25 26 24 25 24
Deltas of deltas: -4 -28 0 2 -1 0 0 0 0 1 0 -1 0 0 0 0 1 -1 0 0 1 0 -1 0 -1 2 -1 0 1 -1 -1 2 0 1 -3 2 -1 0 1 1 -2 -1 2 0 1 -2 1 -1
Maximum entropy: 92
So no, 50 iterations of that loop does not provide 256 bits of entropy due to random fluctuations in nanontime between calls.You don't need 256 bits of entropy, you only need 128.
I have tested this method on over 100 different CPUs and I have never seen such consistent output. I'm genuinely surprised to see that you only hit 92 bits of entropy, but that can trivially be fixed by doing 10x the iterations. 500 iterations is still going to put you under a millisecond of cost.
And, for what it's worth, code I've actually shipped has combined the above technique with Fortuna, and has typically targeted 2000 bits of entropy rather than 128 (for security buffer).
EDIT: I reviewed his code, and he's not hashing between calls to check the clock; the hash call itself causes the CPU to heat up in arbitrary ways which changes the timing between hashes and introduces more entropy; removing that call basically entirely defeats the idea behind the technique, these results are fully invalid.
---
I updated the code to insert the hash call, this is what I got for his original code on my machine, and the updated code with hashing on my machine (and the difference is cryptographically meaningful):
=== Original C — no hashing ===
Clock resolution: 0.000000001
Deltas (ns): 50 34 19 19 13 13 13 13 13 14 13 13 13 13 13 14 13 13 14 12 13 14 13 13 13 14 13 13 14 12 13 14 13 13 14 12 13 14 13 14 13 12 13 14 14 13 13 13 13
Deltas of deltas: -16 -15 0 -6 0 0 0 0 1 -1 0 0 0 0 1 -1 0 1 -2 1 1 -1 0 0 1 -1 0 1 -2 1 1 -1 0 1 -2 1 1 -1 1 -1 -1 1 1 0 -1 0 0 0
Maximum entropy: 90
=== C with SHA-256 between clock reads ===
Clock resolution: 0.000000001
Deltas (ns): 756852 1287 542 470 472 445 442 436 434 439 488 435 433 434 440 439 439 435 432 433 435 432 433 433 429 433 453 441 437 437 431 433 432 430 431 438 436 434 431 433 435 436 435 433 430 436 435 437 428
Deltas of deltas: -755565 -745 -72 2 -27 -3 -6 -2 5 49 -53 -2 1 6 -1 0 -4 -3 1 2 -3 1 0 -4 4 20 -12 -4 0 -6 2 -1 -2 1 7 -2 -2 -3 2 2 1 -1 -2 -3 6 -1 2 -9
Maximum entropy: 188Thanks for writing that code!
The point is this: Getting micro-timing won’t give us as much entropy as we want, but it will still give us entropy. So it’s a perfectly good yet-another-source of entropy to feed in to an entropy pool (such as the input to a XOF).
If those Coldcard devices had used this code as one source of entropy, and this source of entropy was the only entropy still working, they never would had been compromised.
(I won’t update my 18-year-old PRNG to use this code, of course, since that code is now 18 years old and there are no known weaknesses in said code)
Hold on I have to go edit the rest of my responses because I just assumed you wrote the code correctly; you did not.
You are not hashing between calls to the timer. The sha256 hash itself is responsible for doing physical things to the chip (heating up some parts unevenly during the hashing computation) which introduces meaningful entropy between calls to the current time.
You can't just do calls to clock_gettime(), you have do an actual sequential sha256() call between them. Please run this code again and tell me what results you get.