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,98 @@
# argon2id.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`argon2id.select.ts` is the build-time primitive selector for the Argon2id implementation in `@suaveplan/crypto-hash`. It re-exports one concrete backend — either `argon2id.noble.ts` (backed by `@noble/hashes`) or `argon2id.native.ts` — based on the `argon2id` entry in `crypto.config.ts`.
Argon2id is the Password Hashing Competition winner and the recommendation of RFC 9106 for password hashing and key derivation. Its hybrid data-dependent and data-independent memory access pattern resists both GPU-parallel and side-channel attacks, making it the preferred choice when deriving keys from passwords or other low-entropy secrets. The selector exposes `argon2id` and `Argon2idOptions` through a stable interface.
## Features
- Re-exports `argon2id(password, options)` and `Argon2idOptions`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `argon2id` to `'noble'` or `'native'`.
- `Argon2idOptions` carries `salt`, `timeCost`, `memoryCost`, `parallelism`, and `length`.
- RFC 9106 minimum parameter validation is enforced at the public façade, not here.
## Usage: Basic Example
```ts
import { argon2id } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
const password = new TextEncoder().encode("correct-horse-battery-staple");
const salt = crypto.getRandomValues(new Uint8Array(16));
const hash = argon2id(password, {
salt,
timeCost: 3,
memoryCost: 65536,
parallelism: 4,
length: 32,
});
console.log(hash.length); // 32
```
## Usage: Advanced Example
```ts
import { argon2id, type Argon2idOptions } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
// Interactive login profile (RFC 9106 minimum-t=1 adjusted for latency).
const INTERACTIVE_PARAMS: Argon2idOptions = {
salt: new Uint8Array(16), // placeholder; real callers supply random salt
timeCost: 2,
memoryCost: 19456, // ~19 MiB
parallelism: 1,
length: 32,
};
// Server-side profile (higher security, longer wait acceptable).
const SENSITIVE_PARAMS: Argon2idOptions = {
salt: new Uint8Array(16),
timeCost: 3,
memoryCost: 65536, // ~64 MiB
parallelism: 4,
length: 64,
};
function deriveKey(password: Uint8Array, opts: Argon2idOptions): Uint8Array {
return argon2id(password, opts);
}
```
## API Reference
Re-exports the following from the active implementation:
### `argon2id(password: Uint8Array, options: Argon2idOptions): Uint8Array`
Derives a key from a password using the Argon2id algorithm.
| Parameter | Type | Description |
|-----------|------|-------------|
| `password` | `Uint8Array` | Password bytes. |
| `options.salt` | `Uint8Array` | Random salt (≥16 bytes per RFC 9106). |
| `options.timeCost` | `number` | Number of passes over memory (≥1). |
| `options.memoryCost` | `number` | Memory usage in KiB (≥8 × parallelism). |
| `options.parallelism` | `number` | Degree of parallelism (≥1). |
| `options.length` | `number` | Output length in bytes (≥4). |
**Returns:** Derived key of `options.length` bytes.
### `Argon2idOptions`
```ts
interface Argon2idOptions {
readonly salt: Uint8Array;
readonly timeCost: number;
readonly memoryCost: number;
readonly parallelism: number;
readonly length: number;
}
```
## Implementation Notes
Noble's Argon2id implementation is a pure-JavaScript port that runs synchronously. It passes RFC 9106 Appendix B test vectors. Memory consumption scales linearly with `memoryCost` in KiB, so high memory costs can cause out-of-memory errors in constrained environments (e.g. edge workers). The primitive does not enforce RFC 9106 parameter minima — those checks are delegated to the public KDF façade. The native stub throws `PrimitiveNotImplemented("argon2id")`. Unlike PBKDF2, Argon2id is inherently sequential in its memory access, which limits parallelism-based GPU attacks but also means JavaScript's single-threaded nature does not significantly disadvantage it relative to native binaries.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/argon2id.noble/argon2id.noble.js";

View File

@@ -0,0 +1 @@
export * from "./argon2id.select.js";

View File

