first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
# @suaveplan/crypto-keys - Hierarchical keychain chain-signing for the crypto ecosystem: pluggable ChainHasher + ChainCodec interfaces with three shipped implementations each (RawHasher / DomainHasher / MerkleHasher and TlvChainCodec / RecursiveEd25519Codec / CborChainCodec); ChainedKeySigner class with per-slot algorithm dispatch across Ed25519/secp256k1/p256/p384/p521; hierarchical key factory (createRootKeysWithChain / createServerKeysWithChain / rollServerKeysWithChain). Universal.
Hierarchical keychain chain-signing for the crypto ecosystem.
Store-agnostic, free of `node:` / `bun:` imports.
<!-- begin:auto-docs -->
## Documentation tree
- [cbor](./src/codecs/cbor/cbor.md) — `CborChainCodec` is the CBOR-based wire-format `ChainCodec` for `@suaveplan/crypto-keys`. It serialises an ordered list…
- [cbor](./src/codecs/cbor/cbor.md) — `CborChainCodec` is the CBOR-based wire-format `ChainCodec` for `@suaveplan/crypto-keys`. It serialises an ordered list…
- [recursive-ed25519](./src/codecs/recursive-ed25519/recursive-ed25519.md) — `RecursiveEd25519Codec` is a fixed-Ed25519 alternate wire format for signed key chains in `@suaveplan/crypto-keys`. It…
- [recursive-ed25519](./src/codecs/recursive-ed25519/recursive-ed25519.md) — `RecursiveEd25519Codec` is a fixed-Ed25519 alternate wire format for signed key chains in `@suaveplan/crypto-keys`. It…
- [tlv](./src/codecs/tlv/tlv.md) — `TlvChainCodec` is the default wire-format codec for `@suaveplan/crypto-keys`. It encodes and decodes the `ChainLevel[]…
- [tlv](./src/codecs/tlv/tlv.md) — `TlvChainCodec` is the default wire-format codec for `@suaveplan/crypto-keys`. It encodes and decodes the `ChainLevel[]…
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/crypto-keys` package centralises every failure mode it can produce behind a single registry built with…
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/crypto-keys` package centralises every failure mode it can produce behind a single registry built with…
- [factory](./src/factory/factory.md) — Hierarchical-keychain factory helpers that compose `generateKeychain` from `@suaveplan/crypto` with `ChainedKeySigner.s…
- [factory](./src/factory/factory.md) — Hierarchical-keychain factory helpers that compose `generateKeychain` from `@suaveplan/crypto` with `ChainedKeySigner.s…
- [domain](./src/hashers/domain/domain.md) — `DomainHasher` is the `ChainHasher` strategy that domain-separates the signed pre-image with a caller-supplied tag and…
- [domain](./src/hashers/domain/domain.md) — `DomainHasher` is the `ChainHasher` strategy that domain-separates the signed pre-image with a caller-supplied tag and…
- [merkle](./src/hashers/merkle/merkle.md) — `MerkleHasher` is the `ChainHasher` strategy that binds each non-root level to its parent's content via a Merkle-style…
- [merkle](./src/hashers/merkle/merkle.md) — `MerkleHasher` is the `ChainHasher` strategy that binds each non-root level to its parent's content via a Merkle-style…
- [raw](./src/hashers/raw/raw.md) — `RawHasher` is the trivial `ChainHasher` strategy: it returns the payload bytes verbatim, performing no transformation.…
- [raw](./src/hashers/raw/raw.md) — `RawHasher` is the trivial `ChainHasher` strategy: it returns the payload bytes verbatim, performing no transformation.…
- [signer](./src/signer/signer.md) — `ChainedKeySigner` composes a `ChainHasher` (bytes-to-sign strategy) and a `ChainCodec` (wire-format strategy) to produ…
- [signer](./src/signer/signer.md) — `ChainedKeySigner` composes a `ChainHasher` (bytes-to-sign strategy) and a `ChainCodec` (wire-format strategy) to produ…
- [chain-level](./src/types/chain-level.md) — Shared type module declaring the value-shapes that every other module in `@suaveplan/crypto-keys` references. Defining…
- [codec](./src/types/codec.md) — `ChainCodec` is the wire-format strategy that turns an array of `ChainLevel` values into bytes and back. Decoupling the…
- [hasher](./src/types/hasher.md) — `ChainHasher` is the strategy interface that decides what bytes a `ChainedKeySigner` actually signs for one chain level…
<!-- end:auto-docs -->
## Purpose
Ships strong primitives in `@suaveplan/crypto`,
`@suaveplan/crypto-curve`, and `@suaveplan/crypto-hash`, plus a
keychain layer in `@suaveplan/crypto` itself (`encodePublicKeychain`,
`generateKeychain`, per-slot `AlgorithmId`). What it does not ship is
the *pattern* of binding a chain of public keychains together with
signatures — root signs server, server signs user, and so on — with
end-to-end verification back to a trusted root. This package fills
that gap.
The design is pluggable. Two interfaces (`ChainHasher` for the
bytes-to-sign and `ChainCodec` for the wire format) decouple the three
shipped hash modes (raw / domain-tagged / merkle) from the three
shipped codecs (TLV / recursive-Ed25519 / CBOR). The `ChainedKeySigner`
class composes a hasher + codec and dispatches signing per-algorithm
to the matching `@suaveplan/crypto-curve` primitive — so a single
chain can mix Ed25519, secp256k1, and the P-curves across its levels.
## Features
- Three shipped hashers — `RawHasher`, `DomainHasher({ domainTag })`, `MerkleHasher`
- Three shipped codecs — `TlvChainCodec` (default), `RecursiveEd25519Codec`, `CborChainCodec`
- `ChainedKeySigner` class — `sign` / `verify` / `parse` / `stripToDepth`
- Per-slot algorithm dispatch across Ed25519 / secp256k1 / P-256 / P-384 / P-521
- Hierarchical factory — `createRootKeysWithChain`, `createServerKeysWithChain`, `rollServerKeysWithChain`
- `ChainKeysError` extending `SuaveplanError` with `depth?` / `algorithm?` discriminants and 7 stable `CHAIN_KEYS.*` codes
- Constant-time root-key compare via `@suaveplan/crypto-utils`
- 13 tree-shakeable subpath exports — import the codec you want without dragging in the rest
- 100% statement / branch / function / line coverage via Istanbul
## Usage: Basic
```ts
import {
ChainedKeySigner,
createRootKeysWithChain,
createServerKeysWithChain,
} from "@suaveplan/crypto-keys";
// Build a 2-level chain: root → server
const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain();
const { keys: serverKeys, chain: serverChain } = await createServerKeysWithChain({
rootKeys,
rootChain,
});
// Verify end-to-end against the trusted root sign-slot
const signer = new ChainedKeySigner();
const result = await signer.verify(serverChain, rootKeys.sign.public);
if (!result.valid) throw new Error(result.errors.join("; "));
console.log("Chain depth:", result.depth); // 2
```
## Usage: Advanced
```ts
import {
ChainedKeySigner,
DomainHasher,
CborChainCodec,
createRootKeysWithChain,
createServerKeysWithChain,
rollServerKeysWithChain,
} from "@suaveplan/crypto-keys";
// Domain-tagged signing inside CBOR-encoded chains
const signer = new ChainedKeySigner({
hasher: new DomainHasher({ domainTag: "acme/key-chain-v1" }),
codec: new CborChainCodec(),
});
const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain({ signer });
const { keys: serverV1, chain: chainV1 } = await createServerKeysWithChain({
rootKeys,
rootChain,
signer,
});
// Six months later, roll the server keychain forward
const { keys: serverV2, chain: chainV2 } = await rollServerKeysWithChain({
rootKeys,
rootChain,
currentVersion: serverV1.master.private.layout.fields.version,
signer,
});
// Hand a verifier a partial chain (just the leaf + its signature)
const leafOnly = await signer.stripToDepth(chainV2, 1);
// Verifiers reproduce the same hasher + codec to verify
const verifier = new ChainedKeySigner({
hasher: new DomainHasher({ domainTag: "acme/key-chain-v1" }),
codec: new CborChainCodec(),
});
const result = await verifier.verify(chainV2, rootKeys.sign.public);
```
## API Reference
### Class `ChainedKeySigner`
| Method | Signature |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| `constructor` | `(options?: { hasher?: ChainHasher; codec?: ChainCodec })` |
| `sign` | `(publicKeys: PublicKeychain, signSlot: KeySlotPrivate, parent?: Uint8Array) => Promise<Uint8Array>` |
| `verify` | `(chainBytes: Uint8Array, trustedRoot: KeySlotPublic) => Promise<VerificationResult>` |
| `parse` | `(chainBytes: Uint8Array) => Promise<{ depth: number; levels: readonly ChainLevel[] }>` |
| `stripToDepth` | `(chainBytes: Uint8Array, depth: number) => Promise<Uint8Array>` |
### Hashers
- `class RawHasher implements ChainHasher`
- `class DomainHasher implements ChainHasher` — constructor `({ domainTag: string })`, tag must be 1–64 ASCII bytes
- `class MerkleHasher implements ChainHasher`
### Codecs
- `class TlvChainCodec implements ChainCodec` (default)
- `class RecursiveEd25519Codec implements ChainCodec` — Ed25519 only
- `class CborChainCodec implements ChainCodec`
### Factory functions
- `async function createRootKeysWithChain(options?: { signer?: ChainedKeySigner; generate?: GenerateKeychainOptions }): Promise<{ keys: Keychain; chain: Uint8Array }>`
- `async function createServerKeysWithChain(options: { rootKeys: Keychain; rootChain: Uint8Array; signer?: ChainedKeySigner; generate?: GenerateKeychainOptions }): Promise<{ keys: Keychain; chain: Uint8Array }>`
- `async function rollServerKeysWithChain(options: { rootKeys: Keychain; rootChain: Uint8Array; currentVersion: number; signer?: ChainedKeySigner }): Promise<{ keys: Keychain; chain: Uint8Array }>`
### Errors
- `class ChainKeysError extends SuaveplanError<ChainKeysErrorCode>` with optional `depth?: number` and `algorithm?: AlgorithmId` discriminants
- `CHAIN_KEYS_CODES` — frozen registry of 7 codes: `UNSUPPORTED_ALGORITHM`, `INVALID_FORMAT`, `INVALID_SIGNATURE`, `TRUST_MISMATCH`, `INVALID_DEPTH`, `INVALID_KEYCHAIN`, `INVALID_DOMAIN_TAG`
## Implementation Notes
The package is universal — no `node:` / `bun:` imports anywhere
in `src/`. All cryptographic primitives come from
`@suaveplan/crypto-curve` (per-algorithm `sign(digest, privateKey)` /
`verify(...)`) and `@suaveplan/crypto-hash` (`sha256` for the
`DomainHasher` / `MerkleHasher`). Constant-time byte compare comes
from `@suaveplan/crypto-utils`.
The wire format is **self-describing**. Every codec prepends
`[version: u8][hasher: u8]` so a verifier reproduces the exact hash
that was signed. The TLV codec also tags each level with its
algorithm byte, letting a chain mix algorithms across levels — useful
when the root uses Ed25519 for speed and intermediate authorities
use P-256 for compliance.
Signing dispatches at the slot level: the signer reads
`signSlot.algorithm` and calls the matching `crypto-curve` primitive.
This means upstream pre-suaveplan chain bytes are **not** byte-compatible
with this package — they were emitted by an older bitpacker-based
scheme. Migration is one-way: re-sign chains in the new format.
Errors are never raw `Error` — every failure goes through `ChainKeysError`
with a stable `CHAIN_KEYS.*` code. Verification failures don't throw;
they return `{ valid: false, errors: [...] }` so callers can chain
multiple verification attempts without try/catch. Only construction-time
errors (e.g. an invalid `domainTag`) throw synchronously.