@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/dist/index.js CHANGED
@@ -1,61 +1,40 @@
1
- import { buildFingerprintProofFromComponents } from "./proof.js";
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
- const EXCLUDED_SOURCES = ["webGlExtensions"];
5
- const FingerprintJSImport = async () => import("@prosopo/fingerprintjs");
6
- let componentsCache = null;
7
- const loadComponents = () => {
8
- if (!componentsCache) {
9
- const pending = (async () => {
10
- const { sources, loadSources, prepareForSources } = await FingerprintJSImport();
11
- await prepareForSources();
12
- const sourceOptions = {
13
- cache: {}
14
- };
15
- const getComponents = loadSources(
16
- sources,
17
- sourceOptions,
18
- EXCLUDED_SOURCES
19
- );
20
- return getComponents();
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
- const getFingerprint = async () => {
32
- const [{ hashComponents }, components] = await Promise.all([
33
- FingerprintJSImport(),
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
- const getFingerprintProof = async (discloseKeys) => {
39
- const components = await loadComponents();
40
- return buildFingerprintProofFromComponents(components, discloseKeys);
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
- const prefetchFingerprint = () => {
43
- loadComponents().catch(() => {
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
- const at = (arr, index) => {
3
- const value = arr[index];
4
- if (value === void 0) {
5
- throw new Error(
6
- `merkle: index ${index} out of range (length ${arr.length})`
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
- const hashMerklePair = (left, right) => hexHashArray([left, right]);
12
- const buildMerkleLayers = (leafHashes) => {
13
- if (leafHashes.length === 0) {
14
- throw new Error("merkle: cannot build a tree with no leaves");
15
- }
16
- const layers = [[...leafHashes]];
17
- let current = leafHashes;
18
- while (current.length > 1) {
19
- const next = [];
20
- for (let i = 0; i < current.length; i += 2) {
21
- const left = at(current, i);
22
- const right = i + 1 < current.length ? at(current, i + 1) : left;
23
- next.push(hashMerklePair(left, right));
24
- }
25
- layers.push(next);
26
- current = next;
27
- }
28
- return layers;
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
- const merkleRootFromLayers = (layers) => at(at(layers, layers.length - 1), 0);
31
- const merkleProofFromLayers = (layers, leafHash) => {
32
- const proof = [];
33
- let hash = leafHash;
34
- for (let layerNum = 0; layerNum < layers.length - 1; layerNum++) {
35
- const layer = at(layers, layerNum);
36
- const index = layer.indexOf(hash);
37
- if (index === -1) {
38
- throw new Error("merkle: leaf hash not present in layer");
39
- }
40
- let partnerIndex = index % 2 === 1 ? index - 1 : index + 1;
41
- if (partnerIndex > layer.length - 1) {
42
- partnerIndex = index;
43
- }
44
- const partner = at(layer, partnerIndex);
45
- const pair = partnerIndex > index ? [hash, partner] : [partner, hash];
46
- proof.push(pair);
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
- const verifyMerkleProof = (leafHash, proof) => {
52
- try {
53
- const firstLayer = proof[0];
54
- if (firstLayer === void 0 || firstLayer.indexOf(leafHash) === -1) {
55
- return false;
56
- }
57
- if (proof.length === 1) {
58
- return at(firstLayer, 0) === leafHash;
59
- }
60
- const root = at(at(proof, proof.length - 1), 0);
61
- let hash = leafHash;
62
- for (let i = 0; i < proof.length - 1; i++) {
63
- const layer = at(proof, i);
64
- if (layer.indexOf(hash) === -1) {
65
- return false;
66
- }
67
- hash = hashMerklePair(at(layer, 0), at(layer, 1));
68
- if (at(proof, i + 1).indexOf(hash) === -1) {
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
- import { buildMerkleLayers, merkleRootFromLayers, merkleProofFromLayers, verifyMerkleProof } from "./merkle.js";
3
- const FINGERPRINT_PROOF_VERSION = 1;
4
- const FINGERPRINT_DISCLOSURE_KEYS = [
5
- "colorDepth",
6
- "cookiesEnabled",
7
- "sessionStorage",
8
- "localStorage",
9
- "timezone",
10
- "hardwareConcurrency",
11
- "languages",
12
- "architecture"
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
- const escapeKey = (key) => key.replace(/([:|\\])/g, "\\$1");
15
- const componentLeafContent = (key, component) => {
16
- const serialized = "error" in component ? "error" : JSON.stringify(component.value);
17
- return `${escapeKey(key)}:${serialized}`;
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
- const disclosureLeafContent = (disclosure) => {
20
- const serialized = disclosure.error ? "error" : JSON.stringify(disclosure.value);
21
- return `${escapeKey(disclosure.key)}:${serialized}`;
31
+ var disclosureLeafContent = (disclosure) => {
32
+ const serialized = disclosure.error ? "error" : JSON.stringify(disclosure.value);
33
+ return `${escapeKey(disclosure.key)}:${serialized}`;
22
34
  };
23
- const leafHashForContent = (content) => hexHash(content);
24
- const buildFingerprintProofFromComponents = (components, discloseKeys) => {
25
- const keys = Object.keys(components).sort();
26
- if (keys.length === 0) {
27
- throw new Error("fingerprint proof: no components to commit to");
28
- }
29
- const leafHashes = keys.map((key) => {
30
- const component = components[key];
31
- if (component === void 0) {
32
- throw new Error(`fingerprint proof: missing component ${key}`);
33
- }
34
- return leafHashForContent(componentLeafContent(key, component));
35
- });
36
- const layers = buildMerkleLayers(leafHashes);
37
- const root = merkleRootFromLayers(layers);
38
- const disclose = discloseKeys ?? keys;
39
- const disclosures = [];
40
- for (const key of disclose) {
41
- const index = keys.indexOf(key);
42
- if (index === -1) {
43
- continue;
44
- }
45
- const component = components[key];
46
- const leafHash = leafHashes[index];
47
- if (component === void 0 || leafHash === void 0) {
48
- continue;
49
- }
50
- const proof = merkleProofFromLayers(layers, leafHash);
51
- disclosures.push(
52
- "error" in component ? { key, error: true, proof } : { key, value: component.value, proof }
53
- );
54
- }
55
- return { v: FINGERPRINT_PROOF_VERSION, root, disclosures };
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
- const verifyFingerprintProofStructure = (proof) => {
58
- if (proof.v !== FINGERPRINT_PROOF_VERSION) {
59
- return false;
60
- }
61
- if (typeof proof.root !== "string" || proof.disclosures.length === 0) {
62
- return false;
63
- }
64
- for (const disclosure of proof.disclosures) {
65
- const leafHash = leafHashForContent(disclosureLeafContent(disclosure));
66
- if (!verifyMerkleProof(leafHash, disclosure.proof)) {
67
- return false;
68
- }
69
- const rootLayer = disclosure.proof[disclosure.proof.length - 1];
70
- if (rootLayer === void 0 || rootLayer[0] !== proof.root) {
71
- return false;
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.18",
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.9.12",
41
+ "@prosopo/types": "4.10.0",
42
42
  "@prosopo/util-crypto": "13.5.30"
43
43
  },
44
44
  "devDependencies": {
45
- "@prosopo/config": "3.3.4",
45
+ "@prosopo/config": "3.3.5",
46
46
  "@types/node": "22.10.2",
47
- "@vitest/coverage-v8": "3.2.4",
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": "6.4.1",
56
- "vitest": "3.2.4"
55
+ "vite": "8.1.5",
56
+ "vitest": "4.1.10"
57
57
  },
58
58
  "sideEffects": false
59
59
  }