/** * `SealedBox` — the ASYMMETRIC sibling of `SecretBox` (#687, * design/signature-contact-carrier.md Option E). * * `SecretBox` answers "store this where only the holder of the key can read it". * This answers a different question, and it is the one a scope cannot otherwise * ask: **"hand this to a recipient I cannot talk to."** * * A hosted vertical's scope has no channel to a connector. Every path from a * scope outward is a spine row — the outbox, the platform-request payload — so a * symmetric key minted in-scope would have to travel the same rows as the value * it protects, which relocates the problem rather than solving it. What breaks * the deadlock is that the recipient's PUBLIC half may be projected down (#37's * guarantee is about SECRET keys), and a public key is enough to write with. * * So: the connection holds a keypair, the public half is projected into the * scope, module code seals to it before `ctx.emit`, and the connector opens the * envelope at egress with the private half — which never leaves the directory. * Nothing re-enters the scope actor, which is what sank the read-back design * (D-2's verified deadlock). * * **The envelope is a `SealedSecret`, deliberately.** It is the same cell shape * the credential store already writes, and it carries `keyId` for the same * reason: a cell that cannot name its key can only ever have ONE key, and every * ciphertext already written becomes ambiguous the day a second one exists. * Rotation mechanics are deferred (D-4); the envelope that permits them is not. * * **Crypto here, storage in the adapter** — the division `createSubjectKeys` * draws. This file mints, seals and opens; which table the private half sleeps * in is the adapter's business. */ import { type SealedSecret } from './secret-box.js'; /** * One recipient keypair, both halves base64 and neither of them JWK. * * Raw formats rather than JWK because both halves have to survive as STRINGS: * the private one goes through `SecretBox.seal`, which takes text, and the * public one rides a projection row and an HTTP body. A JWK would be a JSON * object inside a JSON field — one more encoding to get wrong, and nothing * gained, since the curve is fixed. */ export interface SealingKeyPair { /** Names this keypair in every envelope it opens. See `SealedSecret.keyId`. */ keyId: string; /** SEC1 uncompressed point (65 bytes), base64. Safe to project, log, or print. */ publicKey: string; /** PKCS#8, base64. Never leaves the directory unsealed. */ privateKey: string; } /** The public half alone — what a recipient publishes and a sender needs. */ export interface SealingPublicKey { keyId: string; publicKey: string; } /** * Mint a recipient keypair. * * `keyId` is the caller's to choose and the caller's to keep: it is what a * ciphertext written today names when it is opened tomorrow, so it must be * stable and unique within whatever set the opener holds. The connection store * uses `connection::` — self-describing, and unique across a rotation. */ export declare function generateSealingKeyPair(keyId: string): Promise; /** * Seal `plaintext` so ONLY the holder of `key`'s private half can read it. * * ECDH P-256 with a fresh ephemeral keypair per call → AES-256-GCM. The * ephemeral public key rides in the envelope, which is what makes the recipient * able to derive the same message key without ever having spoken to the sender. * Fresh per call for the reason `SecretBox` mints a fresh IV: sealing the same * contact twice must not produce the same bytes, and GCM under a reused key+IV * is catastrophic rather than merely weak. * * The `keyId` is bound into the key derivation AND into the GCM additional data, * so an envelope cannot be relabelled to point at a different key without the * open failing. * * Callable from MODULE CODE. Web Crypto is a web standard available in every * runtime the platform targets, which is exactly the exemption module code has * (`globalThis.crypto`); this adds no import a vertical may not make. */ export declare function sealTo(key: SealingPublicKey, plaintext: string): Promise; /** * The refusal raised when an envelope names a key the opener does not hold. * * Typed for the reason `SecretBoxUnconfiguredError` is: it is an OPERATIONAL * fact, not a corrupt payload. After rotation-as-erasure (D-5) it is also the * *expected* answer for an old ciphertext — a scope restored from a backup can * resurrect a pending request whose key has since been destroyed, and that must * read as "the key is gone", not as a mystery in a worker tail. */ export declare class SealedKeyUnavailableError extends Error { readonly keyId: string; constructor(keyId: string, message: string); } /** * Open an envelope with whichever held key it names. * * **Takes a keyId-indexed MAP, not a key** — even when the map has exactly one * member. D-4: widening a single-key column into a set later is a migration * against live connections, and starting with the map is free. The lookup is * also the only thing that makes `keyId` load-bearing rather than decorative. */ export declare function openSealed(privateKeys: Readonly>, sealed: SealedSecret): Promise; /** * The refusal `ctx.sealToConnection` raises when no key for that provider has * been projected into the scope (#687). * * Typed, and separate from `SealedKeyUnavailableError`, because it is a * DEPLOYMENT fact about the write side rather than a fault in a payload: either * the tenant has no live connection for the provider, or the scope was * provisioned before the projection carried keys and has not been reconciled. * Both are fixed by an operator doing something specific, and the message says * which — the same argument `SecretBoxUnconfiguredError` makes. * * **Failing here is the point.** The alternative — emit the request with the * contact silently dropped — is today's invisible failure wearing a new hat: a * document starts at the provider and reaches nobody, and nothing in the system * says so. §7 point 2 of the carrier design makes the deploy order (control * plane and key projection first, vertical second) safe precisely because this * throws rather than degrades. */ export declare class ConnectionSealingKeyUnavailableError extends Error { readonly provider: string; constructor(provider: string, message: string); } /** * The message every adapter raises for a missing projected key, written once so * the pure adapter and the DO adapter cannot drift into two different * explanations of one deployment fact. */ export declare const noSealingKeyMessage: (provider: string, scopeId: string) => string; //# sourceMappingURL=sealed-box.d.ts.map