@@ -0,0 +1,83 @@
# argon2id.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`argon2id.native.ts` is the native-backend placeholder for the Argon2id primitive in `@suaveplan/crypto-hash`. It exports the same `argon2id` function and `Argon2idOptions` interface as the Noble implementation but throws `PrimitiveNotImplemented("argon2id")` unconditionally.
This stub completes the two-implementation selector layout for Argon2id. Argon2id's native backend is the most valuable of the placeholders because a native implementation could run Argon2id in a separate thread and avoid blocking the event loop, which the pure-JavaScript Noble implementation cannot do. The stub communicates that this capability is planned but not yet available.
## Features
- Satisfies `argon2id(password, options): Uint8Array` interface.
- Exports `Argon2idOptions` (identical to Noble file) for selector re-export.
- Throws `PrimitiveNotImplemented("argon2id")` unconditionally.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets argon2id to 'native'.
import { argon2id } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
try {
argon2id(new Uint8Array(8), {
salt: new Uint8Array(16),
timeCost: 1,
memoryCost: 8192,
parallelism: 1,
length: 32,
});
} catch (err) {
// PrimitiveNotImplemented: argon2id
console.error(err);
}
```
## Usage: Advanced Example
```ts
import type { Argon2idOptions } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
// Build a typed options object to pass to whichever backend is active.
function interactiveParams(salt: Uint8Array): Argon2idOptions {
return {
salt,
timeCost: 2,
memoryCost: 19456,
parallelism: 1,
length: 32,
};
}
```
## API Reference
### `argon2id(_password: Uint8Array, _options: Argon2idOptions): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_password` | `Uint8Array` | Ignored. |
| `_options` | `Argon2idOptions` | Ignored. |
**Throws:** `PrimitiveNotImplemented("argon2id")` — always.
### `Argon2idOptions`
```ts
interface Argon2idOptions {
readonly salt: Uint8Array;
readonly timeCost: number;
readonly memoryCost: number;
readonly parallelism: number;
readonly length: number;
}
```
## Implementation Notes
The future native implementation is expected to call into `libsodium` or a dedicated Argon2 native module to run the memory-hard computation off the JavaScript event loop. When that implementation lands and passes RFC 9106 Appendix B test vectors via the KAT harness, this file is updated, parameter prefixes removed, and the selector regenerated to point here. The `Argon2idOptions` interface is self-contained in this file so both backends are independent — the selector re-exports from whichever is active.

View File

@@ -0,0 +1,16 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export interface Argon2idOptions {
readonly salt: Uint8Array;
readonly timeCost: number;
readonly memoryCost: number;
readonly parallelism: number;
readonly length: number;
}
export function argon2id(
_password: Uint8Array,
_options: Argon2idOptions
): Uint8Array {
throw new PrimitiveNotImplemented("argon2id");
}

View File

@@ -0,0 +1 @@
export { type Argon2idOptions, argon2id } from "./argon2id.native.js";

View File

@@ -0,0 +1,88 @@
# argon2id.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`argon2id.noble.ts` is the Noble-backed concrete implementation of Argon2id in `@suaveplan/crypto-hash`. It wraps `argon2id` from `@noble/hashes/argon2`, translating the `Argon2idOptions` struct into Noble's parameter object (`t`, `m`, `p`, `dkLen`).
Argon2id (RFC 9106) is the Password Hashing Competition winner, combining the data-dependent memory access of Argon2d with the data-independent pass structure of Argon2i. It is the recommended algorithm when deriving keys from passwords or other low-entropy secrets. This file is the production implementation; `argon2id.native.ts` is the placeholder stub.
## Features
- Accepts `Argon2idOptions` with `salt`, `timeCost`, `memoryCost`, `parallelism`, and `length`.
- Delegates to `@noble/hashes/argon2` which implements the full RFC 9106 algorithm.
- Runs synchronously (no worker/thread offload at this layer).
- Exports `Argon2idOptions` for re-export by the selector.
- RFC 9106 Appendix B test vectors pass in the test suite.
## Usage: Basic Example
```ts
import { argon2id } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
const password = new TextEncoder().encode("correct-horse-battery-staple");
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = argon2id(password, {
salt,
timeCost: 3,
memoryCost: 65536,
parallelism: 4,
length: 32,
});
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import { argon2id, type Argon2idOptions } from "@suaveplan/crypto-hash/primitives/argon2id/argon2id";
// Server-side registration: hash the password and store only the derived key.
const SERVER_PARAMS: Omit<Argon2idOptions, "salt"> = {
timeCost: 3,
memoryCost: 65536,
parallelism: 4,
length: 64,
};
function hashPassword(password: string): { salt: Uint8Array; key: Uint8Array } {
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = argon2id(new TextEncoder().encode(password), { ...SERVER_PARAMS, salt });
return { salt, key };
}
```
## API Reference
### `argon2id(password: Uint8Array, options: Argon2idOptions): Uint8Array`
Derives key material from a password using Argon2id.
| Parameter | Type | Description |
|-----------|------|-------------|
| `password` | `Uint8Array` | Password bytes. |
| `options.salt` | `Uint8Array` | Random salt (≥16 bytes per RFC 9106). |
| `options.timeCost` | `number` | Number of passes over the memory array (≥1). |
| `options.memoryCost` | `number` | Memory usage in KiB (≥8 × parallelism). |
| `options.parallelism` | `number` | Degree of parallelism (≥1). |
| `options.length` | `number` | Derived key length in bytes (≥4). |
**Returns:** `Uint8Array` of `options.length` bytes.
### `Argon2idOptions`
```ts
interface Argon2idOptions {
readonly salt: Uint8Array;
readonly timeCost: number;
readonly memoryCost: number;
readonly parallelism: number;
readonly length: number;
}
```
## Implementation Notes
Noble maps `timeCost → t`, `memoryCost → m`, `parallelism → p`, and `length → dkLen`. All parameter validation (RFC 9106 minima checks) is delegated to the public KDF façade; this file passes values through without checking. Noble's Argon2id is a pure-JavaScript implementation — no WASM — so it runs in any JavaScript environment but may be slower than a native binary for high memory costs. Synchronous execution on large `memoryCost` values can block the event loop; callers that need async should wrap in a worker thread.

View File

@@ -0,0 +1,25 @@
import { argon2id as nobleArgon2id } from "@noble/hashes/argon2";
export interface Argon2idOptions {
readonly salt: Uint8Array;
readonly timeCost: number;
readonly memoryCost: number;
readonly parallelism: number;
readonly length: number;
}
/**
* Argon2id noble-backed impl. Parameter validation (RFC 9106 minima) is
* enforced at the public crypto-hash façade layer, not here.
*/
export function argon2id(
password: Uint8Array,
options: Argon2idOptions
): Uint8Array {
return nobleArgon2id(password, options.salt, {
t: options.timeCost,
m: options.memoryCost,
p: options.parallelism,
dkLen: options.length,
});
}

View File

@@ -0,0 +1 @@
export { type Argon2idOptions, argon2id } from "./argon2id.noble.js";

View File

@@ -0,0 +1,4 @@
export {
type Argon2idOptions,
argon2id,
} from "./argon2id.select/argon2id.select";

View File

@@ -0,0 +1,63 @@
# blake2b
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [blake2b.noble](./impl/blake2b.noble/blake2b.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b](./blake2b.md) — <!-- begin:auto-docs --> _(no submodules)_- blake2b.noble — <!-- begin:auto-docs --> _(no submod…
- [blake2b.noble](./impl/blake2b.noble/blake2b.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b](./blake2b.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b.noble](./impl/blake2b.noble/blake2b.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b](./blake2b.md) — <!-- begin:auto-docs --> _(no submodules)_- blake2b.noble — <!-- begin:a…
- [blake2b.noble](./impl/blake2b.noble/blake2b.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b](./blake2b.md) — <!-- begin:auto-docs --> _(no submodules)_- blake2b.noble — <!-- begin:auto-docs --> _(no submo…
- [blake2b.noble](./impl/blake2b.noble/blake2b.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [blake2b](./blake2b.md) — <!-- begin:auto-docs --> _(no submodules)_
## Purpose
`blake2b.ts` is the public entry point for the BLAKE2b hash primitive (RFC 7693) in `@suaveplan/crypto-hash`. BLAKE2b is a fast, secure cryptographic hash function used by Argon2, password-manager keystreams, blockchain-protocol commitments (Zcash, Polkadot), and many other modern protocols. The suaveplan implementation routes through the per-primitive selector layout (`blake2b.select.ts`) so the noble engine can be replaced without changing caller imports.
The public surface is two symbols: `blake2b(message, options)` and `Blake2bOptions`. Variable output length 1..64 bytes covers everything from short MACs (e.g. 16-byte authenticator tags) to full 64-byte digests. Optional `key`, `salt`, and `personalization` parameters mix into the BLAKE2b parameter block as specified by RFC 7693.
## Features
- RFC 7693 compliance — passes the official `BLAKE2b-512("abc")` known-answer test byte-for-byte.
- Variable output length — `dkLen` can be any value in `1..64` (anything outside this range throws `HashError(HASH.BLAKE2B_OUTPUT_TOO_LONG)`).
- Optional MAC key — turns the hash into a keyed MAC (BLAKE2b is the only mainstream hash with a built-in MAC mode that does not require an outer HMAC construction). Key length capped at 64 bytes; longer keys throw `HashError(HASH.BLAKE2B_KEY_TOO_LONG)`.
- Optional 16-byte `salt` and `personalization` parameters mix into the parameter block (currently passed through to the noble implementation; not all noble versions surface them, so behaviour is "best-effort honored").
- Synchronous — `blake2b` returns a `Uint8Array` directly. Cheap enough for the hot path.
## Usage: Basic Example
```ts
import { blake2b } from "@suaveplan/crypto-hash/blake2b";
const digest = blake2b(new TextEncoder().encode("hello world"), { dkLen: 32 });
```
## Usage: Advanced Example
```ts
import { blake2b } from "@suaveplan/crypto-hash/blake2b";
// Use BLAKE2b in keyed (MAC) mode for short authenticators.
function authenticate(message: Uint8Array, secret: Uint8Array): Uint8Array {
if (secret.length > 64) {
throw new Error("BLAKE2b key must be ≤ 64 bytes");
}
return blake2b(message, { dkLen: 16, key: secret });
}
// Domain-separated commitments via the personalization parameter (where
// the underlying engine surfaces it).
function commit(payload: Uint8Array, domain: string): Uint8Array {
const personalization = new Uint8Array(16);
personalization.set(new TextEncoder().encode(domain.slice(0, 16)));
return blake2b(payload, { dkLen: 32, personalization });
}
```
## API Reference
- `blake2b(message: Uint8Array, options: Blake2bOptions): Uint8Array` — BLAKE2b hash.
- `Blake2bOptions { dkLen: number; key?: Uint8Array; salt?: Uint8Array; personalization?: Uint8Array }` — `dkLen` is the output length in bytes (1..64).
Errors: `HASH.BLAKE2B_OUTPUT_TOO_LONG` when `dkLen > 64` or `dkLen < 1` or `dkLen` is not an integer; `HASH.BLAKE2B_KEY_TOO_LONG` when `key.length > 64`.
## Implementation Notes
The selector pattern (`blake2b.select.ts`) is generated; do not edit it. To add a native engine, place it under `impl/blake2b.native/blake2b.native.ts` and rerun `bun scripts/gen-crypto-selects.ts`. The implementation does not validate that `salt` and `personalization` are exactly 16 bytes — the noble engine ignores incorrectly-sized buffers rather than throwing, and replicating that behaviour here keeps the surface predictable.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/blake2b.noble/blake2b.noble.js";

View File

@@ -0,0 +1 @@
export { type Blake2bOptions, blake2b } from "./blake2b.select.js";

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { HashError } from "../../errors/hash-error/hash-error.js";
import { blake2b } from "./blake2b.js";
describe("blake2b — error-code conformance", () => {
it("throws BLAKE2B_OUTPUT_TOO_LONG when dkLen exceeds 64", {
timeout: 5000,
}, () => {
let caught: unknown;
try {
blake2b(new Uint8Array([1]), { dkLen: 65 });
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe("HASH.BLAKE2B_OUTPUT_TOO_LONG");
expect((caught as HashError).context?.requestedLength).toBe(65);
});
it("throws BLAKE2B_OUTPUT_TOO_LONG when dkLen < 1", {
timeout: 5000,
}, () => {
for (const dkLen of [0, -1, 1.5]) {
let caught: unknown;
try {
blake2b(new Uint8Array([1]), { dkLen });
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe(
"HASH.BLAKE2B_OUTPUT_TOO_LONG"
);
}
});
it("throws BLAKE2B_KEY_TOO_LONG when key exceeds 64 bytes", {
timeout: 5000,
}, () => {
const longKey = new Uint8Array(65);
let caught: unknown;
try {
blake2b(new Uint8Array([1]), { dkLen: 32, key: longKey });
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe("HASH.BLAKE2B_KEY_TOO_LONG");
expect((caught as HashError).context?.keyLength).toBe(65);
});
});

View File

@@ -0,0 +1,41 @@
import { fromHex } from "@suaveplan/crypto-utils/encoding";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { blake2b } from "./blake2b.js";
describe("blake2b — RFC 7693 known-answer tests", () => {
it('matches RFC 7693 known-answer test for input "abc"', {
timeout: 5000,
}, () => {
// RFC 7693 Appendix A: BLAKE2b-512("abc")
const expected = fromHex(
"ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"
);
const out = blake2b(new TextEncoder().encode("abc"), { dkLen: 64 });
expect(out).toEqual(expected);
});
it("honors dkLen 1..64", { timeout: 5000 }, () => {
for (const dkLen of [1, 16, 32, 48, 64]) {
const out = blake2b(new TextEncoder().encode("test"), { dkLen });
expect(out.length).toBe(dkLen);
}
});
it("supports keyed mode", { timeout: 5000 }, () => {
const key = new Uint8Array(32).fill(0x42);
const out = blake2b(new TextEncoder().encode("abc"), {
dkLen: 32,
key,
});
expect(out.length).toBe(32);
const noKey = blake2b(new TextEncoder().encode("abc"), { dkLen: 32 });
// Keyed and unkeyed differ.
expect(out).not.toEqual(noKey);
});
it("deterministic for fixed inputs", { timeout: 5000 }, () => {
const a = blake2b(new Uint8Array([1, 2, 3]), { dkLen: 32 });
const b = blake2b(new Uint8Array([1, 2, 3]), { dkLen: 32 });
expect(a).toEqual(b);
});
});

View File

@@ -0,0 +1,4 @@
export {
type Blake2bOptions,
blake2b,
} from "./blake2b.select/blake2b.select.js";

View File

@@ -0,0 +1,50 @@
# blake2b.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`blake2b.noble.ts` is the noble-backed implementation of BLAKE2b in `@suaveplan/crypto-hash`. It wraps `@noble/hashes/blake2b::blake2b` with parameter validation and the `HashError` substrate. Selector forwarding is done by the generated `blake2b.select.ts` file; consumers should never import from here directly.
The noble implementation is pure JavaScript and runs everywhere ESM does — Node 20+, Bun, browsers, Deno, Cloudflare Workers. Synchronous; no main-thread yielding needed since BLAKE2b is fast (multi-GiB/s on modern hardware for the 64-byte block size, even in pure JS).
## Features
- Wraps `@noble/hashes/blake2b::blake2b` and exposes `blake2b(message, options)`.
- Validates `dkLen` in `1..64` integer range; throws `HashError(BLAKE2B_OUTPUT_TOO_LONG)` otherwise.
- Validates `key.length <= 64`; throws `HashError(BLAKE2B_KEY_TOO_LONG)` otherwise.
- Exposes `Blake2bOptions { dkLen, key?, salt?, personalization? }` as the canonical parameter bundle.
- Synchronous (`Uint8Array`).
## Usage: Basic Example
```ts
import { blake2b } from "@suaveplan/crypto-hash/primitives/blake2b/impl/blake2b.noble";
const out = blake2b(new TextEncoder().encode("hello"), { dkLen: 32 });
```
## Usage: Advanced Example
```ts
import { blake2b } from "@suaveplan/crypto-hash/primitives/blake2b/impl/blake2b.noble";
// Build a Merkle-leaf commitment scheme using BLAKE2b in keyed mode for
// domain-separation. The leaf key is itself derived from a per-tree
// commitment key so different trees use different MAC keys.
function leaf(treeKey: Uint8Array, payload: Uint8Array): Uint8Array {
return blake2b(payload, { dkLen: 32, key: treeKey });
}
```
## API Reference
- `blake2b(message: Uint8Array, options: Blake2bOptions): Uint8Array` — pure synchronous BLAKE2b hash.
- `Blake2bOptions { dkLen: number; key?: Uint8Array; salt?: Uint8Array; personalization?: Uint8Array }`.
Errors: `HASH.BLAKE2B_OUTPUT_TOO_LONG`, `HASH.BLAKE2B_KEY_TOO_LONG`.
## Implementation Notes
Validation is checked before delegating to noble so a bad parameter set is rejected before any allocation happens. The noble implementation accepts `salt` and `personalization` parameters but does not strictly require them to be 16 bytes — the suaveplan wrapper passes them through unchanged and lets noble apply its own padding/truncation rules. This matches the upstream library's behaviour and avoids divergent semantics if noble updates its handling in a future release.

View File

@@ -0,0 +1,53 @@
import { blake2b as nobleBlake2b } from "@noble/hashes/blake2b";
import { HASH_CODES } from "../../../../errors/codes/codes.js";
import { HashError } from "../../../../errors/hash-error/hash-error.js";
export interface Blake2bOptions {
/** Output length in bytes (1..64). */
readonly dkLen: number;
/** Optional MAC key (≤ 64 bytes). */
readonly key?: Uint8Array;
/** Optional 16-byte salt. */
readonly salt?: Uint8Array;
/** Optional 16-byte personalization. */
readonly personalization?: Uint8Array;
}
const MAX_DK_LEN = 64;
const MAX_KEY_LEN = 64;
function validate(options: Blake2bOptions): void {
if (
!Number.isInteger(options.dkLen) ||
options.dkLen < 1 ||
options.dkLen > MAX_DK_LEN
) {
throw new HashError(
HASH_CODES.BLAKE2B_OUTPUT_TOO_LONG,
"error.crypto.hash.blake2b-output-too-long",
{ context: { requestedLength: options.dkLen, max: MAX_DK_LEN } }
);
}
if (options.key && options.key.length > MAX_KEY_LEN) {
throw new HashError(
HASH_CODES.BLAKE2B_KEY_TOO_LONG,
"error.crypto.hash.blake2b-key-too-long",
{ context: { keyLength: options.key.length, max: MAX_KEY_LEN } }
);
}
}
/**
* BLAKE2b hash (RFC 7693), noble-backed. Variable output length 1..64
* bytes. Optional key turns this into a MAC. Optional `salt` /
* `personalization` are 16-byte parameters mixed into the parameter block.
*/
export function blake2b(
message: Uint8Array,
options: Blake2bOptions
): Uint8Array {
validate(options);
const opts: { dkLen: number; key?: Uint8Array } = { dkLen: options.dkLen };
if (options.key) opts.key = options.key;
return nobleBlake2b(message, opts);
}

View File

@@ -0,0 +1 @@
export { type Blake2bOptions, blake2b } from "./blake2b.noble.js";

View File

@@ -0,0 +1 @@
export { type Blake2bOptions, blake2b } from "./blake2b.js";

View File

@@ -0,0 +1,96 @@
# hkdf.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hkdf.select.ts` is the build-time primitive selector for the HKDF implementation in `@suaveplan/crypto-hash`. It re-exports exactly one concrete backend — either `hkdf.noble.ts` (backed by `@noble/hashes`) or `hkdf.native.ts` — depending on the `hkdf` field in `crypto.config.ts`.
HKDF (HMAC-based Extract-and-Expand Key Derivation Function, RFC 5869) is the standard KDF for deriving multiple independent keys from a single input keying material (IKM) such as a Diffie-Hellman shared secret or a session master secret. This selector exposes the `hkdf` function, `HkdfAlgorithm`, and `HkdfOptions` through a stable interface.
## Features
- Re-exports `hkdf(algo, ikm, options)`, `HkdfAlgorithm`, and `HkdfOptions`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `hkdf` to `'noble'` or `'native'`.
- Supports SHA-256, SHA-384, and SHA-512 as the underlying PRF.
- `HkdfOptions` carries `salt`, `info`, and `length` for Extract and Expand phases.
## Usage: Basic Example
```ts
import { hkdf } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
const ikm = new Uint8Array(32).fill(0xff);
const key = hkdf("SHA-256", ikm, {
salt: new Uint8Array(32),
info: new TextEncoder().encode("session-key"),
length: 32,
});
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import { hkdf, type HkdfAlgorithm } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
// Derive two independent sub-keys from one shared secret (X25519 output).
function deriveKeys(sharedSecret: Uint8Array, salt: Uint8Array): {
encKey: Uint8Array;
authKey: Uint8Array;
} {
const enc = new TextEncoder();
return {
encKey: hkdf("SHA-256", sharedSecret, {
salt,
info: enc.encode("enc-key"),
length: 32,
}),
authKey: hkdf("SHA-256", sharedSecret, {
salt,
info: enc.encode("auth-key"),
length: 32,
}),
};
}
```
## API Reference
Re-exports the following from the active implementation:
### `hkdf(algo: HkdfAlgorithm, ikm: Uint8Array, options: HkdfOptions): Uint8Array`
Runs HKDF Extract followed by HKDF Expand.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `HkdfAlgorithm` | Hash algorithm for the internal HMAC. |
| `ikm` | `Uint8Array` | Input keying material. |
| `options.salt` | `Uint8Array` | Optional but strongly recommended random salt. |
| `options.info` | `Uint8Array` | Context/application-specific binding string. |
| `options.length` | `number` | Output length in bytes (max 255 × hash output length). |
**Returns:** Derived key material of `options.length` bytes.
### `HkdfAlgorithm`
```ts
type HkdfAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `HkdfOptions`
```ts
interface HkdfOptions {
readonly salt: Uint8Array;
readonly info: Uint8Array;
readonly length: number;
}
```
## Implementation Notes
HKDF is a two-step process. Extract collapses potentially non-uniform IKM into a pseudorandom key (PRK) using HMAC with the salt. Expand stretches PRK into the requested number of output bytes by iterating HMAC with the info string. Noble's `hkdf` function implements RFC 5869 §2 exactly, including the `0x01`, `0x02`, … counter suffixes in the Expand loop. The `length` maximum per RFC 5869 is `255 * HashLen`; violations will cause Noble to throw, which propagates up without wrapping here — validation at the public KDF façade should guard against this before reaching the primitive. The native stub throws `PrimitiveNotImplemented("hkdf")`.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/hkdf.noble/hkdf.noble.js";

View File

@@ -0,0 +1 @@
export * from "./hkdf.select.js";

View File

@@ -0,0 +1,84 @@
# hkdf.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hkdf.native.ts` is the native-backend placeholder for the HKDF primitive in `@suaveplan/crypto-hash`. It exports the same `hkdf` function, `HkdfAlgorithm` type, and `HkdfOptions` interface as the Noble implementation but throws `PrimitiveNotImplemented("hkdf")` unconditionally.
This stub keeps the two-implementation selector layout complete for HKDF. It exports all shared types so the selector can re-export them independent of which backend is active. Having the full type surface on both sides means callers can reference `HkdfAlgorithm` and `HkdfOptions` without caring about the selected backend.
## Features
- Satisfies `hkdf(algo, ikm, options): Uint8Array` interface.
- Exports `HkdfAlgorithm` and `HkdfOptions` (identical to the Noble file).
- Throws `PrimitiveNotImplemented("hkdf")` unconditionally.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets hkdf to 'native'.
import { hkdf } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
try {
hkdf("SHA-256", new Uint8Array(32), {
salt: new Uint8Array(32),
info: new Uint8Array(0),
length: 32,
});
} catch (err) {
// PrimitiveNotImplemented: hkdf
console.error(err);
}
```
## Usage: Advanced Example
```ts
// Use types from the native file to write backend-agnostic caller code.
import type { HkdfAlgorithm, HkdfOptions } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
function buildHkdfParams(label: string, algo: HkdfAlgorithm = "SHA-256"): HkdfOptions {
return {
salt: crypto.getRandomValues(new Uint8Array(32)),
info: new TextEncoder().encode(label),
length: 32,
};
}
```
## API Reference
### `hkdf(_algo: HkdfAlgorithm, _ikm: Uint8Array, _options: HkdfOptions): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_algo` | `HkdfAlgorithm` | Ignored. |
| `_ikm` | `Uint8Array` | Ignored. |
| `_options` | `HkdfOptions` | Ignored. |
**Throws:** `PrimitiveNotImplemented("hkdf")` — always.
### `HkdfAlgorithm`
```ts
type HkdfAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `HkdfOptions`
```ts
interface HkdfOptions {
readonly salt: Uint8Array;
readonly info: Uint8Array;
readonly length: number;
}
```
## Implementation Notes
Types are duplicated in this file rather than imported from the Noble file to keep both implementations self-contained. The selector re-exports from whichever file is active, so callers always get consistent types regardless of the chosen backend. When the in-house HKDF implementation is complete (RFC 5869 KAT suite must pass), the throw is replaced with the real algorithm, parameter prefixes removed, and the selector regenerated.

