@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.
Documentation tree
- cbor —
CborChainCodecis the CBOR-based wire-formatChainCodecfor@suaveplan/crypto-keys. It serialises an ordered list…- cbor —
CborChainCodecis the CBOR-based wire-formatChainCodecfor@suaveplan/crypto-keys. It serialises an ordered list…
- cbor —
- recursive-ed25519 —
RecursiveEd25519Codecis a fixed-Ed25519 alternate wire format for signed key chains in@suaveplan/crypto-keys. It…- recursive-ed25519 —
RecursiveEd25519Codecis a fixed-Ed25519 alternate wire format for signed key chains in@suaveplan/crypto-keys. It…
- recursive-ed25519 —
- tlv —
TlvChainCodecis the default wire-format codec for@suaveplan/crypto-keys. It encodes and decodes the `ChainLevel[]…- tlv —
TlvChainCodecis the default wire-format codec for@suaveplan/crypto-keys. It encodes and decodes the `ChainLevel[]…
- tlv —
- error-codes — The
@suaveplan/crypto-keyspackage centralises every failure mode it can produce behind a single registry built with…- error-codes — The
@suaveplan/crypto-keyspackage centralises every failure mode it can produce behind a single registry built with…
- error-codes — The
- factory — Hierarchical-keychain factory helpers that compose
generateKeychainfrom@suaveplan/cryptowith `ChainedKeySigner.s…- factory — Hierarchical-keychain factory helpers that compose
generateKeychainfrom@suaveplan/cryptowith `ChainedKeySigner.s…
- factory — Hierarchical-keychain factory helpers that compose
- domain —
DomainHasheris theChainHasherstrategy that domain-separates the signed pre-image with a caller-supplied tag and…- domain —
DomainHasheris theChainHasherstrategy that domain-separates the signed pre-image with a caller-supplied tag and…
- domain —
- merkle —
MerkleHasheris theChainHasherstrategy that binds each non-root level to its parent's content via a Merkle-style…- merkle —
MerkleHasheris theChainHasherstrategy that binds each non-root level to its parent's content via a Merkle-style…
- merkle —
- raw —
RawHasheris the trivialChainHasherstrategy: it returns the payload bytes verbatim, performing no transformation.…- raw —
RawHasheris the trivialChainHasherstrategy: it returns the payload bytes verbatim, performing no transformation.…
- raw —
- signer —
ChainedKeySignercomposes aChainHasher(bytes-to-sign strategy) and aChainCodec(wire-format strategy) to produ…- signer —
ChainedKeySignercomposes aChainHasher(bytes-to-sign strategy) and aChainCodec(wire-format strategy) to produ…
- signer —
- chain-level — Shared type module declaring the value-shapes that every other module in
@suaveplan/crypto-keysreferences. Defining… - codec —
ChainCodecis the wire-format strategy that turns an array ofChainLevelvalues into bytes and back. Decoupling the… - hasher —
ChainHasheris the strategy interface that decides what bytes aChainedKeySigneractually signs for one chain level…
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 ChainedKeySignerclass —sign/verify/parse/stripToDepth- Per-slot algorithm dispatch across Ed25519 / secp256k1 / P-256 / P-384 / P-521
- Hierarchical factory —
createRootKeysWithChain,createServerKeysWithChain,rollServerKeysWithChain ChainKeysErrorextendingSuaveplanErrorwithdepth?/algorithm?discriminants and 7 stableCHAIN_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
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
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 ChainHasherclass DomainHasher implements ChainHasher— constructor({ domainTag: string }), tag must be 1–64 ASCII bytesclass MerkleHasher implements ChainHasher
Codecs
class TlvChainCodec implements ChainCodec(default)class RecursiveEd25519Codec implements ChainCodec— Ed25519 onlyclass 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 optionaldepth?: numberandalgorithm?: AlgorithmIddiscriminantsCHAIN_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.