logoalt Hacker News

sltkryesterday at 1:29 PM3 repliesview on HN

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.

Replies

Taekyesterday at 2:16 PM

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.

show 1 reply
Taekyesterday at 1:58 PM

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: 188
show 1 reply
strenholmeyesterday at 1:40 PM

Thanks 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)

show 1 reply