Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: crypto-hash (crypto-completion)
**Package**: `@suaveplan/crypto-hash`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: scrypt KDF primitive
The package SHALL expose `scrypt(password, salt, options)` from `primitives/scrypt/scrypt.ts`, where `options` carries `N` (CPU/memory cost, must be a power of two ≥ 2), `r` (block size), `p` (parallelisation), and `dkLen` (output length in bytes). The implementation SHALL match RFC 7914 byte-for-byte and SHALL use the `@noble/hashes/scrypt::scryptAsync` engine in the noble selector. Output is a `Uint8Array` of length `dkLen`.
#### Scenario: matches RFC 7914 known-answer test
- **WHEN** `scrypt("password", "NaCl", { N: 1024, r: 8, p: 16, dkLen: 64 })` is called
- **THEN** the output equals the RFC 7914 §11 KAT for those parameters byte-for-byte
#### Scenario: invalid N rejected
- **WHEN** `scrypt(pw, salt, { N: 1000, r: 8, p: 1, dkLen: 32 })` is called (1000 is not a power of two)
- **THEN** it throws `SuaveplanError` with code `CRYPTO_HASH.SCRYPT_INVALID_N` and `context.N === 1000`
**Verified by:** `packages/crypto/crypto-hash/src/primitives/scrypt/scrypt.test.ts::"matches RFC 7914 known-answer test for N=1024 r=8 p=16"` AND `packages/crypto/crypto-hash/src/primitives/scrypt/scrypt.spec-conformance.test.ts::"throws SCRYPT_INVALID_N when N is not a power of two"`
---
### Requirement: blake2b hash primitive
The package SHALL expose `blake2b(message, options)` from `primitives/blake2b/blake2b.ts`, where `options` carries `dkLen` (1..64), optional `key` (≤ 64 bytes), optional `salt` (16 bytes), and optional `personalization` (16 bytes). The implementation SHALL match RFC 7693 byte-for-byte.
#### Scenario: matches RFC 7693 known-answer test
- **WHEN** `blake2b("abc", { dkLen: 64 })` is called
- **THEN** the output equals the RFC 7693 §B Appendix KAT for the input "abc" byte-for-byte
#### Scenario: dkLen > 64 rejected
- **WHEN** `blake2b(msg, { dkLen: 65 })` is called
- **THEN** it throws `SuaveplanError` with code `CRYPTO_HASH.BLAKE2B_OUTPUT_TOO_LONG` and `context.requestedLength === 65`
#### Scenario: key longer than 64 bytes rejected
- **WHEN** `blake2b(msg, { dkLen: 32, key: 65-byte-buffer })` is called
- **THEN** it throws `SuaveplanError` with code `CRYPTO_HASH.BLAKE2B_KEY_TOO_LONG` and `context.keyLength === 65`
**Verified by:** `packages/crypto/crypto-hash/src/primitives/blake2b/blake2b.test.ts::"matches RFC 7693 known-answer test for input \"abc\""` AND `packages/crypto/crypto-hash/src/primitives/blake2b/blake2b.spec-conformance.test.ts::"throws BLAKE2B_OUTPUT_TOO_LONG when dkLen exceeds 64"` AND `packages/crypto/crypto-hash/src/primitives/blake2b/blake2b.spec-conformance.test.ts::"throws BLAKE2B_KEY_TOO_LONG when key exceeds 64 bytes"`
---
### Requirement: blake2b password hashing and verification
The package SHALL expose `hashPasswordBlake2b(password, options)` returning a stored hash in the format `$blake2b$v=1$cf=<costFactor>$<saltHex>$<hashHex>`, where `costFactor` controls iteration count (default 14, doubling each level) and a 16-byte random salt is generated when not supplied. It SHALL also expose `verifyPasswordBlake2b(storedHash, password)` returning `true` when the password verifies and `false` otherwise. Comparison SHALL be constant-time over the hash bytes.
#### Scenario: hash and verify round-trip
- **WHEN** `hashPasswordBlake2b("correct horse battery staple")` produces `stored` and `verifyPasswordBlake2b(stored, "correct horse battery staple")` is called
- **THEN** the result is `true`
#### Scenario: wrong password verifies as false
- **WHEN** `verifyPasswordBlake2b(stored, "wrong password")` is called
- **THEN** the result is `false` (no exception thrown)
#### Scenario: salt is fresh per call
- **WHEN** `hashPasswordBlake2b("same password")` is called twice
- **THEN** the two stored hashes are not byte-equal (different salts → different outputs)
#### Scenario: malformed stored hash rejected
- **WHEN** `verifyPasswordBlake2b("not-a-valid-format", pw)` is called
- **THEN** it throws `SuaveplanError` with code `CRYPTO_HASH.BLAKE2B_PASSWORD_FORMAT_INVALID`
**Verified by:** `packages/crypto/crypto-hash/src/mac/blake2b-password/blake2b-password.test.ts::"hash and verify round-trip with same password"` AND `packages/crypto/crypto-hash/src/mac/blake2b-password/blake2b-password.test.ts::"verify returns false for wrong password"` AND `packages/crypto/crypto-hash/src/mac/blake2b-password/blake2b-password.test.ts::"salt is fresh per call so identical inputs hash differently"` AND `packages/crypto/crypto-hash/src/mac/blake2b-password/blake2b-password.spec-conformance.test.ts::"throws BLAKE2B_PASSWORD_FORMAT_INVALID for malformed stored hash"`
<!-- markdownlint-enable MD024 -->