@opendatalabs/vana-sdk 3.23.0 → 4.0.0

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.
Files changed (84) hide show
  1. package/README.md +57 -0
  2. package/dist/auth/errors.cjs +1 -1
  3. package/dist/auth/web3-signed.cjs +1 -1
  4. package/dist/crypto/envelope/job-stream.test.d.ts +1 -0
  5. package/dist/crypto/envelope/job.cjs +593 -38
  6. package/dist/crypto/envelope/job.cjs.map +1 -1
  7. package/dist/crypto/envelope/job.d.ts +44 -27
  8. package/dist/crypto/envelope/job.js +589 -37
  9. package/dist/crypto/envelope/job.js.map +1 -1
  10. package/dist/direct/access-request-client.cjs +1 -1
  11. package/dist/direct/connect-flow.cjs +43 -1
  12. package/dist/direct/connect-flow.cjs.map +1 -1
  13. package/dist/direct/connect-flow.d.ts +12 -0
  14. package/dist/direct/connect-flow.js +43 -1
  15. package/dist/direct/connect-flow.js.map +1 -1
  16. package/dist/direct/controller.cjs +1 -1
  17. package/dist/direct/errors.cjs +1 -1
  18. package/dist/direct/escrow-payment.cjs.map +1 -1
  19. package/dist/direct/escrow-payment.d.ts +2 -2
  20. package/dist/direct/escrow-payment.js.map +1 -1
  21. package/dist/direct/personal-server-read.cjs +2 -2
  22. package/dist/direct/use-direct-vana-connect.cjs +2 -1
  23. package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
  24. package/dist/direct/use-direct-vana-connect.d.ts +3 -1
  25. package/dist/direct/use-direct-vana-connect.js +2 -1
  26. package/dist/direct/use-direct-vana-connect.js.map +1 -1
  27. package/dist/error-entry-points.test.d.ts +1 -0
  28. package/dist/errors.cjs +7 -0
  29. package/dist/errors.cjs.map +1 -1
  30. package/dist/errors.d.ts +10 -0
  31. package/dist/errors.js +6 -0
  32. package/dist/errors.js.map +1 -1
  33. package/dist/index.browser.d.ts +4 -4
  34. package/dist/index.browser.js +882 -518
  35. package/dist/index.browser.js.map +4 -4
  36. package/dist/index.node.cjs +1035 -705
  37. package/dist/index.node.cjs.map +4 -4
  38. package/dist/index.node.d.ts +4 -4
  39. package/dist/index.node.js +989 -567
  40. package/dist/index.node.js.map +4 -4
  41. package/dist/protocol/data-point-deletion.cjs +1 -1
  42. package/dist/protocol/derivative-questions.cjs +1 -1
  43. package/dist/protocol/derivative-status.cjs +1 -1
  44. package/dist/protocol/eip712.cjs +28 -2
  45. package/dist/protocol/eip712.cjs.map +1 -1
  46. package/dist/protocol/eip712.d.ts +70 -0
  47. package/dist/protocol/eip712.js +24 -1
  48. package/dist/protocol/eip712.js.map +1 -1
  49. package/dist/protocol/escrow.cjs +154 -2
  50. package/dist/protocol/escrow.cjs.map +1 -1
  51. package/dist/protocol/escrow.d.ts +145 -2
  52. package/dist/protocol/escrow.js +152 -1
  53. package/dist/protocol/escrow.js.map +1 -1
  54. package/dist/protocol/fixtures/moksha-identity.json +20 -0
  55. package/dist/protocol/gateway.cjs +1 -1
  56. package/dist/protocol/gateway.cjs.map +1 -1
  57. package/dist/protocol/gateway.d.ts +8 -38
  58. package/dist/protocol/gateway.js.map +1 -1
  59. package/dist/protocol/identity.cjs +7 -2
  60. package/dist/protocol/identity.cjs.map +1 -1
  61. package/dist/protocol/identity.js +7 -2
  62. package/dist/protocol/identity.js.map +1 -1
  63. package/dist/protocol/jobs-client.cjs +73 -21
  64. package/dist/protocol/jobs-client.cjs.map +1 -1
  65. package/dist/protocol/jobs-client.d.ts +7 -5
  66. package/dist/protocol/jobs-client.js +74 -21
  67. package/dist/protocol/jobs-client.js.map +1 -1
  68. package/dist/protocol/jobs.cjs +0 -3
  69. package/dist/protocol/jobs.cjs.map +1 -1
  70. package/dist/protocol/jobs.d.ts +21 -13
  71. package/dist/protocol/jobs.js +0 -2
  72. package/dist/protocol/jobs.js.map +1 -1
  73. package/dist/protocol/lineage.cjs +1 -1
  74. package/dist/protocol/personal-server-data.cjs +1 -1
  75. package/dist/protocol/personal-server-write.cjs +1 -1
  76. package/dist/protocol/write-request.cjs +1 -1
  77. package/dist/protocol/write-signer.cjs +1 -1
  78. package/dist/react.cjs.map +1 -1
  79. package/dist/react.d.ts +1 -1
  80. package/dist/react.js.map +1 -1
  81. package/dist/session-relay/client.cjs +1 -1
  82. package/dist/session-relay/errors.cjs +1 -1
  83. package/dist/session-relay/index.cjs +1 -1
  84. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/crypto/envelope/job.ts"],"sourcesContent":["/**\n * ECIES envelopes for encrypted job requests and results.\n *\n * Request plaintext is UTF-8 JSON with object keys sorted recursively and\n * array order preserved. Result properties follow their interface order. The\n * wire ciphertext is base64 of `iv || ephemPub || ct || mac`, as specified by\n * the ECIES provider interface. The Gateway hashes raw ciphertext bytes, not\n * plaintext.\n * The builder verifies the decrypted result's job ID, scope, and version\n * bindings. The PS worker verifies\n * `auth.bodyHash === sha256(canonicalJobRequestBytes(request))`; the Gateway\n * never sees the plaintext. Flow: personal-server-ts\n * `docs/260903-jobs-contract.md`, section 1.\n *\n * @category Cryptography\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex, fromHex, isAddress, isHex, toHex, type Hex } from \"viem\";\nimport {\n deserializeECIES,\n serializeECIES,\n type ECIESProvider,\n} from \"../ecies/interface\";\nimport {\n JOB_OPERATIONS,\n JOB_PROTOCOL_VERSION,\n type JobOperation,\n type JobRequest,\n type JobRequestEnvelope,\n type JobResult,\n} from \"../../protocol/jobs\";\nimport { fromBase64, toBase64 } from \"../../utils/encoding\";\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\n/** A job envelope or result did not match the jobs protocol. */\nexport class JobEnvelopeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"JobEnvelopeError\";\n }\n}\n\nfunction sortJsonKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortJsonKeys);\n }\n if (value !== null && typeof value === \"object\") {\n return Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [\n key,\n sortJsonKeys((value as Record<string, unknown>)[key]),\n ]),\n );\n }\n return value;\n}\n\nfunction canonicalJsonBytes(value: unknown): Uint8Array {\n return textEncoder.encode(JSON.stringify(sortJsonKeys(value)));\n}\n\n/**\n * Returns the canonical UTF-8 JSON bytes committed to by the auth body hash.\n *\n * @param request - Job request to validate and serialize canonically.\n * @returns Recursively key-sorted, whitespace-free UTF-8 JSON bytes.\n * @throws {JobEnvelopeError} If the request does not match the protocol schema.\n */\nexport function canonicalJobRequestBytes(request: JobRequest): Uint8Array {\n validateJobRequest(request);\n return canonicalJsonBytes(request);\n}\n\nfunction requestPlaintext(envelope: JobRequestEnvelope): Uint8Array {\n validateRequestEnvelope(envelope);\n return canonicalJsonBytes(envelope);\n}\n\nfunction resultPlaintext(result: JobResult): Uint8Array {\n return textEncoder.encode(\n JSON.stringify({\n v: result.v,\n jobId: result.jobId,\n scope: result.scope,\n version: result.version,\n contentType: result.contentType,\n body: result.body,\n }),\n );\n}\n\nfunction encryptedBytesToBase64(\n encrypted: Awaited<ReturnType<ECIESProvider[\"encrypt\"]>>,\n): string {\n return toBase64(fromHex(`0x${serializeECIES(encrypted)}`, \"bytes\"));\n}\n\nfunction base64ToEncrypted(ciphertext: string) {\n return deserializeECIES(toHex(fromBase64(ciphertext)));\n}\n\nfunction parseObject(\n plaintext: Uint8Array,\n kind: string,\n): Record<string, unknown> {\n try {\n const value: unknown = JSON.parse(textDecoder.decode(plaintext));\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new JobEnvelopeError(`Invalid ${kind}: expected an object`);\n }\n return value as Record<string, unknown>;\n } catch (error) {\n if (error instanceof JobEnvelopeError) throw error;\n throw new JobEnvelopeError(`Invalid ${kind}: malformed JSON`);\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction requirePlainObject(\n value: unknown,\n field: string,\n kind: string,\n): asserts value is Record<string, unknown> {\n if (!isPlainObject(value)) {\n throw new JobEnvelopeError(`Invalid ${kind}: invalid ${field}`);\n }\n}\n\nfunction requireExactKeys(\n value: Record<string, unknown>,\n expectedKeys: readonly string[],\n kind: string,\n): void {\n for (const key of expectedKeys) {\n if (!Object.hasOwn(value, key) || value[key] === undefined) {\n throw new JobEnvelopeError(`Invalid ${kind}: missing ${key}`);\n }\n }\n const expected = new Set<PropertyKey>(expectedKeys);\n for (const key of Reflect.ownKeys(value)) {\n if (!expected.has(key)) {\n throw new JobEnvelopeError(\n `Invalid ${kind}: unknown field ${String(key)}`,\n );\n }\n }\n}\n\nfunction requireString(\n value: unknown,\n field: string,\n kind: string,\n): asserts value is string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new JobEnvelopeError(`Invalid ${kind}: missing ${field}`);\n }\n}\n\nfunction validateJobRequest(value: unknown): JobRequest {\n requirePlainObject(value, \"request\", \"job request\");\n requireExactKeys(\n value,\n [\n \"v\",\n \"jobId\",\n \"owner\",\n \"builder\",\n \"builderPublicKey\",\n \"grantId\",\n \"scope\",\n \"operation\",\n \"pinnedVersion\",\n \"deadline\",\n ],\n \"job request\",\n );\n if (value.v !== JOB_PROTOCOL_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job request version: ${String(value.v)}`,\n );\n }\n requireString(value.jobId, \"jobId\", \"job request\");\n if (typeof value.owner !== \"string\" || !isAddress(value.owner)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid owner\");\n }\n if (typeof value.builder !== \"string\" || !isAddress(value.builder)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid builder\");\n }\n if (\n typeof value.builderPublicKey !== \"string\" ||\n !isHex(value.builderPublicKey)\n ) {\n throw new JobEnvelopeError(\"Invalid job request: invalid builderPublicKey\");\n }\n if (typeof value.grantId !== \"string\" || !isHex(value.grantId)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid grantId\");\n }\n requireString(value.scope, \"scope\", \"job request\");\n if (!JOB_OPERATIONS.includes(value.operation as JobOperation)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid operation\");\n }\n if (value.pinnedVersion !== null && typeof value.pinnedVersion !== \"string\") {\n throw new JobEnvelopeError(\"Invalid job request: missing pinnedVersion\");\n }\n if (\n typeof value.deadline !== \"string\" ||\n !Number.isFinite(Date.parse(value.deadline))\n ) {\n throw new JobEnvelopeError(\"Invalid job request: invalid deadline\");\n }\n return value as unknown as JobRequest;\n}\n\nfunction validateRequestEnvelope(value: unknown): JobRequestEnvelope {\n requirePlainObject(value, \"envelope\", \"job request envelope\");\n requireExactKeys(value, [\"request\", \"auth\"], \"job request envelope\");\n validateJobRequest(value.request);\n requireString(value.auth, \"auth\", \"job request envelope\");\n return value as unknown as JobRequestEnvelope;\n}\n\nfunction validateResult(value: unknown): JobResult {\n requirePlainObject(value, \"result\", \"job result\");\n requireExactKeys(\n value,\n [\"v\", \"jobId\", \"scope\", \"version\", \"contentType\", \"body\"],\n \"job result\",\n );\n if (value.v !== JOB_PROTOCOL_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job result version: ${String(value.v)}`,\n );\n }\n for (const field of [\"jobId\", \"scope\", \"contentType\"]) {\n requireString(value[field], field, \"job result\");\n }\n if (typeof value.body !== \"string\") {\n throw new JobEnvelopeError(\"Invalid job result: missing body\");\n }\n if (value.version !== null && typeof value.version !== \"string\") {\n throw new JobEnvelopeError(\"Invalid job result: missing version\");\n }\n return value as unknown as JobResult;\n}\n\n/**\n * Encrypts a validated job request envelope for a Personal Server enclave.\n *\n * The PS worker verifies\n * `auth.bodyHash === sha256(canonicalJobRequestBytes(request))`; the Gateway\n * never sees the plaintext.\n *\n * @param envelope - Request and builder Web3Signed authorization to encrypt.\n * @param enclavePublicKey - Public key returned by `GET /v1/identity?owner=`.\n * @param ecies - Injected ECIES implementation.\n * @returns Base64 ciphertext encoded as `iv || ephemPub || ct || mac`.\n * @throws {JobEnvelopeError} If the envelope or request is invalid.\n * @throws If ECIES encryption fails or the enclave public key is invalid.\n *\n * @example\n * ```ts\n * const identity = await fetch(`/v1/identity?owner=${owner}`).then((response) =>\n * response.json(),\n * );\n * const requestCiphertext = await sealJobRequest(\n * requestEnvelope,\n * identity.publicKey,\n * ecies,\n * );\n * await fetch(\"/v1/jobs\", {\n * method: \"POST\",\n * body: JSON.stringify({ ...submission, requestCiphertext }),\n * });\n * ```\n */\nexport async function sealJobRequest(\n envelope: JobRequestEnvelope,\n enclavePublicKey: Hex,\n ecies: ECIESProvider,\n): Promise<string> {\n const encrypted = await ecies.encrypt(\n fromHex(enclavePublicKey, \"bytes\"),\n requestPlaintext(envelope),\n );\n return encryptedBytesToBase64(encrypted);\n}\n\n/**\n * Decrypts and validates a job request envelope inside the enclave.\n *\n * @param ciphertext - Base64 `iv || ephemPub || ct || mac` ciphertext.\n * @param privateKey - Enclave key bytes, supplied as `Uint8Array` so the agent can zero them after use.\n * @param ecies - Injected ECIES implementation.\n * @returns The validated request envelope exactly as parsed from plaintext.\n * @throws {JobEnvelopeError} If plaintext is malformed or fails schema validation.\n * @throws If ciphertext decoding or ECIES decryption fails.\n */\nexport async function openJobRequest(\n ciphertext: string,\n privateKey: Uint8Array,\n ecies: ECIESProvider,\n): Promise<JobRequestEnvelope> {\n const plaintext = await ecies.decrypt(\n privateKey,\n base64ToEncrypted(ciphertext),\n );\n return validateRequestEnvelope(\n parseObject(plaintext, \"job request envelope\"),\n );\n}\n\n/**\n * Encrypts a validated job result for its builder and describes the ciphertext.\n *\n * `hash` is lowercase `0x` SHA-256 of decoded ciphertext bytes, not the\n * `sha256:` prefix used by Web3Signed `bodyHash`. `size` is that decoded byte\n * length. They equal the Gateway's `resultHash` and `resultSize`.\n *\n * @param result - Job result to validate and encrypt.\n * @param builderPublicKey - Builder wallet public key recorded in the request.\n * @param ecies - Injected ECIES implementation.\n * @returns Base64 ciphertext plus its Gateway-compatible hash and size.\n * @throws {JobEnvelopeError} If the result does not match the protocol schema.\n * @throws If ECIES encryption fails or the builder public key is invalid.\n */\nexport async function sealJobResult(\n result: JobResult,\n builderPublicKey: Hex,\n ecies: ECIESProvider,\n): Promise<{ ciphertext: string; hash: Hex; size: number }> {\n validateResult(result);\n const encrypted = await ecies.encrypt(\n fromHex(builderPublicKey, \"bytes\"),\n resultPlaintext(result),\n );\n const ciphertext = encryptedBytesToBase64(encrypted);\n const bytes = fromBase64(ciphertext);\n return { ciphertext, hash: bytesToHex(sha256(bytes)), size: bytes.length };\n}\n\n/**\n * Decrypts a job result and verifies its builder-visible protocol bindings.\n *\n * The builder private key is a `Hex` wallet key. For `expect.version`,\n * `undefined` skips the check while `null` requires a null result version.\n *\n * @param ciphertext - Base64 `iv || ephemPub || ct || mac` ciphertext.\n * @param builderPrivateKey - Builder's wallet private key as hex.\n * @param ecies - Injected ECIES implementation.\n * @param expect - Required job ID and optional scope and version bindings.\n * @returns The validated result when every supplied binding matches.\n * @throws {JobEnvelopeError} If plaintext is malformed, invalid, or a binding differs.\n * @throws If ciphertext decoding or ECIES decryption fails.\n *\n * @example\n * ```ts\n * const result = await openJobResult(\n * status.resultCiphertext,\n * key,\n * ecies,\n * { jobId, scope },\n * );\n * const body = fromBase64(result.body);\n * ```\n */\nexport async function openJobResult(\n ciphertext: string,\n builderPrivateKey: Hex,\n ecies: ECIESProvider,\n expect: { jobId: string; scope?: string; version?: string | null },\n): Promise<JobResult> {\n const plaintext = await ecies.decrypt(\n fromHex(builderPrivateKey, \"bytes\"),\n base64ToEncrypted(ciphertext),\n );\n const result = validateResult(parseObject(plaintext, \"job result\"));\n if (result.jobId !== expect.jobId) {\n throw new JobEnvelopeError(\n `Job result ID ${result.jobId} does not match expected job ID ${expect.jobId}`,\n );\n }\n if (expect.scope !== undefined && result.scope !== expect.scope) {\n throw new JobEnvelopeError(\n `Job result scope ${result.scope} does not match expected scope ${expect.scope}`,\n );\n }\n if (expect.version !== undefined && result.version !== expect.version) {\n throw new JobEnvelopeError(\n `Job result version ${String(result.version)} does not match expected version ${String(expect.version)}`,\n );\n }\n return result;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/crypto/envelope/job.ts"],"sourcesContent":["/**\n * ECIES envelopes for encrypted job requests and results.\n *\n * Request plaintext is UTF-8 JSON with object keys sorted recursively and\n * array order preserved. Result bodies use a hybrid streaming envelope:\n * ECIES wraps a random AES key, nonce prefix, and result metadata, while fixed\n * chunks of the body are sealed independently with AES-256-GCM. The Request\n * ciphertext is base64 of `iv || ephemPub || ct || mac`, as specified by the\n * ECIES provider interface. Result ciphertext is the raw hybrid byte sequence\n * stored in object storage. The Gateway hashes raw ciphertext bytes, not\n * plaintext.\n * The builder verifies the decrypted result's job ID, scope, and version\n * bindings. The PS worker verifies\n * `auth.bodyHash === sha256(canonicalJobRequestBytes(request))`; the Gateway\n * never sees the plaintext. Flow: personal-server-ts\n * `docs/260903-jobs-contract.md`, section 1.\n *\n * @category Cryptography\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex, fromHex, isAddress, isHex, toHex, type Hex } from \"viem\";\nimport { CURVE, FORMAT, MAC } from \"../ecies/constants\";\nimport {\n deserializeECIES,\n ECIESError,\n serializeECIES,\n type ECIESEncrypted,\n type ECIESProvider,\n} from \"../ecies/interface\";\nimport {\n JOB_OPERATIONS,\n JOB_PROTOCOL_VERSION,\n type JobOperation,\n type JobRequest,\n type JobRequestEnvelope,\n type JobResult,\n} from \"../../protocol/jobs\";\nimport { fromBase64, toBase64 } from \"../../utils/encoding\";\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n/** Result metadata is only a few short fields; cap it before decoding attacker-reachable bytes. */\nconst MAX_JOB_RESULT_HEADER_BYTES = 4096;\nconst WRAPPED_KEY_LENGTH_BYTES = 4;\nconst FRAME_LENGTH_BYTES = 4;\nconst FRAME_FLAGS_BYTES = 1;\nconst AES_KEY_BYTES = 32;\nconst AES_GCM_NONCE_BYTES = 12;\nconst AES_GCM_TAG_BYTES = 16;\nconst WRAPPED_METADATA_LENGTH_BYTES = 4;\nconst WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES =\n AES_KEY_BYTES + AES_GCM_NONCE_BYTES + WRAPPED_METADATA_LENGTH_BYTES;\nconst MAX_WRAPPED_KEY_BYTES = 8192;\nconst FINAL_FRAME_FLAG = 1;\nconst MAX_CHUNK_INDEX = 0xffffffff;\n\n/** Current hybrid sealed-result wire-format version. */\nexport const JOB_RESULT_FORMAT_VERSION = 2;\n/** Plaintext bytes authenticated independently in each result frame. */\nexport const JOB_RESULT_CHUNK_BYTES = 1024 * 1024;\n\n/** Result fields authenticated for every encrypted body chunk. */\nexport type JobResultMetadata = Omit<JobResult, \"body\">;\n\n/** Streaming sealer output. Write the header before transformed body frames. */\nexport interface SealedJobResultStream {\n header: Uint8Array;\n transform: TransformStream<Uint8Array, Uint8Array>;\n}\n\n/** Verified metadata and incrementally decrypted result body. */\nexport interface OpenedJobResultStream {\n metadata: JobResultMetadata;\n body: ReadableStream<Uint8Array>;\n}\n\n/** Builder-visible bindings checked before any result body chunk is yielded. */\nexport interface JobResultExpectation {\n jobId: string;\n scope?: string;\n version?: string | null;\n}\n\n/** A job envelope or result did not match the jobs protocol. */\nexport class JobEnvelopeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"JobEnvelopeError\";\n }\n}\n\nfunction sortJsonKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortJsonKeys);\n }\n if (value !== null && typeof value === \"object\") {\n return Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [\n key,\n sortJsonKeys((value as Record<string, unknown>)[key]),\n ]),\n );\n }\n return value;\n}\n\nfunction canonicalJsonBytes(value: unknown): Uint8Array {\n return textEncoder.encode(JSON.stringify(sortJsonKeys(value)));\n}\n\n/**\n * Returns the canonical UTF-8 JSON bytes committed to by the auth body hash.\n *\n * @param request - Job request to validate and serialize canonically.\n * @returns Recursively key-sorted, whitespace-free UTF-8 JSON bytes.\n * @throws {JobEnvelopeError} If the request does not match the protocol schema.\n */\nexport function canonicalJobRequestBytes(request: JobRequest): Uint8Array {\n validateJobRequest(request);\n return canonicalJsonBytes(request);\n}\n\nfunction requestPlaintext(envelope: JobRequestEnvelope): Uint8Array {\n validateRequestEnvelope(envelope);\n return canonicalJsonBytes(envelope);\n}\n\nfunction resultMetadata(result: JobResult): JobResultMetadata {\n return {\n v: result.v,\n jobId: result.jobId,\n scope: result.scope,\n version: result.version,\n contentType: result.contentType,\n };\n}\n\nfunction encryptedBytesToBase64(\n encrypted: Awaited<ReturnType<ECIESProvider[\"encrypt\"]>>,\n): string {\n return toBase64(fromHex(`0x${serializeECIES(encrypted)}`, \"bytes\"));\n}\n\nfunction encryptedToBytes(\n encrypted: Awaited<ReturnType<ECIESProvider[\"encrypt\"]>>,\n): Uint8Array {\n const bytes = new Uint8Array(\n encrypted.iv.length +\n encrypted.ephemPublicKey.length +\n encrypted.ciphertext.length +\n encrypted.mac.length,\n );\n let offset = 0;\n bytes.set(encrypted.iv, offset);\n offset += encrypted.iv.length;\n bytes.set(encrypted.ephemPublicKey, offset);\n offset += encrypted.ephemPublicKey.length;\n bytes.set(encrypted.ciphertext, offset);\n offset += encrypted.ciphertext.length;\n bytes.set(encrypted.mac, offset);\n return bytes;\n}\n\nfunction bytesToEncrypted(bytes: Uint8Array): ECIESEncrypted {\n const absoluteMinLength = FORMAT.IV_LENGTH + 1 + MAC.LENGTH + 1;\n if (bytes.length < absoluteMinLength) {\n throw new ECIESError(\n `Invalid ECIES data: too short (${bytes.length} bytes, minimum ${absoluteMinLength} bytes required)`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n const prefix = bytes[FORMAT.EPHEMERAL_KEY_OFFSET];\n if (prefix !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n `Invalid ephemeral public key: must be uncompressed format (0x04 prefix), got 0x${prefix.toString(16).padStart(2, \"0\")}`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n const ephemKeySize = CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH;\n const minLength = FORMAT.IV_LENGTH + ephemKeySize + MAC.LENGTH + 1;\n if (bytes.length < minLength) {\n throw new ECIESError(\n `Invalid ECIES data: too short (${bytes.length} bytes, minimum ${minLength} bytes required)`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n return {\n iv: bytes.subarray(FORMAT.IV_OFFSET, FORMAT.IV_OFFSET + FORMAT.IV_LENGTH),\n ephemPublicKey: bytes.subarray(\n FORMAT.EPHEMERAL_KEY_OFFSET,\n FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize,\n ),\n ciphertext: bytes.subarray(\n FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize,\n bytes.length - MAC.LENGTH,\n ),\n mac: bytes.subarray(bytes.length - MAC.LENGTH),\n };\n}\n\nfunction base64ToEncrypted(ciphertext: string) {\n return deserializeECIES(toHex(fromBase64(ciphertext)));\n}\n\nfunction parseObject(\n plaintext: Uint8Array,\n kind: string,\n): Record<string, unknown> {\n try {\n const value: unknown = JSON.parse(textDecoder.decode(plaintext));\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new JobEnvelopeError(`Invalid ${kind}: expected an object`);\n }\n return value as Record<string, unknown>;\n } catch (error) {\n if (error instanceof JobEnvelopeError) throw error;\n throw new JobEnvelopeError(`Invalid ${kind}: malformed JSON`);\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction requirePlainObject(\n value: unknown,\n field: string,\n kind: string,\n): asserts value is Record<string, unknown> {\n if (!isPlainObject(value)) {\n throw new JobEnvelopeError(`Invalid ${kind}: invalid ${field}`);\n }\n}\n\nfunction requireExactKeys(\n value: Record<string, unknown>,\n expectedKeys: readonly string[],\n kind: string,\n): void {\n for (const key of expectedKeys) {\n if (!Object.hasOwn(value, key) || value[key] === undefined) {\n throw new JobEnvelopeError(`Invalid ${kind}: missing ${key}`);\n }\n }\n const expected = new Set<PropertyKey>(expectedKeys);\n for (const key of Reflect.ownKeys(value)) {\n if (!expected.has(key)) {\n throw new JobEnvelopeError(\n `Invalid ${kind}: unknown field ${String(key)}`,\n );\n }\n }\n}\n\nfunction requireString(\n value: unknown,\n field: string,\n kind: string,\n): asserts value is string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new JobEnvelopeError(`Invalid ${kind}: missing ${field}`);\n }\n}\n\nfunction validateJobRequest(value: unknown): JobRequest {\n requirePlainObject(value, \"request\", \"job request\");\n requireExactKeys(\n value,\n [\n \"v\",\n \"jobId\",\n \"owner\",\n \"builder\",\n \"builderPublicKey\",\n \"grantId\",\n \"scope\",\n \"operation\",\n \"pinnedVersion\",\n \"deadline\",\n ],\n \"job request\",\n );\n if (value.v !== JOB_PROTOCOL_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job request version: ${String(value.v)}`,\n );\n }\n requireString(value.jobId, \"jobId\", \"job request\");\n if (typeof value.owner !== \"string\" || !isAddress(value.owner)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid owner\");\n }\n if (typeof value.builder !== \"string\" || !isAddress(value.builder)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid builder\");\n }\n if (\n typeof value.builderPublicKey !== \"string\" ||\n !isHex(value.builderPublicKey)\n ) {\n throw new JobEnvelopeError(\"Invalid job request: invalid builderPublicKey\");\n }\n if (typeof value.grantId !== \"string\" || !isHex(value.grantId)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid grantId\");\n }\n requireString(value.scope, \"scope\", \"job request\");\n if (!JOB_OPERATIONS.includes(value.operation as JobOperation)) {\n throw new JobEnvelopeError(\"Invalid job request: invalid operation\");\n }\n if (value.pinnedVersion !== null && typeof value.pinnedVersion !== \"string\") {\n throw new JobEnvelopeError(\"Invalid job request: missing pinnedVersion\");\n }\n if (\n typeof value.deadline !== \"string\" ||\n !Number.isFinite(Date.parse(value.deadline))\n ) {\n throw new JobEnvelopeError(\"Invalid job request: invalid deadline\");\n }\n return value as unknown as JobRequest;\n}\n\nfunction validateRequestEnvelope(value: unknown): JobRequestEnvelope {\n requirePlainObject(value, \"envelope\", \"job request envelope\");\n requireExactKeys(value, [\"request\", \"auth\"], \"job request envelope\");\n validateJobRequest(value.request);\n requireString(value.auth, \"auth\", \"job request envelope\");\n return value as unknown as JobRequestEnvelope;\n}\n\nfunction validateResultMetadata(value: unknown): JobResultMetadata {\n requirePlainObject(value, \"metadata\", \"job result\");\n requireExactKeys(\n value,\n [\"v\", \"jobId\", \"scope\", \"version\", \"contentType\"],\n \"job result\",\n );\n if (value.v !== JOB_PROTOCOL_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job result version: ${String(value.v)}`,\n );\n }\n for (const field of [\"jobId\", \"scope\", \"contentType\"]) {\n requireString(value[field], field, \"job result\");\n }\n if (value.version !== null && typeof value.version !== \"string\") {\n throw new JobEnvelopeError(\"Invalid job result: missing version\");\n }\n return value as unknown as JobResultMetadata;\n}\n\nfunction validateResult(value: unknown): JobResult {\n requirePlainObject(value, \"result\", \"job result\");\n requireExactKeys(\n value,\n [\"v\", \"jobId\", \"scope\", \"version\", \"contentType\", \"body\"],\n \"job result\",\n );\n validateResultMetadata(resultMetadata(value as unknown as JobResult));\n if (!(value.body instanceof Uint8Array)) {\n throw new JobEnvelopeError(\"Invalid job result: missing body\");\n }\n return value as unknown as JobResult;\n}\n\nfunction webCrypto(): Crypto {\n if (\n typeof globalThis.crypto === \"undefined\" ||\n typeof globalThis.crypto.getRandomValues !== \"function\" ||\n !globalThis.crypto.subtle\n ) {\n throw new JobEnvelopeError(\n \"Job result streaming requires the Web Crypto API\",\n );\n }\n return globalThis.crypto;\n}\n\nfunction uint32(value: number): Uint8Array {\n const bytes = new Uint8Array(4);\n new DataView(bytes.buffer).setUint32(0, value);\n return bytes;\n}\n\nfunction readUint32(bytes: Uint8Array, offset = 0): number {\n return new DataView(bytes.buffer, bytes.byteOffset + offset, 4).getUint32(0);\n}\n\nfunction wrappedKeyPlaintext(\n contentKey: Uint8Array,\n noncePrefix: Uint8Array,\n metadataBytes: Uint8Array,\n): Uint8Array {\n const plaintext = new Uint8Array(\n WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES + metadataBytes.length,\n );\n plaintext.set(contentKey, 0);\n plaintext.set(noncePrefix, AES_KEY_BYTES);\n new DataView(plaintext.buffer).setUint32(\n AES_KEY_BYTES + AES_GCM_NONCE_BYTES,\n metadataBytes.length,\n );\n plaintext.set(metadataBytes, WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES);\n return plaintext;\n}\n\nfunction parseWrappedKeyPlaintext(plaintext: Uint8Array): {\n contentKey: Uint8Array;\n noncePrefix: Uint8Array;\n metadataBytes: Uint8Array;\n metadata: JobResultMetadata;\n} {\n if (plaintext.length < WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES) {\n throw new JobEnvelopeError(\"Invalid job result: wrapped key is truncated\");\n }\n const metadataLength = readUint32(\n plaintext,\n AES_KEY_BYTES + AES_GCM_NONCE_BYTES,\n );\n if (metadataLength > MAX_JOB_RESULT_HEADER_BYTES) {\n throw new JobEnvelopeError(\n `Invalid job result: header exceeds ${MAX_JOB_RESULT_HEADER_BYTES} bytes`,\n );\n }\n if (\n plaintext.length !==\n WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES + metadataLength\n ) {\n throw new JobEnvelopeError(\n \"Invalid job result: header length exceeds payload\",\n );\n }\n const metadataBytes = plaintext.slice(WRAPPED_KEY_PLAINTEXT_PREFIX_BYTES);\n const metadata = validateResultMetadata(\n parseObject(metadataBytes, \"job result header\"),\n );\n return {\n contentKey: plaintext.slice(0, AES_KEY_BYTES),\n noncePrefix: plaintext.slice(\n AES_KEY_BYTES,\n AES_KEY_BYTES + AES_GCM_NONCE_BYTES,\n ),\n metadataBytes,\n metadata,\n };\n}\n\nasync function importAesKey(\n contentKey: Uint8Array,\n usage: KeyUsage,\n): Promise<CryptoKey> {\n return webCrypto().subtle.importKey(\n \"raw\",\n contentKey as BufferSource,\n { name: \"AES-GCM\" },\n false,\n [usage],\n );\n}\n\nfunction chunkNonce(noncePrefix: Uint8Array, chunkIndex: number): Uint8Array {\n if (chunkIndex > MAX_CHUNK_INDEX) {\n throw new JobEnvelopeError(\"Invalid job result: too many chunks\");\n }\n const nonce = noncePrefix.slice();\n const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength);\n view.setUint32(8, view.getUint32(8) ^ chunkIndex);\n return nonce;\n}\n\nfunction chunkAad(\n metadataBytes: Uint8Array,\n chunkIndex: number,\n final: boolean,\n plaintextLength: number,\n): Uint8Array {\n const aad = new Uint8Array(14 + metadataBytes.length);\n aad[0] = JOB_RESULT_FORMAT_VERSION;\n aad.set(uint32(metadataBytes.length), 1);\n aad.set(metadataBytes, 5);\n aad.set(uint32(chunkIndex), 5 + metadataBytes.length);\n aad[9 + metadataBytes.length] = final ? FINAL_FRAME_FLAG : 0;\n aad.set(uint32(plaintextLength), 10 + metadataBytes.length);\n return aad;\n}\n\nasync function encryptResultChunk(\n key: CryptoKey,\n noncePrefix: Uint8Array,\n metadataBytes: Uint8Array,\n plaintext: Uint8Array,\n chunkIndex: number,\n final: boolean,\n): Promise<Uint8Array> {\n const encrypted = new Uint8Array(\n await webCrypto().subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: chunkNonce(noncePrefix, chunkIndex) as BufferSource,\n additionalData: chunkAad(\n metadataBytes,\n chunkIndex,\n final,\n plaintext.length,\n ) as BufferSource,\n tagLength: AES_GCM_TAG_BYTES * 8,\n },\n key,\n plaintext as BufferSource,\n ),\n );\n const frame = new Uint8Array(\n FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES + encrypted.length,\n );\n new DataView(frame.buffer).setUint32(0, encrypted.length);\n frame[FRAME_LENGTH_BYTES] = final ? FINAL_FRAME_FLAG : 0;\n frame.set(encrypted, FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES);\n return frame;\n}\n\nfunction verifyResultBindings(\n metadata: JobResultMetadata,\n expect: JobResultExpectation,\n): void {\n if (metadata.jobId !== expect.jobId) {\n throw new JobEnvelopeError(\n `Job result ID ${metadata.jobId} does not match expected job ID ${expect.jobId}`,\n );\n }\n if (expect.scope !== undefined && metadata.scope !== expect.scope) {\n throw new JobEnvelopeError(\n `Job result scope ${metadata.scope} does not match expected scope ${expect.scope}`,\n );\n }\n if (expect.version !== undefined && metadata.version !== expect.version) {\n throw new JobEnvelopeError(\n `Job result version ${String(metadata.version)} does not match expected version ${String(expect.version)}`,\n );\n }\n}\n\nclass ByteStreamReader {\n private readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n private current: Uint8Array | undefined;\n private offset = 0;\n private ended = false;\n\n constructor(stream: ReadableStream<Uint8Array>) {\n this.reader = stream.getReader();\n }\n\n private async ensureBytes(): Promise<boolean> {\n while (!this.current || this.offset === this.current.length) {\n if (this.ended) return false;\n const next = await this.reader.read();\n if (next.done) {\n this.ended = true;\n this.current = undefined;\n return false;\n }\n if (!(next.value instanceof Uint8Array)) {\n throw new JobEnvelopeError(\n \"Invalid job result: stream yielded non-byte data\",\n );\n }\n this.current = next.value;\n this.offset = 0;\n }\n return true;\n }\n\n async readExactOrNull(length: number): Promise<Uint8Array | null> {\n if (!(await this.ensureBytes())) return null;\n const bytes = new Uint8Array(length);\n let written = 0;\n while (written < length) {\n if (!(await this.ensureBytes())) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n const available = this.current!.length - this.offset;\n const count = Math.min(available, length - written);\n bytes.set(\n this.current!.subarray(this.offset, this.offset + count),\n written,\n );\n this.offset += count;\n written += count;\n }\n return bytes;\n }\n\n async readExact(length: number): Promise<Uint8Array> {\n const bytes = await this.readExactOrNull(length);\n if (!bytes) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n return bytes;\n }\n\n async hasRemaining(): Promise<boolean> {\n return this.ensureBytes();\n }\n\n async cancel(reason?: unknown): Promise<void> {\n await this.reader.cancel(reason);\n }\n}\n\nfunction normalizeChunkError(error: unknown): Error {\n if (error instanceof JobEnvelopeError) return error;\n return new JobEnvelopeError(\n `Invalid job result: chunk authentication failed${error instanceof Error ? `: ${error.message}` : \"\"}`,\n );\n}\n\n/**\n * Encrypts a validated job request envelope for a Personal Server enclave.\n *\n * The PS worker verifies\n * `auth.bodyHash === sha256(canonicalJobRequestBytes(request))`; the Gateway\n * never sees the plaintext.\n *\n * @param envelope - Request and builder Web3Signed authorization to encrypt.\n * @param enclavePublicKey - Public key returned by `GET /v1/identity?owner=`.\n * @param ecies - Injected ECIES implementation.\n * @returns Base64 ciphertext encoded as `iv || ephemPub || ct || mac`.\n * @throws {JobEnvelopeError} If the envelope or request is invalid.\n * @throws If ECIES encryption fails or the enclave public key is invalid.\n *\n * @example\n * ```ts\n * const identity = await fetch(`/v1/identity?owner=${owner}`).then((response) =>\n * response.json(),\n * );\n * const requestCiphertext = await sealJobRequest(\n * requestEnvelope,\n * identity.publicKey,\n * ecies,\n * );\n * await fetch(\"/v1/jobs\", {\n * method: \"POST\",\n * body: JSON.stringify({ ...submission, requestCiphertext }),\n * });\n * ```\n */\nexport async function sealJobRequest(\n envelope: JobRequestEnvelope,\n enclavePublicKey: Hex,\n ecies: ECIESProvider,\n): Promise<string> {\n const encrypted = await ecies.encrypt(\n fromHex(enclavePublicKey, \"bytes\"),\n requestPlaintext(envelope),\n );\n return encryptedBytesToBase64(encrypted);\n}\n\n/**\n * Decrypts and validates a job request envelope inside the enclave.\n *\n * @param ciphertext - Base64 `iv || ephemPub || ct || mac` ciphertext.\n * @param privateKey - Enclave key bytes, supplied as `Uint8Array` so the agent can zero them after use.\n * @param ecies - Injected ECIES implementation.\n * @returns The validated request envelope exactly as parsed from plaintext.\n * @throws {JobEnvelopeError} If plaintext is malformed or fails schema validation.\n * @throws If ciphertext decoding or ECIES decryption fails.\n */\nexport async function openJobRequest(\n ciphertext: string,\n privateKey: Uint8Array,\n ecies: ECIESProvider,\n): Promise<JobRequestEnvelope> {\n const plaintext = await ecies.decrypt(\n privateKey,\n base64ToEncrypted(ciphertext),\n );\n return validateRequestEnvelope(\n parseObject(plaintext, \"job request envelope\"),\n );\n}\n\n/** Create a bounded-memory hybrid sealer for a job result body. */\nexport async function sealJobResultStream(\n metadataValue: JobResultMetadata,\n builderPublicKey: Hex,\n ecies: ECIESProvider,\n): Promise<SealedJobResultStream> {\n const metadata = validateResultMetadata(metadataValue);\n const metadataBytes = canonicalJsonBytes(metadata);\n if (metadataBytes.length > MAX_JOB_RESULT_HEADER_BYTES) {\n throw new JobEnvelopeError(\n `Invalid job result: header exceeds ${MAX_JOB_RESULT_HEADER_BYTES} bytes`,\n );\n }\n\n const crypto = webCrypto();\n const contentKey = crypto.getRandomValues(new Uint8Array(AES_KEY_BYTES));\n const noncePrefix = crypto.getRandomValues(\n new Uint8Array(AES_GCM_NONCE_BYTES),\n );\n const key = await importAesKey(contentKey, \"encrypt\");\n const keyPlaintext = wrappedKeyPlaintext(\n contentKey,\n noncePrefix,\n metadataBytes,\n );\n const encryptedKey = await ecies.encrypt(\n fromHex(builderPublicKey, \"bytes\"),\n keyPlaintext,\n );\n contentKey.fill(0);\n keyPlaintext.fill(0);\n const wrappedKey = encryptedToBytes(encryptedKey);\n if (wrappedKey.length > MAX_WRAPPED_KEY_BYTES) {\n throw new JobEnvelopeError(\"Invalid job result: wrapped key is too large\");\n }\n const header = new Uint8Array(\n 1 + WRAPPED_KEY_LENGTH_BYTES + wrappedKey.length,\n );\n header[0] = JOB_RESULT_FORMAT_VERSION;\n new DataView(header.buffer).setUint32(1, wrappedKey.length);\n header.set(wrappedKey, 1 + WRAPPED_KEY_LENGTH_BYTES);\n\n let pending = new Uint8Array(JOB_RESULT_CHUNK_BYTES);\n let pendingLength = 0;\n let chunkIndex = 0;\n const transform = new TransformStream<Uint8Array, Uint8Array>({\n async transform(chunk, controller) {\n if (!(chunk instanceof Uint8Array)) {\n throw new JobEnvelopeError(\n \"Invalid job result: stream yielded non-byte data\",\n );\n }\n let offset = 0;\n while (offset < chunk.length) {\n if (pendingLength === JOB_RESULT_CHUNK_BYTES) {\n controller.enqueue(\n await encryptResultChunk(\n key,\n noncePrefix,\n metadataBytes,\n pending,\n chunkIndex,\n false,\n ),\n );\n chunkIndex += 1;\n pending = new Uint8Array(JOB_RESULT_CHUNK_BYTES);\n pendingLength = 0;\n }\n const count = Math.min(\n chunk.length - offset,\n JOB_RESULT_CHUNK_BYTES - pendingLength,\n );\n pending.set(chunk.subarray(offset, offset + count), pendingLength);\n pendingLength += count;\n offset += count;\n }\n },\n async flush(controller) {\n controller.enqueue(\n await encryptResultChunk(\n key,\n noncePrefix,\n metadataBytes,\n pending.subarray(0, pendingLength),\n chunkIndex,\n true,\n ),\n );\n },\n });\n\n return { header, transform };\n}\n\n/**\n * Encrypts a validated job result for its builder and describes the ciphertext.\n *\n * This compatibility wrapper drives {@link sealJobResultStream} and allocates\n * the final sealed byte array once. `hash` and `size` retain their Gateway\n * object-storage semantics.\n */\nexport async function sealJobResult(\n result: JobResult,\n builderPublicKey: Hex,\n ecies: ECIESProvider,\n): Promise<{ bytes: Uint8Array; hash: Hex; size: number }> {\n validateResult(result);\n const sealed = await sealJobResultStream(\n resultMetadata(result),\n builderPublicKey,\n ecies,\n );\n const frameCount = Math.max(\n 1,\n Math.ceil(result.body.length / JOB_RESULT_CHUNK_BYTES),\n );\n const size =\n sealed.header.length +\n result.body.length +\n frameCount * (FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES + AES_GCM_TAG_BYTES);\n const bytes = new Uint8Array(size);\n bytes.set(sealed.header, 0);\n let outputOffset = sealed.header.length;\n const reader = sealed.transform.readable.getReader();\n const drain = (async () => {\n for (;;) {\n const next = await reader.read();\n if (next.done) break;\n bytes.set(next.value, outputOffset);\n outputOffset += next.value.length;\n }\n })();\n const writer = sealed.transform.writable.getWriter();\n const fill = (async () => {\n for (\n let offset = 0;\n offset < result.body.length;\n offset += JOB_RESULT_CHUNK_BYTES\n ) {\n await writer.write(\n result.body.subarray(offset, offset + JOB_RESULT_CHUNK_BYTES),\n );\n }\n await writer.close();\n })();\n await Promise.all([drain, fill]);\n if (outputOffset !== bytes.length) {\n throw new JobEnvelopeError(\"Invalid job result: sealed size mismatch\");\n }\n return { bytes, hash: bytesToHex(sha256(bytes)), size };\n}\n\n/** Open and authenticate a hybrid job result without buffering its body. */\nexport async function openJobResultStream(\n bytesStream: ReadableStream<Uint8Array>,\n builderPrivateKey: Hex,\n ecies: ECIESProvider,\n expect: JobResultExpectation,\n): Promise<OpenedJobResultStream> {\n const reader = new ByteStreamReader(bytesStream);\n const headerPrefix = await reader.readExact(1 + WRAPPED_KEY_LENGTH_BYTES);\n const formatVersion = headerPrefix[0];\n if (formatVersion !== JOB_RESULT_FORMAT_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job result format: ${String(formatVersion)}`,\n );\n }\n const wrappedKeyLength = readUint32(headerPrefix, 1);\n if (wrappedKeyLength === 0 || wrappedKeyLength > MAX_WRAPPED_KEY_BYTES) {\n throw new JobEnvelopeError(\n \"Invalid job result: invalid wrapped key length\",\n );\n }\n const wrappedKey = await reader.readExact(wrappedKeyLength);\n const keyPlaintext = await ecies.decrypt(\n fromHex(builderPrivateKey, \"bytes\"),\n bytesToEncrypted(wrappedKey),\n );\n const parsed = parseWrappedKeyPlaintext(keyPlaintext);\n verifyResultBindings(parsed.metadata, expect);\n const key = await importAesKey(parsed.contentKey, \"decrypt\");\n parsed.contentKey.fill(0);\n keyPlaintext.fill(0);\n\n let chunkIndex = 0;\n let finished = false;\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (finished) {\n controller.close();\n return;\n }\n try {\n const frameHeader = await reader.readExactOrNull(\n FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES,\n );\n if (!frameHeader) {\n throw new JobEnvelopeError(\"Invalid job result: missing final chunk\");\n }\n const encryptedLength = readUint32(frameHeader);\n if (\n encryptedLength < AES_GCM_TAG_BYTES ||\n encryptedLength > JOB_RESULT_CHUNK_BYTES + AES_GCM_TAG_BYTES\n ) {\n throw new JobEnvelopeError(\n \"Invalid job result: invalid chunk length\",\n );\n }\n const flags = frameHeader[FRAME_LENGTH_BYTES];\n if (flags !== 0 && flags !== FINAL_FRAME_FLAG) {\n throw new JobEnvelopeError(\"Invalid job result: invalid chunk flags\");\n }\n const final = flags === FINAL_FRAME_FLAG;\n const plaintextLength = encryptedLength - AES_GCM_TAG_BYTES;\n if (!final && plaintextLength !== JOB_RESULT_CHUNK_BYTES) {\n throw new JobEnvelopeError(\n \"Invalid job result: short non-final chunk\",\n );\n }\n const encrypted = await reader.readExact(encryptedLength);\n const plaintext = new Uint8Array(\n await webCrypto().subtle.decrypt(\n {\n name: \"AES-GCM\",\n iv: chunkNonce(parsed.noncePrefix, chunkIndex) as BufferSource,\n additionalData: chunkAad(\n parsed.metadataBytes,\n chunkIndex,\n final,\n plaintextLength,\n ) as BufferSource,\n tagLength: AES_GCM_TAG_BYTES * 8,\n },\n key,\n encrypted as BufferSource,\n ),\n );\n if (plaintext.length !== plaintextLength) {\n throw new JobEnvelopeError(\n \"Invalid job result: decrypted chunk length mismatch\",\n );\n }\n chunkIndex += 1;\n if (final) {\n if (await reader.hasRemaining()) {\n throw new JobEnvelopeError(\n \"Invalid job result: bytes follow final chunk\",\n );\n }\n finished = true;\n }\n controller.enqueue(plaintext);\n if (finished) controller.close();\n } catch (error) {\n controller.error(normalizeChunkError(error));\n void reader.cancel(error).catch(() => undefined);\n }\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return { metadata: parsed.metadata, body };\n}\n\nfunction bufferedResultBodySize(sealedBytes: Uint8Array): number {\n if (sealedBytes.length < 1 + WRAPPED_KEY_LENGTH_BYTES) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n if (sealedBytes[0] !== JOB_RESULT_FORMAT_VERSION) {\n throw new JobEnvelopeError(\n `Unsupported job result format: ${String(sealedBytes[0])}`,\n );\n }\n const wrappedKeyLength = readUint32(sealedBytes, 1);\n if (wrappedKeyLength === 0 || wrappedKeyLength > MAX_WRAPPED_KEY_BYTES) {\n throw new JobEnvelopeError(\n \"Invalid job result: invalid wrapped key length\",\n );\n }\n let offset = 1 + WRAPPED_KEY_LENGTH_BYTES + wrappedKeyLength;\n if (offset > sealedBytes.length) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n let bodySize = 0;\n let sawFinal = false;\n while (offset < sealedBytes.length) {\n if (sealedBytes.length - offset < FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n const encryptedLength = readUint32(sealedBytes, offset);\n const flags = sealedBytes[offset + FRAME_LENGTH_BYTES];\n if (\n encryptedLength < AES_GCM_TAG_BYTES ||\n encryptedLength > JOB_RESULT_CHUNK_BYTES + AES_GCM_TAG_BYTES ||\n (flags !== 0 && flags !== FINAL_FRAME_FLAG)\n ) {\n throw new JobEnvelopeError(\"Invalid job result: invalid chunk framing\");\n }\n const plaintextLength = encryptedLength - AES_GCM_TAG_BYTES;\n if (flags === 0 && plaintextLength !== JOB_RESULT_CHUNK_BYTES) {\n throw new JobEnvelopeError(\"Invalid job result: short non-final chunk\");\n }\n offset += FRAME_LENGTH_BYTES + FRAME_FLAGS_BYTES;\n if (encryptedLength > sealedBytes.length - offset) {\n throw new JobEnvelopeError(\"Invalid job result: truncated payload\");\n }\n offset += encryptedLength;\n bodySize += plaintextLength;\n if (flags === FINAL_FRAME_FLAG) {\n sawFinal = true;\n if (offset !== sealedBytes.length) {\n throw new JobEnvelopeError(\n \"Invalid job result: bytes follow final chunk\",\n );\n }\n }\n }\n if (!sawFinal) {\n throw new JobEnvelopeError(\"Invalid job result: missing final chunk\");\n }\n return bodySize;\n}\n\n/**\n * Decrypts a job result and verifies its builder-visible protocol bindings.\n *\n * The builder private key is a `Hex` wallet key. For `expect.version`,\n * `undefined` skips the check while `null` requires a null result version.\n *\n * @param sealedBytes - Raw hybrid sealed-result bytes.\n * @param builderPrivateKey - Builder's wallet private key as hex.\n * @param ecies - Injected ECIES implementation.\n * @param expect - Required job ID and optional scope and version bindings.\n * @returns The validated result when every supplied binding matches.\n * @throws {JobEnvelopeError} If plaintext is malformed, invalid, or a binding differs.\n * @throws If ciphertext decoding or ECIES decryption fails.\n *\n * @example\n * ```ts\n * const sealedBytes = new Uint8Array(await response.arrayBuffer());\n * const result = await openJobResult(sealedBytes, key, ecies, {\n * jobId,\n * scope,\n * });\n * const text = new TextDecoder().decode(result.body);\n * ```\n */\nexport async function openJobResult(\n sealedBytes: Uint8Array,\n builderPrivateKey: Hex,\n ecies: ECIESProvider,\n expect: JobResultExpectation,\n): Promise<JobResult> {\n const bodySize = bufferedResultBodySize(sealedBytes);\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(sealedBytes);\n controller.close();\n },\n });\n const opened = await openJobResultStream(\n stream,\n builderPrivateKey,\n ecies,\n expect,\n );\n const body = new Uint8Array(bodySize);\n let offset = 0;\n await opened.body.pipeTo(\n new WritableStream({\n write(chunk) {\n body.set(chunk, offset);\n offset += chunk.length;\n },\n }),\n );\n if (offset !== body.length) {\n throw new JobEnvelopeError(\"Invalid job result: opened size mismatch\");\n }\n return { ...opened.metadata, body };\n}\n"],"mappings":"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","names":[]}
