# factory _(no submodules)_ ## Purpose Hierarchical-keychain factory helpers that compose `generateKeychain` from `@suaveplan/crypto` with `ChainedKeySigner.signWithSlot` to produce signed chains in one call. Three functions cover the common pattern: bootstrap a root keychain, derive a server keychain bound to the root, and roll a server keychain forward to the next version without changing the root. These functions are Ed25519-only in v0.0.0 because the crypto keychain v1 grammar only generates Ed25519 sign-slots. Callers that need a different curve at any chain level should reach for `ChainedKeySigner.signRaw` directly instead. ## Features - `createRootKeysWithChain` — fresh keychain + self-signed chain - `createServerKeysWithChain` — derived keychain + chain bound to root - `rollServerKeysWithChain` — re-derive at next slot version, re-sign - All three accept an injected `signer?` for custom hasher/codec choices - Strict `INVALID_KEYCHAIN` errors when root slots are missing or empty - Sensible `LayoutV1Fields` defaults (`masterVersion: 1, userId: 0, depth: 0, slotVersion: 1`) - Default `Clock` is `Date.now` — caller can override via `generate.clock` ## Usage: Basic ```ts import { ChainedKeySigner, createRootKeysWithChain, createServerKeysWithChain, } from "@suaveplan/crypto-keys"; // Bootstrap a root keychain + self-signed chain const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain(); // Derive a server keychain whose chain anchors to the root const { keys: serverKeys, chain: serverChain } = await createServerKeysWithChain({ rootKeys, rootChain, }); // Verify end-to-end const verifier = new ChainedKeySigner(); const result = await verifier.verify(serverChain, { algorithm: "ed25519", publicKey: (rootKeys as Record).sign.publicKey, }); console.log(result.valid, result.depth); // true 2 ``` ## Usage: Advanced ```ts import { ChainedKeySigner, DomainHasher, createRootKeysWithChain, createServerKeysWithChain, rollServerKeysWithChain, } from "@suaveplan/crypto-keys"; const signer = new ChainedKeySigner({ hasher: new DomainHasher({ domainTag: "tenant.acme.v1" }), }); // Tagged root const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain({ signer, generate: { userId: 42 as never }, // custom layout fields per app }); // First server keychain (slot version 1) const v1 = await createServerKeysWithChain({ rootKeys, rootChain, signer }); // Six months later: roll to slot version 2, same root binding const v2 = await rollServerKeysWithChain({ rootKeys, rootChain, currentVersion: 1, signer, }); // Issuance chain: root -> v1 -> v2 -> leaf user const leaf = await createServerKeysWithChain({ rootKeys, rootChain: v2.chain, signer, }); const verifier = new ChainedKeySigner({ hasher: new DomainHasher({ domainTag: "tenant.acme.v1" }), }); const result = await verifier.verify(leaf.chain, { algorithm: "ed25519", publicKey: (rootKeys as Record).sign.publicKey, }); ``` ## API Reference ### `async function createRootKeysWithChain(options?)` | Field | Type | Default | | ----------------- | ------------------------------------- | ----------------------- | | `signer?` | `ChainedKeySigner` | `new ChainedKeySigner()` | | `generate?` | `Partial` | sensible defaults | Returns `{ keys: Keychain; chain: Uint8Array }`. ### `async function createServerKeysWithChain(options)` | Field | Type | Required | | ------------- | ------------------------------------- | -------- | | `rootKeys` | `Keychain` | yes | | `rootChain` | `Uint8Array` | yes | | `signer?` | `ChainedKeySigner` | no | | `generate?` | `Partial` | no | Raises `CHAIN_KEYS.INVALID_KEYCHAIN` if `rootKeys.master` or `rootKeys.sign` is missing, has an empty `secretKey`, or has a non-Ed25519 `algorithm`. ### `async function rollServerKeysWithChain(options)` | Field | Type | Required | | ----------------- | -------------------------- | -------- | | `rootKeys` | `Keychain` | yes | | `rootChain` | `Uint8Array` | yes | | `currentVersion` | `number` (≥ 0, integer) | yes | | `signer?` | `ChainedKeySigner` | no | Generates a new keychain with `slotVersion: currentVersion + 1` and re-signs against the same root. Raises `CHAIN_KEYS.INVALID_KEYCHAIN` when `currentVersion` is negative or non-integer, or when the root slots are missing/invalid. ## Implementation Notes crypto's `Keychain` is `Record` where `PurposeTag` is a branded string type. The factory module uses a small `getSlot(keys, tag)` helper that casts through `Record` to access individual slots; this keeps the TypeScript narrowing clean without pulling in the `PurposeTag` brand machinery. `LayoutV1Fields` is re-declared locally because it's not currently re-exported from `@suaveplan/crypto/keychain`. The shape is identical to the upstream definition and `generateKeychain` accepts any object satisfying it structurally. If a future crypto release adds a `./derivation` subpath export, this declaration can be replaced with the canonical import. The factory functions trust `generateKeychain`'s contract — that every generated keychain contains all 8 default-purpose slots with valid Ed25519 sign-slots — and do not re-validate the freshly-generated output. Validation is concentrated at the boundaries: `signWithSlot` rejects non-Ed25519 slots, and `assertRootSlots` checks every caller-supplied root before use. This keeps the happy path lean and pushes the cost of bad input onto the caller's stack frame. `signWithSlot` takes an optional `parent: Uint8Array`; the factory functions pass `rootChain` when extending and omit it for the self-signed root. `ChainedKeySigner` distinguishes a root signature (no parent) from a non-root signature (parent supplied) by the presence of the argument — there's no separate "root" API.