fix: allow increment of 1 in Monotonic entropy for io.Reader sources

MonotonicEntropy.random rejected a validly drawn value of 0 in its
rejection-sampling loop, making an increment of exactly 1 unreachable
for entropy sources without a fast Int63n path (e.g. crypto/rand.Reader).
For inc == 2 this made the increment fully deterministic (always 2),
contradicting the documented "random number between 1 and inc inclusive"
contract. Restructure the loop to match the crypto/rand.Int pattern it is
adapted from, so 0 is a valid draw and the result is correctly uniform
over [1, inc].
This commit is contained in:
dualfroz committed 2026-09-06 01:24:59 +02:00
1 parent 8b0a7fd89a
commit 4c737a9be4
2 files changed
+53 -4

No files matched your search

+9 -4
View File
@@ -641,7 +641,7 @@ func (m *MonotonicEntropy) increment() error {
return nil
}
// random returns a uniform random value in [1, m.inc), reading entropy
// random returns a uniform random value in [1, m.inc], reading entropy
// from m.Reader. When m.inc == 0 || m.inc == 1, it returns 1.
// Adapted from: https://golang.org/pkg/crypto/rand/#Int
func (m *MonotonicEntropy) random() (inc uint64, err error) {
@@ -651,7 +651,7 @@ func (m *MonotonicEntropy) random() (inc uint64, err error) {
// Fast path for using a underlying rand.Rand directly.
if m.rng != nil {
// Range: [1, m.inc)
// Range: [1, m.inc]
return 1 + uint64(m.rng.Int63n(int64(m.inc))), nil
}
@@ -667,7 +667,7 @@ func (m *MonotonicEntropy) random() (inc uint64, err error) {
msbitLen = 8
}
for inc == 0 || inc >= m.inc {
for {
if _, err = io.ReadFull(m.Reader, m.rand[:byteLen]); err != nil {
return 0, err
}
@@ -688,9 +688,14 @@ func (m *MonotonicEntropy) random() (inc uint64, err error) {
case 5, 6, 7, 8:
inc = uint64(binary.LittleEndian.Uint64(m.rand[:8]))
}
// inc may exceed m.inc; redraw rather than bias the low end of the range.
if inc < m.inc {
break
}
}
// Range: [1, m.inc)
// Range: [1, m.inc]
return 1 + inc, nil
}