@reclaimprotocol/attestor-core 5.0.3 → 5.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +660 -660
  2. package/browser/resources/attestor-browser.min.mjs +31 -31
  3. package/lib/avs/abis/avsDirectoryABI.js +340 -0
  4. package/lib/avs/abis/delegationABI.js +1 -0
  5. package/lib/avs/abis/registryABI.js +725 -0
  6. package/lib/avs/client/create-claim-on-avs.js +138 -0
  7. package/lib/avs/config.js +20 -0
  8. package/lib/avs/contracts/ReclaimServiceManager.js +1 -0
  9. package/lib/avs/contracts/common.js +1 -0
  10. package/lib/avs/contracts/factories/ReclaimServiceManager__factory.js +1169 -0
  11. package/lib/avs/contracts/factories/index.js +4 -0
  12. package/lib/avs/contracts/index.js +2 -0
  13. package/lib/avs/types/index.js +1 -0
  14. package/lib/avs/utils/contracts.js +33 -0
  15. package/lib/avs/utils/register.js +78 -0
  16. package/lib/avs/utils/tasks.js +40 -0
  17. package/lib/client/create-claim.js +433 -0
  18. package/lib/client/index.js +3 -0
  19. package/lib/client/tunnels/make-rpc-tcp-tunnel.js +51 -0
  20. package/lib/client/tunnels/make-rpc-tls-tunnel.js +131 -0
  21. package/lib/client/utils/attestor-pool.js +25 -0
  22. package/lib/client/utils/client-socket.js +98 -0
  23. package/lib/client/utils/message-handler.js +87 -0
  24. package/lib/config/index.d.ts +2 -2
  25. package/lib/config/index.js +44 -0
  26. package/lib/external-rpc/benchmark.js +69 -0
  27. package/lib/external-rpc/event-bus.js +14 -0
  28. package/lib/external-rpc/handle-incoming-msg.js +233 -0
  29. package/lib/external-rpc/index.js +101 -54
  30. package/lib/external-rpc/jsc-polyfills/1.js +82 -0
  31. package/lib/external-rpc/jsc-polyfills/2.js +20 -0
  32. package/lib/external-rpc/jsc-polyfills/event.js +14 -0
  33. package/lib/external-rpc/jsc-polyfills/index.js +2 -0
  34. package/lib/external-rpc/jsc-polyfills/ws.js +81 -0
  35. package/lib/external-rpc/setup-browser.js +33 -0
  36. package/lib/external-rpc/setup-jsc.js +22 -0
  37. package/lib/external-rpc/types.js +1 -0
  38. package/lib/external-rpc/utils.js +100 -0
  39. package/lib/external-rpc/zk.js +63 -0
  40. package/lib/index.js +121 -72
  41. package/lib/mechain/abis/governanceABI.js +458 -0
  42. package/lib/mechain/abis/taskABI.js +509 -0
  43. package/lib/mechain/client/create-claim-on-mechain.js +28 -0
  44. package/lib/mechain/client/index.js +1 -0
  45. package/lib/mechain/constants/index.js +3 -0
  46. package/lib/mechain/index.js +2 -0
  47. package/lib/mechain/types/index.js +1 -0
  48. package/lib/proto/api.d.ts +2 -0
  49. package/lib/proto/api.js +4273 -0
  50. package/lib/proto/tee-bundle.js +1316 -0
  51. package/lib/providers/http/index.js +658 -0
  52. package/lib/providers/http/patch-parse5-tree.js +33 -0
  53. package/lib/providers/http/utils.js +324 -0
  54. package/lib/providers/index.js +4 -0
  55. package/lib/scripts/fetch-ec2-metadata.d.ts +1 -0
  56. package/lib/server/create-server.js +103 -0
  57. package/lib/server/handlers/claimTeeBundle.js +252 -0
  58. package/lib/server/handlers/claimTunnel.js +73 -0
  59. package/lib/server/handlers/completeClaimOnChain.js +22 -0
  60. package/lib/server/handlers/createClaimOnChain.js +26 -0
  61. package/lib/server/handlers/createTaskOnMechain.js +47 -0
  62. package/lib/server/handlers/createTunnel.js +93 -0
  63. package/lib/server/handlers/disconnectTunnel.js +5 -0
  64. package/lib/server/handlers/fetchCertificateBytes.js +41 -0
  65. package/lib/server/handlers/index.js +22 -0
  66. package/lib/server/handlers/init.js +32 -0
  67. package/lib/server/handlers/toprf.js +16 -0
  68. package/lib/server/index.js +4 -0
  69. package/lib/server/socket.js +109 -0
  70. package/lib/server/tunnels/make-tcp-tunnel.js +177 -0
  71. package/lib/server/utils/apm.js +36 -0
  72. package/lib/server/utils/assert-valid-claim-request.d.ts +1 -1
  73. package/lib/server/utils/assert-valid-claim-request.js +204 -0
  74. package/lib/server/utils/config-env.js +4 -0
  75. package/lib/server/utils/dns.js +18 -0
  76. package/lib/server/utils/gcp-attestation.js +289 -0
  77. package/lib/server/utils/generics.d.ts +1 -0
  78. package/lib/server/utils/generics.js +51 -0
  79. package/lib/server/utils/iso.js +256 -0
  80. package/lib/server/utils/keep-alive.js +38 -0
  81. package/lib/server/utils/nitro-attestation.js +325 -0
  82. package/lib/server/utils/process-handshake.js +215 -0
  83. package/lib/server/utils/proxy-session.js +6 -0
  84. package/lib/server/utils/tee-oprf-mpc-verification.js +90 -0
  85. package/lib/server/utils/tee-oprf-verification.js +174 -0
  86. package/lib/server/utils/tee-transcript-reconstruction.js +187 -0
  87. package/lib/server/utils/tee-verification.js +421 -0
  88. package/lib/server/utils/validation.js +38 -0
  89. package/lib/types/bgp.js +1 -0
  90. package/lib/types/claims.js +1 -0
  91. package/lib/types/client.js +1 -0
  92. package/lib/types/general.js +1 -0
  93. package/lib/types/handlers.js +1 -0
  94. package/lib/types/index.js +10 -0
  95. package/lib/types/providers.d.ts +1 -0
  96. package/lib/types/providers.gen.js +10 -0
  97. package/lib/types/providers.js +1 -0
  98. package/lib/types/rpc.js +1 -0
  99. package/lib/types/signatures.js +1 -0
  100. package/lib/types/tunnel.js +1 -0
  101. package/lib/types/zk.js +1 -0
  102. package/lib/utils/auth.js +59 -0
  103. package/lib/utils/b64-json.js +17 -0
  104. package/lib/utils/bgp-listener.js +119 -0
  105. package/lib/utils/claims.js +99 -0
  106. package/lib/utils/env.js +15 -0
  107. package/lib/utils/error.js +50 -0
  108. package/lib/utils/generics.js +317 -0
  109. package/lib/utils/http-parser.d.ts +2 -1
  110. package/lib/utils/http-parser.js +246 -0
  111. package/lib/utils/index.js +13 -0
  112. package/lib/utils/logger.js +91 -0
  113. package/lib/utils/prepare-packets.js +62 -0
  114. package/lib/utils/redactions.js +148 -0
  115. package/lib/utils/retries.js +24 -0
  116. package/lib/utils/signatures/eth.js +29 -0
  117. package/lib/utils/signatures/index.js +7 -0
  118. package/lib/utils/socket-base.js +90 -0
  119. package/lib/utils/tls.js +58 -0
  120. package/lib/utils/ws.js +22 -0
  121. package/lib/utils/zk.js +577 -0
  122. package/package.json +3 -2
