# orchestration High-level orchestration helpers that bundle keychain generation, password-AEAD encryption, signed public-key file production, and storage I/O behind a single call surface. _(no submodules)_ ## Purpose `@suaveplan/crypto-vault` already ships rich low-level primitives — BIP39 mnemonics, Argon2id KDF, hierarchical key derivation, SecureBuffer, SRP — but no opinionated "create a root keychain plus encrypted vault plus signed public-key file" entrypoint. The orchestration module fills that gap. It composes `@suaveplan/crypto`'s `generateKeychain` / `encodeKeychain` / `encodePublicKeychain` primitives with this package's `deriveKeyWithArgon2id` KEK derivation and the Ed25519 signing surface from `@suaveplan/crypto-curve`, producing the single-call surface used by `@suaveplan/server-keys` operator bins (`create-root-keys`, `create-server-keys`, `roll-server-keys`, `view-keys`, `view-vault`, `view-public-key-file`, etc.). ## Features - One-call `createRootKeys` / `createServerKeys` / `rollServerKeys` producing keychain + encrypted vault + signed public-key file in one trip. - Deterministic server-key derivation: `(rootMaster, version)` reproduces the same private material across hosts so rotation never loses key continuity. - Versioned rotation via `rollServerKeys` with strict `version ≥ 0` validation. - Round-trip `openVault` / `packVault` with constant-time wrong-password rejection (no information leak about which of password / integrity failed). - `viewPublicKeys` with optional Ed25519 signature verification. - Idempotent storage helpers (`saveVaultToStorage`, `savePublicKeysToStorage`, `getDataFromStorage`) that translate "missing key" outcomes from any backend into a typed `CryptoVaultError`. - suaveplan-native wire formats — explicitly NOT byte-compatible with the upstream `@suaveplan/keys` BitPacker format; rotation is an offline migration. ## Usage: Basic ```ts import { createRootKeys, openVault, saveVaultToStorage, } from "@suaveplan/crypto-vault"; import { MemoryStorage } from "@suaveplan/storage"; const storage = new MemoryStorage({ cleanupIntervalMs: 0 }); const root = await createRootKeys("correct horse battery staple"); await saveVaultToStorage(storage, "root.vault", root.vaultBytes); const recovered = await openVault(root.vaultBytes, "correct horse battery staple"); // Eight default-purpose slots: "mast" (master), "sign", "encr", "kx ", // "salt", "hash", "strm", "dev " — keyed by the 4-byte purpose tag. console.log(recovered["mast"].publicKey.length); // 32 ``` ## Usage: Advanced ```ts import { createRootKeys, createServerKeys, rollServerKeys, viewPublicKeys, } from "@suaveplan/crypto-vault"; const root = await createRootKeys("root-pw"); // Provision a server keychain at version 1 (default) const server1 = await createServerKeys(root, "server-pw"); // Roll the server keychain forward — deterministically derives a fresh master. const server2 = await rollServerKeys(root, server1.version, "server-pw"); // Verify the server's signed public-key file using the root sign pubkey. const decoded = await viewPublicKeys( server2.publicKeyBytes, root.keychain["sign"].publicKey ); console.log(decoded["sign"].publicKey === server2.publicKeys["sign"].publicKey); ``` ## API Reference - `createRootKeys(password, options?)` → `Promise`. Generates a fresh root keychain via `generateKeychain`. `options.masterKey` lets callers supply a pre-existing 32-byte seed; `options.layoutFields` overrides the default v1 layout `{ masterVersion: 1, userId: 0, depth: 0, slotVersion: 1 }`; `options.argon2id` overrides KDF parameters. - `createServerKeys(rootKeys, serverPassword, options?)` → `Promise`. Deterministically derives a child master via `HMAC-SHA512(rootMaster, u32be(version))` truncated to 32 bytes. Throws `CryptoVaultError(CRYPTOVAULT.ORCHESTRATION.INVALID_VERSION)` on negative or non-integer `version`. Public-key file is signed by the *root* sign slot. - `rollServerKeys(rootKeys, currentVersion, serverPassword, options?)` → `Promise`. Returns a result at `version = currentVersion + 1`. Throws the same `INVALID_VERSION` error on negative `currentVersion`. - `openVault(vaultBytes, password)` → `Promise`. Splits the wire layout (`salt(16) || nonce(24) || ciphertext`), re-derives the KEK with Argon2id, and decrypts. Throws `CryptoVaultError(CRYPTOVAULT.ORCHESTRATION.DECRYPT_FAILED)` on wrong password, short input, or tampered ciphertext. - `packVault(keychain, password, options?)` → `Promise`. Inverse of `openVault`; reusable by callers that already hold a `Keychain`. - `viewPublicKeys(publicKeyBytes, optionalSignKey?)` → `Promise`. Splits the trailing 64-byte Ed25519 signature, decodes the body, and (when `optionalSignKey` is provided) verifies the signature. Throws `CryptoVaultError(CRYPTOVAULT.ORCHESTRATION.PUBLIC_KEY_SIGNATURE_INVALID)` on short input or signature mismatch. - `saveVaultToStorage(storage, key, vaultBytes)` / `savePublicKeysToStorage(storage, key, publicKeyBytes)` → `Promise`. Thin idempotent wrappers over `AbstractStorage.set`. - `getDataFromStorage(storage, key)` → `Promise`. Translates a missing key (either `undefined` return or a thrown backend error) into `CryptoVaultError(CRYPTOVAULT.ORCHESTRATION.STORAGE_KEY_NOT_FOUND)`. ## Implementation Notes - The KEK is derived with `deriveKeyWithArgon2id` using `DEFAULT_ARGON2ID_PARAMS` (OWASP-compliant: 64 MiB memory, 3 iterations, parallelism 4) unless callers override via `options.argon2id`. - Vault layout: 16-byte Argon2id salt, then a 24-byte XChaCha20-Poly1305 nonce, then ciphertext. Both header values are drawn from `crypto.getRandomValues` so two encryptions of the same plaintext under the same password share no bytes. - Public-key file layout: `encodePublicKeychain(extractPublicKeys(keychain))` followed by a 64-byte Ed25519 signature. For root keychains the keychain self-signs; for server keychains the root's sign slot signs the child's public bytes so a verifier holding only the root sign pubkey can attest the full chain. - `openVault` always uses `DEFAULT_ARGON2ID_PARAMS` to re-derive the KEK; the salt is embedded in the vault header but the params themselves are not — the trade-off keeps the format flat at the cost of locking the on-disk profile to the package default. Future overrides will append a versioned header. - `viewPublicKeys` collapses every signature failure into a single error code to avoid leaking which check failed first; tests rely on that property when asserting the conformance behaviour. - Storage helpers do not introspect the value — they pass `Uint8Array` straight through. Backends with their own size limits (e.g. `MemoryStorage`'s `maxValueSize`) surface as their own typed error, which is intentional: the vault layer should not silently mutate the backend's contract.