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