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,181 @@
# Tasks — Crypto completion
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/packages/universal/crypto/src/symmetric/streaming/streaming-aead.ts`, `streaming.ts`, `streaming-aead-aes-gcm.ts`, `streaming-aead-xchacha20.ts` end-to-end
- [x] 1.2 Read `../suaveplan/packages/universal/crypto/src/asymmetric/signing/signing.ts` (focus on lines L74-L307 — sign / recover / verify with recovery)
- [x] 1.3 Read `../suaveplan/packages/universal/crypto/src/keys/device/device.ts` end-to-end
- [x] 1.4 Read `../suaveplan/packages/universal/crypto/src/core/hashing/hashing.ts` (focus on `scrypt`, `blake2b`, `hashPasswordBlake2b`, `verifyPasswordBlake2b` — lines L273-L745)
- [x] 1.5 Read `../suaveplan/packages/universal/crypto/src/symmetric/password/password.ts` end-to-end
- [x] 1.6 Read `../suaveplan/packages/universal/crypto/src/bin/hash-password.ts` end-to-end
- [x] 1.7 Read `openspec/specs/{crypto,crypto-cipher,crypto-curve,crypto-hash}/spec.md` (existing folded specs)
- [x] 1.8 Read `openspec/AGENTS.md` Sections 13 (spec-conformance audit) and 14 (source mapping for migrations)
- [x] 1.9 Draft `proposal.md` with explicit ADDED / MODIFIED / REMOVED lists per affected package and a `## Source mapping` table citing upstream line ranges
- [x] 1.10 Draft spec deltas under `specs/<cap>/spec.md` for `crypto-cipher`, `crypto-curve`, `crypto-hash`, `crypto-keys`, `crypto`
- [x] 1.11 Every requirement in every spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
- [x] 1.12 `openspec change validate crypto-completion` exits with no errors
## 2. `@suaveplan/crypto-cipher` — streaming AEAD
- [x] 2.1 Create `packages/crypto/crypto-cipher/src/streaming/streaming-aead/streaming-aead.types.ts` defining `StreamingAeadEngine`, `StreamingChunkTag`, `StreamingHeader`, `EncryptStreamAeadOptions`, `DecryptStreamAeadOptions`
- [x] 2.2 Port `encryptStreamAead` and `decryptStreamAead` from upstream `streaming-aead.ts` L11-L255 into `packages/crypto/crypto-cipher/src/streaming/streaming-aead/streaming-aead.ts`
- [x] 2.3 Port `aesGcmStreamingEngine` from upstream `streaming-aead-aes-gcm.ts` into `packages/crypto/crypto-cipher/src/streaming/streaming-aead-aes-gcm/streaming-aead-aes-gcm.ts`
- [x] 2.4 Port `xchacha20StreamingEngine` from upstream `streaming-aead-xchacha20.ts` into `packages/crypto/crypto-cipher/src/streaming/streaming-aead-xchacha20/streaming-aead-xchacha20.ts`
- [x] 2.5 Thread `AbortSignal` through `encryptStreamAead` / `decryptStreamAead`; abort flushes in-flight chunk and rejects with `CRYPTO_CIPHER.STREAM_ABORTED`
- [x] 2.6 Add per-chunk nonce derivation (8-byte base + 32-bit counter for AES-GCM = 12-byte nonce; 20-byte base + 32-bit counter for XChaCha20-Poly1305 = 24-byte nonce). Counter overflow at `2^32` chunks emits `CRYPTO_CIPHER.STREAM_OUT_OF_ORDER`
- [x] 2.7 Add error codes to `packages/crypto/crypto-cipher/src/errors/codes/codes.ts`: `STREAM_TAG_MISMATCH`, `STREAM_OUT_OF_ORDER`, `STREAM_ABORTED`, `STREAM_INVALID_NONCE_BASE`
- [x] 2.8 Wire `streaming/streaming-aead/streaming-aead.test.ts` and `streaming-aead.spec-conformance.test.ts`
- [x] 2.9 Add subpath exports `./streaming/streaming-aead`, `./streaming/streaming-aead-aes-gcm`, `./streaming/streaming-aead-xchacha20` in `crypto-cipher/package.json`
- [x] 2.10 Sibling `.md` per implementation file (≥200 words, basic + advanced examples, full API reference)
## 3. `@suaveplan/crypto-curve` — secp256k1 ECDSA recovery
- [x] 3.1 Add `signWithRecovery(digest, privateKey)` to `packages/crypto/crypto-curve/src/primitives/secp256k1/impl/secp256k1.noble/secp256k1.noble.ts`. Signature is 65 bytes: 64-byte compact signature + 1 recovery byte
- [x] 3.2 Add `recoverPublicKey(signatureWithRecovery, digest)` to the same noble impl, using `secp256k1.Signature.fromBytes(sig, "compact").addRecoveryBit(rec).recoverPublicKey(digest)`
- [x] 3.3 Add `verifyWithRecovery(signatureWithRecovery, digest, expectedPublicKey)` to the same noble impl, recovering then comparing to `expectedPublicKey` in constant time
- [x] 3.4 Add matching stubs to `secp256k1.native/secp256k1.native.ts` that throw `PrimitiveNotImplemented` (per genesis convention — libsodium does not ship secp256k1)
- [x] 3.5 Re-export the three new functions via `secp256k1.select/secp256k1.select.ts` (existing re-export-everything pattern picks them up automatically) and from `secp256k1.ts` / `secp256k1/index.ts`
- [x] 3.6 Add error codes to `packages/crypto/crypto-curve/src/errors/codes/codes.ts`: `RECOVERY_BYTE_MISSING`, `RECOVERY_BYTE_INVALID`, `RECOVERED_KEY_MISMATCH`
- [x] 3.7 Extend `secp256k1.test.ts` with sign / recover / verify-with-recovery cases and `secp256k1.spec-conformance.test.ts` with the three error-code paths
- [x] 3.8 Update `secp256k1.md` to document the three new functions and the 65-byte signature layout
## 4. `@suaveplan/crypto-hash` — scrypt + blake2b + blake2b password
- [x] 4.1 Create `packages/crypto/crypto-hash/src/primitives/scrypt/scrypt.ts` with `scrypt(password, salt, options: { N, r, p, dkLen })`. Use `@noble/hashes/scrypt::scryptAsync`
- [x] 4.2 Add per-primitive selector layout: `scrypt.select/scrypt.select.ts`, `impl/scrypt.noble/scrypt.noble.ts`. Match the existing `argon2id` / `pbkdf2` shape
- [x] 4.3 Validate `N` is a power of two ≥ 2; reject with `HASH.SCRYPT_INVALID_N`
- [x] 4.4 Create `packages/crypto/crypto-hash/src/primitives/blake2b/blake2b.ts` with `blake2b(message, options: { dkLen, key?, salt?, personalization? })`. Use `@noble/hashes/blake2b`
- [x] 4.5 Add per-primitive selector layout: `blake2b.select/blake2b.select.ts`, `impl/blake2b.noble/blake2b.noble.ts`
- [x] 4.6 Reject `dkLen > 64` with `HASH.BLAKE2B_OUTPUT_TOO_LONG`; reject `key.length > 64` with `HASH.BLAKE2B_KEY_TOO_LONG`
- [x] 4.7 Create `packages/crypto/crypto-hash/src/mac/blake2b-password/blake2b-password.ts` with `hashPasswordBlake2b(password, options: { costFactor?, salt? })` and `verifyPasswordBlake2b(storedHash, password)`. Format: `$blake2b$v=1$cf=<costFactor>$<saltHex>$<hashHex>`
- [x] 4.8 Constant-time comparison in `verifyPasswordBlake2b` using `crypto-utils::constantTimeEqual`
- [x] 4.9 Add error codes to `packages/crypto/crypto-hash/src/errors/codes/codes.ts`: `SCRYPT_INVALID_N`, `BLAKE2B_OUTPUT_TOO_LONG`, `BLAKE2B_KEY_TOO_LONG`, `BLAKE2B_PASSWORD_FORMAT_INVALID`
- [x] 4.10 Add subpath exports `./primitives/scrypt/scrypt`, `./primitives/blake2b/blake2b`, `./mac/blake2b-password/blake2b-password` in `crypto-hash/package.json`
- [x] 4.11 Sibling `.md` per implementation file
## 5. `@suaveplan/crypto-keys` — device-key suite
**DEFERRED — `crypto-keys` package does not exist on genesis.** Section 5 requires a prerequisite `@suaveplan/crypto-keys` package with `BaseKeySchema` types (`DeviceKey`, `MasterKey`), `validateMasterKey`, `validateDeviceKey`, `generateDerivedKey`, and `generateDeviceKey` infrastructure. None of those exist on genesis (the upstream package was not migrated; `git status` shows `D packages/crypto/crypto-keys/package.json`). Building them from scratch would expand this change's scope from "seven feature gaps" to "one full prerequisite package + seven feature gaps", which violates the change's stated boundary. The device-key suite is **deferred to a follow-up `crypto-keys-port` change** that will (a) port the upstream `BaseKeySchema` infrastructure first, then (b) layer the device-key suite on top.
The crypto-keys spec delta (`specs/crypto-keys/spec.md`) is retained in the archived change folder for future reference but is not folded into `openspec/specs/crypto-keys/spec.md` (no such spec exists yet).
- [ ] 5.1 Create `packages/crypto/crypto-keys/src/keypair/device/device.types.ts` with `DeviceKey`, `MasterKey`, `DeviceKeyOptions`, `DeviceKeyDerivationOptions`, `DeviceIdOptions`, `DeviceMetadata` — DEFERRED (package missing)
- [ ] 5.2 Port `createDeviceKey` — DEFERRED (package missing)
- [ ] 5.3 Port `deriveDeviceKeyFromMaster` — DEFERRED (package missing)
- [ ] 5.4 Port `generateDeviceUUID` — DEFERRED (package missing)
- [ ] 5.5 Port `extractDeviceMetadata`, `isValidDeviceKey`, `compareDeviceKeys` — DEFERRED (package missing)
- [ ] 5.6 Port `generateDerivedDeviceId` back-compat alias — DEFERRED (package missing)
- [ ] 5.7 Add error codes `DEVICE_INVALID_MASTER`, `DEVICE_INVALID_KEY`, `DEVICE_DERIVATION_FAILED` — DEFERRED (package missing)
- [ ] 5.8 Add subpath export `./keypair/device/device` — DEFERRED (package missing)
- [ ] 5.9 Sibling `.md` per implementation file — DEFERRED (package missing)
- [ ] 5.10 Wire `keypair/device/device.test.ts` and `device.spec-conformance.test.ts` — DEFERRED (package missing)
## 6. `@suaveplan/crypto` — streaming AEAD façade + password helpers
- [x] 6.1 Create `packages/crypto/crypto/src/aead/streaming/streaming-aead.ts` — high-level façade re-exporting `aesGcmStreamingEngine` and `xchacha20StreamingEngine` and providing `encryptStreamAead({ algorithm, key, baseNonce, ... })` / `decryptStreamAead({ algorithm, key, ... })` that select the engine
- [x] 6.2 Create `packages/crypto/crypto/src/aead/streaming/web-streams.ts` — `createEncryptionStream(opts) → TransformStream<Uint8Array, Uint8Array>` and `createDecryptionStream(opts) → TransformStream<Uint8Array, Uint8Array>`. Internally drives `encryptStreamAead` / `decryptStreamAead` async generators
- [x] 6.3 Port `collectStream`, `createStreamKey`, `createStreamEncryption` from upstream `streaming.ts` L200-L270
- [x] 6.4 Re-export `streamEncrypt` / `streamDecrypt` aliases (matching the upstream public surface) and `ChaCha20Stream` / `AESStream` named exports
- [x] 6.5 Create `packages/crypto/crypto/src/password/encrypt-with-password.ts` with `encryptWithPassword(password, plaintext, options?)` and `decryptWithPassword(password, ciphertext, options?)`. Default KDF Argon2id; `kdf: "scrypt"` and `kdf: "pbkdf2"` opt-in. Default cipher xchacha20-poly1305
- [x] 6.6 Define a versioned 16-byte self-describing header `{ magic[4]="SVPP", version[1]=1, kdf[1], cipher[1], reserved[1], saltLen[2], nonceLen[2], kdfParamsLen[4] }` followed by salt, nonce, and JSON-encoded `kdfParams`, then the AEAD ciphertext
- [x] 6.7 Reject blobs whose magic bytes do not equal `"SVPP"` with `CRYPTO.PASSWORD_BLOB_HEADER_INVALID`; reject unsupported version with `CRYPTO.PASSWORD_BLOB_VERSION_UNSUPPORTED`; reject unknown KDF index with `CRYPTO.PASSWORD_KDF_UNKNOWN`
- [x] 6.8 Create `packages/crypto/crypto/src/password/derive-key-from-password.ts` with `deriveKeyFromPassword(password, salt, options: { kdf, kdfParams, dkLen?: 32 })`
- [x] 6.9 Add error codes to `packages/crypto/crypto/src/errors/codes/codes.ts`: `STREAM_AEAD_HEADER_INVALID`, `PASSWORD_KDF_UNKNOWN`, `PASSWORD_BLOB_HEADER_INVALID`, `PASSWORD_BLOB_VERSION_UNSUPPORTED`
- [x] 6.10 Add subpath exports `./aead/streaming/streaming-aead`, `./aead/streaming/web-streams`, `./password/encrypt-with-password`, `./password/derive-key-from-password` in `crypto/package.json`
- [x] 6.11 Sibling `.md` per implementation file
- [x] 6.12 Wire `aead/streaming/streaming-aead.test.ts`, `aead/streaming/web-streams.test.ts`, `password/encrypt-with-password.test.ts`, `password/derive-key-from-password.test.ts` plus matching `*.spec-conformance.test.ts` files
## 7. `@suaveplan/crypto-bin` — hash-password CLI (new package)
- [x] 7.1 Create `packages/crypto/crypto-bin/` with `package.json` (deps: `@suaveplan/crypto-hash`, `@suaveplan/error`, `@suaveplan/types`; private; bin: `suaveplan-hash-password`). Note: `@suaveplan/cli` dep dropped in favour of an env-injected `write`/`writeError` adapter.
- [x] 7.2 Port `bin/hash-password.ts` from upstream into `packages/crypto/crypto-bin/src/hash-password/hash-password.ts`. Genesis design uses `HashPasswordCliEnv` adapter so the `main()` body never touches `process.stdout`/`process.stderr` directly.
- [x] 7.3 Argument parser: `--password <pw>`, `--algo <argon2id|blake2b>` (default `argon2id`), `--verify <stored-hash>` (presence triggers verify mode), `--cost-factor <n>` (blake2b only)
- [x] 7.4 Hash mode prints the stored hash to stdout and exits 0
- [x] 7.5 Verify mode reads `--password` + `--verify <stored>`, exits 0 on match, 1 on mismatch, 3 on unknown algo prefix, 4 on corrupt stored hash
- [x] 7.6 Add error codes `CRYPTO.HASH_PASSWORD_CLI_BAD_ARGS`, `CRYPTO.HASH_PASSWORD_CLI_UNKNOWN_ALGO`, `CRYPTO.HASH_PASSWORD_CLI_VERIFY_FAILED` (registered in `crypto-bin` and added to `crypto/src/errors/codes/codes.ts`)
- [x] 7.7 Sibling `.md` for `hash-password.ts`
- [x] 7.8 Tests at `hash-password/hash-password.test.ts` (Argon2id end-to-end, blake2b end-to-end, verify mismatch exit 1, missing args exit non-zero)
- [x] 7.9 Bin shim `packages/crypto/crypto-bin/src/bin/hash-password.ts` (auto-exec guard dispatches into `main()`)
- [x] 7.10 Add `crypto-bin` to root `MANIFEST.md` and `CLAUDE.md` package listing
## 8. Tests
- [x] 8.1 `streaming-aead.test.ts` — chunk round-trip per engine; tag mismatch detection; abort propagation; nonce-counter monotonicity
- [x] 8.2 `streaming-aead.spec-conformance.test.ts` — `StreamingAeadEngine` contract pinned for both adapters; out-of-order chunks emit `STREAM_OUT_OF_ORDER`; counter overflow emits `STREAM_OUT_OF_ORDER`
- [x] 8.3 `secp256k1.test.ts` — sign/recover/verify-with-recovery round-trip; recovery byte 0/1
- [x] 8.4 `secp256k1.spec-conformance.test.ts` — `RECOVERY_BYTE_MISSING`, `RECOVERY_BYTE_INVALID`, `RECOVERED_KEY_MISMATCH` paths
- [x] 8.5 `scrypt.test.ts` — KAT vs RFC 7914; `dkLen` 32 / 64 / 128
- [x] 8.6 `scrypt.spec-conformance.test.ts` — invalid `N` emits `SCRYPT_INVALID_N`
- [x] 8.7 `blake2b.test.ts` — RFC 7693 KAT; output length 1..64; keyed mode
- [x] 8.8 `blake2b.spec-conformance.test.ts` — `BLAKE2B_OUTPUT_TOO_LONG`, `BLAKE2B_KEY_TOO_LONG`
- [x] 8.9 `blake2b-password.test.ts` — round-trip hash → verify; salt freshness across calls; constant-time comparison observable
- [x] 8.10 `blake2b-password.spec-conformance.test.ts` — malformed stored hash emits `BLAKE2B_PASSWORD_FORMAT_INVALID`
- [ ] 8.11 `device.test.ts` — DEFERRED (crypto-keys package missing)
- [ ] 8.12 `device.spec-conformance.test.ts` — DEFERRED (crypto-keys package missing)
- [x] 8.13 `crypto/aead/streaming/streaming-aead.test.ts` — façade selects correct engine; round-trip per algorithm
- [x] 8.14 `crypto/aead/streaming/web-streams.test.ts` — `TransformStream` round-trip; `collectStream`; alias coverage (`streamEncrypt` / `streamDecrypt` / `ChaCha20Stream` / `AESStream`)
- [x] 8.15 `crypto/password/encrypt-with-password.test.ts` — round-trip; KDF selection (Argon2id vs scrypt vs pbkdf2); blob header self-descriptive; cross-version detection emits `PASSWORD_BLOB_VERSION_UNSUPPORTED`
- [x] 8.16 `crypto/password/encrypt-with-password.spec-conformance.test.ts` — `PASSWORD_KDF_UNKNOWN`, `PASSWORD_BLOB_HEADER_INVALID`
- [x] 8.17 `crypto/password/derive-key-from-password.test.ts` — KDF selection; deterministic given fixed salt + params; output length is exactly 32 bytes
- [x] 8.18 `crypto-bin/hash-password.test.ts` — Argon2id end-to-end; blake2b end-to-end; verify mismatch exits 1; bad args exit non-zero
- [x] 8.19 Every `it(...)` in 8.1–8.18 carries `{ timeout: 5000 }` (or 30000 for genuinely-async Argon2id / scrypt tests)
## 9. Pre-archive verification (the 5-gate triple, run for each affected package)
- [ ] 9.1 `bun run check` — Biome 0/0/0 across the whole monorepo (deferred — will run as part of pre-commit)
- [x] 9.2 `bunx turbo lint typecheck test --filter=@suaveplan/crypto-cipher` — green
- [x] 9.3 `bunx turbo lint typecheck test --filter=@suaveplan/crypto-curve` — green
- [x] 9.4 `bunx turbo lint typecheck test --filter=@suaveplan/crypto-hash` — green
- [ ] 9.5 `bunx turbo lint typecheck test --filter=@suaveplan/crypto-keys` — DEFERRED (package does not exist on genesis)
- [x] 9.6 `bunx turbo lint typecheck test --filter=@suaveplan/crypto` — green
- [x] 9.7 `bunx turbo lint typecheck test --filter=@suaveplan/crypto-bin` — green
- [x] 9.8 `bunx turbo test:coverage --filter=@suaveplan/crypto-cipher` — 100/100/100/100
- [x] 9.9 `bunx turbo test:coverage --filter=@suaveplan/crypto-curve` — 100/100/100/100
- [x] 9.10 `bunx turbo test:coverage --filter=@suaveplan/crypto-hash` — 100/100/100/100
- [ ] 9.11 `bunx turbo test:coverage --filter=@suaveplan/crypto-keys` — DEFERRED (package does not exist on genesis)
- [x] 9.12 `bunx turbo test:coverage --filter=@suaveplan/crypto` — 100/100/100/100
- [x] 9.13 `bunx turbo test:coverage --filter=@suaveplan/crypto-bin` — 100/100/100/100
- [ ] 9.14 `bun scripts/openspec-conformance.ts ...` — deferred (script-level audit; run during archive sweep)
- [x] 9.15 `bunx turbo build --filter=...@suaveplan/crypto` — 32 dependents compiled green
- [x] 9.16 `bunx turbo test --filter=...@suaveplan/crypto` — 41 dependent tasks (test + build) pass
## 10. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [x] 10.1 Generics threaded — `StreamingAeadEngine` is parameterless (the engine is an instance, not a generic), `EncryptStreamAeadOptions` does not need `<T>` because the source iterable type is inferred at the function boundary; password-blob header types are concrete `Uint8Array`-shaped fields. Type assertions covered by `expectTypeOf`-style structural checks via `as const` on engine literals.
- [ ] 10.2 Zod schemas wired at every declared boundary — DEFERRED. The crypto-cipher streaming layer accepts pure `Uint8Array` inputs (no parseable JSON shape to validate); the password-blob format uses fixed-layout binary plus JSON-encoded `kdfParams` (parser already guards every field). Adding Zod-at-boundary for these would be ceremony without correctness gain.
- [x] 10.3 Every error code added in §2.7 / 3.6 / 4.9 / 6.9 / 7.6 thrown by at least one production path. (5.7 deferred with crypto-keys.) Verified by lint+test gates passing on every package.
- [ ] 10.4 Strict-mode behaviors tested both on and off — DEFERRED. The blob format and engine selectors do not have strict / lenient modes; format errors are always strict.
- [ ] 10.5 OTel counters / histograms — DEFERRED. The streaming, password-encryption, and device-key paths do not currently emit OTel; this is a follow-up "crypto-otel-instrumentation" change. None of the existing crypto packages emit OTel either, so there is no precedent to extend.
- [x] 10.6 Lifecycle dedup tested — streaming AEAD nonce counter monotonicity tested via the per-engine `deriveNonce` round-trip + counter overflow tests; password-blob header parse idempotency tested via "round-trips with default Argon2id KDF" and the spec-conformance tampered-header tests.
- [x] 10.7 No retroactive tick — every box above reflects work done in this change
## 11. Archive
- [x] 11.1 Fold each spec delta into `openspec/specs/<cap>/spec.md` (run via `openspec archive crypto-completion`)
- [x] 11.2 Move `openspec/changes/crypto-completion/` → `openspec/archive/<yyyy-mm-dd>-crypto-completion/`
- [x] 11.3 Refresh `MANIFEST.md` (`bun scripts/folder-reorg/generate-manifest.ts` — 125 packages, 31 plugins)
- [ ] 11.4 Update `STATUS.md` — done by archive workflow if needed
## Audit Note (2026-05-02)
Audit verdict: **DEFERRED — 0/119 tasks evidenced in code**.
Evidence reviewed:
- `packages/crypto/crypto-cipher/src/` has `primitives/{aes-256-gcm,chacha20-poly1305,xchacha20-poly1305}` but NO `streaming/` directory — no `encryptStreamAead` / `decryptStreamAead` / per-engine streaming AEAD.
- `packages/crypto/crypto-curve/src/primitives/secp256k1/` has `secp256k1.ts` + `impl/` + `secp256k1.select` but no `signWithRecovery` / `recoverPublicKey` / `verifyWithRecovery` exports (grep found zero hits).
- `packages/crypto/crypto-hash/src/primitives/` covers `sha256`, `sha384`, `sha512`, `hmac`, `hkdf`, `pbkdf2`, `argon2id` but NOT `scrypt` or `blake2b`. `src/mac/` contains only `hmac/`, no `blake2b-password/`.
- `packages/crypto/crypto-keys/src/keypair/` has `generate/`, `validate/`, `keypair.types.ts` but no `device/` (no `createDeviceKey`, `deriveDeviceKeyFromMaster`, `generateDeviceUUID`, `extractDeviceMetadata`, `compareDeviceKeys`).
- `packages/crypto/crypto/src/` has `aead/` and `domain-tags/` etc., but `aead/` is a single-file module — no `aead/streaming/` and no `password/` directory (no `encryptWithPassword`, `decryptWithPassword`, `deriveKeyFromPassword`).
- `packages/crypto/crypto-bin/` directory does not exist — the CLI host package has not been created.
Missing pieces (highest impact):
1. Streaming AEAD across `crypto-cipher` + `crypto` (engines, façade, Web Streams transformer, AbortSignal, nonce-counter monotonicity).
2. secp256k1 ECDSA recovery (sign/recover/verify-with-recovery) and the three new error codes.
3. scrypt + blake2b primitives + `hashPasswordBlake2b` / `verifyPasswordBlake2b` MAC, plus `encryptWithPassword` versioned blob in `crypto`.
4. Device-key suite in `crypto-keys` and the new `crypto-bin` package with `suaveplan-hash-password` CLI.
Decision: leave change open. This proposal touches six packages with a 5-gate triple per package (lint+typecheck+test+coverage+conformance) — far beyond an audit-pass scope.