@@ -0,0 +1,577 @@
1
+ import { concatenateUint8Arrays, crypto, generateIV } from '@reclaimprotocol/tls';
2
+ import { ceilToBlockSizeMultiple, CONFIG as ZK_CONFIG, generateProof, getBlockSizeBytes, makeLocalFileFetch, makeRemoteFileFetch, verifyProof } from '@reclaimprotocol/zk-symmetric-crypto';
3
+ import { makeGnarkOPRFOperator, makeGnarkZkOperator, } from '@reclaimprotocol/zk-symmetric-crypto/gnark';
4
+ import { makeSnarkJsZKOperator } from '@reclaimprotocol/zk-symmetric-crypto/snarkjs';
5
+ import PQueue from 'p-queue';
6
+ import { DEFAULT_REMOTE_FILE_FETCH_BASE_URL, DEFAULT_ZK_CONCURRENCY, TOPRF_DOMAIN_SEPARATOR } from "../config/index.js";
7
+ import { ZKProofEngine } from "../proto/api.js";
8
+ import { detectEnvironment, getEnvVariable } from "./env.js";
9
+ import { AttestorError } from "./error.js";
10
+ import { getPureCiphertext, getRecordIV, getZkAlgorithmForCipherSuite, isTls13Suite, strToUint8Array } from "./generics.js";
11
+ import { logger as LOGGER } from "./logger.js";
12
+ import { binaryHashToStr, isFullyRedacted, isRedactionCongruent, REDACTION_CHAR_CODE } from "./redactions.js";
13
+ const ZK_CONCURRENCY = +(getEnvVariable('ZK_CONCURRENCY') || DEFAULT_ZK_CONCURRENCY);
14
+ export async function makeZkProofGenerator({ zkOperators, oprfOperators, logger = LOGGER, zkProofConcurrency = ZK_CONCURRENCY, cipherSuite, zkEngine = 'snarkjs' }) {
15
+ const zkQueue = new PQueue({ concurrency: zkProofConcurrency, autoStart: true });
16
+ const packetsToProve = [];
17
+ logger = logger.child({ module: 'zk', zkEngine });
18
+ let zkProofsToGen = 0;
19
+ return {
20
+ /**
21
+ * Adds the given packet to the list of packets to
22
+ * generate ZK proofs for.
23
+ *
24
+ * Call `generateProofs()` to finally generate the proofs
25
+ */
26
+ async addPacketToProve(packet, { redactedPlaintext, toprfs = [], overshotToprfFromPrevBlock }, onGeneratedProofs, getNextPacket) {
27
+ if (packet.type === 'plaintext') {
28
+ throw new Error('Cannot generate proof for plaintext');
29
+ }
30
+ const alg = getZkAlgorithmForCipherSuite(cipherSuite);
31
+ const chunkSizeBytes = getChunkSizeBytes(alg);
32
+ const key = await crypto.exportKey(packet.encKey);
33
+ const iv = packet.iv;
34
+ const ciphertext = getPureCiphertext(packet.ciphertext, cipherSuite);
35
+ // if the packet starts with TOPRF overflow from previous packet,
36
+ // we can just redact that part of the ciphertext as it's not required
37
+ // to be proven. Decrypting the raw ciphertext of this part would also
38
+ // reveal the raw underlying text, which we don't want.
39
+ if (overshotToprfFromPrevBlock) {
40
+ redactedPlaintext.set(new Uint8Array(overshotToprfFromPrevBlock.length)
41
+ .fill(REDACTION_CHAR_CODE));
42
+ }
43
+ const trueCiphertextLength = isTls13Suite(cipherSuite)
44
+ ? ciphertext.length - 1 // remove content type byte
45
+ : ciphertext.length;
46
+ const packetToProve = {
47
+ onGeneratedProofs,
48
+ algorithm: alg,
49
+ proofsToGenerate: [],
50
+ toprfsToGenerate: [],
51
+ iv: packet.fixedIv,
52
+ };
53
+ // first we'll handle all TOPRF blocks
54
+ // we do these first, because they can span multiple chunks
55
+ // & we need to be able to span the right chunks
56
+ for (const toprf of toprfs) {
57
+ // if the TOPRF data overshoots the ciphertext length,
58
+ // then it means that the OPRF data is spread across multiple
59
+ // TLS records & we need to include the next record's ciphertext
60
+ // in our proof.
61
+ // At most we support the OPRF data being spread across 2 records
62
+ const toprfDistFromEnd = trueCiphertextLength
63
+ - (toprf.dataLocation.fromIndex + toprf.dataLocation.length);
64
+ if (toprfDistFromEnd < 0) {
65
+ const nextPacket = getNextPacket();
66
+ if (nextPacket?.type !== 'ciphertext') {
67
+ throw new AttestorError('ERROR_INTERNAL', 'TOPRF data overshoots ciphertext length, '
68
+ + 'but no next ciphertext packet found');
69
+ }
70
+ if (nextPacket.encKey !== packet.encKey) {
71
+ throw new AttestorError('ERROR_INTERNAL', 'TOPRF data overshoots ciphertext length, '
72
+ + 'but next packet has different encryption key');
73
+ }
74
+ const nextCiphertext = nextPacket.ciphertext
75
+ .slice(0, Math.abs(toprfDistFromEnd));
76
+ const iv = nextPacket.iv;
77
+ toprf.overshoot = {
78
+ ciphertext: nextCiphertext,
79
+ iv,
80
+ recordNumber: nextPacket.recordNumber,
81
+ };
82
+ }
83
+ const fromIndex = getIdealOffsetForToprfBlock(alg, toprf);
84
+ const toIndex = Math
85
+ .min(fromIndex + chunkSizeBytes, ciphertext.length);
86
+ // ensure this OPRF block doesn't overlap with any other OPRF block
87
+ const slice = { fromIndex, toIndex };
88
+ packetToProve.toprfsToGenerate.push(getTOPRFProofGenerationParamsForSlice({
89
+ key,
90
+ iv,
91
+ ciphertext,
92
+ slice,
93
+ toprf: {
94
+ ...toprf,
95
+ dataLocation: {
96
+ ...toprf.dataLocation,
97
+ fromIndex: toprf.dataLocation.fromIndex - fromIndex
98
+ }
99
+ }
100
+ }));
101
+ zkProofsToGen += 1;
102
+ // we'll redact the OPRF part of the plaintext to not reveal
103
+ // the actual plaintext to the attestor
104
+ const pktToIndex = Math.min(trueCiphertextLength, toprf.dataLocation.fromIndex + toprf.dataLocation.length);
105
+ const pktFromIndex = toprf.dataLocation.fromIndex;
106
+ for (let i = pktFromIndex; i < pktToIndex; i++) {
107
+ redactedPlaintext[i] = REDACTION_CHAR_CODE;
108
+ }
109
+ }
110
+ for (let i = 0; i < ciphertext.length; i += chunkSizeBytes) {
111
+ const slice = {
112
+ fromIndex: i,
113
+ toIndex: Math.min(i + chunkSizeBytes, ciphertext.length)
114
+ };
115
+ const proofParams = getProofGenerationParamsForSlice({ key, iv, ciphertext, redactedPlaintext, slice });
116
+ if (!proofParams) {
117
+ continue;
118
+ }
119
+ packetToProve.proofsToGenerate.push(proofParams);
120
+ zkProofsToGen += 1;
121
+ }
122
+ packetsToProve.push(packetToProve);
123
+ },
124
+ getTotalChunksToProve() {
125
+ return zkProofsToGen;
126
+ },
127
+ async generateProofs(onChunkDone) {
128
+ if (!packetsToProve.length) {
129
+ return;
130
+ }
131
+ const start = Date.now();
132
+ const tasks = [];
133
+ for (const { onGeneratedProofs, algorithm, proofsToGenerate, toprfsToGenerate } of packetsToProve) {
134
+ const proofs = [];
135
+ const toprfs = [];
136
+ let proofsLeft = proofsToGenerate.length
137
+ + toprfsToGenerate.length;
138
+ for (const proofToGen of proofsToGenerate) {
139
+ tasks.push(zkQueue.add(async () => {
140
+ const proof = await generateZkProofForChunk(algorithm, proofToGen);
141
+ onChunkDone?.();
142
+ proofs.push(proof);
143
+ proofsLeft -= 1;
144
+ if (proofsLeft === 0) {
145
+ onGeneratedProofs(proofs, toprfs);
146
+ }
147
+ }, { throwOnTimeout: true }));
148
+ }
149
+ for (const toprfToGen of toprfsToGenerate) {
150
+ tasks.push(zkQueue.add(async () => {
151
+ const toprf = await generateOprfProofForChunk(algorithm, toprfToGen);
152
+ onChunkDone?.();
153
+ toprfs.push(toprf);
154
+ proofsLeft -= 1;
155
+ if (proofsLeft === 0) {
156
+ onGeneratedProofs(proofs, toprfs);
157
+ }
158
+ }, { throwOnTimeout: true }));
159
+ }
160
+ }
161
+ await Promise.all(tasks);
162
+ logger?.info({ durationMs: Date.now() - start, zkProofsToGen }, 'generated ZK proofs');
163
+ // reset the packets to prove
164
+ packetsToProve.splice(0, packetsToProve.length);
165
+ zkProofsToGen = 0;
166
+ // release ZK resources to free up memory
167
+ const alg = getZkAlgorithmForCipherSuite(cipherSuite);
168
+ const zkOperator = await getZkOperatorForAlgorithm(alg);
169
+ zkOperator.release?.();
170
+ },
171
+ };
172
+ async function generateZkProofForChunk(algorithm, { startIdx, redactedPlaintext, privateInput, publicInput }) {
173
+ const operator = getZkOperatorForAlgorithm(algorithm);
174
+ const proof = await generateProof({ algorithm, privateInput, publicInput, operator, logger });
175
+ logger?.debug({ startIdx }, 'generated proof for chunk');
176
+ return {
177
+ proofData: typeof proof.proofData === 'string'
178
+ ? strToUint8Array(proof.proofData)
179
+ : proof.proofData,
180
+ decryptedRedactedCiphertext: proof.plaintext || new Uint8Array(),
181
+ redactedPlaintext,
182
+ startIdx
183
+ };
184
+ }
185
+ async function generateOprfProofForChunk(algorithm, { startIdx, privateInput, publicInput, toprf }) {
186
+ const operator = getOprfOperatorForAlgorithm(algorithm);
187
+ const toprfLocations = [];
188
+ if (toprf?.overshoot) {
189
+ const { dataLocation, overshoot: { ciphertext } } = toprf;
190
+ toprfLocations.push({
191
+ pos: dataLocation.fromIndex,
192
+ len: dataLocation.length - ciphertext.length
193
+ }, {
194
+ pos: ceilToBlockSizeMultiple(dataLocation.fromIndex + dataLocation.length, algorithm),
195
+ len: ciphertext.length
196
+ });
197
+ }
198
+ else if (toprf) {
199
+ toprfLocations.push({
200
+ pos: toprf.dataLocation.fromIndex,
201
+ len: toprf.dataLocation.length,
202
+ });
203
+ }
204
+ const proof = await generateProof({
205
+ algorithm,
206
+ privateInput,
207
+ publicInput,
208
+ operator,
209
+ logger,
210
+ ...(toprf
211
+ ? {
212
+ toprf: {
213
+ locations: toprfLocations,
214
+ output: toprf.nullifier,
215
+ responses: toprf.responses,
216
+ domainSeparator: TOPRF_DOMAIN_SEPARATOR
217
+ },
218
+ mask: toprf.mask,
219
+ }
220
+ : {})
221
+ });
222
+ logger?.debug({ toprfLocations }, 'generated TOPRF proof for chunk');
223
+ return {
224
+ startIdx,
225
+ proofData: typeof proof.proofData === 'string'
226
+ ? strToUint8Array(proof.proofData)
227
+ : proof.proofData,
228
+ payload: toprf,
229
+ };
230
+ }
231
+ function getZkOperatorForAlgorithm(algorithm) {
232
+ return zkOperators?.[algorithm]
233
+ || makeDefaultZkOperator(algorithm, zkEngine, logger);
234
+ }
235
+ function getOprfOperatorForAlgorithm(algorithm) {
236
+ return oprfOperators?.[algorithm]
237
+ || makeDefaultOPRFOperator(algorithm, zkEngine, logger);
238
+ }
239
+ }
240
+ /**
241
+ * Verify the given ZK proof
242
+ */
243
+ export async function verifyZkPacket({ cipherSuite, ciphertext, zkReveal, zkOperators, oprfOperators, logger = LOGGER, zkEngine = 'snarkjs', iv, recordNumber, toprfOvershotNullifier, getNextPacket }) {
244
+ const { proofs, toprfs } = zkReveal;
245
+ const algorithm = getZkAlgorithmForCipherSuite(cipherSuite);
246
+ const recordIV = getRecordIV(ciphertext, cipherSuite);
247
+ ciphertext = new Uint8Array(getPureCiphertext(ciphertext, cipherSuite));
248
+ const realRedactedPlaintext = new Uint8Array(ciphertext.length).fill(REDACTION_CHAR_CODE);
249
+ const replacements = await Promise.all(toprfs.map(async (toprf, i) => {
250
+ try {
251
+ return await verifyToprfProofPacket(toprf);
252
+ }
253
+ catch (e) {
254
+ e.message += ` (TOPRF proof ${i}, `
255
+ + `from ${toprf.payload?.dataLocation?.fromIndex}, `
256
+ + `record ${recordNumber})`;
257
+ throw e;
258
+ }
259
+ }));
260
+ await Promise.all(proofs.map(async (proof, i) => {
261
+ try {
262
+ await verifyZkProofPacket(proof);
263
+ }
264
+ catch (e) {
265
+ e.message +=
266
+ ` (ZK proof ${i}, startIdx ${proof.startIdx}, record ${recordNumber})`;
267
+ throw e;
268
+ }
269
+ }));
270
+ for (const { set, startIdx } of replacements) {
271
+ realRedactedPlaintext.set(set, startIdx);
272
+ }
273
+ /**
274
+ * to verify if the user has given us the correct redacted plaintext,
275
+ * and isn't providing plaintext that they haven't proven they have
276
+ * we start with a fully redacted plaintext, and then replace the
277
+ * redacted parts with the plaintext that the user has provided
278
+ * in the proofs
279
+ */
280
+ if (toprfOvershotNullifier) {
281
+ realRedactedPlaintext.set(toprfOvershotNullifier);
282
+ }
283
+ return { redactedPlaintext: realRedactedPlaintext };
284
+ async function verifyZkProofPacket({ proofData, decryptedRedactedCiphertext, redactedPlaintext, startIdx, }) {
285
+ // get the ciphertext chunk we received from the server
286
+ // the ZK library, will verify that the decrypted redacted
287
+ // ciphertext matches the ciphertext received from the server
288
+ const ciphertextChunkEnd = startIdx + redactedPlaintext.length;
289
+ const ciphertextChunk = ciphertext.slice(startIdx, ciphertextChunkEnd);
290
+ // redact ciphertext if plaintext is redacted
291
+ // to prepare for decryption in ZK circuit
292
+ // the ZK circuit will take in the redacted ciphertext,
293
+ // which shall produce the redacted plaintext
294
+ for (let i = 0; i < ciphertextChunk.length; i++) {
295
+ if (redactedPlaintext[i] === REDACTION_CHAR_CODE) {
296
+ ciphertextChunk[i] = REDACTION_CHAR_CODE;
297
+ }
298
+ }
299
+ let nonce = concatenateUint8Arrays([iv, recordIV]);
300
+ if (!recordIV.length) {
301
+ nonce = generateIV(nonce, recordNumber);
302
+ }
303
+ const ciphertextInput = {
304
+ ciphertext: ciphertextChunk,
305
+ iv: nonce,
306
+ offsetBytes: startIdx
307
+ };
308
+ if (!isRedactionCongruent(redactedPlaintext, decryptedRedactedCiphertext)) {
309
+ throw new Error('redacted ciphertext not congruent');
310
+ }
311
+ await verifyProof({
312
+ proof: {
313
+ algorithm,
314
+ proofData: proofData,
315
+ plaintext: decryptedRedactedCiphertext,
316
+ },
317
+ publicInput: ciphertextInput,
318
+ logger,
319
+ operator: getZkOperator()
320
+ });
321
+ logger?.debug({ startIdx, endIdx: startIdx + redactedPlaintext.length }, 'verified proof');
322
+ realRedactedPlaintext.set(redactedPlaintext, startIdx);
323
+ }
324
+ async function verifyToprfProofPacket({ startIdx, proofData, payload: toprf }) {
325
+ if (!toprf?.dataLocation || !toprf.responses || !toprf.nullifier) {
326
+ throw new Error('invalid TOPRF proof payload');
327
+ }
328
+ const { dataLocation, nullifier } = toprf;
329
+ const ciphertextChunkEnd = Math
330
+ .min(ciphertext.length, getChunkSizeBytes(algorithm) + startIdx);
331
+ const isLastChunk = ciphertextChunkEnd >= ciphertext.length;
332
+ const ciphertextChunk = ciphertext.slice(startIdx, ciphertextChunkEnd);
333
+ let nonce = concatenateUint8Arrays([iv, recordIV]);
334
+ if (!recordIV.length) {
335
+ nonce = generateIV(nonce, recordNumber);
336
+ }
337
+ const ciphertextInput = {
338
+ ciphertext: ciphertextChunk,
339
+ iv: nonce,
340
+ offsetBytes: startIdx
341
+ };
342
+ let pubInput = ciphertextInput;
343
+ const nulliferStr = binaryHashToStr(nullifier, dataLocation.length);
344
+ const locations = [];
345
+ const toprfEndIdx = dataLocation.fromIndex + dataLocation.length;
346
+ const trueCiphLen = isLastChunk && isTls13Suite(cipherSuite)
347
+ ? ciphertextChunk.length - 1
348
+ : ciphertextChunk.length;
349
+ const overshoot = toprfEndIdx - trueCiphLen;
350
+ if (overshoot > 0) {
351
+ // fetch the overshoot part of the nullifier
352
+ const nextPkt = getNextPacket(strToUint8Array(nulliferStr.slice(dataLocation.length - overshoot)));
353
+ if (!nextPkt) {
354
+ throw new Error('OPRF data overshot, but no next packet found');
355
+ }
356
+ const nextRecordIV = getRecordIV(ciphertext, cipherSuite);
357
+ let nextNonce = concatenateUint8Arrays([iv, nextRecordIV]);
358
+ if (!nextRecordIV.length) {
359
+ nextNonce = generateIV(nextNonce, recordNumber + 1);
360
+ }
361
+ pubInput = [
362
+ ciphertextInput,
363
+ {
364
+ ciphertext: nextPkt.slice(0, overshoot),
365
+ iv: nextNonce,
366
+ offsetBytes: 0,
367
+ }
368
+ ];
369
+ locations.push({
370
+ pos: dataLocation.fromIndex,
371
+ len: dataLocation.length - overshoot
372
+ }, {
373
+ pos: ceilToBlockSizeMultiple(dataLocation.fromIndex + dataLocation.length, algorithm),
374
+ len: overshoot
375
+ });
376
+ }
377
+ else {
378
+ locations.push({
379
+ pos: dataLocation.fromIndex,
380
+ len: dataLocation.length,
381
+ });
382
+ }
383
+ await verifyProof({
384
+ proof: { algorithm, proofData: proofData, plaintext: undefined },
385
+ publicInput: pubInput,
386
+ logger,
387
+ operator: getOprfOperator(),
388
+ toprf: {
389
+ locations,
390
+ domainSeparator: TOPRF_DOMAIN_SEPARATOR,
391
+ output: nullifier,
392
+ responses: toprf.responses,
393
+ }
394
+ });
395
+ logger?.debug({ locations }, 'verified TOPRF proof');
396
+ return {
397
+ set: strToUint8Array(nulliferStr.slice(0, locations[0].len)),
398
+ startIdx: locations[0].pos + startIdx,
399
+ };
400
+ }
401
+ function getZkOperator() {
402
+ return zkOperators?.[algorithm]
403
+ || makeDefaultZkOperator(algorithm, zkEngine, logger);
404
+ }
405
+ function getOprfOperator() {
406
+ return oprfOperators?.[algorithm]
407
+ || makeDefaultOPRFOperator(algorithm, zkEngine, logger);
408
+ }
409
+ }
410
+ // the chunk size of the ZK circuit in bytes
411
+ // this will be >= the block size
412
+ function getChunkSizeBytes(alg) {
413
+ const { chunkSize, bitsPerWord } = ZK_CONFIG[alg];
414
+ return chunkSize * bitsPerWord / 8;
415
+ }
416
+ const zkEngines = {};
417
+ const oprfEngines = {};
418
+ const operatorMakers = {
419
+ 'snarkjs': makeSnarkJsZKOperator,
420
+ 'gnark': makeGnarkZkOperator,
421
+ };
422
+ const OPRF_OPERATOR_MAKERS = {
423
+ 'gnark': makeGnarkOPRFOperator
424
+ };
425
+ export function makeDefaultZkOperator(algorithm, zkEngine, logger) {
426
+ let zkOperators = zkEngines[zkEngine];
427
+ if (!zkOperators) {
428
+ zkEngines[zkEngine] = {};
429
+ zkOperators = zkEngines[zkEngine];
430
+ }
431
+ if (!zkOperators[algorithm]) {
432
+ const opType = getOperatorType();
433
+ const zkBaseUrl = opType === 'remote' ? getZkResourcesBaseUrl() : undefined;
434
+ logger?.info({ type: opType, algorithm, zkBaseUrl }, 'fetching zk operator');
435
+ const fetcher = opType === 'local'
436
+ ? makeLocalFileFetch()
437
+ : makeRemoteFileFetch({ baseUrl: zkBaseUrl, logger });
438
+ const maker = operatorMakers[zkEngine];
439
+ if (!maker) {
440
+ throw new Error(`No ZK operator maker for ${zkEngine}`);
441
+ }
442
+ zkOperators[algorithm] = maker({ algorithm, fetcher });
443
+ }
444
+ return zkOperators[algorithm];
445
+ }
446
+ function getOperatorType() {
447
+ const envop = getEnvVariable('ZK_OPERATOR_TYPE');
448
+ if (envop === 'local' || envop === 'remote') {
449
+ return envop;
450
+ }
451
+ return detectEnvironment() === 'node' ? 'local' : 'remote';
452
+ }
453
+ export function makeDefaultOPRFOperator(algorithm, zkEngine, logger) {
454
+ let operators = oprfEngines[zkEngine];
455
+ if (!operators) {
456
+ oprfEngines[zkEngine] = {};
457
+ operators = oprfEngines[zkEngine];
458
+ }
459
+ if (!operators[algorithm]) {
460
+ const type = getOperatorType();
461
+ const zkBaseUrl = type === 'remote' ? getZkResourcesBaseUrl() : undefined;
462
+ logger?.info({ type, algorithm, zkBaseUrl }, 'fetching oprf operator');
463
+ const fetcher = type === 'local'
464
+ ? makeLocalFileFetch()
465
+ : makeRemoteFileFetch({ baseUrl: zkBaseUrl, logger });
466
+ const maker = OPRF_OPERATOR_MAKERS[zkEngine];
467
+ if (!maker) {
468
+ throw new Error(`No OPRF operator maker for ${zkEngine}`);
469
+ }
470
+ operators[algorithm] = maker({ algorithm, fetcher });
471
+ }
472
+ return operators[algorithm];
473
+ }
474
+ export function getEngineString(engine) {
475
+ if (engine === ZKProofEngine.ZK_ENGINE_GNARK) {
476
+ return 'gnark';
477
+ }
478
+ if (engine === ZKProofEngine.ZK_ENGINE_SNARKJS) {
479
+ return 'snarkjs';
480
+ }
481
+ throw new Error(`Unknown ZK engine: ${engine}`);
482
+ }
483
+ export function getEngineProto(engine) {
484
+ if (engine === 'gnark') {
485
+ return ZKProofEngine.ZK_ENGINE_GNARK;
486
+ }
487
+ if (engine === 'snarkjs') {
488
+ return ZKProofEngine.ZK_ENGINE_SNARKJS;
489
+ }
490
+ throw new Error(`Unknown ZK engine: ${engine}`);
491
+ }
492
+ function getProofGenerationParamsForSlice({ key, iv, ciphertext, redactedPlaintext, slice: { fromIndex, toIndex }, }) {
493
+ const ciphertextChunk = ciphertext.slice(fromIndex, toIndex);
494
+ const plaintextChunk = redactedPlaintext.slice(fromIndex, toIndex);
495
+ if (isFullyRedacted(plaintextChunk)) {
496
+ return;
497
+ }
498
+ // redact ciphertext if plaintext is redacted
499
+ // to prepare for decryption in ZK circuit
500
+ // the ZK circuit will take in the redacted ciphertext,
501
+ // which shall produce the redacted plaintext
502
+ for (let i = 0; i < ciphertextChunk.length; i++) {
503
+ if (plaintextChunk[i] === REDACTION_CHAR_CODE) {
504
+ ciphertextChunk[i] = REDACTION_CHAR_CODE;
505
+ }
506
+ }
507
+ return {
508
+ startIdx: fromIndex,
509
+ redactedPlaintext: plaintextChunk,
510
+ privateInput: { key },
511
+ publicInput: { ciphertext: ciphertextChunk, iv, offsetBytes: fromIndex },
512
+ };
513
+ }
514
+ function getTOPRFProofGenerationParamsForSlice({ key, iv, ciphertext, slice: { fromIndex, toIndex }, toprf, }) {
515
+ const ciphertextChunk = ciphertext.slice(fromIndex, toIndex);
516
+ if (toprf?.overshoot) {
517
+ const { overshoot: { ciphertext: overshootCiphertext, iv: overshootIv } } = toprf;
518
+ return {
519
+ privateInput: { key },
520
+ publicInput: [
521
+ {
522
+ ciphertext: ciphertextChunk,
523
+ iv,
524
+ offsetBytes: fromIndex,
525
+ },
526
+ { ciphertext: overshootCiphertext, iv: overshootIv }
527
+ ],
528
+ toprf,
529
+ startIdx: fromIndex,
530
+ };
531
+ }
532
+ return {
533
+ privateInput: { key },
534
+ publicInput: { ciphertext: ciphertextChunk, iv, offsetBytes: fromIndex },
535
+ toprf,
536
+ startIdx: fromIndex,
537
+ };
538
+ }
539
+ /**
540
+ * Get the ideal location to generate a ZK proof for a TOPRF block.
541
+ * Ideally it should be put into a slice that's a divisor of the chunk size,
542
+ * as that'll minimize the number of proofs that need to be generated.
543
+ * @returns the offset in bytes
544
+ */
545
+ function getIdealOffsetForToprfBlock(alg, { dataLocation, overshoot }) {
546
+ const chunkSizeBytes = getChunkSizeBytes(alg);
547
+ const blockSizeBytes = getBlockSizeBytes(alg);
548
+ const offsetChunks = Math
549
+ .floor(dataLocation.fromIndex / chunkSizeBytes);
550
+ const endOffsetChunks = Math
551
+ .floor((dataLocation.fromIndex + dataLocation.length) / chunkSizeBytes);
552
+ // happy case -- the OPRF block fits into a single chunk, that's a
553
+ // divisor of the chunk size
554
+ if (endOffsetChunks === offsetChunks) {
555
+ const start = offsetChunks * chunkSizeBytes;
556
+ if (overshoot) {
557
+ const overshootBlocks = Math
558
+ .ceil(overshoot.ciphertext.length / blockSizeBytes);
559
+ return start + (overshootBlocks * blockSizeBytes);
560
+ }
561
+ return start;
562
+ }
563
+ const offsetBytes = Math
564
+ .floor(dataLocation.fromIndex / blockSizeBytes) * blockSizeBytes;
565
+ const endOffsetBytes = Math
566
+ .ceil((dataLocation.fromIndex + dataLocation.length) / blockSizeBytes);
567
+ if (endOffsetBytes - offsetBytes > chunkSizeBytes) {
568
+ throw new AttestorError('ERROR_BAD_REQUEST', 'OPRF data cannot fit into a single chunk');
569
+ }
570
+ return offsetBytes;
571
+ }
572
+ function getZkResourcesBaseUrl() {
573
+ if (typeof ATTESTOR_BASE_URL !== 'string') {
574
+ return DEFAULT_REMOTE_FILE_FETCH_BASE_URL;
575
+ }
576
+ return new URL(DEFAULT_REMOTE_FILE_FETCH_BASE_URL, ATTESTOR_BASE_URL).toString();
577
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/attestor-core",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "imports": {
@@ -97,6 +97,7 @@
97
97
  "esprima-next": "^5.8.4",
98
98
  "ethers": "^6.16.0",
99
99
  "https-proxy-agent": "^7.0.6",
100
+ "ip-address": "^10.2.0",
100
101
  "ip-cidr": "^3.1.0",
101
102
  "jsonpath-plus": "^10.4.0",
102
103
  "koffi": "^2.15.2",
@@ -104,7 +105,7 @@
104
105
  "parse5": "^8.0.0",
105
106
  "parse5-htmlparser2-tree-adapter": "^8.0.0",
106
107
  "pino": "^9.14.0",
107
- "re2": "^1.23.3",
108
+ "re2": "^1.24.0",
108
109
  "serve-static": "^1.16.3",
109
110
  "snarkjs": "git+https://github.com/reclaimprotocol/snarkjs.git",
110
111
  "ws": "^8.20.0",