Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
export {
type ByteSource,
defaultByteSource,
type RandomBytesOptions,
randomBytes,
} from "./random-bytes/random-bytes.js";

View File

@@ -0,0 +1,6 @@
export {
type ByteSource,
defaultByteSource,
type RandomBytesOptions,
randomBytes,
} from "./random-bytes";

View File

@@ -0,0 +1,57 @@
# randomBytes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Produce cryptographically-secure random bytes. Every `@suaveplan/crypto-*` package routes entropy through this single helper so there is exactly one callsite of `globalThis.crypto.getRandomValues` to audit, and callers that need deterministic entropy in tests (or a hardware-RNG adapter in production) can swap it with dependency injection rather than monkey-patching `globalThis`.
## Features
- Wraps `globalThis.crypto.getRandomValues` (Web Crypto) — works in Node 20+, Bun, browsers, Deno, Cloudflare Workers.
- Dependency-injectable `ByteSource` for tests and hardware RNG adapters.
- Explicit `n` range check; throws stable `CRYPTO.UTILS.RNG.INVALID_LENGTH` on bad input.
- Returns a fresh `Uint8Array` every call.
## Usage: Basic Example
```ts
import { randomBytes } from "@suaveplan/crypto-utils/random";
const nonce = randomBytes(12);
```
## Usage: Advanced Example
```ts
import { randomBytes, type ByteSource } from "@suaveplan/crypto-utils/random";
// Deterministic source for tests — feeds a known sequence.
function fixedSequence(bytes: Uint8Array): ByteSource {
let pos = 0;
return (n) => {
const out = bytes.slice(pos, pos + n);
pos += n;
return out;
};
}
function generateKey(opts?: { byteSource?: ByteSource }): Uint8Array {
return randomBytes(32, opts);
}
const testKey = generateKey({
byteSource: fixedSequence(new Uint8Array(32).fill(0xaa)),
});
```
## API Reference
- `randomBytes(n: number, options?: { byteSource?: ByteSource }): Uint8Array` — returns `n` random bytes. Throws `CryptoUtilsError(CRYPTO.UTILS.RNG.INVALID_LENGTH)` if `n` is not a non-negative integer.
- `defaultByteSource: ByteSource` — the `getRandomValues`-backed source. Exported for adapters that want to delegate to it.
- `type ByteSource = (n: number) => Uint8Array` — the injection surface.
## Implementation Notes
`globalThis.crypto` is a WHATWG Web Crypto `Crypto` instance on every supported runtime. In Node we rely on the polyfill injected at test-setup time (`node:crypto` `webcrypto`). The test-only `byteSource` option is deliberately on the call, not a module-level setter, so there is never a case where a test leaks a deterministic source into production code. The length check rejects `NaN`, non-integers, and negatives — all would previously have been forwarded to `getRandomValues`, which throws a less-precise `RangeError`.

View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { CryptoUtilsError } from "../../errors/crypto-utils-error/crypto-utils-error.js";
import { defaultByteSource, randomBytes } from "./random-bytes.js";
describe("randomBytes", () => {
it("returns requested length", { timeout: 5000 }, () => {
for (const n of [0, 1, 16, 32, 4096]) {
expect(randomBytes(n).length).toBe(n);
}
});
it("produces different output on consecutive calls", {
timeout: 5000,
}, () => {
const a = randomBytes(32);
const b = randomBytes(32);
expect(a).not.toEqual(b);
});
it("accepts a deterministic byteSource", { timeout: 5000 }, () => {
let counter = 0;
const stub = (n: number): Uint8Array => {
const out = new Uint8Array(n);
for (let i = 0; i < n; i++) out[i] = counter++;
return out;
};
expect(randomBytes(4, { byteSource: stub })).toEqual(
new Uint8Array([0, 1, 2, 3])
);
expect(randomBytes(2, { byteSource: stub })).toEqual(
new Uint8Array([4, 5])
);
});
it("rejects non-integer and negative lengths", { timeout: 5000 }, () => {
expect(() => randomBytes(-1)).toThrow(CryptoUtilsError);
expect(() => randomBytes(1.5)).toThrow(CryptoUtilsError);
expect(() => randomBytes(Number.NaN)).toThrow(CryptoUtilsError);
});
it("defaultByteSource returns the right length", { timeout: 5000 }, () => {
expect(defaultByteSource(16).length).toBe(16);
});
});

View File

@@ -0,0 +1,47 @@
import { CRYPTO_UTILS_CODES } from "../../errors/codes/codes.js";
import { CryptoUtilsError } from "../../errors/crypto-utils-error/crypto-utils-error.js";
/**
* A function that produces `n` cryptographically-random bytes.
*/
export type ByteSource = (n: number) => Uint8Array;
/**
* The single callsite for `crypto.getRandomValues` in this package. Every
* consumer that needs entropy MUST call `randomBytes` or accept a
* `ByteSource` — never call `crypto.getRandomValues` directly.
*/
export const defaultByteSource: ByteSource = (n) => {
const out = new Uint8Array(n);
crypto.getRandomValues(out);
return out;
};
export interface RandomBytesOptions {
readonly byteSource?: ByteSource;
}
/**
* Return `n` cryptographically-random bytes.
*
* The default source is `globalThis.crypto.getRandomValues`. Pass
* `{ byteSource }` to inject a deterministic or alternative source
* (useful for tests and hardware RNG adapters).
*
* @throws CryptoUtilsError(CRYPTO.UTILS.RNG.INVALID_LENGTH) if `n` is not a
* non-negative integer.
*/
export function randomBytes(
n: number,
options: RandomBytesOptions = {}
): Uint8Array {
if (!Number.isInteger(n) || n < 0) {
throw new CryptoUtilsError(
CRYPTO_UTILS_CODES["RNG.INVALID_LENGTH"],
"error.crypto.utils.rng.invalid-length",
{ context: { requested: n } }
);
}
const source = options.byteSource ?? defaultByteSource;
return source(n);
}