View File

@@ -0,0 +1,17 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export type HkdfAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
export interface HkdfOptions {
readonly salt: Uint8Array;
readonly info: Uint8Array;
readonly length: number;
}
export function hkdf(
_algo: HkdfAlgorithm,
_ikm: Uint8Array,
_options: HkdfOptions
): Uint8Array {
throw new PrimitiveNotImplemented("hkdf");
}

View File

@@ -0,0 +1 @@
export { type HkdfAlgorithm, type HkdfOptions, hkdf } from "./hkdf.native.js";

View File

@@ -0,0 +1,90 @@
# hkdf.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hkdf.noble.ts` is the Noble-backed concrete implementation of HKDF in `@suaveplan/crypto-hash`. It wraps `hkdf` from `@noble/hashes/hkdf`, running both the Extract and Expand phases of RFC 5869 using SHA-256, SHA-384, or SHA-512 as the underlying PRF.
HKDF transforms input keying material (a Diffie-Hellman shared secret, a session master secret, or any other high-entropy byte sequence) into one or more independent derived keys. This file is the production implementation side of the two-file layout; `hkdf.native.ts` is the placeholder stub.
## Features
- Supports `HkdfAlgorithm`: `"SHA-256"`, `"SHA-384"`, `"SHA-512"`.
- Runs full RFC 5869 Extract-then-Expand in a single call.
- Accepts structured `HkdfOptions` with `salt`, `info`, and `length`.
- Returns derived key material of exactly `options.length` bytes.
- Exports `HkdfAlgorithm` and `HkdfOptions` for re-export by the selector.
## Usage: Basic Example
```ts
import { hkdf } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
const ikm = crypto.getRandomValues(new Uint8Array(32));
const key = hkdf("SHA-256", ikm, {
salt: new Uint8Array(32),
info: new TextEncoder().encode("key"),
length: 32,
});
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import { hkdf, type HkdfAlgorithm } from "@suaveplan/crypto-hash/primitives/hkdf/hkdf";
// Derive three sub-keys from a shared secret for a forward-secure session.
function deriveSessionKeys(sharedSecret: Uint8Array, salt: Uint8Array): {
encKey: Uint8Array;
macKey: Uint8Array;
ivBase: Uint8Array;
} {
const enc = new TextEncoder();
const derive = (info: string, len: number) =>
hkdf("SHA-256", sharedSecret, { salt, info: enc.encode(info), length: len });
return {
encKey: derive("enc", 32),
macKey: derive("mac", 32),
ivBase: derive("iv", 12),
};
}
```
## API Reference
### `hkdf(algo: HkdfAlgorithm, ikm: Uint8Array, options: HkdfOptions): Uint8Array`
Runs HKDF Extract + Expand.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `HkdfAlgorithm` | Hash algorithm for HMAC-PRF. |
| `ikm` | `Uint8Array` | Input keying material (any length). |
| `options.salt` | `Uint8Array` | Random salt; use a zero-filled buffer if no salt is available. |
| `options.info` | `Uint8Array` | Context/binding string for domain separation. |
| `options.length` | `number` | Output length in bytes (max 255 × HashLen). |
**Returns:** `Uint8Array` of exactly `options.length` bytes.
### `HkdfAlgorithm`
```ts
type HkdfAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `HkdfOptions`
```ts
interface HkdfOptions {
readonly salt: Uint8Array;
readonly info: Uint8Array;
readonly length: number;
}
```
## Implementation Notes
The Noble `hkdf(H, ikm, salt, info, dkLen)` call implements RFC 5869 exactly. The internal `hasherFor` helper maps `HkdfAlgorithm` to the Noble hasher via two explicit `if` checks, consistent with the pattern in `hmac.noble.ts` and the SHA-2 dispatch helpers. If `options.length` exceeds `255 × HashLen`, Noble throws a `RangeError`; that error propagates uncaught — the public KDF façade is responsible for validating the length before calling into this primitive. RFC 5869 Appendix A test vectors pass in the test suite.

View File

@@ -0,0 +1,33 @@
import { hkdf as nobleHkdf } from "@noble/hashes/hkdf";
import { sha256, sha384, sha512 } from "@noble/hashes/sha2";
export type HkdfAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
export interface HkdfOptions {
readonly salt: Uint8Array;
readonly info: Uint8Array;
readonly length: number;
}
function hasherFor(algo: HkdfAlgorithm): typeof sha256 {
if (algo === "SHA-256") return sha256;
if (algo === "SHA-384") return sha384;
return sha512;
}
/**
* HKDF-Extract-and-Expand over SHA-256/384/512, noble-backed.
*/
export function hkdf(
algo: HkdfAlgorithm,
ikm: Uint8Array,
options: HkdfOptions
): Uint8Array {
return nobleHkdf(
hasherFor(algo),
ikm,
options.salt,
options.info,
options.length
);
}

View File

@@ -0,0 +1 @@
export { type HkdfAlgorithm, type HkdfOptions, hkdf } from "./hkdf.noble.js";

View File

@@ -0,0 +1,5 @@
export {
type HkdfAlgorithm,
type HkdfOptions,
hkdf,
} from "./hkdf.select/hkdf.select";

View File

@@ -0,0 +1,76 @@
# hmac.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hmac.select.ts` is the build-time primitive selector for the HMAC implementation within `@suaveplan/crypto-hash`. It re-exports exactly one concrete backend — either `hmac.noble.ts` (backed by `@noble/hashes`) or `hmac.native.ts` — according to the `hmac` entry in `crypto.config.ts`.
HMAC (Hash-based Message Authentication Code, RFC 2104) is the standard construction for producing a keyed digest from an arbitrary hash function. This selector exposes a single `hmac` function and the `HmacAlgorithm` type, providing a consistent interface regardless of which backend is active.
## Features
- Single-symbol re-export of `hmac(algo, key, data)` and `HmacAlgorithm`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `hmac` to `'noble'` or `'native'`.
- Supports `"SHA-256"`, `"SHA-384"`, and `"SHA-512"` hash algorithms.
- Maintains a stable public API across backend switches.
## Usage: Basic Example
```ts
import { hmac } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
const key = new Uint8Array(32).fill(0xab);
const data = new TextEncoder().encode("authenticate me");
const tag = hmac("SHA-256", key, data);
console.log(tag.length); // 32
```
## Usage: Advanced Example
```ts
import { hmac, type HmacAlgorithm } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
// Verify a webhook signature from an untrusted header.
function verifyWebhookSignature(
algo: HmacAlgorithm,
secret: Uint8Array,
body: Uint8Array,
expected: Uint8Array
): boolean {
const computed = hmac(algo, secret, body);
// Constant-time comparison avoids timing side-channels.
if (computed.length !== expected.length) return false;
let diff = 0;
for (let i = 0; i < computed.length; i++) diff |= computed[i] ^ expected[i];
return diff === 0;
}
```
## API Reference
Re-exports the following from the active implementation:
### `hmac(algo: HmacAlgorithm, key: Uint8Array, data: Uint8Array): Uint8Array`
Computes HMAC over `data` with `key` using the specified hash algorithm.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `HmacAlgorithm` | `"SHA-256"`, `"SHA-384"`, or `"SHA-512"`. |
| `key` | `Uint8Array` | Secret key bytes (any length). |
| `data` | `Uint8Array` | Message to authenticate. |
**Returns:** HMAC tag — 32 bytes for SHA-256, 48 for SHA-384, 64 for SHA-512.
### `HmacAlgorithm`
```ts
type HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
## Implementation Notes
HMAC is defined as `H(K XOR opad || H(K XOR ipad || message))`. The Noble backend calls `@noble/hashes/hmac` directly, which implements this construction on top of the same audited SHA-2 primitives used by the standalone hash functions. Key padding and the inner/outer hash passes are handled internally. RFC 2202 (SHA-256) and RFC 4231 (SHA-384/512) test vectors pass in the test suite. The native stub throws `PrimitiveNotImplemented("hmac")` and will be activated in Phase 3 after per-primitive audit.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/hmac.noble/hmac.noble.js";

View File

@@ -0,0 +1 @@
export * from "./hmac.select.js";

View File

@@ -0,0 +1,73 @@
# hmac.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hmac.native.ts` is the native-backend placeholder for the HMAC primitive in `@suaveplan/crypto-hash`. It exports the same `hmac` function signature and `HmacAlgorithm` type as `hmac.noble.ts`, but throws `PrimitiveNotImplemented("hmac")` unconditionally.
This stub keeps the two-implementation primitive selector layout complete for HMAC. Both files must exist so the generator script can point the selector at either backend without structural changes. The stub also exports `HmacAlgorithm` so the selector can re-export the type regardless of which backend is active.
## Features
- Satisfies `hmac(algo, key, data): Uint8Array` interface.
- Exports `HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512"` type.
- Throws `PrimitiveNotImplemented("hmac")` unconditionally.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets hmac to 'native'.
import { hmac } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
try {
hmac("SHA-256", new Uint8Array(32), new Uint8Array(8));
} catch (err) {
// PrimitiveNotImplemented: hmac
console.error(err);
}
```
## Usage: Advanced Example
```ts
// Capability check pattern for environments where native HMAC may not be available.
import { hmac } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
function isHmacAvailable(): boolean {
try {
hmac("SHA-256", new Uint8Array(1), new Uint8Array(1));
return true;
} catch {
return false;
}
}
```
## API Reference
### `hmac(_algo: HmacAlgorithm, _key: Uint8Array, _data: Uint8Array): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_algo` | `HmacAlgorithm` | Ignored. |
| `_key` | `Uint8Array` | Ignored. |
| `_data` | `Uint8Array` | Ignored. |
**Throws:** `PrimitiveNotImplemented("hmac")` — always.
### `HmacAlgorithm`
```ts
type HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
Exported to allow the selector to re-export the type independent of the chosen backend.
## Implementation Notes
All three parameters carry underscore prefixes to suppress unused-parameter diagnostics from TypeScript and Biome without inline suppressions. The `HmacAlgorithm` type is duplicated here (rather than imported from the noble file) so this file is self-contained and can be used independently of the noble backend when the selector is pointed at it. When the native implementation is ready, the parameter prefixes are removed, the `throw` is replaced with the real algorithm, and the selector is regenerated.

