@prosopo/fingerprint 2.7.18 → 2.7.19
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/.turbo/turbo-build$colon$cjs.log +11 -9
- package/.turbo/turbo-build$colon$tsc.log +10 -10
- package/.turbo/turbo-build.log +13 -10
- package/CHANGELOG.md +9 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/cjs/index.cjs +46 -74
- package/dist/cjs/merkle.cjs +64 -74
- package/dist/cjs/proof.cjs +91 -73
- package/dist/index.js +35 -56
- package/dist/merkle.js +64 -78
- package/dist/proof.js +90 -78
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.js
CHANGED
|
@@ -1,61 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { FINGERPRINT_DISCLOSURE_KEYS, FINGERPRINT_PROOF_VERSION, encodeFingerprintProof, leafHashForContent, verifyFingerprintProofStructure } from "./proof.js";
|
|
1
|
+
import "./_virtual/_rolldown/runtime.js";
|
|
3
2
|
import { buildMerkleLayers, hashMerklePair, merkleProofFromLayers, merkleRootFromLayers, verifyMerkleProof } from "./merkle.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
componentsCache = pending;
|
|
23
|
-
pending.catch(() => {
|
|
24
|
-
if (componentsCache === pending) {
|
|
25
|
-
componentsCache = null;
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return componentsCache;
|
|
3
|
+
import { FINGERPRINT_DISCLOSURE_KEYS, FINGERPRINT_PROOF_VERSION, buildFingerprintProofFromComponents, encodeFingerprintProof, leafHashForContent, verifyFingerprintProofStructure } from "./proof.js";
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
var EXCLUDED_SOURCES = ["webGlExtensions"];
|
|
6
|
+
var FingerprintJSImport = async () => import("@prosopo/fingerprintjs");
|
|
7
|
+
var componentsCache = null;
|
|
8
|
+
var loadComponents = () => {
|
|
9
|
+
if (!componentsCache) {
|
|
10
|
+
const pending = (async () => {
|
|
11
|
+
const { sources, loadSources, prepareForSources } = await FingerprintJSImport();
|
|
12
|
+
await prepareForSources();
|
|
13
|
+
return loadSources(sources, { cache: {} }, EXCLUDED_SOURCES)();
|
|
14
|
+
})();
|
|
15
|
+
componentsCache = pending;
|
|
16
|
+
pending.catch(() => {
|
|
17
|
+
if (componentsCache === pending) componentsCache = null;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return componentsCache;
|
|
30
21
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
loadComponents()
|
|
35
|
-
]);
|
|
36
|
-
return hashComponents(components);
|
|
22
|
+
var getFingerprint = async () => {
|
|
23
|
+
const [{ hashComponents }, components] = await Promise.all([FingerprintJSImport(), loadComponents()]);
|
|
24
|
+
return hashComponents(components);
|
|
37
25
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Produce a proof of fingerprint: a Merkle commitment to the full component set
|
|
28
|
+
* plus a selective disclosure of the requested components. Callers should pass
|
|
29
|
+
* {@link FINGERPRINT_DISCLOSURE_KEYS} — omitting `discloseKeys` discloses every
|
|
30
|
+
* component, which produces a very large payload. Derived from the same
|
|
31
|
+
* component run as {@link getFingerprint}.
|
|
32
|
+
*/
|
|
33
|
+
var getFingerprintProof = async (discloseKeys) => {
|
|
34
|
+
return buildFingerprintProofFromComponents(await loadComponents(), discloseKeys);
|
|
41
35
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
export {
|
|
47
|
-
FINGERPRINT_DISCLOSURE_KEYS,
|
|
48
|
-
FINGERPRINT_PROOF_VERSION,
|
|
49
|
-
buildFingerprintProofFromComponents,
|
|
50
|
-
buildMerkleLayers,
|
|
51
|
-
encodeFingerprintProof,
|
|
52
|
-
getFingerprint,
|
|
53
|
-
getFingerprintProof,
|
|
54
|
-
hashMerklePair,
|
|
55
|
-
leafHashForContent,
|
|
56
|
-
merkleProofFromLayers,
|
|
57
|
-
merkleRootFromLayers,
|
|
58
|
-
prefetchFingerprint,
|
|
59
|
-
verifyFingerprintProofStructure,
|
|
60
|
-
verifyMerkleProof
|
|
36
|
+
var prefetchFingerprint = () => {
|
|
37
|
+
loadComponents().catch(() => {});
|
|
61
38
|
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { FINGERPRINT_DISCLOSURE_KEYS, FINGERPRINT_PROOF_VERSION, buildFingerprintProofFromComponents, buildMerkleLayers, encodeFingerprintProof, getFingerprint, getFingerprintProof, hashMerklePair, leafHashForContent, merkleProofFromLayers, merkleRootFromLayers, prefetchFingerprint, verifyFingerprintProofStructure, verifyMerkleProof };
|
package/dist/merkle.js
CHANGED
|
@@ -1,83 +1,69 @@
|
|
|
1
1
|
import { hexHashArray } from "@prosopo/util-crypto";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
);
|
|
8
|
-
}
|
|
9
|
-
return value;
|
|
2
|
+
//#region src/merkle.ts
|
|
3
|
+
var at = (arr, index) => {
|
|
4
|
+
const value = arr[index];
|
|
5
|
+
if (value === void 0) throw new Error(`merkle: index ${index} out of range (length ${arr.length})`);
|
|
6
|
+
return value;
|
|
10
7
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
8
|
+
/** Hash a pair of child hashes into their parent. */
|
|
9
|
+
var hashMerklePair = (left, right) => hexHashArray([left, right]);
|
|
10
|
+
/**
|
|
11
|
+
* Build every layer of the Merkle tree, bottom-up. Layer 0 is the leaf hashes,
|
|
12
|
+
* the final layer is `[root]`. An odd node at the end of a layer is paired with
|
|
13
|
+
* itself.
|
|
14
|
+
*/
|
|
15
|
+
var buildMerkleLayers = (leafHashes) => {
|
|
16
|
+
if (leafHashes.length === 0) throw new Error("merkle: cannot build a tree with no leaves");
|
|
17
|
+
const layers = [[...leafHashes]];
|
|
18
|
+
let current = leafHashes;
|
|
19
|
+
while (current.length > 1) {
|
|
20
|
+
const next = [];
|
|
21
|
+
for (let i = 0; i < current.length; i += 2) {
|
|
22
|
+
const left = at(current, i);
|
|
23
|
+
const right = i + 1 < current.length ? at(current, i + 1) : left;
|
|
24
|
+
next.push(hashMerklePair(left, right));
|
|
25
|
+
}
|
|
26
|
+
layers.push(next);
|
|
27
|
+
current = next;
|
|
28
|
+
}
|
|
29
|
+
return layers;
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
hash = hashMerklePair(pair[0], pair[1]);
|
|
48
|
-
}
|
|
49
|
-
return [...proof, [merkleRootFromLayers(layers)]];
|
|
31
|
+
var merkleRootFromLayers = (layers) => at(at(layers, layers.length - 1), 0);
|
|
32
|
+
/** Produce the sibling path proving `leafHash` belongs to the tree. */
|
|
33
|
+
var merkleProofFromLayers = (layers, leafHash) => {
|
|
34
|
+
const proof = [];
|
|
35
|
+
let hash = leafHash;
|
|
36
|
+
for (let layerNum = 0; layerNum < layers.length - 1; layerNum++) {
|
|
37
|
+
const layer = at(layers, layerNum);
|
|
38
|
+
const index = layer.indexOf(hash);
|
|
39
|
+
if (index === -1) throw new Error("merkle: leaf hash not present in layer");
|
|
40
|
+
let partnerIndex = index % 2 === 1 ? index - 1 : index + 1;
|
|
41
|
+
if (partnerIndex > layer.length - 1) partnerIndex = index;
|
|
42
|
+
const partner = at(layer, partnerIndex);
|
|
43
|
+
const pair = partnerIndex > index ? [hash, partner] : [partner, hash];
|
|
44
|
+
proof.push(pair);
|
|
45
|
+
hash = hashMerklePair(pair[0], pair[1]);
|
|
46
|
+
}
|
|
47
|
+
return [...proof, [merkleRootFromLayers(layers)]];
|
|
50
48
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return hash === root;
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
export {
|
|
78
|
-
buildMerkleLayers,
|
|
79
|
-
hashMerklePair,
|
|
80
|
-
merkleProofFromLayers,
|
|
81
|
-
merkleRootFromLayers,
|
|
82
|
-
verifyMerkleProof
|
|
49
|
+
/** Verify a leaf + sibling path recomputes to the proof's embedded root. */
|
|
50
|
+
var verifyMerkleProof = (leafHash, proof) => {
|
|
51
|
+
try {
|
|
52
|
+
const firstLayer = proof[0];
|
|
53
|
+
if (firstLayer === void 0 || firstLayer.indexOf(leafHash) === -1) return false;
|
|
54
|
+
if (proof.length === 1) return at(firstLayer, 0) === leafHash;
|
|
55
|
+
const root = at(at(proof, proof.length - 1), 0);
|
|
56
|
+
let hash = leafHash;
|
|
57
|
+
for (let i = 0; i < proof.length - 1; i++) {
|
|
58
|
+
const layer = at(proof, i);
|
|
59
|
+
if (layer.indexOf(hash) === -1) return false;
|
|
60
|
+
hash = hashMerklePair(at(layer, 0), at(layer, 1));
|
|
61
|
+
if (at(proof, i + 1).indexOf(hash) === -1) return false;
|
|
62
|
+
}
|
|
63
|
+
return hash === root;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
83
67
|
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { buildMerkleLayers, hashMerklePair, merkleProofFromLayers, merkleRootFromLayers, verifyMerkleProof };
|
package/dist/proof.js
CHANGED
|
@@ -1,84 +1,96 @@
|
|
|
1
|
+
import { buildMerkleLayers, merkleProofFromLayers, merkleRootFromLayers, verifyMerkleProof } from "./merkle.js";
|
|
1
2
|
import { hexHash } from "@prosopo/util-crypto";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
//#region src/proof.ts
|
|
4
|
+
var FINGERPRINT_PROOF_VERSION = 1;
|
|
5
|
+
/**
|
|
6
|
+
* The fixed set of components the client discloses in a proof of fingerprint.
|
|
7
|
+
* Disclosing every component (the previous default) made the proof enormous —
|
|
8
|
+
* each disclosure carries a full Merkle path and its raw value, and components
|
|
9
|
+
* like webgl/audio/fonts have very large values. Selective disclosure keeps the
|
|
10
|
+
* commitment (`root`) over the whole component set while only these cheap,
|
|
11
|
+
* stable scalar components carry a path + value, shrinking the payload ~10x.
|
|
12
|
+
*
|
|
13
|
+
* The provider-side validator's bounds MUST be a subset of these keys: a bounded
|
|
14
|
+
* key that isn't disclosed would always fail validation as `missing:<key>`.
|
|
15
|
+
*/
|
|
16
|
+
var FINGERPRINT_DISCLOSURE_KEYS = [
|
|
17
|
+
"colorDepth",
|
|
18
|
+
"cookiesEnabled",
|
|
19
|
+
"sessionStorage",
|
|
20
|
+
"localStorage",
|
|
21
|
+
"timezone",
|
|
22
|
+
"hardwareConcurrency",
|
|
23
|
+
"languages",
|
|
24
|
+
"architecture"
|
|
13
25
|
];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
var escapeKey = (key) => key.replace(/([:|\\])/g, "\\$1");
|
|
27
|
+
var componentLeafContent = (key, component) => {
|
|
28
|
+
const serialized = "error" in component ? "error" : JSON.stringify(component.value);
|
|
29
|
+
return `${escapeKey(key)}:${serialized}`;
|
|
18
30
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
var disclosureLeafContent = (disclosure) => {
|
|
32
|
+
const serialized = disclosure.error ? "error" : JSON.stringify(disclosure.value);
|
|
33
|
+
return `${escapeKey(disclosure.key)}:${serialized}`;
|
|
22
34
|
};
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
var leafHashForContent = (content) => hexHash(content);
|
|
36
|
+
/**
|
|
37
|
+
* Build a {@link FingerprintProof} from an already-resolved FingerprintJS
|
|
38
|
+
* components map. The commitment always covers every component (in sorted key
|
|
39
|
+
* order); `discloseKeys` selects which components are revealed (defaulting to
|
|
40
|
+
* all). Keys that are not present in the fingerprint are skipped — a validator
|
|
41
|
+
* should treat an expected-but-absent disclosure as a failure.
|
|
42
|
+
*/
|
|
43
|
+
var buildFingerprintProofFromComponents = (components, discloseKeys) => {
|
|
44
|
+
const keys = Object.keys(components).sort();
|
|
45
|
+
if (keys.length === 0) throw new Error("fingerprint proof: no components to commit to");
|
|
46
|
+
const leafHashes = keys.map((key) => {
|
|
47
|
+
const component = components[key];
|
|
48
|
+
if (component === void 0) throw new Error(`fingerprint proof: missing component ${key}`);
|
|
49
|
+
return leafHashForContent(componentLeafContent(key, component));
|
|
50
|
+
});
|
|
51
|
+
const layers = buildMerkleLayers(leafHashes);
|
|
52
|
+
const root = merkleRootFromLayers(layers);
|
|
53
|
+
const disclose = discloseKeys ?? keys;
|
|
54
|
+
const disclosures = [];
|
|
55
|
+
for (const key of disclose) {
|
|
56
|
+
const index = keys.indexOf(key);
|
|
57
|
+
if (index === -1) continue;
|
|
58
|
+
const component = components[key];
|
|
59
|
+
const leafHash = leafHashes[index];
|
|
60
|
+
if (component === void 0 || leafHash === void 0) continue;
|
|
61
|
+
const proof = merkleProofFromLayers(layers, leafHash);
|
|
62
|
+
disclosures.push("error" in component ? {
|
|
63
|
+
key,
|
|
64
|
+
error: true,
|
|
65
|
+
proof
|
|
66
|
+
} : {
|
|
67
|
+
key,
|
|
68
|
+
value: component.value,
|
|
69
|
+
proof
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
v: 1,
|
|
74
|
+
root,
|
|
75
|
+
disclosures
|
|
76
|
+
};
|
|
56
77
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
};
|
|
76
|
-
const encodeFingerprintProof = (proof) => JSON.stringify(proof);
|
|
77
|
-
export {
|
|
78
|
-
FINGERPRINT_DISCLOSURE_KEYS,
|
|
79
|
-
FINGERPRINT_PROOF_VERSION,
|
|
80
|
-
buildFingerprintProofFromComponents,
|
|
81
|
-
encodeFingerprintProof,
|
|
82
|
-
leafHashForContent,
|
|
83
|
-
verifyFingerprintProofStructure
|
|
78
|
+
/**
|
|
79
|
+
* Structural verification of a proof: every disclosed `(key, value)` re-hashes
|
|
80
|
+
* to a leaf that is proven to belong to the single committed `root`. This is
|
|
81
|
+
* the non-secret half of validation — the value-bounding rules live in the
|
|
82
|
+
* provider-side (closed-source) validator and are layered on top of this.
|
|
83
|
+
*/
|
|
84
|
+
var verifyFingerprintProofStructure = (proof) => {
|
|
85
|
+
if (proof.v !== 1) return false;
|
|
86
|
+
if (typeof proof.root !== "string" || proof.disclosures.length === 0) return false;
|
|
87
|
+
for (const disclosure of proof.disclosures) {
|
|
88
|
+
if (!verifyMerkleProof(leafHashForContent(disclosureLeafContent(disclosure)), disclosure.proof)) return false;
|
|
89
|
+
const rootLayer = disclosure.proof[disclosure.proof.length - 1];
|
|
90
|
+
if (rootLayer === void 0 || rootLayer[0] !== proof.root) return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
84
93
|
};
|
|
94
|
+
var encodeFingerprintProof = (proof) => JSON.stringify(proof);
|
|
95
|
+
//#endregion
|
|
96
|
+
export { FINGERPRINT_DISCLOSURE_KEYS, FINGERPRINT_PROOF_VERSION, buildFingerprintProofFromComponents, encodeFingerprintProof, leafHashForContent, verifyFingerprintProofStructure };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/fingerprint",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.19",
|
|
4
4
|
"description": "Provider fingerprint",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"homepage": "https://github.com/prosopo/captcha#readme",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@prosopo/fingerprintjs": "5.2.0",
|
|
41
|
-
"@prosopo/types": "4.
|
|
41
|
+
"@prosopo/types": "4.10.0",
|
|
42
42
|
"@prosopo/util-crypto": "13.5.30"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@prosopo/config": "3.3.
|
|
45
|
+
"@prosopo/config": "3.3.5",
|
|
46
46
|
"@types/node": "22.10.2",
|
|
47
|
-
"@vitest/coverage-v8": "
|
|
47
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
48
48
|
"concurrently": "9.0.1",
|
|
49
49
|
"del-cli": "6.0.0",
|
|
50
50
|
"npm-run-all": "4.1.5",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"tslib": "2.7.0",
|
|
53
53
|
"tsx": "4.20.3",
|
|
54
54
|
"typescript": "5.6.2",
|
|
55
|
-
"vite": "
|
|
56
|
-
"vitest": "
|
|
55
|
+
"vite": "8.1.5",
|
|
56
|
+
"vitest": "4.1.10"
|
|
57
57
|
},
|
|
58
58
|
"sideEffects": false
|
|
59
59
|
}
|