From 1f1381a866a110953f9950d8c0c6497a2567daa0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 5 Jan 2026 20:42:37 -0800 Subject: [PATCH] update API usage of std.crypto.random to io.random --- lib/compiler/aro/aro/Driver.zig | 3 +- lib/std/Io/Threaded.zig | 65 ++++++--- lib/std/Io/net/test.zig | 6 +- lib/std/Io/test.zig | 25 +++- lib/std/Random.zig | 2 +- lib/std/Random/test.zig | 3 +- lib/std/crypto.zig | 4 + lib/std/crypto/25519/ed25519.zig | 55 ++++---- lib/std/crypto/25519/edwards25519.zig | 8 +- lib/std/crypto/25519/scalar.zig | 13 +- lib/std/crypto/25519/x25519.zig | 4 +- lib/std/crypto/argon2.zig | 2 +- lib/std/crypto/bcrypt.zig | 111 +++++++++------ lib/std/crypto/ecdsa.zig | 28 ++-- lib/std/crypto/hybrid_kem.zig | 135 ++++++++++--------- lib/std/crypto/ml_dsa.zig | 19 +-- lib/std/crypto/ml_kem.zig | 49 ++++--- lib/std/crypto/pcurves/p256.zig | 4 +- lib/std/crypto/pcurves/p256/scalar.zig | 8 +- lib/std/crypto/pcurves/p384.zig | 4 +- lib/std/crypto/pcurves/p384/scalar.zig | 8 +- lib/std/crypto/pcurves/secp256k1.zig | 4 +- lib/std/crypto/pcurves/secp256k1/scalar.zig | 8 +- lib/std/crypto/pcurves/tests/p256.zig | 17 ++- lib/std/crypto/pcurves/tests/p384.zig | 17 ++- lib/std/crypto/pcurves/tests/secp256k1.zig | 22 +-- lib/std/crypto/salsa20.zig | 34 ++--- lib/std/crypto/scrypt.zig | 11 +- lib/std/crypto/timing_safe.zig | 23 ++-- lib/std/crypto/tls/Client.zig | 14 +- lib/std/fs/test.zig | 2 +- lib/std/http/Client.zig | 4 +- lib/std/os/linux/tls.zig | 12 +- lib/std/posix/test.zig | 10 -- lib/std/testing.zig | 2 +- src/Compilation.zig | 17 ++- src/Package.zig | 4 +- src/Package/Fetch.zig | 16 ++- src/Package/Manifest.zig | 10 +- src/link.zig | 7 +- src/link/Lld.zig | 6 +- src/main.zig | 31 +++-- test/standalone/simple/guess_number/main.zig | 3 +- tools/doctest.zig | 7 +- tools/incr-check.zig | 13 +- 45 files changed, 514 insertions(+), 336 deletions(-) diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 400a1ef6f6..154b4d56a5 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1217,11 +1217,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u } fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 { + const io = d.comp.io; const random_bytes_count = 12; const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); var random_bytes: [random_bytes_count]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var random_name: [sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&random_name, &random_bytes); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d86268629a..123d300cc3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -78,7 +78,7 @@ pub const Csprng = struct { pub const seed_len = std.Random.DefaultCsprng.secret_seed_length; pub fn isInitialized(c: *const Csprng) bool { - return c.rng.offset == std.math.maxInt(usize); + return c.rng.offset != std.math.maxInt(usize); } }; @@ -2395,11 +2395,11 @@ fn dirCreateDirPath( status = .created; } else |err| switch (err) { error.PathAlreadyExists => { - // stat the file and return an error if it's not a directory - // this is important because otherwise a dangling symlink - // could cause an infinite loop - const fstat = try dirStatFile(t, dir, component.path, .{}); - if (fstat.kind != .directory) return error.NotDir; + // It is important to return an error if it's not a directory + // because otherwise a dangling symlink could cause an infinite + // loop. + const kind = try filePathKind(t, dir, component.path); + if (kind != .directory) return error.NotDir; }, error.FileNotFound => |e| { component = it.previous() orelse return e; @@ -2740,6 +2740,35 @@ fn dirStatFileWasi( } } +fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind { + if (native_os == .linux) { + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + const linux = std.os.linux; + const syscall: Syscall = try .start(); + while (true) { + var statx = std.mem.zeroes(linux.Statx); + switch (linux.errno(linux.statx(dir.handle, sub_path_posix, 0, .{ .TYPE = true }, &statx))) { + .SUCCESS => { + syscall.finish(); + if (!statx.mask.TYPE) return error.Unexpected; + return statxKind(statx.mode); + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .NOMEM => return syscall.fail(error.SystemResources), + else => |err| return syscall.unexpectedErrno(err), + } + } + } + + const stat = try dirStatFile(t, dir, sub_path, .{}); + return stat.kind; +} + fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -12310,16 +12339,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { .nlink = stx.nlink, .size = stx.size, .permissions = .fromMode(stx.mode), - .kind = switch (stx.mode & std.os.linux.S.IFMT) { - std.os.linux.S.IFDIR => .directory, - std.os.linux.S.IFCHR => .character_device, - std.os.linux.S.IFBLK => .block_device, - std.os.linux.S.IFREG => .file, - std.os.linux.S.IFIFO => .named_pipe, - std.os.linux.S.IFLNK => .sym_link, - std.os.linux.S.IFSOCK => .unix_domain_socket, - else => .unknown, - }, + .kind = statxKind(stx.mode), .atime = if (!stx.mask.ATIME) null else .{ .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec), }, @@ -12328,6 +12348,19 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { }; } +fn statxKind(stx_mode: u16) File.Kind { + return switch (stx_mode & std.os.linux.S.IFMT) { + std.os.linux.S.IFDIR => .directory, + std.os.linux.S.IFCHR => .character_device, + std.os.linux.S.IFBLK => .block_device, + std.os.linux.S.IFREG => .file, + std.os.linux.S.IFIFO => .named_pipe, + std.os.linux.S.IFLNK => .sym_link, + std.os.linux.S.IFSOCK => .unix_domain_socket, + else => .unknown, + }; +} + fn statFromPosix(st: *const posix.Stat) File.Stat { const atime = st.atime(); const mtime = st.mtime(); diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 45c26f9540..75f72d3aff 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -275,7 +275,7 @@ test "listen on a unix socket, send bytes, receive bytes" { const io = testing.io; - const socket_path = try generateFileName("socket.unix"); + const socket_path = try generateFileName(io, "socket.unix"); defer testing.allocator.free(socket_path); const socket_addr = try net.UnixAddress.init(socket_path); @@ -308,11 +308,11 @@ test "listen on a unix socket, send bytes, receive bytes" { try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); } -fn generateFileName(base_name: []const u8) ![]const u8 { +fn generateFileName(io: Io, base_name: []const u8) ![]const u8 { const random_bytes_count = 12; const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); var random_bytes: [12]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var sub_path: [sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name }); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index cca89dcb1f..17b86af234 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -565,13 +565,28 @@ test "tasks spawned in group after Group.cancel are canceled" { try group.concurrent(io, global.waitThenSpawn, .{ io, &group }); } -test "CSPRNG" { +test "random" { const io = testing.io; - var random = io.random(); + var a: u64 = undefined; + var b: u64 = undefined; + var c: u64 = undefined; + + io.random(@ptrCast(&a)); + io.random(@ptrCast(&b)); + io.random(@ptrCast(&c)); - const a = random.int(u64); - const b = random.int(u64); - const c = random.int(u64); try std.testing.expect(a ^ b ^ c != 0); } + +test "randomSecure" { + const io = testing.io; + + var buf_a: [50]u8 = undefined; + var buf_b: [50]u8 = undefined; + try io.randomSecure(&buf_a); + try io.randomSecure(&buf_b); + // If this test fails the chance is significantly higher that there is a bug than + // that two sets of 50 bytes were equal. + try expect(!mem.eql(u8, &buf_a, &buf_b)); +} diff --git a/lib/std/Random.zig b/lib/std/Random.zig index 6a7cae3e78..218ddac1d6 100644 --- a/lib/std/Random.zig +++ b/lib/std/Random.zig @@ -38,7 +38,7 @@ pub const IoSource = struct { pub fn interface(this: *const @This()) std.Random { return .{ - .ptr = this, + .ptr = @constCast(this), .fillFn = fill, }; } diff --git a/lib/std/Random/test.zig b/lib/std/Random/test.zig index 8ceacbc934..3db56df655 100644 --- a/lib/std/Random/test.zig +++ b/lib/std/Random/test.zig @@ -436,8 +436,9 @@ fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void { } test "CSPRNG" { + const io = std.testing.io; var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined; - std.crypto.random.bytes(&secret_seed); + io.random(&secret_seed); var csprng = DefaultCsprng.init(secret_seed); const random = csprng.random(); const a = random.int(u64); diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 15ca7a0167..7833a31237 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -303,6 +303,9 @@ test { _ = dh.X25519; _ = kem.kyber_d00; + _ = kem.hybrid; + _ = kem.kyber_d00; + _ = kem.ml_kem; _ = ecc.Curve25519; _ = ecc.Edwards25519; @@ -340,6 +343,7 @@ test { _ = sign.Ed25519; _ = sign.ecdsa; + _ = sign.mldsa; _ = stream.chacha.ChaCha20IETF; _ = stream.chacha.ChaCha12IETF; diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index a78e1aa3ab..b6f582839e 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -333,12 +333,10 @@ pub const Ed25519 = struct { } /// Generate a new, random key pair. - /// - /// `crypto.random.bytes` must be supported by the target. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -389,18 +387,21 @@ pub const Ed25519 = struct { /// Create a Signer, that can be used for incremental signing. /// Note that the signature is not deterministic. - /// The noise parameter, if set, should be something unique for each message, - /// such as a random nonce, or a counter. - pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer { + pub fn signer( + key_pair: KeyPair, + /// If set, should be something unique for each message, such as a + /// random nonce, or a counter. + noise: ?[noise_length]u8, + /// Filled with cryptographically secure randomness. + entropy: *const [noise_length]u8, + ) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer { if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) { return error.KeyMismatch; } const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix(); var h = Sha512.init(.{}); h.update(&scalar_and_prefix.prefix); - var noise2: [noise_length]u8 = undefined; - crypto.random.bytes(&noise2); - h.update(&noise2); + h.update(entropy); if (noise) |*z| { h.update(z); } @@ -420,7 +421,7 @@ pub const Ed25519 = struct { }; /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one - pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void { + pub fn verifyBatch(io: std.Io, comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void { var r_batch: [count]CompressedScalar = undefined; var s_batch: [count]CompressedScalar = undefined; var a_batch: [count]Curve = undefined; @@ -454,7 +455,7 @@ pub const Ed25519 = struct { var z_batch: [count]Curve.scalar.CompressedScalar = undefined; for (&z_batch) |*z| { - crypto.random.bytes(z[0..16]); + io.random(z[0..16]); @memset(z[16..], 0); } @@ -587,12 +588,14 @@ test "signature" { } test "batch verification" { + const io = std.testing.io; + for (0..16) |_| { - const key_pair = Ed25519.KeyPair.generate(); + const key_pair = Ed25519.KeyPair.generate(io); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; - crypto.random.bytes(&msg1); - crypto.random.bytes(&msg2); + io.random(&msg1); + io.random(&msg2); const sig1 = try key_pair.sign(&msg1, null); const sig2 = try key_pair.sign(&msg2, null); var signature_batch = [_]Ed25519.BatchElement{ @@ -607,10 +610,10 @@ test "batch verification" { .public_key = key_pair.public_key, }, }; - try Ed25519.verifyBatch(2, signature_batch); + try Ed25519.verifyBatch(io, 2, signature_batch); signature_batch[1].sig = sig1; - try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch)); + try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(io, signature_batch.len, signature_batch)); } } @@ -718,14 +721,15 @@ test "test vectors" { } test "with blind keys" { + const io = std.testing.io; const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair; // Create a standard Ed25519 key pair - const kp = Ed25519.KeyPair.generate(); + const kp = Ed25519.KeyPair.generate(io); // Create a random blinding seed var blind: [32]u8 = undefined; - crypto.random.bytes(&blind); + io.random(&blind); // Blind the key pair const blind_kp = try BlindKeyPair.init(kp, blind, "ctx"); @@ -741,9 +745,12 @@ test "with blind keys" { } test "signatures with streaming" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); - var signer = try kp.signer(null); + var entropy: [Ed25519.noise_length]u8 = undefined; + io.random(&entropy); + var signer = try kp.signer(null, &entropy); signer.update("mes"); signer.update("sage"); const sig = signer.finalize(); @@ -757,7 +764,8 @@ test "signatures with streaming" { } test "key pair from secret key" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key); try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes()); try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes()); @@ -788,7 +796,8 @@ test "cofactored vs cofactorless verification" { } test "regular signature verifies with both verify and verifyStrict" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); const msg = "test message"; const sig = try kp.sign(msg, null); try sig.verify(msg, kp.public_key); diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 0bce0139f6..0672474eab 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -575,10 +575,11 @@ test "packing/unpacking" { } test "point addition/subtraction" { + const io = std.testing.io; var s1: [32]u8 = undefined; var s2: [32]u8 = undefined; - crypto.random.bytes(&s1); - crypto.random.bytes(&s2); + io.random(&s1); + io.random(&s2); const p = try Edwards25519.basePoint.clampedMul(s1); const q = try Edwards25519.basePoint.clampedMul(s2); const r = p.add(q).add(q).sub(q).sub(q); @@ -622,9 +623,10 @@ test "implicit reduction of invalid scalars" { } test "subgroup check" { + const io = std.testing.io; for (0..100) |_| { var p = Edwards25519.basePoint; - const s = Edwards25519.scalar.random(); + const s = Edwards25519.scalar.random(io); p = try p.mulPublic(s); try p.rejectUnexpectedSubgroup(); } diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index 59a4b41f7a..97887ee004 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -101,8 +101,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar) CompressedScalar { } /// Return a random scalar < L -pub fn random() CompressedScalar { - return Scalar.random().toBytes(); +pub fn random(io: std.Io) CompressedScalar { + return Scalar.random(io).toBytes(); } /// A scalar in unpacked representation @@ -560,10 +560,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [64]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes64(s); if (!n.isZero()) { return n; @@ -879,8 +879,9 @@ test "scalar field inversion" { } test "random scalar" { - const s1 = random(); - const s2 = random(); + const io = std.testing.io; + const s1 = random(io); + const s2 = random(io); try std.testing.expect(!mem.eql(u8, &s1, &s2)); } diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index ee40f34940..dc49d3e9a2 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -41,10 +41,10 @@ pub const X25519 = struct { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig index 0fd76e1282..f6bf85782d 100644 --- a/lib/std/crypto/argon2.zig +++ b/lib/std/crypto/argon2.zig @@ -533,7 +533,7 @@ const PhcFormatHasher = struct { if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding; var salt: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt); + io.random(&salt); var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params, mode, io); diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 62fc4fd4cc..738d4060df 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -17,7 +17,7 @@ const HasherError = pwhash.HasherError; const EncodingError = phc_format.Error; const Error = pwhash.Error; -const salt_length: usize = 16; +pub const salt_length: usize = 16; const salt_str_length: usize = 22; const ct_str_length: usize = 31; const ct_length: usize = 24; @@ -426,7 +426,7 @@ pub const Params = struct { fn bcryptWithTruncation( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [dk_length]u8 { var state = State{}; @@ -435,13 +435,13 @@ fn bcryptWithTruncation( @memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]); password_buf[trimmed_len] = 0; const passwordZ = password_buf[0 .. trimmed_len + 1]; - state.expand(salt[0..], passwordZ); + state.expand(salt, passwordZ); const rounds: u64 = @as(u64, 1) << params.rounds_log; var k: u64 = 0; while (k < rounds) : (k += 1) { state.expand0(passwordZ); - state.expand0(salt[0..]); + state.expand0(salt); } crypto.secureZero(u8, &password_buf); @@ -467,7 +467,7 @@ fn bcryptWithTruncation( /// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead. pub fn bcrypt( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [dk_length]u8 { if (password.len <= 72 or params.silently_truncate_password) { @@ -475,7 +475,7 @@ pub fn bcrypt( } var pre_hash: [HmacSha512.mac_length]u8 = undefined; - HmacSha512.create(&pre_hash, password, &salt); + HmacSha512.create(&pre_hash, password, salt); const Encoder = crypt_format.Codec.Encoder; var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined; @@ -623,16 +623,16 @@ const crypt_format = struct { fn strHashInternal( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [hash_length]u8 { var dk = bcrypt(password, salt, params); var salt_str: [salt_str_length]u8 = undefined; - _ = Codec.Encoder.encode(salt_str[0..], salt[0..]); + _ = Codec.Encoder.encode(&salt_str, salt); var ct_str: [ct_str_length]u8 = undefined; - _ = Codec.Encoder.encode(ct_str[0..], dk[0..]); + _ = Codec.Encoder.encode(&ct_str, dk[0..]); var s_buf: [hash_length]u8 = undefined; const s = fmt.bufPrint( @@ -657,21 +657,20 @@ const PhcFormatHasher = struct { hash: BinValue(dk_length), }; - /// Return a non-deterministic hash of the password encoded as a PHC-format string + /// Return a non-deterministic hash of the password encoded as a PHC-format string. fn create( password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) HasherError![]const u8 { - var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); - const hash = bcrypt(password, salt, params); return phc_format.serialize(HashResult{ .alg_id = alg_id, .r = params.rounds_log, - .salt = try BinValue(salt_length).fromSlice(&salt), + .salt = try BinValue(salt_length).fromSlice(salt), .hash = try BinValue(dk_length).fromSlice(&hash), }, buf); } @@ -688,11 +687,11 @@ const PhcFormatHasher = struct { if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length) return HasherError.InvalidEncoding; - const params = Params{ + const params: Params = .{ .rounds_log = hash_result.r, .silently_truncate_password = silently_truncate_password, }; - const hash = bcrypt(password, hash_result.salt.buf, params); + const hash = bcrypt(password, &hash_result.salt.buf, params); const expected_hash = hash_result.hash.constSlice(); if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed; @@ -709,12 +708,11 @@ const CryptFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) HasherError![]const u8 { if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; - var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); - const hash = crypt_format.strHashInternal(password, salt, params); @memcpy(buf[0..hash.len], &hash); @@ -736,9 +734,9 @@ const CryptFormatHasher = struct { const salt_str = str[7..][0..salt_str_length]; var salt: [salt_length]u8 = undefined; - crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding; + crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding; - const wanted_s = crypt_format.strHashInternal(password, salt, .{ + const wanted_s = crypt_format.strHashInternal(password, &salt, .{ .rounds_log = rounds_log, .silently_truncate_password = silently_truncate_password, }); @@ -756,21 +754,28 @@ pub const HashOptions = struct { encoding: pwhash.Encoding, }; -/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. -/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches. +/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key +/// stretching function. /// -/// The function returns a string that includes all the parameters required for verification. +/// bcrypt is a computationally expensive and cache-hard function, explicitly +/// designed to slow down exhaustive searches. /// -/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. -/// If this is an issue for your application, set the `silently_truncate_password` option to `false`. +/// The function returns a string that includes all the parameters required for +/// verification. +/// +/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an +/// issue for your application, set the `silently_truncate_password` option to +/// `false`. pub fn strHash( password: []const u8, options: HashOptions, out: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) Error![]const u8 { switch (options.encoding) { - .phc => return PhcFormatHasher.create(password, options.params, out), - .crypt => return CryptFormatHasher.create(password, options.params, out), + .phc => return PhcFormatHasher.create(password, options.params, out, salt), + .crypt => return CryptFormatHasher.create(password, options.params, out, salt), } } @@ -796,8 +801,9 @@ pub fn strVerify( } test "bcrypt codec" { + const io = testing.io; var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); + io.random(&salt); var salt_str: [salt_str_length]u8 = undefined; _ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]); var salt2: [salt_length]u8 = undefined; @@ -806,14 +812,20 @@ test "bcrypt codec" { } test "bcrypt crypt format" { - var hash_options = HashOptions{ + const io = testing.io; + + var hash_options: HashOptions = .{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .crypt, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; var buf: [hash_length]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password", hash_options, &buf, &salt); + }; try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); try strVerify(s, "password", verify_options); @@ -823,7 +835,11 @@ test "bcrypt crypt format" { ); var long_buf: [hash_length]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -834,7 +850,11 @@ test "bcrypt crypt format" { hash_options.params.silently_truncate_password = true; verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); + long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try strVerify(long_s, "password" ** 101, verify_options); try strVerify( @@ -845,15 +865,20 @@ test "bcrypt crypt format" { } test "bcrypt phc format" { - var hash_options = HashOptions{ + const io = testing.io; + var hash_options: HashOptions = .{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .phc, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; const prefix = "$bcrypt$"; var buf: [hash_length * 2]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password", hash_options, &buf, &salt); + }; try testing.expect(mem.startsWith(u8, s, prefix)); try strVerify(s, "password", verify_options); @@ -863,7 +888,11 @@ test "bcrypt phc format" { ); var long_buf: [hash_length * 2]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try testing.expect(mem.startsWith(u8, long_s, prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -874,7 +903,11 @@ test "bcrypt phc format" { hash_options.params.silently_truncate_password = true; verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); + long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try strVerify(long_s, "password" ** 101, verify_options); try strVerify( diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig index e649cc8c7f..ff583896b9 100644 --- a/lib/std/crypto/ecdsa.zig +++ b/lib/std/crypto/ecdsa.zig @@ -323,10 +323,10 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -417,12 +417,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { test "Basic operations over EcdsaP384Sha384" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaP384Sha384; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -433,12 +434,13 @@ test "Basic operations over EcdsaP384Sha384" { test "Basic operations over Secp256k1" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaSecp256k1Sha256oSha256; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -449,12 +451,13 @@ test "Basic operations over Secp256k1" { test "Basic operations over EcdsaP384Sha256" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256); - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -502,8 +505,10 @@ test "Verifying a existing signature with EcdsaP384Sha256" { test "Prehashed message operations" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; + const Scheme = EcdsaP256Sha256; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test message for prehashed signing"; const Hash = crypto.hash.sha2.Sha256; @@ -518,7 +523,7 @@ test "Prehashed message operations" { try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key)); var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig_with_noise = try kp.signPrehashed(msg_hash, noise); try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key); @@ -1628,8 +1633,9 @@ fn tvTry(comptime Scheme: type, vector: TestVector) !void { test "Sec1 encoding/decoding" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaP384Sha384; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const pk = kp.public_key; const pk_compressed_sec1 = pk.toCompressedSec1(); const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1); diff --git a/lib/std/crypto/hybrid_kem.zig b/lib/std/crypto/hybrid_kem.zig index ee38a8022d..1ce034882a 100644 --- a/lib/std/crypto/hybrid_kem.zig +++ b/lib/std/crypto/hybrid_kem.zig @@ -174,43 +174,56 @@ pub fn HybridKem(comptime params: Params) type { return .{ .bytes = buf.* }; } - /// Generates a shared secret and encapsulates it for the public key. - /// If `seed` is `null`, uses random bytes from `std.crypto.random`. - /// If `seed` is set, encapsulation is deterministic (for testing only). - pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret { - const pq_nek = params.PqKem.PublicKey.encoded_length; - const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]); - const ek_t = self.bytes[pq_nek..][0..params.Group.element_length]; - + /// Generates a shared secret, encapsulated for the public key, + /// using random bytes. + /// + /// This is recommended over `encapsDeterministic`. + pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret { var seed_pq: [32]u8 = undefined; + io.random(&seed_pq); + var seed_t: [32]u8 = undefined; + io.random(&seed_t); + var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t); + return encapsInner(pk, &seed_pq, &seed_t_expanded); + } + + /// Generates a shared secret, encapsulated for the public key, + /// using the provided seed. + /// + /// Calling `encaps` instead is recommended. + pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret { + if (seed.len < 32) return error.InsufficientRandomness; + var seed_pq: [32]u8 = seed[0..32].*; var seed_t_expanded: [params.Group.seed_length]u8 = undefined; - if (seed) |r| { - if (r.len < 32) return error.InsufficientRandomness; - seed_pq = r[0..32].*; - - const t_randomness = r[32..]; + const t_randomness = seed[32..]; + if (t_randomness.len < params.Group.seed_length) { + // Provided randomness is shorter than seed_length, use it directly + // (test vectors provide just enough for randomScalar) + @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness); + // Pad the rest with zeros if needed (shouldn't be used by randomScalar) if (t_randomness.len < params.Group.seed_length) { - // Provided randomness is shorter than seed_length, use it directly - // (test vectors provide just enough for randomScalar) - @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness); - // Pad the rest with zeros if needed (shouldn't be used by randomScalar) - if (t_randomness.len < params.Group.seed_length) { - @memset(seed_t_expanded[t_randomness.len..], 0); - } - } else { - // Full randomness provided - @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]); + @memset(seed_t_expanded[t_randomness.len..], 0); } } else { - crypto.random.bytes(&seed_pq); - var seed_t: [32]u8 = undefined; - crypto.random.bytes(&seed_t); - seed_t_expanded = try expandRandomnessSeed(seed_t); + // Full randomness provided + @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]); } - const pq_encap = ek_pq.encaps(seed_pq); - const sk_e = try params.Group.randomScalar(&seed_t_expanded); + return encapsInner(pk, &seed_pq, &seed_t_expanded); + } + + fn encapsInner( + pk: PublicKey, + seed_pq: *[32]u8, + seed_t_expanded: *[params.Group.seed_length]u8, + ) !EncapsulatedSecret { + const pq_nek = params.PqKem.PublicKey.encoded_length; + const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]); + const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length]; + + const pq_encap = ek_pq.encapsDeterministic(seed_pq); + const sk_e = try params.Group.randomScalar(seed_t_expanded); const ct_t_point = try params.Group.mulBase(sk_e); const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point; @@ -280,9 +293,9 @@ pub fn HybridKem(comptime params: Params) type { } /// Generates a new random key pair. - pub fn generate() !KeyPair { + pub fn generate(io: std.Io) !KeyPair { var seed: [params.Nseed]u8 = undefined; - crypto.random.bytes(&seed); + io.random(&seed); return generateDeterministic(seed); } }; @@ -386,7 +399,7 @@ test "MLKEM768-X25519 basic round trip" { var enc_seed: [64]u8 = undefined; @memset(&enc_seed, 0x43); - const encap_result = try kp.public_key.encaps(&enc_seed); + const encap_result = try kp.public_key.encapsDeterministic(&enc_seed); const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext); try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap); @@ -408,7 +421,7 @@ test "MLKEM768-X25519 test vector 0" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -432,7 +445,7 @@ test "MLKEM768-X25519 test vector 1" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -456,7 +469,7 @@ test "MLKEM768-X25519 test vector 2" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -480,7 +493,7 @@ test "MLKEM768-X25519 test vector 3" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -504,7 +517,7 @@ test "MLKEM768-X25519 test vector 4" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -528,7 +541,7 @@ test "MLKEM768-X25519 test vector 5" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -552,7 +565,7 @@ test "MLKEM768-X25519 test vector 6" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -576,7 +589,7 @@ test "MLKEM768-X25519 test vector 7" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -600,7 +613,7 @@ test "MLKEM768-X25519 test vector 8" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -624,7 +637,7 @@ test "MLKEM768-X25519 test vector 9" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -648,7 +661,7 @@ test "MLKEM768-P256 test vector 0" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -672,7 +685,7 @@ test "MLKEM768-P256 test vector 1" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -696,7 +709,7 @@ test "MLKEM768-P256 test vector 2" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -720,7 +733,7 @@ test "MLKEM768-P256 test vector 3" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -744,7 +757,7 @@ test "MLKEM768-P256 test vector 4" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -768,7 +781,7 @@ test "MLKEM768-P256 test vector 5" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -792,7 +805,7 @@ test "MLKEM768-P256 test vector 6" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -816,7 +829,7 @@ test "MLKEM768-P256 test vector 7" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -840,7 +853,7 @@ test "MLKEM768-P256 test vector 8" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -864,7 +877,7 @@ test "MLKEM768-P256 test vector 9" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -888,7 +901,7 @@ test "MLKEM1024-P384 test vector 0" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -912,7 +925,7 @@ test "MLKEM1024-P384 test vector 1" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -936,7 +949,7 @@ test "MLKEM1024-P384 test vector 2" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -960,7 +973,7 @@ test "MLKEM1024-P384 test vector 3" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -984,7 +997,7 @@ test "MLKEM1024-P384 test vector 4" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1008,7 +1021,7 @@ test "MLKEM1024-P384 test vector 5" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1032,7 +1045,7 @@ test "MLKEM1024-P384 test vector 6" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1056,7 +1069,7 @@ test "MLKEM1024-P384 test vector 7" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1080,7 +1093,7 @@ test "MLKEM1024-P384 test vector 8" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); diff --git a/lib/std/crypto/ml_dsa.zig b/lib/std/crypto/ml_dsa.zig index 9a699e5439..5132899c48 100644 --- a/lib/std/crypto/ml_dsa.zig +++ b/lib/std/crypto/ml_dsa.zig @@ -2019,12 +2019,9 @@ fn MLDSAImpl(comptime p: Params) type { secret_key: SecretKey, /// Generate a new random key pair. - /// This uses the system's cryptographically secure random number generator. - /// - /// `crypto.random.bytes` must be supported by the target. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var seed: [Self.seed_length]u8 = undefined; - crypto.random.bytes(&seed); + io.random(&seed); return generateDeterministic(seed) catch unreachable; } @@ -3198,8 +3195,9 @@ test "ML-DSA-87 KAT test vector 0" { } test "KeyPair API - generate and sign" { + const io = std.testing.io; // Test the new KeyPair API with random generation - const kp = MLDSA44.KeyPair.generate(); + const kp = MLDSA44.KeyPair.generate(io); const msg = "Test message for KeyPair API"; // Sign with deterministic mode (no noise) @@ -3222,8 +3220,9 @@ test "KeyPair API - generateDeterministic" { } test "KeyPair API - fromSecretKey" { + const io = std.testing.io; // Generate a key pair - const kp1 = MLDSA44.KeyPair.generate(); + const kp1 = MLDSA44.KeyPair.generate(io); // Derive public key from secret key const kp2 = try MLDSA44.KeyPair.fromSecretKey(kp1.secret_key); @@ -3235,8 +3234,9 @@ test "KeyPair API - fromSecretKey" { } test "Signature verification with noise" { + const io = std.testing.io; // Test signing with randomness (hedged signatures) - const kp = MLDSA65.KeyPair.generate(); + const kp = MLDSA65.KeyPair.generate(io); const msg = "Message to be signed with randomness"; // Create some noise @@ -3250,8 +3250,9 @@ test "Signature verification with noise" { } test "Signature verification failure" { + const io = std.testing.io; // Test that invalid signatures are rejected - const kp = MLDSA44.KeyPair.generate(); + const kp = MLDSA44.KeyPair.generate(io); const msg = "Original message"; const sig = try kp.sign(msg, null); diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index 62e21f60a9..468be166d0 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -244,32 +244,41 @@ fn Kyber(comptime p: Params) type { /// Size of a serialized representation of the key, in bytes. pub const encoded_length = InnerPk.encoded_length; - /// Generates a shared secret, and encapsulates it for the public key. - /// If `seed` is `null`, a random seed is used. This is recommended. - /// If `seed` is set, encapsulation is deterministic. - pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret { + /// Generates a shared secret, encapsulated for the public key, + /// using random bytes. + /// + /// This is recommended over `encapsDeterministic`. + pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret { var m: [inner_plaintext_length]u8 = undefined; + io.random(&m); + return encapsInner(pk, &m); + } - if (seed_) |seed| { - if (p.ml_kem) { - @memcpy(&m, &seed); - } else { - // m = H(seed) - sha3.Sha3_256.hash(&seed, &m, .{}); - } + /// Generates a shared secret, encapsulated for the public key, + /// using the provided seed. + /// + /// Calling `encaps` instead is recommended. + pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret { + var m: [inner_plaintext_length]u8 = undefined; + if (p.ml_kem) { + @memcpy(&m, seed); } else { - crypto.random.bytes(&m); + // m = H(seed) + sha3.Sha3_256.hash(seed, &m, .{}); } + return encapsInner(pk, &m); + } + fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret { // (K', r) = G(m ‖ H(pk)) var kr: [inner_plaintext_length + h_length]u8 = undefined; var g = sha3.Sha3_512.init(.{}); - g.update(&m); + g.update(m); g.update(&pk.hpk); g.final(&kr); // c = innerEncrypt(pk, m, r) - const ct = pk.pk.encrypt(&m, kr[32..64]); + const ct = pk.pk.encrypt(m, kr[32..64]); if (p.ml_kem) { return EncapsulatedSecret{ @@ -398,10 +407,10 @@ fn Kyber(comptime p: Params) type { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -1634,15 +1643,15 @@ test "Test happy flow" { } inline for (modes) |mode| { for (0..10) |i| { - seed[0] = @as(u8, @intCast(i)); + seed[0] = @intCast(i); const kp = try mode.KeyPair.generateDeterministic(seed); const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes()); try testing.expectEqual(sk, kp.secret_key); const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes()); try testing.expectEqual(pk, kp.public_key); for (0..10) |j| { - seed[1] = @as(u8, @intCast(j)); - const e = pk.encaps(seed[0..32].*); + seed[1] = @intCast(j); + const e = pk.encapsDeterministic(seed[0..32]); try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext)); } } @@ -1695,7 +1704,7 @@ fn testNistKat(mode: type, hash: []const u8) !void { g2.fill(kseed[32..64]); g2.fill(&eseed); const kp = try mode.KeyPair.generateDeterministic(kseed); - const e = kp.public_key.encaps(eseed); + const e = kp.public_key.encapsDeterministic(&eseed); const ss2 = try kp.secret_key.decaps(&e.ciphertext); try testing.expectEqual(ss2, e.shared_secret); try fw.writer.print("pk = {X}\n", .{&kp.public_key.toBytes()}); diff --git a/lib/std/crypto/pcurves/p256.zig b/lib/std/crypto/pcurves/p256.zig index aa9226ae0d..4746061e02 100644 --- a/lib/std/crypto/pcurves/p256.zig +++ b/lib/std/crypto/pcurves/p256.zig @@ -122,8 +122,8 @@ pub const P256 = struct { } /// Return a random point. - pub fn random() P256 { - const n = scalar.random(.little); + pub fn random(io: std.Io) P256 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/p256/scalar.zig b/lib/std/crypto/pcurves/p256/scalar.zig index bd071f4b8b..cd31d5e10f 100644 --- a/lib/std/crypto/pcurves/p256/scalar.zig +++ b/lib/std/crypto/pcurves/p256/scalar.zig @@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -170,10 +170,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [48]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes48(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/p384.zig b/lib/std/crypto/pcurves/p384.zig index 61632f61ed..0dbfdc67f1 100644 --- a/lib/std/crypto/pcurves/p384.zig +++ b/lib/std/crypto/pcurves/p384.zig @@ -122,8 +122,8 @@ pub const P384 = struct { } /// Return a random point. - pub fn random() P384 { - const n = scalar.random(.little); + pub fn random(io: std.Io) P384 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/p384/scalar.zig b/lib/std/crypto/pcurves/p384/scalar.zig index 6ea25f214a..19e94c4b42 100644 --- a/lib/std/crypto/pcurves/p384/scalar.zig +++ b/lib/std/crypto/pcurves/p384/scalar.zig @@ -63,8 +63,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -159,10 +159,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [64]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes64(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/secp256k1.zig b/lib/std/crypto/pcurves/secp256k1.zig index c891f414f5..1c1caae19a 100644 --- a/lib/std/crypto/pcurves/secp256k1.zig +++ b/lib/std/crypto/pcurves/secp256k1.zig @@ -175,8 +175,8 @@ pub const Secp256k1 = struct { } /// Return a random point. - pub fn random() Secp256k1 { - const n = scalar.random(.little); + pub fn random(io: std.Io) Secp256k1 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/secp256k1/scalar.zig b/lib/std/crypto/pcurves/secp256k1/scalar.zig index 132325026f..d79d2a2305 100644 --- a/lib/std/crypto/pcurves/secp256k1/scalar.zig +++ b/lib/std/crypto/pcurves/secp256k1/scalar.zig @@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -170,10 +170,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [48]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes48(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/tests/p256.zig b/lib/std/crypto/pcurves/tests/p256.zig index 7da68044f8..3ca12fa241 100644 --- a/lib/std/crypto/pcurves/tests/p256.zig +++ b/lib/std/crypto/pcurves/tests/p256.zig @@ -5,8 +5,9 @@ const testing = std.testing; const P256 = @import("../p256.zig").P256; test "p256 ECDH key exchange" { - const dha = P256.scalar.random(.little); - const dhb = P256.scalar.random(.little); + const io = testing.io; + const dha = P256.scalar.random(io, .little); + const dhb = P256.scalar.random(io, .little); const dhA = try P256.basePoint.mul(dha, .little); const dhB = try P256.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -66,28 +67,32 @@ test "p256 test vectors - doubling" { } test "p256 compressed sec1 encoding/decoding" { - const p = P256.random(); + const io = testing.io; + const p = P256.random(io); const s = p.toCompressedSec1(); const q = try P256.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p256 uncompressed sec1 encoding/decoding" { - const p = P256.random(); + const io = testing.io; + const p = P256.random(io); const s = p.toUncompressedSec1(); const q = try P256.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p256 public key is the neutral element" { + const io = testing.io; const n = P256.scalar.Scalar.zero.toBytes(.little); - const p = P256.random(); + const p = P256.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "p256 public key is the neutral element (public verification)" { + const io = testing.io; const n = P256.scalar.Scalar.zero.toBytes(.little); - const p = P256.random(); + const p = P256.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/pcurves/tests/p384.zig b/lib/std/crypto/pcurves/tests/p384.zig index e51a683136..316e8679ad 100644 --- a/lib/std/crypto/pcurves/tests/p384.zig +++ b/lib/std/crypto/pcurves/tests/p384.zig @@ -5,8 +5,9 @@ const testing = std.testing; const P384 = @import("../p384.zig").P384; test "p384 ECDH key exchange" { - const dha = P384.scalar.random(.little); - const dhb = P384.scalar.random(.little); + const io = testing.io; + const dha = P384.scalar.random(io, .little); + const dhb = P384.scalar.random(io, .little); const dhA = try P384.basePoint.mul(dha, .little); const dhB = try P384.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -67,7 +68,8 @@ test "p384 test vectors - doubling" { } test "p384 compressed sec1 encoding/decoding" { - const p = P384.random(); + const io = testing.io; + const p = P384.random(io); const s0 = p.toUncompressedSec1(); const s = p.toCompressedSec1(); try testing.expectEqualSlices(u8, s0[1..49], s[1..49]); @@ -76,21 +78,24 @@ test "p384 compressed sec1 encoding/decoding" { } test "p384 uncompressed sec1 encoding/decoding" { - const p = P384.random(); + const io = testing.io; + const p = P384.random(io); const s = p.toUncompressedSec1(); const q = try P384.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p384 public key is the neutral element" { + const io = testing.io; const n = P384.scalar.Scalar.zero.toBytes(.little); - const p = P384.random(); + const p = P384.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "p384 public key is the neutral element (public verification)" { + const io = testing.io; const n = P384.scalar.Scalar.zero.toBytes(.little); - const p = P384.random(); + const p = P384.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/pcurves/tests/secp256k1.zig b/lib/std/crypto/pcurves/tests/secp256k1.zig index 0fb2a13cf8..ff400c0322 100644 --- a/lib/std/crypto/pcurves/tests/secp256k1.zig +++ b/lib/std/crypto/pcurves/tests/secp256k1.zig @@ -5,8 +5,9 @@ const testing = std.testing; const Secp256k1 = @import("../secp256k1.zig").Secp256k1; test "secp256k1 ECDH key exchange" { - const dha = Secp256k1.scalar.random(.little); - const dhb = Secp256k1.scalar.random(.little); + const io = testing.io; + const dha = Secp256k1.scalar.random(io, .little); + const dhb = Secp256k1.scalar.random(io, .little); const dhA = try Secp256k1.basePoint.mul(dha, .little); const dhB = try Secp256k1.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -15,8 +16,9 @@ test "secp256k1 ECDH key exchange" { } test "secp256k1 ECDH key exchange including public multiplication" { - const dha = Secp256k1.scalar.random(.little); - const dhb = Secp256k1.scalar.random(.little); + const io = testing.io; + const dha = Secp256k1.scalar.random(io, .little); + const dhb = Secp256k1.scalar.random(io, .little); const dhA = try Secp256k1.basePoint.mul(dha, .little); const dhB = try Secp256k1.basePoint.mulPublic(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -77,28 +79,32 @@ test "secp256k1 test vectors - doubling" { } test "secp256k1 compressed sec1 encoding/decoding" { - const p = Secp256k1.random(); + const io = testing.io; + const p = Secp256k1.random(io); const s = p.toCompressedSec1(); const q = try Secp256k1.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "secp256k1 uncompressed sec1 encoding/decoding" { - const p = Secp256k1.random(); + const io = testing.io; + const p = Secp256k1.random(io); const s = p.toUncompressedSec1(); const q = try Secp256k1.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "secp256k1 public key is the neutral element" { + const io = testing.io; const n = Secp256k1.scalar.Scalar.zero.toBytes(.little); - const p = Secp256k1.random(); + const p = Secp256k1.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "secp256k1 public key is the neutral element (public verification)" { + const io = testing.io; const n = Secp256k1.scalar.Scalar.zero.toBytes(.little); - const p = Secp256k1.random(); + const p = Secp256k1.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index eb434ed15a..d47498704d 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -533,9 +533,9 @@ pub const SealedBox = struct { /// Encrypt a message `m` for a recipient whose public key is `public_key`. /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. - pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { + pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { debug.assert(c.len == m.len + seal_length); - var ekp = KeyPair.generate(); + var ekp = KeyPair.generate(io); const nonce = createNonce(ekp.public_key, public_key); c[0..public_length].* = ekp.public_key; try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); @@ -573,29 +573,31 @@ test "(x)salsa20" { } test "xsalsa20poly1305" { + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var c: [msg.len]u8 = undefined; var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&key); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&key); + io.random(&nonce); XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); } test "xsalsa20poly1305 secretbox" { + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&key); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&key); + io.random(&nonce); SecretBox.seal(boxed[0..], msg[0..], nonce, key); try SecretBox.open(msg2[0..], boxed[0..], nonce, key); @@ -604,15 +606,16 @@ test "xsalsa20poly1305 secretbox" { test "xsalsa20poly1305 box" { if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299 + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&nonce); - const kp1 = Box.KeyPair.generate(); - const kp2 = Box.KeyPair.generate(); + const kp1 = Box.KeyPair.generate(io); + const kp2 = Box.KeyPair.generate(io); try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key); try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key); } @@ -620,13 +623,14 @@ test "xsalsa20poly1305 box" { test "xsalsa20poly1305 sealedbox" { if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299 + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; - crypto.random.bytes(&msg); + io.random(&msg); - const kp = Box.KeyPair.generate(); - try SealedBox.seal(boxed[0..], msg[0..], kp.public_key); + const kp = Box.KeyPair.generate(io); + try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key); try SealedBox.open(msg2[0..], boxed[0..], kp); } diff --git a/lib/std/crypto/scrypt.zig b/lib/std/crypto/scrypt.zig index 00440642e3..43bf5ff054 100644 --- a/lib/std/crypto/scrypt.zig +++ b/lib/std/crypto/scrypt.zig @@ -20,7 +20,7 @@ const Error = pwhash.Error; const max_size = math.maxInt(usize); const max_int = max_size >> 1; -const default_salt_len = 32; +pub const default_salt_len = 32; const default_hash_len = 32; const max_salt_len = 64; const max_hash_len = 64; @@ -417,10 +417,9 @@ const PhcFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: []const u8, ) HasherError![]const u8 { - var salt: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt); - var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params); @@ -466,9 +465,9 @@ const CryptFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt_bin: []const u8, ) HasherError![]const u8 { - var salt_bin: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt_bin); const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin); var hash: [default_hash_len]u8 = undefined; diff --git a/lib/std/crypto/timing_safe.zig b/lib/std/crypto/timing_safe.zig index 85767eb006..7d9b53c0aa 100644 --- a/lib/std/crypto/timing_safe.zig +++ b/lib/std/crypto/timing_safe.zig @@ -180,24 +180,24 @@ pub fn declassify(ptr: anytype) void { } test eql { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var a: [100]u8 = undefined; var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); + io.random(&a); + io.random(&b); try expect(!eql([100]u8, a, b)); a = b; try expect(eql([100]u8, a, b)); } test "eql (vectors)" { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var a: [100]u8 = undefined; var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); + io.random(&a); + io.random(&b); const v1: @Vector(100, u8) = a; const v2: @Vector(100, u8) = b; try expect(!eql(@Vector(100, u8), v1, v2)); @@ -220,9 +220,10 @@ test compare { } test "add and sub" { + const io = std.testing.io; + const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; - const random = std.crypto.random; const len = 32; var a: [len]u8 = undefined; var b: [len]u8 = undefined; @@ -230,8 +231,8 @@ test "add and sub" { const zero = [_]u8{0} ** len; var iterations: usize = 100; while (iterations != 0) : (iterations -= 1) { - random.bytes(&a); - random.bytes(&b); + io.random(&a); + io.random(&b); const endian = if (iterations % 2 == 0) Endian.big else Endian.little; _ = sub(u8, &a, &b, &c, endian); // a-b _ = add(u8, &c, &b, &c, endian); // (a-b)+b @@ -243,11 +244,11 @@ test "add and sub" { } test classify { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var secret: [32]u8 = undefined; - random.bytes(&secret); + io.random(&secret); // Input of the hash function is marked as secret classify(&secret); diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index d3a06819bf..c809a2e998 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -109,7 +109,7 @@ pub const Options = struct { read_buffer: []u8, /// Cryptographically secure random bytes. The pointer is not captured; data is only /// read during `init`. - entropy: *const [176]u8, + entropy: *const [240]u8, /// Current time according to the wall clock / calendar, in seconds. realtime_now_seconds: i64, @@ -200,7 +200,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client var server_hello_rand: [32]u8 = undefined; const legacy_session_id = options.entropy[32..64].*; - var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) { + var key_share = KeyShare.init(options.entropy[64..240]) catch |err| switch (err) { // Only possible to happen if the seed is all zeroes. error.IdentityElement => return error.InsufficientEntropy, }; @@ -1330,12 +1330,12 @@ const KeyShare = struct { crypto.dh.X25519.shared_length, ); - fn init(seed: [112]u8) error{IdentityElement}!KeyShare { + fn init(seed: *const [176]u8) error{IdentityElement}!KeyShare { return .{ - .ml_kem768_kp = .generate(), - .secp256r1_kp = try .generateDeterministic(seed[0..32].*), - .secp384r1_kp = try .generateDeterministic(seed[32..80].*), - .x25519_kp = try .generateDeterministic(seed[80..112].*), + .ml_kem768_kp = try .generateDeterministic(seed[0..64].*), + .secp256r1_kp = try .generateDeterministic(seed[64..96].*), + .secp384r1_kp = try .generateDeterministic(seed[96..144].*), + .x25519_kp = try .generateDeterministic(seed[144..176].*), .sk_buf = undefined, .sk_len = 0, }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index c53ce18996..4695dd9a8e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1761,7 +1761,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const io = testing.io; var random_bytes: [12]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 3979a18029..e571d1a4b0 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -321,8 +321,8 @@ pub const Connection = struct { assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len); @memcpy(host_buffer, remote_host.bytes); const tls: *Tls = @ptrCast(base); - var random_buffer: [176]u8 = undefined; - std.crypto.random.bytes(&random_buffer); + var random_buffer: [240]u8 = undefined; + io.random(&random_buffer); tls.* = .{ .connection = .{ .client = client, diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index cbec3cc00a..e8598eb111 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -531,13 +531,11 @@ pub fn prepareArea(area: []u8) usize { }; } -/// The main motivation for the size chosen here is that this is how much ends up being requested for -/// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up -/// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned -/// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I -/// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is -/// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead. -var main_thread_area_buffer: [0x2100]u8 align(page_size_min) = undefined; +/// The main motivation for the size chosen here is to be larger than total +/// amount of thread-local variables for most programs. Putting this allocation +/// in the ELF like this is equivalent to moving the `mmap` call below into the +/// kernel, avoiding syscall overhead. +var main_thread_area_buffer: [0x1000]u8 align(page_size_min) = undefined; /// Computes the layout of the static TLS area, allocates the area, initializes all of its fields, /// and assigns the architecture-specific value to the TP register. diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 905b3438e4..47341ea525 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -33,16 +33,6 @@ test "check WASI CWD" { } } -test "getrandom" { - var buf_a: [50]u8 = undefined; - var buf_b: [50]u8 = undefined; - try posix.getrandom(&buf_a); - try posix.getrandom(&buf_b); - // If this test fails the chance is significantly higher that there is a bug than - // that two sets of 50 bytes were equal. - try expect(!mem.eql(u8, &buf_a, &buf_b)); -} - test "getuid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; _ = posix.getuid(); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 9bb2622c3d..1138869663 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -631,7 +631,7 @@ pub const TmpDir = struct { pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { comptime assert(builtin.is_test); var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); diff --git a/src/Compilation.zig b/src/Compilation.zig index f308a60bd3..bfd0193a26 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2942,7 +2942,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE .none => |none| { assert(none.tmp_artifact_directory == null); none.tmp_artifact_directory = d: { - tmp_dir_rand_int = std.crypto.random.int(u64); + io.random(@ptrCast(&tmp_dir_rand_int)); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { @@ -3023,7 +3023,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // Compile the artifacts to a temporary directory. whole.tmp_artifact_directory = d: { - tmp_dir_rand_int = std.crypto.random.int(u64); + io.random(@ptrCast(&tmp_dir_rand_int)); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { @@ -5759,7 +5759,11 @@ pub fn translateC( const gpa = comp.gpa; const io = comp.io; - const tmp_basename = std.fmt.hex(std.crypto.random.int(u64)); + const tmp_basename = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r std.fmt.hex(x); + }; const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; const cache_dir = comp.dirs.local_cache.handle; var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{}); @@ -6889,8 +6893,13 @@ fn spawnZigRc( } pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { + const io = comp.io; + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const s = fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); if (comp.dirs.local_cache.path) |p| { return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); } else { diff --git a/src/Package.zig b/src/Package.zig index 9f05f33a46..fda4c1c178 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -14,9 +14,9 @@ pub const Fingerprint = packed struct(u64) { id: u32, checksum: u32, - pub fn generate(name: []const u8) Fingerprint { + pub fn generate(rng: std.Random, name: []const u8) Fingerprint { return .{ - .id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff), + .id = rng.intRangeLessThan(u32, 1, 0xffffffff), .checksum = std.hash.Crc32.hash(name), }; } diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d595465db5..5a7948f8e8 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -494,7 +494,11 @@ fn runResource( const eb = &f.error_bundle; const s = fs.path.sep_str; const cache_root = f.job_queue.global_cache; - const rand_int = std.crypto.random.int(u64); + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int); const package_sub_path = blk: { @@ -690,7 +694,9 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { return error.FetchFailed; } - f.manifest = try Manifest.parse(arena, ast.*, .{ + const rng: std.Random.IoSource = .{ .io = io }; + + f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{ .allow_missing_paths_field = f.allow_missing_paths_field, .allow_missing_fingerprint = f.allow_missing_fingerprint, .allow_name_string = f.allow_name_string, @@ -1305,7 +1311,11 @@ fn unzip( zip_path[prefix.len + random_len ..].* = suffix.*; var zip_file = while (true) { - const random_integer = std.crypto.random.int(u64); + const random_integer = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer); break cache_root.handle.createFile(io, &zip_path, .{ diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 90243a23e0..43cffb5ace 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -57,7 +57,7 @@ pub const ParseOptions = struct { pub const Error = Allocator.Error; -pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { +pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest { const main_node_index = ast.nodeData(.root).node; var arena_instance = std.heap.ArenaAllocator.init(gpa); @@ -87,7 +87,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { defer p.dependencies.deinit(gpa); defer p.paths.deinit(gpa); - p.parseRoot(main_node_index) catch |err| switch (err) { + p.parseRoot(main_node_index, rng) catch |err| switch (err) { error.ParseFailure => assert(p.errors.items.len > 0), else => |e| return e, }; @@ -157,7 +157,7 @@ const Parse = struct { const InnerError = error{ ParseFailure, OutOfMemory }; - fn parseRoot(p: *Parse, node: Ast.Node.Index) !void { + fn parseRoot(p: *Parse, node: Ast.Node.Index, rng: std.Random) !void { const ast = p.ast; const main_token = ast.nodeMainToken(node); @@ -217,13 +217,13 @@ const Parse = struct { if (fingerprint) |n| { if (!n.validate(p.name)) { return fail(p, main_token, "invalid fingerprint: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{ - n.int(), Package.Fingerprint.generate(p.name).int(), + n.int(), Package.Fingerprint.generate(rng, p.name).int(), }); } p.id = n.id; } else if (!p.allow_missing_fingerprint) { try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{ - Package.Fingerprint.generate(p.name).int(), + Package.Fingerprint.generate(rng, p.name).int(), }); } else { p.id = 0; diff --git a/src/link.zig b/src/link.zig index 13306b90a4..0e89723296 100644 --- a/src/link.zig +++ b/src/link.zig @@ -616,8 +616,13 @@ pub const File = struct { // it will return ETXTBSY. So instead, we copy the file, atomically rename it // over top of the exe path, and then proceed normally. This changes the inode, // avoiding the error. + const random_integer = r: { + var x: u32 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{ - emit.sub_path, std.crypto.random.int(u32), + emit.sub_path, random_integer, }); defer gpa.free(tmp_sub_path); try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{}); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 0110e21770..dd84da15d4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1636,7 +1636,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi const err = switch (first_err) { error.NameTooLong => err: { const s = fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); diff --git a/src/main.zig b/src/main.zig index 1f373b2340..97a4e53c2f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3395,7 +3395,7 @@ fn buildOutputType( // "-" is stdin. Dump it to a real file. const sep = fs.path.sep_str; const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{ - std.crypto.random.int(u64), ext.canonicalName(target), + randInt(io, u64), ext.canonicalName(target), }); try dirs.local_cache.handle.createDirPath(io, "tmp"); @@ -4433,7 +4433,7 @@ fn runOrTest( try argv.append(exe_path); if (arg_mode == .zig_test) { try argv.append( - try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}), + try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}), ); } } else { @@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! const cwd_basename = fs.path.basename(cwd_path); const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename); - const fingerprint: Package.Fingerprint = .generate(sanitized_root_name); + const rng: std.Random.IoSource = .{ .io = io }; + const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name); switch (template) { .example => { @@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, try child_argv.appendSlice(&.{ "--seed", - try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}), + try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}), }); const argv_index_seed = child_argv.items.len - 1; @@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, // the strategy is to choose a temporary file name ahead of time, and then // read this file in the parent to obtain the results, in the case the child // exits with code 3. - const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64)); + const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64)); try child_argv.append("-Z" ++ results_tmp_file_nonce); var color: Color = .auto; @@ -7223,7 +7224,7 @@ fn createDependenciesModule( ) !*Package.Module { // Atomically create the file in a directory named after the hash of its contents. const basename = "dependencies.zig"; - const rand_int = std.crypto.random.int(u64); + const rand_int = randInt(io, u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); { var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}); @@ -7339,6 +7340,8 @@ fn loadManifest( io: Io, options: LoadManifestOptions, ) !struct { Package.Manifest, Ast } { + const rng: std.Random.IoSource = .{ .io = io }; + const manifest_bytes = while (true) { break options.dir.readFileAllocOptions( io, @@ -7360,15 +7363,13 @@ fn loadManifest( , .{ options.root_name, build_options.version, - Package.Fingerprint.generate(options.root_name).int(), + Package.Fingerprint.generate(rng.interface(), options.root_name).int(), }) catch |e| { - fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) }); + fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e }); }; continue; }, - else => |e| fatal("unable to load {s}: {s}", .{ - Package.Manifest.basename, @errorName(e), - }), + else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }), }; }; var ast = try Ast.parse(gpa, manifest_bytes, .zon); @@ -7379,7 +7380,7 @@ fn loadManifest( process.exit(2); } - var manifest = try Package.Manifest.parse(gpa, ast, .{}); + var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{}); errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { @@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void { threaded_impl_ptr.setAsyncLimit(limit); threaded_impl_ptr.concurrent_limit = limit; } + +fn randInt(io: Io, comptime T: type) T { + var x: T = undefined; + io.random(@ptrCast(&x)); + return x; +} diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index e7b30867ec..9af4787488 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -10,7 +10,8 @@ pub fn main(init: std.process.Init) !void { try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); - const answer = std.crypto.random.intRangeLessThan(u8, 0, 100) + 1; + var rng: std.Random.IoSource = .{ .io = init.io }; + const answer = rng.interface().intRangeLessThan(u8, 0, 100) + 1; while (true) { try out.writeAll("\nGuess a number between 1 and 100: "); diff --git a/tools/doctest.zig b/tools/doctest.zig index 7f4b8fbda8..5037bac2c2 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -78,9 +78,10 @@ pub fn main(init: std.process.Init) !void { const code = try parseManifest(arena, source_bytes); const source = stripManifest(source_bytes); - const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ - cache_root, std.crypto.random.int(u64), - }); + var random_integer: u64 = undefined; + io.random(@ptrCast(&random_integer)); + + const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer }); Dir.cwd().createDirPath(io, tmp_dir_path) catch |err| fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 2dfb23447a..daf93be73f 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -100,7 +100,7 @@ pub fn main(init: std.process.Init) !void { const prog_node = std.Progress.start(io, .{}); defer prog_node.end(); - const rand_int = std.crypto.random.int(u64); + const rand_int = rand64(io); const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{}); defer { @@ -452,20 +452,19 @@ const Eval = struct { std.debug.assert(eval.target.backend == .sema); return; }; + const io = eval.io; const binary_path = switch (eval.target.backend) { .sema => unreachable, .selfhosted, .llvm => emitted_path, .cbe => bin: { - const rand_int = std.crypto.random.int(u64); + const rand_int = rand64(io); const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); try eval.buildCOutput(emitted_path, out_bin_name, prog_node); break :bin out_bin_name; }, }; - const io = eval.io; - var argv_buf: [2][]const u8 = undefined; const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( io, @@ -957,3 +956,9 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { .msg = message, }; } + +fn rand64(io: Io) u64 { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + return x; +}