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..cf9db7721d208c 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,20 @@ 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. + 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);