@@ -2,9 +2,12 @@
2
2
  * ECIES envelopes for encrypted job requests and results.
3
3
  *
4
4
  * Request plaintext is UTF-8 JSON with object keys sorted recursively and
5
- * array order preserved. Result properties follow their interface order. The
6
- * wire ciphertext is base64 of `iv || ephemPub || ct || mac`, as specified by
7
- * the ECIES provider interface. The Gateway hashes raw ciphertext bytes, not
5
+ * array order preserved. Result bodies use a hybrid streaming envelope:
6
+ * ECIES wraps a random AES key, nonce prefix, and result metadata, while fixed
7
+ * chunks of the body are sealed independently with AES-256-GCM. The Request
8
+ * ciphertext is base64 of `iv || ephemPub || ct || mac`, as specified by the
9
+ * ECIES provider interface. Result ciphertext is the raw hybrid byte sequence
10
+ * stored in object storage. The Gateway hashes raw ciphertext bytes, not
8
11
  * plaintext.
9
12
  * The builder verifies the decrypted result's job ID, scope, and version
10
13
  * bindings. The PS worker verifies
@@ -17,6 +20,28 @@
17
20
  import { type Hex } from "viem";
18
21
  import { type ECIESProvider } from "../ecies/interface.js";
19
22
  import { type JobRequest, type JobRequestEnvelope, type JobResult } from "../../protocol/jobs.js";
