@reclaimprotocol/attestor-core 5.0.1-beta.2 → 5.0.1-beta.22

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 (145) hide show
  1. package/browser/resources/attestor-browser.min.mjs +4512 -0
  2. package/lib/avs/abis/avsDirectoryABI.js +338 -341
  3. package/lib/avs/abis/delegationABI.js +1 -4
  4. package/lib/avs/abis/registryABI.js +719 -722
  5. package/lib/avs/client/create-claim-on-avs.js +129 -157
  6. package/lib/avs/config.js +18 -24
  7. package/lib/avs/contracts/ReclaimServiceManager.js +1 -0
  8. package/lib/avs/contracts/common.js +1 -0
  9. package/lib/avs/contracts/factories/ReclaimServiceManager__factory.js +1139 -1156
  10. package/lib/avs/contracts/factories/index.js +4 -4
  11. package/lib/avs/contracts/index.js +2 -6
  12. package/lib/avs/types/index.js +1 -0
  13. package/lib/avs/utils/contracts.js +30 -50
  14. package/lib/avs/utils/register.js +75 -70
  15. package/lib/avs/utils/tasks.js +38 -45
  16. package/lib/client/create-claim.js +402 -431
  17. package/lib/client/tunnels/make-rpc-tcp-tunnel.js +46 -48
  18. package/lib/client/tunnels/make-rpc-tls-tunnel.js +125 -121
  19. package/lib/client/utils/attestor-pool.js +23 -22
  20. package/lib/client/utils/client-socket.js +86 -109
  21. package/lib/client/utils/message-handler.js +79 -89
  22. package/lib/config/index.js +40 -58
  23. package/lib/external-rpc/benchmark.js +61 -74
  24. package/lib/external-rpc/event-bus.js +12 -15
  25. package/lib/external-rpc/handle-incoming-msg.js +216 -225
  26. package/lib/external-rpc/jsc-polyfills/1.js +70 -68
  27. package/lib/external-rpc/jsc-polyfills/2.js +17 -12
  28. package/lib/external-rpc/jsc-polyfills/event.js +10 -15
  29. package/lib/external-rpc/jsc-polyfills/index.js +2 -2
  30. package/lib/external-rpc/jsc-polyfills/ws.js +77 -79
  31. package/lib/external-rpc/setup-browser.js +28 -28
  32. package/lib/external-rpc/setup-jsc.js +17 -17
  33. package/lib/external-rpc/types.js +1 -0
  34. package/lib/external-rpc/utils.js +89 -89
  35. package/lib/external-rpc/zk.js +55 -50
  36. package/lib/index.js +2 -6
  37. package/lib/mechain/abis/governanceABI.js +457 -460
  38. package/lib/mechain/abis/taskABI.js +502 -505
  39. package/lib/mechain/client/create-claim-on-mechain.js +24 -29
  40. package/lib/mechain/constants/index.js +3 -8
  41. package/lib/mechain/types/index.js +1 -0
  42. package/lib/proto/api.js +4200 -4087
  43. package/lib/proto/tee-bundle.js +1261 -1241
  44. package/lib/providers/http/index.js +616 -603
  45. package/lib/providers/http/patch-parse5-tree.js +27 -29
  46. package/lib/providers/http/utils.js +289 -248
  47. package/lib/providers/index.js +3 -6
  48. package/lib/server/create-server.js +89 -91
  49. package/lib/server/handlers/claimTeeBundle.js +231 -211
  50. package/lib/server/handlers/claimTunnel.js +66 -73
  51. package/lib/server/handlers/completeClaimOnChain.js +20 -25
  52. package/lib/server/handlers/createClaimOnChain.js +21 -27
  53. package/lib/server/handlers/createTaskOnMechain.js +40 -50
  54. package/lib/server/handlers/createTunnel.js +85 -90
  55. package/lib/server/handlers/disconnectTunnel.js +4 -7
  56. package/lib/server/handlers/fetchCertificateBytes.js +37 -53
  57. package/lib/server/handlers/index.js +21 -24
  58. package/lib/server/handlers/init.js +27 -28
  59. package/lib/server/handlers/toprf.js +13 -16
  60. package/lib/server/socket.js +97 -100
  61. package/lib/server/tunnels/make-tcp-tunnel.js +161 -186
  62. package/lib/server/utils/apm.js +32 -25
  63. package/lib/server/utils/assert-valid-claim-request.js +305 -334
  64. package/lib/server/utils/config-env.js +2 -2
  65. package/lib/server/utils/dns.js +12 -18
  66. package/lib/server/utils/gcp-attestation.js +233 -181
  67. package/lib/server/utils/generics.d.ts +1 -1
  68. package/lib/server/utils/generics.js +43 -37
  69. package/lib/server/utils/iso.js +253 -256
  70. package/lib/server/utils/keep-alive.js +36 -36
  71. package/lib/server/utils/nitro-attestation.js +295 -220
  72. package/lib/server/utils/oprf-raw.js +48 -55
  73. package/lib/server/utils/process-handshake.js +200 -218
  74. package/lib/server/utils/proxy-session.js +5 -5
  75. package/lib/server/utils/tee-oprf-mpc-verification.js +82 -78
  76. package/lib/server/utils/tee-oprf-verification.js +165 -142
  77. package/lib/server/utils/tee-transcript-reconstruction.js +176 -129
  78. package/lib/server/utils/tee-verification.js +397 -334
  79. package/lib/server/utils/validation.js +30 -37
  80. package/lib/types/bgp.js +1 -0
  81. package/lib/types/claims.js +1 -0
  82. package/lib/types/client.js +1 -0
  83. package/lib/types/general.js +1 -0
  84. package/lib/types/handlers.js +1 -0
  85. package/lib/types/providers.d.ts +3 -2
  86. package/lib/types/providers.gen.js +9 -15
  87. package/lib/types/providers.js +1 -0
  88. package/lib/types/rpc.js +1 -0
  89. package/lib/types/signatures.d.ts +1 -2
  90. package/lib/types/signatures.js +1 -0
  91. package/lib/types/tunnel.js +1 -0
  92. package/lib/types/zk.js +1 -0
  93. package/lib/utils/auth.js +54 -66
  94. package/lib/utils/b64-json.js +15 -15
  95. package/lib/utils/bgp-listener.js +107 -111
  96. package/lib/utils/claims.js +89 -80
  97. package/lib/utils/env.js +13 -17
  98. package/lib/utils/error.js +43 -47
  99. package/lib/utils/generics.js +284 -235
  100. package/lib/utils/http-parser.js +232 -187
  101. package/lib/utils/logger.js +80 -71
  102. package/lib/utils/prepare-packets.js +69 -67
  103. package/lib/utils/redactions.js +163 -121
  104. package/lib/utils/retries.js +22 -24
  105. package/lib/utils/signatures/eth.js +29 -28
  106. package/lib/utils/signatures/index.js +5 -10
  107. package/lib/utils/socket-base.js +84 -88
  108. package/lib/utils/tls.js +28 -28
  109. package/lib/utils/ws.js +19 -19
  110. package/lib/utils/zk.js +542 -582
  111. package/package.json +12 -5
  112. package/lib/external-rpc/global.d.js +0 -0
  113. package/lib/scripts/build-browser.d.ts +0 -1
  114. package/lib/scripts/build-jsc.d.ts +0 -1
  115. package/lib/scripts/build-lib.d.ts +0 -1
  116. package/lib/scripts/check-avs-registration.d.ts +0 -1
  117. package/lib/scripts/check-avs-registration.js +0 -28
  118. package/lib/scripts/fallbacks/crypto.d.ts +0 -1
  119. package/lib/scripts/fallbacks/crypto.js +0 -4
  120. package/lib/scripts/fallbacks/empty.d.ts +0 -3
  121. package/lib/scripts/fallbacks/empty.js +0 -4
  122. package/lib/scripts/fallbacks/re2.d.ts +0 -1
  123. package/lib/scripts/fallbacks/re2.js +0 -7
  124. package/lib/scripts/fallbacks/snarkjs.d.ts +0 -1
  125. package/lib/scripts/fallbacks/snarkjs.js +0 -10
  126. package/lib/scripts/fallbacks/stwo.d.ts +0 -6
  127. package/lib/scripts/fallbacks/stwo.js +0 -159
  128. package/lib/scripts/generate-provider-types.d.ts +0 -5
  129. package/lib/scripts/generate-provider-types.js +0 -101
  130. package/lib/scripts/generate-receipt.d.ts +0 -9
  131. package/lib/scripts/generate-receipt.js +0 -101
  132. package/lib/scripts/generate-toprf-keys.d.ts +0 -1
  133. package/lib/scripts/generate-toprf-keys.js +0 -24
  134. package/lib/scripts/jsc-cli-rpc.d.ts +0 -1
  135. package/lib/scripts/jsc-cli-rpc.js +0 -35
  136. package/lib/scripts/register-avs-operator.d.ts +0 -1
  137. package/lib/scripts/register-avs-operator.js +0 -3
  138. package/lib/scripts/start-server.d.ts +0 -1
  139. package/lib/scripts/start-server.js +0 -11
  140. package/lib/scripts/update-avs-metadata.d.ts +0 -1
  141. package/lib/scripts/update-avs-metadata.js +0 -20
  142. package/lib/scripts/utils.d.ts +0 -1
  143. package/lib/scripts/utils.js +0 -10
  144. package/lib/scripts/whitelist-operator.d.ts +0 -1
  145. package/lib/scripts/whitelist-operator.js +0 -16