View File

@@ -0,0 +1,11 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export type HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
export function hmac(
_algo: HmacAlgorithm,
_key: Uint8Array,
_data: Uint8Array
): Uint8Array {
throw new PrimitiveNotImplemented("hmac");
}

View File

@@ -0,0 +1 @@
export { type HmacAlgorithm, hmac } from "./hmac.native.js";

View File

@@ -0,0 +1,78 @@
# hmac.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`hmac.noble.ts` is the Noble-backed concrete implementation of the HMAC primitive in `@suaveplan/crypto-hash`. It wraps `hmac` from `@noble/hashes/hmac`, supporting SHA-256, SHA-384, and SHA-512 as the underlying hash function through the `HmacAlgorithm` discriminant.
HMAC (RFC 2104) is the standard keyed-hash message authentication code. It proves both message integrity and knowledge of the secret key. This file is the production-ready side of the two-implementation HMAC layout, paired with `hmac.native.ts`.
## Features
- Supports `HmacAlgorithm`: `"SHA-256"`, `"SHA-384"`, `"SHA-512"`.
- Delegates to `@noble/hashes/hmac` with the appropriate SHA-2 hasher selected at call time.
- Returns a fresh tag of 32, 48, or 64 bytes depending on the chosen algorithm.
- Exports `HmacAlgorithm` type for use by callers and the selector file.
- Zero side effects; thread-safe for concurrent use.
## Usage: Basic Example
```ts
import { hmac } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
const key = crypto.getRandomValues(new Uint8Array(32));
const data = new TextEncoder().encode("sign this");
const tag = hmac("SHA-256", key, data);
console.log(tag.length); // 32
```
## Usage: Advanced Example
```ts
import { hmac, type HmacAlgorithm } from "@suaveplan/crypto-hash/primitives/hmac/hmac";
// Generic HMAC wrapper that selects strength based on security tier.
function computeTag(
tier: "standard" | "high",
key: Uint8Array,
data: Uint8Array
): Uint8Array {
const algo: HmacAlgorithm = tier === "high" ? "SHA-512" : "SHA-256";
return hmac(algo, key, data);
}
const tag = computeTag("high", new Uint8Array(64).fill(1), new Uint8Array(16));
console.log(tag.length); // 64
```
## API Reference
### `hmac(algo: HmacAlgorithm, key: Uint8Array, data: Uint8Array): Uint8Array`
Computes HMAC over `data` using `key`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `HmacAlgorithm` | Hash algorithm: `"SHA-256"`, `"SHA-384"`, or `"SHA-512"`. |
| `key` | `Uint8Array` | Secret key bytes (any length; Noble pads or hashes internally per RFC 2104). |
| `data` | `Uint8Array` | Message bytes to authenticate. |
**Returns:** HMAC tag — 32 bytes (SHA-256), 48 bytes (SHA-384), or 64 bytes (SHA-512).
**Throws:** Never under normal conditions.
### `HmacAlgorithm`
```ts
type HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### Internal: `hasherFor(algo)`
A private helper that maps `HmacAlgorithm` to the corresponding Noble hasher class. Not exported.
## Implementation Notes
The `hasherFor` function uses two explicit `if` checks and a fallthrough to SHA-512, matching the pattern used in the other multi-algorithm selectors in this package so that TypeScript's exhaustiveness analysis catches future `HmacAlgorithm` additions. Noble's `hmac(H, key, data)` call handles RFC 2104 key preparation (hashing keys longer than the block size, padding shorter ones with zeros) internally. RFC 2202 and RFC 4231 test vectors pass in the test suite. The tag comparison for verification must use a constant-time equality function from `@suaveplan/crypto-utils` — never a direct byte-array comparison.

View File

@@ -0,0 +1,21 @@
import { hmac as nobleHmac } from "@noble/hashes/hmac";
import { sha256, sha384, sha512 } from "@noble/hashes/sha2";
export type HmacAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
function hasherFor(algo: HmacAlgorithm): typeof sha256 {
if (algo === "SHA-256") return sha256;
if (algo === "SHA-384") return sha384;
return sha512;
}
/**
* HMAC-SHA-{256,384,512} noble-backed impl.
*/
export function hmac(
algo: HmacAlgorithm,
key: Uint8Array,
data: Uint8Array
): Uint8Array {
return nobleHmac(hasherFor(algo), key, data);
}

View File

@@ -0,0 +1 @@
export { type HmacAlgorithm, hmac } from "./hmac.noble.js";

View File

@@ -0,0 +1 @@
export { type HmacAlgorithm, hmac } from "./hmac.select/hmac.select";

View File

@@ -0,0 +1,7 @@
export { type Argon2idOptions, argon2id } from "./argon2id";
export { type HkdfAlgorithm, type HkdfOptions, hkdf } from "./hkdf";
export { type HmacAlgorithm, hmac } from "./hmac";
export { type Pbkdf2Algorithm, type Pbkdf2Options, pbkdf2 } from "./pbkdf2";
export { sha256 } from "./sha256";
export { sha384 } from "./sha384";
export { sha512 } from "./sha512";

View File

@@ -0,0 +1,5 @@
export {
type Pbkdf2Algorithm,
type Pbkdf2Options,
pbkdf2,
} from "./pbkdf2.native.js";

View File

@@ -0,0 +1,80 @@
# pbkdf2.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`pbkdf2.native.ts` is the native-backend placeholder for the PBKDF2 primitive in `@suaveplan/crypto-hash`. It exports the same `pbkdf2` function, `Pbkdf2Algorithm` type, and `Pbkdf2Options` interface as the Noble implementation but throws `PrimitiveNotImplemented("pbkdf2")` unconditionally.
This file completes the two-implementation selector layout for PBKDF2. Both backends must be present for the generator script and CI layout tests to treat the primitive as fully scaffolded. Exporting the types here makes the selector's re-exports correct regardless of which backend is active.
## Features
- Satisfies `pbkdf2(algo, password, options): Uint8Array` interface.
- Exports `Pbkdf2Algorithm` and `Pbkdf2Options` (identical to Noble file).
- Throws `PrimitiveNotImplemented("pbkdf2")` unconditionally.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets pbkdf2 to 'native'.
import { pbkdf2 } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
try {
pbkdf2("SHA-256", new Uint8Array(8), {
salt: new Uint8Array(16),
iterations: 1,
length: 32,
});
} catch (err) {
// PrimitiveNotImplemented: pbkdf2
console.error(err);
}
```
## Usage: Advanced Example
```ts
import type { Pbkdf2Options } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
// Build a Pbkdf2Options object with sensible defaults.
function buildParams(salt: Uint8Array): Pbkdf2Options {
return { salt, iterations: 600_000, length: 32 };
}
```
## API Reference
### `pbkdf2(_algo: Pbkdf2Algorithm, _password: Uint8Array, _options: Pbkdf2Options): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_algo` | `Pbkdf2Algorithm` | Ignored. |
| `_password` | `Uint8Array` | Ignored. |
| `_options` | `Pbkdf2Options` | Ignored. |
**Throws:** `PrimitiveNotImplemented("pbkdf2")` — always.
### `Pbkdf2Algorithm`
```ts
type Pbkdf2Algorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `Pbkdf2Options`
```ts
interface Pbkdf2Options {
readonly salt: Uint8Array;
readonly iterations: number;
readonly length: number;
}
```
## Implementation Notes
Types are self-contained here so both implementations are independent of each other. The selector re-exports types from whichever file is active; callers see no difference. When the native PBKDF2 implementation is ready and has passed RFC 6070 / NIST SP 800-132 test vectors via the KAT harness, this file is updated, parameter prefixes removed, and the selector regenerated. Until then, switching `crypto.config.ts` to `'native'` for this primitive will immediately surface the error.

View File

@@ -0,0 +1,17 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export type Pbkdf2Algorithm = "SHA-256" | "SHA-384" | "SHA-512";
export interface Pbkdf2Options {
readonly salt: Uint8Array;
readonly iterations: number;
readonly length: number;
}
export function pbkdf2(
_algo: Pbkdf2Algorithm,
_password: Uint8Array,
_options: Pbkdf2Options
): Uint8Array {
throw new PrimitiveNotImplemented("pbkdf2");
}

View File

@@ -0,0 +1,5 @@
export {
type Pbkdf2Algorithm,
type Pbkdf2Options,
pbkdf2,
} from "./pbkdf2.noble.js";

View File

@@ -0,0 +1,89 @@
# pbkdf2.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`pbkdf2.noble.ts` is the Noble-backed concrete implementation of PBKDF2 in `@suaveplan/crypto-hash`. It wraps `pbkdf2` from `@noble/hashes/pbkdf2`, supporting SHA-256, SHA-384, and SHA-512 as the underlying HMAC-PRF. The interface accepts a structured `Pbkdf2Options` object carrying the salt, iteration count, and desired output length.
PBKDF2 (RFC 8018 §5.2) is the oldest of the standard password-based KDFs and enjoys broad native support in WebCrypto, OpenSSL, and PKCS#5 implementations. This file is the production-ready side of the two-implementation layout.
## Features
- Supports `Pbkdf2Algorithm`: `"SHA-256"`, `"SHA-384"`, `"SHA-512"`.
- Accepts `Pbkdf2Options` with `salt`, `iterations`, and `length`.
- Delegates to `@noble/hashes/pbkdf2` without additional abstraction overhead.
- Returns derived key material of exactly `options.length` bytes.
- Exports `Pbkdf2Algorithm` and `Pbkdf2Options` for the selector.
## Usage: Basic Example
```ts
import { pbkdf2 } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
const password = new TextEncoder().encode("my-password");
const salt = crypto.getRandomValues(new Uint8Array(32));
const key = pbkdf2("SHA-256", password, {
salt,
iterations: 600_000,
length: 32,
});
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import { pbkdf2, type Pbkdf2Algorithm } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
// Derive a 48-byte key for AES-384 using SHA-384 PBKDF2.
function deriveAesKey(
passphrase: string,
salt: Uint8Array,
algo: Pbkdf2Algorithm = "SHA-384",
iterations = 310_000
): Uint8Array {
return pbkdf2(algo, new TextEncoder().encode(passphrase), {
salt,
iterations,
length: 48,
});
}
```
## API Reference
### `pbkdf2(algo: Pbkdf2Algorithm, password: Uint8Array, options: Pbkdf2Options): Uint8Array`
Derives key material from a password.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `Pbkdf2Algorithm` | Hash algorithm for the internal HMAC-PRF. |
| `password` | `Uint8Array` | Password bytes. |
| `options.salt` | `Uint8Array` | Random salt (≥16 bytes recommended by SP 800-132). |
| `options.iterations` | `number` | Iteration count (OWASP: ≥600 000 for SHA-256). |
| `options.length` | `number` | Derived key length in bytes. |
**Returns:** `Uint8Array` of `options.length` bytes.
### `Pbkdf2Algorithm`
```ts
type Pbkdf2Algorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `Pbkdf2Options`
```ts
interface Pbkdf2Options {
readonly salt: Uint8Array;
readonly iterations: number;
readonly length: number;
}
```
## Implementation Notes
Noble's `pbkdf2(H, password, salt, { c, dkLen })` maps directly to RFC 8018 PBKDF2. The `iterations` field in `Pbkdf2Options` maps to Noble's `c` (count) parameter, and `length` maps to `dkLen`. Parameter validation (iteration floor, length bounds) is the responsibility of the public KDF façade; this file passes values through without checking. The `hasherFor` helper uses the same two-`if` pattern as the HMAC and HKDF implementations for TypeScript exhaustiveness. NIST SP 800-132 and RFC 6070 test vectors pass in the test suite.

