@prosopo/datasets 3.0.9 → 3.0.10
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 +26 -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/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 +15 -12
- package/vite.cjs.config.ts +4 -1
- package/vite.esm.config.ts +20 -0
- 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,31 @@
|
|
|
1
1
|
# @prosopo/datasets
|
|
2
2
|
|
|
3
|
+
## 3.0.10
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- 3573f0b: fix npm scripts bundle command
|
|
7
|
+
- 3573f0b: build using vite, typecheck using tsc
|
|
8
|
+
- efd8102: Add tests for unwrap error helper
|
|
9
|
+
- 3573f0b: standardise all vite based npm scripts for bundling
|
|
10
|
+
- Updated dependencies [52dbf21]
|
|
11
|
+
- Updated dependencies [93d5e50]
|
|
12
|
+
- Updated dependencies [3573f0b]
|
|
13
|
+
- Updated dependencies [8a64429]
|
|
14
|
+
- Updated dependencies [3573f0b]
|
|
15
|
+
- Updated dependencies [efd8102]
|
|
16
|
+
- Updated dependencies [93d5e50]
|
|
17
|
+
- Updated dependencies [63519d7]
|
|
18
|
+
- Updated dependencies [f29fc7e]
|
|
19
|
+
- Updated dependencies [3573f0b]
|
|
20
|
+
- Updated dependencies [2d0dd8a]
|
|
21
|
+
- Updated dependencies [6d604ad]
|
|
22
|
+
- @prosopo/util@3.0.3
|
|
23
|
+
- @prosopo/util-crypto@13.5.2
|
|
24
|
+
- @prosopo/types@3.0.4
|
|
25
|
+
- @prosopo/types-database@3.0.10
|
|
26
|
+
- @prosopo/common@3.1.0
|
|
27
|
+
- @prosopo/config@3.1.1
|
|
28
|
+
|
|
3
29
|
## 3.0.9
|
|
4
30
|
### Patch Changes
|
|
5
31
|
|
package/dist/captcha/captcha.js
CHANGED
|
@@ -1,175 +1,194 @@
|
|
|
1
1
|
import { isHex } from "@polkadot/util";
|
|
2
|
-
import { ProsopoDatasetError, ProsopoEnvError,
|
|
3
|
-
import {
|
|
2
|
+
import { ProsopoDatasetError, ProsopoEnvError, hexHashArray, hexHash } from "@prosopo/common";
|
|
3
|
+
import { DatasetWithNumericSolutionSchema, CaptchaSolutionArraySchema } from "@prosopo/types";
|
|
4
4
|
import { at } from "@prosopo/util";
|
|
5
5
|
import { downloadImage } from "./util.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
...captcha,
|
|
15
|
-
solution: captcha.solution
|
|
16
|
-
? matchItemsToSolutions(captcha.solution, captcha.items)
|
|
17
|
-
: [],
|
|
18
|
-
unlabelled: captcha.unlabelled
|
|
19
|
-
? matchItemsToSolutions(captcha.unlabelled, captcha.items)
|
|
20
|
-
: [],
|
|
21
|
-
};
|
|
22
|
-
}),
|
|
23
|
-
};
|
|
24
|
-
if (result.datasetId !== undefined)
|
|
25
|
-
result2.datasetId = result.datasetId;
|
|
26
|
-
if (result.contentTree !== undefined)
|
|
27
|
-
result2.contentTree = result.contentTree;
|
|
28
|
-
if (result.datasetContentId !== undefined)
|
|
29
|
-
result2.datasetContentId = result.datasetContentId;
|
|
30
|
-
if (result.solutionTree !== undefined)
|
|
31
|
-
result2.solutionTree = result.solutionTree;
|
|
32
|
-
return result2;
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
throw new ProsopoDatasetError("DATASET.DATASET_PARSE_ERROR", {
|
|
36
|
-
context: { error: err },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export function parseAndSortCaptchaSolutions(captchaJSON) {
|
|
41
|
-
try {
|
|
42
|
-
return CaptchaSolutionArraySchema.parse(captchaJSON).map((captcha) => ({
|
|
43
|
-
...captcha,
|
|
44
|
-
solution: captcha.solution.sort(),
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
throw new ProsopoDatasetError("DATASET.SOLUTION_PARSE_ERROR", {
|
|
49
|
-
context: { error: err },
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
export function captchaSort(a, b) {
|
|
54
|
-
return a.captchaId.localeCompare(b.captchaId);
|
|
55
|
-
}
|
|
56
|
-
export function sortAndComputeHashes(received, stored) {
|
|
57
|
-
received.sort(captchaSort);
|
|
58
|
-
stored.sort(captchaSort);
|
|
59
|
-
return stored.map(({ salt, items = [], target = "", captchaId, solved }, index) => {
|
|
60
|
-
const item = at(received, index);
|
|
61
|
-
if (captchaId !== item.captchaId) {
|
|
62
|
-
throw new ProsopoEnvError("CAPTCHA.ID_MISMATCH");
|
|
63
|
-
}
|
|
6
|
+
const NO_SOLUTION_VALUE = "NO_SOLUTION";
|
|
7
|
+
function parseCaptchaDataset(datasetJSON) {
|
|
8
|
+
try {
|
|
9
|
+
const result = DatasetWithNumericSolutionSchema.parse(datasetJSON);
|
|
10
|
+
const result2 = {
|
|
11
|
+
format: result.format,
|
|
12
|
+
captchas: result.captchas.map((captcha) => {
|
|
64
13
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
items,
|
|
69
|
-
target,
|
|
70
|
-
}, true, true, false),
|
|
71
|
-
captchaId,
|
|
14
|
+
...captcha,
|
|
15
|
+
solution: captcha.solution ? matchItemsToSolutions(captcha.solution, captcha.items) : [],
|
|
16
|
+
unlabelled: captcha.unlabelled ? matchItemsToSolutions(captcha.unlabelled, captcha.items) : []
|
|
72
17
|
};
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
if (result.datasetId !== void 0) result2.datasetId = result.datasetId;
|
|
21
|
+
if (result.contentTree !== void 0)
|
|
22
|
+
result2.contentTree = result.contentTree;
|
|
23
|
+
if (result.datasetContentId !== void 0)
|
|
24
|
+
result2.datasetContentId = result.datasetContentId;
|
|
25
|
+
if (result.solutionTree !== void 0)
|
|
26
|
+
result2.solutionTree = result.solutionTree;
|
|
27
|
+
return result2;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new ProsopoDatasetError("DATASET.DATASET_PARSE_ERROR", {
|
|
30
|
+
context: { error: err }
|
|
73
31
|
});
|
|
32
|
+
}
|
|
74
33
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const captchaIdMismatch = received.some((captcha, index) => solutions[index] && captcha.captchaId !== solutions[index].captchaId);
|
|
85
|
-
if (captchaIdMismatch) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return received.every((captcha, index) => {
|
|
90
|
-
const sortedReceivedSolution = captcha.solution.sort();
|
|
91
|
-
const targetSolution = solutions[index]?.solution.sort();
|
|
92
|
-
if (!targetSolution) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
const incorrectCount = sortedReceivedSolution.filter((solution) => !targetSolution.includes(solution)).length;
|
|
96
|
-
const missingCount = targetSolution.filter((solution) => !sortedReceivedSolution.includes(solution)).length;
|
|
97
|
-
const totalIncorrect = incorrectCount + missingCount;
|
|
98
|
-
const percentageCorrect = 1 - totalIncorrect / totalImages;
|
|
99
|
-
return percentageCorrect >= threshold;
|
|
34
|
+
function parseAndSortCaptchaSolutions(captchaJSON) {
|
|
35
|
+
try {
|
|
36
|
+
return CaptchaSolutionArraySchema.parse(captchaJSON).map((captcha) => ({
|
|
37
|
+
...captcha,
|
|
38
|
+
solution: captcha.solution.sort()
|
|
39
|
+
}));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new ProsopoDatasetError("DATASET.SOLUTION_PARSE_ERROR", {
|
|
42
|
+
context: { error: err }
|
|
100
43
|
});
|
|
44
|
+
}
|
|
101
45
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const itemHashes = captcha.items.map((item, index) => {
|
|
105
|
-
if (item.hash) {
|
|
106
|
-
return item.hash;
|
|
107
|
-
}
|
|
108
|
-
throw new ProsopoDatasetError("CAPTCHA.MISSING_ITEM_HASH", {
|
|
109
|
-
context: {
|
|
110
|
-
computeCaptchaHashName: computeCaptchaHash.name,
|
|
111
|
-
index,
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
return hexHashArray([
|
|
116
|
-
captcha.target,
|
|
117
|
-
...(includeSolution ? getSolutionValueToHash(captcha.solution) : []),
|
|
118
|
-
includeSalt ? captcha.salt : "",
|
|
119
|
-
sortItemHashes ? itemHashes.sort() : itemHashes,
|
|
120
|
-
]);
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
throw new ProsopoDatasetError("DATASET.HASH_ERROR", {
|
|
124
|
-
context: { error: err },
|
|
125
|
-
});
|
|
126
|
-
}
|
|
46
|
+
function captchaSort(a, b) {
|
|
47
|
+
return a.captchaId.localeCompare(b.captchaId);
|
|
127
48
|
}
|
|
128
|
-
|
|
129
|
-
|
|
49
|
+
function sortAndComputeHashes(received, stored) {
|
|
50
|
+
received.sort(captchaSort);
|
|
51
|
+
stored.sort(captchaSort);
|
|
52
|
+
return stored.map(
|
|
53
|
+
({ salt, items = [], target = "", captchaId, solved }, index) => {
|
|
54
|
+
const item = at(received, index);
|
|
55
|
+
if (captchaId !== item.captchaId) {
|
|
56
|
+
throw new ProsopoEnvError("CAPTCHA.ID_MISMATCH");
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
hash: computeCaptchaHash(
|
|
60
|
+
{
|
|
61
|
+
solution: solved ? item.solution : [],
|
|
62
|
+
salt,
|
|
63
|
+
items,
|
|
64
|
+
target
|
|
65
|
+
},
|
|
66
|
+
true,
|
|
67
|
+
true,
|
|
68
|
+
false
|
|
69
|
+
),
|
|
70
|
+
captchaId
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
);
|
|
130
74
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
75
|
+
function compareCaptchaSolutions(received, solutions, totalImages, threshold) {
|
|
76
|
+
received.sort(captchaSort);
|
|
77
|
+
solutions.sort(captchaSort);
|
|
78
|
+
if (received.length !== solutions.length) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (received.length && solutions.length && received.length === solutions.length) {
|
|
82
|
+
const captchaIdMismatch = received.some(
|
|
83
|
+
(captcha, index) => solutions[index] && captcha.captchaId !== solutions[index].captchaId
|
|
84
|
+
);
|
|
85
|
+
if (captchaIdMismatch) {
|
|
86
|
+
return false;
|
|
134
87
|
}
|
|
135
|
-
|
|
136
|
-
|
|
88
|
+
}
|
|
89
|
+
return received.every((captcha, index) => {
|
|
90
|
+
const sortedReceivedSolution = captcha.solution.sort();
|
|
91
|
+
const targetSolution = solutions[index]?.solution.sort();
|
|
92
|
+
if (!targetSolution) {
|
|
93
|
+
return false;
|
|
137
94
|
}
|
|
138
|
-
|
|
95
|
+
const incorrectCount = sortedReceivedSolution.filter(
|
|
96
|
+
(solution) => !targetSolution.includes(solution)
|
|
97
|
+
).length;
|
|
98
|
+
const missingCount = targetSolution.filter(
|
|
99
|
+
(solution) => !sortedReceivedSolution.includes(solution)
|
|
100
|
+
).length;
|
|
101
|
+
const totalIncorrect = incorrectCount + missingCount;
|
|
102
|
+
const percentageCorrect = 1 - totalIncorrect / totalImages;
|
|
103
|
+
return percentageCorrect >= threshold;
|
|
104
|
+
});
|
|
139
105
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
106
|
+
function computeCaptchaHash(captcha, includeSolution, includeSalt, sortItemHashes) {
|
|
107
|
+
try {
|
|
108
|
+
const itemHashes = captcha.items.map((item, index) => {
|
|
109
|
+
if (item.hash) {
|
|
110
|
+
return item.hash;
|
|
111
|
+
}
|
|
112
|
+
throw new ProsopoDatasetError("CAPTCHA.MISSING_ITEM_HASH", {
|
|
113
|
+
context: {
|
|
114
|
+
computeCaptchaHashName: computeCaptchaHash.name,
|
|
115
|
+
index
|
|
150
116
|
}
|
|
151
|
-
|
|
152
|
-
const item = at(items, solution);
|
|
153
|
-
return item.hash;
|
|
154
|
-
}
|
|
155
|
-
throw new ProsopoDatasetError("CAPTCHA.INVALID_SOLUTION_TYPE");
|
|
117
|
+
});
|
|
156
118
|
});
|
|
157
|
-
}
|
|
158
|
-
export function computeCaptchaSolutionHash(captcha) {
|
|
159
119
|
return hexHashArray([
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
120
|
+
captcha.target,
|
|
121
|
+
// empty array hashes as empty string, undefined solution results in the array [`NO_SOLUTION`]
|
|
122
|
+
// [undefined] also hashes as empty string, which is why we don't use it
|
|
123
|
+
...includeSolution ? getSolutionValueToHash(captcha.solution) : [],
|
|
124
|
+
includeSalt ? captcha.salt : "",
|
|
125
|
+
sortItemHashes ? itemHashes.sort() : itemHashes
|
|
164
126
|
]);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
throw new ProsopoDatasetError("DATASET.HASH_ERROR", {
|
|
129
|
+
context: { error: err }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
165
132
|
}
|
|
166
|
-
|
|
167
|
-
|
|
133
|
+
function getSolutionValueToHash(solution) {
|
|
134
|
+
return solution !== void 0 ? solution.sort() : [NO_SOLUTION_VALUE];
|
|
168
135
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
136
|
+
async function computeItemHash(item) {
|
|
137
|
+
if (item.type === "text") {
|
|
138
|
+
return { ...item, hash: hexHash(item.data) };
|
|
139
|
+
}
|
|
140
|
+
if (item.type === "image") {
|
|
141
|
+
return { ...item, hash: hexHash(await downloadImage(item.data)) };
|
|
142
|
+
}
|
|
143
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_ITEM_FORMAT");
|
|
144
|
+
}
|
|
145
|
+
function matchItemsToSolutions(solutions, items) {
|
|
146
|
+
if (!items) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return solutions.map((solution) => {
|
|
150
|
+
if (typeof solution === "string" && isHex(solution)) {
|
|
151
|
+
if (!items?.some((item) => item.hash === solution)) {
|
|
152
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_ITEM_HASH");
|
|
153
|
+
}
|
|
154
|
+
return solution;
|
|
155
|
+
}
|
|
156
|
+
if (typeof solution === "number") {
|
|
157
|
+
const item = at(items, solution);
|
|
158
|
+
return item.hash;
|
|
159
|
+
}
|
|
160
|
+
throw new ProsopoDatasetError("CAPTCHA.INVALID_SOLUTION_TYPE");
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function computeCaptchaSolutionHash(captcha) {
|
|
164
|
+
return hexHashArray([
|
|
165
|
+
captcha.captchaId,
|
|
166
|
+
captcha.captchaContentId,
|
|
167
|
+
[...captcha.solution].sort(),
|
|
168
|
+
captcha.salt
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
function computePendingRequestHash(captchaIds, userAccount, salt) {
|
|
172
|
+
return hexHashArray([...captchaIds.sort(), userAccount, salt]);
|
|
173
|
+
}
|
|
174
|
+
function parseCaptchaAssets(item, assetsResolver) {
|
|
175
|
+
return {
|
|
176
|
+
...item,
|
|
177
|
+
data: assetsResolver?.resolveAsset(item.data).getURL() || item.data
|
|
178
|
+
};
|
|
174
179
|
}
|
|
175
|
-
|
|
180
|
+
export {
|
|
181
|
+
NO_SOLUTION_VALUE,
|
|
182
|
+
captchaSort,
|
|
183
|
+
compareCaptchaSolutions,
|
|
184
|
+
computeCaptchaHash,
|
|
185
|
+
computeCaptchaSolutionHash,
|
|
186
|
+
computeItemHash,
|
|
187
|
+
computePendingRequestHash,
|
|
188
|
+
getSolutionValueToHash,
|
|
189
|
+
matchItemsToSolutions,
|
|
190
|
+
parseAndSortCaptchaSolutions,
|
|
191
|
+
parseCaptchaAssets,
|
|
192
|
+
parseCaptchaDataset,
|
|
193
|
+
sortAndComputeHashes
|
|
194
|
+
};
|
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
|
+
};
|