This was actually a regression introduced when we started using BLAKE3.
Previously, our Read method for Hash would modify the state of the hash,
so multiple subsequent calls would result in different values.
With BLAKE3, we were calling .Digest().Read() each time, which finalized
the current state of the hash, and then read some bytes from that
digest. The problem is that each new call to Read calls Digest() from
the same state, thus producing the same output.
This lead to a spurious deadlock in methods that use rejection sampling, since
if you were unlucky in your sampling, you would always get stuck, since
you would never read different data.
This was spurious, because you need to be unlucky for this to happen.
The fix is to explicitly require calling .Digest() on Hash, in order to
get an io.Reader, instead of having Hash implement io.Reader itself.
This means that there's a clear point where you start reading the output
of what you've hashed so far.
Fixes #44
This was actually a regression introduced when we started using BLAKE3. Previously, our Read method for Hash would modify the state of the hash, so multiple subsequent calls would result in different values.
With BLAKE3, we were calling .Digest().Read() each time, which finalized the current state of the hash, and then read some bytes from that digest. The problem is that each new call to Read calls Digest() from the same state, thus producing the same output.
This lead to a spurious deadlock in methods that use rejection sampling, since if you were unlucky in your sampling, you would always get stuck, since you would never read different data.
This was spurious, because you need to be unlucky for this to happen.
The fix is to explicitly require calling .Digest() on Hash, in order to get an io.Reader, instead of having Hash implement io.Reader itself. This means that there's a clear point where you start reading the output of what you've hashed so far.