# Opaque key handles _(no submodules)_ ## Purpose `opaque-handle.ts` lets the broader `vault` layer hold a typed reference to a SecureBuffer without ever seeing the raw bytes. The handle is a frozen, brand-symbol-tagged object containing only a stable `id`, a `length`, and a `_zeroize` thunk. The mapping back to the SecureBuffer is held in a module-level `WeakMap` keyed by the handle, so a leaked handle on its own does not yield key material. ## Features - `wrap(SecureBuffer): OpaqueKeyHandle` — frozen handle with a unique symbol id, the buffer's length, and a `_zeroize` callable. - `unwrap(OpaqueKeyHandle): SecureBuffer` — package-internal resolver, validates the brand and throws when the handle is malformed or the underlying buffer has been GC'd. - `isValidHandle(value): value is OpaqueKeyHandle` — runtime brand check for values arriving across an `unknown`-typed boundary. ## Usage: Basic Example ```ts import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer"; import { wrap } from "@suaveplan/crypto-vault/handle/opaque-handle"; const buf = new SecureBuffer(32); buf.fillRandom(); const handle = wrap(buf); console.log(handle.length); // 32 handle._zeroize(); console.log(buf.isClosed); // true ``` ## Usage: Advanced Example ```ts import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer"; import { isValidHandle, wrap, } from "@suaveplan/crypto-vault/handle/opaque-handle"; function passToVaultLayer(handle: unknown) { if (!isValidHandle(handle)) { throw new Error("vault layer received a non-handle value"); } // The vault layer stores the handle but cannot read the underlying bytes. return handle; } const wrapped = wrap(new SecureBuffer(32)); passToVaultLayer(wrapped); ``` ## API Reference ### `wrap(buffer: SecureBuffer): OpaqueKeyHandle` Returns a frozen handle. The handle's `_zeroize` thunk forwards to the buffer's `close()` method. ### `unwrap(handle: OpaqueKeyHandle): SecureBuffer` Resolves the handle back to the underlying SecureBuffer. Throws `CryptoVaultError(CRYPTOVAULT.OPAQUE.INVALID_HANDLE)` for missing brand, unknown handle, or GC'd buffer. **Internal — vault-side packages within `@suaveplan/*` may import this; consumers outside the project should treat handles as opaque.** ### `isValidHandle(value: unknown): value is OpaqueKeyHandle` Runtime brand check. ## Implementation Notes - The handle uses a `unique symbol` brand so the TypeScript compiler can refuse foreign objects masquerading as `OpaqueKeyHandle`. - The `WeakMap` ensures that once the handle becomes unreachable, the SecureBuffer link is also collected. Callers that want to keep the buffer alive must keep a strong reference to the handle. - The `_zeroize` field deliberately starts with an underscore to flag it as an implementation detail rather than a stable public method. - This module is the boundary that prevents the `vault` layer from needing direct `Uint8Array` access to derived keys; the orchestrator hands handles to AEAD operations and the AEAD layer calls `unwrap` immediately before passing the bytes into the noble cipher.