# Tasks — crypto-keys > Every checkbox MUST reflect work actually done in this change. ## 1. Spec authoring - [x] 1.1 Read `openspec/AGENTS.md` §0, §0a, §0b, §0c, §6a, §7, §13, §14 end-to-end - [x] 1.2 Read `../suaveplan/packages/universal/keys/src/lib/chain-signer.ts` in full - [x] 1.3 Read `../suaveplan/packages/universal/keys/src/lib/keys-common.ts` (factory section) - [x] 1.4 Read `../suaveplan/enterprise/keys.md` to confirm scope boundary - [x] 1.5 Read genesis `packages/crypto/crypto/src/keychain/keychain.ts` and `keychain/index.ts` (public surface) - [x] 1.6 Skim every `packages/crypto/crypto-curve/src/primitives/*/impl/*.noble/*.ts` and confirm sign/verify signature - [x] 1.7 Draft `proposal.md` with scope, divergence table, source mapping, affected dependents - [x] 1.8 Draft `design.md` covering file layout, three codecs, three hashers, algorithm dispatch, error-code map, factory layer - [x] 1.9 Draft `specs/crypto-keys/spec.md` with `### Requirement: / #### Scenario: / **Verified by:**` form - [x] 1.10 Run `openspec validate add-crypto-keys` and fix any structural failures ## 2. Scaffolding - [x] 2.1 Create `packages/crypto/crypto-keys/` mirroring `packages/database/seed/` layout - [x] 2.2 Write `package.json` v0.0.0, `sideEffects: false`, subpath exports for every feature module (error-codes/error-codes, types/{chain-level,hasher,codec}, hashers/{raw,domain,merkle}/, codecs/{tlv,recursive-ed25519,cbor}/, signer/signer, factory/factory) - [x] 2.3 Wire deps: `@suaveplan/crypto: workspace:^`, `@suaveplan/crypto-curve: workspace:^`, `@suaveplan/crypto-hash: workspace:^`, `@suaveplan/cbor: workspace:^`, `@suaveplan/error: workspace:^`, `@suaveplan/types: workspace:^`, `tslib: catalog:build` - [x] 2.4 Wire devDeps: `@suaveplan/testing: workspace:^`, `@types/node: catalog:`, `@vitest/coverage-istanbul: catalog:testing`, `fast-check: catalog:testing`, `typescript: catalog:`, `vite: catalog:build`, `vite-plugin-dts: catalog:build`, `vitest: catalog:testing` - [x] 2.5 Write `tsconfig.json`, `tsconfig.lib.json` extending the genesis base - [x] 2.6 Write `vite.config.ts` (multi-entry, ES-only, preserveModules) - [x] 2.7 Write `vitest.config.ts` using a project-local config with `pool: 'forks'` + 100% Istanbul thresholds - [x] 2.8 Write `tests/setup/polyfills.ts` (universal tier, no polyfills needed — same pattern as seed) - [x] 2.9 Write `README.md` (≥ 200 words, all 6 required sections per CLAUDE.md docs gate) ## 3. Implementation — errors & shared types - [x] 3.1 `src/error-codes/error-codes.ts` — `defineCodes("CHAIN_KEYS", [...])` with 7 codes; `ChainKeysError extends SuaveplanError` with `depth?` / `algorithm?` discriminants - [x] 3.2 `src/error-codes/error-codes.test.ts` — every code reachable from a `ChainKeysError` construction; `cause` chain preservation; discriminants populate - [x] 3.3 `src/error-codes/error-codes.md` — full doc with required sections - [x] 3.4 `src/error-codes/index.ts` — explicit named re-exports - [x] 3.5 `src/types/chain-level.ts` — `ChainLevel`, `VerificationResult`, `ParsedChainLevel` interfaces; `chain-level.md` sibling - [x] 3.6 `src/types/hasher.ts` — `ChainHasher` interface (`hash`, `name`); `hasher.md` sibling - [x] 3.7 `src/types/codec.ts` — `ChainCodec` interface (`encode`, `decode`, `name`); `codec.md` sibling ## 4. Implementation — hashers (3) - [x] 4.1 `src/hashers/raw/raw.ts` — `RawHasher implements ChainHasher` (identity hash) - [x] 4.2 `src/hashers/raw/raw.test.ts` — identity property; ignores parent; name is "raw" - [x] 4.3 `src/hashers/raw/raw.md` + `index.ts` - [x] 4.4 `src/hashers/domain/domain.ts` — `DomainHasher implements ChainHasher` with constructor-supplied `domainTag` (1–64 ASCII bytes); raises `CHAIN_KEYS.INVALID_DOMAIN_TAG` - [x] 4.5 `src/hashers/domain/domain.test.ts` — `sha256(utf8(tag) || encodedKeys)`; constructor validation (empty / oversize); ignores parent; name is "domain" - [x] 4.6 `src/hashers/domain/domain.md` + `index.ts` - [x] 4.7 `src/hashers/merkle/merkle.ts` — `MerkleHasher implements ChainHasher`; root hash without parent, parent root hash for non-root - [x] 4.8 `src/hashers/merkle/merkle.test.ts` — root path; non-root commits to parent; tampering with parent invalidates child - [x] 4.9 `src/hashers/merkle/merkle.md` + `index.ts` ## 5. Implementation — codecs (3) - [x] 5.1 `src/codecs/tlv/tlv.ts` — `TlvChainCodec implements ChainCodec`; `[version=1][hasher][per level: algorithm + keys_len:u16 + keys + sig_len:u16 + sig]`; algorithm-id ↔ enum byte table; hasher-name ↔ enum byte table; raises `CHAIN_KEYS.INVALID_FORMAT` on bad version/truncation/length overrun - [x] 5.2 `src/codecs/tlv/tlv.test.ts` — encode/decode round-trip for every algorithm; truncated buffer; bad version byte; length overrun; multi-level chain - [x] 5.3 `src/codecs/tlv/tlv.md` + `index.ts` - [x] 5.4 `src/codecs/recursive-ed25519/recursive-ed25519.ts` — `RecursiveEd25519Codec implements ChainCodec`; fixed `keys_size = 8 * KEY_SLOT_PUBLIC_BYTE_SIZE`; rejects non-Ed25519 at encode (`CHAIN_KEYS.UNSUPPORTED_ALGORITHM`) - [x] 5.5 `src/codecs/recursive-ed25519/recursive-ed25519.test.ts` — Ed25519 round-trip; non-Ed25519 rejection; multi-level recursion; bad version - [x] 5.6 `src/codecs/recursive-ed25519/recursive-ed25519.md` + `index.ts` - [x] 5.7 `src/codecs/cbor/cbor.ts` — `CborChainCodec implements ChainCodec`; CBOR map `{ v, h, levels: [{ a, k, s }] }`; deterministic encoder; rejects unknown `h`/`a` strings - [x] 5.8 `src/codecs/cbor/cbor.test.ts` — encode/decode round-trip; deterministic byte equality; bad CBOR; missing fields - [x] 5.9 `src/codecs/cbor/cbor.md` + `index.ts` ## 6. Implementation — ChainedKeySigner - [x] 6.1 `src/signer/signer.types.ts` — `ISigner` / `SignerOptions` / `SignerSignOptions` interfaces - [x] 6.2 `src/signer/signer.ts` — `ChainedKeySigner` class: - constructor `{ hasher?, codec? }` defaults `RawHasher` + `TlvChainCodec` - `sign(publicKeys, signSlot, parent?)` — encodes public keychain, computes bytes-to-sign via hasher (passing parsed parent's leaf level if applicable), dispatches to `@suaveplan/crypto-curve` sign by `signSlot.algorithm`, prepends new level via codec - `verify(chainBytes, trustedRoot)` — decodes via codec, walks every level computing the same bytes-to-sign, dispatches to `crypto-curve` verify, checks deepest signer matches `trustedRoot.public` via constant-time compare, returns `{ valid, depth, levels, errors }` - `parse(chainBytes)` — decode-only - `stripToDepth(chainBytes, depth)` — re-encode the leaf-most `depth` levels via codec - Wraps any crypto-curve exception as `ChainKeysError` with `cause` preserved - [x] 6.3 `src/signer/signer.test.ts` — every public method × every (hasher, codec) combination × every algorithm; tampering tests at every byte boundary; abort signal future-proofing (omitted intentionally; sign is in-memory) - [x] 6.4 `src/signer/signer.md` + `index.ts` ## 7. Implementation — hierarchical factory - [x] 7.1 `src/factory/factory.ts` — `createRootKeysWithChain`, `createServerKeysWithChain`, `rollServerKeysWithChain`; all default to `new ChainedKeySigner()` when omitted; raise `CHAIN_KEYS.INVALID_KEYCHAIN` when `rootKeys.master`/`rootKeys.sign` lacks private material - [x] 7.2 `src/factory/factory.test.ts` — 3-level chain (root → server → user); verify against root; tamper at server level → fails; missing root.master / root.sign → INVALID_KEYCHAIN; injected signer with non-default hasher works end-to-end - [x] 7.3 `src/factory/factory.md` + `index.ts` ## 8. Root barrel & public surface - [x] 8.1 `src/index.ts` — re-export every public symbol via explicit named exports (no `export *` — rule §6a) - [x] 8.2 Verify `bunx turbo build --filter=@suaveplan/crypto-keys` succeeds and dist contains every advertised subpath ## 9. Pre-archive gates - [x] 9.1 `bunx turbo lint --filter=@suaveplan/crypto-keys` green; zero biome suppressions - [x] 9.2 `bunx turbo typecheck --filter=@suaveplan/crypto-keys` green - [x] 9.3 `bunx turbo test --filter=@suaveplan/crypto-keys` green; zero `.skip` / `.only` / `it.todo` - [x] 9.4 `bunx turbo test:coverage --filter=@suaveplan/crypto-keys` green at 100% statement/branch/function/line (Istanbul) - [x] 9.5 `bun run docs:check --filter=@suaveplan/crypto-keys` green - [x] 9.6 `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/crypto/crypto-keys/src/` → zero matches (rule §7) - [x] 9.7 `grep -rnE "^export \*" packages/crypto/crypto-keys/src/` → only the root barrel and exempt forms (rule §6a) - [x] 9.8 `grep -rnE "from ['\"](node:|bun:|fs|path|stream|net|http)['\"]" packages/crypto/crypto-keys/src/` → zero matches (universal tier) - [x] 9.9 `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\\.skip\|\\.only\|\\bany\\b\|console\\.log" packages/crypto/crypto-keys/src/ --include="*.ts"` → zero matches - [x] 9.10 Every `### Requirement` in `specs/crypto-keys/spec.md` checked off against the implementation - [x] 9.11 `bun scripts/folder-reorg/generate-manifest.ts`; `MANIFEST.md` includes `@suaveplan/crypto-keys` - [x] 9.12 `STATUS.md` row added or updated — entry under crypto plugin, status `✅` ## 10. Fold + archive - [x] 10.1 Fold `specs/crypto-keys/spec.md` into `openspec/specs/crypto-keys/spec.md` (new canonical capability; convert `## ADDED Requirements` → `## Requirements` and prepend `## Purpose`) - [x] 10.2 `openspec validate crypto-keys` clean - [x] 10.3 Move `openspec/changes/add-crypto-keys/` → `openspec/archive/$(date +%Y-%m-%d)-add-crypto-keys/` - [x] 10.4 Commit per CLAUDE.md commit-style; do not push without user approval