run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
837 lines • 49.8 kB
TypeScript
/**
* vault — `r402s/v0` crypto core (protocol rev 40, §1 + §2; task 5.1).
*
* Pure, isomorphic (WebCrypto + noble + `@hpke/core`), no I/O. Everything
* byte-level the protocol pins lives here ONCE and is replayed against the
* frozen vector set (`vault.crypto.test.ts`):
*
* - JCS (RFC 8785) for the no-JSON-numbers profile + the strict-parse profile
* - the two hash rules: stored bytes (JCS INCLUDING `signature`) and the
* signature preimage `"r402s/v0/" + object_kind + "\n" + JCS(object minus signature)`
* - Ed25519 strict RFC 8032 — noble with `zip215: false` pinned; a
* ZIP215-only signature is REJECTED (vector `zip215-002`)
* - `lp` / `lp_opt` length prefixes + the open-id / open-binding preimages
* - HKDF-SHA-256 `k_obj` / `K_digest` derivations and HMAC keyed commitments
* - the XChaCha20-Poly1305 frame (`"R402S0"` ‖ suite byte ‖ 24-byte nonce ‖ ct‖tag)
* with its seven-member JCS AAD
* - the `key_envelope` HPKE seal/open (Base mode, DHKEM(X25519,HKDF-SHA256) /
* HKDF-SHA256 / ChaCha20-Poly1305) with the EXACT D188 `info` / AAD bytes
* - genesis key-binding + recovery-receipt checks
*
* Design rules carried from the protocol: HPKE is a NAMED implementation
* (`@hpke/core` + `@hpke/chacha20poly1305`), never assembled from primitives
* (D38); an envelope's signature is verified BEFORE any open; plain hashes of
* plaintext content never leave the client (every server-comparable digest is
* keyed); object ids are single-use key labels — callers MUST NOT re-encrypt
* under an id (retries read-and-compare).
*/
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
import { CipherSuite } from "@hpke/core";
import type { VaultAddWriterKeyPayload, VaultAllocation, VaultDigestLabel, VaultEncryptedObjectKind, VaultEncryptionKeypair, VaultEnvelopeAad, VaultFormat, VaultFrameAad, VaultKeyEnvelope, VaultKeyEnvelopeReceipt, VaultObjectKind, VaultRecoveryReceipt, VaultRotateEpochPayload, VaultSealedFrame, VaultSealedKeyEnvelope, VaultSignedObject, VaultSigningKeypair, VaultStrictParseReason, VaultSuite, VaultGenesis } from "./vault.types.js";
/** Wire format tag (retained by decision D181 across the product rename). */
export declare const VAULT_FORMAT: "r402s/v0";
/** The one V0 cryptographic suite. */
export declare const VAULT_SUITE: "r402s-1";
/** Frame magic, bytes 0–5 of every encrypted object. */
export declare const VAULT_FRAME_MAGIC: "R402S0";
/** Frame suite byte (byte 6) and its two-char AAD spelling. */
export declare const VAULT_FRAME_SUITE_BYTE: 1;
export declare const VAULT_FRAME_SUITE_ID: "01";
/** Protocol §2: max plaintext per encrypted object (256 MiB). */
export declare const VAULT_MAX_PLAINTEXT_BYTES: number;
/** The genesis generation / epoch constants (schema consts). */
export declare const VAULT_GENESIS_GENERATION: "0000000000000000";
export declare const VAULT_GENESIS_EPOCH: "0000000000000001";
/** HPKE `info` label for `key_envelope` (D188). */
export declare const VAULT_ENVELOPE_INFO_LABEL: "r402s/v0/envelope";
/**
* The V0-A terminal-loss sentence — protocol §0 / client-surface spec. Doctor
* and status surfaces MUST state it verbatim while V0-A is current. Do not
* paraphrase; the wording is a reviewed product commitment.
*/
export declare const VAULT_TERMINAL_LOSS_STATEMENT: "whole-machine or whole-keystore loss is terminal for vault history until human envelopes ship";
/**
* The protocol's durability sentence — the keystore-qualified half of the
* terminal-loss statement, reusable on its own. It is TRUE regardless of how
* many principals cover a vault (unlike {@link VAULT_TERMINAL_LOSS_STATEMENT},
* which is specifically the single-principal V0-A claim), so a caller that has
* locally proven a vault carries >= 2 covering recipients prints this sentence
* instead of the terminal-loss one — see `Repos.status()`'s `covering_recipients`
* field. Never paraphrase; the wording is a reviewed product commitment.
*/
export declare const VAULT_DURABILITY_STATEMENT: "The vault protects source history from host-side loss while a principal keystore survives.";
/** Protocol §0 sentence in full, as `doctor`/`status` print it. */
export declare const VAULT_TERMINAL_LOSS_DOCTOR_TEXT: string;
/**
* vault-mirror-and-recover (design D8) — the two honesty statements. Both
* appear VERBATIM wherever mirror status or recovery success is shown (mirror
* `status`/`sync`, `recover`, keyless `mirror verify`), same voice and
* mechanism as {@link VAULT_TERMINAL_LOSS_STATEMENT} above. Never
* paraphrased — the §6.4 honesty posture is a reviewed product commitment.
*/
export declare const VAULT_MIRROR_VALIDITY_NOT_FRESHNESS_STATEMENT: "this recovery proves validity, never freshness \u2014 a mirror (or the vault itself) can only tell you the newest generation it happens to hold, never that a newer one does not exist elsewhere";
/** The mirror does not change the V0 terminal-loss sentence: it is durability, not a second key. */
export declare const VAULT_MIRROR_KEYSTORE_STILL_REQUIRED_STATEMENT: "a mirror without the principal keystore (or an equivalent key) recovers nothing \u2014 mirroring ciphertext does not create a second key, and the V0 terminal-loss sentence is unchanged";
/**
* vault-mirror-default — the recommended-default framing, stated the same
* way everywhere (the copy gate pins the doc surfaces to it). "Replicated"
* describes the platform's storage substrate (S3 multi-AZ redundancy), never
* a second run402 service; the customer-owned mirror is the only copy outside
* run402's custody, and — per the two honesty statements above — it proves
* validity, never freshness, and recovers nothing without the keystore.
*/
export declare const VAULT_MIRROR_THREE_COPIES_STATEMENT: "the recommended shape is three copies: your working clone, the platform's replicated vault, and a mirror in storage you own";
/**
* vault-byo-primary-bucket (design D4) — the degraded-read mechanism
* sentence: the invariant part of the ONE stderr line a degraded chain/
* payload read prints (`formatVaultDegradedReadNote`, `vault-node-
* degraded-read.ts`). Mechanism-only, per the copy rules — never a
* confidentiality claim: it names WHY (run402 is unreachable), restates
* validity-not-freshness in brief, and states that a write still needs the
* gateway (D194 — admission is irreducibly live-server, so writes are never
* rerouted). The caller prepends the fallback's own destination (never a
* credential); that is data, not part of this claim, so it lives outside
* the constant. Never paraphrase — same reviewed-product-commitment voice
* as the other statements in this file.
*/
export declare const VAULT_DEGRADED_READ_STATEMENT: "run402 is unreachable \u2014 this read is served from your mirror; it proves validity, not freshness; a later push still requires the gateway";
/**
* vault-mirror-default — the `vault_unmirrored` standing finding, worded to
* stay true in BOTH pre-clear states (no mirror configured at all, and a
* configured mirror with no successful write/sync yet — the finding clears on
* the first successful mirror write or sync, never on configuration alone).
* Informational, never blocking; computed client-side only, so the gateway
* never learns whether a mirror exists.
*/
export declare const VAULT_UNMIRRORED_FINDING_STATEMENT: "this vault has no customer-held mirror copy yet — the recommended shape is three copies: your working clone, the platform's replicated vault, and a mirror in storage you own; until a first mirror write or sync succeeds, the third copy does not exist";
/**
* vault-mirror-default — the one-liner `repos create` prints beside the
* recovery receipt: the two things worth doing in the first minute, stated in
* the first minute. Deliberately custody-scoped ("stays in your custody"),
* never a recoverability claim — the terminal-loss statement printed beside it
* carries the key half.
*/
export declare const VAULT_MIRROR_SETUP_HINT: "recommended: 'run402 repos mirror <destination>' (an s3:// bucket or a local directory) starts the customer-owned mirror \u2014 every later capture is mirrored to it automatically, and it is the copy that stays in your custody";
/**
* vault-byo-primary-bucket (proposal, the ratified D + chain-copy rung-3
* headline; design D10 — verified from network topology once the client's
* BYO write path ships, not by this sentence alone). Structurally scoped to
* `storage_profile:"byo"` ONLY — the sentence is FALSE for a managed vault
* (payload ciphertext IS client-written to run402's own bucket there), so
* every caller MUST gate it behind a BYO-profile check before printing it
* and MUST NEVER use it as a blanket claim describing vault in general.
* INERT as of this fold (Phase 1, protocol coordination only) — nothing
* emits it yet; Phase 2/3 wire the allocation/doctor/`repos view` surfaces
* that print it once `storage_profile:"byo"` is a real, allocatable vault
* state. Never paraphrase — the wording is a reviewed product commitment.
*/
export declare const VAULT_BYO_HEADLINE_STATEMENT: "your source ciphertext never touches our infrastructure \u2014 not even encrypted";
/**
* vault-byo-primary-bucket (design D7) — the allocation-time no-payload-copy
* disclosure a BYO `repos create` prints, unconditionally and independent of
* mirror status: unlike a managed vault (where the platform's own bucket is
* an extra copy beside the customer's clone), a BYO vault's primary bucket is
* the ONLY payload copy from the moment it is allocated — run402 holds the
* small signed chain only, never the payload. Distinct from
* {@link VAULT_BYO_UNMIRRORED_REMEDY_STATEMENT} below, which is about a
* missing SECOND customer-held copy on top of this primary one. INERT as of
* this fold — Phase 2/3 wire the surfaces that print it. Never paraphrase.
*/
export declare const VAULT_BYO_NO_PAYLOAD_COPY_STATEMENT: "run402 holds no payload copy of a BYO vault \u2014 only the small signed chain; your primary bucket is the sole copy of your source until you add a second customer-held location";
/**
* vault-byo-primary-bucket (design D7) — the BYO variant of the
* `vault_unmirrored` finding's remedy (vault-mirror-default): for a
* managed vault the remedy is an ADDITIONAL mirror beside the platform's own
* replicated copy ({@link VAULT_MIRROR_SETUP_HINT}); for a BYO vault the
* platform holds no payload copy to fall back on
* ({@link VAULT_BYO_NO_PAYLOAD_COPY_STATEMENT}), so the remedy names a
* SECOND customer-held location as the only additional copy available — the
* ordinary `repos mirror` machinery works unchanged against a different
* destination (design D7). The `vault_unmirrored` finding itself still
* applies to BYO vaults; only this remedy half is BYO-specific. INERT as of
* this fold — Phase 2/3 wire the surfaces that print it. Never paraphrase.
*/
export declare const VAULT_BYO_UNMIRRORED_REMEDY_STATEMENT: "add a second customer-held location: 'run402 repos mirror <destination>' works unchanged against a different destination \u2014 for a BYO vault this is your only additional copy, since run402 holds no payload copy of its own";
export declare const VAULT_HEX16_RE: RegExp;
export declare const VAULT_HEX32_RE: RegExp;
export declare const VAULT_SHA256_RE: RegExp;
export declare const VAULT_OID40_RE: RegExp;
export declare const VAULT_VK_RE: RegExp;
export declare const VAULT_EK_RE: RegExp;
export declare const VAULT_SRC_RE: RegExp;
export declare const VAULT_SERVICE_KEY_ID_RE: RegExp;
export declare const VAULT_SIZE_BYTES_RE: RegExp;
/** 32 bytes base64url: 43 chars, final char in the 16-value padding class. */
export declare const VAULT_B64U_32_RE: RegExp;
/** 64 bytes base64url: 86 chars, final char ∈ {A,Q,g,w}. */
export declare const VAULT_B64U_64_RE: RegExp;
/** The HPKE-sealed K_repo: 48 bytes = exactly 64 base64url chars. */
export declare const VAULT_HPKE_CT_RE: RegExp;
export declare const VAULT_TIMESTAMP_RE: RegExp;
/** Canonical base64url (no padding, length mod 4 ≠ 1, zero trailing bits): decode→encode round-trips. */
export declare function isCanonicalBase64url(value: string): boolean;
/** Decode canonical base64url or throw (`VAULT_NONCANONICAL_BASE64URL`). */
export declare function fromBase64url(value: string, field?: string): Uint8Array;
export declare function toBase64url(bytes: Uint8Array): string;
/** RFC 3339 UTC ms `Z` with semantic calendar validation (D187: `2026-02-31` and a non-leap Feb 29 are rejects). */
export declare function isValidVaultTimestamp(value: string): boolean;
/** Format a Date as the protocol timestamp grammar (RFC 3339 UTC, milliseconds, `Z`). */
export declare function formatVaultTimestamp(date?: Date): string;
/** `lp(x)` = 4-byte big-endian UTF-8 byte length ‖ UTF-8 bytes (protocol §1). */
export declare function lp(value: string): Uint8Array;
/** `lp_opt(null)` = `0x00`; `lp_opt(x)` = `0x01` ‖ `lp(x)`. */
export declare function lpOpt(value: string | null): Uint8Array;
/**
* The protocol-hash seam (vault-native-hash) — the second narrow slot
* beside the bulk-AEAD one below, same doctrine: the core owns a slot and the
* Node entry fills it (capability is INJECTED at the entry point, never
* sniffed in the core), and with nothing registered the `@noble/hashes` path
* runs byte-for-byte as before. `sha256Hex` is the single chokepoint every
* protocol hash flows through — frame receipt hashes, plaintext pack checks,
* stored-bytes preimages, fingerprints, ledger ids — so this one dispatch
* converts them all. `hkdf`/`hmac` deliberately stay on `@noble`: they
* consume the hash CONSTRUCTOR, not the digest, and are small-input.
*/
export interface VaultHashBackend {
/** SHA-256 digest — 32 bytes, byte-identical to `@noble/hashes`'. */
sha256(bytes: Uint8Array): Uint8Array;
}
/**
* SDK-INTERNAL (underscore-exported so the byte-equality suite can swap
* backends). Installing `null` restores the `@noble` default.
*/
export declare function _setVaultHashBackend(backend: VaultHashBackend | null): void;
/** SDK-INTERNAL: which hash backend is live. The isomorphic-entry test asserts `null`. */
export declare function _vaultHashBackend(): VaultHashBackend | null;
export declare function sha256Hex(bytes: Uint8Array): string;
/** CSPRNG bytes from the platform WebCrypto (protocol §2: all keys/nonces/ephemerals from the OS CSPRNG). */
export declare function randomBytes(length: number): Uint8Array;
/** A fresh `<prefix>_<32 lowercase hex>` id (protocol §1: ids are CSPRNG). */
export declare function newVaultId(prefix: string): string;
/** A fresh 32-lowercase-hex scalar (client_creation_id, capture_id, …). */
export declare function newHex32(): string;
/** RFC 8785 canonical JSON (no-numbers profile) as UTF-8 bytes. */
export declare function jcs(value: unknown): Uint8Array;
/** RFC 8785 canonical JSON as a string. */
export declare function jcsString(value: unknown): string;
/**
* The §1 strict-parse profile: reject ANY JSON number, duplicate members,
* invalid JSON, and any text that is not byte-for-byte the JCS of its value
* (member order, whitespace, escapes). Schema-awareness (unknown members,
* hex case, scalar lengths) is the caller's job.
*/
export declare function parseVaultStrict(text: string): unknown;
/** Classify a strict-parse failure (for vector replay + diagnostics). */
export declare function vaultStrictParseReason(error: unknown): VaultStrictParseReason | null;
/** Stored bytes of a signed object = JCS of the COMPLETE object including `signature`. */
export declare function storedBytes(object: VaultSignedObject): Uint8Array;
/** SHA-256 over stored bytes — what every `*_sha256` naming a signed object means. */
export declare function storedBytesSha256(object: VaultSignedObject): string;
/**
* Exported so the rotation producer (D195) can build the SAME
* signature-stripped shape `rotation_id`'s own derivation needs
* (`sha256(JCS(descriptor minus signature))`) without reaching into this
* module's private helper twice under two different names.
*/
export declare function vaultWithoutSignature<T extends {
signature?: unknown;
}>(object: T): Omit<T, "signature">;
/**
* Signature preimage = `"r402s/v0/" + domain + "\n" + JCS(object without its
* single top-level signature)`. `domain` is USUALLY a `VaultObjectKind`
* (an actual `object_kind`-carrying signed object), but the type accepts any
* string: vault-multi-writer rev 47's `writer_acceptance.statement` reuses
* this exact byte-construction under the ad-hoc domain
* `"handoff-writer-accept/v1"` for a sub-object that is NOT itself a
* `VaultSignedObject` (no `object_kind`/`signature` fields of its own —
* both signatures over it live on the PARENT `writer_acceptance`). Matches
* the gateway's own `signaturePreimage(objectKind: string, ...)`, which was
* never narrower than `string` to begin with.
*/
export declare function signaturePreimage(domain: VaultObjectKind | string, objectWithoutSignature: Record<string, unknown>): Uint8Array;
export declare function ed25519PublicKey(seed: Uint8Array): Uint8Array;
export declare function ed25519Sign(message: Uint8Array, seed: Uint8Array): Uint8Array;
/**
* Strict RFC 8032 verification: `zip215: false` is PINNED. Non-canonical R or A
* encodings, non-canonical S, and wrong-length inputs are all `false` — never
* an exception a caller could mistake for a transport error.
*/
export declare function ed25519VerifyStrict(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean;
/** Sign an object under its `object_kind` domain; returns the complete signed object. */
export declare function signVaultObject<T extends {
object_kind: VaultObjectKind;
}>(objectWithoutSignature: T & {
signature?: never;
}, seed: Uint8Array): T & {
signature: string;
};
/**
* Verify a signed object against a raw public key. Exact scalar lengths are
* checked FIRST (an 85-char signature or a 42-char key is a schema reject,
* never a decode-time surprise), then strict Ed25519 under the object's domain.
*/
export declare function verifyVaultObject(object: VaultSignedObject, publicKey: Uint8Array | string): boolean;
/** Verify or throw `VAULT_SIGNATURE_INVALID`. */
export declare function assertVaultSignature(object: VaultSignedObject, publicKey: Uint8Array | string, context?: string): void;
/** First 16 bytes of SHA-256(raw pubkey), lowercase hex (32 chars). */
export declare function keyFingerprintHex(rawPublicKey: Uint8Array): string;
/** `vk_` fingerprint of a raw Ed25519 public key. */
export declare function vkFingerprint(rawPublicKey: Uint8Array): string;
/** `ek_` fingerprint of a raw X25519 public key. */
export declare function ekFingerprint(rawPublicKey: Uint8Array): string;
export declare function generateSigningKeypair(seed?: Uint8Array): VaultSigningKeypair;
export declare function generateEncryptionKeypair(privateKey?: Uint8Array): VaultEncryptionKeypair;
/** `k_obj` info = lp("r402s/v0") ‖ lp(suite) ‖ lp(repo_id) ‖ lp(epoch) ‖ lp(object_kind) ‖ lp(object_id) ‖ lp("L=32"). */
export declare function objectKeyInfo(repoId: string, epoch: string, objectKind: VaultEncryptedObjectKind, objectId: string): Uint8Array;
/** `k_obj = HKDF-SHA-256(ikm=K_repo, salt=∅, info, L=32)`. */
export declare function deriveObjectKey(kRepo: Uint8Array, repoId: string, epoch: string, objectKind: VaultEncryptedObjectKind, objectId: string): Uint8Array;
/** `K_digest` info = lp("r402s/v0") ‖ lp("r402s-1") ‖ lp(repo_id) ‖ lp(epoch) ‖ lp("digest") ‖ lp(label) ‖ lp("L=32"). */
export declare function digestKeyInfo(repoId: string, epoch: string, label: VaultDigestLabel): Uint8Array;
export declare function deriveDigestKey(kRepo: Uint8Array, repoId: string, epoch: string, label: VaultDigestLabel): Uint8Array;
/**
* The general form `deriveDigestKey` is a fixed-`ikm=K_repo` specialization
* of (D195/D198/D200, rev 42): every rev-42 rotation label is keyed by the
* SAMPLED epoch key (`K_e` for a rotation, `K_1` for genesis) instead of
* `K_repo` — "the value being committed IS the key being distributed"
* (protocol-v0.md S1). Same HKDF info construction either way.
*/
export declare function deriveDigestKeyFrom(ikm: Uint8Array, repoId: string, epoch: string, label: VaultDigestLabel): Uint8Array;
/** Commitment = HMAC-SHA-256(K_digest(label), JCS(content)), lowercase hex. */
export declare function keyedCommitment(kDigest: Uint8Array, content: unknown): string;
/** The `"objectset"` content shape: `{oids:[sorted unique lowercase 40-hex]}`. */
export declare function objectsetContent(oids: Iterable<string>): {
oids: string[];
};
/** True iff an `objectset` content is already in canonical (sorted-unique) order. */
export declare function isCanonicalObjectset(content: {
oids: string[];
}): boolean;
/** The `"snapshot_oid"` content shape. */
export declare function snapshotOidContent(oid: string): {
oid: string;
format: "sha1";
};
/** `"r402s/v0/open-id" ‖ lp(org_id) ‖ lp(repo_id) ‖ lp(client_open_id)`. */
export declare function openIdPreimage(orgId: string, repoId: string, clientOpenId: string): Uint8Array;
/** `"r402s/v0/open-binding" ‖ lp(client_open_id) ‖ lp(base_head_sha256) ‖ lp_opt(prior) ‖ lp(requested_r2_cap_size_bytes)`. */
export declare function openBindingPreimage(clientOpenId: string, baseHeadSha256: string, priorCheckpointClaimSetSha256: string | null, requestedR2CapSizeBytes: string): Uint8Array;
/** The seven-member frame AAD, as the object JCS serializes. */
export declare function frameAad(repoId: string, objectKind: VaultEncryptedObjectKind, objectId: string, epoch: string): VaultFrameAad;
/**
* The bulk-AEAD seam (vault-native-bulk-crypto, design D1/D3).
*
* `sealFrame`/`openFrame`/`openFrameWithAad` are the BULK path — every WAL
* pack, checkpoint pack, `ref_state` and `retention_roots` frame runs through
* them — and they are SYNC and ISOMORPHIC, so a faster runtime-specific
* implementation can be neither statically imported (the core must load
* outside Node) nor lazily awaited (the signatures are sync). So the core owns
* a slot and the Node entry fills it, exactly as that entry already layers on
* the keystore and wallet: capability is INJECTED at the entry point, never
* sniffed in the core.
*
* The contract is narrow on purpose (D3): the FRAME primitives only. Envelope
* scalar unwrap, HPKE, HKDF, HMAC, signing and hashing keep their current
* implementations — they are microseconds, and a small seam is a small review
* surface. With nothing registered (every non-Node runtime, and the
* independent `r402s-verify` lineage, which deliberately registers nothing so
* it stays a SECOND implementation of the wire format) the `@noble/ciphers`
* path below runs byte-for-byte as before.
*/
export interface VaultAeadBackend {
/** XChaCha20-Poly1305 seal → `ct‖tag`, byte-identical to `@noble`'s. */
seal(key32: Uint8Array, nonce24: Uint8Array, aad: Uint8Array, plaintext: Uint8Array): Uint8Array;
/** Open `ct‖tag`; `null` on authentication failure — never a throw of its own shape (D4). */
open(key32: Uint8Array, nonce24: Uint8Array, aad: Uint8Array, ciphertextAndTag: Uint8Array): Uint8Array | null;
}
/**
* SDK-INTERNAL (underscore-exported so the byte-equality suite can swap
* backends). Installing `null` restores the `@noble` default — which is what
* every runtime without a registration already has.
*/
export declare function _setVaultAeadBackend(backend: VaultAeadBackend | null): void;
/** SDK-INTERNAL: which backend is live. The verifier-lineage test asserts `null`. */
export declare function _vaultAeadBackend(): VaultAeadBackend | null;
export interface VaultSealFrameInput {
/** The per-object key — derive with {@link deriveObjectKey}. */
k_obj: Uint8Array;
repo_id: string;
object_kind: VaultEncryptedObjectKind;
object_id: string;
epoch: string;
plaintext: Uint8Array;
/** 24-byte nonce; CSPRNG when omitted. Vectors inject it for determinism. */
nonce?: Uint8Array;
}
/**
* Seal a plaintext into the §2 frame: `"R402S0"` ‖ `0x01` ‖ nonce(24) ‖ ct‖tag.
* `ciphertext_sha256` covers the COMPLETE frame. Callers own the single-use
* rule: one object id, one seal — never re-encrypt under an id.
*/
export declare function sealFrame(input: VaultSealFrameInput): VaultSealedFrame;
export interface VaultOpenFrameInput {
k_obj: Uint8Array;
repo_id: string;
object_kind: VaultEncryptedObjectKind;
object_id: string;
epoch: string;
frame: Uint8Array;
/** When given, the frame's SHA-256 must equal the receipt BEFORE any decryption (the header is authenticated twice: by the receipt and by the AAD). */
expected_ciphertext_sha256?: string;
}
/**
* Open a §2 frame: header checked (magic, suite byte, length), receipt
* compared when supplied, then the AEAD opened under the seven-member AAD.
* No unverified streaming release — the whole plaintext or nothing.
*/
export declare function openFrame(input: VaultOpenFrameInput): Uint8Array;
/**
* The AEAD half of {@link openFrame} with the AAD supplied verbatim — the
* vector replay uses it to prove every one of the seven AAD members is
* authenticated (including the constant `suite`/`magic`/`suite_id` members
* that {@link openFrame}'s typed input cannot vary). Header already checked.
*/
export declare function openFrameWithAad(kObj: Uint8Array, frame: Uint8Array, aad: Uint8Array, objectId?: string): Uint8Array;
/** The one `r402s-1` HPKE suite: Base mode, DHKEM(X25519,HKDF-SHA256) 0x0020 / HKDF-SHA256 0x0001 / ChaCha20-Poly1305 0x0003. */
export declare function vaultHpkeSuite(): CipherSuite;
/** HPKE `info` (D188): lp("r402s/v0/envelope") ‖ lp("r402s-1") ‖ lp(lowerhex(SHA-256(recipient raw X25519 pubkey))) ‖ lp(created_by). */
export declare function envelopeInfo(recipientPublicKeyRaw: Uint8Array, createdBy: string): Uint8Array;
/**
* HPKE AAD (D188, discriminated by `rotation_id` presence — D203, rev 42):
* `rotation_id` ABSENT (genesis / ADD-workaround envelope) -> the rev-41
* four-field form, BYTE-IDENTICAL to every existing golden HPKE digest;
* `rotation_id` PRESENT (a rotation-attempt envelope) -> the new five-field
* form. Field ORDER in the returned object does not matter (JCS sorts
* object members), but `rotation_id` must never be included as an explicit
* `null` — the two shapes are genuinely different objects, not one object
* with an optional-null member (protocol-v0.md S2).
*/
export declare function envelopeAad(repoId: string, epoch: string, recipientFingerprint: string, rotationId?: string | null): VaultEnvelopeAad;
export interface VaultHpkeSealInput {
recipient_public_key: Uint8Array;
info: Uint8Array;
aad: Uint8Array;
plaintext: Uint8Array;
/** RFC 9180 §7.1.3 `DeriveKeyPair(ikmE)` injection — TEST/vector determinism only; production seals use the OS CSPRNG. */
ikm_e?: Uint8Array;
}
/** Single-shot HPKE seal (Base mode) returning `{enc, ct}`. */
export declare function hpkeSeal(input: VaultHpkeSealInput): Promise<{
enc: Uint8Array;
ct: Uint8Array;
}>;
export interface VaultHpkeOpenInput {
recipient_private_key: Uint8Array;
enc: Uint8Array;
info: Uint8Array;
aad: Uint8Array;
ct: Uint8Array;
}
/** Single-shot HPKE open; any failure is `VAULT_HPKE_OPEN_FAILED` (never garbage plaintext). */
export declare function hpkeOpen(input: VaultHpkeOpenInput): Promise<Uint8Array>;
export interface VaultSealKeyEnvelopeInput {
/** The raw 32-byte secret being distributed — `K_repo`/`K_1` at genesis, `K_e` for a rotation. */
k_repo: Uint8Array;
repo_id: string;
epoch: string;
/** The recipient's raw X25519 public key. */
recipient_public_key: Uint8Array;
/** The creator's signing keypair; its `vk_` fingerprint becomes `created_by`. */
signer: VaultSigningKeypair;
created_at: string;
/**
* PRESENT for a rotation-attempt envelope (D195/D196, rev 42): widens the
* path to `(repo_id, epoch, rotation_id, recipient_fingerprint)` and the
* HPKE AAD to the five-field form. ABSENT/omitted for a genesis/ADD-
* workaround envelope — byte-identical to rev 41.
*/
rotation_id?: string | null;
/** Vector determinism only. */
ikm_e?: Uint8Array;
}
/** Seal + sign a `key_envelope` for one principal; returns the object with its stored-bytes identity + genesis receipt. */
export declare function sealKeyEnvelope(input: VaultSealKeyEnvelopeInput): Promise<VaultSealedKeyEnvelope>;
export interface VaultOpenKeyEnvelopeInput {
envelope: VaultKeyEnvelope;
/** The recipient's raw X25519 keypair (the public half feeds `info`; the fingerprint must match the path identity). */
recipient: VaultEncryptionKeypair;
/** The creator's raw Ed25519 public key — the envelope signature is verified BEFORE any open. */
signer_public_key: Uint8Array | string;
}
/**
* Open a `key_envelope`: exact-scalar checks → signature (strict, domain
* `key_envelope`) → path identity (recipient fingerprint = ours) → HPKE open
* with the D188 info/AAD (widened to the five-field form when the envelope
* itself carries `rotation_id` — D195/D203). Returns the raw 32-byte secret
* (`K_repo`/`K_1` at genesis, `K_e` for a rotation).
*/
export declare function openKeyEnvelope(input: VaultOpenKeyEnvelopeInput): Promise<Uint8Array>;
/**
* Decode + validate one head's `transition.payload` as a `rotate_epoch_payload`
* (D193-D203, rev 42): base64url-jcs decode, `payload_sha256` self-check,
* strict JSON parse, and the payload's OWN `new_epoch` agreeing with the
* carrying head's `epoch` field (a second, independent check over the SIGNED
* payload bytes — separate from {@link checkChainLink}'s epoch-continuity
* check on the head's wire `epoch` field itself, protocol-v0.md S4.3). Pure
* and keyless: this never opens an envelope, so every reader can call it
* (including the fully keyless chain walk) before any key material is needed.
*/
export declare function parseRotateEpochPayload(head: {
generation: string;
epoch: string;
transition: {
kind: string;
payload_format: string;
payload: string;
payload_sha256: string;
} | null;
}): VaultRotateEpochPayload;
/**
* Decode + validate one head's `transition.payload` as an
* `add_writer_key_payload` (vault-multi-writer rev 47, protocol §4.16):
* base64url-jcs decode, `payload_sha256` self-check, strict JSON parse, and
* the payload's OWN `repo_id` agreeing with the carrying head's — mirrors
* {@link parseRotateEpochPayload} exactly, `repo_id` in place of `epoch`
* (this transition never changes the epoch). Pure and keyless: the FULL
* cryptographic + structural validation (signatures, base/next writer-set
* commitments, handoff-grant binding) is `validateAddWriterKeyPayload`'s job
* (`../node/vault-writer-state.js`) — this function only gets the bytes
* off the wire safely, so every reader can call it before any key material
* is needed.
*/
export declare function parseAddWriterKeyPayload(head: {
generation: string;
repo_id: string;
transition: {
kind: string;
payload_format: string;
payload: string;
payload_sha256: string;
} | null;
}): VaultAddWriterKeyPayload;
/**
* Open ONE `rotate_epoch` transition's own `key_envelope` for
* `own_fingerprint` and return the derived `K_e`, after verifying it
* reproduces the payload's `epoch_key_commitment` (D200's per-recipient
* self-check — never a global set-coherence proof, §14ao/§2). Fails CLOSED
* with `VAULT_EPOCH_NOT_OPENABLE` — never a bare `VAULT_AEAD_AUTH_FAILURE`
* — either when this recipient is not among the rotation's `envelopes[]` at
* all, or when the addressed envelope's stored bytes are absent or altered.
*
* Transport-agnostic by design (`get_envelope_bytes` + `envelope_path`
* callbacks): this is the ONE place a reader opens a rotation envelope, and
* both the live publication read path (`Vault.verifyToNewest`, over
* the gateway transport) and the offline mirror recovery path
* (`recoverVaultMirror`, over a `VaultMirrorBackend`) call it —
* differing only in HOW they fetch bytes, never in the open/verify logic.
*/
export declare function openEpochRotationForRecipient(input: {
repo_id: string;
payload: VaultRotateEpochPayload;
own_fingerprint: string;
own_encryption_keypair: VaultEncryptionKeypair;
writer_signing_public_key: Uint8Array | string;
get_envelope_bytes: (path: string) => Promise<Uint8Array | null | undefined>;
envelope_path: (epoch: string, recipient_fingerprint: string, rotation_id: string) => string;
}): Promise<Uint8Array>;
export interface VaultBuildGenesisInput {
repo_id: string;
org_id: string;
project_id: string;
allocation_nonce: string;
creator_signing: VaultSigningKeypair;
creator_encryption_public_key: Uint8Array;
envelope_receipt: VaultKeyEnvelopeReceipt;
created_at: string;
}
/** Build + sign a `vault_genesis` (generation 0, epoch 1, the creator's single envelope). */
export declare function buildVaultGenesis(input: VaultBuildGenesisInput): VaultGenesis;
/** A named genesis key-binding failure (semantic constraints `genesis-key-bindings` / `envelope-receipt-binding`). */
export type VaultGenesisBindingProblem = "envelope_count" | "envelope_epoch" | "envelope_recipient_fingerprint" | "writer_key_id" | "stored_envelope_hash" | "stored_envelope_size" | "stored_envelope_repo_id" | "stored_envelope_epoch" | "stored_envelope_recipient_fingerprint" | "stored_envelope_created_by";
/**
* The genesis key bindings (round-10 A6): exactly one envelope at epoch 1 whose
* `recipient_fingerprint` is the creator's `ek_`; `writer_key_id` is the
* creator's `vk_`; and, when the stored envelope is supplied, the receipt's
* hash/size name its stored bytes and its path identity + `created_by` equal
* the genesis values. Schema-valid-but-protocol-invalid objects surface here.
*/
export declare function checkGenesisKeyBindings(genesis: VaultGenesis, storedEnvelope?: VaultKeyEnvelope): VaultGenesisBindingProblem[];
export interface VaultBuildRecoveryReceiptInput {
repo_id: string;
org_id: string;
project_id: string;
genesis_sha256: string;
creator_signing: VaultSigningKeypair;
creator_encryption_public_key: Uint8Array;
}
/** Build + sign the creator's `recovery_receipt` (emitted at ACTIVE; integrity data, not a secret). */
export declare function buildRecoveryReceipt(input: VaultBuildRecoveryReceiptInput): VaultRecoveryReceipt;
export type VaultRecoveryReceiptProblem = "signature" | "genesis_sha256" | "repo_id" | "org_id" | "project_id" | "creator_signing_fingerprint" | "creator_encryption_fingerprint";
/**
* Check a recovery receipt against a genesis it claims to pin: the receipt's
* signature (under the genesis's creator key), the genesis stored-bytes hash,
* and every identity field. A substituted, internally consistent vault fails
* on `genesis_sha256` — exactly the fabricated-vault scenario.
*/
export declare function checkRecoveryReceipt(receipt: VaultRecoveryReceipt, genesis: VaultGenesis): VaultRecoveryReceiptProblem[];
/**
* Field-level checks on an `allocation` for THIS creation attempt (the
* control-plane signature itself is verified through the service-key registry,
* which task 5.4 wires; pass `service_public_key` once it is pinned).
*
* `expected.org_id` / `expected.project_id` are OPTIONAL (repo-first-onramp
* task 4.5's push-to-create addition): a push-to-create attempt does not know
* its org/project ahead of the allocation response (only `org_slug`/
* `repo_name`), so those two fields are trust-on-first-use — the caller skips
* the comparison on the FIRST allocation of a fresh push-to-create journal and
* pins whatever the (owner-authorized, signature-verified) allocation says,
* then compares against that pin on every later call for the SAME journal.
* Every other field is checked unconditionally, same as before.
*/
export declare function checkAllocation(allocation: VaultAllocation, expected: {
client_creation_id: string;
creator_signing_fingerprint: string;
creator_encryption_fingerprint: string;
org_id?: string;
project_id?: string;
}, servicePublicKey?: Uint8Array | string): string[];
/** 16-hex epochs compare/increment as unsigned integers (chain.ts's `nextGeneration`, mirrored). */
export declare function epochValue(hex16: string): bigint;
export declare function nextEpoch(hex16: string): string;
export interface VaultIncludedPair {
principal_id: string;
ek_fingerprint: string;
}
/**
* D196: `target_partition_digest = SHA-256(JCS({recipient_state_version,
* recipient_revocation_version, pin_manifest_version, pin_manifest_sha256,
* included: [sorted {principal_id, ek_fingerprint}], excluded_keyless_principal_ids:
* [sorted], excluded_unconfirmed_principal_ids: [sorted]}))`. The producer
* calls this ONCE to build the value it embeds in both the descriptor
* (D195) and the payload (D196); the gateway recomputes the SAME formula
* twice server-side and refuses on any disagreement — so a bug here is a
* `RECIPIENT_SET_MISMATCH` at admission time, not a silent forgery.
*/
export declare function computeTargetPartitionDigest(input: {
recipient_state_version: string;
recipient_revocation_version: string;
pin_manifest_version: string;
pin_manifest_sha256: string;
included: readonly VaultIncludedPair[];
excluded_keyless_principal_ids: readonly string[];
excluded_unconfirmed_principal_ids: readonly string[];
}): string;
/**
* D195: `rotation_id = lowerhex(SHA-256(JCS(the complete descriptor object,
* MINUS signature)))` — the full 64-hex digest, computed over the descriptor
* INCLUDING `attempt_key_commitment` (a field ON the descriptor). Callers
* pass the SIGNED descriptor (or the unsigned one — the signature member is
* stripped either way, matching the gateway's `computeRotationId`).
*/
export declare function computeRotationId(descriptor: Record<string, unknown> & {
signature?: unknown;
}): string;
/**
* D195's `attempt_key_commitment` preimage: the descriptor's OWN fields
* minus `attempt_key_commitment` itself (and minus `signature`, which does
* not exist yet at this point in the build) — `rotation_id` is never a
* member of the descriptor object at all (it is the object's derived path
* key), so there is nothing to additionally strip for it. Noncircular by
* construction: `attempt_key_commitment` must exist before `rotation_id`
* can be computed FROM the complete descriptor.
*/
export declare function attemptKeyCommitmentPreimage(descriptorFields: {
format: VaultFormat;
object_kind: "rotation_attempt_descriptor";
suite: VaultSuite;
repo_id: string;
base_head_sha256: string;
new_epoch: string;
recipient_state_version: string;
recipient_revocation_version: string;
pin_manifest_sha256: string;
target_partition_digest: string;
client_idempotency_key: string;
writer_key_id: string;
}): Record<string, unknown>;
/**
* D195/§1's `"epoch_rotation_attempt"` commitment: `HMAC-SHA-256(K_digest(
* "epoch_rotation_attempt", repo_id, new_epoch), JCS(the descriptor's own
* fields minus attempt_key_commitment), ikm=K_e)` — keyed by the SAMPLED
* epoch key being distributed, not `K_repo` (D195/D200/§1: "every one keyed
* by the SAMPLED epoch key... since the value being committed IS the key
* being distributed"). `K_digest`'s own `epoch` parameter is `new_epoch`
* (this rotation's OWN epoch) — the natural, and only internally-consistent,
* reading of §1's generic formula for a value that is a property of the new
* epoch's key; there is no gateway-side computation of this value to cross-
* check against (the gateway never sees `K_e`), so this is the SDK's own
* considered wire-shape decision, stated here rather than guessed silently.
*/
export declare function attemptKeyCommitment(kE: Uint8Array, repoId: string, newEpoch: string, descriptorFields: Parameters<typeof attemptKeyCommitmentPreimage>[0]): string;
/**
* D200/§1's `"epoch_rotation"` commitment: `HMAC-SHA-256(K_digest(
* "epoch_rotation", repo_id, new_epoch), JCS({rotation_id, fingerprints:
* [sorted pairwise-distinct]}), ikm=K_e)`. Proves a PER-RECIPIENT self-check
* only (D200's narrowed claim) — never global set-coherence. A recipient
* who opens their own envelope recovers `K_e` + `epoch` (== `new_epoch`)
* directly from the envelope itself, so they can recompute this independent
* of the descriptor/payload — confirming `new_epoch` is the right `K_digest`
* epoch parameter (the same value both the producer and every recipient
* arrive at without coordination).
*/
export declare function epochRotationKeyCommitment(kE: Uint8Array, repoId: string, newEpoch: string, rotationId: string, fingerprints: readonly string[]): string;
/**
* D198's genesis-specific `"vault_genesis_epoch_key"` commitment — the same
* per-recipient-self-check discipline applied to an N-recipient genesis,
* with its own noncircular preimage (no `rotation_id` exists at generation
* zero). Not built by this SDK's genesis path today (single-recipient
* genesis only); exported for vector generation / future N-recipient
* genesis support.
*/
export declare function genesisEpochKeyCommitment(k1: Uint8Array, repoId: string, fingerprints: readonly string[]): string;
/**
* D195's producer obligation the round-3 review's own decisive witness
* forces: "a producer that cannot prove it is resuming its OWN prior
* in-flight attempt... MUST mint a fresh `client_idempotency_key`... before
* uploading any envelope." The corollary — and what actually PREVENTS the
* complementary-path mosaic client-side, before the descriptor's create-only
* CAS even gets a chance to catch it server-side — is that a fresh K_e must
* never equal any epoch key this client has ever locally held (constant-time
* comparison; a match is evidence of a broken CSPRNG or a reused seed, not a
* legitimate resume, since a genuine resume reuses the SAME
* client_idempotency_key AND therefore builds the identical descriptor, not
* merely the same K_e in isolation).
*/
export declare function checkFreshEpochKeyAgainstPriorKeys(kE: Uint8Array, priorKeys: Iterable<Uint8Array>): void;
export type VaultHPartitionVerdict = {
ok: true;
} | {
ok: false;
detail: string;
};
/**
* D196's pair-level bijection, mirrored client-side as a PRE-SUBMIT
* self-check (the gateway's own recomputation under its live desired-state
* lock remains the authoritative check — this exists so a client-side
* partition bug surfaces as a clear local error instead of a round trip
* that ends in `RECIPIENT_SET_MISMATCH`).
*/
export declare function checkHPartition(input: {
desiredPrincipalIds: ReadonlySet<string>;
keyedPrincipalIds: ReadonlySet<string>;
pinnedFingerprintOf: ReadonlyMap<string, string>;
included: readonly VaultIncludedPair[];
excludedKeylessPrincipalIds: readonly string[];
excludedUnconfirmedPrincipalIds: readonly string[];
/**
* Rotation inclusion by writer set (kygit-handoff's member-removal
* decision, mirrored from the gateway's own check): a keyed principal in
* H whose signing key is an active writer on the chain and survives this
* rotation is included on its CURRENT directory fingerprint, pin or no
* pin, and may never sit in `excluded_unconfirmed`. A live pin still
* governs a pinned principal exactly as before. Absent means the pin-only
* rule.
*/
survivingWriterPrincipalIds?: ReadonlySet<string>;
directoryFingerprintOf?: ReadonlyMap<string, string>;
}): VaultHPartitionVerdict;
/** D197's full-map conservation rule: `next_map = prior_map + receipt-authorized additions/replacements`, deletion-by-omission forbidden. */
export declare function checkPinManifestConservation(prior: readonly {
principal_id: string;
}[], next: readonly {
principal_id: string;
}[]): VaultHPartitionVerdict;
/** The synthetic ledger id a path-addressed `key_envelope` gets — mirrors the gateway's `keyEnvelopeLedgerId` exactly (drift here breaks receipt pairing at upload finalize). */
export declare function keyEnvelopeLedgerId(epoch: string, fingerprint: string, rotationId: string | null): string;
export declare function pinManifestLedgerId(pinManifestVersion: string): string;
export declare const SOURCE_WRAP_BLOB_PREFIX = "swrap2_";
export declare const SOURCE_RC_DISPLAY_PREFIX = "SRC1";
export type SourceWrapperKind = "webauthn_prf" | "recovery_code";
export interface SourceWrapperContextFields {
/** The seal-time ceremony host (`location.hostname` of the sealing page — `console.run402.com` for every wrapper sealed today). */
rp_id: string;
principal_id: string;
encryption_key_id: string;
wrapper_id: string;
kind: SourceWrapperKind;
/** WebAuthn `public_subject` for `webauthn_prf` wrappers; always null in the JCS for `recovery_code`. */
credential_subject: string | null;
/** The raw 32-byte member public key — the context binds its FULL SHA-256, never the truncated `ek_` fingerprint. */
member_public_key: Uint8Array;
}
/** The pinned canonical context for one wrapper — BOTH the KEK HKDF-info suffix AND the AEAD AAD. */
export declare function buildSourceWrapperContext(fields: SourceWrapperContextFields): Uint8Array;
/**
* Open a `swrap2_...` wrapper blob back into the 32-byte member scalar.
* `ikm` is the UTF-8 bytes of the normalized code CORE (`recovery_code`) or
* the raw PRF output (`webauthn_prf` — only ever exercised by browser
* surfaces; offline recovery refuses raw PRF as an input by policy, at the
* recover layer). A failed AEAD is `WRAPPER_DID_NOT_OPEN` — the truthful
* cause-neutral error: wrong code, corrupt blob, and wrong context (rp_id
* included) are indistinguishable here. The caller MUST compare the derived
* FULL public key against the published one before trusting the scalar.
*/
export declare function openSourceWrapper(input: {
kind: SourceWrapperKind;
ikm: Uint8Array;
blob: string;
context: Uint8Array;
}): Uint8Array;
/**
* Pinned normalization (ONE canonical accepted form): uppercase; strip every
* char outside [0-9A-Z]; map I→1, L→1, O→0; drop a leading "SRC1" when the
* result is 37 chars; require exactly 33 chars; validate the check character
* (`RECOVERY_CODE_CHECKSUM_INVALID` — a local typo, caught before any KEK
* derivation or wrapper read). Returns the 32-char CORE (the KEK ikm).
*/
export declare function normalizeSourceRecoveryCode(input: string): string;
export { bytesToHex, hexToBytes };
/**
* The client half of the gateway's ECDH key-confirmation challenge for a
* `keystore_v1` enrollment (`POST /agent/v1/whoami/encryption-key` →
* `activation.{challenge_id, epk}`; `POST …/encryption-key/activate`
* `{challenge_id, proof}`). The server minted an ephemeral X25519 pair,
* computed HMAC-SHA256(ECDH(esk, our public key), message) and stored only
* its SHA-256; a holder of the keystore's private scalar reproduces the HMAC
* from `epk`. Byte-identical to the gateway's `keyPossessionMessage` /
* `mintKeyPossessionChallenge` (services/principal-encryption-keys.ts) —
* the prefix + JCS message are pinned here so the two lineages cannot drift.
* Client convention, not r402s/v0 wire protocol.
*/
export declare const VAULT_KEYSTORE_POSSESSION_PREFIX = "r402s/v0/keystore-key-possession/v1\n";
export declare function computeKeystorePossessionProof(input: {
private_key: Uint8Array;
epk_b64u: string;
challenge_id: string;
encryption_key_id: string;
public_key_b64u: string;
}): string;
/**
* vault-multi-writer rev 47 (design D9) — the writer-signing-key
* possession message. Byte-identical to the gateway's own
* `signingKeyPossessionMessage` (services/principal-encryption-keys.ts) —
* `JSON.stringify({domain, principal_id, signing_pubkey, encryption_pubkey})`
* in EXACTLY this field order, verified by reading that function's source
* directly rather than trusting its own comment (which claims "sorted
* keys" — this order is NOT alphabetically sorted; only the byte-for-byte
* match to what the gateway actually re-derives at verify time matters, and
* `JSON.stringify` is deterministic for a fixed insertion order regardless
* of whether that order happens to be alphabetical). Deliberately plain
* `JSON.stringify`, not `jcs()` — the RFC 8785 canonicalizer would SORT
* these four keys into a DIFFERENT order (`domain, encryption_pubkey,
* principal_id, signing_pubkey`) and silently produce a signature that
* never verifies.
*/
export declare const VAULT_SIGNING_KEY_POSSESSION_DOMAIN = "r402s/v0/signing-key-possession/v1";
export declare function signingKeyPossessionMessage(fields: {
principal_id: string;
signing_pubkey: string;
encryption_pubkey: string;
}): Uint8Array;
/** Ed25519 signature over {@link signingKeyPossessionMessage} — proves possession of the signing half's private key over this specific (principal, signing key, encryption key) binding. */
export declare function computeSigningKeyPossessionSignature(input: {
signing_seed: Uint8Array;
principal_id: string;
signing_pubkey: string;
encryption_pubkey: string;
}): string;
//# sourceMappingURL=vault.crypto.d.ts.map