@@ -1,354 +1,325 @@
1
- import { areUint8ArraysEqual, concatenateUint8Arrays } from "@reclaimprotocol/tls";
1
+ import { areUint8ArraysEqual, concatenateUint8Arrays } from '@reclaimprotocol/tls';
2
2
  import { ClaimTunnelRequest, TranscriptMessageSenderType } from "../../proto/api.js";
3
3
  import { providers } from "../../providers/index.js";
4
- import { niceParseJsonObject } from "../../server/utils/generics.js";
5
- import { computeOPRFRaw } from "../../server/utils/oprf-raw.js";
6
- import { processHandshake } from "../../server/utils/process-handshake.js";
7
- import { assertValidateProviderParams } from "../../server/utils/validation.js";
8
- import {
9
- AttestorError,
10
- binaryHashToStr,
11
- canonicalStringify,
12
- decryptDirect,
13
- extractApplicationDataFromTranscript,
14
- hashProviderParams,
15
- SIGNATURES,
16
- verifyZkPacket
17
- } from "../../utils/index.js";
4
+ import { niceParseJsonObject } from "./generics.js";
5
+ import { computeOPRFRaw } from "./oprf-raw.js";
6
+ import { processHandshake } from "./process-handshake.js";
7
+ import { assertValidateProviderParams } from "./validation.js";
8
+ import { AttestorError, binaryHashToStr, canonicalStringify, decryptDirect, extractApplicationDataFromTranscript, hashProviderParams, SIGNATURES, verifyZkPacket } from "../../utils/index.js";
18
9
  import { getEngineString } from "../../utils/zk.js";
