crypto.rand: add support for convenient generation of a random big integer in the interval [0, n) (#22266)

This commit is contained in:
Einar Hjortdal 2024-09-21 14:58:10 +02:00 committed by GitHub
parent 8ae627ff79
commit 5e624a5b23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 0 deletions

View File

@ -5,6 +5,7 @@
module rand
import math.bits
import math.big
import encoding.binary
// int_u64 returns a random unsigned 64-bit integer `u64` read from a real OS source of entropy.
@ -53,3 +54,42 @@ fn bytes_to_u64(b []u8) []u64 {
}
return z
}
// int_big creates a random `big.Integer` with range [0, n)
// returns an error if `n` is 0 or negative.
pub fn int_big(n big.Integer) !big.Integer {
if n.signum < 1 {
return error('`n` cannot be 0 or negative.')
}
max := n - big.integer_from_int(1)
len := max.bit_len()
if len == 0 {
// max = n - 1, if max = 0 then return max, as it is the only valid integer in [0, 1)
return max
}
// k is the maximum byte length needed to encode a value < n
k := (len + 7) / 8
// b is the number of bits in the most significant byte of n-1
mut b := u8(len % 8)
if b == 0 {
b = 8
}
mut result := big.Integer{}
for {
mut bytes := read(k)!
// Clear bits in the first byte to increase the probability that the result is < max
bytes[0] &= u8(int(1 << b) - 1)
result = big.integer_from_bytes(bytes)
if result < max {
break
}
}
return result
}

View File

@ -0,0 +1,23 @@
import math.big
import crypto.rand
fn test_int_big() {
z := big.integer_from_int(0)
if _ := rand.int_big(z) {
assert false
} else {
assert true
}
n := big.integer_from_int(-1)
if _ := rand.int_big(n) {
assert false
} else {
assert true
}
m := big.integer_from_int(1).left_shift(128)
l := rand.int_big(m)! // actual large number
assert l < m
assert l > n
}