import { type Sphere } from "./did.js"; import { type Identity, type DidDocument } from "./identity.js"; import { type Section, type ZoneWrap, type ManifestSignature, type GammaManifestAnchor, AITHOS_VERSION_V03 } from "./ethos.js"; import { type Author } from "./author.js"; /** Per-section AEAD parameters (§3.4′). Present iff the zone is encrypted. */ export interface SectionCipher { alg: "xchacha20poly1305-ietf"; /** base64url, 24 bytes. */ nonce: string; /** Per-recipient DEK wraps (X25519-HKDF-SHA256-AEAD, §3.6 — shared with v0.2). */ wraps: ZoneWrap[]; } /** The plaintext sealed inside a section's {@link TitleCipher}. */ export interface SectionTitle { title: string; tags?: string[]; } /** * A section's title+tags, sealed to that SECTION's recipients (§3.3.2′ revised). * For an encrypted-index zone (self), each section carries its own * `title_cipher` instead of the zone carrying one global index blob. Plaintext * is `jcs({ title, tags? })`; AAD = `"aithos-title-v1\0" ‖ subject_did ‖ "\0" ‖ * section_id`. Because it is sealed to the same recipients as the section body, * whoever can read the body can read the title — and a section-scoped delegate * sees only its own sections' titles, never the others'. */ export interface TitleCipher { alg: "xchacha20poly1305-ietf"; /** base64url, 24 bytes. */ nonce: string; /** Per-recipient wraps (X25519-HKDF-SHA256-AEAD) — same recipient set as the body. */ wraps: ZoneWrap[]; /** base64url XChaCha20-Poly1305 ciphertext of `jcs({ title, tags? })`. */ ct: string; } /** One section's manifest descriptor (§3.3.2′). */ export interface SectionDescriptor { section_id: string; /** * Section title in clear. Present for zones with a CLEAR index (public, * circle). MUST be absent when the zone's index is encrypted (self), where * the title lives in {@link title_cipher} instead. */ title?: string; /** `/.md` (public) or `/.enc` (encrypted). */ file: string; /** Hex SHA-256 of the section's plaintext markdown body (no prefix). */ sha256_of_plaintext: string; /** * Hex SHA-256 of the bytes AS STORED (no prefix) — the section's * content-address. For public sections the stored bytes ARE the plaintext * markdown, so `blob_sha === sha256_of_plaintext`; for encrypted zones the * stored bytes are the (nonce-prefixed) ciphertext, so `blob_sha` differs from * `sha256_of_plaintext` and is the ONLY stable address for the blob. * * Optional and additive (§3.3.2′ delta-upload): when present, the server stores * and serves the blob content-addressed at * `ethos/{subject_did}/blobs/{blob_sha}` (dedup across editions → per-section * delta upload). When absent, readers fall back to the legacy per-edition path * `editions/{height}/{file}`. Because the manifest is JCS-canonicalised before * hashing/signing, `blob_sha` is covered by the manifest signature * automatically — no `aithos` version bump required. */ blob_sha?: string; /** REQUIRED iff the zone is encrypted; MUST be absent for public sections (B15). */ cipher?: SectionCipher; gamma_ref: string; /** Clear tags. Like {@link title}, MUST be absent when the zone index is encrypted. */ tags?: string[]; /** Encrypted title+tags. REQUIRED iff the zone's index is encrypted; absent otherwise. */ title_cipher?: TitleCipher; } /** A zone in the v0.3 per-section schema. Shared shape for all three zones. */ export interface BundleZoneV2 { format_version: "v2"; /** `false` for public, `true` for circle/self. Fixed per zone identity. */ encrypted: boolean; /** * `true` when each section's title/tags are encrypted into its own * `title_cipher` rather than carried in clear. Fixed per zone identity: * `self` → true, `public`/`circle` → absent/false. This is the circle-clear / * self-private compromise — the host sees circle titles but never self titles, * and a section-scoped delegate sees only the titles of sections it can read. */ index_encrypted?: boolean; /** Ordered list of section descriptors (canonical display order). MAY be []. */ sections: SectionDescriptor[]; } /** * Per-zone index-privacy policy (fixed by zone identity). `self`'s index is * encrypted so only the subject can read their section titles; `public` and * `circle` keep a clear index (the host / circle can browse titles). */ export declare const ZONE_INDEX_ENCRYPTED: Record; /** A v0.3 manifest. Mirrors the v0.2 `Manifest` but with v2 zones (§3.3′). */ export interface ManifestV03 { aithos: typeof AITHOS_VERSION_V03; bundle_id: string; subject_did: string; subject_handle: string; display_name: string; edition: { version: string; created_at: string; supersedes: string | null; prev_hash: string | null; height: number; }; zones: Record; /** Gamma anchor — carried for forward-compat; deep gamma checks are §3.8′ #9 (deferred). */ gamma?: GammaManifestAnchor; integrity: { sha256_of_did_json: string; manifest_signature: ManifestSignature; }; } /** A recipient the section DEK is sealed to (subject sphere, or a delegate). */ export interface SectionRecipient { did: string; x25519PublicKey: Uint8Array; } /** A reader's decrypt credential: the DID URL it is wrapped under + its X25519 secret. */ export interface SectionReader { didUrl: string; x25519Secret: Uint8Array; } /** * AEAD additional data for a section (§3.4.3′, as amended): * * "aithos-section-v1\0" ‖ utf8(subject_did) ‖ "\0" ‖ utf8(section_id) * * Binds the ciphertext to the SUBJECT (stable across editions → unchanged * sections carry forward byte-identical, B3) and to the SECTION_ID (resists * swapping ciphertexts between sections of the same bundle, B4). subject_did * replaces the draft's per-edition bundle_id; see the module header for the * rationale. */ export declare function sectionAad(subjectDid: string, sectionId: string): Uint8Array; export interface EncryptedSection { ciphertext: Uint8Array; cipher: SectionCipher; } /** * Encrypt one section (§3.4.1′). Generates a FRESH per-section DEK and nonce * (each section is an independent random secret — §3.4.4′; no HKDF-from-a-zone- * master), seals the markdown under XChaCha20-Poly1305 with the section AAD, * and wraps the DEK to every recipient. */ export declare function encryptSection(plaintext: string, subjectDid: string, sectionId: string, recipients: SectionRecipient[]): EncryptedSection; /** * Decrypt one section (§3.4.2′). Finds the wrap for `myDidUrl`, unwraps the * per-section DEK, and opens the ciphertext under the section AAD. Throws if * there is no matching wrap or the AEAD tag does not verify (wrong key, wrong * subject, wrong section_id, or tampering). */ export declare function decryptSection(ciphertext: Uint8Array, cipher: SectionCipher, subjectDid: string, sectionId: string, myDidUrl: string, myX25519Secret: Uint8Array): string; /** * AEAD additional data for a section title: * "aithos-title-v1\0" ‖ utf8(subject_did) ‖ "\0" ‖ utf8(section_id) * Binds the title to the subject and the specific section (resists cross-subject * and cross-section replay; distinct prefix from the body AAD). */ export declare function titleAad(subjectDid: string, sectionId: string): Uint8Array; /** Seal a section's title/tags to `recipients` (the same set as the section body). */ export declare function encryptSectionTitle(meta: SectionTitle, subjectDid: string, sectionId: string, recipients: SectionRecipient[]): TitleCipher; /** Decrypt a section's {@link TitleCipher}. Throws on no-wrap / tamper. */ export declare function decryptSectionTitle(title: TitleCipher, subjectDid: string, sectionId: string, reader: SectionReader): SectionTitle; /** A resolved index row for display: `title` is undefined when encrypted and no key was supplied. */ export interface ZoneIndexRow { section_id: string; title?: string; tags?: string[]; /** True when the title is hidden (encrypted, no/failed key for this section). */ title_hidden: boolean; } /** * Resolve a zone's section index for display. Clear-index zones (public, * circle) return titles directly from the descriptors. For the encrypted (self) * index, each section's title is decrypted from its own `title_cipher` when the * `reader` is one of that section's recipients — so a section-scoped delegate * sees the titles of exactly the sections it can read, the host sees none, and * the subject sees all. */ export declare function readZoneIndex(_zoneName: Sphere, zone: BundleZoneV2, subjectDid: string, reader?: SectionReader): ZoneIndexRow[]; /** * Render one section to its canonical markdown form (§2.6 / §3.4.5′): the title * heading, an optional tags comment, then the body. This is the exact plaintext * that is written verbatim to `public/.md` or fed into XChaCha20-Poly1305 * for an encrypted zone. Identity (`section_id`) and provenance (`gamma_ref`) * live in the manifest, NOT in this plaintext — so a section's hash is over its * title/body/tags only, matching the gamma payload cross-check of §3.4.2′ #7. */ export declare function renderSectionMarkdown(section: { title: string; body: string; tags?: string[]; }): string; /** Inverse of {@link renderSectionMarkdown}. */ export declare function parseSectionMarkdown(md: string): { title: string; body: string; tags?: string[]; }; export interface SectionWriteCtx { /** Bundle root directory to write the blob under. */ bundleDir: string; zone: Sphere; /** Gates the AEAD layer: false → plaintext .md, true → .enc ciphertext. */ encrypted: boolean; /** When true, the title/tags are sealed into `title_cipher` instead of clear (self). */ indexEncrypted: boolean; subjectDid: string; /** Recipients for the per-section DEK + title (encrypted zones only). */ recipients: SectionRecipient[]; } /** * Write one section's blob into the bundle and return its manifest descriptor. * Public → the markdown file is written directly; circle/self → the markdown is * encrypted with a fresh per-section DEK (§3.4.1′ / §3.4.5′). When the zone's * index is encrypted (self), the title/tags are sealed into a per-section * `title_cipher` (sealed to the same recipients) instead of left clear. */ export declare function writeSection(ctx: SectionWriteCtx, section: Section): SectionDescriptor; export interface SectionReadResult { accessible: boolean; section?: Section; reason?: string; } /** * Read one section's blob back to a {@link Section}. Public → read + hash-check * the file directly; circle/self → require a `reader` whose key matches one of * the section's wraps, decrypt, then hash-check. Never throws: an inaccessible * section (no key, no matching wrap, decrypt failure, hash mismatch) is reported * via `{ accessible: false, reason }` so a caller can iterate a zone and read * the sections it is entitled to while skipping the rest (§3.4.2′ / B11). */ export declare function readSection(bundleDir: string, zone: BundleZoneV2, desc: SectionDescriptor, subjectDid: string, reader?: SectionReader): SectionReadResult; /** Canonical bytes of a manifest with the signature value blanked (JCS, RFC 8785). */ export declare function canonicalManifestV03Bytes(m: ManifestV03): Uint8Array; /** SHA-256 hex of the canonical (blank-sig) manifest — the `prev_hash` anchor. */ export declare function canonicalManifestHashHexV03(m: ManifestV03): string; /** * Sign a v0.3 manifest. Owner → the subject's `#public` sphere key. Delegate → * the delegate Ed25519 seed, with `manifest_signature.key` = the delegate * pubkey multibase and `authorized_by` = the mandate id (mirrors v0.2 * `signManifest`). The canonical bytes include `key` + `authorized_by` (value * blanked) so the signature binds to both the signer and the mandate claimed. */ export declare function signManifestV03(subject: Identity | Author, m: ManifestV03): ManifestV03; /** * Resolver for a delegate's Ed25519 public key when the manifest signature * carries `authorized_by`. Returns the raw 32-byte key or throws. It is * expected to validate the mandate (signature + window + scope) before * returning — see `keystoreDelegateResolver`. */ export interface VerifyV03SignatureOpts { resolveDelegatePubkey?: (keyId: string, mandateId: string) => Uint8Array; } /** * Verify the manifest signature (§3.8′ #5). Owner signatures verify against the * `#public` key in `did.json`; delegate signatures (`authorized_by`) resolve the * delegate pubkey via `opts.resolveDelegatePubkey`. */ export declare function verifyManifestSignatureV03(m: ManifestV03, didDoc: DidDocument, opts?: VerifyV03SignatureOpts): { ok: boolean; error?: string; }; export interface BuildManifestV2Params { subjectDid: string; handle: string; displayName: string; bundleId: string; editionVersion: string; createdAt: string; supersedes: string | null; prevHash: string | null; height: number; zones: Record; sha256OfDidJson: string; /** Gamma deep-memory anchor (§10.7) — the signed manifest commits to the log tail. */ gamma?: GammaManifestAnchor; } /** * Assemble an UNSIGNED v0.3 manifest (signature value blank). The placeholder * signature key is the `#public` sphere URL of the subject; {@link signManifestV03} * overwrites `key`/`value` (and adds `authorized_by` for delegate signatures). */ export declare function buildManifestV2(p: BuildManifestV2Params): ManifestV03; /** Default recipients for an encrypted zone: the subject sphere only (§3.5.1′). */ export declare function defaultZoneRecipients(identity: Identity, zone: "circle" | "self"): SectionRecipient[]; export interface AuthorBundleV03Args { /** The owner identity. Provide this OR {@link author}. */ identity?: Identity; /** * The author abstraction (owner or delegate). A `DelegateAuthor` may only * (re)author its mandate's `actor_sphere` zone; the other zones are carried * forward WHOLESALE from {@link prev} (their blobs are copied verbatim — no * decryption needed) and the manifest is signed with the delegate key + * `authorized_by`. When omitted, `identity` is wrapped as an owner author. */ author?: Author; /** Bundle root directory to (re)write. Created if absent. */ outDir: string; /** * Sections per zone, in canonical display order. A delegate need only supply * the zone they author (`actor_sphere`); other zones are ignored and carried * forward from `prev`. */ zones: Partial>; now?: Date; /** * Previous v0.3 edition for the chain + carry-forward. `dir` is where the * prior edition's section blobs live (so unchanged sections can be copied * byte-identical instead of re-encrypted — the property B3 asserts). */ prev?: { manifest: ManifestV03; dir: string; }; /** * Explicit predecessor edition link, used when the predecessor is NOT a v0.3 * bundle (e.g. a v0.2 → v0.3 migration edition per §3.10.3′). Chains the * edition (supersedes / prev_hash / height) without carry-forward — every * section is freshly encrypted, since a v0.2 monolithic ciphertext cannot be * reused per-section. Ignored when `prev` is also supplied. */ prevEdition?: { /** Predecessor `edition.version` (drives the new edition's N allocation). */ version: string; /** Predecessor `bundle_id` → the new edition's `supersedes`. */ bundleId: string; /** Hex SHA-256 of the predecessor's canonical (blank-sig) manifest, no prefix. */ manifestHashHex: string; /** Predecessor `edition.height` (new height = this + 1). */ height: number; }; /** Override recipients per encrypted zone (default: subject only). */ recipientsFor?: (zone: "circle" | "self") => SectionRecipient[]; /** * Gamma deep-memory anchor to record in the manifest (§10.7). When omitted, * a v0.3 `prev`'s anchor carries forward. The `gamma.jsonl.enc` log file is * carried forward from `prev.dir` when present. */ gamma?: GammaManifestAnchor; /** Path to a `gamma.jsonl.enc` to copy into the bundle (e.g. from a v0.2 source). */ gammaLogSrc?: string; } /** * Author a complete v0.3 bundle directory: per-section blobs for all three * zones, `did.json`, and a signed v2 `manifest.json`. * * Carry-forward (the heart of v0.3's cost property): a section whose plaintext * hash, gamma_ref, and recipient set are unchanged from `prev` has its prior * blob copied verbatim and its prior manifest descriptor reused — so its * on-disk ciphertext is byte-identical across editions and only genuinely * changed sections pay the re-encryption cost (§3.5.3′ / B3 / B14). */ export declare function authorBundleV03(args: AuthorBundleV03Args): ManifestV03; /** Per-zone patch: sections to add/replace, and section ids to delete. */ export interface ZonePatch { upserts?: Section[]; deletes?: string[]; } export interface PatchEditionV03Args { identity?: Identity; author?: Author; /** Bundle dir to write the new edition into. */ outDir: string; /** Predecessor v0.3 edition (its blobs are the carry-forward source). */ prev: { manifest: ManifestV03; dir: string; }; /** What changes, per zone. Zones absent from the patch carry forward unchanged. */ patch: Partial>; now?: Date; } /** * Author a new edition by PATCHING the predecessor: changed/added sections are * (re)encrypted; every other section carries forward VERBATIM (blob + descriptor * + title_cipher) WITHOUT being decrypted. This is what lets a section-scoped * delegate add/edit/delete its own sections (it only reads the section it is * changing — never its siblings), and makes owner edits cheap (no whole-zone * decrypt). Because each self title lives in its own `title_cipher`, adding a * section just seals its title to the section's recipients — no global index to * rebuild. */ export declare function patchEditionV03(args: PatchEditionV03Args): ManifestV03; export interface BundleV03VerifyResult { ok: boolean; errors: string[]; warnings: string[]; bundle_id?: string; edition?: { version: string; height: number; }; subject_handle?: string; /** Encrypted zones the verifier could not open (no reader / no matching wrap). */ zonesSkipped: Sphere[]; } export interface BundleV03VerifyOpts { /** Reader credentials to attempt section decryption (enables the deep hash check + B11). */ readers?: SectionReader[]; /** Predecessor edition's manifest, to verify `edition.prev_hash` (§3.8′ #8). */ predecessorManifest?: ManifestV03; /** Resolver for a delegate-signed manifest (`authorized_by`); see `keystoreDelegateResolver`. */ resolveDelegatePubkey?: (keyId: string, mandateId: string) => Uint8Array; } /** * Verify a v0.3 bundle directory against §3.8′ (checks 1–8; check 9, the gamma * cross-check, is deferred with the gamma-v0.3 integration). Returns structured * errors/warnings rather than throwing on a malformed-but-parseable bundle. */ export declare function verifyBundleV03Dir(dir: string, opts?: BundleV03VerifyOpts): BundleV03VerifyResult; /** * Verify a v0.3 bundle at a filesystem path — either an unpacked directory or a * `.ethos` zip (detected by header). The zip is extracted to a private temp dir, * verified, then removed. */ export declare function verifyBundleV03AtPath(pathArg: string, opts?: BundleV03VerifyOpts): BundleV03VerifyResult;