@tinycloud/sdk-services 3.7.0-beta.3 → 3.7.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/encryption/namedKeys.ts","../../src/encryption/canonical.ts","../../src/encryption/networkId.ts","../../src/encryption/receiverKey.ts","../../src/encryption/types.ts","../../src/encryption/response.ts","../../src/encryption/resource.ts"],"sourcesContent":["/**\n * Named-key client: key admin, descriptor reads, `ensureKey` and key-scoped\n * encrypt / decrypt.\n *\n * Rules enforced here:\n * - Everything is dark unless the node's `/info` `features` contains\n * `encryption-named-keys-v1`. Without it no key ability, key URN or `keyPath`\n * is sent to the node; the call throws `NAMED_KEYS_UNSUPPORTED` first.\n * - Every invocation carries exactly ONE capability whose `with` is the EXACT\n * key URN (a parsed `kind: \"key\"`; a prefix or bare network is never a\n * target) and whose `can` is the one ability the call needs.\n * - Admin bodies are hashed and serialized with the same canonical JSON used\n * by the network-admin invocation (`canonicalHashHex` / `canonicalize`).\n * - Envelope v2 carries `keyPath` and is wrapped to the KEY's public key. It is\n * decrypted only through `POST /invoke` with a single decrypt capability on\n * that key. v1 envelopes (no `keyPath`) go to the root-key path unchanged.\n * A key and the root, or two keys, are never mixed in one invocation.\n * - `key.delete` is not offered. The create body carries `deletable`, which is\n * `true` exactly when the path starts with `apps/`; the node refuses it with\n * `deletable_unavailable` while app-key deletion is not enabled there.\n */\n\nimport {\n base64Decode,\n base64Encode,\n canonicalHashHex,\n canonicalize,\n type Json,\n} from \"./canonical\";\nimport { parseNetworkId } from \"./networkId\";\nimport { generateRandomReceiverKey } from \"./receiverKey\";\nimport { openWrappedKey, verifyDecryptResponse } from \"./response\";\nimport {\n formatKeyResource,\n KEY_BLOCK_ABILITY,\n KEY_CREATE_ABILITY,\n KEY_UNBLOCK_ABILITY,\n nodeSupportsNamedKeys,\n parseEncryptionResource,\n type ParsedKeyResource,\n} from \"./resource\";\nimport {\n DECRYPT_ACTION,\n DECRYPT_FACT_TYPE,\n DEFAULT_ENCRYPTION_ALG,\n type DecryptInvocationFact,\n type DecryptRequestBody,\n type DecryptResponseBody,\n type EncryptionCrypto,\n type InlineEncryptedEnvelope,\n} from \"./types\";\n\nexport const KEY_ADMIN_FACT_TYPE = \"tinycloud.encryption.key-admin/v1\" as const;\nexport const KEY_DESCRIBE_FACT_TYPE =\n \"tinycloud.encryption.key-describe/v1\" as const;\nexport const NAMED_KEY_ENVELOPE_VERSION = 2 as const;\n\nexport type NamedKeyErrorCode =\n | \"NAMED_KEYS_UNSUPPORTED\"\n | \"INVALID_KEY_TARGET\"\n | \"INVALID_ENVELOPE\"\n | \"INVALID_RESPONSE\"\n | \"KEY_BLOCKED\"\n | \"KEY_NOT_FOUND\"\n | \"KEY_EXISTS\"\n | \"NETWORK_NOT_FOUND\"\n | \"NETWORK_NOT_ACTIVE\"\n | \"KEY_LIMIT_EXCEEDED\"\n | \"DELETABLE_UNAVAILABLE\"\n /** The key is not deletable (a deterministic key answers 409 `key_not_deletable`). */\n | \"KEY_NOT_DELETABLE\"\n | \"KEY_PATH_INVALID\"\n | \"KEY_UNWRAP_FAILED\"\n /** Key is in a state other than active/blocked; `rawState` holds the node's string. */\n | \"KEY_UNAVAILABLE\"\n /** `ensureKey` found a key that is not deterministic. */\n | \"KEY_KIND_MISMATCH\"\n | \"DECRYPT_FAILED\"\n | \"NODE_ERROR\"\n /** The node rejected `receiverPublicKey` (400, invocation not consumed). */\n | \"RECEIVER_KEY_INVALID\"\n /** The node object lacks an invocation hook the node-sdk wiring needs. */\n | \"NODE_HOOK_MISSING\";\n\n/** Longest untrusted value echoed into an error message. */\nconst MAX_ECHO_CHARS = 120;\n\n/** Escape every code point outside printable ASCII as `\\u{XXXX}`. */\nfunction escapeNonPrintable(value: string): string {\n let out = \"\";\n for (const ch of value) {\n const cp = ch.codePointAt(0)!;\n out += cp >= 0x20 && cp <= 0x7e ? ch : `\\\\u{${cp.toString(16).toUpperCase().padStart(4, \"0\")}}`;\n }\n return out;\n}\n\n/**\n * Render an untrusted value (resource, path, node-supplied state, parser or\n * signer text) for an error message: capped at 120 characters, then every\n * character outside printable ASCII escaped, so bidi, zero-width and other\n * format or control characters can never reach a terminal or log through a\n * message. Raw values live only in non-message fields (`input`, `rawState`,\n * `kind`, `detail`, `cause`).\n */\nexport function safeEcho(value: unknown): string {\n const text = typeof value === \"string\" ? value : String(value);\n const chars = Array.from(text);\n const clipped = chars.length > MAX_ECHO_CHARS ? `${chars.slice(0, MAX_ECHO_CHARS).join(\"\")}...` : text;\n return escapeNonPrintable(clipped);\n}\n\nexport interface NamedKeyErrorInit {\n status?: number;\n /** The node's `<code>` prefix, when it sent one. */\n nodeCode?: string;\n /** The node's `<detail>` text. Raw: never in `message`. */\n detail?: string;\n /** Raw state string (KEY_UNAVAILABLE). Raw: never in `message`. */\n rawState?: string;\n kind?: string;\n /** The rejected caller input (resource or path), unescaped. Raw: never in `message`. */\n input?: string;\n cause?: unknown;\n}\n\nexport class NamedKeyError extends Error {\n readonly code: NamedKeyErrorCode;\n readonly status?: number;\n readonly nodeCode?: string;\n readonly detail?: string;\n readonly rawState?: string;\n readonly kind?: string;\n readonly input?: string;\n\n constructor(code: NamedKeyErrorCode, message: string, init: NamedKeyErrorInit = {}) {\n // Backstop: whatever a call site interpolated, the message is printable ASCII.\n super(escapeNonPrintable(message), init.cause === undefined ? undefined : { cause: init.cause });\n this.name = \"NamedKeyError\";\n this.code = code;\n if (init.status !== undefined) this.status = init.status;\n if (init.nodeCode !== undefined) this.nodeCode = init.nodeCode;\n if (init.detail !== undefined) this.detail = init.detail;\n if (init.rawState !== undefined) this.rawState = init.rawState;\n if (init.kind !== undefined) this.kind = init.kind;\n if (init.input !== undefined) this.input = init.input;\n }\n}\n\n/**\n * Null-prototype tables: a node body such as `constructor: x` or `__proto__: x`\n * must never match an inherited property.\n */\nconst NODE_CODE_MAP: Readonly<Record<string, NamedKeyErrorCode>> = Object.assign(\n Object.create(null) as Record<string, NamedKeyErrorCode>,\n {\n key_blocked: \"KEY_BLOCKED\",\n key_not_found: \"KEY_NOT_FOUND\",\n key_exists: \"KEY_EXISTS\",\n network_not_found: \"NETWORK_NOT_FOUND\",\n network_not_active: \"NETWORK_NOT_ACTIVE\",\n key_limit_exceeded: \"KEY_LIMIT_EXCEEDED\",\n deletable_unavailable: \"DELETABLE_UNAVAILABLE\",\n key_not_deletable: \"KEY_NOT_DELETABLE\",\n key_path_invalid: \"KEY_PATH_INVALID\",\n key_unwrap_failed: \"KEY_UNWRAP_FAILED\",\n receiver_key_invalid: \"RECEIVER_KEY_INVALID\",\n } satisfies Record<string, NamedKeyErrorCode>,\n);\n\n/**\n * A deleted key answers `key_deleted`. It maps to the generic \"unavailable\"\n * error carrying the raw code.\n */\nconst UNAVAILABLE_NODE_CODES: Readonly<Record<string, true>> = Object.assign(\n Object.create(null) as Record<string, true>,\n { key_deleted: true as const },\n);\n\nconst NODE_ERROR_RE = /^([a-z][a-z0-9_]*):\\s?([\\s\\S]*)$/;\n\n/** Longest node error code kept in `nodeCode` and echoed into a message. */\nconst MAX_NODE_CODE_CHARS = 64;\n\n/** Map a node plain-text `<code>: <detail>` error response to a typed error. */\nexport function mapNodeKeyError(\n status: number,\n text: string,\n context: string,\n): NamedKeyError {\n const match = NODE_ERROR_RE.exec(text.trim());\n const nodeCode = match?.[1]?.slice(0, MAX_NODE_CODE_CHARS);\n const detail = match?.[2]?.trim();\n const base = {\n status,\n ...(nodeCode === undefined ? {} : { nodeCode }),\n ...(detail === undefined || detail.length === 0 ? {} : { detail }),\n };\n if (nodeCode !== undefined) {\n const mapped = NODE_CODE_MAP[nodeCode];\n if (mapped !== undefined) {\n return new NamedKeyError(mapped, `${context}: ${nodeCode}`, base);\n }\n if (UNAVAILABLE_NODE_CODES[nodeCode] === true) {\n return new NamedKeyError(\"KEY_UNAVAILABLE\", `${context}: key unavailable (${nodeCode})`, {\n ...base,\n rawState: nodeCode,\n });\n }\n }\n return new NamedKeyError(\"NODE_ERROR\", `${context}: node responded ${status}`, base);\n}\n\nexport interface PublicKeyDescriptor {\n networkId: string;\n keyPath: string;\n alg: string;\n keyVersion: number;\n publicEncryptionKey: string;\n}\n\nexport interface KeyDescriptor extends PublicKeyDescriptor {\n /** Raw node state string (`active`, `blocked`, ...). */\n state: string;\n /** `deterministic` outside `apps/`, `deletable` under it; kept as a string so unknown kinds surface. */\n kind: string;\n createdAt?: string;\n updatedAt?: string;\n}\n\n/** v2 inline envelope: v1 fields plus `keyPath`, wrapped to the key's public key. */\nexport interface NamedKeyEnvelope extends Omit<InlineEncryptedEnvelope, \"v\"> {\n v: typeof NAMED_KEY_ENVELOPE_VERSION;\n keyPath: string;\n}\n\nexport interface NamedKeyInvocationInput {\n targetNode: string;\n /** The EXACT key URN. This is the single capability's `with`. */\n resource: string;\n /** The single capability's `can`. */\n action: string;\n facts: Record<string, unknown>;\n}\n\nexport interface NamedKeyClientConfig {\n host: string;\n crypto: EncryptionCrypto;\n fetch: typeof fetch;\n /** Network used when a call does not name one. */\n defaultNetworkId: () => string;\n /** Mint one single-capability invocation and return the Authorization header value. */\n signInvocation: (\n input: NamedKeyInvocationInput,\n ) => Promise<{ authorization: string; invocationCid: string }>;\n /**\n * Decrypt a v1 (root key, bare network capability) envelope exactly as the\n * existing network decrypt path does.\n */\n decryptRoot?: (\n envelope: InlineEncryptedEnvelope,\n ) => Promise<\n | { ok: true; data: Uint8Array }\n | { ok: false; error: { message: string } }\n >;\n}\n\nexport interface KeyCallOptions {\n /** Full network id URN. Defaults to the client's default network. */\n networkId?: string;\n}\n\nexport interface KeyEncryptOptions extends KeyCallOptions {\n aad?: Uint8Array;\n metadata?: Record<string, string>;\n}\n\nexport type DecryptManyResult =\n | { ok: true; data: Uint8Array }\n | { ok: false; error: NamedKeyError };\n\n/** Accept only a parsed `kind: \"key\"`; a prefix or bare network is never a target. */\nexport function requireKeyTarget(resource: string): ParsedKeyResource {\n let parsed;\n try {\n parsed = parseEncryptionResource(resource);\n } catch (error) {\n throw new NamedKeyError(\n \"INVALID_KEY_TARGET\",\n `not a valid encryption key resource: ${safeEcho(error instanceof Error ? error.message : error)}`,\n { cause: error, input: resource },\n );\n }\n if (parsed.kind !== \"key\") {\n throw new NamedKeyError(\n \"INVALID_KEY_TARGET\",\n `a ${parsed.kind} resource cannot be an invocation target; an exact key is required`,\n { input: resource },\n );\n }\n return parsed;\n}\n\n/** `{type, targetNode, networkId, keyPath, action, bodyHash}` for key admin routes. */\nexport function buildKeyAdminFacts(input: {\n crypto: EncryptionCrypto;\n targetNode: string;\n keyResource: string;\n action: string;\n body: Json;\n}): Record<string, unknown> {\n const key = requireKeyTarget(input.keyResource);\n return {\n type: KEY_ADMIN_FACT_TYPE,\n targetNode: input.targetNode,\n networkId: key.networkId,\n keyPath: key.path,\n action: input.action,\n bodyHash: canonicalHashHex(input.crypto.sha256, input.body),\n };\n}\n\n/** `{type, targetNode, networkId, keyPath}`; no body hash. */\nexport function buildKeyDescribeFacts(input: {\n targetNode: string;\n keyResource: string;\n}): Record<string, unknown> {\n const key = requireKeyTarget(input.keyResource);\n return {\n type: KEY_DESCRIBE_FACT_TYPE,\n targetNode: input.targetNode,\n networkId: key.networkId,\n keyPath: key.path,\n };\n}\n\nconst DESCRIBE_ABILITIES: Record<string, true> = {\n [DECRYPT_ACTION]: true,\n [KEY_CREATE_ABILITY]: true,\n [KEY_BLOCK_ABILITY]: true,\n [KEY_UNBLOCK_ABILITY]: true,\n};\n\nfunction invalidResponse(what: string): NamedKeyError {\n return new NamedKeyError(\"INVALID_RESPONSE\", `node returned an invalid ${what}`);\n}\n\nfunction parsePublicDescriptor(\n raw: unknown,\n networkId: string,\n keyPath: string,\n): PublicKeyDescriptor {\n const value = (typeof raw === \"object\" && raw !== null ? raw : {}) as Record<string, unknown>;\n if (\n typeof value.alg !== \"string\" ||\n typeof value.keyVersion !== \"number\" ||\n !Number.isInteger(value.keyVersion) ||\n typeof value.publicEncryptionKey !== \"string\" ||\n value.publicEncryptionKey.length === 0\n ) {\n throw invalidResponse(\"key descriptor\");\n }\n if (value.networkId !== networkId || value.keyPath !== keyPath) {\n throw new NamedKeyError(\n \"INVALID_RESPONSE\",\n \"node returned a descriptor for a different network or key\",\n );\n }\n return {\n networkId,\n keyPath,\n alg: value.alg,\n keyVersion: value.keyVersion,\n publicEncryptionKey: value.publicEncryptionKey,\n };\n}\n\nfunction parseFullDescriptor(\n value: unknown,\n networkId: string,\n keyPath: string,\n): KeyDescriptor {\n const base = parsePublicDescriptor(value, networkId, keyPath);\n const record = value as Record<string, unknown>;\n if (typeof record.state !== \"string\" || typeof record.kind !== \"string\") {\n throw invalidResponse(\"key descriptor (state/kind missing)\");\n }\n return {\n ...base,\n state: record.state,\n kind: record.kind,\n ...(typeof record.createdAt === \"string\" ? { createdAt: record.createdAt } : {}),\n ...(typeof record.updatedAt === \"string\" ? { updatedAt: record.updatedAt } : {}),\n };\n}\n\n/** Success only for `state: \"active\"` AND the expected `kind`. */\nfunction assertEnsuredDescriptor(descriptor: KeyDescriptor, expectedKind: string): KeyDescriptor {\n if (descriptor.state === \"blocked\") {\n throw new NamedKeyError(\"KEY_BLOCKED\", `key ${safeEcho(descriptor.keyPath)} is blocked`, {\n rawState: descriptor.state,\n });\n }\n if (descriptor.state !== \"active\") {\n throw new NamedKeyError(\n \"KEY_UNAVAILABLE\",\n `key ${safeEcho(descriptor.keyPath)} is unavailable (state ${safeEcho(descriptor.state)})`,\n { rawState: descriptor.state },\n );\n }\n if (descriptor.kind !== expectedKind) {\n throw new NamedKeyError(\n \"KEY_KIND_MISMATCH\",\n `key ${safeEcho(descriptor.keyPath)} exists with kind ${safeEcho(descriptor.kind)}, not ${expectedKind}`,\n { kind: descriptor.kind },\n );\n }\n return descriptor;\n}\n\n/** Keys under `apps/` are the deletable keys; everything else is deterministic. */\nfunction isAppKeyPath(path: string): boolean {\n return path.startsWith(\"apps/\");\n}\n\nconst REQUIRED_ENVELOPE_STRINGS = [\n \"alg\",\n \"encryptedSymmetricKey\",\n \"encryptedSymmetricKeyHash\",\n \"ciphertext\",\n] as const;\n\nexport class NamedKeyClient {\n /** One `/info` read per client: the node DID once the feature is confirmed. */\n private nodeId?: Promise<string>;\n\n constructor(private readonly config: NamedKeyClientConfig) {}\n\n private networkFor(options?: KeyCallOptions): string {\n return options?.networkId ?? this.config.defaultNetworkId();\n }\n\n private keyUrn(networkId: string, path: string): string {\n try {\n return formatKeyResource(networkId, path);\n } catch (error) {\n throw new NamedKeyError(\n \"INVALID_KEY_TARGET\",\n safeEcho(error instanceof Error ? error.message : error),\n { cause: error, input: `${networkId}/${path}` },\n );\n }\n }\n\n /**\n * Require the named-keys feature and return the node DID. The first call\n * reads `/info`; a positive answer is cached for the life of this client, so\n * later calls make no `/info` request. A node without the feature is NOT\n * cached: the typed `NAMED_KEYS_UNSUPPORTED` error is thrown and the next\n * call re-reads (so a retry sees an upgraded node). No key request is made\n * before the gate passes.\n */\n private requireNode(): Promise<string> {\n if (this.nodeId === undefined) {\n const pending = this.readNodeInfo();\n this.nodeId = pending;\n pending.catch(() => {\n if (this.nodeId === pending) this.nodeId = undefined;\n });\n }\n return this.nodeId;\n }\n\n /** Clear the cached `/info` answer; the next call reads it again. */\n refreshNodeInfo(): void {\n this.nodeId = undefined;\n }\n\n private async readNodeInfo(): Promise<string> {\n const response = await this.config.fetch(`${this.config.host}/info`);\n if (!response.ok) {\n throw mapNodeKeyError(response.status, await safeText(response), \"failed to read node info\");\n }\n const info = (await response.json()) as { nodeId?: unknown; features?: unknown };\n if (!nodeSupportsNamedKeys(info)) {\n throw new NamedKeyError(\n \"NAMED_KEYS_UNSUPPORTED\",\n \"this node does not support named encryption keys\",\n );\n }\n if (typeof info.nodeId !== \"string\" || info.nodeId.length === 0) {\n throw invalidResponse(\"/info response (nodeId missing)\");\n }\n return info.nodeId;\n }\n\n private keysUrl(networkId: string, path: string): string {\n // The network id is validated by `keyUrn` (strict grammar) before any URL is\n // built; every character it can hold (`A-Za-z0-9._:-`) is an RFC 3986 pchar\n // and the node refuses `%`, so it goes into the path raw. keyPath segments\n // are raw too: the grammar excludes `%`.\n return `${this.config.host}/encryption/networks/${networkId}/keys/${path}`;\n }\n\n /** Public GET: the key's public key. Derive-only (no 404) outside `apps/`; under `apps/` the node answers 404. */\n async getPublicKey(path: string, options?: KeyCallOptions): Promise<PublicKeyDescriptor> {\n const networkId = this.networkFor(options);\n this.keyUrn(networkId, path);\n await this.requireNode();\n const response = await this.config.fetch(this.keysUrl(networkId, path), { method: \"GET\" });\n if (!response.ok) {\n throw mapNodeKeyError(response.status, await safeText(response), \"failed to read key\");\n }\n return parsePublicDescriptor(await response.json(), networkId, path);\n }\n\n /**\n * Authenticated GET: the full descriptor, or `null` on `key_not_found`.\n * `ability` is the one capability the invocation carries (default decrypt).\n */\n async describeKey(\n path: string,\n options?: KeyCallOptions & { ability?: string },\n ): Promise<KeyDescriptor | null> {\n const networkId = this.networkFor(options);\n const body = await this.describeBody(path, options);\n return body === null ? null : parseFullDescriptor(body, networkId, path);\n }\n\n /** Public fields of an authenticated describe (decrypt ability); `key_not_found` throws. */\n private async describePublic(path: string, options?: KeyCallOptions): Promise<PublicKeyDescriptor> {\n const networkId = this.networkFor(options);\n const body = await this.describeBody(path, options);\n if (body === null) {\n throw new NamedKeyError(\"KEY_NOT_FOUND\", `key ${safeEcho(path)} does not exist`, { status: 404 });\n }\n return parsePublicDescriptor(body, networkId, path);\n }\n\n /** The authenticated GET response body, or `null` on `key_not_found`. */\n private async describeBody(\n path: string,\n options?: KeyCallOptions & { ability?: string },\n ): Promise<unknown> {\n const networkId = this.networkFor(options);\n const keyResource = this.keyUrn(networkId, path);\n const ability = options?.ability ?? DECRYPT_ACTION;\n if (DESCRIBE_ABILITIES[ability] !== true) {\n throw new NamedKeyError(\"INVALID_KEY_TARGET\", `${safeEcho(ability)} cannot describe a key`, { input: ability });\n }\n const targetNode = await this.requireNode();\n const signed = await this.config.signInvocation({\n targetNode,\n resource: keyResource,\n action: ability,\n facts: buildKeyDescribeFacts({ targetNode, keyResource }),\n });\n const response = await this.config.fetch(this.keysUrl(networkId, path), {\n method: \"GET\",\n headers: { Authorization: signed.authorization },\n });\n if (response.status === 404) {\n const error = mapNodeKeyError(404, await safeText(response), \"failed to describe key\");\n if (error.code === \"KEY_NOT_FOUND\") return null;\n throw error;\n }\n if (!response.ok) {\n throw mapNodeKeyError(response.status, await safeText(response), \"failed to describe key\");\n }\n return response.json();\n }\n\n private async admin(\n route: \"keys\" | \"key-actions/block\" | \"key-actions/unblock\",\n action: string,\n path: string,\n options?: KeyCallOptions,\n ): Promise<KeyDescriptor> {\n const networkId = this.networkFor(options);\n const keyResource = this.keyUrn(networkId, path);\n const body: Json =\n route === \"keys\" ? { keyPath: path, deletable: isAppKeyPath(path) } : { keyPath: path };\n const targetNode = await this.requireNode();\n const signed = await this.config.signInvocation({\n targetNode,\n resource: keyResource,\n action,\n facts: buildKeyAdminFacts({\n crypto: this.config.crypto,\n targetNode,\n keyResource,\n action,\n body,\n }),\n });\n const response = await this.config.fetch(\n `${this.config.host}/encryption/networks/${networkId}/${route}`,\n {\n method: \"POST\",\n headers: {\n Authorization: signed.authorization,\n \"Content-Type\": \"application/json\",\n },\n body: canonicalize(body),\n },\n );\n if (!response.ok) {\n throw mapNodeKeyError(response.status, await safeText(response), `failed to ${action}`);\n }\n return parseFullDescriptor(unwrapDescriptor(await response.json()), networkId, path);\n }\n\n /** `key.create`: `deletable` is `true` exactly under `apps/`, else a deterministic key. */\n createKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor> {\n return this.admin(\"keys\", KEY_CREATE_ABILITY, path, options);\n }\n\n blockKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor> {\n return this.admin(\"key-actions/block\", KEY_BLOCK_ABILITY, path, options);\n }\n\n unblockKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor> {\n return this.admin(\"key-actions/unblock\", KEY_UNBLOCK_ABILITY, path, options);\n }\n\n /**\n * Make sure a key exists and is usable: authenticated GET; on\n * `key_not_found` create it; on 409 `key_exists` read it again. Succeeds only\n * for `state: \"active\"` and the kind the path implies: `deletable` under\n * `apps/`, `deterministic` elsewhere.\n */\n async ensureKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor> {\n const describe = () =>\n this.describeKey(path, { ...options, ability: KEY_CREATE_ABILITY });\n let descriptor = await describe();\n if (descriptor === null) {\n try {\n descriptor = await this.createKey(path, options);\n } catch (error) {\n if (!(error instanceof NamedKeyError) || error.code !== \"KEY_EXISTS\") throw error;\n descriptor = await describe();\n if (descriptor === null) {\n throw new NamedKeyError(\n \"KEY_NOT_FOUND\",\n `key ${safeEcho(path)} reported as existing but could not be read back`,\n );\n }\n }\n }\n return assertEnsuredDescriptor(descriptor, isAppKeyPath(path) ? \"deletable\" : \"deterministic\");\n }\n\n /** Encrypt to a named key: envelope v2, symmetric key wrapped to the key's public key. */\n async encrypt(\n path: string,\n plaintext: Uint8Array,\n options?: KeyEncryptOptions,\n ): Promise<NamedKeyEnvelope> {\n // The node answers a public GET under `apps/` with 404 `key_not_found`, so\n // an app key's public key is read through the authenticated describe.\n const descriptor = isAppKeyPath(path)\n ? await this.describePublic(path, options)\n : await this.getPublicKey(path, options);\n const { crypto } = this.config;\n const symmetricKey = crypto.randomBytes(32);\n const ciphertext = crypto.authEncrypt(symmetricKey, plaintext, options?.aad);\n const wrapped = crypto.sealToNetworkKey(\n base64Decode(descriptor.publicEncryptionKey),\n symmetricKey,\n );\n const encryptedSymmetricKey = base64Encode(wrapped);\n return {\n v: NAMED_KEY_ENVELOPE_VERSION,\n networkId: descriptor.networkId,\n keyPath: descriptor.keyPath,\n alg: descriptor.alg ?? DEFAULT_ENCRYPTION_ALG,\n keyVersion: descriptor.keyVersion,\n encryptedSymmetricKey,\n encryptedSymmetricKeyHash: canonicalHashHex(crypto.sha256, encryptedSymmetricKey),\n ciphertext: base64Encode(ciphertext),\n ...(options?.aad !== undefined ? { aad: base64Encode(options.aad) } : {}),\n ...(options?.metadata !== undefined ? { metadata: options.metadata } : {}),\n };\n }\n\n /**\n * Decrypt one envelope. v1 (no `keyPath`) uses the root-key path exactly as\n * before; v2 uses one decrypt capability on the exact key via `/invoke`.\n */\n async decrypt(envelope: InlineEncryptedEnvelope | NamedKeyEnvelope): Promise<Uint8Array> {\n const candidate = (typeof envelope === \"object\" && envelope !== null ? envelope : {}) as unknown as Record<string, unknown>;\n if (candidate.v === 1 && candidate.keyPath === undefined) {\n return this.decryptRoot(candidate as unknown as InlineEncryptedEnvelope);\n }\n if (candidate.v === NAMED_KEY_ENVELOPE_VERSION && typeof candidate.keyPath === \"string\") {\n return this.decryptKey(this.validateKeyEnvelope(candidate));\n }\n throw new NamedKeyError(\n \"INVALID_ENVELOPE\",\n \"envelope must be v1 without keyPath or v2 with keyPath\",\n );\n }\n\n /**\n * Decrypt a batch. Each envelope is its own invocation with its own single\n * capability, so v1 and v2 (or two different keys) are never combined.\n */\n async decryptMany(\n envelopes: ReadonlyArray<InlineEncryptedEnvelope | NamedKeyEnvelope>,\n ): Promise<DecryptManyResult[]> {\n const results: DecryptManyResult[] = [];\n for (const envelope of envelopes) {\n try {\n results.push({ ok: true, data: await this.decrypt(envelope) });\n } catch (error) {\n results.push({\n ok: false,\n error:\n error instanceof NamedKeyError\n ? error\n : new NamedKeyError(\"DECRYPT_FAILED\", \"decrypt failed\", { cause: error }),\n });\n }\n }\n return results;\n }\n\n private async decryptRoot(envelope: InlineEncryptedEnvelope): Promise<Uint8Array> {\n if (this.config.decryptRoot === undefined) {\n throw new NamedKeyError(\"DECRYPT_FAILED\", \"no root-key decrypt path is configured\");\n }\n const result = await this.config.decryptRoot(envelope);\n if (!result.ok) {\n throw new NamedKeyError(\"DECRYPT_FAILED\", safeEcho(result.error.message), { cause: result.error });\n }\n return result.data;\n }\n\n private validateKeyEnvelope(e: Record<string, unknown>): NamedKeyEnvelope {\n for (const field of REQUIRED_ENVELOPE_STRINGS) {\n if (typeof e[field] !== \"string\" || (e[field] as string).length === 0) {\n throw new NamedKeyError(\"INVALID_ENVELOPE\", `envelope.${field} must be a non-empty string`);\n }\n }\n if (typeof e.keyVersion !== \"number\" || !Number.isInteger(e.keyVersion)) {\n throw new NamedKeyError(\"INVALID_ENVELOPE\", \"envelope.keyVersion must be an integer\");\n }\n if (typeof e.networkId !== \"string\") {\n throw new NamedKeyError(\"INVALID_ENVELOPE\", \"envelope.networkId must be a string\");\n }\n try {\n parseNetworkId(e.networkId);\n } catch (error) {\n throw new NamedKeyError(\"INVALID_ENVELOPE\", \"envelope.networkId is malformed\", { cause: error });\n }\n const expectedHash = canonicalHashHex(\n this.config.crypto.sha256,\n e.encryptedSymmetricKey as string,\n );\n if (expectedHash !== e.encryptedSymmetricKeyHash) {\n throw new NamedKeyError(\n \"INVALID_ENVELOPE\",\n \"envelope.encryptedSymmetricKeyHash does not match encryptedSymmetricKey\",\n );\n }\n return e as unknown as NamedKeyEnvelope;\n }\n\n private async decryptKey(envelope: NamedKeyEnvelope): Promise<Uint8Array> {\n const { crypto } = this.config;\n const keyResource = this.keyUrn(envelope.networkId, envelope.keyPath);\n const targetNode = await this.requireNode();\n\n const receiverKey = generateRandomReceiverKey({ crypto });\n const receiverPublicKey = base64Encode(receiverKey.publicKey);\n const receiverPublicKeyHash = canonicalHashHex(crypto.sha256, receiverPublicKey);\n const body: DecryptRequestBody & { keyPath: string } = {\n type: DECRYPT_FACT_TYPE,\n targetNode,\n networkId: envelope.networkId,\n keyPath: envelope.keyPath,\n alg: envelope.alg,\n keyVersion: envelope.keyVersion,\n encryptedSymmetricKey: envelope.encryptedSymmetricKey,\n encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,\n receiverPublicKey,\n receiverPublicKeyHash,\n };\n const canonicalBody = canonicalize(body as unknown as Json);\n const facts: DecryptInvocationFact & { keyPath: string } = {\n type: DECRYPT_FACT_TYPE,\n targetNode,\n networkId: envelope.networkId,\n keyPath: envelope.keyPath,\n bodyHash: canonicalHashHex(crypto.sha256, body as unknown as Json),\n encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,\n receiverPublicKeyHash,\n alg: envelope.alg,\n keyVersion: envelope.keyVersion,\n };\n const signed = await this.config.signInvocation({\n targetNode,\n resource: keyResource,\n action: DECRYPT_ACTION,\n facts: facts as unknown as Record<string, unknown>,\n });\n const response = await this.config.fetch(`${this.config.host}/invoke`, {\n method: \"POST\",\n headers: {\n Authorization: signed.authorization,\n \"Content-Type\": \"application/json\",\n },\n body: canonicalBody,\n });\n if (!response.ok) {\n throw mapNodeKeyError(response.status, await safeText(response), \"failed to decrypt\");\n }\n const verified = verifyDecryptResponse({\n crypto,\n request: body,\n facts,\n invocationCid: signed.invocationCid,\n requestBodyHash: facts.bodyHash,\n response: (await response.json()) as DecryptResponseBody,\n });\n if (!verified.ok) {\n throw new NamedKeyError(\"INVALID_RESPONSE\", safeEcho(verified.error.message), { cause: verified.error });\n }\n const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data);\n try {\n return crypto.authDecrypt(\n symmetricKey,\n base64Decode(envelope.ciphertext),\n envelope.aad !== undefined ? base64Decode(envelope.aad) : undefined,\n );\n } catch (error) {\n throw new NamedKeyError(\"DECRYPT_FAILED\", \"local decryption failed\", { cause: error });\n }\n }\n}\n\nfunction unwrapDescriptor(body: unknown): unknown {\n const outer = (typeof body === \"object\" && body !== null ? body : {}) as { descriptor?: unknown };\n return typeof outer.descriptor === \"object\" && outer.descriptor !== null ? outer.descriptor : body;\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return \"\";\n }\n}\n\n","/**\n * Canonical JSON serialization and content hashing for TinyCloud\n * encryption requests/responses.\n *\n * The node and SDK must agree byte-for-byte on the canonical form so\n * that body-hash bindings (`bodyHash`, `encryptedSymmetricKeyHash`,\n * `receiverPublicKeyHash`) verify on both sides.\n *\n * Canonical rules:\n * - Object keys are sorted lexicographically by code point.\n * - Strings are encoded with the JSON.stringify default (RFC 8259).\n * - Numbers are emitted via JSON.stringify; callers should restrict to\n * integers or use string fields for high-precision values.\n * - `undefined` properties are dropped. `null` is preserved.\n * - Arrays preserve element order.\n *\n * Hashing uses SHA-256 supplied by the caller (via an `EncryptionCrypto`\n * binding) so this module stays platform-agnostic.\n */\n\nexport type Json =\n | null\n | boolean\n | number\n | string\n | Json[]\n | { [key: string]: Json | undefined };\n\n/**\n * Produce the canonical JSON string for {@link value}. Object keys are\n * sorted, `undefined` properties are dropped, and primitive types are\n * encoded by `JSON.stringify`.\n */\nexport function canonicalize(value: Json | undefined): string {\n if (value === undefined) {\n return \"\";\n }\n return stringify(value);\n}\n\nfunction stringify(value: Json): string {\n if (value === null) return \"null\";\n switch (typeof value) {\n case \"boolean\":\n case \"number\":\n return JSON.stringify(value);\n case \"string\":\n return JSON.stringify(value);\n case \"object\": {\n if (Array.isArray(value)) {\n return `[${value.map(stringify).join(\",\")}]`;\n }\n const keys = Object.keys(value).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = value[k];\n if (v === undefined) continue;\n parts.push(`${JSON.stringify(k)}:${stringify(v)}`);\n }\n return `{${parts.join(\",\")}}`;\n }\n default:\n throw new TypeError(\n `canonicalize: unsupported value type ${typeof value}`,\n );\n }\n}\n\nconst HEX = \"0123456789abcdef\";\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i];\n out += HEX[(b >> 4) & 0xf] + HEX[b & 0xf];\n }\n return out;\n}\n\nexport function hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) {\n throw new Error(\"hex string must have even length\");\n }\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n const hi = parseInt(hex[i * 2], 16);\n const lo = parseInt(hex[i * 2 + 1], 16);\n if (Number.isNaN(hi) || Number.isNaN(lo)) {\n throw new Error(\"invalid hex character\");\n }\n out[i] = (hi << 4) | lo;\n }\n return out;\n}\n\nexport function base64Encode(bytes: Uint8Array): string {\n // base64 (standard, not url-safe) so envelopes pass through JSON cleanly.\n // We avoid `btoa` to stay node-friendly without polyfills.\n const chars =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i];\n const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;\n const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;\n out += chars[(b0 >> 2) & 0x3f];\n out += chars[((b0 << 4) | (b1 >> 4)) & 0x3f];\n out += i + 1 < bytes.length ? chars[((b1 << 2) | (b2 >> 6)) & 0x3f] : \"=\";\n out += i + 2 < bytes.length ? chars[b2 & 0x3f] : \"=\";\n }\n return out;\n}\n\nexport function base64Decode(s: string): Uint8Array {\n const clean = s.replace(/[^A-Za-z0-9+/=]/g, \"\");\n const len = clean.length;\n if (len % 4 !== 0) {\n throw new Error(\"invalid base64 input\");\n }\n const padding =\n clean.endsWith(\"==\") ? 2 : clean.endsWith(\"=\") ? 1 : 0;\n const outLen = (len / 4) * 3 - padding;\n const out = new Uint8Array(outLen);\n const lookup: Record<string, number> = {};\n const chars =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;\n\n let outIdx = 0;\n for (let i = 0; i < len; i += 4) {\n const v0 = lookup[clean[i]] ?? 0;\n const v1 = lookup[clean[i + 1]] ?? 0;\n const v2 = clean[i + 2] === \"=\" ? 0 : (lookup[clean[i + 2]] ?? 0);\n const v3 = clean[i + 3] === \"=\" ? 0 : (lookup[clean[i + 3]] ?? 0);\n const b0 = (v0 << 2) | (v1 >> 4);\n const b1 = ((v1 & 0x0f) << 4) | (v2 >> 2);\n const b2 = ((v2 & 0x03) << 6) | v3;\n if (outIdx < outLen) out[outIdx++] = b0;\n if (outIdx < outLen) out[outIdx++] = b1;\n if (outIdx < outLen) out[outIdx++] = b2;\n }\n return out;\n}\n\nexport function utf8Encode(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\nexport function utf8Decode(b: Uint8Array): string {\n return new TextDecoder().decode(b);\n}\n\n/**\n * Compute `hexEncode(sha256(canonicalize(value)))`. The SHA-256 binding\n * is injected so the module remains usable in both the WASM and pure-JS\n * paths.\n */\nexport function canonicalHashHex(\n sha256: (bytes: Uint8Array) => Uint8Array,\n value: Json,\n): string {\n const canonical = canonicalize(value);\n return hexEncode(sha256(utf8Encode(canonical)));\n}\n","/**\n * TinyCloud encryption network identifiers.\n *\n * A network id is `urn:tinycloud:encryption:<ownerDid>:<network>` where\n * `ownerDid` is the owner's DID and `network` is a\n * non-empty label drawn from `[a-z0-9][a-z0-9-]*`.\n *\n * The embedded owner DID is the root authority for the network: any\n * delegation chain ending in a `tinycloud.encryption/decrypt` grant on\n * the network must root at this owner DID.\n */\n\nconst URN_PREFIX = \"urn:tinycloud:encryption:\";\nconst NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;\nconst PKH_EIP155_DID_RE = /^did:pkh:eip155:(\\d+):(0x[a-fA-F0-9]{40})$/;\n\nexport interface ParsedNetworkId {\n /** The full URN string. */\n networkId: string;\n /** Owner DID embedded in the URN (the network's root authority). */\n ownerDid: string;\n /** Network label (the suffix after the owner DID). */\n name: string;\n}\n\nexport class NetworkIdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NetworkIdError\";\n }\n}\n\n/**\n * Parse a network id string into its owner DID and name components.\n *\n * Throws {@link NetworkIdError} when the input does not match\n * `urn:tinycloud:encryption:<did>:<name>`, when the embedded DID is\n * malformed, or when the network name fails {@link NETWORK_NAME_RE}.\n */\nexport function parseNetworkId(networkId: string): ParsedNetworkId {\n if (typeof networkId !== \"string\" || networkId.length === 0) {\n throw new NetworkIdError(\"networkId must be a non-empty string\");\n }\n if (!networkId.startsWith(URN_PREFIX)) {\n throw new NetworkIdError(\n `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`,\n );\n }\n const body = networkId.slice(URN_PREFIX.length);\n // `body` = \"<ownerDid>:<network>\"\n // ownerDid contains ':' (e.g. did:key:z6Mk...), so we split on the LAST colon\n // and treat the suffix as the network name.\n const lastColon = body.lastIndexOf(\":\");\n if (lastColon <= 0 || lastColon === body.length - 1) {\n throw new NetworkIdError(\n `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`,\n );\n }\n const ownerDid = body.slice(0, lastColon);\n const name = body.slice(lastColon + 1);\n\n if (!ownerDid.startsWith(\"did:\")) {\n throw new NetworkIdError(\n `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`,\n );\n }\n // Minimal DID shape: did:<method>:<id> — three colon-separated segments,\n // each non-empty.\n const didParts = ownerDid.split(\":\");\n if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {\n throw new NetworkIdError(\n `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`,\n );\n }\n if (!NETWORK_NAME_RE.test(name)) {\n throw new NetworkIdError(\n `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`,\n );\n }\n return { networkId, ownerDid, name };\n}\n\n/**\n * Construct a network id URN from an owner DID and a network name.\n * Validates inputs and throws {@link NetworkIdError} on bad shape.\n */\nexport function buildNetworkId(ownerDid: string, name: string): string {\n if (typeof ownerDid !== \"string\" || !ownerDid.startsWith(\"did:\")) {\n throw new NetworkIdError(\"ownerDid must be a DID\");\n }\n if (typeof name !== \"string\" || !NETWORK_NAME_RE.test(name)) {\n throw new NetworkIdError(\n `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`,\n );\n }\n const networkId = `${URN_PREFIX}${ownerDid}:${name}`;\n // Re-validate the composed result so the same error path triggers\n // for caller inputs that compose into a malformed URN.\n parseNetworkId(networkId);\n return networkId;\n}\n\n/**\n * Returns true when {@link networkId} is a syntactically valid network URN.\n */\nexport function isNetworkId(networkId: unknown): networkId is string {\n if (typeof networkId !== \"string\") {\n return false;\n }\n try {\n parseNetworkId(networkId);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction parsePkhOwnerDid(ownerDid: string):\n | { chainId: string; address: string }\n | null {\n const match = ownerDid.match(PKH_EIP155_DID_RE);\n if (!match) return null;\n return {\n chainId: match[1],\n address: match[2].toLowerCase(),\n };\n}\n\n/**\n * Compare owner DIDs as network principals. For `did:pkh:eip155`, EVM\n * address casing is not part of principal identity; other DID methods\n * remain exact string matches.\n */\nexport function ownerDidMatches(a: string, b: string): boolean {\n const aPkh = parsePkhOwnerDid(a);\n const bPkh = parsePkhOwnerDid(b);\n if (aPkh && bPkh) {\n return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;\n }\n return a === b;\n}\n\n/**\n * Resolve the discovery key used to look up a network's descriptor under\n * an owner's public account-space.\n *\n * Format: `.well-known/encryption/network/<name>`.\n */\nexport function networkDiscoveryKey(name: string): string {\n if (!NETWORK_NAME_RE.test(name)) {\n throw new NetworkIdError(\n `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`,\n );\n }\n return `.well-known/encryption/network/${name}`;\n}\n\nexport const ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;\nexport const NETWORK_NAME_PATTERN = NETWORK_NAME_RE;\n","/**\n * Per-request receiver key generation.\n *\n * The receiver key pair is generated for a single decrypt request: the\n * SDK sends `receiverPublicKey` to the node so the node can rewrap the\n * symmetric key; the matching private key never leaves the SDK.\n *\n * Two derivation modes are supported:\n *\n * 1. **Random** — `crypto.randomBytes(32)` seeds a fresh x25519 pair.\n * This is the default for one-of-one flows.\n *\n * 2. **Signed-context** — the caller provides a signer that signs a\n * deterministic context message (network id + nonce + invocation\n * intent). The signature bytes are SHA-256'd into the seed. This is\n * useful when callers want the receiver key to be reproducible by\n * the same session signer (e.g. for delegated reads where the\n * signer is the only stable secret).\n */\n\nimport { utf8Encode } from \"./canonical\";\nimport type {\n EncryptionCrypto,\n ReceiverKeyPair,\n ReceiverKeySigner,\n} from \"./types\";\n\nexport interface RandomReceiverKeyInput {\n crypto: EncryptionCrypto;\n}\n\nexport function generateRandomReceiverKey(\n input: RandomReceiverKeyInput,\n): ReceiverKeyPair {\n const seed = input.crypto.randomBytes(32);\n return input.crypto.x25519FromSeed(seed);\n}\n\nexport interface SignedReceiverKeyInput {\n crypto: EncryptionCrypto;\n signer: ReceiverKeySigner;\n networkId: string;\n /** Optional extra context (e.g. invocation nonce) folded into the message. */\n context?: string;\n}\n\n/**\n * Deterministic receiver-key derivation: signs a context string with\n * the supplied signer, then SHA-256s the signature bytes into the\n * x25519 seed.\n *\n * The context message is:\n * `tinycloud.encryption.receiver-key/v1:<networkId>:<context>`\n *\n * Callers MUST include unique context (e.g. a fresh nonce) on every\n * request unless they explicitly want reproducibility.\n */\nexport async function deriveSignedReceiverKey(\n input: SignedReceiverKeyInput,\n): Promise<ReceiverKeyPair> {\n const message = `tinycloud.encryption.receiver-key/v1:${input.networkId}:${input.context ?? \"\"}`;\n const sig = await input.signer.signMessage(message);\n // Treat the signature as opaque bytes; sha256 collapses to a 32-byte seed.\n const sigBytes = utf8Encode(sig);\n const seed = input.crypto.sha256(sigBytes);\n return input.crypto.x25519FromSeed(seed);\n}\n","/**\n * Type definitions for TinyCloud one-of-one encryption.\n *\n * Wire shapes mirror the protocol described in the encryption\n * architecture: inline envelopes carry the encrypted symmetric key\n * alongside the ciphertext; decrypt requests are short-lived UCAN\n * invocations against a target node plus networkId.\n */\n\nimport type { Json } from \"./canonical\";\nimport type { PermissionHint } from \"../types\";\n\n/** Default ciphersuite identifier for v1 envelopes. */\nexport const DEFAULT_ENCRYPTION_ALG = \"x25519-aes256gcm/v1\" as const;\n\n/** Inline-envelope schema version. */\nexport const ENVELOPE_VERSION = 1 as const;\n\n/** Default key version on freshly-created networks. */\nexport const DEFAULT_KEY_VERSION = 1 as const;\n\n/** Decrypt-invocation fact type. */\nexport const DECRYPT_FACT_TYPE = \"tinycloud.encryption.decrypt/v1\" as const;\n\n/** Decrypt response type. */\nexport const DECRYPT_RESULT_TYPE =\n \"tinycloud.encryption.decrypt-result/v1\" as const;\n\n/** Encryption service identifier (manifest long form). */\nexport const ENCRYPTION_SERVICE = \"tinycloud.encryption\" as const;\n\n/** Short form used in recap/abilities maps. */\nexport const ENCRYPTION_SERVICE_SHORT = \"encryption\" as const;\n\n/** Decrypt ability URN. */\nexport const DECRYPT_ACTION = \"tinycloud.encryption/decrypt\" as const;\n\n/**\n * Inline encrypted envelope persisted in KV/SQL records.\n *\n * - `encryptedSymmetricKey` is opaque to the SDK; only the network can\n * unwrap it.\n * - `encryptedSymmetricKeyHash` is the canonical hash of the wrapped key\n * bytes (hex-encoded sha-256 of the base64 string's canonical JSON\n * form). The node recomputes this on every decrypt request.\n * - `ciphertext` and `aad` are payload bytes only; the node never sees\n * them.\n */\nexport interface InlineEncryptedEnvelope {\n /** Schema version. */\n v: typeof ENVELOPE_VERSION;\n /** Network id URN. */\n networkId: string;\n /** Ciphersuite identifier. */\n alg: string;\n /** Network key version that was used to wrap the symmetric key. */\n keyVersion: number;\n /** Base64-encoded wrapped symmetric key / capsule. */\n encryptedSymmetricKey: string;\n /** Hex sha-256 of the canonical encryptedSymmetricKey string. */\n encryptedSymmetricKeyHash: string;\n /** Base64-encoded payload ciphertext. */\n ciphertext: string;\n /** Base64-encoded associated data, if any. */\n aad?: string;\n /** Caller-supplied metadata. Not authenticated against the node. */\n metadata?: Record<string, string>;\n}\n\n/**\n * Node-published network descriptor. The node DB is authoritative; a\n * cached copy may also live under\n * `.well-known/encryption/network/<name>` in the owner's account\n * space (a discovery record only).\n */\nexport interface NetworkDescriptor {\n networkId: string;\n ownerDid: string;\n name: string;\n members: ReadonlyArray<{ nodeId: string; role: \"primary\" | \"share\" }>;\n threshold: { n: number; t: number };\n state: \"pending\" | \"generating\" | \"active\" | \"rotating\" | \"revoked\" | \"failed\";\n /** Base64-encoded network public key. */\n publicEncryptionKey: string;\n alg: string;\n keyVersion: number;\n keyBackend: \"local-one-of-one\" | \"dstack\" | \"threshold\";\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Decrypt-request body sent over the wire. Hashed (canonically) and\n * bound to the UCAN invocation via `facts.bodyHash`.\n */\nexport interface DecryptRequestBody {\n type: typeof DECRYPT_FACT_TYPE;\n targetNode: string;\n networkId: string;\n alg: string;\n keyVersion: number;\n /** Base64-encoded wrapped symmetric key from the envelope. */\n encryptedSymmetricKey: string;\n /** Recomputed hash of the wrapped key. */\n encryptedSymmetricKeyHash: string;\n /** Base64-encoded per-request receiver public key. */\n receiverPublicKey: string;\n /** Hash of the receiver public key. */\n receiverPublicKeyHash: string;\n}\n\n/**\n * Decrypt-response body returned by the node. The SDK verifies the\n * signature, recomputes hashes, then unwraps `wrappedKey` with the\n * per-request receiver private key before decrypting the payload.\n */\nexport interface DecryptResponseBody {\n type: typeof DECRYPT_RESULT_TYPE;\n targetNode: string;\n networkId: string;\n invocationCid: string;\n encryptedSymmetricKeyHash: string;\n receiverPublicKeyHash: string;\n /** Base64-encoded symmetric key re-encrypted to receiverPublicKey. */\n wrappedKey: string;\n alg: string;\n keyVersion: number;\n requestHash: string;\n nodeId: string;\n /** Base64-encoded ed25519 signature over canonical(response - signature field). */\n nodeSignature: string;\n}\n\n/**\n * Decrypt-invocation facts attached to the UCAN. Verifiers recompute\n * `bodyHash`, `encryptedSymmetricKeyHash`, and `receiverPublicKeyHash`\n * from the request body and reject any mismatch.\n */\nexport interface DecryptInvocationFact {\n type: typeof DECRYPT_FACT_TYPE;\n targetNode: string;\n networkId: string;\n bodyHash: string;\n encryptedSymmetricKeyHash: string;\n receiverPublicKeyHash: string;\n alg: string;\n keyVersion: number;\n}\n\n/**\n * Per-request receiver key pair (x25519). The private key never\n * leaves the SDK; the public key is sent to the node so the node can\n * rewrap the symmetric key.\n */\nexport interface ReceiverKeyPair {\n publicKey: Uint8Array;\n privateKey: Uint8Array;\n}\n\n/**\n * Crypto primitives injected into the encryption module. The SDK\n * provides these via WASM bindings; tests provide simple in-memory\n * implementations.\n */\nexport interface EncryptionCrypto {\n /** SHA-256 → 32-byte digest. */\n sha256(data: Uint8Array): Uint8Array;\n /** Cryptographically secure random bytes. */\n randomBytes(length: number): Uint8Array;\n /** Derive an x25519 key pair from a 32-byte seed. */\n x25519FromSeed(seed: Uint8Array): ReceiverKeyPair;\n /** Compute the x25519 ECDH shared secret. */\n x25519Dh(privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array;\n /** Authenticated symmetric encryption (the node's symmetric scheme). */\n authEncrypt(\n key: Uint8Array,\n plaintext: Uint8Array,\n aad?: Uint8Array,\n ): Uint8Array;\n /** Authenticated symmetric decryption (matched to authEncrypt). */\n authDecrypt(\n key: Uint8Array,\n ciphertext: Uint8Array,\n aad?: Uint8Array,\n ): Uint8Array;\n /**\n * Wrap a symmetric key for the network's public encryption key using\n * a sealed-box / hpke / x25519+symmetric construction. Implementation\n * is opaque; only the node can unwrap.\n */\n sealToNetworkKey(\n networkPublicKey: Uint8Array,\n symmetricKey: Uint8Array,\n ): Uint8Array;\n /**\n * Open a wrapped symmetric key that was re-encrypted to the\n * per-request receiver public key. The matching private key must be\n * supplied here; the SDK never sends it to the node.\n */\n openWithReceiverKey(\n receiverPrivateKey: Uint8Array,\n wrappedKey: Uint8Array,\n ): Uint8Array;\n /**\n * Verify an ed25519 signature over `message` produced by the node\n * identified by `nodeId` (the public-key DID).\n */\n verifyNodeSignature(\n nodeId: string,\n message: Uint8Array,\n signature: Uint8Array,\n ): boolean;\n}\n\n/**\n * Signer interface used to derive a receiver key pair from a wallet or\n * session signer. The signature is HKDF-extracted into the receiver\n * seed so the public key is reproducible given the same context.\n */\nexport interface ReceiverKeySigner {\n signMessage(message: string): Promise<string>;\n}\n\n/** Capability proof material accompanying a decrypt invocation. */\nexport interface DecryptCapabilityProof {\n /** Delegation chain CIDs rooted at the network owner DID. */\n proofs: ReadonlyArray<string>;\n /** Optional Authorization header value to use instead of building one. */\n authorization?: string;\n}\n\n/**\n * Inputs to the decrypt invocation builder.\n */\nexport interface BuildDecryptInvocationInput {\n /** Target node DID — also the UCAN audience. */\n targetNode: string;\n /** Network id URN — also the recap resource. */\n networkId: string;\n /** Canonical body that will be POSTed. */\n body: DecryptRequestBody;\n /** Facts include hashes bound to the canonical body. */\n facts: DecryptInvocationFact;\n /** Capability proof chain. */\n proof: DecryptCapabilityProof;\n /** Optional `nbf` UCAN field as an ISO date string. */\n notBefore?: string;\n /** Optional `exp` UCAN field as an ISO date string. */\n expiration?: string;\n}\n\n/**\n * The output of {@link buildDecryptInvocation}.\n */\nexport interface BuiltDecryptInvocation {\n /** HTTP `Authorization` header value. */\n authorization: string;\n /** CID of the invocation (used by the node response binding). */\n invocationCid: string;\n /** Canonical body string the node will hash. */\n canonicalBody: string;\n}\n\n/**\n * Signer interface for producing the decrypt invocation. WASM bindings\n * implement this with the same session signer used for KV/SQL\n * invocations; tests can stub it.\n */\nexport interface DecryptInvocationSigner {\n signDecryptInvocation(input: BuildDecryptInvocationInput): Promise<BuiltDecryptInvocation>;\n}\n\n/**\n * Errors thrown / returned from the encryption module.\n */\nexport type EncryptionErrorInput =\n | { code: \"NETWORK_NOT_FOUND\"; networkId?: string; name?: string; message?: string }\n | { code: \"NETWORK_NOT_ACTIVE\"; state: string; message?: string }\n | { code: \"INVALID_NETWORK_ID\"; message: string }\n | { code: \"INVALID_ENVELOPE\"; message: string }\n | { code: \"DECRYPT_DENIED\"; message: string; permissionHint?: PermissionHint }\n | { code: \"INVALID_RESPONSE\"; message: string }\n | { code: \"RESPONSE_SIGNATURE_INVALID\"; message?: string }\n | { code: \"RESPONSE_BINDING_MISMATCH\"; field: string; message?: string }\n | { code: \"TRANSPORT_ERROR\"; cause: Error; message?: string }\n | { code: \"INVALID_INPUT\"; message: string };\n\nexport type EncryptionError = EncryptionErrorInput & {\n service: \"encryption\";\n message: string;\n};\n\nfunction defaultEncryptionMessage(input: EncryptionErrorInput): string {\n switch (input.code) {\n case \"NETWORK_NOT_FOUND\":\n return (\n input.message ??\n `Network not found: ${input.networkId ?? input.name ?? \"<unknown>\"}`\n );\n case \"NETWORK_NOT_ACTIVE\":\n return input.message ?? `Network not active (state=${input.state})`;\n case \"INVALID_NETWORK_ID\":\n return input.message;\n case \"INVALID_ENVELOPE\":\n return input.message;\n case \"DECRYPT_DENIED\":\n return input.message;\n case \"INVALID_RESPONSE\":\n return input.message;\n case \"RESPONSE_SIGNATURE_INVALID\":\n return input.message ?? \"Node response signature failed to verify\";\n case \"RESPONSE_BINDING_MISMATCH\":\n return (\n input.message ??\n `Node response binding mismatch on field ${JSON.stringify(input.field)}`\n );\n case \"TRANSPORT_ERROR\":\n return input.message ?? input.cause.message;\n case \"INVALID_INPUT\":\n return input.message;\n }\n}\n\nexport function encryptionError(input: EncryptionErrorInput): EncryptionError {\n return {\n ...input,\n service: \"encryption\",\n message: defaultEncryptionMessage(input),\n };\n}\n\n/** Helper for the test/runtime layers to coerce arbitrary throwables. */\nexport function toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (typeof error === \"object\" && error !== null) {\n return new Error(JSON.stringify(error));\n }\n return new Error(String(error));\n}\n\n/** Re-export for ergonomic typing of canonical payloads. */\nexport type CanonicalJson = Json;\n","/**\n * Decrypt-response verification.\n *\n * The node returns the unwrapped symmetric key re-encrypted to the\n * per-request receiver public key. Before the SDK uses the wrapped\n * key, it must:\n *\n * 1. Verify the node signature over the canonical response (excluding\n * the signature field).\n * 2. Recompute every binding hash and reject any mismatch.\n * 3. Confirm `targetNode`, `networkId`, `alg`, and `keyVersion` echo\n * what the SDK sent.\n *\n * After verification, the SDK opens `wrappedKey` with the per-request\n * receiver private key and uses the resulting symmetric key to\n * decrypt the inline envelope payload.\n */\n\nimport { base64Decode, canonicalize, hexEncode, utf8Encode } from \"./canonical\";\nimport {\n DECRYPT_RESULT_TYPE,\n encryptionError,\n type CanonicalJson,\n type DecryptInvocationFact,\n type DecryptRequestBody,\n type DecryptResponseBody,\n type EncryptionCrypto,\n type EncryptionError,\n} from \"./types\";\n\n/**\n * Construct the canonical bytes that the node signed.\n *\n * The signature covers the response with `nodeSignature` removed. We\n * canonicalize and hash the same way the node does so the binding is\n * stable.\n */\nexport function canonicalSignedResponse(\n response: DecryptResponseBody,\n): string {\n // Strip the signature; canonicalize sorts keys for us.\n const { nodeSignature: _drop, ...rest } = response;\n return canonicalize(rest as unknown as CanonicalJson);\n}\n\nexport interface VerifyDecryptResponseInput {\n crypto: EncryptionCrypto;\n request: DecryptRequestBody;\n facts: DecryptInvocationFact;\n /** CID of the signed invocation that produced this response. */\n invocationCid: string;\n /** Hex bodyHash that was bound in `facts.bodyHash`. */\n requestBodyHash: string;\n response: DecryptResponseBody;\n}\n\n/**\n * Verify the node's decrypt response. Returns the response on success;\n * a structured error on signature, binding, or shape failure.\n */\nexport function verifyDecryptResponse(\n input: VerifyDecryptResponseInput,\n):\n | { ok: true; data: DecryptResponseBody }\n | { ok: false; error: EncryptionError } {\n const { crypto, request, facts, invocationCid, requestBodyHash, response } =\n input;\n\n if (response.type !== DECRYPT_RESULT_TYPE) {\n return {\n ok: false,\n error: encryptionError({\n code: \"INVALID_RESPONSE\",\n message: `response.type must be ${DECRYPT_RESULT_TYPE}`,\n }),\n };\n }\n if (response.targetNode !== request.targetNode) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"targetNode\",\n }),\n };\n }\n if (response.networkId !== request.networkId) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"networkId\",\n }),\n };\n }\n if (response.nodeId !== request.targetNode) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"nodeId\",\n }),\n };\n }\n if (response.alg !== request.alg) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"alg\",\n }),\n };\n }\n if (response.keyVersion !== request.keyVersion) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"keyVersion\",\n }),\n };\n }\n if (\n response.encryptedSymmetricKeyHash !==\n request.encryptedSymmetricKeyHash\n ) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"encryptedSymmetricKeyHash\",\n }),\n };\n }\n if (response.receiverPublicKeyHash !== request.receiverPublicKeyHash) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"receiverPublicKeyHash\",\n }),\n };\n }\n if (response.invocationCid !== invocationCid) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"invocationCid\",\n }),\n };\n }\n // requestHash binds invocationCid || requestBodyHash per spec. The `||`\n // notation is concatenation, not a literal delimiter.\n const expectedRequestHash = hexEncode(\n crypto.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`)),\n );\n if (response.requestHash !== expectedRequestHash) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"requestHash\",\n }),\n };\n }\n // The facts must agree with the response (defensive: catches a node\n // that returns a fresh body bound to a different invocation).\n if (\n facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash ||\n facts.receiverPublicKeyHash !== response.receiverPublicKeyHash ||\n facts.networkId !== response.networkId ||\n facts.targetNode !== response.targetNode ||\n facts.alg !== response.alg ||\n facts.keyVersion !== response.keyVersion\n ) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_BINDING_MISMATCH\",\n field: \"facts\",\n }),\n };\n }\n\n const signedBytes = new TextEncoder().encode(\n canonicalSignedResponse(response),\n );\n const signatureBytes = base64Decode(response.nodeSignature);\n if (\n !crypto.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)\n ) {\n return {\n ok: false,\n error: encryptionError({\n code: \"RESPONSE_SIGNATURE_INVALID\",\n }),\n };\n }\n return { ok: true, data: response };\n}\n\n/**\n * After verification, open the response's `wrappedKey` with the\n * per-request receiver private key and return the symmetric key.\n */\nexport function openWrappedKey(\n crypto: EncryptionCrypto,\n receiverPrivateKey: Uint8Array,\n response: DecryptResponseBody,\n): Uint8Array {\n const wrapped = base64Decode(response.wrappedKey);\n return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);\n}\n","/**\n * Typed encryption resources: bare networks, named keys and key prefixes.\n *\n * network: urn:tinycloud:encryption:<ownerDid>:<network>\n * key: urn:tinycloud:encryption:<ownerDid>:<network>/<path>\n * prefix: urn:tinycloud:encryption:<ownerDid>:<network>/<prefix>/\n *\n * Grammar rules:\n * - The owner DID contains no `/`. The string is split at the FIRST `/` into\n * `<ownerDid>:<network>` and the path; that head splits at its last `:`.\n * - A key path has 1 to 8 segments joined by `/`; each segment is 1 to 64 bytes\n * of `[a-z0-9._-]` starting with `[a-z0-9]` (so `.` and `..` are impossible);\n * the whole path is at most 255 bytes.\n * - A prefix is the key form with exactly ONE trailing `/`. The trailing `/` is\n * significant: `apps/foo/` and `apps/foo` are different resources and\n * `apps/foo/` does not cover `apps/foobar`.\n * - Under the reserved first segment `apps`, a key needs at least 3 segments\n * (`apps/<id>/<name>`) and a prefix at least 2 (`apps/<id>/`); `apps`,\n * `apps/<id>` as a key and `apps/` as a prefix are refused.\n * - The canonical form is the exact input. Non-canonical input is rejected and\n * never normalized: wrong case, bad characters, empty segments, a\n * non-canonical owner (EIP-55 for `did:pkh:eip155`).\n * - Bare networks follow `parseNetworkId` unchanged. When PARSING a key or\n * prefix the network part must match `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`; builders\n * take a network id that `parseNetworkId` produced, so emitted names stay strict.\n */\n\nimport { pkhDid } from \"@tinycloud/bootstrap\";\nimport {\n ENCRYPTION_NETWORK_URN_PREFIX,\n NetworkIdError,\n parseNetworkId,\n} from \"./networkId\";\nimport { DECRYPT_ACTION } from \"./types\";\n\n/** Path grammar. This is the only place the key-path charset/limits live. */\nconst SEGMENT_SOURCE = \"[a-z0-9][a-z0-9._-]{0,63}\";\nconst PATH_SOURCE = `${SEGMENT_SOURCE}(?:/${SEGMENT_SOURCE}){0,7}`;\nconst KEY_PATH_RE = new RegExp(`^${PATH_SOURCE}$`);\n/**\n * Network part of a KEY or PREFIX resource: printable ASCII, first character\n * alphanumeric, at most 64 characters. Bare networks are not checked against\n * this; they go through `parseNetworkId` unchanged.\n */\nconst KEY_NETWORK_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n/** `<ownerDid>:<network>` head, then the raw path (prefix keeps its trailing `/`). */\nconst RESOURCE_RE = /^(did:[a-z0-9]+:[A-Za-z0-9._:-]+):([^:/]+)(?:\\/(.*))?$/s;\nconst MAX_PATH_BYTES = 255;\nconst PKH_EIP155_PREFIX = \"did:pkh:eip155:\";\nconst MAX_U64 = 18446744073709551615n;\n\n/** Ability URNs for named encryption keys. */\nexport const KEY_CREATE_ABILITY = \"tinycloud.encryption/key.create\";\nexport const KEY_BLOCK_ABILITY = \"tinycloud.encryption/key.block\";\nexport const KEY_UNBLOCK_ABILITY = \"tinycloud.encryption/key.unblock\";\nexport const KEY_DELETE_ABILITY = \"tinycloud.encryption/key.delete\";\n\n/** Key abilities that may appear in a delegation: decrypt, create, block, unblock. `key.delete` never does. */\nexport const DELEGABLE_KEY_ABILITIES: readonly string[] = Object.freeze([\n DECRYPT_ACTION,\n KEY_CREATE_ABILITY,\n KEY_BLOCK_ABILITY,\n KEY_UNBLOCK_ABILITY,\n]);\n\n/** Node `/info` feature string advertising named-key support. */\nexport const NAMED_KEYS_FEATURE = \"encryption-named-keys-v1\";\n\nexport type EncryptionResourceErrorCode =\n | \"not_encryption_urn\"\n | \"malformed\"\n | \"bad_owner\"\n | \"non_canonical_owner\"\n | \"bad_network\"\n | \"empty_path\"\n | \"bad_path\"\n | \"path_too_long\";\n\nexport class EncryptionResourceError extends Error {\n readonly code: EncryptionResourceErrorCode;\n constructor(code: EncryptionResourceErrorCode, message: string) {\n super(message);\n this.name = \"EncryptionResourceError\";\n this.code = code;\n }\n}\n\nexport interface ParsedNetworkResource {\n kind: \"network\";\n ownerDid: string;\n network: string;\n networkId: string;\n}\n\nexport interface ParsedKeyResource {\n kind: \"key\";\n ownerDid: string;\n network: string;\n networkId: string;\n path: string;\n}\n\nexport interface ParsedPrefixResource {\n kind: \"prefix\";\n ownerDid: string;\n network: string;\n networkId: string;\n /** Path without the trailing `/`. */\n prefix: string;\n}\n\nexport type ParsedEncryptionResource =\n | ParsedNetworkResource\n | ParsedKeyResource\n | ParsedPrefixResource;\n\n/**\n * Canonical form of an owner DID, matching the node: `did:pkh:eip155:<chain>:<address>`\n * needs a positive u64 decimal chain id and a 20-byte hex address (rendered EIP-55);\n * every other DID that passed the owner grammar is returned as is.\n */\nfunction canonicalOwner(ownerDid: string): string {\n if (!ownerDid.startsWith(PKH_EIP155_PREFIX)) return ownerDid;\n const rest = ownerDid.slice(PKH_EIP155_PREFIX.length);\n const colon = rest.indexOf(\":\");\n const chain = colon === -1 ? \"\" : rest.slice(0, colon);\n const address = colon === -1 ? \"\" : rest.slice(colon + 1);\n if (!/^\\d+$/.test(chain) || !/^0x[a-fA-F0-9]{40}$/.test(address)) {\n throw new EncryptionResourceError(\n \"bad_owner\",\n `owner DID is not a valid did:pkh:eip155 (got ${JSON.stringify(ownerDid)})`,\n );\n }\n const chainId = BigInt(chain);\n if (chainId === 0n || chainId > MAX_U64) {\n throw new EncryptionResourceError(\n \"bad_owner\",\n `did:pkh:eip155 chain id must be a positive u64 (got ${JSON.stringify(chain)})`,\n );\n }\n // Checksum through chain 1 so the chain id is never narrowed to a Number.\n const checksummed = pkhDid(address, 1).slice(PKH_EIP155_PREFIX.length + 2);\n return `${PKH_EIP155_PREFIX}${chainId}:${checksummed}`;\n}\n\nfunction assertCanonicalOwner(ownerDid: string): void {\n const canonical = canonicalOwner(ownerDid);\n if (canonical !== ownerDid) {\n throw new EncryptionResourceError(\n \"non_canonical_owner\",\n `owner DID is not canonical (expected ${canonical})`,\n );\n }\n}\n\nfunction assertPath(path: string, what: \"key path\" | \"prefix\"): void {\n if (path.length === 0) {\n throw new EncryptionResourceError(\"empty_path\", `${what} must not be empty`);\n }\n if (new TextEncoder().encode(path).length > MAX_PATH_BYTES) {\n throw new EncryptionResourceError(\n \"path_too_long\",\n `${what} exceeds ${MAX_PATH_BYTES} bytes`,\n );\n }\n if (!KEY_PATH_RE.test(path)) {\n throw new EncryptionResourceError(\n \"bad_path\",\n `${what} ${JSON.stringify(path)} must match ${KEY_PATH_RE.source}`,\n );\n }\n // Depth rule for the reserved `apps` first segment: a key is\n // `apps/<id>/<name>...` (3 or more segments), a prefix is `apps/<id>/...`\n // (2 or more). `apps`, `apps/<id>` as a key and `apps/` as a prefix are refused.\n const segments = path.split(\"/\");\n if (segments[0] === \"apps\" && segments.length < (what === \"key path\" ? 3 : 2)) {\n throw new EncryptionResourceError(\n \"bad_path\",\n `${what} ${JSON.stringify(path)} under apps/ needs ${what === \"key path\" ? \"apps/<id>/<name>\" : \"apps/<id>/\"}`,\n );\n }\n}\n\n/**\n * Parse an encryption resource URN into a bare network, a named key or a key\n * prefix. Throws {@link EncryptionResourceError} on anything that is not\n * already in canonical form.\n */\nexport function parseEncryptionResource(uri: string): ParsedEncryptionResource {\n if (typeof uri !== \"string\" || !uri.startsWith(ENCRYPTION_NETWORK_URN_PREFIX)) {\n throw new EncryptionResourceError(\n \"not_encryption_urn\",\n `resource must start with ${ENCRYPTION_NETWORK_URN_PREFIX}`,\n );\n }\n const body = uri.slice(ENCRYPTION_NETWORK_URN_PREFIX.length);\n const match = RESOURCE_RE.exec(body);\n if (match === null) {\n throw new EncryptionResourceError(\n \"malformed\",\n `resource is not <ownerDid>:<network>[/<path>] (got ${JSON.stringify(uri)})`,\n );\n }\n const ownerDid = match[1]!;\n const network = match[2]!;\n const rawPath = match[3];\n if (rawPath === undefined) {\n // Bare networks are normalized (the node does the same); key ids never are.\n const canonicalOwnerDid = canonicalOwner(ownerDid);\n const bareId = `${ENCRYPTION_NETWORK_URN_PREFIX}${canonicalOwnerDid}:${network}`;\n try {\n parseNetworkId(bareId);\n } catch (error) {\n if (error instanceof NetworkIdError) {\n throw new EncryptionResourceError(\"bad_network\", error.message);\n }\n throw error;\n }\n return { kind: \"network\", ownerDid: canonicalOwnerDid, network, networkId: bareId };\n }\n if (!KEY_NETWORK_RE.test(network)) {\n throw new EncryptionResourceError(\n \"bad_network\",\n `network ${JSON.stringify(network)} must match ${KEY_NETWORK_RE.source}`,\n );\n }\n assertCanonicalOwner(ownerDid);\n const networkId = `${ENCRYPTION_NETWORK_URN_PREFIX}${ownerDid}:${network}`;\n\n if (rawPath.endsWith(\"/\")) {\n const prefix = rawPath.slice(0, -1);\n assertPath(prefix, \"prefix\");\n return { kind: \"prefix\", ownerDid, network, networkId, prefix };\n }\n assertPath(rawPath, \"key path\");\n return { kind: \"key\", ownerDid, network, networkId, path: rawPath };\n}\n\nfunction canonicalNetworkId(networkId: string): string {\n let parsed;\n try {\n parsed = parseNetworkId(networkId);\n } catch (error) {\n if (error instanceof NetworkIdError) {\n throw new EncryptionResourceError(\"bad_network\", error.message);\n }\n throw error;\n }\n assertCanonicalOwner(parsed.ownerDid);\n return parsed.networkId;\n}\n\n/**\n * Builders re-parse their output so they can never emit a URN the parser\n * would reject (for example an owner outside the owner grammar).\n */\nfunction parseBuilt(resource: string, networkId: string): void {\n if (parseEncryptionResource(resource).networkId !== networkId) {\n throw new EncryptionResourceError(\n \"malformed\",\n `built resource does not round-trip (got ${JSON.stringify(resource)})`,\n );\n }\n}\n\n/** Build the exact canonical URN of a named key. */\nexport function formatKeyResource(networkId: string, path: string): string {\n const id = canonicalNetworkId(networkId);\n assertPath(path, \"key path\");\n const resource = `${id}/${path}`;\n parseBuilt(resource, id);\n return resource;\n}\n\n/** Build the exact canonical URN of a key prefix grant (one trailing `/`). */\nexport function formatPrefixResource(networkId: string, prefix: string): string {\n const id = canonicalNetworkId(networkId);\n assertPath(prefix, \"prefix\");\n const resource = `${id}/${prefix}/`;\n parseBuilt(resource, id);\n return resource;\n}\n\n/**\n * Does the `granted` resource cover the `requested` resource?\n * exact == exact; a prefix covers keys whose path starts with `<prefix>/` and\n * prefixes whose `/`-terminated form starts with the granted one; a bare\n * network covers only the same bare network. Every other pair is false, and\n * so is anything that does not parse.\n */\nexport function encryptionResourceContains(\n granted: string,\n requested: string,\n): boolean {\n let g: ParsedEncryptionResource;\n let r: ParsedEncryptionResource;\n try {\n g = parseEncryptionResource(granted);\n r = parseEncryptionResource(requested);\n } catch {\n return false;\n }\n if (g.networkId !== r.networkId) return false;\n switch (g.kind) {\n case \"network\":\n return r.kind === \"network\";\n case \"key\":\n return r.kind === \"key\" && r.path === g.path;\n case \"prefix\":\n if (r.kind === \"key\") return r.path.startsWith(`${g.prefix}/`);\n if (r.kind === \"prefix\") return `${r.prefix}/`.startsWith(`${g.prefix}/`);\n return false;\n }\n}\n\n/**\n * True only when the node `/info` response lists exactly\n * `\"encryption-named-keys-v1\"` in `features`.\n */\nexport function nodeSupportsNamedKeys(\n info: { features?: unknown } | null | undefined,\n): boolean {\n const features = info?.features;\n return Array.isArray(features) && features.includes(NAMED_KEYS_FEATURE);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,220 @@
1
+ import { E as EncryptionCrypto, I as InlineEncryptedEnvelope, J as Json } from '../types-B_rbS4XE.cjs';
2
+ import { ParsedKeyResource } from './resource.cjs';
3
+ import '../types-DAOyIbXu.cjs';
4
+
5
+ /**
6
+ * Named-key client: key admin, descriptor reads, `ensureKey` and key-scoped
7
+ * encrypt / decrypt.
8
+ *
9
+ * Rules enforced here:
10
+ * - Everything is dark unless the node's `/info` `features` contains
11
+ * `encryption-named-keys-v1`. Without it no key ability, key URN or `keyPath`
12
+ * is sent to the node; the call throws `NAMED_KEYS_UNSUPPORTED` first.
13
+ * - Every invocation carries exactly ONE capability whose `with` is the EXACT
14
+ * key URN (a parsed `kind: "key"`; a prefix or bare network is never a
15
+ * target) and whose `can` is the one ability the call needs.
16
+ * - Admin bodies are hashed and serialized with the same canonical JSON used
17
+ * by the network-admin invocation (`canonicalHashHex` / `canonicalize`).
18
+ * - Envelope v2 carries `keyPath` and is wrapped to the KEY's public key. It is
19
+ * decrypted only through `POST /invoke` with a single decrypt capability on
20
+ * that key. v1 envelopes (no `keyPath`) go to the root-key path unchanged.
21
+ * A key and the root, or two keys, are never mixed in one invocation.
22
+ * - `key.delete` is not offered. The create body carries `deletable`, which is
23
+ * `true` exactly when the path starts with `apps/`; the node refuses it with
24
+ * `deletable_unavailable` while app-key deletion is not enabled there.
25
+ */
26
+
27
+ declare const KEY_ADMIN_FACT_TYPE: "tinycloud.encryption.key-admin/v1";
28
+ declare const KEY_DESCRIBE_FACT_TYPE: "tinycloud.encryption.key-describe/v1";
29
+ declare const NAMED_KEY_ENVELOPE_VERSION: 2;
30
+ type NamedKeyErrorCode = "NAMED_KEYS_UNSUPPORTED" | "INVALID_KEY_TARGET" | "INVALID_ENVELOPE" | "INVALID_RESPONSE" | "KEY_BLOCKED" | "KEY_NOT_FOUND" | "KEY_EXISTS" | "NETWORK_NOT_FOUND" | "NETWORK_NOT_ACTIVE" | "KEY_LIMIT_EXCEEDED" | "DELETABLE_UNAVAILABLE"
31
+ /** The key is not deletable (a deterministic key answers 409 `key_not_deletable`). */
32
+ | "KEY_NOT_DELETABLE" | "KEY_PATH_INVALID" | "KEY_UNWRAP_FAILED"
33
+ /** Key is in a state other than active/blocked; `rawState` holds the node's string. */
34
+ | "KEY_UNAVAILABLE"
35
+ /** `ensureKey` found a key that is not deterministic. */
36
+ | "KEY_KIND_MISMATCH" | "DECRYPT_FAILED" | "NODE_ERROR"
37
+ /** The node rejected `receiverPublicKey` (400, invocation not consumed). */
38
+ | "RECEIVER_KEY_INVALID"
39
+ /** The node object lacks an invocation hook the node-sdk wiring needs. */
40
+ | "NODE_HOOK_MISSING";
41
+ /**
42
+ * Render an untrusted value (resource, path, node-supplied state, parser or
43
+ * signer text) for an error message: capped at 120 characters, then every
44
+ * character outside printable ASCII escaped, so bidi, zero-width and other
45
+ * format or control characters can never reach a terminal or log through a
46
+ * message. Raw values live only in non-message fields (`input`, `rawState`,
47
+ * `kind`, `detail`, `cause`).
48
+ */
49
+ declare function safeEcho(value: unknown): string;
50
+ interface NamedKeyErrorInit {
51
+ status?: number;
52
+ /** The node's `<code>` prefix, when it sent one. */
53
+ nodeCode?: string;
54
+ /** The node's `<detail>` text. Raw: never in `message`. */
55
+ detail?: string;
56
+ /** Raw state string (KEY_UNAVAILABLE). Raw: never in `message`. */
57
+ rawState?: string;
58
+ kind?: string;
59
+ /** The rejected caller input (resource or path), unescaped. Raw: never in `message`. */
60
+ input?: string;
61
+ cause?: unknown;
62
+ }
63
+ declare class NamedKeyError extends Error {
64
+ readonly code: NamedKeyErrorCode;
65
+ readonly status?: number;
66
+ readonly nodeCode?: string;
67
+ readonly detail?: string;
68
+ readonly rawState?: string;
69
+ readonly kind?: string;
70
+ readonly input?: string;
71
+ constructor(code: NamedKeyErrorCode, message: string, init?: NamedKeyErrorInit);
72
+ }
73
+ /** Map a node plain-text `<code>: <detail>` error response to a typed error. */
74
+ declare function mapNodeKeyError(status: number, text: string, context: string): NamedKeyError;
75
+ interface PublicKeyDescriptor {
76
+ networkId: string;
77
+ keyPath: string;
78
+ alg: string;
79
+ keyVersion: number;
80
+ publicEncryptionKey: string;
81
+ }
82
+ interface KeyDescriptor extends PublicKeyDescriptor {
83
+ /** Raw node state string (`active`, `blocked`, ...). */
84
+ state: string;
85
+ /** `deterministic` outside `apps/`, `deletable` under it; kept as a string so unknown kinds surface. */
86
+ kind: string;
87
+ createdAt?: string;
88
+ updatedAt?: string;
89
+ }
90
+ /** v2 inline envelope: v1 fields plus `keyPath`, wrapped to the key's public key. */
91
+ interface NamedKeyEnvelope extends Omit<InlineEncryptedEnvelope, "v"> {
92
+ v: typeof NAMED_KEY_ENVELOPE_VERSION;
93
+ keyPath: string;
94
+ }
95
+ interface NamedKeyInvocationInput {
96
+ targetNode: string;
97
+ /** The EXACT key URN. This is the single capability's `with`. */
98
+ resource: string;
99
+ /** The single capability's `can`. */
100
+ action: string;
101
+ facts: Record<string, unknown>;
102
+ }
103
+ interface NamedKeyClientConfig {
104
+ host: string;
105
+ crypto: EncryptionCrypto;
106
+ fetch: typeof fetch;
107
+ /** Network used when a call does not name one. */
108
+ defaultNetworkId: () => string;
109
+ /** Mint one single-capability invocation and return the Authorization header value. */
110
+ signInvocation: (input: NamedKeyInvocationInput) => Promise<{
111
+ authorization: string;
112
+ invocationCid: string;
113
+ }>;
114
+ /**
115
+ * Decrypt a v1 (root key, bare network capability) envelope exactly as the
116
+ * existing network decrypt path does.
117
+ */
118
+ decryptRoot?: (envelope: InlineEncryptedEnvelope) => Promise<{
119
+ ok: true;
120
+ data: Uint8Array;
121
+ } | {
122
+ ok: false;
123
+ error: {
124
+ message: string;
125
+ };
126
+ }>;
127
+ }
128
+ interface KeyCallOptions {
129
+ /** Full network id URN. Defaults to the client's default network. */
130
+ networkId?: string;
131
+ }
132
+ interface KeyEncryptOptions extends KeyCallOptions {
133
+ aad?: Uint8Array;
134
+ metadata?: Record<string, string>;
135
+ }
136
+ type DecryptManyResult = {
137
+ ok: true;
138
+ data: Uint8Array;
139
+ } | {
140
+ ok: false;
141
+ error: NamedKeyError;
142
+ };
143
+ /** Accept only a parsed `kind: "key"`; a prefix or bare network is never a target. */
144
+ declare function requireKeyTarget(resource: string): ParsedKeyResource;
145
+ /** `{type, targetNode, networkId, keyPath, action, bodyHash}` for key admin routes. */
146
+ declare function buildKeyAdminFacts(input: {
147
+ crypto: EncryptionCrypto;
148
+ targetNode: string;
149
+ keyResource: string;
150
+ action: string;
151
+ body: Json;
152
+ }): Record<string, unknown>;
153
+ /** `{type, targetNode, networkId, keyPath}`; no body hash. */
154
+ declare function buildKeyDescribeFacts(input: {
155
+ targetNode: string;
156
+ keyResource: string;
157
+ }): Record<string, unknown>;
158
+ declare class NamedKeyClient {
159
+ private readonly config;
160
+ /** One `/info` read per client: the node DID once the feature is confirmed. */
161
+ private nodeId?;
162
+ constructor(config: NamedKeyClientConfig);
163
+ private networkFor;
164
+ private keyUrn;
165
+ /**
166
+ * Require the named-keys feature and return the node DID. The first call
167
+ * reads `/info`; a positive answer is cached for the life of this client, so
168
+ * later calls make no `/info` request. A node without the feature is NOT
169
+ * cached: the typed `NAMED_KEYS_UNSUPPORTED` error is thrown and the next
170
+ * call re-reads (so a retry sees an upgraded node). No key request is made
171
+ * before the gate passes.
172
+ */
173
+ private requireNode;
174
+ /** Clear the cached `/info` answer; the next call reads it again. */
175
+ refreshNodeInfo(): void;
176
+ private readNodeInfo;
177
+ private keysUrl;
178
+ /** Public GET: the key's public key. Derive-only (no 404) outside `apps/`; under `apps/` the node answers 404. */
179
+ getPublicKey(path: string, options?: KeyCallOptions): Promise<PublicKeyDescriptor>;
180
+ /**
181
+ * Authenticated GET: the full descriptor, or `null` on `key_not_found`.
182
+ * `ability` is the one capability the invocation carries (default decrypt).
183
+ */
184
+ describeKey(path: string, options?: KeyCallOptions & {
185
+ ability?: string;
186
+ }): Promise<KeyDescriptor | null>;
187
+ /** Public fields of an authenticated describe (decrypt ability); `key_not_found` throws. */
188
+ private describePublic;
189
+ /** The authenticated GET response body, or `null` on `key_not_found`. */
190
+ private describeBody;
191
+ private admin;
192
+ /** `key.create`: `deletable` is `true` exactly under `apps/`, else a deterministic key. */
193
+ createKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor>;
194
+ blockKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor>;
195
+ unblockKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor>;
196
+ /**
197
+ * Make sure a key exists and is usable: authenticated GET; on
198
+ * `key_not_found` create it; on 409 `key_exists` read it again. Succeeds only
199
+ * for `state: "active"` and the kind the path implies: `deletable` under
200
+ * `apps/`, `deterministic` elsewhere.
201
+ */
202
+ ensureKey(path: string, options?: KeyCallOptions): Promise<KeyDescriptor>;
203
+ /** Encrypt to a named key: envelope v2, symmetric key wrapped to the key's public key. */
204
+ encrypt(path: string, plaintext: Uint8Array, options?: KeyEncryptOptions): Promise<NamedKeyEnvelope>;
205
+ /**
206
+ * Decrypt one envelope. v1 (no `keyPath`) uses the root-key path exactly as
207
+ * before; v2 uses one decrypt capability on the exact key via `/invoke`.
208
+ */
209
+ decrypt(envelope: InlineEncryptedEnvelope | NamedKeyEnvelope): Promise<Uint8Array>;
210
+ /**
211
+ * Decrypt a batch. Each envelope is its own invocation with its own single
212
+ * capability, so v1 and v2 (or two different keys) are never combined.
213
+ */
214
+ decryptMany(envelopes: ReadonlyArray<InlineEncryptedEnvelope | NamedKeyEnvelope>): Promise<DecryptManyResult[]>;
215
+ private decryptRoot;
216
+ private validateKeyEnvelope;
217
+ private decryptKey;
218
+ }
219
+
220
+ export { type DecryptManyResult, KEY_ADMIN_FACT_TYPE, KEY_DESCRIBE_FACT_TYPE, type KeyCallOptions, type KeyDescriptor, type KeyEncryptOptions, NAMED_KEY_ENVELOPE_VERSION, NamedKeyClient, type NamedKeyClientConfig, type NamedKeyEnvelope, NamedKeyError, type NamedKeyErrorCode, type NamedKeyErrorInit, type NamedKeyInvocationInput, type PublicKeyDescriptor, buildKeyAdminFacts, buildKeyDescribeFacts, mapNodeKeyError, requireKeyTarget, safeEcho };