19
- async function assertValidClaimRequest(request, metadata, logger) {
20
- const {
21
- data,
22
- signatures: { requestSignature } = {},
23
- zkEngine,
24
- fixedServerIV,
25
- fixedClientIV
26
- } = request;
27
- if (!data) {
28
- throw new AttestorError(
29
- "ERROR_INVALID_CLAIM",
30
- "No info provided on claim request"
31
- );
32
- }
33
- if (!requestSignature?.length) {
34
- throw new AttestorError(
35
- "ERROR_INVALID_CLAIM",
36
- "No signature provided on claim request"
37
- );
38
- }
39
- const serialisedReq = ClaimTunnelRequest.encode({ ...request, signatures: void 0 }).finish();
40
- const { verify: verifySig } = SIGNATURES[metadata.signatureType];
41
- const verified = await verifySig(serialisedReq, requestSignature, data.owner);
42
- if (!verified) {
43
- throw new AttestorError(
44
- "ERROR_INVALID_CLAIM",
45
- "Invalid signature on claim request"
46
- );
47
- }
48
- const receipt = await decryptTranscript(
49
- request.transcript,
50
- logger,
51
- getEngineString(zkEngine),
52
- fixedServerIV,
53
- fixedClientIV
54
- );
55
- const reqHost = request.request?.host;
56
- if (receipt.hostname !== reqHost) {
57
- throw new Error(
58
- `Expected server name ${reqHost}, got ${receipt.hostname}`
59
- );
60
- }
61
- const applData = extractApplicationDataFromTranscript(receipt);
62
- const newData = await assertValidProviderTranscript(
63
- applData,
64
- data,
65
- logger,
66
- { version: metadata.clientVersion },
67
- receipt.oprfRawReplacements
68
- );
69
- if (newData !== data) {
70
- logger.info({ newData }, "updated claim info");
71
- }
72
- return newData;
73
- }
74
- async function assertValidProviderTranscript(applData, info, logger, providerCtx, oprfRawReplacements) {
75
- const providerName = info.provider;
76
- const provider = providers[providerName];
77
- if (!provider) {
78
- throw new AttestorError(
79
- "ERROR_INVALID_CLAIM",
80
- `Unsupported provider: ${providerName}`
81
- );
82
- }
83
- let params = niceParseJsonObject(info.parameters, "params");
84
- const ctx = niceParseJsonObject(info.context, "context");
85
- if (oprfRawReplacements?.length) {
86
- let strParams = canonicalStringify(params) ?? "{}";
87
- for (const { originalText, nullifierText } of oprfRawReplacements) {
88
- strParams = strParams.replaceAll(originalText, nullifierText);
10
+ /**
11
+ * Asserts that the claim request is valid.
12
+ *
13
+ * 1. We begin by verifying the signature of the claim request.
14
+ * 2. Next, we produce the transcript of the TLS exchange
15
+ * from the proofs provided by the client.
16
+ * 3. We then pull the provider the client is trying to claim
17
+ * from
18
+ * 4. We then use the provider's verification function to verify
19
+ * whether the claim is valid.
20
+ *
21
+ * If any of these steps fail, we throw an error.
22
+ */
23
+ export async function assertValidClaimRequest(request, metadata, logger) {
24
+ const { data, signatures: { requestSignature } = {}, zkEngine, fixedServerIV, fixedClientIV } = request;
25
+ if (!data) {
26
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'No info provided on claim request');
27
+ }
28
+ if (!requestSignature?.length) {
29
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'No signature provided on claim request');
30
+ }
31
+ // verify request signature
32
+ const serialisedReq = ClaimTunnelRequest
33
+ .encode({ ...request, signatures: undefined })
34
+ .finish();
35
+ const { verify: verifySig } = SIGNATURES[metadata.signatureType];
36
+ const verified = await verifySig(serialisedReq, requestSignature, data.owner);
37
+ if (!verified) {
38
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'Invalid signature on claim request');
39
+ }
40
+ const receipt = await decryptTranscript(request.transcript, logger, getEngineString(zkEngine), fixedServerIV, fixedClientIV);
41
+ const reqHost = request.request?.host;
42
+ if (receipt.hostname !== reqHost) {
43
+ throw new Error(`Expected server name ${reqHost}, got ${receipt.hostname}`);
89
44
  }
90
- params = JSON.parse(strParams);
91
- info.parameters = strParams;
92
- logger.debug(
93
- { replacements: oprfRawReplacements.length },
94
- "applied oprf-raw parameter replacements"
95
- );
96
- }
97
- assertValidateProviderParams(providerName, params);
98
- const rslt = await provider.assertValidProviderReceipt({
99
- receipt: applData,
100
- params,
101
- logger,
102
- ctx: providerCtx
103
- });
104
- ctx.providerHash = hashProviderParams(params);
105
- const extractedParameters = rslt?.extractedParameters || {};
106
- if (Object.keys(extractedParameters).length) {
107
- ctx.extractedParameters = extractedParameters;
108
- }
109
- info.context = canonicalStringify(ctx) ?? "";
110
- return info;
45
+ // get all application data messages
46
+ const applData = extractApplicationDataFromTranscript(receipt);
47
+ const newData = await assertValidProviderTranscript(applData, data, logger, { version: metadata.clientVersion }, receipt.oprfRawReplacements);
48
+ if (newData !== data) {
49
+ logger.info({ newData }, 'updated claim info');
50
+ }
51
+ return newData;
111
52
  }
