@prosopo/procaptcha 0.2.29 → 0.2.32
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/dist/cjs/api/handlers.cjs +20 -0
- package/dist/cjs/contracts/captcha/dist/contract-info/captcha.cjs +6 -0
- package/dist/cjs/index.cjs +0 -2
- package/dist/cjs/modules/Manager.cjs +21 -55
- package/dist/cjs/modules/ProsopoCaptchaApi.cjs +16 -15
- package/dist/cjs/modules/canvas.cjs +3 -2
- package/dist/cjs/modules/index.cjs +0 -2
- package/dist/cjs/packages/datasets/dist/captcha/captcha.cjs +143 -0
- package/dist/cjs/packages/datasets/dist/captcha/dataset.cjs +87 -0
- package/dist/cjs/packages/datasets/dist/captcha/index.cjs +27 -0
- package/dist/cjs/packages/datasets/dist/captcha/merkle.cjs +106 -0
- package/dist/cjs/packages/datasets/dist/captcha/util.cjs +16 -0
- package/dist/cjs/packages/datasets/dist/index.cjs +2 -0
- package/dist/modules/ProsopoCaptchaApi.d.ts.map +1 -1
- package/dist/modules/ProsopoCaptchaApi.js +7 -0
- package/dist/modules/ProsopoCaptchaApi.js.map +1 -1
- package/package.json +7 -7
- package/dist/cjs/modules/collector.cjs +0 -60
package/dist/cjs/index.cjs
CHANGED
|
@@ -6,13 +6,11 @@ require("./types/index.cjs");
|
|
|
6
6
|
require("./utils/index.cjs");
|
|
7
7
|
const Manager = require("./modules/Manager.cjs");
|
|
8
8
|
const ProsopoCaptchaApi = require("./modules/ProsopoCaptchaApi.cjs");
|
|
9
|
-
const collector = require("./modules/collector.cjs");
|
|
10
9
|
const manager = require("./types/manager.cjs");
|
|
11
10
|
const utils = require("./utils/utils.cjs");
|
|
12
11
|
exports.Manager = Manager.Manager;
|
|
13
12
|
exports.defaultState = Manager.defaultState;
|
|
14
13
|
exports.getNetwork = Manager.getNetwork;
|
|
15
14
|
exports.ProsopoCaptchaApi = ProsopoCaptchaApi.ProsopoCaptchaApi;
|
|
16
|
-
exports.startCollector = collector.startCollector;
|
|
17
15
|
exports.ProcapchaEventNames = manager.ProcapchaEventNames;
|
|
18
16
|
exports.sleep = utils.sleep;
|
|
@@ -5,10 +5,10 @@ const Api = require("@polkadot/api/promise/Api");
|
|
|
5
5
|
const types = require("@prosopo/types");
|
|
6
6
|
const api = require("@prosopo/api");
|
|
7
7
|
const keyring = require("@polkadot/keyring");
|
|
8
|
-
const common = require("@prosopo/common");
|
|
9
8
|
const contract = require("@prosopo/contract");
|
|
9
|
+
const common = require("@prosopo/common");
|
|
10
10
|
const ws = require("@polkadot/rpc-provider/ws");
|
|
11
|
-
const
|
|
11
|
+
const captcha = require("../contracts/captcha/dist/contract-info/captcha.cjs");
|
|
12
12
|
const util = require("@prosopo/util");
|
|
13
13
|
const random = require("@polkadot/util-crypto/random");
|
|
14
14
|
const utils = require("../utils/utils.cjs");
|
|
@@ -42,9 +42,7 @@ const buildUpdateState = (state, onStateUpdate) => {
|
|
|
42
42
|
const getNetwork = (config) => {
|
|
43
43
|
const network = config.networks[config.defaultNetwork];
|
|
44
44
|
if (!network) {
|
|
45
|
-
throw new
|
|
46
|
-
context: { error: `No network found for environment ${config.defaultEnvironment}` }
|
|
47
|
-
});
|
|
45
|
+
throw new Error(`No network found for environment ${config.defaultEnvironment}`);
|
|
48
46
|
}
|
|
49
47
|
return network;
|
|
50
48
|
};
|
|
@@ -59,14 +57,12 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
59
57
|
onError: alertError,
|
|
60
58
|
onHuman: (output) => {
|
|
61
59
|
console.log("onHuman event triggered", output);
|
|
62
|
-
updateState({ sendData: !state.sendData });
|
|
63
60
|
},
|
|
64
61
|
onExtensionNotFound: () => {
|
|
65
62
|
alert("No extension found");
|
|
66
63
|
},
|
|
67
64
|
onFailed: () => {
|
|
68
65
|
alert("Captcha challenge failed. Please try again");
|
|
69
|
-
updateState({ sendData: !state.sendData });
|
|
70
66
|
},
|
|
71
67
|
onExpired: () => {
|
|
72
68
|
alert("Completed challenge has expired, please try again");
|
|
@@ -76,7 +72,6 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
76
72
|
},
|
|
77
73
|
onOpen: () => {
|
|
78
74
|
console.log("onOpen event triggered");
|
|
79
|
-
updateState({ sendData: !state.sendData });
|
|
80
75
|
},
|
|
81
76
|
onClose: () => {
|
|
82
77
|
console.log("onClose event triggered");
|
|
@@ -151,12 +146,7 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
151
146
|
if (providerUrlFromStorage) {
|
|
152
147
|
providerApi = await loadProviderApi(providerUrlFromStorage);
|
|
153
148
|
try {
|
|
154
|
-
const verifyDappUserResponse = await providerApi.verifyDappUser(
|
|
155
|
-
getDappAccount(),
|
|
156
|
-
account.account.address,
|
|
157
|
-
void 0,
|
|
158
|
-
configOptional.challengeValidLength
|
|
159
|
-
);
|
|
149
|
+
const verifyDappUserResponse = await providerApi.verifyDappUser(account.account.address);
|
|
160
150
|
if (verifyDappUserResponse.solutionApproved) {
|
|
161
151
|
updateState({ isHuman: true, loading: false });
|
|
162
152
|
events.onHuman({
|
|
@@ -193,9 +183,9 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
193
183
|
const challenge = await captchaApi.getCaptchaChallenge();
|
|
194
184
|
console.log("challenge", challenge);
|
|
195
185
|
if (challenge.captchas.length <= 0) {
|
|
196
|
-
throw new
|
|
186
|
+
throw new Error("No captchas returned from provider");
|
|
197
187
|
}
|
|
198
|
-
const timeMillis = challenge.captchas.map((
|
|
188
|
+
const timeMillis = challenge.captchas.map((captcha2) => captcha2.captcha.timeLimitMs || 30 * 1e3).reduce((a, b) => a + b);
|
|
199
189
|
const timeout = setTimeout(() => {
|
|
200
190
|
console.log("challenge expired after " + timeMillis + "ms");
|
|
201
191
|
events.onChallengeExpired();
|
|
@@ -216,18 +206,16 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
216
206
|
console.log("submitting solutions");
|
|
217
207
|
clearTimeout();
|
|
218
208
|
if (!state.challenge) {
|
|
219
|
-
throw new
|
|
220
|
-
context: { error: "Cannot submit, no Captcha found in state" }
|
|
221
|
-
});
|
|
209
|
+
throw new Error("cannot submit, no challenge found");
|
|
222
210
|
}
|
|
223
211
|
updateState({ showModal: false });
|
|
224
212
|
const challenge = state.challenge;
|
|
225
213
|
const salt = random.randomAsHex();
|
|
226
|
-
const captchaSolution = state.challenge.captchas.map((
|
|
214
|
+
const captchaSolution = state.challenge.captchas.map((captcha2, index) => {
|
|
227
215
|
const solution = util.at(state.solutions, index);
|
|
228
216
|
return {
|
|
229
|
-
captchaId:
|
|
230
|
-
captchaContentId:
|
|
217
|
+
captchaId: captcha2.captcha.captchaId,
|
|
218
|
+
captchaContentId: captcha2.captcha.captchaContentId,
|
|
231
219
|
salt,
|
|
232
220
|
solution
|
|
233
221
|
};
|
|
@@ -237,9 +225,7 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
237
225
|
const signer = account.extension.signer;
|
|
238
226
|
const first = util.at(challenge.captchas, 0);
|
|
239
227
|
if (!first.captcha.datasetId) {
|
|
240
|
-
throw new
|
|
241
|
-
context: { error: "No datasetId set for challenge" }
|
|
242
|
-
});
|
|
228
|
+
throw new Error("No datasetId set for challenge");
|
|
243
229
|
}
|
|
244
230
|
const captchaApi = getCaptchaApi();
|
|
245
231
|
const submission = await captchaApi.submitCaptchaSolution(
|
|
@@ -280,14 +266,10 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
280
266
|
};
|
|
281
267
|
const select = (hash) => {
|
|
282
268
|
if (!state.challenge) {
|
|
283
|
-
throw new
|
|
284
|
-
context: { error: "Cannot select, no Captcha found in state" }
|
|
285
|
-
});
|
|
269
|
+
throw new Error("cannot select, no challenge found");
|
|
286
270
|
}
|
|
287
271
|
if (state.index >= state.challenge.captchas.length || state.index < 0) {
|
|
288
|
-
throw new
|
|
289
|
-
context: { error: "Cannot select, index is out of range for this Captcha" }
|
|
290
|
-
});
|
|
272
|
+
throw new Error("cannot select, round index out of range");
|
|
291
273
|
}
|
|
292
274
|
const index = state.index;
|
|
293
275
|
const solutions = state.solutions;
|
|
@@ -303,14 +285,10 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
303
285
|
};
|
|
304
286
|
const nextRound = () => {
|
|
305
287
|
if (!state.challenge) {
|
|
306
|
-
throw new
|
|
307
|
-
context: { error: "Cannot select, no Captcha found in state" }
|
|
308
|
-
});
|
|
288
|
+
throw new Error("cannot proceed to next round, no challenge found");
|
|
309
289
|
}
|
|
310
290
|
if (state.index + 1 >= state.challenge.captchas.length) {
|
|
311
|
-
throw new
|
|
312
|
-
context: { error: "Cannot select, index is out of range for this Captcha" }
|
|
313
|
-
});
|
|
291
|
+
throw new Error("cannot proceed to next round, already at last round");
|
|
314
292
|
}
|
|
315
293
|
console.log("proceeding to next round");
|
|
316
294
|
updateState({ index: state.index + 1 });
|
|
@@ -356,16 +334,14 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
356
334
|
};
|
|
357
335
|
const getCaptchaApi = () => {
|
|
358
336
|
if (!state.captchaApi) {
|
|
359
|
-
throw new
|
|
337
|
+
throw new Error("Captcha api not set");
|
|
360
338
|
}
|
|
361
339
|
return state.captchaApi;
|
|
362
340
|
};
|
|
363
341
|
const loadAccount = async () => {
|
|
364
342
|
const config = getConfig();
|
|
365
343
|
if (!config.web2 && !config.userAccountAddress) {
|
|
366
|
-
throw new
|
|
367
|
-
context: { error: "Account address has not been set for web3 mode" }
|
|
368
|
-
});
|
|
344
|
+
throw new Error("Account address has not been set for web3 mode");
|
|
369
345
|
}
|
|
370
346
|
const ext = config.web2 ? new ExtensionWeb2() : new ExtensionWeb3();
|
|
371
347
|
const account = await ext.getAccount(config);
|
|
@@ -376,7 +352,7 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
376
352
|
};
|
|
377
353
|
const getAccount = () => {
|
|
378
354
|
if (!state.account) {
|
|
379
|
-
throw new
|
|
355
|
+
throw new Error("Account not loaded");
|
|
380
356
|
}
|
|
381
357
|
const account = state.account;
|
|
382
358
|
return account;
|
|
@@ -390,7 +366,7 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
390
366
|
};
|
|
391
367
|
const getBlockNumber = () => {
|
|
392
368
|
if (!state.blockNumber) {
|
|
393
|
-
throw new
|
|
369
|
+
throw new Error("Account not loaded");
|
|
394
370
|
}
|
|
395
371
|
const blockNumber = state.blockNumber;
|
|
396
372
|
return blockNumber;
|
|
@@ -403,29 +379,19 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
403
379
|
const keyring$1 = new keyring.Keyring({ type, ss58Format: api2.registry.chainSS58 });
|
|
404
380
|
return new contract.ProsopoCaptchaContract(
|
|
405
381
|
api2,
|
|
406
|
-
JSON.parse(
|
|
382
|
+
JSON.parse(captcha.ContractAbi),
|
|
407
383
|
network.contract.address,
|
|
408
384
|
"prosopo",
|
|
409
385
|
0,
|
|
410
386
|
keyring$1.addFromAddress(getAccount().account.address)
|
|
411
387
|
);
|
|
412
388
|
};
|
|
413
|
-
const exportData = async (events2) => {
|
|
414
|
-
var _a;
|
|
415
|
-
const providerUrl = storage.getProviderUrl() || ((_a = state.captchaApi) == null ? void 0 : _a.provider.provider.url.toString());
|
|
416
|
-
if (!providerUrl) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
const providerApi = await loadProviderApi(providerUrl);
|
|
420
|
-
await providerApi.submitUserEvents(events2, getAccount().account.address);
|
|
421
|
-
};
|
|
422
389
|
return {
|
|
423
390
|
start,
|
|
424
391
|
cancel,
|
|
425
392
|
submit,
|
|
426
393
|
select,
|
|
427
|
-
nextRound
|
|
428
|
-
exportData
|
|
394
|
+
nextRound
|
|
429
395
|
};
|
|
430
396
|
}
|
|
431
397
|
exports.Manager = Manager;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
-
|
|
3
|
+
require("../packages/datasets/dist/index.cjs");
|
|
4
|
+
const handlers = require("../api/handlers.cjs");
|
|
4
5
|
const common = require("@prosopo/common");
|
|
5
6
|
const util = require("@prosopo/util");
|
|
6
7
|
const string = require("@polkadot/util/string");
|
|
8
|
+
const merkle = require("../packages/datasets/dist/captcha/merkle.cjs");
|
|
9
|
+
const captcha = require("../packages/datasets/dist/captcha/captcha.cjs");
|
|
7
10
|
class ProsopoCaptchaApi {
|
|
8
11
|
constructor(userAccount, contract, provider, providerApi, web2, dappAccount) {
|
|
9
12
|
this.userAccount = userAccount;
|
|
@@ -18,8 +21,8 @@ class ProsopoCaptchaApi {
|
|
|
18
21
|
const captchaChallenge = await this.providerApi.getCaptchaChallenge(this.userAccount, this.provider);
|
|
19
22
|
this.verifyCaptchaChallengeContent(this.provider, captchaChallenge);
|
|
20
23
|
return captchaChallenge;
|
|
21
|
-
} catch (
|
|
22
|
-
throw new common.ProsopoEnvError(
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw new common.ProsopoEnvError(e);
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
verifyCaptchaChallengeContent(provider, captchaChallenge) {
|
|
@@ -34,7 +37,7 @@ class ProsopoCaptchaApi {
|
|
|
34
37
|
if (!verifyCaptchaData(captchaWithProof)) {
|
|
35
38
|
throw new common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_CHALLENGE");
|
|
36
39
|
}
|
|
37
|
-
if (!
|
|
40
|
+
if (!merkle.verifyProof(captchaWithProof.captcha.captchaContentId, captchaWithProof.proof)) {
|
|
38
41
|
throw new common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_CHALLENGE");
|
|
39
42
|
}
|
|
40
43
|
}
|
|
@@ -42,8 +45,8 @@ class ProsopoCaptchaApi {
|
|
|
42
45
|
return;
|
|
43
46
|
}
|
|
44
47
|
async submitCaptchaSolution(signer, requestHash, datasetId, solutions, salt) {
|
|
45
|
-
const tree = new
|
|
46
|
-
const captchasHashed = solutions.map((captcha) =>
|
|
48
|
+
const tree = new merkle.CaptchaMerkleTree();
|
|
49
|
+
const captchasHashed = solutions.map((captcha$1) => captcha.computeCaptchaSolutionHash(captcha$1));
|
|
47
50
|
tree.build(captchasHashed);
|
|
48
51
|
const commitmentId = tree.root.hash;
|
|
49
52
|
console.log("solveCaptchaChallenge commitmentId", commitmentId);
|
|
@@ -51,9 +54,7 @@ class ProsopoCaptchaApi {
|
|
|
51
54
|
let signature = void 0;
|
|
52
55
|
if (this.web2) {
|
|
53
56
|
if (!signer || !signer.signRaw) {
|
|
54
|
-
throw new
|
|
55
|
-
context: { error: "Signer is not defined, cannot sign message to prove account ownership" }
|
|
56
|
-
});
|
|
57
|
+
throw new Error("Signer is not defined, cannot sign message to prove account ownership");
|
|
57
58
|
}
|
|
58
59
|
const signed = await signer.signRaw({
|
|
59
60
|
address: this.userAccount,
|
|
@@ -71,22 +72,22 @@ class ProsopoCaptchaApi {
|
|
|
71
72
|
salt,
|
|
72
73
|
signature
|
|
73
74
|
);
|
|
74
|
-
} catch (
|
|
75
|
-
throw new
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new handlers.ProsopoApiError(err);
|
|
76
77
|
}
|
|
77
78
|
return [result, commitmentId, tx];
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
async function verifyCaptchaData(captchaWithProof) {
|
|
81
|
-
const captcha = captchaWithProof.captcha;
|
|
82
|
+
const captcha$1 = captchaWithProof.captcha;
|
|
82
83
|
const proof = captchaWithProof.proof;
|
|
83
|
-
if (!(await Promise.all(captcha.items.map(async (item) => (await
|
|
84
|
+
if (!(await Promise.all(captcha$1.items.map(async (item) => (await captcha.computeItemHash(item)).hash === item.hash))).every(
|
|
84
85
|
(hash) => hash === true
|
|
85
86
|
)) {
|
|
86
87
|
return false;
|
|
87
88
|
}
|
|
88
|
-
const captchaHash =
|
|
89
|
-
if (captchaHash !== captcha.captchaContentId) {
|
|
89
|
+
const captchaHash = captcha.computeCaptchaHash(captcha$1, false, false, false);
|
|
90
|
+
if (captchaHash !== captcha$1.captchaContentId) {
|
|
90
91
|
return false;
|
|
91
92
|
}
|
|
92
93
|
return util.at(proof, 0).indexOf(captchaHash) !== -1;
|
|
@@ -366,8 +366,9 @@ function picassoCanvas(roundNumber, seed, params) {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
return x64hash128(canvasElt.toDataURL(), seed);
|
|
369
|
-
} catch (
|
|
370
|
-
|
|
369
|
+
} catch (e) {
|
|
370
|
+
console.log(e);
|
|
371
|
+
throw new common.ProsopoEnvError(e);
|
|
371
372
|
}
|
|
372
373
|
}
|
|
373
374
|
exports.picassoCanvas = picassoCanvas;
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const Manager = require("./Manager.cjs");
|
|
4
4
|
const ProsopoCaptchaApi = require("./ProsopoCaptchaApi.cjs");
|
|
5
|
-
const collector = require("./collector.cjs");
|
|
6
5
|
exports.Manager = Manager.Manager;
|
|
7
6
|
exports.defaultState = Manager.defaultState;
|
|
8
7
|
exports.getNetwork = Manager.getNetwork;
|
|
9
8
|
exports.ProsopoCaptchaApi = ProsopoCaptchaApi.ProsopoCaptchaApi;
|
|
10
|
-
exports.startCollector = collector.startCollector;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const types = require("@prosopo/types");
|
|
4
|
+
const common = require("@prosopo/common");
|
|
5
|
+
const util = require("@prosopo/util");
|
|
6
|
+
const util$1 = require("./util.cjs");
|
|
7
|
+
const NO_SOLUTION_VALUE = "NO_SOLUTION";
|
|
8
|
+
function parseCaptchaDataset(datasetJSON) {
|
|
9
|
+
try {
|
|
10
|
+
const result = types.DatasetWithNumericSolutionSchema.parse(datasetJSON);
|
|
11
|
+
const result2 = {
|
|
12
|
+
format: result.format,
|
|
13
|
+
captchas: result.captchas.map((captcha) => {
|
|
14
|
+
return {
|
|
15
|
+
...captcha,
|
|
16
|
+
solution: captcha.solution ? matchItemsToSolutions(captcha.solution, captcha.items) : [],
|
|
17
|
+
unlabelled: captcha.unlabelled ? matchItemsToSolutions(captcha.unlabelled, captcha.items) : []
|
|
18
|
+
};
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
if (result.datasetId !== void 0)
|
|
22
|
+
result2.datasetId = result.datasetId;
|
|
23
|
+
if (result.contentTree !== void 0)
|
|
24
|
+
result2.contentTree = result.contentTree;
|
|
25
|
+
if (result.datasetContentId !== void 0)
|
|
26
|
+
result2.datasetContentId = result.datasetContentId;
|
|
27
|
+
if (result.solutionTree !== void 0)
|
|
28
|
+
result2.solutionTree = result.solutionTree;
|
|
29
|
+
return result2;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new common.ProsopoEnvError(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function parseAndSortCaptchaSolutions(captchaJSON) {
|
|
35
|
+
try {
|
|
36
|
+
return types.CaptchaSolutionArraySchema.parse(captchaJSON).map((captcha) => ({
|
|
37
|
+
...captcha,
|
|
38
|
+
solution: captcha.solution.sort()
|
|
39
|
+
}));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new common.ProsopoEnvError(err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function captchaSort(a, b) {
|
|
45
|
+
return a.captchaId.localeCompare(b.captchaId);
|
|
46
|
+
}
|
|
47
|
+
function sortAndComputeHashes(received, stored) {
|
|
48
|
+
received.sort(captchaSort);
|
|
49
|
+
stored.sort(captchaSort);
|
|
50
|
+
return stored.map(({ salt, items = [], target = "", captchaId, solved }, index) => {
|
|
51
|
+
const item = util.at(received, index);
|
|
52
|
+
if (captchaId != item.captchaId) {
|
|
53
|
+
throw new common.ProsopoEnvError("CAPTCHA.ID_MISMATCH");
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
hash: computeCaptchaHash({
|
|
57
|
+
solution: solved ? item.solution : [],
|
|
58
|
+
salt,
|
|
59
|
+
items,
|
|
60
|
+
target
|
|
61
|
+
}, true, true, false),
|
|
62
|
+
captchaId
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function compareCaptchaSolutions(received, stored) {
|
|
67
|
+
if (received.length && stored.length && received.length === stored.length) {
|
|
68
|
+
const hashes = sortAndComputeHashes(received, stored);
|
|
69
|
+
return hashes.every(({ hash, captchaId }) => hash === captchaId);
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function computeCaptchaHash(captcha, includeSolution = false, includeSalt = false, sortItemHashes) {
|
|
74
|
+
try {
|
|
75
|
+
const itemHashes = captcha.items.map((item, index) => {
|
|
76
|
+
if (item.hash) {
|
|
77
|
+
return item.hash;
|
|
78
|
+
} else {
|
|
79
|
+
throw new common.ProsopoEnvError("CAPTCHA.MISSING_ITEM_HASH", computeCaptchaHash.name, void 0, index);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return common.hexHashArray([
|
|
83
|
+
captcha.target,
|
|
84
|
+
// empty array hashes as empty string, undefined solution results in the array [`NO_SOLUTION`]
|
|
85
|
+
// [undefined] also hashes as empty string, which is why we don't use it
|
|
86
|
+
...includeSolution ? getSolutionValueToHash(captcha.solution) : [],
|
|
87
|
+
includeSalt ? captcha.salt : "",
|
|
88
|
+
sortItemHashes ? itemHashes.sort() : itemHashes
|
|
89
|
+
]);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
throw new common.ProsopoEnvError(err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function getSolutionValueToHash(solution) {
|
|
95
|
+
return solution !== void 0 ? solution.sort() : [NO_SOLUTION_VALUE];
|
|
96
|
+
}
|
|
97
|
+
async function computeItemHash(item) {
|
|
98
|
+
if (item.type === "text") {
|
|
99
|
+
return { ...item, hash: common.hexHash(item.data) };
|
|
100
|
+
} else if (item.type === "image") {
|
|
101
|
+
return { ...item, hash: common.hexHash(await util$1.downloadImage(item.data)) };
|
|
102
|
+
} else {
|
|
103
|
+
throw new common.ProsopoEnvError("CAPTCHA.INVALID_ITEM_FORMAT");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function matchItemsToSolutions(solutions, items) {
|
|
107
|
+
if (!items) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return solutions.map((solution) => {
|
|
111
|
+
if (typeof solution === "string") {
|
|
112
|
+
if (!(items == null ? void 0 : items.some((item2) => item2.hash === solution))) {
|
|
113
|
+
throw new common.ProsopoEnvError("CAPTCHA.INVALID_ITEM_HASH");
|
|
114
|
+
}
|
|
115
|
+
return solution;
|
|
116
|
+
}
|
|
117
|
+
const item = util.at(items, solution);
|
|
118
|
+
const hash = item.hash;
|
|
119
|
+
return hash;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function computeCaptchaSolutionHash(captcha) {
|
|
123
|
+
return common.hexHashArray([captcha.captchaId, captcha.captchaContentId, [...captcha.solution].sort(), captcha.salt]);
|
|
124
|
+
}
|
|
125
|
+
function computePendingRequestHash(captchaIds, userAccount, salt) {
|
|
126
|
+
return common.hexHashArray([...captchaIds.sort(), userAccount, salt]);
|
|
127
|
+
}
|
|
128
|
+
function parseCaptchaAssets(item, assetsResolver) {
|
|
129
|
+
return { ...item, path: (assetsResolver == null ? void 0 : assetsResolver.resolveAsset(item.data).getURL()) || item.data };
|
|
130
|
+
}
|
|
131
|
+
exports.NO_SOLUTION_VALUE = NO_SOLUTION_VALUE;
|
|
132
|
+
exports.captchaSort = captchaSort;
|
|
133
|
+
exports.compareCaptchaSolutions = compareCaptchaSolutions;
|
|
134
|
+
exports.computeCaptchaHash = computeCaptchaHash;
|
|
135
|
+
exports.computeCaptchaSolutionHash = computeCaptchaSolutionHash;
|
|
136
|
+
exports.computeItemHash = computeItemHash;
|
|
137
|
+
exports.computePendingRequestHash = computePendingRequestHash;
|
|
138
|
+
exports.getSolutionValueToHash = getSolutionValueToHash;
|
|
139
|
+
exports.matchItemsToSolutions = matchItemsToSolutions;
|
|
140
|
+
exports.parseAndSortCaptchaSolutions = parseAndSortCaptchaSolutions;
|
|
141
|
+
exports.parseCaptchaAssets = parseCaptchaAssets;
|
|
142
|
+
exports.parseCaptchaDataset = parseCaptchaDataset;
|
|
143
|
+
exports.sortAndComputeHashes = sortAndComputeHashes;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const merkle = require("./merkle.cjs");
|
|
4
|
+
const common = require("@prosopo/common");
|
|
5
|
+
const util = require("@prosopo/util");
|
|
6
|
+
const captcha = require("./captcha.cjs");
|
|
7
|
+
const logger = common.getLogger(`Info`, `dataset.ts`);
|
|
8
|
+
async function hashDatasetItems(datasetRaw) {
|
|
9
|
+
return datasetRaw.captchas.map(async (captcha$1) => {
|
|
10
|
+
const items = await Promise.all(captcha$1.items.map(async (item) => captcha.computeItemHash(item)));
|
|
11
|
+
return {
|
|
12
|
+
...captcha$1,
|
|
13
|
+
items
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function validateDatasetContent(datasetOriginal) {
|
|
18
|
+
const captchaPromises = await hashDatasetItems(datasetOriginal);
|
|
19
|
+
const captchas = await Promise.all(captchaPromises);
|
|
20
|
+
const dataset = {
|
|
21
|
+
...datasetOriginal,
|
|
22
|
+
captchas
|
|
23
|
+
};
|
|
24
|
+
const hashes = dataset.captchas.map((captcha2) => {
|
|
25
|
+
const captchaRaw = datasetOriginal.captchas.find((captchaRaw2) => "captchaId" in captchaRaw2 ? captchaRaw2.captchaId === captcha2.captchaId : false);
|
|
26
|
+
if (captchaRaw) {
|
|
27
|
+
return captcha2.items.every((item, index) => item.hash === util.at(captchaRaw.items, index).hash);
|
|
28
|
+
} else {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return hashes.every((hash) => hash);
|
|
33
|
+
}
|
|
34
|
+
async function buildDataset(datasetRaw) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
logger.info(`Adding solution hashes to dataset`);
|
|
37
|
+
const dataset = await addSolutionHashesToDataset(datasetRaw);
|
|
38
|
+
logger.info(`Building dataset merkle trees`);
|
|
39
|
+
const contentTree = await buildCaptchaTree(dataset, false, false, true);
|
|
40
|
+
const solutionTree = await buildCaptchaTree(dataset, true, true, false);
|
|
41
|
+
dataset.captchas = dataset.captchas.map((captcha2, index) => {
|
|
42
|
+
var _a2, _b2;
|
|
43
|
+
return {
|
|
44
|
+
...captcha2,
|
|
45
|
+
captchaId: util.at(solutionTree.leaves, index).hash,
|
|
46
|
+
captchaContentId: util.at(contentTree.leaves, index).hash,
|
|
47
|
+
datasetId: (_a2 = solutionTree.root) == null ? void 0 : _a2.hash,
|
|
48
|
+
datasetContentId: (_b2 = contentTree.root) == null ? void 0 : _b2.hash
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
dataset.solutionTree = solutionTree.layers;
|
|
52
|
+
dataset.contentTree = contentTree.layers;
|
|
53
|
+
dataset.datasetId = (_a = solutionTree.root) == null ? void 0 : _a.hash;
|
|
54
|
+
dataset.datasetContentId = (_b = contentTree.root) == null ? void 0 : _b.hash;
|
|
55
|
+
return dataset;
|
|
56
|
+
}
|
|
57
|
+
async function buildCaptchaTree(dataset, includeSolution, includeSalt, sortItemHashes) {
|
|
58
|
+
try {
|
|
59
|
+
const tree = new merkle.CaptchaMerkleTree();
|
|
60
|
+
const datasetWithItemHashes = { ...dataset };
|
|
61
|
+
const captchaHashes = datasetWithItemHashes.captchas.map((captcha$1) => captcha.computeCaptchaHash(captcha$1, includeSolution, includeSalt, sortItemHashes));
|
|
62
|
+
tree.build(captchaHashes);
|
|
63
|
+
return tree;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
throw new common.ProsopoEnvError("DATASET.HASH_ERROR");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function addSolutionHashesToDataset(datasetRaw) {
|
|
69
|
+
const captchaPromises = datasetRaw.captchas.map(async (captcha$1) => {
|
|
70
|
+
return {
|
|
71
|
+
...captcha$1,
|
|
72
|
+
items: captcha$1.items,
|
|
73
|
+
// some captcha challenges will not have a solution
|
|
74
|
+
...captcha$1.solution !== void 0 && { solution: captcha.matchItemsToSolutions(captcha$1.solution, captcha$1.items) }
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const captchas = await Promise.all(captchaPromises);
|
|
78
|
+
return {
|
|
79
|
+
...datasetRaw,
|
|
80
|
+
captchas
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
exports.addSolutionHashesToDataset = addSolutionHashesToDataset;
|
|
84
|
+
exports.buildCaptchaTree = buildCaptchaTree;
|
|
85
|
+
exports.buildDataset = buildDataset;
|
|
86
|
+
exports.hashDatasetItems = hashDatasetItems;
|
|
87
|
+
exports.validateDatasetContent = validateDatasetContent;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const captcha = require("./captcha.cjs");
|
|
4
|
+
const merkle = require("./merkle.cjs");
|
|
5
|
+
const util = require("./util.cjs");
|
|
6
|
+
const dataset = require("./dataset.cjs");
|
|
7
|
+
exports.NO_SOLUTION_VALUE = captcha.NO_SOLUTION_VALUE;
|
|
8
|
+
exports.captchaSort = captcha.captchaSort;
|
|
9
|
+
exports.compareCaptchaSolutions = captcha.compareCaptchaSolutions;
|
|
10
|
+
exports.computeCaptchaHash = captcha.computeCaptchaHash;
|
|
11
|
+
exports.computeCaptchaSolutionHash = captcha.computeCaptchaSolutionHash;
|
|
12
|
+
exports.computeItemHash = captcha.computeItemHash;
|
|
13
|
+
exports.computePendingRequestHash = captcha.computePendingRequestHash;
|
|
14
|
+
exports.getSolutionValueToHash = captcha.getSolutionValueToHash;
|
|
15
|
+
exports.matchItemsToSolutions = captcha.matchItemsToSolutions;
|
|
16
|
+
exports.parseAndSortCaptchaSolutions = captcha.parseAndSortCaptchaSolutions;
|
|
17
|
+
exports.parseCaptchaAssets = captcha.parseCaptchaAssets;
|
|
18
|
+
exports.parseCaptchaDataset = captcha.parseCaptchaDataset;
|
|
19
|
+
exports.sortAndComputeHashes = captcha.sortAndComputeHashes;
|
|
20
|
+
exports.CaptchaMerkleTree = merkle.CaptchaMerkleTree;
|
|
21
|
+
exports.verifyProof = merkle.verifyProof;
|
|
22
|
+
exports.downloadImage = util.downloadImage;
|
|
23
|
+
exports.addSolutionHashesToDataset = dataset.addSolutionHashesToDataset;
|
|
24
|
+
exports.buildCaptchaTree = dataset.buildCaptchaTree;
|
|
25
|
+
exports.buildDataset = dataset.buildDataset;
|
|
26
|
+
exports.hashDatasetItems = dataset.hashDatasetItems;
|
|
27
|
+
exports.validateDatasetContent = dataset.validateDatasetContent;
|