@prosopo/datasets 3.0.42 → 3.0.48
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/CHANGELOG.md +68 -0
- package/dist/captcha/captcha.js +173 -154
- package/dist/captcha/dataset.js +83 -69
- package/dist/captcha/index.js +27 -5
- package/dist/captcha/merkle.js +112 -109
- package/dist/captcha/util.js +19 -18
- package/dist/cjs/captcha/captcha.cjs +195 -0
- package/dist/cjs/captcha/dataset.cjs +94 -0
- package/dist/cjs/captcha/index.cjs +27 -0
- package/dist/cjs/captcha/merkle.cjs +128 -0
- package/dist/cjs/captcha/util.cjs +23 -0
- package/dist/cjs/index.cjs +31 -0
- package/dist/cjs/tests/mocks/data/captchas.cjs +1044 -0
- package/dist/index.js +31 -3
- package/dist/tests/mocks/data/captchas.js +1039 -1033
- package/dist/tests/mocks/data/captchas.json +886 -886
- package/dist/tests/mocks/data/captchas1.json +132 -132
- package/dist/tests/mocks/data/captchas2.json +175 -175
- package/dist/tests/mocks/data/captchas3.json +133 -133
- package/dist/tests/mocks/data/captchas4.json +132 -132
- package/package.json +9 -9
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -493
- package/coverage/coverage-final.json +0 -7
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/captcha/captcha.ts.html +0 -1093
- package/coverage/src/captcha/dataset.ts.html +0 -490
- package/coverage/src/captcha/index.html +0 -176
- package/coverage/src/captcha/index.ts.html +0 -136
- package/coverage/src/captcha/merkle.ts.html +0 -610
- package/coverage/src/captcha/util.ts.html +0 -187
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -139
- package/dist/captcha/captcha.d.ts +0 -21
- package/dist/captcha/captcha.d.ts.map +0 -1
- package/dist/captcha/captcha.js.map +0 -1
- package/dist/captcha/dataset.d.ts +0 -8
- package/dist/captcha/dataset.d.ts.map +0 -1
- package/dist/captcha/dataset.js.map +0 -1
- package/dist/captcha/index.d.ts +0 -5
- package/dist/captcha/index.d.ts.map +0 -1
- package/dist/captcha/index.js.map +0 -1
- package/dist/captcha/merkle.d.ts +0 -20
- package/dist/captcha/merkle.d.ts.map +0 -1
- package/dist/captcha/merkle.js.map +0 -1
- package/dist/captcha/util.d.ts +0 -2
- package/dist/captcha/util.d.ts.map +0 -1
- package/dist/captcha/util.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tests/captcha.unit.test.d.ts +0 -2
- package/dist/tests/captcha.unit.test.d.ts.map +0 -1
- package/dist/tests/captcha.unit.test.js +0 -357
- package/dist/tests/captcha.unit.test.js.map +0 -1
- package/dist/tests/dataset.unit.test.d.ts +0 -2
- package/dist/tests/dataset.unit.test.d.ts.map +0 -1
- package/dist/tests/dataset.unit.test.js +0 -126
- package/dist/tests/dataset.unit.test.js.map +0 -1
- package/dist/tests/merkle.unit.test.d.ts +0 -2
- package/dist/tests/merkle.unit.test.d.ts.map +0 -1
- package/dist/tests/merkle.unit.test.js +0 -171
- package/dist/tests/merkle.unit.test.js.map +0 -1
- package/dist/tests/mocks/data/captchas.d.ts +0 -24
- package/dist/tests/mocks/data/captchas.d.ts.map +0 -1
- package/dist/tests/mocks/data/captchas.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# @prosopo/datasets
|
|
2
2
|
|
|
3
|
+
## 3.0.48
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- 93d92a7: little bump for publish all
|
|
7
|
+
- Updated dependencies [93d92a7]
|
|
8
|
+
- @prosopo/common@3.1.25
|
|
9
|
+
- @prosopo/types@3.6.3
|
|
10
|
+
- @prosopo/types-database@4.0.5
|
|
11
|
+
- @prosopo/util@3.2.3
|
|
12
|
+
- @prosopo/util-crypto@13.5.27
|
|
13
|
+
|
|
14
|
+
## 3.0.47
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 8ee8434: bump node engines to 24 and npm version to 11
|
|
18
|
+
- cfee479: make @prosopo/config a dev dep
|
|
19
|
+
- Updated dependencies [8ee8434]
|
|
20
|
+
- Updated dependencies [cfee479]
|
|
21
|
+
- @prosopo/types-database@4.0.4
|
|
22
|
+
- @prosopo/util-crypto@13.5.26
|
|
23
|
+
- @prosopo/common@3.1.24
|
|
24
|
+
- @prosopo/types@3.6.2
|
|
25
|
+
- @prosopo/util@3.2.2
|
|
26
|
+
|
|
27
|
+
## 3.0.46
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- e926831: mega mini bump for all to trigger publish all
|
|
31
|
+
- Updated dependencies [e926831]
|
|
32
|
+
- @prosopo/config@3.1.23
|
|
33
|
+
- @prosopo/common@3.1.23
|
|
34
|
+
- @prosopo/types@3.6.1
|
|
35
|
+
- @prosopo/types-database@4.0.3
|
|
36
|
+
- @prosopo/util@3.2.1
|
|
37
|
+
- @prosopo/util-crypto@13.5.25
|
|
38
|
+
|
|
39
|
+
## 3.0.45
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [0a9887c]
|
|
43
|
+
- @prosopo/types-database@4.0.2
|
|
44
|
+
|
|
45
|
+
## 3.0.44
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [3e5d80a]
|
|
49
|
+
- @prosopo/types-database@4.0.1
|
|
50
|
+
|
|
51
|
+
## 3.0.43
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- 8ce9205: Change engine requirements
|
|
55
|
+
- b6e98b2: Run npm audit
|
|
56
|
+
- Updated dependencies [15ae7cf]
|
|
57
|
+
- Updated dependencies [bb5f41c]
|
|
58
|
+
- Updated dependencies [55a64c6]
|
|
59
|
+
- Updated dependencies [8ce9205]
|
|
60
|
+
- Updated dependencies [df79c03]
|
|
61
|
+
- Updated dependencies [8f22479]
|
|
62
|
+
- Updated dependencies [b6e98b2]
|
|
63
|
+
- Updated dependencies [55a64c6]
|
|
64
|
+
- @prosopo/types@3.6.0
|
|
65
|
+
- @prosopo/types-database@4.0.0
|
|
66
|
+
- @prosopo/util@3.2.0
|
|
67
|
+
- @prosopo/util-crypto@13.5.24
|
|
68
|
+
- @prosopo/common@3.1.22
|
|
69
|
+
- @prosopo/config@3.1.22
|
|
70
|
+
|
|
3
71
|
## 3.0.42
|
|
4
72
|
### Patch Changes
|
|
5
73
|
|
package/dist/captcha/captcha.js
CHANGED
|
@@ -1,176 +1,195 @@
|
|
|
1
1
|
import { isHex } from "@polkadot/util";
|
|
2
2
|
import { ProsopoDatasetError, ProsopoEnvError } from "@prosopo/common";
|
|
3
|
-
import {
|
|
3
|
+
import { DatasetWithNumericSolutionSchema, CaptchaSolutionArraySchema } from "@prosopo/types";
|
|
4
4
|
import { at } from "@prosopo/util";
|
|
5
|
-
import {
|
|
5
|
+
import { hexHashArray, hexHash } from "@prosopo/util-crypto";
|
|
6
6
|
import { downloadImage } from "./util.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
...captcha,
|
|
16
|
-
solution: captcha.solution
|
|
17
|
-
? matchItemsToSolutions(captcha.solution, captcha.items)
|
|
18
|
-
: [],
|
|
19
|
-
unlabelled: captcha.unlabelled
|
|
20
|
-
? matchItemsToSolutions(captcha.unlabelled, captcha.items)
|
|
21
|
-
: [],
|
|
22
|
-
};
|
|
23
|
-
}),
|
|
24
|
-
};
|
|
25
|
-
if (result.datasetId !== undefined)
|
|
26
|
-
result2.datasetId = result.datasetId;
|
|
27
|
-
if (result.contentTree !== undefined)
|
|
28
|
-
result2.contentTree = result.contentTree;
|
|
29
|
-
if (result.datasetContentId !== undefined)
|
|
30
|
-
result2.datasetContentId = result.datasetContentId;
|
|
31
|
-
if (result.solutionTree !== undefined)
|
|
32
|
-
result2.solutionTree = result.solutionTree;
|
|
33
|
-
return result2;
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
throw new ProsopoDatasetError("DATASET.DATASET_PARSE_ERROR", {
|
|
37
|
-
context: { error: err },
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export function parseAndSortCaptchaSolutions(captchaJSON) {
|
|
42
|
-
try {
|
|
43
|
-
return CaptchaSolutionArraySchema.parse(captchaJSON).map((captcha) => ({
|
|
44
|
-
...captcha,
|
|
45
|
-
solution: captcha.solution.sort(),
|
|
46
|
-
}));
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
throw new ProsopoDatasetError("DATASET.SOLUTION_PARSE_ERROR", {
|
|
50
|
-
context: { error: err },
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export function captchaSort(a, b) {
|
|
55
|
-
return a.captchaId.localeCompare(b.captchaId);
|
|
56
|
-
}
|
|
57
|
-
export function sortAndComputeHashes(received, stored) {
|
|
58
|
-
received.sort(captchaSort);
|
|
59
|
-
stored.sort(captchaSort);
|
|
60
|
-
return stored.map(({ salt, items = [], target = "", captchaId, solved }, index) => {
|
|
61
|
-
const item = at(received, index);
|
|
62
|
-
if (captchaId !== item.captchaId) {
|
|
63
|
-
throw new ProsopoEnvError("CAPTCHA.ID_MISMATCH");
|
|
64
|
-
}
|
|
7
|
+
const NO_SOLUTION_VALUE = "NO_SOLUTION";
|
|
8
|
+
function parseCaptchaDataset(datasetJSON) {
|
|
9
|
+
try {
|
|
10
|
+
const result = DatasetWithNumericSolutionSchema.parse(datasetJSON);
|
|
11
|
+
const result2 = {
|
|
12
|
+
format: result.format,
|
|
13
|
+
captchas: result.captchas.map((captcha) => {
|
|
65
14
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
items,
|
|
70
|
-
target,
|
|
71
|
-
}, true, true, false),
|
|
72
|
-
captchaId,
|
|
15
|
+
...captcha,
|
|
16
|
+
solution: captcha.solution ? matchItemsToSolutions(captcha.solution, captcha.items) : [],
|
|
17
|
+
unlabelled: captcha.unlabelled ? matchItemsToSolutions(captcha.unlabelled, captcha.items) : []
|
|
73
18
|
};
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
if (result.datasetId !== void 0) result2.datasetId = result.datasetId;
|
|
22
|
+
if (result.contentTree !== void 0)
|
|
23
|
+
result2.contentTree = result.contentTree;
|
|
24
|
+
if (result.datasetContentId !== void 0)
|
|
25
|
+
result2.datasetContentId = result.datasetContentId;
|
|
26
|
+
if (result.solutionTree !== void 0)
|
|
27
|
+
result2.solutionTree = result.solutionTree;
|
|
28
|
+
return result2;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
throw new ProsopoDatasetError("DATASET.DATASET_PARSE_ERROR", {
|
|
31
|
+
context: { error: err }
|
|
74
32
|
});
|
|
33
|
+
}
|
|
75
34
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const captchaIdMismatch = received.some((captcha, index) => solutions[index] && captcha.captchaId !== solutions[index].captchaId);
|
|
86
|
-
if (captchaIdMismatch) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return received.every((captcha, index) => {
|
|
91
|
-
const sortedReceivedSolution = captcha.solution.sort();
|
|
92
|
-
const targetSolution = solutions[index]?.solution.sort();
|
|
93
|
-
if (!targetSolution) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
const incorrectCount = sortedReceivedSolution.filter((solution) => !targetSolution.includes(solution)).length;
|
|
97
|
-
const missingCount = targetSolution.filter((solution) => !sortedReceivedSolution.includes(solution)).length;
|
|
98
|
-
const totalIncorrect = incorrectCount + missingCount;
|
|
99
|
-
const percentageCorrect = 1 - totalIncorrect / totalImages;
|
|
100
|
-
return percentageCorrect >= threshold;
|
|
35
|
+
function parseAndSortCaptchaSolutions(captchaJSON) {
|
|
36
|
+
try {
|
|
37
|
+
return CaptchaSolutionArraySchema.parse(captchaJSON).map((captcha) => ({
|
|
38
|
+
...captcha,
|
|
39
|
+
solution: captcha.solution.sort()
|
|
40
|
+
}));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new ProsopoDatasetError("DATASET.SOLUTION_PARSE_ERROR", {
|
|
43
|
+
context: { error: err }
|
|
101
44
|
});
|
|
45
|
+
}
|
|
102
46
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const itemHashes = captcha.items.map((item, index) => {
|
|
106
|
-
if (item.hash) {
|
|
107
|
-
return item.hash;
|
|
108
|
-
}
|
|
109
|
-
throw new ProsopoDatasetError("CAPTCHA.MISSING_ITEM_HASH", {
|
|
110
|
-
context: {
|
|
111
|
-
computeCaptchaHashName: computeCaptchaHash.name,
|
|
112
|
-
index,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
return hexHashArray([
|
|
117
|
-
captcha.target,
|
|
118
|
-
...(includeSolution ? getSolutionValueToHash(captcha.solution) : []),
|
|
119
|
-
includeSalt ? captcha.salt : "",
|
|
120
|
-
sortItemHashes ? itemHashes.sort() : itemHashes,
|
|
121
|
-
]);
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
throw new ProsopoDatasetError("DATASET.HASH_ERROR", {
|
|
125
|
-
context: { error: err },
|
|
126
|
-
});
|
|
127
|
-
}
|
|
47
|
+
function captchaSort(a, b) {
|
|
48
|
+
return a.captchaId.localeCompare(b.captchaId);
|
|
128
49
|
}
|
|
129
|
-
|
|
130
|
-
|
|
50
|
+
function sortAndComputeHashes(received, stored) {
|
|
51
|
+
received.sort(captchaSort);
|
|
52
|
+
stored.sort(captchaSort);
|
|
53
|
+
return stored.map(
|
|
54
|
+
({ salt, items = [], target = "", captchaId, solved }, index) => {
|
|
55
|
+
const item = at(received, index);
|
|
56
|
+
if (captchaId !== item.captchaId) {
|
|
57
|
+
throw new ProsopoEnvError("CAPTCHA.ID_MISMATCH");
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
hash: computeCaptchaHash(
|
|
61
|
+
{
|
|
62
|
+
solution: solved ? item.solution : [],
|
|
63
|
+
salt,
|
|
64
|
+
items,
|
|
65
|
+
target
|
|
66
|
+
},
|
|
67
|
+
true,
|
|
68
|
+
true,
|
|
69
|
+
false
|
|
70
|
+
),
|
|
71
|
+
captchaId
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
);
|
|
131
75
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
76
|
+
function compareCaptchaSolutions(received, solutions, totalImages, threshold) {
|
|
77
|
+
received.sort(captchaSort);
|
|
78
|
+
solutions.sort(captchaSort);
|
|
79
|
+
if (received.length !== solutions.length) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (received.length && solutions.length && received.length === solutions.length) {
|
|
83
|
+
const captchaIdMismatch = received.some(
|
|
84
|
+
(captcha, index) => solutions[index] && captcha.captchaId !== solutions[index].captchaId
|
|
85
|
+
);
|
|
86
|
+
if (captchaIdMismatch) {
|
|
87
|
+
return false;
|
|
135
88
|
}
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
}
|
|
90
|
+
return received.every((captcha, index) => {
|
|
91
|
+
const sortedReceivedSolution = captcha.solution.sort();
|
|
92
|
+
const targetSolution = solutions[index]?.solution.sort();
|
|
93
|
+
if (!targetSolution) {
|
|
94
|
+
return false;
|
|
138
95
|
}
|
|
139
|
-
|
|
96
|
+
const incorrectCount = sortedReceivedSolution.filter(
|
|
97
|
+
(solution) => !targetSolution.includes(solution)
|
|
98
|
+
).length;
|
|
99
|
+
const missingCount = targetSolution.filter(
|
|
100
|
+
(solution) => !sortedReceivedSolution.includes(solution)
|
|
101
|
+
).length;
|
|
102
|
+
const totalIncorrect = incorrectCount + missingCount;
|
|
103
|
+
const percentageCorrect = 1 - totalIncorrect / totalImages;
|
|
104
|
+
return percentageCorrect >= threshold;
|
|
105
|
+
});
|
|
140
106
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
107
|
+
function computeCaptchaHash(captcha, includeSolution, includeSalt, sortItemHashes) {
|
|
108
|
+
try {
|
|
109
|
+
const itemHashes = captcha.items.map((item, index) => {
|
|
110
|
+
if (item.hash) {
|
|
111
|
+
return item.hash;
|
|
112
|
+
}
|
|
113
|
+
throw new ProsopoDatasetError("CAPTCHA.MISSING_ITEM_HASH", {
|
|
114
|
+
context: {
|
|
115
|
+
computeCaptchaHashName: computeCaptchaHash.name,
|
|
116
|
+
index
|
|
151
117
|
}
|
|
152
|
-
|
|
153
|
-
const item = at(items, solution);
|
|
154
|
-
return item.hash;
|
|
155
|
-
}
|
|
156
|
-
throw new ProsopoDatasetError("CAPTCHA.INVALID_SOLUTION_TYPE");
|
|
118
|
+
});
|
|
157
119
|
});
|
|
158
|
-
}
|
|
159
|
-
export function computeCaptchaSolutionHash(captcha) {
|
|
160
120
|
return hexHashArray([
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
121
|
+
captcha.target,
|
|
122
|
+
// empty array hashes as empty string, undefined solution results in the array [`NO_SOLUTION`]
|
|
123
|
+
// [undefined] also hashes as empty string, which is why we don't use it
|
|
124
|
+
...includeSolution ? getSolutionValueToHash(captcha.solution) : [],
|
|
125
|
+
includeSalt ? captcha.salt : "",
|
|
126
|
+
sortItemHashes ? itemHashes.sort() : itemHashes
|
|
165
127
|
]);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
throw new ProsopoDatasetError("DATASET.HASH_ERROR", {
|
|
130
|
+
context: { error: err }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
166
133
|
}
|
|
167
|
-
|
|
168
|
-
|
|
134
|
+
function getSolutionValueToHash(solution) {
|
|
135
|
+
return solution !== void 0 ? solution.sort() : [NO_SOLUTION_VALUE];
|
|
169
136
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
137
|
+
async function computeItemHash(item) {
|
|
138
|
+
if (item.type === "text") {
|
|
139
|
+
return { ...item, hash: hexHash(item.data) };
|
|
140
|
+
}
|
|
141
|
+
if (item.type === "image") {
|
|
142
|
+
return { ...item, hash: hexHash(await downloadImage(item.data)) };
|
|
143
|
+
}
|
|
144
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_ITEM_FORMAT");
|
|
145
|
+
}
|
|
146
|
+
function matchItemsToSolutions(solutions, items) {
|
|
147
|
+
if (!items) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
return solutions.map((solution) => {
|
|
151
|
+
if (typeof solution === "string" && isHex(solution)) {
|
|
152
|
+
if (!items?.some((item) => item.hash === solution)) {
|
|
153
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_ITEM_HASH");
|
|
154
|
+
}
|
|
155
|
+
return solution;
|
|
156
|
+
}
|
|
157
|
+
if (typeof solution === "number") {
|
|
158
|
+
const item = at(items, solution);
|
|
159
|
+
return item.hash;
|
|
160
|
+
}
|
|
161
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_SOLUTION_TYPE");
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function computeCaptchaSolutionHash(captcha) {
|
|
165
|
+
return hexHashArray([
|
|
166
|
+
captcha.captchaId,
|
|
167
|
+
captcha.captchaContentId,
|
|
168
|
+
[...captcha.solution].sort(),
|
|
169
|
+
captcha.salt
|
|
170
|
+
]);
|
|
171
|
+
}
|
|
172
|
+
function computePendingRequestHash(captchaIds, userAccount, salt) {
|
|
173
|
+
return hexHashArray([...captchaIds.sort(), userAccount, salt]);
|
|
174
|
+
}
|
|
175
|
+
function parseCaptchaAssets(item, assetsResolver) {
|
|
176
|
+
return {
|
|
177
|
+
...item,
|
|
178
|
+
data: assetsResolver?.resolveAsset(item.data).getURL() || item.data
|
|
179
|
+
};
|
|
175
180
|
}
|
|
176
|
-
|
|
181
|
+
export {
|
|
182
|
+
NO_SOLUTION_VALUE,
|
|
183
|
+
captchaSort,
|
|
184
|
+
compareCaptchaSolutions,
|
|
185
|
+
computeCaptchaHash,
|
|
186
|
+
computeCaptchaSolutionHash,
|
|
187
|
+
computeItemHash,
|
|
188
|
+
computePendingRequestHash,
|
|
189
|
+
getSolutionValueToHash,
|
|
190
|
+
matchItemsToSolutions,
|
|
191
|
+
parseAndSortCaptchaSolutions,
|
|
192
|
+
parseCaptchaAssets,
|
|
193
|
+
parseCaptchaDataset,
|
|
194
|
+
sortAndComputeHashes
|
|
195
|
+
};
|
package/dist/captcha/dataset.js
CHANGED
|
@@ -1,79 +1,93 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLogger, ProsopoEnvError } from "@prosopo/common";
|
|
2
2
|
import { at } from "@prosopo/util";
|
|
3
|
-
import {
|
|
3
|
+
import { computeItemHash, computeCaptchaHash, matchItemsToSolutions } from "./captcha.js";
|
|
4
4
|
import { CaptchaMerkleTree } from "./merkle.js";
|
|
5
5
|
const logger = getLogger("info", import.meta.url);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export async function validateDatasetContent(datasetOriginal) {
|
|
16
|
-
const captchaPromises = await hashDatasetItems(datasetOriginal);
|
|
17
|
-
const captchas = await Promise.all(captchaPromises);
|
|
18
|
-
const dataset = {
|
|
19
|
-
...datasetOriginal,
|
|
20
|
-
captchas,
|
|
6
|
+
async function hashDatasetItems(datasetRaw) {
|
|
7
|
+
return datasetRaw.captchas.map(async (captcha) => {
|
|
8
|
+
const items = await Promise.all(
|
|
9
|
+
captcha.items.map(async (item) => computeItemHash(item))
|
|
10
|
+
);
|
|
11
|
+
return {
|
|
12
|
+
...captcha,
|
|
13
|
+
items
|
|
21
14
|
};
|
|
22
|
-
|
|
23
|
-
const captchaRaw = datasetOriginal.captchas.find((captchaRaw) => "captchaId" in captchaRaw
|
|
24
|
-
? captchaRaw.captchaId === captcha.captchaId
|
|
25
|
-
: false);
|
|
26
|
-
if (captchaRaw) {
|
|
27
|
-
return captcha.items.every((item, index) => item.hash === at(captchaRaw.items, index).hash);
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
});
|
|
31
|
-
return hashes.every((hash) => hash);
|
|
32
|
-
}
|
|
33
|
-
export async function buildDataset(datasetRaw) {
|
|
34
|
-
logger.debug(() => ({ msg: "Adding solution hashes to dataset" }));
|
|
35
|
-
const dataset = await addSolutionHashesToDataset(datasetRaw);
|
|
36
|
-
logger.debug(() => ({ msg: "Building dataset merkle trees" }));
|
|
37
|
-
const contentTree = await buildCaptchaTree(dataset, false, false, true);
|
|
38
|
-
const solutionTree = await buildCaptchaTree(dataset, true, true, false);
|
|
39
|
-
dataset.captchas = dataset.captchas.map((captcha, index) => ({
|
|
40
|
-
...captcha,
|
|
41
|
-
captchaId: at(solutionTree.leaves, index).hash,
|
|
42
|
-
captchaContentId: at(contentTree.leaves, index).hash,
|
|
43
|
-
datasetId: solutionTree.root?.hash,
|
|
44
|
-
datasetContentId: contentTree.root?.hash,
|
|
45
|
-
}));
|
|
46
|
-
dataset.solutionTree = solutionTree.layers;
|
|
47
|
-
dataset.contentTree = contentTree.layers;
|
|
48
|
-
dataset.datasetId = solutionTree.root?.hash;
|
|
49
|
-
dataset.datasetContentId = contentTree.root?.hash;
|
|
50
|
-
return dataset;
|
|
15
|
+
});
|
|
51
16
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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((captcha) => {
|
|
25
|
+
const captchaRaw = datasetOriginal.captchas.find(
|
|
26
|
+
(captchaRaw2) => "captchaId" in captchaRaw2 ? captchaRaw2.captchaId === captcha.captchaId : false
|
|
27
|
+
);
|
|
28
|
+
if (captchaRaw) {
|
|
29
|
+
return captcha.items.every(
|
|
30
|
+
(item, index) => item.hash === at(captchaRaw.items, index).hash
|
|
31
|
+
);
|
|
62
32
|
}
|
|
33
|
+
return false;
|
|
34
|
+
});
|
|
35
|
+
return hashes.every((hash) => hash);
|
|
36
|
+
}
|
|
37
|
+
async function buildDataset(datasetRaw) {
|
|
38
|
+
logger.debug(() => ({ msg: "Adding solution hashes to dataset" }));
|
|
39
|
+
const dataset = await addSolutionHashesToDataset(datasetRaw);
|
|
40
|
+
logger.debug(() => ({ msg: "Building dataset merkle trees" }));
|
|
41
|
+
const contentTree = await buildCaptchaTree(dataset, false, false, true);
|
|
42
|
+
const solutionTree = await buildCaptchaTree(dataset, true, true, false);
|
|
43
|
+
dataset.captchas = dataset.captchas.map(
|
|
44
|
+
(captcha, index) => ({
|
|
45
|
+
...captcha,
|
|
46
|
+
captchaId: at(solutionTree.leaves, index).hash,
|
|
47
|
+
captchaContentId: at(contentTree.leaves, index).hash,
|
|
48
|
+
datasetId: solutionTree.root?.hash,
|
|
49
|
+
datasetContentId: contentTree.root?.hash
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
dataset.solutionTree = solutionTree.layers;
|
|
53
|
+
dataset.contentTree = contentTree.layers;
|
|
54
|
+
dataset.datasetId = solutionTree.root?.hash;
|
|
55
|
+
dataset.datasetContentId = contentTree.root?.hash;
|
|
56
|
+
return dataset;
|
|
57
|
+
}
|
|
58
|
+
async function buildCaptchaTree(dataset, includeSolution, includeSalt, sortItemHashes) {
|
|
59
|
+
try {
|
|
60
|
+
const tree = new CaptchaMerkleTree();
|
|
61
|
+
const datasetWithItemHashes = { ...dataset };
|
|
62
|
+
const captchaHashes = datasetWithItemHashes.captchas.map(
|
|
63
|
+
(captcha) => computeCaptchaHash(captcha, includeSolution, includeSalt, sortItemHashes)
|
|
64
|
+
);
|
|
65
|
+
tree.build(captchaHashes);
|
|
66
|
+
return tree;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
throw new ProsopoEnvError("DATASET.HASH_ERROR");
|
|
69
|
+
}
|
|
63
70
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
...captcha,
|
|
68
|
-
items: captcha.items,
|
|
69
|
-
...(captcha.solution !== undefined && {
|
|
70
|
-
solution: matchItemsToSolutions(captcha.solution, captcha.items),
|
|
71
|
-
}),
|
|
72
|
-
};
|
|
73
|
-
});
|
|
71
|
+
function addSolutionHashesToDataset(datasetRaw) {
|
|
72
|
+
const captchas = datasetRaw.captchas.map((captcha) => {
|
|
74
73
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
...captcha,
|
|
75
|
+
items: captcha.items,
|
|
76
|
+
// some captcha challenges will not have a solution
|
|
77
|
+
...captcha.solution !== void 0 && {
|
|
78
|
+
solution: matchItemsToSolutions(captcha.solution, captcha.items)
|
|
79
|
+
}
|
|
77
80
|
};
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
...datasetRaw,
|
|
84
|
+
captchas
|
|
85
|
+
};
|
|
78
86
|
}
|
|
79
|
-
|
|
87
|
+
export {
|
|
88
|
+
addSolutionHashesToDataset,
|
|
89
|
+
buildCaptchaTree,
|
|
90
|
+
buildDataset,
|
|
91
|
+
hashDatasetItems,
|
|
92
|
+
validateDatasetContent
|
|
93
|
+
};
|
package/dist/captcha/index.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { NO_SOLUTION_VALUE, captchaSort, compareCaptchaSolutions, computeCaptchaHash, computeCaptchaSolutionHash, computeItemHash, computePendingRequestHash, getSolutionValueToHash, matchItemsToSolutions, parseAndSortCaptchaSolutions, parseCaptchaAssets, parseCaptchaDataset, sortAndComputeHashes } from "./captcha.js";
|
|
2
|
+
import { CaptchaMerkleTree, verifyProof } from "./merkle.js";
|
|
3
|
+
import { downloadImage } from "./util.js";
|
|
4
|
+
import { addSolutionHashesToDataset, buildCaptchaTree, buildDataset, hashDatasetItems, validateDatasetContent } from "./dataset.js";
|
|
5
|
+
export {
|
|
6
|
+
CaptchaMerkleTree,
|
|
7
|
+
NO_SOLUTION_VALUE,
|
|
8
|
+
addSolutionHashesToDataset,
|
|
9
|
+
buildCaptchaTree,
|
|
10
|
+
buildDataset,
|
|
11
|
+
captchaSort,
|
|
12
|
+
compareCaptchaSolutions,
|
|
13
|
+
computeCaptchaHash,
|
|
14
|
+
computeCaptchaSolutionHash,
|
|
15
|
+
computeItemHash,
|
|
16
|
+
computePendingRequestHash,
|
|
17
|
+
downloadImage,
|
|
18
|
+
getSolutionValueToHash,
|
|
19
|
+
hashDatasetItems,
|
|
20
|
+
matchItemsToSolutions,
|
|
21
|
+
parseAndSortCaptchaSolutions,
|
|
22
|
+
parseCaptchaAssets,
|
|
23
|
+
parseCaptchaDataset,
|
|
24
|
+
sortAndComputeHashes,
|
|
25
|
+
validateDatasetContent,
|
|
26
|
+
verifyProof
|
|
27
|
+
};
|