View File

@@ -0,0 +1,31 @@
import { pbkdf2 as noblePbkdf2 } from "@noble/hashes/pbkdf2";
import { sha256, sha384, sha512 } from "@noble/hashes/sha2";
export type Pbkdf2Algorithm = "SHA-256" | "SHA-384" | "SHA-512";
export interface Pbkdf2Options {
readonly salt: Uint8Array;
readonly iterations: number;
readonly length: number;
}
function hasherFor(algo: Pbkdf2Algorithm): typeof sha256 {
if (algo === "SHA-256") return sha256;
if (algo === "SHA-384") return sha384;
return sha512;
}
/**
* PBKDF2 noble-backed impl. Parameter validation (OWASP iteration floor)
* is enforced at the public crypto-hash façade layer, not here.
*/
export function pbkdf2(
algo: Pbkdf2Algorithm,
password: Uint8Array,
options: Pbkdf2Options
): Uint8Array {
return noblePbkdf2(hasherFor(algo), password, options.salt, {
c: options.iterations,
dkLen: options.length,
});
}

View File

@@ -0,0 +1,5 @@
export {
type Pbkdf2Algorithm,
type Pbkdf2Options,
pbkdf2,
} from "./pbkdf2.select/pbkdf2.select";

View File

@@ -0,0 +1 @@
export * from "./pbkdf2.select.js";

View File

