@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
|
@@ -1,354 +1,325 @@
|
|
|
1
|
-
import { areUint8ArraysEqual, concatenateUint8Arrays } from
|
|
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 "
|
|
5
|
-
import { computeOPRFRaw } from "
|
|
6
|
-
import { processHandshake } from "
|
|
7
|
-
import { assertValidateProviderParams } from "
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
request.transcript,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
logger.
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
};
|