From 8a36fe3fd82b73666e9d0d9e8294c59cced1b268 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 14 May 2026 06:57:49 +0300 Subject: [PATCH] Revert "rand: use the OS CSPRNG for `uuid_v4`, `uuid_v7`, `UUIDSession` and `ulid` (#27152)" This reverts commit 11d274235aa458c4d5e86ce45763336195f17411. --- vlib/crypto/rand/bigint/bigint.v | 51 --------------- vlib/crypto/rand/utils.v | 1 - .../{bigint/bigint_test.v => utils_test.v} | 8 +-- vlib/rand/rand.c.v | 63 ++++--------------- vlib/rand/rand.js.v | 15 ----- vlib/rand/rand.v | 15 +++++ .../c/testdata/struct_field_result_init.vv | 4 +- 7 files changed, 33 insertions(+), 124 deletions(-) delete mode 100644 vlib/crypto/rand/bigint/bigint.v rename vlib/crypto/rand/{bigint/bigint_test.v => utils_test.v} (66%) diff --git a/vlib/crypto/rand/bigint/bigint.v b/vlib/crypto/rand/bigint/bigint.v deleted file mode 100644 index 81278b66b..000000000 --- a/vlib/crypto/rand/bigint/bigint.v +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -module bigint - -import crypto.rand -import math.big - -// int_big creates a random `big.Integer` with range `[0, n)`. -// Returns an error if `n` is 0 or negative. -// This function used to live in `crypto.rand`; it moved here so that -// `crypto.rand` itself does not depend on `math.big`, which keeps -// `crypto.rand` usable from lower-level modules without introducing -// import cycles. -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 := rand.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.v b/vlib/crypto/rand/utils.v index 1f3b66341..59ca92ef7 100644 --- a/vlib/crypto/rand/utils.v +++ b/vlib/crypto/rand/utils.v @@ -57,7 +57,6 @@ fn bytes_to_u64(b []u8) []u64 { // int_big creates a random `big.Integer` with range [0, n) // returns an error if `n` is 0 or negative. -@[deprecated: 'use crypto.rand.bigint.int_big() instead'] pub fn int_big(n big.Integer) !big.Integer { if n.signum < 1 { return error('`n` cannot be 0 or negative.') diff --git a/vlib/crypto/rand/bigint/bigint_test.v b/vlib/crypto/rand/utils_test.v similarity index 66% rename from vlib/crypto/rand/bigint/bigint_test.v rename to vlib/crypto/rand/utils_test.v index 8da40a786..5fe37551d 100644 --- a/vlib/crypto/rand/bigint/bigint_test.v +++ b/vlib/crypto/rand/utils_test.v @@ -1,23 +1,23 @@ import math.big -import crypto.rand.bigint +import crypto.rand fn test_int_big() { z := big.integer_from_int(0) - if _ := bigint.int_big(z) { + if _ := rand.int_big(z) { assert false } else { assert true } n := big.integer_from_int(-1) - if _ := bigint.int_big(n) { + if _ := rand.int_big(n) { assert false } else { assert true } m := big.integer_from_int(1).left_shift(128) - l := bigint.int_big(m)! // actual large number + l := rand.int_big(m)! // actual large number assert l < m assert l > n } diff --git a/vlib/rand/rand.c.v b/vlib/rand/rand.c.v index a750a0dd4..a705c81ce 100644 --- a/vlib/rand/rand.c.v +++ b/vlib/rand/rand.c.v @@ -1,27 +1,13 @@ module rand import time -import crypto.rand as crypto_rand -// csprng_u64_pair reads 16 bytes from the OS CSPRNG and packs them into two -// big-endian u64 values, suitable for feeding `internal_uuid`. -fn csprng_u64_pair() (u64, u64) { - buf := crypto_rand.read(16) or { panic('rand: OS CSPRNG unavailable: ${err}') } - mut a := u64(0) - mut b := u64(0) - for i in 0 .. 8 { - a = (a << 8) | u64(buf[i]) - b = (b << 8) | u64(buf[8 + i]) - } - return a, b -} - -// uuid_v4 generates a random (v4) UUID, sourcing its 122 random bits from the -// OS CSPRNG (see `crypto.rand.read`). +// uuid_v4 generates a random (v4) UUID. // See https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) // See https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4 pub fn uuid_v4() string { - rand_1, rand_2 := csprng_u64_pair() + rand_1 := default_rng.u64() + rand_2 := default_rng.u64() return internal_uuid(4, rand_1, rand_2) } @@ -73,14 +59,12 @@ fn internal_uuid(version u8, rand_1 u64, rand_2 u64) string { } } -// uuid_v7 generates a time-ordered (v7) UUID. The 48-bit Unix millisecond -// timestamp is concatenated with random bits drawn from the OS CSPRNG. +// uuid_v7 generates a time-ordered (v7) UUID. // See https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7 pub fn uuid_v7() string { timestamp_48 := u64(time.now().unix_milli()) << 16 - r1, r2 := csprng_u64_pair() - rand_1 := timestamp_48 | (r1 & 0xFFFF) - rand_2 := r2 + rand_1 := timestamp_48 | default_rng.u16() + rand_2 := default_rng.u64() return internal_uuid(7, rand_1, rand_2) } @@ -104,7 +88,7 @@ pub fn (mut u UUIDSession) next() string { // make place for holding 4 bits `version` timestamp_shift_4bits := (timestamp & 0xFFFF_FFFF_FFFF_0000) | ((timestamp & 0x0000_0000_0000_FFFF) >> 4) rand_1 := (timestamp_shift_4bits & 0xFFFF_FFFF_FFFF_FFC0) | u64(u.counter & 0x3F) // 6 bits session counter - _, rand_2 := csprng_u64_pair() + rand_2 := default_rng.u64() u.counter++ @@ -113,11 +97,8 @@ pub fn (mut u UUIDSession) next() string { const ulid_encoding = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' -// internal_ulid_format encodes a 48-bit Unix millisecond timestamp and 128 -// bits of randomness (supplied as two u64s, of which only the low 45 + 35 -// bits are used) into a 26-character Crockford Base32 ULID. @[direct_array_access] -fn internal_ulid_format(unix_time_milli u64, rand_a u64, rand_b u64) string { +fn internal_ulid_at_millisecond(mut rng PRNG, unix_time_milli u64) string { buflen := 26 mut buf := unsafe { malloc_noscan(27) } mut t := unix_time_milli @@ -129,7 +110,8 @@ fn internal_ulid_format(unix_time_milli u64, rand_a u64, rand_b u64) string { t = t >> 5 i-- } - mut x := rand_a + // first rand set + mut x := rng.u64() i = 10 for i < 19 { unsafe { @@ -138,7 +120,8 @@ fn internal_ulid_format(unix_time_milli u64, rand_a u64, rand_b u64) string { x = x >> 5 i++ } - x = rand_b + // second rand set + x = rng.u64() for i < 26 { unsafe { buf[i] = ulid_encoding[int(x & 0x1F)] @@ -152,28 +135,6 @@ fn internal_ulid_format(unix_time_milli u64, rand_a u64, rand_b u64) string { } } -@[direct_array_access] -fn internal_ulid_at_millisecond(mut rng PRNG, unix_time_milli u64) string { - return internal_ulid_format(unix_time_milli, rng.u64(), rng.u64()) -} - -// ulid generates a unique lexicographically sortable identifier whose -// 80 random bits are sourced from the OS CSPRNG (see `crypto.rand.read`). -// See https://github.com/ulid/spec . -// Note: ULIDs can leak timing information, if you make them public, because -// you can infer the rate at which some resource is being created, like -// users or business transactions. -// (https://news.ycombinator.com/item?id=14526173) -pub fn ulid() string { - return ulid_at_millisecond(u64(time.utc().unix_milli())) -} - -// ulid_at_millisecond does the same as `ulid` but takes a custom Unix millisecond timestamp via `unix_milli`. -pub fn ulid_at_millisecond(unix_time_milli u64) string { - rand_a, rand_b := csprng_u64_pair() - return internal_ulid_format(unix_time_milli, rand_a, rand_b) -} - @[direct_array_access] fn internal_string_from_set(mut rng PRNG, charset string, len int) string { if len == 0 { diff --git a/vlib/rand/rand.js.v b/vlib/rand/rand.js.v index 00e66bfbf..46b0f6435 100644 --- a/vlib/rand/rand.js.v +++ b/vlib/rand/rand.js.v @@ -64,18 +64,3 @@ fn read_internal(mut rng PRNG, mut buf []u8) { buf[i] = rng.u8() } } - -// ulid generates an unique lexicographically sortable identifier. -// See https://github.com/ulid/spec . -// Note: ULIDs can leak timing information, if you make them public, because -// you can infer the rate at which some resource is being created, like -// users or business transactions. -// (https://news.ycombinator.com/item?id=14526173) -pub fn ulid() string { - return default_rng.ulid() -} - -// ulid_at_millisecond does the same as `ulid` but takes a custom Unix millisecond timestamp via `unix_milli`. -pub fn ulid_at_millisecond(unix_time_milli u64) string { - return default_rng.ulid_at_millisecond(unix_time_milli) -} diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index 575de7b34..3a3ce3e65 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -710,6 +710,21 @@ const english_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' const hex_chars = '0123456789abcdef' const ascii_chars = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_`abcdefghijklmnopqrstuvwxyz{|}~' +// ulid generates an unique lexicographically sortable identifier. +// See https://github.com/ulid/spec . +// Note: ULIDs can leak timing information, if you make them public, because +// you can infer the rate at which some resource is being created, like +// users or business transactions. +// (https://news.ycombinator.com/item?id=14526173) +pub fn ulid() string { + return default_rng.ulid() +} + +// ulid_at_millisecond does the same as `ulid` but takes a custom Unix millisecond timestamp via `unix_milli`. +pub fn ulid_at_millisecond(unix_time_milli u64) string { + return default_rng.ulid_at_millisecond(unix_time_milli) +} + // string_from_set returns a string of length `len` containing random characters sampled from the given `charset`. pub fn string_from_set(charset string, len int) string { return default_rng.string_from_set(charset, len) diff --git a/vlib/v/gen/c/testdata/struct_field_result_init.vv b/vlib/v/gen/c/testdata/struct_field_result_init.vv index 164f92bde..9ddf17418 100644 --- a/vlib/v/gen/c/testdata/struct_field_result_init.vv +++ b/vlib/v/gen/c/testdata/struct_field_result_init.vv @@ -1,6 +1,6 @@ import math import math.big { Integer } -import crypto.rand.bigint +import crypto.rand struct SSS { mut: @@ -11,7 +11,7 @@ mut: fn (s SSS) random() !Integer { mut result := big.zero_int + s.prime result = result - big.one_int - return bigint.int_big(result) + return rand.int_big(result) } // mod_inverse computes the multiplicative inverse of the number on the field -- 2.39.5