/** * gitvault — `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 (`gitvault.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 { GitvaultAddWriterKeyPayload, GitvaultAllocation, GitvaultDigestLabel, GitvaultEncryptedObjectKind, GitvaultEncryptionKeypair, GitvaultEnvelopeAad, GitvaultFormat, GitvaultFrameAad, GitvaultKeyEnvelope, GitvaultKeyEnvelopeReceipt, GitvaultObjectKind, GitvaultRecoveryReceipt, GitvaultRotateEpochPayload, GitvaultSealedFrame, GitvaultSealedKeyEnvelope, GitvaultSignedObject, GitvaultSigningKeypair, GitvaultStrictParseReason, GitvaultSuite, GitvaultVaultGenesis } from "./gitvault.types.js"; /** Wire format tag (retained by decision D181 across the product rename). */ export declare const GITVAULT_FORMAT: "r402s/v0"; /** The one V0 cryptographic suite. */ export declare const GITVAULT_SUITE: "r402s-1"; /** Frame magic, bytes 0–5 of every encrypted object. */ export declare const GITVAULT_FRAME_MAGIC: "R402S0"; /** Frame suite byte (byte 6) and its two-char AAD spelling. */ export declare const GITVAULT_FRAME_SUITE_BYTE: 1; export declare const GITVAULT_FRAME_SUITE_ID: "01"; /** Protocol §2: max plaintext per encrypted object (256 MiB). */ export declare const GITVAULT_MAX_PLAINTEXT_BYTES: number; /** The genesis generation / epoch constants (schema consts). */ export declare const GITVAULT_GENESIS_GENERATION: "0000000000000000"; export declare const GITVAULT_GENESIS_EPOCH: "0000000000000001"; /** HPKE `info` label for `key_envelope` (D188). */ export declare const GITVAULT_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 GITVAULT_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 GITVAULT_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 `Gitvault.status()`'s `covering_recipients` * field. Never paraphrase; the wording is a reviewed product commitment. */ export declare const GITVAULT_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 GITVAULT_TERMINAL_LOSS_DOCTOR_TEXT: string; /** * gitvault-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 GITVAULT_TERMINAL_LOSS_STATEMENT} above. Never * paraphrased — the §6.4 honesty posture is a reviewed product commitment. */ export declare const GITVAULT_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 GITVAULT_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"; /** * gitvault-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 GITVAULT_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"; /** * gitvault-byo-primary-bucket (design D4) — the degraded-read mechanism * sentence: the invariant part of the ONE stderr line a degraded chain/ * payload read prints (`formatGitvaultDegradedReadNote`, `gitvault-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 GITVAULT_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"; /** * gitvault-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 GITVAULT_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"; /** * gitvault-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 GITVAULT_MIRROR_SETUP_HINT: "recommended: 'run402 repos mirror ' (an s3:// bucket or a local directory) starts the customer-owned mirror \u2014 every later snapshot dual-pushes to it automatically, and it is the copy that stays in your custody"; /** * gitvault-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 gitvault 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 GITVAULT_BYO_HEADLINE_STATEMENT: "your source ciphertext never touches our infrastructure \u2014 not even encrypted"; /** * gitvault-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 GITVAULT_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 GITVAULT_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"; /** * gitvault-byo-primary-bucket (design D7) — the BYO variant of the * `vault_unmirrored` finding's remedy (gitvault-mirror-default): for a * managed vault the remedy is an ADDITIONAL mirror beside the platform's own * replicated copy ({@link GITVAULT_MIRROR_SETUP_HINT}); for a BYO vault the * platform holds no payload copy to fall back on * ({@link GITVAULT_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 GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT: "add a second customer-held location: 'run402 repos mirror ' 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 GITVAULT_HEX16_RE: RegExp; export declare const GITVAULT_HEX32_RE: RegExp; export declare const GITVAULT_SHA256_RE: RegExp; export declare const GITVAULT_OID40_RE: RegExp; export declare const GITVAULT_VK_RE: RegExp; export declare const GITVAULT_EK_RE: RegExp; export declare const GITVAULT_SRC_RE: RegExp; export declare const GITVAULT_SERVICE_KEY_ID_RE: RegExp; export declare const GITVAULT_SIZE_BYTES_RE: RegExp; /** 32 bytes base64url: 43 chars, final char in the 16-value padding class. */ export declare const GITVAULT_B64U_32_RE: RegExp; /** 64 bytes base64url: 86 chars, final char ∈ {A,Q,g,w}. */ export declare const GITVAULT_B64U_64_RE: RegExp; /** The HPKE-sealed K_repo: 48 bytes = exactly 64 base64url chars. */ export declare const GITVAULT_HPKE_CT_RE: RegExp; export declare const GITVAULT_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 (`GITVAULT_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 isValidGitvaultTimestamp(value: string): boolean; /** Format a Date as the protocol timestamp grammar (RFC 3339 UTC, milliseconds, `Z`). */ export declare function formatGitvaultTimestamp(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 (gitvault-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 GitvaultHashBackend { /** 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 _setGitvaultHashBackend(backend: GitvaultHashBackend | null): void; /** SDK-INTERNAL: which hash backend is live. The isomorphic-entry test asserts `null`. */ export declare function _gitvaultHashBackend(): GitvaultHashBackend | 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 `_<32 lowercase hex>` id (protocol §1: ids are CSPRNG). */ export declare function newGitvaultId(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 parseGitvaultStrict(text: string): unknown; /** Classify a strict-parse failure (for vector replay + diagnostics). */ export declare function gitvaultStrictParseReason(error: unknown): GitvaultStrictParseReason | null; /** Stored bytes of a signed object = JCS of the COMPLETE object including `signature`. */ export declare function storedBytes(object: GitvaultSignedObject): Uint8Array; /** SHA-256 over stored bytes — what every `*_sha256` naming a signed object means. */ export declare function storedBytesSha256(object: GitvaultSignedObject): 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 gitvaultWithoutSignature(object: T): Omit; /** * Signature preimage = `"r402s/v0/" + domain + "\n" + JCS(object without its * single top-level signature)`. `domain` is USUALLY a `GitvaultObjectKind` * (an actual `object_kind`-carrying signed object), but the type accepts any * string: gitvault-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 * `GitvaultSignedObject` (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: GitvaultObjectKind | string, objectWithoutSignature: Record): 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 signGitvaultObject(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 verifyGitvaultObject(object: GitvaultSignedObject, publicKey: Uint8Array | string): boolean; /** Verify or throw `GITVAULT_SIGNATURE_INVALID`. */ export declare function assertGitvaultSignature(object: GitvaultSignedObject, 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): GitvaultSigningKeypair; export declare function generateEncryptionKeypair(privateKey?: Uint8Array): GitvaultEncryptionKeypair; /** `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: GitvaultEncryptedObjectKind, 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: GitvaultEncryptedObjectKind, 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: GitvaultDigestLabel): Uint8Array; export declare function deriveDigestKey(kRepo: Uint8Array, repoId: string, epoch: string, label: GitvaultDigestLabel): 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: GitvaultDigestLabel): 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): { 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: GitvaultEncryptedObjectKind, objectId: string, epoch: string): GitvaultFrameAad; /** * The bulk-AEAD seam (gitvault-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 allowance: 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 GitvaultAeadBackend { /** 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 _setGitvaultAeadBackend(backend: GitvaultAeadBackend | null): void; /** SDK-INTERNAL: which backend is live. The verifier-lineage test asserts `null`. */ export declare function _gitvaultAeadBackend(): GitvaultAeadBackend | null; export interface GitvaultSealFrameInput { /** The per-object key — derive with {@link deriveObjectKey}. */ k_obj: Uint8Array; repo_id: string; object_kind: GitvaultEncryptedObjectKind; 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: GitvaultSealFrameInput): GitvaultSealedFrame; export interface GitvaultOpenFrameInput { k_obj: Uint8Array; repo_id: string; object_kind: GitvaultEncryptedObjectKind; 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: GitvaultOpenFrameInput): 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 gitvaultHpkeSuite(): 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): GitvaultEnvelopeAad; export interface GitvaultHpkeSealInput { 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: GitvaultHpkeSealInput): Promise<{ enc: Uint8Array; ct: Uint8Array; }>; export interface GitvaultHpkeOpenInput { recipient_private_key: Uint8Array; enc: Uint8Array; info: Uint8Array; aad: Uint8Array; ct: Uint8Array; } /** Single-shot HPKE open; any failure is `GITVAULT_HPKE_OPEN_FAILED` (never garbage plaintext). */ export declare function hpkeOpen(input: GitvaultHpkeOpenInput): Promise; export interface GitvaultSealKeyEnvelopeInput { /** 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: GitvaultSigningKeypair; 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: GitvaultSealKeyEnvelopeInput): Promise; export interface GitvaultOpenKeyEnvelopeInput { envelope: GitvaultKeyEnvelope; /** The recipient's raw X25519 keypair (the public half feeds `info`; the fingerprint must match the path identity). */ recipient: GitvaultEncryptionKeypair; /** 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: GitvaultOpenKeyEnvelopeInput): Promise; /** * 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; }): GitvaultRotateEpochPayload; /** * Decode + validate one head's `transition.payload` as an * `add_writer_key_payload` (gitvault-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/gitvault-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; }): GitvaultAddWriterKeyPayload; /** * 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 `GITVAULT_EPOCH_NOT_OPENABLE` — never a bare `GITVAULT_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 (`GitvaultVault.verifyToNewest`, over * the gateway transport) and the offline mirror recovery path * (`recoverGitvaultMirror`, over a `GitvaultMirrorBackend`) call it — * differing only in HOW they fetch bytes, never in the open/verify logic. */ export declare function openEpochRotationForRecipient(input: { repo_id: string; payload: GitvaultRotateEpochPayload; own_fingerprint: string; own_encryption_keypair: GitvaultEncryptionKeypair; writer_signing_public_key: Uint8Array | string; get_envelope_bytes: (path: string) => Promise; envelope_path: (epoch: string, recipient_fingerprint: string, rotation_id: string) => string; }): Promise; export interface GitvaultBuildGenesisInput { repo_id: string; org_id: string; project_id: string; allocation_nonce: string; creator_signing: GitvaultSigningKeypair; creator_encryption_public_key: Uint8Array; envelope_receipt: GitvaultKeyEnvelopeReceipt; created_at: string; } /** Build + sign a `vault_genesis` (generation 0, epoch 1, the creator's single envelope). */ export declare function buildVaultGenesis(input: GitvaultBuildGenesisInput): GitvaultVaultGenesis; /** A named genesis key-binding failure (semantic constraints `genesis-key-bindings` / `envelope-receipt-binding`). */ export type GitvaultGenesisBindingProblem = "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: GitvaultVaultGenesis, storedEnvelope?: GitvaultKeyEnvelope): GitvaultGenesisBindingProblem[]; export interface GitvaultBuildRecoveryReceiptInput { repo_id: string; org_id: string; project_id: string; genesis_sha256: string; creator_signing: GitvaultSigningKeypair; 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: GitvaultBuildRecoveryReceiptInput): GitvaultRecoveryReceipt; export type GitvaultRecoveryReceiptProblem = "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: GitvaultRecoveryReceipt, genesis: GitvaultVaultGenesis): GitvaultRecoveryReceiptProblem[]; /** * 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: GitvaultAllocation, 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 GitvaultIncludedPair { 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 GitvaultIncludedPair[]; 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 & { 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: GitvaultFormat; object_kind: "rotation_attempt_descriptor"; suite: GitvaultSuite; 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; /** * 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[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): void; export type GitvaultHPartitionVerdict = { 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; keyedPrincipalIds: ReadonlySet; pinnedFingerprintOf: ReadonlyMap; included: readonly GitvaultIncludedPair[]; 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; directoryFingerprintOf?: ReadonlyMap; }): GitvaultHPartitionVerdict; /** 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; }[]): GitvaultHPartitionVerdict; /** 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 GITVAULT_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; /** * gitvault-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 GITVAULT_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=gitvault.crypto.d.ts.map