@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.3
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.
- package/dist/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
- package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
- package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
- package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
- package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
- package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
- package/dist/context.cjs +4 -0
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -1
- package/dist/encryption/index.cjs +60 -3
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +12 -22
- package/dist/encryption/index.d.ts +12 -22
- package/dist/encryption/index.js +60 -3
- package/dist/encryption/index.js.map +1 -1
- package/dist/encryption/named-keys.cjs +2 -0
- package/dist/encryption/named-keys.cjs.map +1 -1
- package/dist/encryption/named-keys.d.cts +2 -2
- package/dist/encryption/named-keys.d.ts +2 -2
- package/dist/encryption/named-keys.js +2 -0
- package/dist/encryption/named-keys.js.map +1 -1
- package/dist/encryption/resource.cjs.map +1 -1
- package/dist/encryption/resource.js.map +1 -1
- package/dist/feed/index.cjs +4 -0
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +3 -3
- package/dist/feed/index.d.ts +3 -3
- package/dist/feed/index.js +4 -0
- package/dist/feed/index.js.map +1 -1
- package/dist/index.cjs +260 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +253 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +34 -0
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -4
- package/dist/kv/index.d.ts +4 -4
- package/dist/kv/index.js +34 -0
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +34 -0
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +34 -0
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
- package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
- package/dist/sql/index.cjs +36 -0
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +4 -4
- package/dist/sql/index.d.ts +4 -4
- package/dist/sql/index.js +36 -0
- package/dist/sql/index.js.map +1 -1
- package/dist/telemetry/requestLog.cjs +4 -0
- package/dist/telemetry/requestLog.cjs.map +1 -1
- package/dist/telemetry/requestLog.d.cts +1 -1
- package/dist/telemetry/requestLog.d.ts +1 -1
- package/dist/telemetry/requestLog.js +4 -0
- package/dist/telemetry/requestLog.js.map +1 -1
- package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
- package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
- package/dist/{types-D4XbIfFJ.d.cts → types-XE4tyBLH.d.ts} +60 -3
- package/dist/{types-Bf91gup6.d.ts → types-cWtQmCCw.d.cts} +60 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/encryption/resource.ts","../../src/encryption/networkId.ts","../../src/encryption/types.ts"],"sourcesContent":["/**\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","/**\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\nexport interface ParsedEncryptionResource extends ParsedNetworkId {\n /** Named-key path, if the resource is scoped below a network. */\n keyPath?: string;\n}\n\n/**\n * Parse a network-root or named-key encryption resource.\n *\n * A trailing slash on keyPath denotes a prefix grant; empty and traversal\n * segments are rejected so resource matching cannot escape the named path.\n */\nexport function parseEncryptionResource(\n resource: string,\n): ParsedEncryptionResource {\n const networkSeparator = resource.lastIndexOf(\":\");\n const nameAndPath = resource.slice(networkSeparator + 1);\n const pathSeparator = nameAndPath.indexOf(\"/\");\n const name =\n pathSeparator === -1\n ? nameAndPath\n : nameAndPath.slice(0, pathSeparator);\n const networkId =\n pathSeparator === -1\n ? resource\n : `${resource.slice(0, networkSeparator + 1)}${name}`;\n const parsed = parseNetworkId(networkId);\n if (pathSeparator === -1) return parsed;\n\n const keyPath = nameAndPath.slice(pathSeparator + 1);\n const segments = keyPath.split(\"/\");\n const prefix = keyPath.endsWith(\"/\");\n if (\n keyPath.length === 0 ||\n /[\\u0000-\\u001f\\u007f\\\\]/.test(keyPath) ||\n segments.some(\n (segment, index) =>\n segment === \".\" ||\n segment === \"..\" ||\n (segment.length === 0 && !(prefix && index === segments.length - 1)),\n )\n ) {\n throw new NetworkIdError(\n `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`,\n );\n }\n return { ...parsed, keyPath };\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 * 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 v: typeof ENVELOPE_VERSION;\n /** Network id URN. */\n networkId: string;\n /** Exact named-key path; absent on network-root v1 envelopes. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/encryption/resource.ts","../../src/encryption/networkId.ts","../../src/encryption/types.ts"],"sourcesContent":["/**\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","/**\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\nexport interface ParsedEncryptionResource extends ParsedNetworkId {\n /** Named-key path, if the resource is scoped below a network. */\n keyPath?: string;\n}\n\n/**\n * Parse a network-root or named-key encryption resource.\n *\n * A trailing slash on keyPath denotes a prefix grant; empty and traversal\n * segments are rejected so resource matching cannot escape the named path.\n */\nexport function parseEncryptionResource(\n resource: string,\n): ParsedEncryptionResource {\n const networkSeparator = resource.lastIndexOf(\":\");\n const nameAndPath = resource.slice(networkSeparator + 1);\n const pathSeparator = nameAndPath.indexOf(\"/\");\n const name =\n pathSeparator === -1\n ? nameAndPath\n : nameAndPath.slice(0, pathSeparator);\n const networkId =\n pathSeparator === -1\n ? resource\n : `${resource.slice(0, networkSeparator + 1)}${name}`;\n const parsed = parseNetworkId(networkId);\n if (pathSeparator === -1) return parsed;\n\n const keyPath = nameAndPath.slice(pathSeparator + 1);\n const segments = keyPath.split(\"/\");\n const prefix = keyPath.endsWith(\"/\");\n if (\n keyPath.length === 0 ||\n /[\\u0000-\\u001f\\u007f\\\\]/.test(keyPath) ||\n segments.some(\n (segment, index) =>\n segment === \".\" ||\n segment === \"..\" ||\n (segment.length === 0 && !(prefix && index === segments.length - 1)),\n )\n ) {\n throw new NetworkIdError(\n `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`,\n );\n }\n return { ...parsed, keyPath };\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 * 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\";\nimport type { RoutingFailure } from \"../resourceRouting\";\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 v: typeof ENVELOPE_VERSION;\n /** Network id URN. */\n networkId: string;\n /** Exact named-key path; absent on network-root v1 envelopes. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 | RoutingFailure\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 default:\n return input.message ?? input.code;\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"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/encryption/resource.ts","../../src/encryption/networkId.ts","../../src/encryption/types.ts"],"sourcesContent":["/**\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","/**\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\nexport interface ParsedEncryptionResource extends ParsedNetworkId {\n /** Named-key path, if the resource is scoped below a network. */\n keyPath?: string;\n}\n\n/**\n * Parse a network-root or named-key encryption resource.\n *\n * A trailing slash on keyPath denotes a prefix grant; empty and traversal\n * segments are rejected so resource matching cannot escape the named path.\n */\nexport function parseEncryptionResource(\n resource: string,\n): ParsedEncryptionResource {\n const networkSeparator = resource.lastIndexOf(\":\");\n const nameAndPath = resource.slice(networkSeparator + 1);\n const pathSeparator = nameAndPath.indexOf(\"/\");\n const name =\n pathSeparator === -1\n ? nameAndPath\n : nameAndPath.slice(0, pathSeparator);\n const networkId =\n pathSeparator === -1\n ? resource\n : `${resource.slice(0, networkSeparator + 1)}${name}`;\n const parsed = parseNetworkId(networkId);\n if (pathSeparator === -1) return parsed;\n\n const keyPath = nameAndPath.slice(pathSeparator + 1);\n const segments = keyPath.split(\"/\");\n const prefix = keyPath.endsWith(\"/\");\n if (\n keyPath.length === 0 ||\n /[\\u0000-\\u001f\\u007f\\\\]/.test(keyPath) ||\n segments.some(\n (segment, index) =>\n segment === \".\" ||\n segment === \"..\" ||\n (segment.length === 0 && !(prefix && index === segments.length - 1)),\n )\n ) {\n throw new NetworkIdError(\n `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`,\n );\n }\n return { ...parsed, keyPath };\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 * 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 v: typeof ENVELOPE_VERSION;\n /** Network id URN. */\n networkId: string;\n /** Exact named-key path; absent on network-root v1 envelopes. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/encryption/resource.ts","../../src/encryption/networkId.ts","../../src/encryption/types.ts"],"sourcesContent":["/**\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","/**\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\nexport interface ParsedEncryptionResource extends ParsedNetworkId {\n /** Named-key path, if the resource is scoped below a network. */\n keyPath?: string;\n}\n\n/**\n * Parse a network-root or named-key encryption resource.\n *\n * A trailing slash on keyPath denotes a prefix grant; empty and traversal\n * segments are rejected so resource matching cannot escape the named path.\n */\nexport function parseEncryptionResource(\n resource: string,\n): ParsedEncryptionResource {\n const networkSeparator = resource.lastIndexOf(\":\");\n const nameAndPath = resource.slice(networkSeparator + 1);\n const pathSeparator = nameAndPath.indexOf(\"/\");\n const name =\n pathSeparator === -1\n ? nameAndPath\n : nameAndPath.slice(0, pathSeparator);\n const networkId =\n pathSeparator === -1\n ? resource\n : `${resource.slice(0, networkSeparator + 1)}${name}`;\n const parsed = parseNetworkId(networkId);\n if (pathSeparator === -1) return parsed;\n\n const keyPath = nameAndPath.slice(pathSeparator + 1);\n const segments = keyPath.split(\"/\");\n const prefix = keyPath.endsWith(\"/\");\n if (\n keyPath.length === 0 ||\n /[\\u0000-\\u001f\\u007f\\\\]/.test(keyPath) ||\n segments.some(\n (segment, index) =>\n segment === \".\" ||\n segment === \"..\" ||\n (segment.length === 0 && !(prefix && index === segments.length - 1)),\n )\n ) {\n throw new NetworkIdError(\n `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`,\n );\n }\n return { ...parsed, keyPath };\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 * 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\";\nimport type { RoutingFailure } from \"../resourceRouting\";\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 v: typeof ENVELOPE_VERSION;\n /** Network id URN. */\n networkId: string;\n /** Exact named-key path; absent on network-root v1 envelopes. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 /** Exact named-key path for path-scoped decrypt, absent for root keys. */\n keyPath?: 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 | RoutingFailure\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 default:\n return input.message ?? input.code;\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"],"mappings":"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","names":[]}
|
package/dist/feed/index.cjs
CHANGED
|
@@ -60,6 +60,10 @@ var ErrorCodes = {
|
|
|
60
60
|
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
61
61
|
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
62
62
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
63
|
+
NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
|
|
64
|
+
NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
|
|
65
|
+
PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
|
|
66
|
+
RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
|
|
63
67
|
TIMEOUT: "TIMEOUT",
|
|
64
68
|
ABORTED: "ABORTED",
|
|
65
69
|
INVALID_INPUT: "INVALID_INPUT",
|