From 5e624a5b23d1c8ce2a901f1c1612fdc4d623d48d Mon Sep 17 00:00:00 2001 From: Einar Hjortdal <102909397+einar-hjortdal@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:58:10 +0200 Subject: [PATCH] crypto.rand: add support for convenient generation of a random big integer in the interval `[0, n)` (#22266) --- vlib/crypto/rand/utils.v | 40 +++++++++++++++++++++++++++++++++++ vlib/crypto/rand/utils_test.v | 23 ++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 vlib/crypto/rand/utils_test.v diff --git a/vlib/crypto/rand/utils.v b/vlib/crypto/rand/utils.v index bde236f791..59ca92ef76 100644 --- a/vlib/crypto/rand/utils.v +++ b/vlib/crypto/rand/utils.v @@ -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 +} diff --git a/vlib/crypto/rand/utils_test.v b/vlib/crypto/rand/utils_test.v new file mode 100644 index 0000000000..5fe37551dd --- /dev/null +++ b/vlib/crypto/rand/utils_test.v @@ -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 +}