Files
2026-05-27 23:29:53 +01:00

182 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.