23
+ /** Current hybrid sealed-result wire-format version. */
24
+ export declare const JOB_RESULT_FORMAT_VERSION = 2;
25
+ /** Plaintext bytes authenticated independently in each result frame. */
26
+ export declare const JOB_RESULT_CHUNK_BYTES: number;
27
+ /** Result fields authenticated for every encrypted body chunk. */
28
+ export type JobResultMetadata = Omit<JobResult, "body">;
29
+ /** Streaming sealer output. Write the header before transformed body frames. */
30
+ export interface SealedJobResultStream {
31
+ header: Uint8Array;
32
+ transform: TransformStream<Uint8Array, Uint8Array>;
33
+ }
34
+ /** Verified metadata and incrementally decrypted result body. */
35
+ export interface OpenedJobResultStream {
36
+ metadata: JobResultMetadata;
37
+ body: ReadableStream<Uint8Array>;
38
+ }
39
+ /** Builder-visible bindings checked before any result body chunk is yielded. */
40
+ export interface JobResultExpectation {
41
+ jobId: string;
42
+ scope?: string;
43
+ version?: string | null;
44
+ }
20
45
  /** A job envelope or result did not match the jobs protocol. */
21
46
  export declare class JobEnvelopeError extends Error {
22
47
  constructor(message: string);
@@ -71,32 +96,29 @@ export declare function sealJobRequest(envelope: JobRequestEnvelope, enclavePubl
71
96
  * @throws If ciphertext decoding or ECIES decryption fails.
72
97
  */
73
98
  export declare function openJobRequest(ciphertext: string, privateKey: Uint8Array, ecies: ECIESProvider): Promise<JobRequestEnvelope>;
99
+ /** Create a bounded-memory hybrid sealer for a job result body. */
100
+ export declare function sealJobResultStream(metadataValue: JobResultMetadata, builderPublicKey: Hex, ecies: ECIESProvider): Promise<SealedJobResultStream>;
74
101
  /**
75
102
  * Encrypts a validated job result for its builder and describes the ciphertext.
76
103
  *
77
- * `hash` is lowercase `0x` SHA-256 of decoded ciphertext bytes, not the
78
- * `sha256:` prefix used by Web3Signed `bodyHash`. `size` is that decoded byte
79
- * length. They equal the Gateway's `resultHash` and `resultSize`.
80
- *
81
- * @param result - Job result to validate and encrypt.
82
- * @param builderPublicKey - Builder wallet public key recorded in the request.
83
- * @param ecies - Injected ECIES implementation.
84
- * @returns Base64 ciphertext plus its Gateway-compatible hash and size.
85
- * @throws {JobEnvelopeError} If the result does not match the protocol schema.
86
- * @throws If ECIES encryption fails or the builder public key is invalid.
104
+ * This compatibility wrapper drives {@link sealJobResultStream} and allocates
105
+ * the final sealed byte array once. `hash` and `size` retain their Gateway
106
+ * object-storage semantics.
87
107
  */
88
108
  export declare function sealJobResult(result: JobResult, builderPublicKey: Hex, ecies: ECIESProvider): Promise<{
89
- ciphertext: string;
109
+ bytes: Uint8Array;
90
110
  hash: Hex;
91
111
  size: number;
92
112
  }>;
113
+ /** Open and authenticate a hybrid job result without buffering its body. */
114
+ export declare function openJobResultStream(bytesStream: ReadableStream<Uint8Array>, builderPrivateKey: Hex, ecies: ECIESProvider, expect: JobResultExpectation): Promise<OpenedJobResultStream>;
93
115
  /**
94
116
  * Decrypts a job result and verifies its builder-visible protocol bindings.
95
117
  *
96
118
  * The builder private key is a `Hex` wallet key. For `expect.version`,
97
119
  * `undefined` skips the check while `null` requires a null result version.
98
120
  *
99
- * @param ciphertext - Base64 `iv || ephemPub || ct || mac` ciphertext.
121
+ * @param sealedBytes - Raw hybrid sealed-result bytes.
100
122
  * @param builderPrivateKey - Builder's wallet private key as hex.
101
123
  * @param ecies - Injected ECIES implementation.
102
124
  * @param expect - Required job ID and optional scope and version bindings.
@@ -106,17 +128,12 @@ export declare function sealJobResult(result: JobResult, builderPublicKey: Hex,
106
128
  *
107
129
  * @example
108
130
  * ```ts
109
- * const result = await openJobResult(
110
- * status.resultCiphertext,
111
- * key,
112
- * ecies,
113
- * { jobId, scope },
114
- * );
115
- * const body = fromBase64(result.body);
131
+ * const sealedBytes = new Uint8Array(await response.arrayBuffer());
132
+ * const result = await openJobResult(sealedBytes, key, ecies, {
133
+ * jobId,
134
+ * scope,
135
+ * });
136
+ * const text = new TextDecoder().decode(result.body);
116
137
  * ```
117
138
  */
118
- export declare function openJobResult(ciphertext: string, builderPrivateKey: Hex, ecies: ECIESProvider, expect: {
119
- jobId: string;
120
- scope?: string;
121
- version?: string | null;
122
- }): Promise<JobResult>;
139
+ export declare function openJobResult(sealedBytes: Uint8Array, builderPrivateKey: Hex, ecies: ECIESProvider, expect: JobResultExpectation): Promise<JobResult>;