# Extend @suaveplan/crypto-vault — vault orchestration layer **Package**: `@suaveplan/crypto-vault` **Status**: PROPOSED **Type**: feature (additive — no breaking changes) - **Spec delta**: ./specs/crypto-vault/spec.md ## Why `@suaveplan/crypto-vault` ships rich primitives (BIP39 / Argon2id / 2SKD / HKDF / SecureBuffer / OpaqueKeyHandle / SRP) but no high-level orchestration helpers — the layer that bundles "generate a root keychain + encrypt it into a vault file + write the signed public-key file" into one call. Upstream `../suaveplan/packages/universal/keys/src/lib/keys-common.ts` shipped that orchestration as part of the now-split `@suaveplan/keys` umbrella; on genesis it has no home yet. `@suaveplan/server-keys` (in-flight; full-parity port of 18 operator CLI bins) is blocked on this layer: 7 of its bins (`create-root-keys`, `create-server-keys`, `roll-server-keys`, `view-keys`, `view-vault`, `view-public-key-file`, plus the orchestration half of the chain bins) call `createRootKeys` / `createServerKeys` / `rollServerKeys` / `openVault` / `packVault` / `viewPublicKeys` / `saveVaultToStorage` / `savePublicKeysToStorage` / `getDataFromStorage`. The 3 `*WithChain` variants already shipped in `@suaveplan/crypto-keys` (factory layer) demonstrate the pattern; this proposal adds the non-chain siblings to `crypto-vault` so server-keys can ship at parity. The wire format is **genesis-native**, not byte-compatible with upstream vaults (upstream used the deprecated `BitPacker(schema)` layer; genesis uses the v1 derivation grammar `encodePublicKeychain` + AEAD regime-2 secret blob + regime-3 application data payload). Cross-version interop is explicitly out of scope — operators rotating from upstream to genesis use a one-shot migration command (in a separate follow-up package), not in-place vault parsing. ## What changes All additive — no existing `crypto-vault` export changes shape. New module `src/orchestration/` exports 9 functions + supporting types. ### 9 new functions - `createRootKeys(password, options?): Promise` — generates a fresh root `Keychain` via `@suaveplan/crypto` `generateKeychain`, derives an Argon2id KEK from `password`, encrypts the full keychain into vault bytes (AEAD regime-2 secret blob), extracts public keys + canonical bytes via `encodePublicKeychain`. Returns `{ keychain, vaultBytes, publicKeyBytes, publicKeys }`. - `createServerKeys(rootKeys, serverPassword, options?): Promise` — generates a child keychain derived from `rootKeys.keychain.master.private` (HD-key path), encrypts it under `serverPassword`. Returns `{ keychain, vaultBytes, publicKeyBytes, publicKeys, version }`. - `rollServerKeys(rootKeys, currentVersion, serverPassword, options?): Promise` — deterministic re-derivation at `version = currentVersion + 1` using the same root master key + serverPassword. Re-encryption produces a fresh vault; the underlying private material is reproducible from `(rootMasterKey, version, serverPassword)`. - `openVault(vaultBytes, password): Promise` — Argon2id-derive KEK from `password`, AEAD-decrypt `vaultBytes` to recover the full `Keychain`. Wrong password raises `CryptoVaultError(VAULT_DECRYPT_FAILED)`; tampered bytes raise the same. - `packVault(keychain, password, options?): Promise` — inverse of `openVault`. Used by callers that already have a `Keychain` instance (e.g. after a rotation) and need to re-encrypt it. - `viewPublicKeys(publicKeyBytes, optionalSignKey?): Promise` — decode the signed public-key file. If `optionalSignKey` is provided, verify the embedded signature; mismatch raises `CryptoVaultError(PUBLIC_KEY_SIGNATURE_INVALID)`. - `saveVaultToStorage(storage, key, vaultBytes): Promise` — convenience wrapper over `@suaveplan/storage`'s `AbstractStorage.put(key, vaultBytes)`. Idempotent. - `savePublicKeysToStorage(storage, key, publicKeyBytes): Promise` — same shape, for public-key files. - `getDataFromStorage(storage, key): Promise` — convenience wrapper over `AbstractStorage.get(key)`. Raises `CryptoVaultError(STORAGE_KEY_NOT_FOUND)` when absent (translates whatever the storage backend throws). ### Supporting types - `RootKeysResult`, `ServerKeysResult` (interfaces declaring the return shapes) - `CreateRootKeysOptions`, `CreateServerKeysOptions`, `RollServerKeysOptions`, `PackVaultOptions` (option bags — all optional; defaults for KDF parameters, algorithm choice, derivation path) ### New error codes (additive — registered alongside existing `CRYPTO_VAULT_*` codes) - `CRYPTO_VAULT_DECRYPT_FAILED` — vault decryption failed (wrong password or tampered bytes) - `CRYPTO_VAULT_PUBLIC_KEY_SIGNATURE_INVALID` — `viewPublicKeys` signature check failed - `CRYPTO_VAULT_STORAGE_KEY_NOT_FOUND` — `getDataFromStorage` key absent - `CRYPTO_VAULT_INVALID_VERSION` — `rollServerKeys` received `currentVersion < 0` ### Subpath export `./orchestration/orchestration` — new entry in `package.json`'s `exports` map. ### Dependency additions - `@suaveplan/crypto: workspace:^` (already used; ensure declared) — `generateKeychain`, `encodePublicKeychain`, `decodePublicKeychain`, `extractPublicKeys`, regime-2 seal/open helpers - `@suaveplan/storage: workspace:^` (new dep) — `AbstractStorage` interface for the storage-aware helpers ## Out of scope - **Byte-compat with upstream `@suaveplan/keys` vault format.** Upstream used `BitPacker(fullKeychainSchema)`; genesis uses the v1 derivation grammar. A migration command from old → new format belongs in a separate `@suaveplan/keys-migration` package. - **`VaultManager` event-emitter class** (upstream `vault/vault.ts` 451-LOC orchestrator with `open`/`unlock`/`lock` FSM). Belongs in a follow-up extension; the 9 functions here are the primitives `VaultManager` would compose if/when it lands. - **`VaultStreams`** (chunked streaming vault decryption). Belongs in a separate package once a real consumer exists. - **Hardware-fingerprint binding for keychains.** Out of scope (application policy). ## Source mapping | Capability | Upstream source | Notes | |---|---|---| | Public-API surface (9 functions) | `../suaveplan/packages/universal/keys/src/lib/keys-common.ts` L1-L469 | re-implemented on top of genesis v1 grammar (NOT byte-compat) | | Argon2id KDF parameter defaults | `../suaveplan/enterprise/keys.md` | uses existing `DEFAULT_ARGON2ID_PARAMS` from `crypto-vault` | | `CRYPTO_VAULT_*` code additions | genesis-original | matches existing crypto-vault error registry shape | ## Impact ### Affected dependents `@suaveplan/server-keys` (in-flight) — this proposal unblocks 7 of its 18 bins. No other current consumers. Genesis `crypto-vault` is foundation-tier: changes ripple through its existing consumers (`@suaveplan/vault`, `@suaveplan/crypto-vault` itself). The orchestration module is **additive** — no existing exports change — so dependent tests should not regress. The pre-archive `bunx turbo test --filter=...@suaveplan/crypto-vault` sweep will confirm.