Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion wasi-shims/src/random.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,25 @@ export interface RandomOptions {
insecureSeed?: readonly [bigint, bigint];
}

/**
* `crypto.getRandomValues` rejects requests over 65536 bytes
* (QuotaExceededError), while the WIT requires exactly `len` bytes back
* ("Return `len` cryptographically-secure random or pseudo-random bytes",
* random.wit) — there is no shorter-return latitude, and callers (Rust
* `getrandom`, the Go runtime) fill fixed-size buffers trusting the length.
* So: chunk the fill, never clamp it. Still synchronous, satisfying the
* WIT's "must not block ... including on requests for [large] numbers of
* bytes".
*/
const GET_RANDOM_VALUES_MAX = 65536;

function randomBytes(len: bigint): Uint8Array {
const out = new Uint8Array(Number(len));
crypto.getRandomValues(out);
for (let i = 0; i < out.length; i += GET_RANDOM_VALUES_MAX) {
crypto.getRandomValues(
out.subarray(i, Math.min(i + GET_RANDOM_VALUES_MAX, out.length)),
);
}
return out;
}

Expand Down
23 changes: 23 additions & 0 deletions wasi-shims/tests/random_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ Deno.test("random: get-random-bytes returns exactly `len` bytes", () => {
assertEq(r.getRandomBytes(0n).length, 0);
});

Deno.test("random: get-random-bytes spans the 64KiB getRandomValues quota", () => {
// Fail-on-pre-fix pin: a single getRandomValues call rejects >65536 bytes
// (QuotaExceededError), and the WIT allows no shorter return — the
// provider must chunk the fill and still hand back exactly `len` bytes.
const { imports } = random();
const r = imports["wasi:random/random@0.2"] as {
getRandomBytes(len: bigint): Uint8Array;
};
const len = 3 * 65536 + 17; // three full chunks + a ragged tail
const bytes = r.getRandomBytes(BigInt(len));
assertEq(bytes.length, len);
// Every chunk actually got filled: a 32-byte window of a CSPRNG output is
// all-zero with probability 2^-256 — treat that as impossible. Check the
// start of each chunk and the ragged tail.
for (const off of [0, 65536, 2 * 65536, 3 * 65536, len - 17]) {
const window = bytes.subarray(off, Math.min(off + 32, len));
assertTrue(
window.some((b) => b !== 0),
`window at ${off} is all-zero (chunk not filled)`,
);
}
});

Deno.test("random: get-random-u64 returns a bigint", () => {
const { imports } = random();
const r = imports["wasi:random/random@0.2"] as { getRandomU64(): bigint };
Expand Down
Loading