/** * Crypto helpers for thought encryption + signing. * * Phase 5 of the SDK introduces client-side crypto. Thought CONTENT is * encrypted under K_master (AES-256-GCM); thoughts are signed with the * agent's ed25519 signing key over canonical bytes the API verifies. * * The wire format is byte-identical to `cli/think/src/crypto.ts` and the * api-side verifier at `api/src/services/strand/sig.ts`. Two versions * exist; the server accepts either. * * strand-thought/v1 (default — every existing signed row uses it) * canonical = sha256( * utf8(strand_id) || 0x00 || * ciphertext || 0x00 || * nonce || 0x00 || * utf8(kind ?? "") * ) * * strand-thought/v2 (length-prefixed — unambiguous framing) * canonical = sha256( * utf8("strand-thought/v2") || * u32be(len(utf8(strand_id))) || utf8(strand_id) || * u32be(len(ciphertext)) || ciphertext || * u32be(len(nonce)) || nonce || * u32be(len(utf8(kind ?? ""))) || utf8(kind ?? "") * ) * * signature = ed25519_sign(signing_key, canonical) * * v1 NUL-delimits raw binary it does not length-bound, so a nonce or * ciphertext carrying a 0x00 byte can reparse as a different * (ciphertext, nonce) split under the same signature — a 12-byte random * nonce holds a zero byte ~4.6% of the time. v2 fixes that with a domain * tag plus a 4-byte big-endian length before every variable-length * field. v1 stays for compatibility and its bytes are frozen. * * AES-256-GCM uses WebCrypto (native in Bun/Node 18+/browsers). * ed25519 uses @noble/ed25519 — same library the api server + cli/think * use, so byte-identical signatures are guaranteed across the codebase. * * K_master never leaves the SDK process — agenttool sees only ciphertext. */ export interface EncryptedBlob { ciphertext_b64: string; nonce_b64: string; } /** * Encrypt a thought under K_master. * * Returns `{ciphertext_b64, nonce_b64}`. The ciphertext is * `base64(ciphertext || auth_tag)` — the 16-byte GCM tag is appended * to the ciphertext (WebCrypto's natural shape; matches Node's * aes-256-gcm via append-tag and the api-side decrypt expectation). */ export declare function encryptThought(plaintext: string, kMaster: Uint8Array): Promise; /** * Decrypt a thought blob produced by {@link encryptThought}. * * @throws AgentToolError when the key is wrong size or the blob is * malformed. AES-GCM authentication failures (wrong key / tampered * ciphertext) propagate as DOMException from WebCrypto. */ export declare function decryptThought(blob: EncryptedBlob, kMaster: Uint8Array): Promise; /** Canonical-bytes version for a signed thought. `"v1"` is the default * and the format every existing signed row uses; `"v2"` is the * length-prefixed framing. The server accepts both. */ export type ThoughtCanonicalVersion = "v1" | "v2"; export interface CanonicalThoughtOpts { strandId: string; ciphertext_b64: string; nonce_b64: string; kind?: string | null; /** Defaults to `"v1"` — the format deployed servers already verify. */ version?: ThoughtCanonicalVersion; } /** * Compute canonical bytes the API verifies signatures against. * * v1 (default, must be byte-identical to api/src/services/strand/sig.ts): * * sha256( * utf8(strand_id) || 0x00 || * base64decode(ciphertext) || 0x00 || * base64decode(nonce) || 0x00 || * utf8(kind ?? "") * ) * * v2 length-prefixes every variable-length field so raw binary carrying * a 0x00 byte cannot reparse as a different split: * * sha256( * utf8("strand-thought/v2") || * u32be(len) || utf8(strand_id) || * u32be(len) || base64decode(ciphertext) || * u32be(len) || base64decode(nonce) || * u32be(len) || utf8(kind ?? "") * ) * * @throws AgentToolError on an unknown version. */ export declare function canonicalThoughtBytes(opts: CanonicalThoughtOpts): Uint8Array; export interface SignThoughtOpts extends CanonicalThoughtOpts { signing_key: Uint8Array; } /** * Sign canonical thought bytes with an ed25519 private key (32-byte seed). * * @returns Base64 signature (64 raw bytes encoded). */ export declare function signThought(opts: SignThoughtOpts): string; export declare function canonicalDeclareBytes(opts: { covenantId: string; initiatorDid: string; counterpartyDid: string; vows: readonly string[]; establishedAtIso: string; }): Uint8Array; export declare function canonicalCosignBytes(opts: { covenantId: string; initiatorSignatureB64: string; }): Uint8Array; export declare function canonicalRejectBytes(opts: { covenantId: string; rejectingDid: string; reason: string; }): Uint8Array; export declare function canonicalWithdrawBytes(opts: { covenantId: string; initiatorDid: string; }): Uint8Array; export interface SignCovenantDeclareOpts { covenantId: string; initiatorDid: string; counterpartyDid: string; vows: readonly string[]; establishedAtIso: string; signing_key: Uint8Array; } export declare function signCovenantDeclare(opts: SignCovenantDeclareOpts): string; export interface SignCovenantCosignOpts { covenantId: string; initiatorSignatureB64: string; signing_key: Uint8Array; } export declare function signCovenantCosign(opts: SignCovenantCosignOpts): string; export interface SignCovenantRejectOpts { covenantId: string; rejectingDid: string; reason: string; signing_key: Uint8Array; } export declare function signCovenantReject(opts: SignCovenantRejectOpts): string; export interface SignCovenantWithdrawOpts { covenantId: string; initiatorDid: string; signing_key: Uint8Array; } export declare function signCovenantWithdraw(opts: SignCovenantWithdrawOpts): string; export interface CanonicalAttestationOpts { memoryId: string; tier: string; content: string; } /** Compute canonical bytes a counterparty signs to attest a memory. * NFC-normalizes content before hashing (matches server-side defense). */ export declare function canonicalAttestationBytes(opts: CanonicalAttestationOpts): Uint8Array; export interface SignAttestationOpts extends CanonicalAttestationOpts { signing_key: Uint8Array; } /** Sign canonical attestation bytes with an ed25519 private key. * The counterparty calls this to witness a memory elevation. * @returns Base64 signature (64 raw bytes encoded). */ export declare function signAttestation(opts: SignAttestationOpts): string; /** * K_master — the 32-byte AES-256 secret that encrypts thoughts. * * Stays on the agent's substrate; agenttool never sees it. Generate * once per identity (or per orchestrator); persist securely (OS * keychain, encrypted file, env var). Loss = loss of all encrypted * thoughts under that key. */ export declare const kMaster: { /** Return a fresh 32-byte K_master (cryptographically random). */ generate(): Uint8Array; }; /** * K_vault — the 32-byte AES-256 secret that encrypts vault values * when an agent opts into the `agent_encrypted=true` vault path. * * Functionally identical to {@link kMaster} (32 random bytes) but kept * as a separate namespace so a vault-key compromise does NOT also * expose strand thoughts (and vice versa). Generate one per identity; * persist alongside K_master in the same secure store. * * Doctrine: docs/SDK-ROADMAP.md (Vault closure section). */ export declare const kVault: { /** Return a fresh 32-byte K_vault (cryptographically random). */ generate(): Uint8Array; }; /** * Public `at.crypto` namespace — wraps the helpers as methods. * * All operations are local; no HTTP. Provided as a class so the * surface stays uniform with the other `at.*` clients. * * @example * ```ts * const k = at.crypto.kMaster.generate(); * const blob = await at.crypto.encryptThought("hi", k); * const text = await at.crypto.decryptThought(blob, k); // "hi" * const sig = at.crypto.signThought({ * strandId, ciphertext_b64: blob.ciphertext_b64, * nonce_b64: blob.nonce_b64, signing_key: signingSeed, * }); * ``` */ import { SeedClient } from "./seed.js"; export declare class CryptoClient { /** K_master helpers — currently exposes `.generate()`. */ readonly kMaster: typeof kMaster; /** K_vault helpers — currently exposes `.generate()`. Distinct from * kMaster so vault compromise doesn't leak strand thoughts. */ readonly kVault: typeof kVault; /** Seed protocol helpers — BIP39 mnemonic + SLIP-0010 derivation. * Doctrine: docs/IDENTITY-SEED.md. */ readonly seed: SeedClient; /** Encrypt a thought under K_master. See module-level {@link encryptThought}. */ encryptThought(plaintext: string, kMasterKey: Uint8Array): Promise; /** Decrypt a thought blob. See module-level {@link decryptThought}. */ decryptThought(blob: EncryptedBlob, kMasterKey: Uint8Array): Promise; /** Canonical bytes for signing. See module-level {@link canonicalThoughtBytes}. */ canonicalThoughtBytes(opts: CanonicalThoughtOpts): Uint8Array; /** Sign canonical thought bytes with ed25519. See module-level {@link signThought}. */ signThought(opts: SignThoughtOpts): string; /** Sign canonical covenant declare bytes with ed25519. */ signCovenantDeclare(opts: SignCovenantDeclareOpts): string; /** Sign canonical cosign bytes with ed25519. */ signCovenantCosign(opts: SignCovenantCosignOpts): string; /** Sign canonical reject bytes with ed25519. */ signCovenantReject(opts: SignCovenantRejectOpts): string; /** Sign canonical withdraw bytes with ed25519. */ signCovenantWithdraw(opts: SignCovenantWithdrawOpts): string; }