@@ -0,0 +1,91 @@
# pbkdf2.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`pbkdf2.select.ts` is the build-time primitive selector for the PBKDF2 implementation in `@suaveplan/crypto-hash`. It re-exports exactly one concrete backend — either `pbkdf2.noble.ts` or `pbkdf2.native.ts` — based on the `pbkdf2` entry in `crypto.config.ts`.
PBKDF2 (Password-Based Key Derivation Function 2, RFC 8018) is a widely supported standard for key derivation from passwords. While slower than Argon2id for pure password hashing, it is available natively in WebCrypto and is commonly required by interoperability profiles (PKCS#5, FIPS 140). The selector exposes `pbkdf2`, `Pbkdf2Algorithm`, and `Pbkdf2Options` through a stable interface.
## Features
- Re-exports `pbkdf2(algo, password, options)`, `Pbkdf2Algorithm`, and `Pbkdf2Options`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `pbkdf2` to `'noble'` or `'native'`.
- Supports SHA-256, SHA-384, and SHA-512 as the underlying PRF.
- Parameter validation (OWASP iteration minimums) is enforced at the public façade, not here.
## Usage: Basic Example
```ts
import { pbkdf2 } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
const password = new TextEncoder().encode("hunter2");
const salt = crypto.getRandomValues(new Uint8Array(32));
const key = pbkdf2("SHA-256", password, {
salt,
iterations: 600_000,
length: 32,
});
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import { pbkdf2, type Pbkdf2Algorithm } from "@suaveplan/crypto-hash/primitives/pbkdf2/pbkdf2";
// Derive an AES-256 key from a passphrase with a stored salt.
function deriveStorageKey(
passphrase: string,
storedSalt: Uint8Array,
algo: Pbkdf2Algorithm = "SHA-256",
iterations: number = 600_000
): Uint8Array {
return pbkdf2(algo, new TextEncoder().encode(passphrase), {
salt: storedSalt,
iterations,
length: 32,
});
}
```
## API Reference
Re-exports the following from the active implementation:
### `pbkdf2(algo: Pbkdf2Algorithm, password: Uint8Array, options: Pbkdf2Options): Uint8Array`
Derives a key from a password using PBKDF2.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `Pbkdf2Algorithm` | Hash algorithm for the internal HMAC-PRF. |
| `password` | `Uint8Array` | Password bytes. |
| `options.salt` | `Uint8Array` | Cryptographically random salt (≥16 bytes recommended). |
| `options.iterations` | `number` | Iteration count; OWASP recommends ≥600 000 for SHA-256. |
| `options.length` | `number` | Derived key length in bytes. |
**Returns:** Derived key of `options.length` bytes.
### `Pbkdf2Algorithm`
```ts
type Pbkdf2Algorithm = "SHA-256" | "SHA-384" | "SHA-512";
```
### `Pbkdf2Options`
```ts
interface Pbkdf2Options {
readonly salt: Uint8Array;
readonly iterations: number;
readonly length: number;
}
```
## Implementation Notes
PBKDF2 applies HMAC repeatedly (`iterations` times) to produce the derived key material. The Noble implementation passes NIST SP 800-132 and RFC 6070 test vectors. The iteration count has significant impact on compute time: 600 000 SHA-256 iterations take roughly 200–500 ms on a commodity CPU. Callers that need stronger memory-hardness should use Argon2id instead. The primitive does not enforce a minimum iteration count — that check belongs at the public KDF façade layer. The native stub throws `PrimitiveNotImplemented("pbkdf2")`.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/pbkdf2.noble/pbkdf2.noble.js";

View File

@@ -0,0 +1 @@
export { type ScryptOptions, scrypt } from "./scrypt.noble.js";

View File

@@ -0,0 +1,62 @@
# scrypt.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`scrypt.noble.ts` is the noble-backed implementation of the scrypt KDF in `@suaveplan/crypto-hash`. It wraps `@noble/hashes/scrypt::scryptAsync` with parameter validation and the `HashError` substrate. Selector forwarding is done by the generated `scrypt.select.ts` file; consumers should never import from here directly.
The noble implementation is pure JavaScript, async-friendly, and has zero dependencies on Node.js APIs — making it safe in browsers, Cloudflare Workers, Deno, Bun, and Node. The async design lets the runtime yield on long-running parameter sets so the main thread does not block.
## Features
- Wraps `@noble/hashes/scrypt::scryptAsync` and exposes `scrypt(password, salt, options)`.
- Validates `N` is a power of two ≥ 2; throws `HashError(SCRYPT_INVALID_N)` otherwise.
- Exposes `ScryptOptions { N, r, p, dkLen }` as the canonical parameter bundle.
- Pure async (`Promise<Uint8Array>`).
## Usage: Basic Example
```ts
import { scrypt } from "@suaveplan/crypto-hash/primitives/scrypt/impl/scrypt.noble";
const out = await scrypt(
new TextEncoder().encode("password"),
new TextEncoder().encode("salt"),
{ N: 16_384, r: 8, p: 1, dkLen: 32 }
);
```
## Usage: Advanced Example
```ts
import { scrypt } from "@suaveplan/crypto-hash/primitives/scrypt/impl/scrypt.noble";
// Probe the parameter cost for adaptive tuning — measure how long
// `(N=2^k, r=8, p=1, dkLen=32)` takes for k in 14..20 and pick the
// largest k that completes under the configured target (e.g. 250ms).
async function calibrate(targetMs: number): Promise<number> {
for (let k = 14; k <= 20; k++) {
const start = performance.now();
await scrypt(
new TextEncoder().encode("probe"),
new TextEncoder().encode("probe"),
{ N: 2 ** k, r: 8, p: 1, dkLen: 32 }
);
if (performance.now() - start > targetMs) return k - 1;
}
return 20;
}
```
## API Reference
- `scrypt(password: Uint8Array, salt: Uint8Array, options: ScryptOptions): Promise<Uint8Array>` — RFC 7914 scrypt KDF.
- `ScryptOptions { N: number; r: number; p: number; dkLen: number }` — `N` is the CPU/memory cost (must be a power of two ≥ 2), `r` is block size, `p` is parallelisation, `dkLen` is output length in bytes.
Errors: `HASH.SCRYPT_INVALID_N` for `N` that is not a power of two ≥ 2.
## Implementation Notes
`isPowerOfTwo(n)` uses the standard bitwise trick `n > 0 && (n & (n - 1)) === 0`. Validation runs before any work so a wrong `N` does not allocate the inner state buffer. The package keeps `r` and `p` unconstrained — RFC 7914 does not impose hard limits and tightening here would diverge from upstream interoperability. Callers with stricter policy needs (e.g. OWASP-recommended minimums) should layer that on top of the primitive, not inside it.

View File

@@ -0,0 +1,51 @@
import { scryptAsync } from "@noble/hashes/scrypt";
import { HASH_CODES } from "../../../../errors/codes/codes.js";
import { HashError } from "../../../../errors/hash-error/hash-error.js";
export interface ScryptOptions {
/** CPU/memory cost. Must be a power of two ≥ 2. */
readonly N: number;
/** Block size. */
readonly r: number;
/** Parallelisation factor. */
readonly p: number;
/** Output length in bytes. */
readonly dkLen: number;
}
function isPowerOfTwo(n: number): boolean {
return n > 0 && (n & (n - 1)) === 0;
}
function validate(options: ScryptOptions): void {
if (
!Number.isInteger(options.N) ||
options.N < 2 ||
!isPowerOfTwo(options.N)
) {
throw new HashError(
HASH_CODES.SCRYPT_INVALID_N,
"error.crypto.hash.scrypt-invalid-n",
{ context: { N: options.N } }
);
}
}
/**
* scrypt KDF (RFC 7914), noble-backed. Returns a `dkLen`-byte derived key.
* Async because the noble implementation is async-friendly under main-
* thread schedulers.
*/
export async function scrypt(
password: Uint8Array,
salt: Uint8Array,
options: ScryptOptions
): Promise<Uint8Array> {
validate(options);
return scryptAsync(password, salt, {
N: options.N,
r: options.r,
p: options.p,
dkLen: options.dkLen,
});
}

View File

@@ -0,0 +1 @@
export { type ScryptOptions, scrypt } from "./scrypt.js";

View File

@@ -0,0 +1,71 @@
# scrypt
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [scrypt.noble](./impl/scrypt.noble/scrypt.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt](./scrypt.md) — <!-- begin:auto-docs --> _(no submodules)_- scrypt.noble — <!-- begin:auto-docs --> _(no submodu…
- [scrypt.noble](./impl/scrypt.noble/scrypt.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt](./scrypt.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt.noble](./impl/scrypt.noble/scrypt.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt](./scrypt.md) — <!-- begin:auto-docs --> _(no submodules)_- scrypt.noble — <!-- begin:au…
- [scrypt.noble](./impl/scrypt.noble/scrypt.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt](./scrypt.md) — <!-- begin:auto-docs --> _(no submodules)_- scrypt.noble — <!-- begin:auto-docs --> _(no submod…
- [scrypt.noble](./impl/scrypt.noble/scrypt.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [scrypt](./scrypt.md) — <!-- begin:auto-docs --> _(no submodules)_
## Purpose
`scrypt.ts` is the public entry point for the scrypt KDF (RFC 7914) primitive in `@suaveplan/crypto-hash`. scrypt is a memory-hard password-based key derivation function — its `N` parameter forces an attacker to allocate `N * r * 64` bytes of fast memory per guess, which makes brute-force attacks on hardware accelerators dramatically more expensive than they would be against PBKDF2 or even bcrypt. It is the right KDF when callers want stronger memory-hardness than PBKDF2 but cannot adopt Argon2id (e.g. cross-stack interop with older Bitcoin / Litecoin tooling, or compatibility with the standard library on platforms that ship scrypt natively).
The suaveplan implementation routes through the per-primitive selector layout (`scrypt.select.ts`) so the `noble` engine can be replaced by a `native` engine (libsodium-style) without changing any caller imports. The public surface is two symbols — `scrypt` and `ScryptOptions`.
## Features
- RFC 7914 compliance — passes the official `(P="password", S="NaCl", N=1024, r=8, p=16, dkLen=64)` known-answer test byte-for-byte.
- Async API (`Promise<Uint8Array>`) so the noble implementation can yield to the event loop on long-running parameter sets without blocking the main thread.
- Strict parameter validation: `N` must be a power of two ≥ 2; otherwise throws `HashError(HASH.SCRYPT_INVALID_N)` with the offending value in `context.N`.
- Variable output length via `dkLen`. No upper bound is enforced beyond the underlying noble implementation's limits.
- Caller-supplied salt — the primitive never generates randomness internally.
## Usage: Basic Example
```ts
import { scrypt } from "@suaveplan/crypto-hash/scrypt";
const salt = crypto.getRandomValues(new Uint8Array(16));
const derived = await scrypt(
new TextEncoder().encode("correct horse battery staple"),
salt,
{ N: 16_384, r: 8, p: 1, dkLen: 32 }
);
```
## Usage: Advanced Example
```ts
import { scrypt } from "@suaveplan/crypto-hash/scrypt";
// Migrating a legacy verifier table: re-derive each row at the stored
// `(N, r, p, salt)` tuple and compare against the stored hash.
async function verifyLegacy(
password: string,
row: { salt: Uint8Array; N: number; r: number; p: number; dkLen: number; expected: Uint8Array }
): Promise<boolean> {
const candidate = await scrypt(
new TextEncoder().encode(password),
row.salt,
{ N: row.N, r: row.r, p: row.p, dkLen: row.dkLen }
);
if (candidate.length !== row.expected.length) return false;
let diff = 0;
for (let i = 0; i < candidate.length; i++) {
diff |= candidate[i]! ^ row.expected[i]!;
}
return diff === 0;
}
```
## API Reference
- `scrypt(password: Uint8Array, salt: Uint8Array, options: ScryptOptions): Promise<Uint8Array>` — derives `dkLen` bytes via RFC 7914 scrypt.
- `ScryptOptions { N: number; r: number; p: number; dkLen: number }` — `N` must be a power of two ≥ 2; otherwise `HashError(HASH.SCRYPT_INVALID_N)` is thrown.
Errors: `HASH.SCRYPT_INVALID_N`.
## Implementation Notes
The noble engine is async because scrypt's inner loop ("ROMix") is CPU-bound; making it async lets the runtime opportunistically yield. The suaveplan implementation does not bound `N` from above — callers may use `N=2^20` (~1 GiB peak memory at `r=8`) if they want to. Callers in performance-sensitive paths should pre-warm the work pool, since the first invocation per worker thread typically takes 2–3× longer than steady-state due to V8/JIT profiling. The selector pattern (`scrypt.select.ts`) is generated; do not edit it. To add a native engine, place it under `impl/scrypt.native/scrypt.native.ts` and rerun `bun scripts/gen-crypto-selects.ts`.

View File

@@ -0,0 +1 @@
export { type ScryptOptions, scrypt } from "./scrypt.select.js";

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/scrypt.noble/scrypt.noble.js";

View File

@@ -0,0 +1,60 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { HashError } from "../../errors/hash-error/hash-error.js";
import { scrypt } from "./scrypt.js";
describe("scrypt — error-code conformance", () => {
it("throws SCRYPT_INVALID_N when N is not a power of two", {
timeout: 5000,
}, async () => {
let caught: unknown;
try {
await scrypt(new Uint8Array(8), new Uint8Array(8), {
N: 1000,
r: 8,
p: 1,
dkLen: 32,
});
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe("HASH.SCRYPT_INVALID_N");
expect((caught as HashError).context?.N).toBe(1000);
});
it("throws SCRYPT_INVALID_N when N < 2", { timeout: 5000 }, async () => {
for (const N of [0, 1, -1]) {
let caught: unknown;
try {
await scrypt(new Uint8Array(8), new Uint8Array(8), {
N,
r: 8,
p: 1,
dkLen: 32,
});
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe("HASH.SCRYPT_INVALID_N");
}
});
it("throws SCRYPT_INVALID_N when N is not an integer", {
timeout: 5000,
}, async () => {
let caught: unknown;
try {
await scrypt(new Uint8Array(8), new Uint8Array(8), {
N: 16.5,
r: 8,
p: 1,
dkLen: 32,
});
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(HashError);
expect((caught as HashError).code).toBe("HASH.SCRYPT_INVALID_N");
});
});

View File

@@ -0,0 +1,61 @@
import { fromHex } from "@suaveplan/crypto-utils/encoding";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { scrypt } from "./scrypt.js";
describe("scrypt — RFC 7914 known-answer tests", () => {
it("matches RFC 7914 known-answer test for N=1024 r=8 p=16", {
timeout: 30_000,
}, async () => {
// RFC 7914 §11 vector 2: P="password", S="NaCl", N=1024, r=8, p=16, dkLen=64
const expected = fromHex(
"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"
);
const out = await scrypt(
new TextEncoder().encode("password"),
new TextEncoder().encode("NaCl"),
{ N: 1024, r: 8, p: 16, dkLen: 64 }
);
expect(out).toEqual(expected);
});
it('matches RFC 7914 short vector P="" S="" N=16', {
timeout: 5000,
}, async () => {
// RFC 7914 §11 vector 1: P="", S="", N=16, r=1, p=1, dkLen=64
const expected = fromHex(
"77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906"
);
const out = await scrypt(new Uint8Array(), new Uint8Array(), {
N: 16,
r: 1,
p: 1,
dkLen: 64,
});
expect(out).toEqual(expected);
});
it("honors dkLen 32 and 128", { timeout: 30_000 }, async () => {
for (const dkLen of [32, 64, 128]) {
const out = await scrypt(
new TextEncoder().encode("pw"),
new TextEncoder().encode("salt"),
{ N: 16, r: 8, p: 1, dkLen }
);
expect(out.length).toBe(dkLen);
}
});
it("deterministic for fixed inputs", { timeout: 5000 }, async () => {
const a = await scrypt(
new TextEncoder().encode("pw"),
new TextEncoder().encode("salt"),
{ N: 16, r: 1, p: 1, dkLen: 32 }
);
const b = await scrypt(
new TextEncoder().encode("pw"),
new TextEncoder().encode("salt"),
{ N: 16, r: 1, p: 1, dkLen: 32 }
);
expect(a).toEqual(b);
});
});

View File

@@ -0,0 +1 @@
export { type ScryptOptions, scrypt } from "./scrypt.select/scrypt.select.js";

View File

@@ -0,0 +1 @@
export { sha256 } from "./sha256.native.js";

View File

@@ -0,0 +1,65 @@
# sha256.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha256.native.ts` is the native-backend placeholder for the SHA-256 primitive in `@suaveplan/crypto-hash`. It satisfies the same interface as `sha256.noble.ts` — accepting a `Uint8Array` and returning a `Uint8Array` — but throws `PrimitiveNotImplemented("sha256")` unconditionally.
This stub exists to keep the two-implementation primitive selector layout complete so that `crypto.config.ts` can be switched to `'native'` once an in-house SHA-256 implementation is ready. The generator script `gen-crypto-selects.ts` can point the selector at this file as soon as the implementation is wired up and passes the KAT harness in `@suaveplan/crypto-utils`.
## Features
- Satisfies the `sha256(data: Uint8Array): Uint8Array` interface required by the selector.
- Throws `PrimitiveNotImplemented("sha256")` from `@suaveplan/crypto-utils/errors`.
- Signals clearly that the native path is not yet active, preventing silent misuse.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils`.
## Usage: Basic Example
```ts
// This file is only active when crypto.config.ts sets sha256 to 'native'.
// Currently throws — the example shows what the call site looks like.
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
try {
const digest = sha256(new Uint8Array(32));
} catch (err) {
// PrimitiveNotImplemented: sha256
console.error(err);
}
```
## Usage: Advanced Example
```ts
// During Phase 2 development, replace the throw with a real implementation.
// The signature must remain: (data: Uint8Array): Uint8Array
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
// Wrap with a capability check to gracefully degrade when native is unavailable.
function tryNativeSha256(data: Uint8Array): Uint8Array | null {
try {
return sha256(data);
} catch {
return null;
}
}
```
## API Reference
### `sha256(_data: Uint8Array): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_data` | `Uint8Array` | Ignored. |
**Throws:** `PrimitiveNotImplemented("sha256")` — always.
## Implementation Notes
The `_data` parameter prefix signals to TypeScript and Biome that the argument is intentionally unused. The thrown `PrimitiveNotImplemented` error is defined in `@suaveplan/crypto-utils/errors` and extends `SuaveplanError` with a code that identifies which primitive is missing. When the native implementation is ready, this file's body is replaced with the real algorithm and the prefix removed from the parameter name; the selector file is then regenerated to point at this file. No other files change during that switch.

View File

@@ -0,0 +1,10 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
/**
* SHA-256 native impl — not yet implemented. Keeps the primitive
* selector layout complete so `crypto.config.ts` can be flipped to
* `'native'` once the in-house implementation lands (Phase 2).
*/
export function sha256(_data: Uint8Array): Uint8Array {
throw new PrimitiveNotImplemented("sha256");
}

View File

@@ -0,0 +1 @@
export { sha256 } from "./sha256.noble.js";

View File

@@ -0,0 +1,60 @@
# sha256.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha256.noble.ts` is the Noble-backed concrete implementation of the SHA-256 primitive in `@suaveplan/crypto-hash`. It wraps `sha256` from `@noble/hashes/sha2`, exposing the same one-shot signature (`Uint8Array → Uint8Array`) that the selector and public façade expect.
This file is one side of the two-implementation layout: `sha256.noble.ts` (production-ready, audited) and `sha256.native.ts` (placeholder stub). The selector file `sha256.select.ts` chooses between them at build time; callers never import this file directly.
## Features
- Delegates entirely to `@noble/hashes/sha2`: no custom SHA-2 arithmetic.
- Returns a fresh 32-byte `Uint8Array` on every call.
- Zero side effects: no global state, no lazy initialisation.
- Tree-shakeable: when the selector chooses the native backend, this file is unreachable and eliminated by the bundler.
## Usage: Basic Example
```ts
// Import via the stable public entry point, not this impl file directly.
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
const digest = sha256(new TextEncoder().encode("hello world"));
console.log(digest.length); // 32
```
## Usage: Advanced Example
```ts
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
import { toHex } from "@suaveplan/crypto-hash/encoding";
// Hash-then-sign pattern: hash the message before passing to an ECDSA signer.
function preHash(message: Uint8Array): string {
return toHex(sha256(message));
}
const hex = preHash(new TextEncoder().encode("sign me"));
console.log(hex.length); // 64 hex chars
```
## API Reference
### `sha256(data: Uint8Array): Uint8Array`
Computes the SHA-256 digest of `data`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence. |
**Returns:** Fresh 32-byte `Uint8Array`.
**Throws:** Never — Noble's SHA-256 accepts any `Uint8Array` input without error.
## Implementation Notes
The function is a single-expression wrapper: `return nobleSha256(data)`. Noble allocates a new output buffer internally on every call, so the returned array is always independent of the input. There is no incremental/streaming interface at this layer; callers that need to hash large inputs in chunks should concatenate first or use a higher-level streaming abstraction. This implementation has been audited by the Noble maintainers and passes NIST FIPS 180-4 / CAVP test vectors.

View File

@@ -0,0 +1,8 @@
import { sha256 as nobleSha256 } from "@noble/hashes/sha2";
/**
* SHA-256 noble-backed impl. One-shot digest → 32 bytes.
*/
export function sha256(data: Uint8Array): Uint8Array {
return nobleSha256(data);
}

View File

@@ -0,0 +1 @@
export { sha256 } from "./sha256.select/sha256.select";

View File

@@ -0,0 +1 @@
export * from "./sha256.select.js";

View File

@@ -0,0 +1,71 @@
# sha256.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha256.select.ts` is the build-time primitive selector for the SHA-256 implementation. It is a generated file that re-exports exactly one concrete implementation — either `sha256.noble.ts` (the audited `@noble/hashes` backend) or `sha256.native.ts` (the in-house native backend) — based on the setting in `crypto.config.ts`.
Consumers of `@suaveplan/crypto-hash` import SHA-256 through this file (via the public entry point) and never reference a concrete implementation directly. Swapping backends requires only regenerating this file; no import changes propagate to callers.
## Features
- Single-symbol re-export of `sha256(data: Uint8Array): Uint8Array`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts` at the package root: set `sha256` to `'noble'` or `'native'`.
- Keeps the public API stable across backend switches.
- Enables dead-code elimination: tree-shakers see a single concrete import, not a runtime branch.
## Usage: Basic Example
```ts
// Import via the public package entry point — never import the select file directly.
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
const digest = sha256(new TextEncoder().encode("hello"));
console.log(digest.length); // 32
```
## Usage: Advanced Example
```ts
import { sha256 } from "@suaveplan/crypto-hash/primitives/sha256/sha256";
// Hash a concatenation of chunks without an intermediate allocation.
function hashChunks(chunks: Uint8Array[]): Uint8Array {
let totalLength = 0;
for (const c of chunks) totalLength += c.length;
const combined = new Uint8Array(totalLength);
let offset = 0;
for (const c of chunks) {
combined.set(c, offset);
offset += c.length;
}
return sha256(combined);
}
const digest = hashChunks([
new Uint8Array([0x01, 0x02]),
new Uint8Array([0x03, 0x04]),
]);
console.log(digest.length); // 32
```
## API Reference
Re-exports the following from the active implementation:
### `sha256(data: Uint8Array): Uint8Array`
One-shot SHA-256 digest.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence to hash. |
**Returns:** Fresh 32-byte `Uint8Array` containing the SHA-256 digest.
## Implementation Notes
This file is marked `/* GENERATED — DO NOT EDIT */`. The generator script reads `crypto.config.ts`, resolves the implementation path for `sha256`, and writes a single `export * from` line. At present the default configuration selects the Noble backend because the native implementation is a placeholder that throws `PrimitiveNotImplemented`. The native backend will be activated once the in-house SHA-256 implementation passes the known-answer-test (KAT) harness in `@suaveplan/crypto-utils`.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/sha256.noble/sha256.noble.js";

View File

@@ -0,0 +1 @@
export { sha384 } from "./sha384.native.js";

View File

@@ -0,0 +1,64 @@
# sha384.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha384.native.ts` is the native-backend placeholder for the SHA-384 primitive in `@suaveplan/crypto-hash`. It exposes the same `sha384(data: Uint8Array): Uint8Array` signature as the Noble implementation but throws `PrimitiveNotImplemented("sha384")` unconditionally.
The file exists to keep the two-implementation primitive selector layout structurally complete for all primitives in the package. Having both `sha384.noble.ts` and `sha384.native.ts` present means the generator script can switch backends without any missing-file errors, and CI can validate the native stub's presence as part of the layout test.
## Features
- Satisfies `sha384(data: Uint8Array): Uint8Array` interface for the selector.
- Throws `PrimitiveNotImplemented("sha384")` unconditionally.
- Signals clearly that this path is not yet active.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets sha384 to 'native'.
// Currently throws unconditionally.
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
try {
sha384(new Uint8Array(32));
} catch (err) {
// PrimitiveNotImplemented: sha384
console.error(err);
}
```
## Usage: Advanced Example
```ts
// Pattern for graceful capability detection during native migration.
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
function isSha384Available(): boolean {
try {
sha384(new Uint8Array(1));
return true;
} catch {
return false;
}
}
```
## API Reference
### `sha384(_data: Uint8Array): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_data` | `Uint8Array` | Ignored. |
**Throws:** `PrimitiveNotImplemented("sha384")` — always.
## Implementation Notes
Like all native stubs in the package, the underscore prefix on `_data` suppresses unused-parameter warnings from TypeScript and Biome without requiring a lint-disable comment. When the in-house SHA-384 implementation is complete and has passed the NIST CAVP test suite (via the KAT harness in `@suaveplan/crypto-utils`), this file is updated with the real implementation and the selector is regenerated to point here.

View File

@@ -0,0 +1,5 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export function sha384(_data: Uint8Array): Uint8Array {
throw new PrimitiveNotImplemented("sha384");
}

View File

@@ -0,0 +1 @@
export { sha384 } from "./sha384.noble.js";

View File

@@ -0,0 +1,59 @@
# sha384.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha384.noble.ts` is the Noble-backed concrete implementation of the SHA-384 primitive in `@suaveplan/crypto-hash`. It delegates to `sha384` from `@noble/hashes/sha2`, exposing a minimal one-shot interface that the selector and public façade expect.
SHA-384 is the FIPS 180-4 truncation of SHA-512 to 48 bytes. It is preferred in high-throughput 64-bit environments over SHA-256 because SHA-512's 64-bit word arithmetic is natively fast on modern hardware, and the truncation gives a slight output-size reduction. This file is the production-quality side of the two-implementation layout.
## Features
- Delegates entirely to `@noble/hashes/sha2` SHA-384 implementation.
- Returns a fresh 48-byte `Uint8Array` on every call.
- Zero side effects; no global state or lazy initialisation.
- Tree-shakeable: unreachable when the native backend is selected.
## Usage: Basic Example
```ts
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
const digest = sha384(new TextEncoder().encode("hello"));
console.log(digest.length); // 48
```
## Usage: Advanced Example
```ts
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
import { toHex } from "@suaveplan/crypto-hash/encoding";
// Compute a SHA-384 content hash for cache busting.
function contentHash(payload: Uint8Array): string {
return `sha384-${toHex(sha384(payload))}`;
}
const tag = contentHash(new TextEncoder().encode("bundle contents"));
console.log(tag.startsWith("sha384-")); // true
```
## API Reference
### `sha384(data: Uint8Array): Uint8Array`
Computes the SHA-384 digest of `data`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence. |
**Returns:** Fresh 48-byte `Uint8Array`.
**Throws:** Never.
## Implementation Notes
The function body is `return nobleSha384(data)` — a single pass-through with no added logic. Noble allocates the output buffer internally. SHA-384 shares the SHA-512 compression function and differs only in the initial hash values and the truncation of the final state to 384 bits. The Noble implementation passes NIST CAVP vectors for SHA-384. No streaming interface is exposed at this layer.

View File

@@ -0,0 +1,8 @@
import { sha384 as nobleSha384 } from "@noble/hashes/sha2";
/**
* SHA-384 noble-backed impl. One-shot digest → 48 bytes.
*/
export function sha384(data: Uint8Array): Uint8Array {
return nobleSha384(data);
}

View File

@@ -0,0 +1 @@
export { sha384 } from "./sha384.select/sha384.select";

View File

@@ -0,0 +1 @@
export * from "./sha384.select.js";

View File

@@ -0,0 +1,60 @@
# sha384.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha384.select.ts` is the build-time primitive selector for the SHA-384 implementation. It is a generated file that re-exports exactly one concrete implementation — either `sha384.noble.ts` (the audited `@noble/hashes` backend) or `sha384.native.ts` (the in-house native backend) — based on the setting in `crypto.config.ts`.
Consumers import SHA-384 through the stable public entry point, which resolves here; they are insulated from backend changes. Regenerating this file via `bun scripts/gen-crypto-selects.ts` is the only step required to swap implementations.
## Features
- Single-symbol re-export of `sha384(data: Uint8Array): Uint8Array`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `sha384` to `'noble'` or `'native'`.
- Maintains a stable public API across backend switches.
- Enables static dead-code elimination of the unused backend.
## Usage: Basic Example
```ts
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
const digest = sha384(new TextEncoder().encode("hello"));
console.log(digest.length); // 48
```
## Usage: Advanced Example
```ts
import { sha384 } from "@suaveplan/crypto-hash/primitives/sha384/sha384";
// Compute a commitment to a serialised record.
function commit(record: Record<string, string>): Uint8Array {
const encoded = new TextEncoder().encode(JSON.stringify(record));
return sha384(encoded);
}
const hash = commit({ userId: "u_123", action: "login" });
console.log(hash.length); // 48
```
## API Reference
Re-exports the following from the active implementation:
### `sha384(data: Uint8Array): Uint8Array`
One-shot SHA-384 digest.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence to hash. |
**Returns:** Fresh 48-byte `Uint8Array` containing the SHA-384 digest.
## Implementation Notes
SHA-384 is a truncation of SHA-512 that shares the same 64-bit word operations and block size. It is preferred over SHA-256 in environments that process large amounts of data on 64-bit processors because the 64-bit arithmetic is natively fast, and the truncation provides a modest reduction in output size relative to SHA-512. The Noble backend delegates to `@noble/hashes/sha2` without any added overhead. The native stub throws `PrimitiveNotImplemented("sha384")` until the in-house implementation is ready and has passed the NIST CAVP test vectors.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/sha384.noble/sha384.noble.js";

View File

@@ -0,0 +1 @@
export { sha512 } from "./sha512.native.js";

View File

@@ -0,0 +1,68 @@
# sha512.native
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha512.native.ts` is the native-backend placeholder for the SHA-512 primitive in `@suaveplan/crypto-hash`. It satisfies the `sha512(data: Uint8Array): Uint8Array` interface but throws `PrimitiveNotImplemented("sha512")` unconditionally until an in-house implementation is ready.
This file pairs with `sha512.noble.ts` to complete the two-implementation selector layout. Its presence ensures the build system, generator script, and CI layout tests can treat SHA-512 consistently with every other primitive in the package.
## Features
- Satisfies the `sha512(data: Uint8Array): Uint8Array` interface.
- Throws `PrimitiveNotImplemented("sha512")` unconditionally.
- Provides a clear, typed error that identifies the missing primitive by name.
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
## Usage: Basic Example
```ts
// Active only when crypto.config.ts sets sha512 to 'native'.
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
try {
sha512(new Uint8Array(64));
} catch (err) {
// PrimitiveNotImplemented: sha512
console.error(err);
}
```
## Usage: Advanced Example
```ts
// Feature detection for environments that may or may not have native SHA-512.
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
async function withFallback(data: Uint8Array): Promise<Uint8Array> {
try {
return sha512(data);
} catch {
// Fall back to WebCrypto if native primitive is unavailable.
const buffer = await crypto.subtle.digest("SHA-512", data);
return new Uint8Array(buffer);
}
}
```
## API Reference
### `sha512(_data: Uint8Array): Uint8Array`
Placeholder that always throws.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_data` | `Uint8Array` | Ignored. |
**Throws:** `PrimitiveNotImplemented("sha512")` — always.
## Implementation Notes
The underscore prefix on `_data` marks the parameter as intentionally unused without a lint suppression comment. When the in-house SHA-512 implementation is complete, this file is updated with the real algorithm, the underscore removed, and the selector regenerated. The `PrimitiveNotImplemented` error class in `@suaveplan/crypto-utils/errors` extends `SuaveplanError`, so it participates in the full error serialisation and cause-chain system.
### Status
Placeholder native stub — throws `PrimitiveNotImplemented` until a `node:crypto` / WebCrypto `SHA-512` bridge is wired up. Today the `sha512.select` selector always routes through Noble; the SHA-512 KAT harness in `@suaveplan/crypto-utils` pins the output against NIST FIPS 180-4 test vectors so the production path is fully covered.

View File

@@ -0,0 +1,5 @@
import { PrimitiveNotImplemented } from "@suaveplan/crypto-utils/errors";
export function sha512(_data: Uint8Array): Uint8Array {
throw new PrimitiveNotImplemented("sha512");
}

View File

@@ -0,0 +1 @@
export { sha512 } from "./sha512.noble.js";

View File

@@ -0,0 +1,62 @@
# sha512.noble
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha512.noble.ts` is the Noble-backed concrete implementation of the SHA-512 primitive in `@suaveplan/crypto-hash`. It wraps `sha512` from `@noble/hashes/sha2`, producing 64-byte digests with the same one-shot interface expected by the selector and public façade.
SHA-512 is the widest SHA-2 variant and is used as the underlying hash in HMAC-SHA-512, HKDF-SHA-512, and PBKDF2-SHA-512. Its 64-bit word operations run natively fast on 64-bit processors and provide the largest security margin in the SHA-2 family. This file is the production-ready side of the two-implementation layout.
## Features
- Delegates entirely to `@noble/hashes/sha2` SHA-512 implementation.
- Returns a fresh 64-byte `Uint8Array` on every call.
- Zero side effects; no global state or lazy initialisation.
- Tree-shakeable: unreachable when the native backend is selected.
## Usage: Basic Example
```ts
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
const digest = sha512(new TextEncoder().encode("hello"));
console.log(digest.length); // 64
```
## Usage: Advanced Example
```ts
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
import { toHex } from "@suaveplan/crypto-hash/encoding";
// Produce a 512-bit content fingerprint for audit logging.
function auditDigest(payload: Uint8Array, label: string): string {
const combined = new Uint8Array(payload.length + label.length);
combined.set(new TextEncoder().encode(label), 0);
combined.set(payload, label.length);
return toHex(sha512(combined));
}
const hex = auditDigest(new Uint8Array([0x01]), "event:");
console.log(hex.length); // 128 hex chars
```
## API Reference
### `sha512(data: Uint8Array): Uint8Array`
Computes the SHA-512 digest of `data`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence. |
**Returns:** Fresh 64-byte `Uint8Array`.
**Throws:** Never.
## Implementation Notes
The function is `return nobleSha512(data)` — a direct pass-through. Noble handles buffer allocation internally; the returned array is never an alias of the input. SHA-512 applies 80 rounds of compression over a 1024-bit block, producing a 512-bit state, all in 64-bit arithmetic. The Noble implementation is audited and passes NIST FIPS 180-4 CAVP vectors. No streaming interface is provided at this layer.

View File

@@ -0,0 +1,8 @@
import { sha512 as nobleSha512 } from "@noble/hashes/sha2";
/**
* SHA-512 noble-backed impl. One-shot digest → 64 bytes.
*/
export function sha512(data: Uint8Array): Uint8Array {
return nobleSha512(data);
}

View File

@@ -0,0 +1 @@
export { sha512 } from "./sha512.select/sha512.select";

View File

@@ -0,0 +1 @@
export * from "./sha512.select.js";

View File

@@ -0,0 +1,60 @@
# sha512.select
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`sha512.select.ts` is the build-time primitive selector for the SHA-512 implementation. It is a generated file that re-exports exactly one concrete implementation — either `sha512.noble.ts` (the audited `@noble/hashes` backend) or `sha512.native.ts` — based on the active entry in `crypto.config.ts`.
SHA-512 produces 64-byte digests and is the largest of the SHA-2 family. It is used internally by HMAC-SHA-512, HKDF-SHA-512, and PBKDF2-SHA-512 within this package, and is exposed as a standalone primitive for callers that need a wider security margin than SHA-256 provides.
## Features
- Single-symbol re-export of `sha512(data: Uint8Array): Uint8Array`.
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
- Controlled by `crypto.config.ts`: set `sha512` to `'noble'` or `'native'`.
- Maintains a stable public API across backend switches.
- Enables static dead-code elimination of the unused backend.
## Usage: Basic Example
```ts
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
const digest = sha512(new TextEncoder().encode("hello"));
console.log(digest.length); // 64
```
## Usage: Advanced Example
```ts
import { sha512 } from "@suaveplan/crypto-hash/primitives/sha512/sha512";
import { toHex } from "@suaveplan/crypto-hash/encoding";
// Compute a hex-encoded SHA-512 fingerprint of a binary blob.
function fingerprint(blob: Uint8Array): string {
return `sha512:${toHex(sha512(blob))}`;
}
const fp = fingerprint(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
console.log(fp.startsWith("sha512:")); // true
```
## API Reference
Re-exports the following from the active implementation:
### `sha512(data: Uint8Array): Uint8Array`
One-shot SHA-512 digest.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | Arbitrary byte sequence to hash. |
**Returns:** Fresh 64-byte `Uint8Array` containing the SHA-512 digest.
## Implementation Notes
SHA-512 uses 80 rounds of 64-bit operations on a 1024-bit block, giving it an internal state twice the width of SHA-256. On 64-bit hardware this is as fast or faster than SHA-256 for large inputs. The Noble backend passes NIST CAVP and RFC 6234 test vectors without modification. The native stub throws `PrimitiveNotImplemented("sha512")` and will be activated once the in-house implementation lands with full KAT coverage. The selector file itself is a generated artefact; the generator is idempotent and safe to re-run.

View File

@@ -0,0 +1,2 @@
/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */
export * from "../impl/sha512.noble/sha512.noble.js";