@mikeargento/bitgraph 1.9.0 → 1.9.2
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/LICENSE +1 -1
- package/README.md +58 -35
- package/dist/constructor.js +1 -1
- package/dist/constructor.js.map +1 -1
- package/dist/fuse-cli.js +1 -1
- package/dist/fuse-cli.js.map +1 -1
- package/dist/fuse.js +1 -1
- package/dist/fuse.js.map +1 -1
- package/dist/host.js +1 -1
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/policy.js +1 -1
- package/dist/policy.js.map +1 -1
- package/package.json +23 -8
- package/src/constructor.ts +1 -1
- package/src/fuse-cli.ts +1 -1
- package/src/fuse.ts +1 -1
- package/src/host.ts +1 -1
- package/src/index.ts +3 -2
- package/src/policy.ts +1 -1
- package/dist/__tests__/audit-anchors.test.d.ts +0 -2
- package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
- package/dist/__tests__/audit-anchors.test.js +0 -152
- package/dist/__tests__/audit-anchors.test.js.map +0 -1
- package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
- package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
- package/dist/__tests__/audit-anomalies.test.js +0 -663
- package/dist/__tests__/audit-anomalies.test.js.map +0 -1
- package/dist/__tests__/audit-attestation.test.d.ts +0 -2
- package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
- package/dist/__tests__/audit-attestation.test.js +0 -403
- package/dist/__tests__/audit-attestation.test.js.map +0 -1
- package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
- package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
- package/dist/__tests__/audit-bench-smoke.test.js +0 -87
- package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
- package/dist/__tests__/audit-boundary.test.d.ts +0 -20
- package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
- package/dist/__tests__/audit-boundary.test.js +0 -84
- package/dist/__tests__/audit-boundary.test.js.map +0 -1
- package/dist/__tests__/audit-cli.test.d.ts +0 -2
- package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
- package/dist/__tests__/audit-cli.test.js +0 -239
- package/dist/__tests__/audit-cli.test.js.map +0 -1
- package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
- package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
- package/dist/__tests__/audit-dependencies.test.js +0 -116
- package/dist/__tests__/audit-dependencies.test.js.map +0 -1
- package/dist/__tests__/audit-fixtures.d.ts +0 -265
- package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
- package/dist/__tests__/audit-fixtures.js +0 -511
- package/dist/__tests__/audit-fixtures.js.map +0 -1
- package/dist/__tests__/audit-ingest.test.d.ts +0 -2
- package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
- package/dist/__tests__/audit-ingest.test.js +0 -473
- package/dist/__tests__/audit-ingest.test.js.map +0 -1
- package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
- package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
- package/dist/__tests__/audit-memory-ingest.test.js +0 -135
- package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
- package/dist/__tests__/audit-property.test.d.ts +0 -2
- package/dist/__tests__/audit-property.test.d.ts.map +0 -1
- package/dist/__tests__/audit-property.test.js +0 -771
- package/dist/__tests__/audit-property.test.js.map +0 -1
- package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
- package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
- package/dist/__tests__/audit-real-fixtures.test.js +0 -132
- package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
- package/dist/__tests__/audit-real-live.test.d.ts +0 -2
- package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
- package/dist/__tests__/audit-real-live.test.js +0 -107
- package/dist/__tests__/audit-real-live.test.js.map +0 -1
- package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
- package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
- package/dist/__tests__/audit-reconstruct.test.js +0 -287
- package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
- package/dist/__tests__/audit-report.test.d.ts +0 -2
- package/dist/__tests__/audit-report.test.d.ts.map +0 -1
- package/dist/__tests__/audit-report.test.js +0 -186
- package/dist/__tests__/audit-report.test.js.map +0 -1
- package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/audit-roundtrip.test.js +0 -368
- package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
- package/dist/__tests__/audit-temporal.test.d.ts +0 -2
- package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
- package/dist/__tests__/audit-temporal.test.js +0 -364
- package/dist/__tests__/audit-temporal.test.js.map +0 -1
- package/dist/__tests__/audit-tiers.test.d.ts +0 -2
- package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
- package/dist/__tests__/audit-tiers.test.js +0 -282
- package/dist/__tests__/audit-tiers.test.js.map +0 -1
- package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
- package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
- package/dist/__tests__/audit-witness-real.test.js +0 -82
- package/dist/__tests__/audit-witness-real.test.js.map +0 -1
- package/dist/__tests__/audit-witness.test.d.ts +0 -2
- package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
- package/dist/__tests__/audit-witness.test.js +0 -308
- package/dist/__tests__/audit-witness.test.js.map +0 -1
- package/dist/__tests__/canonical.test.d.ts +0 -2
- package/dist/__tests__/canonical.test.d.ts.map +0 -1
- package/dist/__tests__/canonical.test.js +0 -162
- package/dist/__tests__/canonical.test.js.map +0 -1
- package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
- package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
- package/dist/__tests__/chain-hash-anchor.test.js +0 -55
- package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
- package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
- package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/commit-service.integration.test.js +0 -95
- package/dist/__tests__/commit-service.integration.test.js.map +0 -1
- package/dist/__tests__/constructor.test.d.ts +0 -2
- package/dist/__tests__/constructor.test.d.ts.map +0 -1
- package/dist/__tests__/constructor.test.js +0 -267
- package/dist/__tests__/constructor.test.js.map +0 -1
- package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
- package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/declared-proof-fixture.js +0 -66
- package/dist/__tests__/declared-proof-fixture.js.map +0 -1
- package/dist/__tests__/fuse-placement.test.d.ts +0 -2
- package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-placement.test.js +0 -31
- package/dist/__tests__/fuse-placement.test.js.map +0 -1
- package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-roundtrip.test.js +0 -241
- package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
- package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
- package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-sdk.test.js +0 -117
- package/dist/__tests__/fuse-sdk.test.js.map +0 -1
- package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
- package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set-sdk.test.js +0 -721
- package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
- package/dist/__tests__/fuse-set.test.d.ts +0 -2
- package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set.test.js +0 -868
- package/dist/__tests__/fuse-set.test.js.map +0 -1
- package/dist/__tests__/fuse-set2.test.d.ts +0 -2
- package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set2.test.js +0 -294
- package/dist/__tests__/fuse-set2.test.js.map +0 -1
- package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
- package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-vectors.test.js +0 -175
- package/dist/__tests__/fuse-vectors.test.js.map +0 -1
- package/dist/__tests__/fuse-verify.test.d.ts +0 -2
- package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-verify.test.js +0 -250
- package/dist/__tests__/fuse-verify.test.js.map +0 -1
- package/dist/__tests__/hello-world.test.d.ts +0 -2
- package/dist/__tests__/hello-world.test.d.ts.map +0 -1
- package/dist/__tests__/hello-world.test.js +0 -9
- package/dist/__tests__/hello-world.test.js.map +0 -1
- package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
- package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/mock-proof-fixture.js +0 -34
- package/dist/__tests__/mock-proof-fixture.js.map +0 -1
- package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
- package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
- package/dist/__tests__/policy-enforcement.test.js +0 -188
- package/dist/__tests__/policy-enforcement.test.js.map +0 -1
- package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
- package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
- package/dist/__tests__/proof-hash-regression.test.js +0 -95
- package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
- package/dist/__tests__/proof-hash.test.d.ts +0 -2
- package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
- package/dist/__tests__/proof-hash.test.js +0 -116
- package/dist/__tests__/proof-hash.test.js.map +0 -1
- package/dist/__tests__/proof-integrity.test.d.ts +0 -2
- package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
- package/dist/__tests__/proof-integrity.test.js +0 -426
- package/dist/__tests__/proof-integrity.test.js.map +0 -1
- package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
- package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/realistic-proof-fixture.js +0 -57
- package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
- package/dist/__tests__/verifier.test.d.ts +0 -2
- package/dist/__tests__/verifier.test.d.ts.map +0 -1
- package/dist/__tests__/verifier.test.js +0 -531
- package/dist/__tests__/verifier.test.js.map +0 -1
- package/dist/canonical.d.ts +0 -54
- package/dist/canonical.d.ts.map +0 -1
- package/dist/canonical.js +0 -119
- package/dist/canonical.js.map +0 -1
- package/dist/proof-hash.d.ts +0 -9
- package/dist/proof-hash.d.ts.map +0 -1
- package/dist/proof-hash.js +0 -75
- package/dist/proof-hash.js.map +0 -1
- package/dist/types.d.ts +0 -705
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/verifier.d.ts +0 -27
- package/dist/verifier.d.ts.map +0 -1
- package/dist/verifier.js +0 -844
- package/dist/verifier.js.map +0 -1
- package/src/__tests__/audit-anchors.test.ts +0 -188
- package/src/__tests__/audit-anomalies.test.ts +0 -815
- package/src/__tests__/audit-attestation.test.ts +0 -529
- package/src/__tests__/audit-bench-smoke.test.ts +0 -108
- package/src/__tests__/audit-boundary.test.ts +0 -96
- package/src/__tests__/audit-cli.test.ts +0 -288
- package/src/__tests__/audit-dependencies.test.ts +0 -141
- package/src/__tests__/audit-fixtures.ts +0 -736
- package/src/__tests__/audit-ingest.test.ts +0 -575
- package/src/__tests__/audit-memory-ingest.test.ts +0 -156
- package/src/__tests__/audit-property.test.ts +0 -892
- package/src/__tests__/audit-real-fixtures.test.ts +0 -153
- package/src/__tests__/audit-real-live.test.ts +0 -128
- package/src/__tests__/audit-reconstruct.test.ts +0 -353
- package/src/__tests__/audit-report.test.ts +0 -222
- package/src/__tests__/audit-roundtrip.test.ts +0 -464
- package/src/__tests__/audit-temporal.test.ts +0 -446
- package/src/__tests__/audit-tiers.test.ts +0 -343
- package/src/__tests__/audit-witness-real.test.ts +0 -92
- package/src/__tests__/audit-witness.test.ts +0 -345
- package/src/__tests__/canonical.test.ts +0 -253
- package/src/__tests__/chain-hash-anchor.test.ts +0 -67
- package/src/__tests__/commit-service.integration.test.ts +0 -117
- package/src/__tests__/constructor.test.ts +0 -340
- package/src/__tests__/declared-proof-fixture.ts +0 -65
- package/src/__tests__/fuse-placement.test.ts +0 -32
- package/src/__tests__/fuse-roundtrip.test.ts +0 -243
- package/src/__tests__/fuse-sdk.test.ts +0 -142
- package/src/__tests__/fuse-set-sdk.test.ts +0 -852
- package/src/__tests__/fuse-set.test.ts +0 -1005
- package/src/__tests__/fuse-set2.test.ts +0 -343
- package/src/__tests__/fuse-vectors.test.ts +0 -218
- package/src/__tests__/fuse-verify.test.ts +0 -278
- package/src/__tests__/hello-world.test.ts +0 -10
- package/src/__tests__/mock-proof-fixture.ts +0 -36
- package/src/__tests__/policy-enforcement.test.ts +0 -226
- package/src/__tests__/proof-hash-regression.test.ts +0 -107
- package/src/__tests__/proof-hash.test.ts +0 -129
- package/src/__tests__/proof-integrity.test.ts +0 -541
- package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
- package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
- package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
- package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
- package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
- package/src/__tests__/real-fixtures/successor-112760.json +0 -38
- package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
- package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
- package/src/__tests__/realistic-proof-fixture.ts +0 -57
- package/src/__tests__/verifier.test.ts +0 -624
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* BitGraph Fuse: reconstruction round trips (spec 15.3). A placement enters
|
|
5
|
-
* the registry only with this test passing: original plus proof rebuilds
|
|
6
|
-
* bytes whose hash equals the artifact digest, from fixed vectors.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, test } from "node:test";
|
|
10
|
-
import * as assert from "node:assert/strict";
|
|
11
|
-
import { readFileSync } from "node:fs";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
import { sha256 } from "@noble/hashes/sha256";
|
|
14
|
-
import { PLACEMENTS, getPlacement, computeSlotCommitment, bytesToBase64, buildFrame, parseFrame, readFrameMarker, readFuseAttribution, fuseAttribution } from "@mikeargento/bitgraph-verify";
|
|
15
|
-
import type { BitGraphProof } from "@mikeargento/bitgraph-verify";
|
|
16
|
-
|
|
17
|
-
const FIX = fileURLToPath(new URL("../../src/__tests__/fuse-fixtures/", import.meta.url));
|
|
18
|
-
const bytes = (name: string) => new Uint8Array(readFileSync(FIX + name));
|
|
19
|
-
const proofOf = (name: string) => JSON.parse(readFileSync(FIX + name, "utf8")) as BitGraphProof;
|
|
20
|
-
|
|
21
|
-
describe("registry", () => {
|
|
22
|
-
test("fixed order and ids", () => {
|
|
23
|
-
assert.deepEqual(PLACEMENTS.map((p) => p.id), ["trailer/1", "container/1", "container/2", "produced/1"]);
|
|
24
|
-
assert.deepEqual(PLACEMENTS.map((p) => p.byteExact), [true, true, true, false]);
|
|
25
|
-
assert.equal(getPlacement("xmp/9"), undefined);
|
|
26
|
-
assert.equal(getPlacement(""), undefined);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("trailer/1", () => {
|
|
31
|
-
const p = getPlacement("trailer/1")!;
|
|
32
|
-
test("original + proof rebuilds the committed bytes", () => {
|
|
33
|
-
const proof = proofOf("trailer.proof.json");
|
|
34
|
-
const rebuilt = p.build({ original: bytes("original.txt"), commitment: computeSlotCommitment(proof.slotAllocation!) });
|
|
35
|
-
assert.deepEqual(rebuilt, bytes("fused-trailer.bin"));
|
|
36
|
-
assert.equal(bytesToBase64(sha256(rebuilt)), proof.artifact.digestB64);
|
|
37
|
-
});
|
|
38
|
-
test("locate returns the commitment and the exact original prefix", () => {
|
|
39
|
-
const proof = proofOf("trailer.proof.json");
|
|
40
|
-
const l = p.locate(bytes("fused-trailer.bin"))!;
|
|
41
|
-
assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
|
|
42
|
-
assert.deepEqual(l.originalBytes, bytes("original.txt"));
|
|
43
|
-
assert.equal(l.originDigest, undefined, "trailer/1 writes no origin digest");
|
|
44
|
-
});
|
|
45
|
-
test("locate refuses short input, wrong magic, non-zero reserved bytes", () => {
|
|
46
|
-
assert.equal(p.locate(new Uint8Array(47)), null);
|
|
47
|
-
const f = bytes("fused-trailer.bin");
|
|
48
|
-
const magic = f.slice(); magic[magic.length - 48] = 0x58;
|
|
49
|
-
assert.equal(p.locate(magic), null);
|
|
50
|
-
const reserved = f.slice(); reserved[reserved.length - 40] = 1;
|
|
51
|
-
assert.equal(p.locate(reserved), null);
|
|
52
|
-
assert.equal(p.locate(bytes("original.txt")), null, "an unfused file has no trailer");
|
|
53
|
-
});
|
|
54
|
-
test("build refuses a bad commitment length and a missing original", () => {
|
|
55
|
-
assert.throws(() => p.build({ commitment: new Uint8Array(32) }), /original/);
|
|
56
|
-
assert.throws(() => p.build({ original: new Uint8Array(1), commitment: new Uint8Array(31) }), /32 bytes/);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("container/1", () => {
|
|
61
|
-
const p = getPlacement("container/1")!;
|
|
62
|
-
test("original + proof rebuilds the committed archive byte for byte, twice", () => {
|
|
63
|
-
const proof = proofOf("container.proof.json");
|
|
64
|
-
const c = computeSlotCommitment(proof.slotAllocation!);
|
|
65
|
-
const a = p.build({ original: bytes("image.png"), commitment: c });
|
|
66
|
-
const b = p.build({ original: bytes("image.png"), commitment: c });
|
|
67
|
-
assert.deepEqual(a, b, "deterministic");
|
|
68
|
-
assert.deepEqual(a, bytes("fused-container.tar"));
|
|
69
|
-
assert.equal(bytesToBase64(sha256(a)), proof.artifact.digestB64);
|
|
70
|
-
});
|
|
71
|
-
test("locate returns commitment, origin digest and the original bytes", () => {
|
|
72
|
-
const proof = proofOf("container.proof.json");
|
|
73
|
-
const l = p.locate(bytes("fused-container.tar"))!;
|
|
74
|
-
assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
|
|
75
|
-
assert.deepEqual(l.originDigest, sha256(bytes("image.png")));
|
|
76
|
-
assert.deepEqual(l.originalBytes, bytes("image.png"));
|
|
77
|
-
});
|
|
78
|
-
test("locate refuses any deviation from the fixed layout", () => {
|
|
79
|
-
const f = bytes("fused-container.tar");
|
|
80
|
-
const mtime = f.slice(); mtime[136] = 0x31; // manifest header mtime
|
|
81
|
-
assert.equal(p.locate(mtime), null, "non-zero mtime");
|
|
82
|
-
const uid = f.slice(); uid[108] = 0x31;
|
|
83
|
-
assert.equal(p.locate(uid), null, "uid");
|
|
84
|
-
const truncated = f.subarray(0, f.length - 512);
|
|
85
|
-
assert.equal(p.locate(truncated), null, "missing end block");
|
|
86
|
-
const extra = new Uint8Array(f.length + 512); extra.set(f);
|
|
87
|
-
assert.equal(p.locate(extra), null, "extra trailing block");
|
|
88
|
-
// Swap entry order: rebuild with the original first is not this placement.
|
|
89
|
-
const swapped = f.slice(); swapped[0] = 0x78; // corrupt the manifest name
|
|
90
|
-
assert.equal(p.locate(swapped), null, "wrong entry name");
|
|
91
|
-
assert.equal(p.locate(bytes("image.png")), null, "not a tar");
|
|
92
|
-
assert.equal(p.locate(bytes("fused-trailer.bin")), null);
|
|
93
|
-
});
|
|
94
|
-
test("an archive with a wrong manifest origin does not locate", () => {
|
|
95
|
-
const proof = proofOf("container.proof.json");
|
|
96
|
-
const c = computeSlotCommitment(proof.slotAllocation!);
|
|
97
|
-
const lying = p.build({ original: bytes("image.png"), originDigest: sha256(bytes("original.txt")), commitment: c });
|
|
98
|
-
// build accepts a caller-supplied origin digest; locate returns what is written,
|
|
99
|
-
// and the verifier compares it against the marker and the original's hash.
|
|
100
|
-
const l = p.locate(lying)!;
|
|
101
|
-
assert.deepEqual(l.originDigest, sha256(bytes("original.txt")));
|
|
102
|
-
assert.notDeepEqual(l.originDigest, sha256(l.originalBytes!));
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe("produced/1", () => {
|
|
107
|
-
const p = getPlacement("produced/1")!;
|
|
108
|
-
test("build and locate agree, with and without an origin", () => {
|
|
109
|
-
for (const [name, proofName] of [["produced-origin.json", "produced-origin.proof.json"], ["produced-bare.json", "produced-bare.proof.json"]] as const) {
|
|
110
|
-
const proof = proofOf(proofName);
|
|
111
|
-
const l = p.locate(bytes(name))!;
|
|
112
|
-
assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
|
|
113
|
-
assert.equal(bytesToBase64(sha256(bytes(name))), proof.artifact.digestB64);
|
|
114
|
-
}
|
|
115
|
-
assert.equal(p.locate(bytes("original.txt")), null);
|
|
116
|
-
assert.throws(() => p.build({ original: new Uint8Array(1), commitment: new Uint8Array(32) }), /no original/);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("legacy files that carry a nonce line", () => {
|
|
121
|
-
test("a file with a 'nonce:' line and no trailer, container or payload locates nothing", () => {
|
|
122
|
-
// ~/BitGraph holds recordings whose bytes begin with a test label and a line
|
|
123
|
-
// "nonce: <hex>" (the proto pattern, minted 2026-06-27 and 2026-08-06). That
|
|
124
|
-
// line is content, not a commitment: every registered placement must miss.
|
|
125
|
-
const legacy = new TextEncoder().encode("BitGraph test subject folder-02 file-02\nnonce: e317096692576a308263bc283d66b42e\nuoJjpYb8igrEhhSlczKKTKDyw6guO2iU5iHdBRF+mPELQqBfjrO2WZYrSWyHEuKK\n");
|
|
126
|
-
for (const p of PLACEMENTS) assert.equal(p.locate(legacy), null, p.id);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("attribution and Frame", () => {
|
|
131
|
-
test("fused attribution carries placement in title and origin in message (standard base64)", () => {
|
|
132
|
-
const origin = sha256(bytes("original.txt"));
|
|
133
|
-
const a = fuseAttribution("trailer/1", origin);
|
|
134
|
-
assert.deepEqual(a, { name: "bitgraph-fuse/1", title: "trailer/1", message: bytesToBase64(origin) });
|
|
135
|
-
assert.deepEqual(fuseAttribution("produced/1"), { name: "bitgraph-fuse/1", title: "produced/1" });
|
|
136
|
-
const proof = proofOf("trailer.proof.json");
|
|
137
|
-
const m = readFuseAttribution(proof)!;
|
|
138
|
-
assert.equal(m.placement, "trailer/1");
|
|
139
|
-
assert.equal(m.placementSource, "attribution");
|
|
140
|
-
assert.equal(m.source, "attribution");
|
|
141
|
-
assert.deepEqual(m.originDigest, origin);
|
|
142
|
-
assert.equal(m.originSource, "attribution");
|
|
143
|
-
const bare = readFuseAttribution(proofOf("trailer-undeclared.proof.json"))!;
|
|
144
|
-
assert.equal(bare.originDigest, undefined);
|
|
145
|
-
assert.equal(bare.originSource, undefined);
|
|
146
|
-
assert.equal(readFuseAttribution(proofOf("recorded.proof.json")), null, "an ordinary proof has no marker");
|
|
147
|
-
});
|
|
148
|
-
test("a Frame parses regardless of outer formatting and the nested proof is untouched", () => {
|
|
149
|
-
const text = readFileSync(FIX + "trailer.bitgraph-fuse.json", "utf8");
|
|
150
|
-
const a = parseFrame(text)!;
|
|
151
|
-
const b = parseFrame(JSON.stringify(JSON.parse(text)))!;
|
|
152
|
-
assert.deepEqual(a, b);
|
|
153
|
-
assert.equal(a.manifest.placement, "trailer/1");
|
|
154
|
-
assert.deepEqual(a.proof, proofOf("trailer.proof.json"));
|
|
155
|
-
const marker = readFrameMarker(a);
|
|
156
|
-
assert.equal(marker.source, "manifest");
|
|
157
|
-
assert.equal(marker.placementSource, "manifest");
|
|
158
|
-
assert.deepEqual(marker.originDigest, sha256(bytes("original.txt")));
|
|
159
|
-
const undeclared = parseFrame({ type: "bitgraph-fuse/1", manifest: { artifact: { algorithm: "sha256", digest: "00".repeat(32) }, fusedFile: null }, proof: proofOf("trailer.proof.json") })!;
|
|
160
|
-
assert.equal(readFrameMarker(undeclared).placement, null, "a manifest may leave the placement undeclared");
|
|
161
|
-
assert.equal(parseFrame(proofOf("trailer.proof.json")), null, "a bare proof is not a Frame");
|
|
162
|
-
assert.equal(parseFrame("{not json"), null);
|
|
163
|
-
assert.equal(parseFrame({ type: "bitgraph-fuse/1", manifest: { placement: "trailer/1", artifact: { algorithm: "sha256", digest: "zz" }, fusedFile: null }, proof: {} }), null);
|
|
164
|
-
});
|
|
165
|
-
test("a Frame is never proof-shaped", () => {
|
|
166
|
-
const f = JSON.parse(readFileSync(FIX + "produced-origin.bitgraph-fuse.json", "utf8")) as Record<string, unknown>;
|
|
167
|
-
assert.equal(f.version, undefined);
|
|
168
|
-
assert.equal(f.artifact, undefined);
|
|
169
|
-
assert.equal(f.signer, undefined);
|
|
170
|
-
assert.equal(f.type, "bitgraph-fuse/1");
|
|
171
|
-
assert.ok(f.fusePayload, "Form C frames carry the parsed payload view");
|
|
172
|
-
assert.throws(() => buildFrame({ proof: proofOf("produced-origin.proof.json"), placement: "produced/1", artifactDigest: new Uint8Array(32), fusedFile: null, fusePayload: new Uint8Array([1]) }), /canonical/);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("container/2: the original first, then the manifest", () => {
|
|
177
|
-
const p = getPlacement("container/2")!;
|
|
178
|
-
const commitment = new Uint8Array(32).fill(0x5a);
|
|
179
|
-
const other = new Uint8Array(32).fill(0xa5);
|
|
180
|
-
const sizes = [0, 1, 511, 512, 513, 1023, 1024, 1025, 100_000, 1_048_579];
|
|
181
|
-
const filled = (n: number) => { const b = new Uint8Array(n); for (let i = 0; i < n; i++) b[i] = (i * 13 + 7) & 0xff; return b; };
|
|
182
|
-
|
|
183
|
-
test("build carries the original whole and first; locate reads it back and each container refuses the other's bytes", () => {
|
|
184
|
-
for (const n of sizes) {
|
|
185
|
-
const original = filled(n);
|
|
186
|
-
const fused = p.build({ original, commitment });
|
|
187
|
-
assert.equal(new TextDecoder().decode(fused.subarray(0, 22)), "bitgraph-fuse/original");
|
|
188
|
-
const l = p.locate(fused);
|
|
189
|
-
assert.ok(l, `locate ${n}`);
|
|
190
|
-
assert.deepEqual(l!.commitment, commitment);
|
|
191
|
-
assert.deepEqual(l!.originDigest, sha256(original));
|
|
192
|
-
assert.deepEqual(l!.originalBytes, original);
|
|
193
|
-
const c1 = getPlacement("container/1")!.build({ original, commitment });
|
|
194
|
-
assert.equal(p.locate(c1), null, `container/2 refuses container/1 bytes (${n})`);
|
|
195
|
-
assert.equal(getPlacement("container/1")!.locate(fused), null, `container/1 refuses container/2 bytes (${n})`);
|
|
196
|
-
const tampered = new Uint8Array(fused); tampered[tampered.length - 600] = (tampered[tampered.length - 600] ?? 0) ^ 1;
|
|
197
|
-
assert.equal(p.locate(tampered), null);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("frame: prefix, original, suffix is the build, byte for byte, for every Form A and B placement and size", () => {
|
|
202
|
-
for (const id of ["trailer/1", "container/1", "container/2"] as const) {
|
|
203
|
-
const q = getPlacement(id)!;
|
|
204
|
-
for (const n of sizes) {
|
|
205
|
-
const original = filled(n);
|
|
206
|
-
const originDigest = sha256(original);
|
|
207
|
-
for (const c of [commitment, other]) {
|
|
208
|
-
const f = q.frame!({ originalSize: n, originDigest, commitment: c });
|
|
209
|
-
const joined = new Uint8Array(f.prefix.length + n + f.suffix.length);
|
|
210
|
-
joined.set(f.prefix, 0); joined.set(original, f.prefix.length); joined.set(f.suffix, f.prefix.length + n);
|
|
211
|
-
assert.deepEqual(joined, q.build({ original, originDigest, commitment: c }), `${id} ${n}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("scanPrefix: fixed for trailer/1 (empty) and container/2 (the original's header), null for container/1 whose prefix carries the commitment", () => {
|
|
218
|
-
for (const n of sizes) {
|
|
219
|
-
const originDigest = sha256(filled(n));
|
|
220
|
-
assert.deepEqual(getPlacement("trailer/1")!.scanPrefix!(n), new Uint8Array(0));
|
|
221
|
-
const pre = p.scanPrefix!(n)!;
|
|
222
|
-
assert.equal(pre.length, 512);
|
|
223
|
-
for (const c of [commitment, other]) assert.deepEqual(p.frame!({ originalSize: n, originDigest, commitment: c }).prefix, pre, `the same prefix under any commitment (${n})`);
|
|
224
|
-
assert.equal(getPlacement("container/1")!.scanPrefix!(n), null);
|
|
225
|
-
}
|
|
226
|
-
assert.equal(getPlacement("produced/1")!.frame, undefined);
|
|
227
|
-
assert.equal(getPlacement("set/1")!.frame, undefined);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("a hasher state saved after prefix and original, finished with the suffix, is the fused digest: the file is read once", () => {
|
|
231
|
-
for (const n of [0, 1, 511, 512, 513, 100_000]) {
|
|
232
|
-
const original = filled(n);
|
|
233
|
-
const originDigest = sha256(original);
|
|
234
|
-
const h = sha256.create();
|
|
235
|
-
h.update(p.scanPrefix!(n)!);
|
|
236
|
-
h.update(original);
|
|
237
|
-
for (const c of [commitment, other]) {
|
|
238
|
-
const finished = h.clone().update(p.frame!({ originalSize: n, originDigest, commitment: c }).suffix).digest();
|
|
239
|
-
assert.deepEqual(finished, sha256(p.build({ original, originDigest, commitment: c })), `${n} bytes`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* fuse() against a fake transport built from real fixture proofs (minted
|
|
5
|
-
* through the local enclave harness). Every refusal path runs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test } from "node:test";
|
|
9
|
-
import * as assert from "node:assert/strict";
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { fuse, builderFor, FuseError } from "../fuse.js";
|
|
13
|
-
import type { BitGraphProof, SlotAllocation } from "../fuse.js";
|
|
14
|
-
|
|
15
|
-
const FIX = fileURLToPath(new URL("../../src/__tests__/fuse-fixtures/", import.meta.url));
|
|
16
|
-
const bytes = (name: string) => new Uint8Array(readFileSync(FIX + name));
|
|
17
|
-
const proofOf = (name: string) => JSON.parse(readFileSync(FIX + name, "utf8")) as BitGraphProof;
|
|
18
|
-
|
|
19
|
-
interface Call { path: string; body: unknown }
|
|
20
|
-
|
|
21
|
-
/** A transport that hands out `slot` and answers the commit with `answer`. */
|
|
22
|
-
function fakeTransport(slot: SlotAllocation, answer: (calls: Call[], body: unknown) => { status: number; json: unknown }, lookup?: (calls: Call[]) => { status: number; json: unknown }) {
|
|
23
|
-
const calls: Call[] = [];
|
|
24
|
-
const f: typeof fetch = async (input, init) => {
|
|
25
|
-
const url = String(input);
|
|
26
|
-
const path = url.replace(/^https?:\/\/[^/]+/, "");
|
|
27
|
-
const body = init?.body ? JSON.parse(String(init.body)) : undefined;
|
|
28
|
-
calls.push({ path, body });
|
|
29
|
-
const reply = (r: { status: number; json: unknown }) => new Response(JSON.stringify(r.json), { status: r.status, headers: { "Content-Type": "application/json" } });
|
|
30
|
-
if (path === "/api/fuse/allocate") return reply({ status: 200, json: { slotId: slot.nonceB64, slot, chainId: "bitgraph:main" } });
|
|
31
|
-
if (path === "/api/fuse/commit") return reply(answer(calls, body));
|
|
32
|
-
if (path.startsWith("/api/proofs/")) return reply(lookup ? lookup(calls) : { status: 200, json: { proofs: [] } });
|
|
33
|
-
return reply({ status: 404, json: { error: "no route" } });
|
|
34
|
-
};
|
|
35
|
-
return { calls, transport: { baseUrl: "https://example.test", fetch: f, recoveryAttempts: 2, recoveryDelayMs: 1 } };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe("fuse(): the four beats", () => {
|
|
39
|
-
test("trailer/1 over an existing original: allocate, fuse, hash, fill; Frame carries the unchanged proof", async () => {
|
|
40
|
-
const proof = proofOf("trailer.proof.json");
|
|
41
|
-
const { calls, transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 200, json: { proof } }));
|
|
42
|
-
const original = bytes("original.txt");
|
|
43
|
-
const r = await fuse(builderFor("trailer/1", original), { placement: "trailer/1", original, fusedFile: "photo.jpg", transport });
|
|
44
|
-
assert.equal(r.artifactDigestB64, proof.artifact.digestB64);
|
|
45
|
-
assert.deepEqual(r.proof, proof);
|
|
46
|
-
assert.equal(r.frame.type, "bitgraph-fuse/1");
|
|
47
|
-
assert.equal(r.frame.manifest.placement, "trailer/1");
|
|
48
|
-
assert.equal(r.frame.manifest.fusedFile, "photo.jpg");
|
|
49
|
-
assert.deepEqual(r.frame.proof, proof);
|
|
50
|
-
assert.equal(r.recovered, false);
|
|
51
|
-
assert.equal(r.verification.category, "FUSED_DIRECT");
|
|
52
|
-
assert.equal(r.fusedBytes, undefined, "byte-exact placements evaporate by default");
|
|
53
|
-
// The commit carried the placement and origin in the attribution, and named the slot.
|
|
54
|
-
const commit = calls.find((c) => c.path === "/api/fuse/commit")!.body as Record<string, unknown>;
|
|
55
|
-
assert.equal(commit.slotId, proof.slotAllocation!.nonceB64);
|
|
56
|
-
assert.deepEqual(commit.attribution, { name: "bitgraph-fuse/1", title: "trailer/1", message: r.originDigestB64 });
|
|
57
|
-
assert.deepEqual(commit.digests, [{ digestB64: proof.artifact.digestB64, hashAlg: "sha256" }]);
|
|
58
|
-
assert.equal(commit.chainId, "bitgraph:main");
|
|
59
|
-
// The raw nonce appears in no error and nowhere but the slot field it must ride in.
|
|
60
|
-
assert.ok(!JSON.stringify(commit.attribution).includes(proof.slotAllocation!.nonceB64));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("produced/1 keeps its bytes and its payload view in the Frame", async () => {
|
|
64
|
-
const proof = proofOf("produced-origin.proof.json");
|
|
65
|
-
const { transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 200, json: [proof] }));
|
|
66
|
-
const originDigest = new Uint8Array(Buffer.from(proof.attribution!.message!, "base64"));
|
|
67
|
-
const r = await fuse(builderFor("produced/1"), { placement: "produced/1", originDigest, transport });
|
|
68
|
-
assert.ok(r.fusedBytes, "Form C keeps the fused bytes");
|
|
69
|
-
assert.deepEqual(r.fusedBytes, bytes("produced-origin.json"));
|
|
70
|
-
assert.ok(r.frame.fusePayload);
|
|
71
|
-
assert.equal(r.frame.fusePayload!.type, "bitgraph-fuse/1");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("a builder that drops the commitment is refused BEFORE any commit; the slot is left to expire", async () => {
|
|
75
|
-
const proof = proofOf("trailer.proof.json");
|
|
76
|
-
const { calls, transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 200, json: { proof } }));
|
|
77
|
-
await assert.rejects(
|
|
78
|
-
fuse(() => bytes("original.txt"), { placement: "trailer/1", original: bytes("original.txt"), transport }),
|
|
79
|
-
(e: FuseError) => e.code === "commitment-missing",
|
|
80
|
-
);
|
|
81
|
-
assert.equal(calls.filter((c) => c.path === "/api/fuse/commit").length, 0, "no commit was attempted");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("a proof under a different slot is never labelled fused", async () => {
|
|
85
|
-
const proof = proofOf("trailer.proof.json");
|
|
86
|
-
const other = proofOf("trailer-undeclared.proof.json");
|
|
87
|
-
const { transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 200, json: { proof: other } }));
|
|
88
|
-
await assert.rejects(
|
|
89
|
-
fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport }),
|
|
90
|
-
(e: FuseError) => e.code === "slot-mismatch",
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("409 slot-unavailable: the proof is read back by digest and matched on slotHashB64; nothing is re-allocated", async () => {
|
|
95
|
-
const proof = proofOf("trailer.proof.json");
|
|
96
|
-
const decoy = proofOf("trailer-undeclared.proof.json");
|
|
97
|
-
const { calls, transport } = fakeTransport(
|
|
98
|
-
proof.slotAllocation!,
|
|
99
|
-
() => ({ status: 409, json: { error: "gone", code: "slot-unavailable" } }),
|
|
100
|
-
() => ({ status: 200, json: { proofs: [{ proof: decoy }, { proof }] } }),
|
|
101
|
-
);
|
|
102
|
-
const r = await fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport });
|
|
103
|
-
assert.equal(r.recovered, true);
|
|
104
|
-
assert.deepEqual(r.proof, proof);
|
|
105
|
-
assert.equal(calls.filter((c) => c.path === "/api/fuse/allocate").length, 1, "allocated exactly once");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("409 with no matching proof on the ledger is reported as slot-unavailable, not retried into a new slot", async () => {
|
|
109
|
-
const proof = proofOf("trailer.proof.json");
|
|
110
|
-
const { calls, transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 409, json: { error: "gone", code: "slot-unavailable" } }));
|
|
111
|
-
await assert.rejects(
|
|
112
|
-
fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport }),
|
|
113
|
-
(e: FuseError) => e.code === "slot-unavailable" && !e.message.includes(proof.slotAllocation!.nonceB64),
|
|
114
|
-
);
|
|
115
|
-
assert.equal(calls.filter((c) => c.path === "/api/fuse/allocate").length, 1);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("tee-restarting on commit: read back first, then report; a network failure after send does the same", async () => {
|
|
119
|
-
const proof = proofOf("trailer.proof.json");
|
|
120
|
-
const a = fakeTransport(proof.slotAllocation!, () => ({ status: 503, json: { error: "restarting", code: "tee-restarting" } }), () => ({ status: 200, json: { proofs: [{ proof }] } }));
|
|
121
|
-
const r = await fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport: a.transport });
|
|
122
|
-
assert.equal(r.recovered, true);
|
|
123
|
-
const b = fakeTransport(proof.slotAllocation!, () => ({ status: 503, json: { error: "restarting", code: "tee-restarting" } }));
|
|
124
|
-
await assert.rejects(fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport: b.transport }), (e: FuseError) => e.code === "tee-restarting");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("bad inputs are refused before any request", async () => {
|
|
128
|
-
const proof = proofOf("trailer.proof.json");
|
|
129
|
-
const { calls, transport } = fakeTransport(proof.slotAllocation!, () => ({ status: 200, json: { proof } }));
|
|
130
|
-
await assert.rejects(fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "xmp/9" as never, original: bytes("original.txt"), transport }), (e: FuseError) => e.code === "bad-placement");
|
|
131
|
-
await assert.rejects(fuse(builderFor("trailer/1"), { placement: "trailer/1", transport }), (e: FuseError) => e.code === "bad-input");
|
|
132
|
-
await assert.rejects(fuse(builderFor("produced/1"), { placement: "produced/1", original: bytes("original.txt"), transport }), (e: FuseError) => e.code === "bad-input");
|
|
133
|
-
assert.equal(calls.length, 0);
|
|
134
|
-
assert.throws(() => builderFor("nope/1" as never), (e: FuseError) => e.code === "bad-placement");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("a slot off the anchored chain is refused", async () => {
|
|
138
|
-
const proof = proofOf("trailer.proof.json");
|
|
139
|
-
const { transport } = fakeTransport({ ...proof.slotAllocation!, chainId: "global" }, () => ({ status: 200, json: { proof } }));
|
|
140
|
-
await assert.rejects(fuse(builderFor("trailer/1", bytes("original.txt")), { placement: "trailer/1", original: bytes("original.txt"), transport }), (e: FuseError) => e.code === "allocate-failed" && /anchored chain/.test(e.message));
|
|
141
|
-
});
|
|
142
|
-
});
|