From 59d0ee85dcc6176e4614b5c9c3b2a104a34acd7e Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Mon, 11 May 2026 23:42:28 +0200 Subject: [PATCH 1/2] Reduce RNG bytes used by Guid.CreateVersion7 from 16 to 10 on Unix UUIDv7 only consumes 74 bits of randomness, all of which fit in the trailing 10 bytes of the GUID. The first 6 bytes are immediately overwritten with the unix_ts_ms timestamp, so generating them is wasted work. On Apple platforms CCRandomGenerateBytes has a fast path for requests <= 12 bytes, so cutting the request from 16 to 10 bytes hits that path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Guid.Unix.cs | 20 +++++++++++++++++++ .../src/System/Guid.Windows.cs | 9 +++++++++ .../System.Private.CoreLib/src/System/Guid.cs | 14 ++++++------- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs index ca0a63de87a5b5..3692eef65f7311 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs @@ -33,5 +33,25 @@ public static unsafe Guid NewGuid() return g; } + + // Returns a Guid whose bytes 6..15 contain cryptographically-secure random data, as + // required by CreateVersion7. Bytes 0..5 are intentionally left uninitialized; the + // caller overwrites them with the unix_ts_ms timestamp. + // + // Asking for only 10 bytes (instead of all 16) hits the Apple CoreCrypto fast path + // for <=12 byte requests and reduces the amount of entropy pulled from the kernel + // on other Unix platforms. The version and variant fields are applied by the caller, + // so this helper does not set them (unlike NewGuid which produces a v4 Guid). + private static unsafe Guid CreateVersion7Random() + { + Unsafe.SkipInit(out Guid g); +#if !TARGET_WASI + Interop.GetCryptographicallySecureRandomBytes((byte*)&g + 6, 10); +#else + // TODOWASI: crypto secure random bytes + Interop.GetRandomBytes((byte*)&g + 6, 10); +#endif + return g; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs index acef4c79a976fc..7e69b0dafa02f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs @@ -19,6 +19,15 @@ public static unsafe Guid NewGuid() return g; } + // Returns a Guid whose bytes 6..15 contain cryptographically-secure random data, as + // required by CreateVersion7. Bytes 0..5 are unspecified; the caller overwrites them + // with the unix_ts_ms timestamp. + // + // On Windows we keep using CoCreateGuid via NewGuid: it has been measured to be + // faster than BCryptGenRandom for this size class, and the 6 bytes that get + // overwritten by the timestamp don't make BCryptGenRandom competitive. + private static Guid CreateVersion7Random() => NewGuid(); + private static void ThrowForHr(int hr) { // We don't expect that this will ever throw an error, none are even documented, and so we don't want to pull diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index 60b6b289991ca3..5fd34e137cca00 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -305,13 +305,6 @@ public Guid(string g) /// public static Guid CreateVersion7(DateTimeOffset timestamp) { - // NewGuid uses CoCreateGuid on Windows and Interop.GetCryptographicallySecureRandomBytes on Unix to get - // cryptographically-secure random bytes. We could use Interop.BCrypt.BCryptGenRandom to generate the random - // bytes on Windows, as is done in RandomNumberGenerator, but that's measurably slower than using CoCreateGuid. - // And while CoCreateGuid only generates 122 bits of randomness, the other 6 bits being for the version / variant - // fields, this method also needs those bits to be non-random, so we can just use NewGuid for efficiency. - Guid result = NewGuid(); - // 2^48 is roughly 8925.5 years, which from the Unix Epoch means we won't // overflow until around July of 10,895. So there isn't any need to handle // it given that DateTimeOffset.MaxValue is December 31, 9999. However, we @@ -321,6 +314,13 @@ public static Guid CreateVersion7(DateTimeOffset timestamp) long unix_ts_ms = timestamp.ToUnixTimeMilliseconds(); ArgumentOutOfRangeException.ThrowIfNegative(unix_ts_ms, nameof(timestamp)); + // UUIDv7 only consumes 74 bits of randomness, which fit in the trailing 10 bytes + // of the GUID (12 bits of rand_a in bytes 6..7 next to the 4-bit version field, + // and 62 bits of rand_b in bytes 8..15 next to the 2-bit variant field). Bytes 0..5 + // are overwritten with unix_ts_ms below, so the platform-specific helper only needs + // to ensure bytes 6..15 of the returned Guid contain crypto-secure random data. + Guid result = CreateVersion7Random(); + Unsafe.AsRef(in result._a) = (int)(unix_ts_ms >> 16); Unsafe.AsRef(in result._b) = (short)(unix_ts_ms); From 92dbf286a2df5eeb1289d97757991b54bb15968c Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Mon, 11 May 2026 23:45:15 +0200 Subject: [PATCH 2/2] Update Guid.Unix.cs --- src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs index 3692eef65f7311..cf9db7721d208c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs @@ -37,11 +37,6 @@ public static unsafe Guid NewGuid() // Returns a Guid whose bytes 6..15 contain cryptographically-secure random data, as // required by CreateVersion7. Bytes 0..5 are intentionally left uninitialized; the // caller overwrites them with the unix_ts_ms timestamp. - // - // Asking for only 10 bytes (instead of all 16) hits the Apple CoreCrypto fast path - // for <=12 byte requests and reduces the amount of entropy pulled from the kernel - // on other Unix platforms. The version and variant fields are applied by the caller, - // so this helper does not set them (unlike NewGuid which produces a v4 Guid). private static unsafe Guid CreateVersion7Random() { Unsafe.SkipInit(out Guid g);