import { type Identity, type DidDocument } from "./identity.js"; import { type Sphere } from "./did.js"; import type { Author } from "./author.js"; export declare function gammaDir(handle: string): string; export declare function gammaFilePath(handle: string): string; /** * Gamma operations. Unchanged from v0.2 — only the storage format changed. * Unknown ops are permitted under an `x-` prefix for experimentation but MUST * cause a strict verifier to reject the log otherwise. */ export type GammaOp = "section.add" | "section.modify" | "section.delete" | "section.reorder" | "zone.meta.set" | "section.redact" | "identity.rotate" | "mandate.issue" | "mandate.revoke" | "bundle.migrate.v0.3"; export declare const GAMMA_VERSION: "0.3.0"; export declare const GAMMA_FILE_VERSION: "0.3.0"; export declare function newGammaId(): string; /** * The reader half of a v0.3 envelope: a stable recipient identifier and * their multibase X25519 public key. Writers build the per-entry envelopes * list from an array of these. * * This is a local mirror of the `GammaReader` shape that lives in the ethos * manifest (`manifest.gamma.readers`). The two types are structurally * compatible — ethos.ts re-exports its version for manifest typing. */ export interface GammaReaderKey { /** Stable identifier (e.g. `did:aithos:*#` or `urn:aithos:agent:*`). */ recipient: string; /** Multibase-encoded X25519 public key. */ pubkey: string; } /** * One wrap of an entry's symmetric key, sealed to a single reader. */ export interface GammaEnvelope { recipient: string; alg: "x25519-hkdf-sha256-aead"; ephemeral_public: string; wrap_nonce: string; wrapped_key: string; } /** * Cleartext header for a v0.3 entry. Carries everything needed to: * - walk the chain (`prev_gamma_hash`, `hash`), * - recognize the entry (`id`, `at`, `subject_did`, `zone`, `op`, `target`), * - verify the reader set (`readers_hash` commits to `envelopes` — §10.3.4′). * * The header is committed by the entry hash (§10.5.1′). Tampering with any * header field breaks the hash, which in turn breaks the signature. */ export interface GammaPublicHeader { "aithos-gamma": typeof GAMMA_VERSION; id: string; at: string; subject_did: string; zone: Sphere; op: GammaOp; target: Record; prev_gamma_hash: string | null; prev_section_gamma?: string; readers_hash: string; hash: string; } /** * Signature block for a v0.3 entry. Signature domain (§10.5.2′) is * intentionally narrow: `jcs({ hash, authorized_by: | null, key })`. * The entry hash already commits to ciphertext + header; the signature only * binds "I, this signer, attest to this hash under this mandate (if any)." */ export interface GammaSignatureBlock { alg: "ed25519"; key: string; authorized_by?: string; value: string; } /** * A full v0.3 entry as persisted on disk. */ export interface GammaEntryV03 { format: "v0.3"; payload_ct: string; nonce: string; envelopes: GammaEnvelope[]; public_header: GammaPublicHeader; signature: GammaSignatureBlock; } /** * On-disk file envelope. Versioned so future changes can ship side-by-side. */ export interface GammaFileV03 { "aithos-gamma-file": typeof GAMMA_FILE_VERSION; entries: GammaEntryV03[]; } /** * Logical entry returned to callers by the read APIs. Flattens header + * decrypted payload into a single object, matching the v0.2 `GammaEntry` * shape for minimum call-site churn. For entries the caller could not * decrypt (access-denied), `payload = {}` and `_access_denied = true`. * * IMPORTANT: Chain / hash verification MUST operate on the on-disk form * (`GammaEntryV03`), not on this logical view — the hash covers the * ciphertext, which is lost after decryption. */ export interface GammaEntry { "aithos-gamma": typeof GAMMA_VERSION; id: string; at: string; subject_did: string; zone: Sphere; op: GammaOp; target: Record; payload: Record; prev_gamma_hash: string | null; prev_section_gamma?: string; hash: string; signature: { alg: "ed25519"; key: string; value: string; }; authorized_by?: string; note?: string; /** Set on v0.3 entries the caller lacks an envelope for. Payload is {}. */ _access_denied?: true; } /** * The three sphere X25519 pubkeys, packaged as `GammaReaderKey`s. Used when * bootstrapping a fresh identity's `manifest.gamma.readers` list so every * v0.3 entry is always sealed to the subject's own keys regardless of * delegate state. */ export declare function defaultGammaReaderKeys(identity: Identity): GammaReaderKey[]; /** * Decryption handle for one reader. Used to open per-entry envelopes. */ export interface GammaReaderSecret { /** Must match an envelope's `recipient` on disk, exactly. */ recipient: string; /** X25519 secret key (32 bytes). */ x25519Secret: Uint8Array; } /** * Derive one of the subject's decryption identities (owner path). Any of the * three sphere-derived X25519 secrets can open an entry envelope sealed to * that sphere pubkey. */ export declare function subjectGammaReaderSecret(identity: Identity, zone: Sphere): GammaReaderSecret; /** * Stable envelope-recipient label for a delegate reader. Keep in sync with * `ethos.ts#delegateWrapDid` for zone DEK wraps so a delegate's envelopes * are uniformly keyed regardless of which chunk of ciphertext they target. */ export declare function delegateGammaRecipient(granteeId: string, pubkeyMultibase: string): string; /** * readers_hash (§10.3.4′) — commits to the reader set of an entry without * committing to the (necessarily non-canonical) wrapped key material. * * sha256(jcs(sort_by_recipient([ {recipient, alg, ephemeral_public, wrap_nonce} ]))) * * Note that `wrapped_key` is EXCLUDED: each wrap uses a fresh ephemeral * pubkey and therefore produces different ciphertext even for the same * entry_key, so including it would make every seal produce a different * readers_hash. */ export declare function computeReadersHash(envelopes: GammaEnvelope[]): string; /** * Entry hash (§10.5.1′): * sha256(jcs({ payload_ct, nonce, public_header: { ... hash: "" } })) * * The header is blanked on its own `hash` field (otherwise the computation * would be recursive). All other header fields, including `readers_hash`, * are in scope. Any change to the ciphertext, nonce, or public_header * invalidates the entry hash. */ export declare function computeEntryHashV03(payload_ct: string, nonce: string, header: GammaPublicHeader): string; /** * Bytes to sign (§10.5.2′): * * jcs({ hash, authorized_by: , key }) * * Narrow by design. The entry hash already commits to every other aspect of * the entry (ciphertext + nonce + header). The signature's only job is to * bind the hash to the signer identity + optional mandate id. */ export declare function signableBytesV03(hash: string, key: string, authorizedBy: string | undefined): Uint8Array; export interface GammaSigner { /** Public identifier of the signing key — used as `signature.key`. */ keyId: string; /** Sign raw bytes and return the 64-byte Ed25519 signature. */ sign(payload: Uint8Array): Uint8Array; /** Mandate id if this is a delegate; undefined for direct sphere-key signers. */ mandateId?: string; } export declare function sphereGammaSigner(identity: Identity, zone: Sphere): GammaSigner; export declare function delegateGammaSigner(mandateId: string, keySeed: Uint8Array, keyMultibase: string): GammaSigner; export interface BuildGammaEntryInput { subjectDid: string; zone: Sphere; op: GammaOp; target: Record; payload: Record; prevGammaHash: string | null; prevSectionGamma?: string; signer: GammaSigner; /** Readers to seal the entry_key to. MUST be non-empty. */ readers: GammaReaderKey[]; at?: Date; /** Override the generated id (tests / deterministic fixtures). */ id?: string; } /** * Build a fully-formed v0.3 entry (ready to append). Steps §10.11′ 2–9: * * 1. Fresh 32-byte entry_key. * 2. Encrypt payload under entry_key + fresh nonce. * 3. Seal entry_key to every reader's X25519 pubkey. * 4. Build public_header with readers_hash. * 5. Compute entry hash. * 6. Sign the narrow signature domain. * * Does NOT touch disk. Append is the caller's responsibility. */ export declare function buildGammaEntryV03(input: BuildGammaEntryInput): GammaEntryV03; /** * Backward-compatible alias for v0.3. Callers migrated from v0.2 that just * want a "signed gamma entry" get a v0.3 on-disk record. */ export declare const buildGammaEntry: typeof buildGammaEntryV03; export interface VerifyGammaEntryContext { didDoc: DidDocument; prev: GammaEntryV03 | null; /** * Resolver for delegate public keys when `signature.authorized_by` is set. * Returns raw 32-byte Ed25519 public key or throws. */ resolveDelegatePubkey?: (keyId: string, mandateId: string) => Uint8Array; } export interface VerifyGammaEntryResult { ok: boolean; error?: string; } /** * Verify a single v0.3 entry without decryption: * - readers_hash matches envelopes * - entry hash matches ciphertext + nonce + header * - chain link (prev_gamma_hash + strictly increasing at) * - signature over the narrow signature domain verifies * * This is the integrity tier (§10.14.2′). A caller with no envelope on the * entry can still check everything here. */ export declare function verifyGammaEntry(entry: GammaEntryV03, ctx: VerifyGammaEntryContext): VerifyGammaEntryResult; export interface VerifyGammaLogResult { ok: boolean; count: number; errors: Array<{ index: number; entryId: string; error: string; }>; } /** * Walk the on-disk log in order and verify each entry. Integrity-only; * does not decrypt any payload. */ export declare function verifyGammaLog(entries: GammaEntryV03[], didDoc: DidDocument, opts?: { resolveDelegatePubkey?: (keyId: string, mandateId: string) => Uint8Array; }): VerifyGammaLogResult; export declare function ensureGammaDir(handle: string): void; /** * Read the gamma file from disk. Returns null if the file does not exist * yet (fresh identity with no history). * * Validates the top-level version marker. Unknown marker → throw. */ export declare function readGammaFile(handle: string): GammaFileV03 | null; /** * Write the gamma file to disk atomically (temp + rename). */ export declare function writeGammaFile(handle: string, file: GammaFileV03, mode?: number): void; /** * Convenience: return the on-disk entries array, or [] if the file doesn't * exist. Integrity-only callers (verifiers) use this; it does not touch * any key material. */ export declare function readGammaEntriesOnDisk(handle: string): GammaEntryV03[]; /** * Return just the public headers — no envelope decoding, no decryption. * Used by callers that need chain navigation / section lookup without any * cryptographic material (`latestGammaForSection`, the head/count anchor). */ export declare function readGammaHeaders(handle: string): GammaPublicHeader[]; /** * Append an already-built v0.3 entry to the log. * * The caller is responsible for having built the entry with correct * `prev_gamma_hash` (typically from `gammaHead(handle)`) and a readers list * derived from `manifest.gamma.readers`. * * This function touches no plaintext and decrypts nothing. A delegate with * `ethos.write.` but no gamma-read envelope can execute this path end * to end. */ export declare function appendGammaEntryOnDisk(handle: string, entry: GammaEntryV03): void; /** * Back-compat wrapper for callers migrated from v0.2 who passed a single * GammaEntryV03 as `entry`. Same as `appendGammaEntryOnDisk` but accepts the * old positional identity arg (ignored — no plaintext is decrypted here). * * @deprecated Use `appendGammaEntryOnDisk(handle, entry)` directly. */ export declare function appendGammaEntry(handle: string, _identity: Identity, entry: GammaEntryV03): void; /** * @deprecated Use `appendGammaEntryOnDisk(handle, entry)` directly. */ export declare function appendGammaEntryForAuthor(handle: string, _author: Author, entry: GammaEntryV03): void; /** * Decrypt one entry for a given reader secret. Returns the logical GammaEntry * shape. If the reader has no envelope on this entry, returns a flat entry * with `payload = {}` and `_access_denied = true`. */ export declare function openGammaEntry(entry: GammaEntryV03, me: GammaReaderSecret): GammaEntry; /** * Decrypt the whole log for a reader secret. Entries the reader can't open * are still returned, with `payload = {}` and `_access_denied = true`. */ export declare function readGammaLogWith(handle: string, me: GammaReaderSecret): GammaEntry[]; /** * Owner path: read with any of the three sphere X25519 secrets (they all * produce identical envelope lookups when the subject's sphere pubkey is * the recipient). We pick `self` by convention. */ export declare function readGammaLog(handle: string, identity: Identity): GammaEntry[]; /** * Author-aware variant. Owner path uses the subject's `self` X25519 secret; * delegate path uses the delegate seed's X25519 secret with the delegate's * recipient label (`#`). */ export declare function readGammaLogForAuthor(handle: string, author: Author): GammaEntry[]; /** * Head hash of the chain (tail entry's `public_header.hash`), or `null` if * the log is empty. Reads headers only — no keys required. */ export declare function gammaHead(handle: string, _identity?: Identity): string | null; /** Author-aware variant — identical to `gammaHead`; no decryption required. */ export declare function gammaHeadForAuthor(handle: string, _author: Author): string | null; /** * Latest gamma entry (logical view — but only the header is consulted) for * a given section id. Walks entries in reverse. * * Accepts either v0.3 on-disk entries or logical GammaEntry views — both * carry `target.section_id` in the same position (`target`). */ export declare function latestGammaForSection(entries: T[], sectionId: string): T | null; /** * Helper: latest section-scoped gamma entry, return the id. Works against * the on-disk form without decrypting anything. */ export declare function latestGammaIdForSection(handle: string, sectionId: string): string | null;