112
- function assertTranscriptsMatch(clientTranscript, tunnelTranscript) {
113
- const clientSends = concatenateUint8Arrays(
114
- clientTranscript.filter((m) => m.sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT).map((m) => m.message)
115
- );
116
- const tunnelSends = concatenateUint8Arrays(
117
- tunnelTranscript.filter((m) => m.sender === "client").map((m) => m.message)
118
- );
119
- if (!areUint8ArraysEqual(clientSends, tunnelSends)) {
120
- throw AttestorError.badRequest(
121
- "Outgoing messages from client do not match the tunnel transcript"
122
- );
123
- }
124
- const clientRecvs = concatenateUint8Arrays(
125
- clientTranscript.filter((m) => m.sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER).map((m) => m.message)
126
- );
127
- const tunnelRecvs = concatenateUint8Arrays(
128
- tunnelTranscript.filter((m) => m.sender === "server").map((m) => m.message)
129
- ).slice(0, clientRecvs.length);
130
- if (!areUint8ArraysEqual(clientRecvs, tunnelRecvs)) {
131
- throw AttestorError.badRequest(
132
- "Incoming messages from server do not match the tunnel transcript"
133
- );
134
- }
53
+ /**
54
+ * Verify that the transcript contains a valid claim
55
+ * for the provider.
56
+ */
57
+ export async function assertValidProviderTranscript(applData, info, logger, providerCtx, oprfRawReplacements) {
58
+ const providerName = info.provider;
59
+ const provider = providers[providerName];
60
+ if (!provider) {
61
+ throw new AttestorError('ERROR_INVALID_CLAIM', `Unsupported provider: ${providerName}`);
62
+ }
63
+ let params = niceParseJsonObject(info.parameters, 'params');
64
+ const ctx = niceParseJsonObject(info.context, 'context');
65
+ // Apply oprf-raw replacements to parameters (server-side OPRF)
66
+ if (oprfRawReplacements?.length) {
67
+ let strParams = canonicalStringify(params) ?? '{}';
68
+ for (const { originalText, nullifierText } of oprfRawReplacements) {
69
+ strParams = strParams.replaceAll(originalText, nullifierText);
70
+ }
71
+ params = JSON.parse(strParams);
72
+ // Update info.parameters with replaced values
73
+ info.parameters = strParams;
74
+ logger.debug({ replacements: oprfRawReplacements.length }, 'applied oprf-raw parameter replacements');
75
+ }
76
+ assertValidateProviderParams(providerName, params);
77
+ const rslt = await provider.assertValidProviderReceipt({
78
+ receipt: applData,
79
+ params,
80
+ logger,
81
+ ctx: providerCtx
82
+ });
83
+ ctx.providerHash = hashProviderParams(params);
84
+ const extractedParameters = rslt?.extractedParameters || {};
85
+ if (Object.keys(extractedParameters).length) {
86
+ ctx.extractedParameters = extractedParameters;
87
+ }
88
+ info.context = canonicalStringify(ctx) ?? '';
89
+ return info;
135
90
  }
136
- async function decryptTranscript(transcript, logger, zkEngine, serverIV, clientIV) {
137
- const {
138
- tlsVersion,
139
- cipherSuite,
140
- hostname,
141
- nextMsgIndex
142
- } = await processHandshake(transcript, logger);
143
- let clientRecordNumber = tlsVersion === "TLS1_3" ? -1 : 0;
144
- let serverRecordNumber = clientRecordNumber;
145
- transcript = transcript.slice(nextMsgIndex);
146
- const overshotMap = {};
147
- const decryptedTranscript = [];
148
- const oprfRawReplacements = [];
149
- const pendingOprfRaw = {};
150
- for (const [i, {
151
- sender,
152
- message,
153
- reveal: { zkReveal, directReveal } = {}
154
- }] of transcript.entries()) {
155
- try {
156
- await decryptMessage(sender, message, directReveal, zkReveal, i);
157
- } catch (error) {
158
- const err = new AttestorError(
159
- "ERROR_INVALID_CLAIM",
160
- `error in handling packet at idx ${i}: ${error}`,
161
- { packetIdx: i, error }
162
- );
163
- if (error.stack) {
164
- err.stack = error.stack;
165
- }
166
- throw err;
91
+ /**
92
+ * Verify that the transcript provided by the client
93
+ * matches the transcript of the tunnel, the server
94
+ * has created.
95
+ */
96
+ export function assertTranscriptsMatch(clientTranscript, tunnelTranscript) {
97
+ const clientSends = concatenateUint8Arrays(clientTranscript
98
+ .filter(m => m.sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT)
99
+ .map(m => m.message));
100
+ const tunnelSends = concatenateUint8Arrays(tunnelTranscript
101
+ .filter(m => m.sender === 'client')
102
+ .map(m => m.message));
103
+ if (!areUint8ArraysEqual(clientSends, tunnelSends)) {
104
+ throw AttestorError.badRequest('Outgoing messages from client do not match the tunnel transcript');
167
105
  }
168
- }
169
- const remainingPending = Object.keys(pendingOprfRaw);
170
- if (remainingPending.length) {
171
- throw new AttestorError(
172
- "ERROR_INVALID_CLAIM",
173
- `oprf-raw cross-block markers incomplete: pending for packets ${remainingPending.join(", ")}`
174
- );
175
- }
176
- return {
177
- transcript: decryptedTranscript,
178
- hostname,
179
- tlsVersion,
180
- oprfRawReplacements: oprfRawReplacements.length ? oprfRawReplacements : void 0
181
- };
182
- async function decryptMessage(sender, message, directReveal, zkReveal, i) {
183
- const isServer = sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER;
184
- const recordHeader = message.slice(0, 5);
185
- const content = getWithoutHeader(message);
186
- if (isServer) {
187
- serverRecordNumber++;
188
- } else {
189
- clientRecordNumber++;
106
+ const clientRecvs = concatenateUint8Arrays(clientTranscript
107
+ .filter(m => m.sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER)
108
+ .map(m => m.message));
109
+ const tunnelRecvs = concatenateUint8Arrays(tunnelTranscript
110
+ .filter(m => m.sender === 'server')
111
+ .map(m => m.message))
112
+ // We only need to compare the first N messages
113
+ // that the client claims to have received
114
+ // the rest are not relevant -- so even if they're
115
+ // not present in the tunnel transcript, it's fine
116
+ .slice(0, clientRecvs.length);
117
+ if (!areUint8ArraysEqual(clientRecvs, tunnelRecvs)) {
118
+ throw AttestorError.badRequest('Incoming messages from server do not match the tunnel transcript');
190
119
  }
191
- let redacted = true;
192
- let plaintext = void 0;
193
- let plaintextLength;
194
- if (directReveal?.key?.length) {
195
- const result = await decryptDirect(
196
- directReveal,
197
- cipherSuite,
198
- recordHeader,
199
- tlsVersion,
200
- content
201
- );
202
- plaintext = result.plaintext;
203
- redacted = false;
204
- plaintextLength = plaintext.length;
205
- } else if (zkReveal?.proofs?.length) {
206
- const iv = sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER ? serverIV : clientIV;
207
- const recordNumber = isServer ? serverRecordNumber : clientRecordNumber;
208
- const result = await verifyZkPacket(
209
- {
210
- ciphertext: content,
211
- zkReveal,
212
- iv,
213
- recordNumber,
214
- toprfOvershotNullifier: overshotMap[i]?.data,
215
- getNextPacket(overshot) {
216
- const nextIdx = transcript.findIndex((t, j) => t.sender === sender && j > i);
217
- if (nextIdx < 0) {
218
- return;
120
+ }
121
+ export async function decryptTranscript(transcript, logger, zkEngine, serverIV, clientIV) {
122
+ const { tlsVersion, cipherSuite, hostname, nextMsgIndex } = await processHandshake(transcript, logger);
123
+ // TLS 1.3 has already one record encrypted at this point
124
+ let clientRecordNumber = tlsVersion === 'TLS1_3' ? -1 : 0;
125
+ let serverRecordNumber = clientRecordNumber;
126
+ transcript = transcript.slice(nextMsgIndex);
127
+ const overshotMap = {};
128
+ const decryptedTranscript = [];
129
+ const oprfRawReplacements = [];
130
+ // Track pending oprf-raw markers that span multiple packets
131
+ // keyed by packet index that will receive the overshot data
132
+ const pendingOprfRaw = {};
133
+ for (const [i, { sender, message, reveal: { zkReveal, directReveal } = {} }] of transcript.entries()) {
134
+ try {
135
+ //start with first message after last handshake message
136
+ await decryptMessage(sender, message, directReveal, zkReveal, i);
137
+ }
138
+ catch (error) {
139
+ const err = new AttestorError('ERROR_INVALID_CLAIM', `error in handling packet at idx ${i}: ${error}`, { packetIdx: i, error });
140
+ if (error.stack) {
141
+ err.stack = error.stack;
219
142
  }
220
- overshotMap[nextIdx] = { data: overshot };
221
- return getWithoutHeader(transcript[nextIdx].message);
222
- },
223
- logger,
224
- cipherSuite,
225
- zkEngine
143
+ throw err;
144
+ }
145
+ }
146
+ // Fail if any oprf-raw markers remain incomplete
147
+ const remainingPending = Object.keys(pendingOprfRaw);
148
+ if (remainingPending.length) {
149
+ throw new AttestorError('ERROR_INVALID_CLAIM', `oprf-raw cross-block markers incomplete: pending for packets ${remainingPending.join(', ')}`);
150
+ }
151
+ return {
152
+ transcript: decryptedTranscript,
153
+ hostname: hostname,
154
+ tlsVersion: tlsVersion,
155
+ oprfRawReplacements: oprfRawReplacements.length ? oprfRawReplacements : undefined
156
+ };
157
+ async function decryptMessage(sender, message, directReveal, zkReveal, i) {
158
+ const isServer = sender === TranscriptMessageSenderType
159
+ .TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER;
160
+ const recordHeader = message.slice(0, 5);
161
+ const content = getWithoutHeader(message);
162
+ if (isServer) {
163
+ serverRecordNumber++;
226
164
  }
227
- );
228
- plaintext = result.redactedPlaintext;
229
- const pendingForThis = pendingOprfRaw[i];
230
- if (pendingForThis && zkReveal?.overshotOprfRawLength) {
231
- const overshootLen = zkReveal.overshotOprfRawLength;
232
- const overshootData = plaintext.slice(0, overshootLen);
233
- const fullData = concatenateUint8Arrays([
234
- pendingForThis.partialData,
235
- overshootData
236
- ]);
237
- const expectedLen = pendingForThis.dataLocation.length;
238
- if (fullData.length !== expectedLen) {
239
- throw new AttestorError(
240
- "ERROR_INVALID_CLAIM",
241
- `oprf-raw cross-block length mismatch: got ${fullData.length}, expected ${expectedLen}`
242
- );
165
+ else {
166
+ clientRecordNumber++;
243
167
  }
244
- const oprfResults = await computeOPRFRaw(
245
- fullData,
246
- [{ dataLocation: { fromIndex: 0, length: fullData.length } }],
247
- logger
248
- );
249
- if (oprfResults.length) {
250
- const { nullifier } = oprfResults[0];
251
- const originalText = new TextDecoder().decode(fullData);
252
- const nullifierStr = binaryHashToStr(nullifier, fullData.length);
253
- oprfRawReplacements.push({ originalText, nullifierText: nullifierStr });
254
- const nullifierBytes = new TextEncoder().encode(nullifierStr);
255
- const overshootNullifier = nullifierBytes.slice(pendingForThis.partialData.length);
256
- plaintext.set(overshootNullifier, 0);
257
- const prevPkt = decryptedTranscript[pendingForThis.originPktIdx];
258
- if (prevPkt) {
259
- const firstPartNullifier = nullifierBytes.slice(0, pendingForThis.partialData.length);
260
- prevPkt.message.set(firstPartNullifier, pendingForThis.dataLocation.fromIndex);
261
- }
168
+ let redacted = true;
169
+ let plaintext = undefined;
170
+ let plaintextLength;
171
+ if (directReveal?.key?.length) {
172
+ const result = await decryptDirect(directReveal, cipherSuite, recordHeader, tlsVersion, content);
173
+ plaintext = result.plaintext;
174
+ redacted = false;
175
+ plaintextLength = plaintext.length;
262
176
  }
263
- delete pendingOprfRaw[i];
264
- }
265
- if (result.oprfRawMarkers?.length) {
266
- const { markersThisPacket, pendingMarker } = separateOprfRawMarkers(
267
- result.oprfRawMarkers,
268
- plaintext.length,
269
- () => transcript.findIndex((t, j) => t.sender === sender && j > i),
270
- decryptedTranscript.length,
271
- logger
272
- );
273
- if (pendingMarker) {
274
- pendingMarker.pending.partialData.set(
275
- plaintext.slice(pendingMarker.pending.dataLocation.fromIndex)
276
- );
277
- pendingOprfRaw[pendingMarker.nextIdx] = pendingMarker.pending;
177
+ else if (zkReveal?.proofs?.length) {
178
+ const iv = sender === TranscriptMessageSenderType
179
+ .TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER
180
+ ? serverIV
181
+ : clientIV;
182
+ const recordNumber = isServer
183
+ ? serverRecordNumber
184
+ : clientRecordNumber;
185
+ const result = await verifyZkPacket({
186
+ ciphertext: content,
187
+ zkReveal,
188
+ iv,
189
+ recordNumber,
190
+ toprfOvershotNullifier: overshotMap[i]?.data,
191
+ getNextPacket(overshot) {
192
+ const nextIdx = transcript
193
+ .findIndex((t, j) => t.sender === sender && j > i);
194
+ if (nextIdx < 0) {
195
+ return;
196
+ }
197
+ overshotMap[nextIdx] = { data: overshot };
198
+ return getWithoutHeader(transcript[nextIdx].message);
199
+ },
200
+ logger,
201
+ cipherSuite,
202
+ zkEngine: zkEngine,
203
+ });
204
+ plaintext = result.redactedPlaintext;
205
+ // Handle pending oprf-raw data from previous packet (cross-block)
206
+ const pendingForThis = pendingOprfRaw[i];
207
+ if (pendingForThis && zkReveal?.overshotOprfRawLength) {
208
+ const overshootLen = zkReveal.overshotOprfRawLength;
209
+ // Collect the overshot plaintext from this packet
210
+ const overshootData = plaintext.slice(0, overshootLen);
211
+ const fullData = concatenateUint8Arrays([
212
+ pendingForThis.partialData,
213
+ overshootData
214
+ ]);
215
+ // Verify accumulated length matches declared length
216
+ const expectedLen = pendingForThis.dataLocation.length;
217
+ if (fullData.length !== expectedLen) {
218
+ throw new AttestorError('ERROR_INVALID_CLAIM', `oprf-raw cross-block length mismatch: got ${fullData.length}, expected ${expectedLen}`);
219
+ }
220
+ // Compute OPRF for the complete data
221
+ const oprfResults = await computeOPRFRaw(fullData, [{ dataLocation: { fromIndex: 0, length: fullData.length } }], logger);
222
+ if (oprfResults.length) {
223
+ const { nullifier } = oprfResults[0];
224
+ const originalText = new TextDecoder().decode(fullData);
225
+ const nullifierStr = binaryHashToStr(nullifier, fullData.length);
226
+ oprfRawReplacements.push({ originalText, nullifierText: nullifierStr });
227
+ // Replace in original packet (handled when that packet was processed)
228
+ // Replace in current packet
229
+ const nullifierBytes = new TextEncoder().encode(nullifierStr);
230
+ const overshootNullifier = nullifierBytes.slice(pendingForThis.partialData.length);
231
+ plaintext.set(overshootNullifier, 0);
232
+ // Also need to update the previous packet's plaintext
233
+ // The previous packet has the first part of the nullifier
234
+ const prevPkt = decryptedTranscript[pendingForThis.originPktIdx];
235
+ if (prevPkt) {
236
+ const firstPartNullifier = nullifierBytes.slice(0, pendingForThis.partialData.length);
237
+ prevPkt.message.set(firstPartNullifier, pendingForThis.dataLocation.fromIndex);
238
+ }
239
+ }
240
+ delete pendingOprfRaw[i];
241
+ }
242
+ // Process oprf-raw markers: compute OPRF server-side and replace with nullifier
243
+ if (result.oprfRawMarkers?.length) {
244
+ const { markersThisPacket, pendingMarker } = separateOprfRawMarkers(result.oprfRawMarkers, plaintext.length, () => transcript.findIndex((t, j) => t.sender === sender && j > i), decryptedTranscript.length, logger);
245
+ // Store pending marker for cross-block processing
246
+ if (pendingMarker) {
247
+ // Copy partial data from plaintext
248
+ pendingMarker.pending.partialData.set(plaintext.slice(pendingMarker.pending.dataLocation.fromIndex));
249
+ pendingOprfRaw[pendingMarker.nextIdx] = pendingMarker.pending;
250
+ }
251
+ // Process markers that fit in this packet
252
+ if (markersThisPacket.length) {
253
+ const pt = plaintext;
254
+ const oprfResults = await computeOPRFRaw(pt, markersThisPacket, logger);
255
+ // Capture all original texts BEFORE any replacements
256
+ // to avoid reading corrupted data when markers are adjacent
257
+ const originalTexts = oprfResults.map(({ dataLocation }) => new TextDecoder().decode(pt.slice(dataLocation.fromIndex, dataLocation.fromIndex + dataLocation.length)));
258
+ // Now replace plaintext at marker positions with nullifier string
259
+ for (const [idx, { dataLocation, nullifier }] of oprfResults.entries()) {
260
+ const originalText = originalTexts[idx];
261
+ const nullifierStr = binaryHashToStr(nullifier, dataLocation.length);
262
+ oprfRawReplacements.push({ originalText, nullifierText: nullifierStr });
263
+ const nullifierBytes = new TextEncoder().encode(nullifierStr);
264
+ pt.set(nullifierBytes, dataLocation.fromIndex);
265
+ }
266
+ }
267
+ }
268
+ redacted = false;
269
+ plaintextLength = plaintext.length;
278
270
  }
279
- if (markersThisPacket.length) {
280
- const pt = plaintext;
281
- const oprfResults = await computeOPRFRaw(pt, markersThisPacket, logger);
282
- const originalTexts = oprfResults.map(({ dataLocation }) => new TextDecoder().decode(
283
- pt.slice(dataLocation.fromIndex, dataLocation.fromIndex + dataLocation.length)
284
- ));
285
- for (const [idx, { dataLocation, nullifier }] of oprfResults.entries()) {
286
- const originalText = originalTexts[idx];
287
- const nullifierStr = binaryHashToStr(nullifier, dataLocation.length);
288
- oprfRawReplacements.push({ originalText, nullifierText: nullifierStr });
289
- const nullifierBytes = new TextEncoder().encode(nullifierStr);
290
- pt.set(nullifierBytes, dataLocation.fromIndex);
291
- }
271
+ else {
272
+ plaintext = content;
273
+ plaintextLength = plaintext.length;
292
274
  }
293
- }
294
- redacted = false;
295
- plaintextLength = plaintext.length;
296
- } else {
297
- plaintext = content;
298
- plaintextLength = plaintext.length;
275
+ decryptedTranscript.push({
276
+ sender: sender === TranscriptMessageSenderType
277
+ .TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT
278
+ ? 'client'
279
+ : 'server',
280
+ redacted,
281
+ message: plaintext,
282
+ recordHeader,
283
+ plaintextLength,
284
+ });
299
285
  }
300
- decryptedTranscript.push({
301
- sender: sender === TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT ? "client" : "server",
302
- redacted,
303
- message: plaintext,
304
- recordHeader,
305
- plaintextLength
306
- });
307
- }
308
286
  }
309
- function getWithoutHeader(message) {
310
- return message.slice(5);
287
+ export function getWithoutHeader(message) {
288
+ // strip the record header (xx 03 03 xx xx)
289
+ return message.slice(5);
311
290
  }
291
+ /**
292
+ * Separate oprf-raw markers into those that fit in current packet
293
+ * vs those that span to the next packet
294
+ */
312
295
  function separateOprfRawMarkers(markers, plaintextLength, findNextPacketIdx, currentTranscriptLength, logger) {
313
- const markersThisPacket = [];
314
- let pendingMarker;
315
- for (const marker of markers) {
316
- const dataLocation = marker.dataLocation;
317
- if (!dataLocation) {
318
- continue;
319
- }
320
- const { fromIndex, length } = dataLocation;
321
- const endInPacket = fromIndex + length;
322
- if (endInPacket <= plaintextLength) {
323
- markersThisPacket.push({ dataLocation });
324
- continue;
325
- }
326
- const nextIdx = findNextPacketIdx();
327
- if (nextIdx < 0) {
328
- throw new AttestorError(
329
- "ERROR_INVALID_CLAIM",
330
- "oprf-raw marker spans packets but no next packet found"
331
- );
296
+ const markersThisPacket = [];
297
+ let pendingMarker;
298
+ for (const marker of markers) {
299
+ const dataLocation = marker.dataLocation;
300
+ if (!dataLocation) {
301
+ continue;
302
+ }
303
+ const { fromIndex, length } = dataLocation;
304
+ const endInPacket = fromIndex + length;
305
+ if (endInPacket <= plaintextLength) {
306
+ markersThisPacket.push({ dataLocation });
307
+ continue;
308
+ }
309
+ // Spans to next packet
310
+ const nextIdx = findNextPacketIdx();
311
+ if (nextIdx < 0) {
312
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'oprf-raw marker spans packets but no next packet found');
313
+ }
314
+ pendingMarker = {
315
+ nextIdx,
316
+ pending: {
317
+ partialData: new Uint8Array(plaintextLength - fromIndex),
318
+ dataLocation: { fromIndex, length },
319
+ originPktIdx: currentTranscriptLength
320
+ }
321
+ };
322
+ logger.debug({ fromIndex, length, partialLen: plaintextLength - fromIndex, nextIdx }, 'oprf-raw marker spans packets, storing partial data');
332
323
  }
333
- pendingMarker = {
334
- nextIdx,
335
- pending: {
336
- partialData: new Uint8Array(plaintextLength - fromIndex),
337
- dataLocation: { fromIndex, length },
338
- originPktIdx: currentTranscriptLength
339
- }
340
- };
341
- logger.debug(
342
- { fromIndex, length, partialLen: plaintextLength - fromIndex, nextIdx },
343
- "oprf-raw marker spans packets, storing partial data"
344
- );
345
- }
346
- return { markersThisPacket, pendingMarker };
324
+ return { markersThisPacket, pendingMarker };
347
325
  }
348
- export {
349
- assertTranscriptsMatch,
350
- assertValidClaimRequest,
351
- assertValidProviderTranscript,
352
- decryptTranscript,
353
- getWithoutHeader
354
- };