@mikeargento/bitgraph 1.1.0 → 1.2.0
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/README.md +49 -40
- package/dist/__tests__/audit-anchors.test.d.ts +2 -0
- package/dist/__tests__/audit-anchors.test.d.ts.map +1 -0
- package/dist/__tests__/audit-anchors.test.js +152 -0
- package/dist/__tests__/audit-anchors.test.js.map +1 -0
- package/dist/__tests__/audit-anomalies.test.d.ts +2 -0
- package/dist/__tests__/audit-anomalies.test.d.ts.map +1 -0
- package/dist/__tests__/audit-anomalies.test.js +663 -0
- package/dist/__tests__/audit-anomalies.test.js.map +1 -0
- package/dist/__tests__/audit-attestation.test.d.ts +2 -0
- package/dist/__tests__/audit-attestation.test.d.ts.map +1 -0
- package/dist/__tests__/audit-attestation.test.js +403 -0
- package/dist/__tests__/audit-attestation.test.js.map +1 -0
- package/dist/__tests__/audit-bench-smoke.test.d.ts +2 -0
- package/dist/__tests__/audit-bench-smoke.test.d.ts.map +1 -0
- package/dist/__tests__/audit-bench-smoke.test.js +87 -0
- package/dist/__tests__/audit-bench-smoke.test.js.map +1 -0
- package/dist/__tests__/audit-boundary.test.d.ts +20 -0
- package/dist/__tests__/audit-boundary.test.d.ts.map +1 -0
- package/dist/__tests__/audit-boundary.test.js +84 -0
- package/dist/__tests__/audit-boundary.test.js.map +1 -0
- package/dist/__tests__/audit-cli.test.d.ts +2 -0
- package/dist/__tests__/audit-cli.test.d.ts.map +1 -0
- package/dist/__tests__/audit-cli.test.js +239 -0
- package/dist/__tests__/audit-cli.test.js.map +1 -0
- package/dist/__tests__/audit-dependencies.test.d.ts +2 -0
- package/dist/__tests__/audit-dependencies.test.d.ts.map +1 -0
- package/dist/__tests__/audit-dependencies.test.js +116 -0
- package/dist/__tests__/audit-dependencies.test.js.map +1 -0
- package/dist/__tests__/audit-fixtures.d.ts +265 -0
- package/dist/__tests__/audit-fixtures.d.ts.map +1 -0
- package/dist/__tests__/audit-fixtures.js +511 -0
- package/dist/__tests__/audit-fixtures.js.map +1 -0
- package/dist/__tests__/audit-ingest.test.d.ts +2 -0
- package/dist/__tests__/audit-ingest.test.d.ts.map +1 -0
- package/dist/__tests__/audit-ingest.test.js +473 -0
- package/dist/__tests__/audit-ingest.test.js.map +1 -0
- package/dist/__tests__/audit-memory-ingest.test.d.ts +12 -0
- package/dist/__tests__/audit-memory-ingest.test.d.ts.map +1 -0
- package/dist/__tests__/audit-memory-ingest.test.js +120 -0
- package/dist/__tests__/audit-memory-ingest.test.js.map +1 -0
- package/dist/__tests__/audit-property.test.d.ts +2 -0
- package/dist/__tests__/audit-property.test.d.ts.map +1 -0
- package/dist/__tests__/audit-property.test.js +771 -0
- package/dist/__tests__/audit-property.test.js.map +1 -0
- package/dist/__tests__/audit-real-fixtures.test.d.ts +2 -0
- package/dist/__tests__/audit-real-fixtures.test.d.ts.map +1 -0
- package/dist/__tests__/audit-real-fixtures.test.js +132 -0
- package/dist/__tests__/audit-real-fixtures.test.js.map +1 -0
- package/dist/__tests__/audit-real-live.test.d.ts +2 -0
- package/dist/__tests__/audit-real-live.test.d.ts.map +1 -0
- package/dist/__tests__/audit-real-live.test.js +107 -0
- package/dist/__tests__/audit-real-live.test.js.map +1 -0
- package/dist/__tests__/audit-reconstruct.test.d.ts +2 -0
- package/dist/__tests__/audit-reconstruct.test.d.ts.map +1 -0
- package/dist/__tests__/audit-reconstruct.test.js +287 -0
- package/dist/__tests__/audit-reconstruct.test.js.map +1 -0
- package/dist/__tests__/audit-report.test.d.ts +2 -0
- package/dist/__tests__/audit-report.test.d.ts.map +1 -0
- package/dist/__tests__/audit-report.test.js +186 -0
- package/dist/__tests__/audit-report.test.js.map +1 -0
- package/dist/__tests__/audit-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/audit-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/audit-roundtrip.test.js +368 -0
- package/dist/__tests__/audit-roundtrip.test.js.map +1 -0
- package/dist/__tests__/audit-temporal.test.d.ts +2 -0
- package/dist/__tests__/audit-temporal.test.d.ts.map +1 -0
- package/dist/__tests__/audit-temporal.test.js +364 -0
- package/dist/__tests__/audit-temporal.test.js.map +1 -0
- package/dist/__tests__/audit-tiers.test.d.ts +2 -0
- package/dist/__tests__/audit-tiers.test.d.ts.map +1 -0
- package/dist/__tests__/audit-tiers.test.js +282 -0
- package/dist/__tests__/audit-tiers.test.js.map +1 -0
- package/dist/__tests__/audit-witness-real.test.d.ts +19 -0
- package/dist/__tests__/audit-witness-real.test.d.ts.map +1 -0
- package/dist/__tests__/audit-witness-real.test.js +82 -0
- package/dist/__tests__/audit-witness-real.test.js.map +1 -0
- package/dist/__tests__/audit-witness.test.d.ts +2 -0
- package/dist/__tests__/audit-witness.test.d.ts.map +1 -0
- package/dist/__tests__/audit-witness.test.js +308 -0
- package/dist/__tests__/audit-witness.test.js.map +1 -0
- package/dist/__tests__/chain-hash-anchor.test.d.ts +22 -0
- package/dist/__tests__/chain-hash-anchor.test.d.ts.map +1 -0
- package/dist/__tests__/chain-hash-anchor.test.js +55 -0
- package/dist/__tests__/chain-hash-anchor.test.js.map +1 -0
- package/dist/__tests__/declared-proof-fixture.d.ts +66 -0
- package/dist/__tests__/declared-proof-fixture.d.ts.map +1 -0
- package/dist/__tests__/declared-proof-fixture.js +66 -0
- package/dist/__tests__/declared-proof-fixture.js.map +1 -0
- package/dist/__tests__/fuse-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/fuse-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/fuse-roundtrip.test.js +172 -0
- package/dist/__tests__/fuse-roundtrip.test.js.map +1 -0
- package/dist/__tests__/fuse-sdk.test.d.ts +2 -0
- package/dist/__tests__/fuse-sdk.test.d.ts.map +1 -0
- package/dist/__tests__/fuse-sdk.test.js +117 -0
- package/dist/__tests__/fuse-sdk.test.js.map +1 -0
- package/dist/__tests__/fuse-vectors.test.d.ts +2 -0
- package/dist/__tests__/fuse-vectors.test.d.ts.map +1 -0
- package/dist/__tests__/fuse-vectors.test.js +175 -0
- package/dist/__tests__/fuse-vectors.test.js.map +1 -0
- package/dist/__tests__/fuse-verify.test.d.ts +2 -0
- package/dist/__tests__/fuse-verify.test.d.ts.map +1 -0
- package/dist/__tests__/fuse-verify.test.js +250 -0
- package/dist/__tests__/fuse-verify.test.js.map +1 -0
- package/dist/__tests__/mock-proof-fixture.d.ts +15 -0
- package/dist/__tests__/mock-proof-fixture.d.ts.map +1 -0
- package/dist/__tests__/mock-proof-fixture.js +34 -0
- package/dist/__tests__/mock-proof-fixture.js.map +1 -0
- package/dist/__tests__/proof-hash-regression.test.js +4 -43
- package/dist/__tests__/proof-hash-regression.test.js.map +1 -1
- package/dist/__tests__/proof-hash.test.js +3 -21
- package/dist/__tests__/proof-hash.test.js.map +1 -1
- package/dist/__tests__/proof-integrity.test.d.ts +2 -0
- package/dist/__tests__/proof-integrity.test.d.ts.map +1 -0
- package/dist/__tests__/proof-integrity.test.js +426 -0
- package/dist/__tests__/proof-integrity.test.js.map +1 -0
- package/dist/__tests__/realistic-proof-fixture.d.ts +56 -0
- package/dist/__tests__/realistic-proof-fixture.d.ts.map +1 -0
- package/dist/__tests__/realistic-proof-fixture.js +57 -0
- package/dist/__tests__/realistic-proof-fixture.js.map +1 -0
- package/dist/__tests__/verifier.test.js +74 -0
- package/dist/__tests__/verifier.test.js.map +1 -1
- package/dist/fuse-cli.d.ts +3 -0
- package/dist/fuse-cli.d.ts.map +1 -0
- package/dist/fuse-cli.js +210 -0
- package/dist/fuse-cli.js.map +1 -0
- package/dist/fuse.d.ts +75 -0
- package/dist/fuse.d.ts.map +1 -0
- package/dist/fuse.js +273 -0
- package/dist/fuse.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +19 -9
- package/src/__tests__/audit-anchors.test.ts +188 -0
- package/src/__tests__/audit-anomalies.test.ts +815 -0
- package/src/__tests__/audit-attestation.test.ts +529 -0
- package/src/__tests__/audit-bench-smoke.test.ts +108 -0
- package/src/__tests__/audit-boundary.test.ts +96 -0
- package/src/__tests__/audit-cli.test.ts +288 -0
- package/src/__tests__/audit-dependencies.test.ts +141 -0
- package/src/__tests__/audit-fixtures.ts +736 -0
- package/src/__tests__/audit-ingest.test.ts +575 -0
- package/src/__tests__/audit-memory-ingest.test.ts +140 -0
- package/src/__tests__/audit-property.test.ts +892 -0
- package/src/__tests__/audit-real-fixtures.test.ts +153 -0
- package/src/__tests__/audit-real-live.test.ts +128 -0
- package/src/__tests__/audit-reconstruct.test.ts +353 -0
- package/src/__tests__/audit-report.test.ts +222 -0
- package/src/__tests__/audit-roundtrip.test.ts +464 -0
- package/src/__tests__/audit-temporal.test.ts +446 -0
- package/src/__tests__/audit-tiers.test.ts +343 -0
- package/src/__tests__/audit-witness-real.test.ts +92 -0
- package/src/__tests__/audit-witness.test.ts +345 -0
- package/src/__tests__/chain-hash-anchor.test.ts +67 -0
- package/src/__tests__/declared-proof-fixture.ts +65 -0
- package/src/__tests__/fuse-roundtrip.test.ts +174 -0
- package/src/__tests__/fuse-sdk.test.ts +142 -0
- package/src/__tests__/fuse-vectors.test.ts +218 -0
- package/src/__tests__/fuse-verify.test.ts +278 -0
- package/src/__tests__/mock-proof-fixture.ts +36 -0
- package/src/__tests__/proof-hash-regression.test.ts +4 -44
- package/src/__tests__/proof-hash.test.ts +3 -22
- package/src/__tests__/proof-integrity.test.ts +541 -0
- package/src/__tests__/real-fixtures/anchor-after-112762.json +47 -0
- package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +47 -0
- package/src/__tests__/real-fixtures/live-proof-0.json +42 -0
- package/src/__tests__/real-fixtures/live-proof-1.json +43 -0
- package/src/__tests__/real-fixtures/live-proof-2.json +43 -0
- package/src/__tests__/real-fixtures/successor-112760.json +38 -0
- package/src/__tests__/real-fixtures/witness-25525146.json +6 -0
- package/src/__tests__/real-fixtures/witness-25525147.json +6 -0
- package/src/__tests__/realistic-proof-fixture.ts +57 -0
- package/src/__tests__/verifier.test.ts +83 -0
- package/src/fuse-cli.ts +187 -0
- package/src/fuse.ts +356 -0
- package/src/index.ts +6 -0
- 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
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Temporal bounds tests: verified-witness anchors only, one-sided bounds
|
|
5
|
+
* with honest semantics (not-before is grounded in block-hash
|
|
6
|
+
* unpredictability; not-after is causal precedence whose wall-clock
|
|
7
|
+
* reading carries an explicit freshness assumption), chain-link versus
|
|
8
|
+
* counter-order evidence marked, unanchored segments reported, and
|
|
9
|
+
* cross-epoch ordering only from non-overlapping covered bounds.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, before } from "node:test";
|
|
13
|
+
import * as assert from "node:assert/strict";
|
|
14
|
+
import { keccak_256 } from "@noble/hashes/sha3";
|
|
15
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
16
|
+
import {
|
|
17
|
+
deriveTemporalBounds,
|
|
18
|
+
identifyAnchors,
|
|
19
|
+
ingestBundle,
|
|
20
|
+
reconstructChains,
|
|
21
|
+
verifyAnchorWitnesses,
|
|
22
|
+
verifyObservedProofs,
|
|
23
|
+
} from "@mikeargento/bitgraph-audit";
|
|
24
|
+
import type {
|
|
25
|
+
IngestResult,
|
|
26
|
+
ReconstructionResult,
|
|
27
|
+
TemporalAnalysis,
|
|
28
|
+
TemporalSegment,
|
|
29
|
+
} from "@mikeargento/bitgraph-audit";
|
|
30
|
+
import type { BitGraphProof } from "@mikeargento/bitgraph-verify";
|
|
31
|
+
import { computeProofHash, computeChainHash } from "@mikeargento/bitgraph-verify";
|
|
32
|
+
import {
|
|
33
|
+
b64,
|
|
34
|
+
makeAnchorProof,
|
|
35
|
+
makeEthereumHeader,
|
|
36
|
+
makeKey,
|
|
37
|
+
makeTempDir,
|
|
38
|
+
proofJson,
|
|
39
|
+
signBody,
|
|
40
|
+
utf8,
|
|
41
|
+
witnessJson,
|
|
42
|
+
writeBundleDir,
|
|
43
|
+
type ManualKey,
|
|
44
|
+
} from "./audit-fixtures.js";
|
|
45
|
+
|
|
46
|
+
const T1 = 1_000_000;
|
|
47
|
+
const T2 = 2_000_000;
|
|
48
|
+
const T3 = 3_000_000;
|
|
49
|
+
const T_OVERLAP = 1_500_000;
|
|
50
|
+
|
|
51
|
+
function header(blockNumber: number, timestamp: number): { rlpHex: string; hash: string } {
|
|
52
|
+
const h = makeEthereumHeader({ blockNumber, timestamp });
|
|
53
|
+
return { rlpHex: h.headerRlpHex, hash: `0x${Buffer.from(keccak_256(h.headerBytes)).toString("hex")}` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const H1 = header(100, T1);
|
|
57
|
+
const H2 = header(101, T2);
|
|
58
|
+
const H3 = header(102, T3);
|
|
59
|
+
const H4 = header(103, T_OVERLAP);
|
|
60
|
+
|
|
61
|
+
async function userProof(
|
|
62
|
+
key: ManualKey,
|
|
63
|
+
epochId: string,
|
|
64
|
+
slot: string,
|
|
65
|
+
commit: string,
|
|
66
|
+
prevB64?: string
|
|
67
|
+
): Promise<{ proof: BitGraphProof; proofHash: string; chainHash: string }> {
|
|
68
|
+
const bytes = utf8(`temporal-${epochId}-${commit}`);
|
|
69
|
+
const commitBody: BitGraphProof["commit"] = {
|
|
70
|
+
nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))),
|
|
71
|
+
counter: commit,
|
|
72
|
+
slotCounter: slot,
|
|
73
|
+
...(prevB64 !== undefined ? { prevB64 } : {}),
|
|
74
|
+
epochId,
|
|
75
|
+
};
|
|
76
|
+
(commitBody as unknown as Record<string, unknown>)["chainId"] = "bitgraph:main";
|
|
77
|
+
const proof = await signBody(
|
|
78
|
+
key,
|
|
79
|
+
{ hashAlg: "sha256", digestB64: b64(sha256(bytes)) },
|
|
80
|
+
commitBody,
|
|
81
|
+
"test-measurement-temporal"
|
|
82
|
+
);
|
|
83
|
+
return { proof, proofHash: computeProofHash(proof), chainHash: computeChainHash(proof) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface Fixture {
|
|
87
|
+
ingest: IngestResult;
|
|
88
|
+
reconstruction: ReconstructionResult;
|
|
89
|
+
analysis: TemporalAnalysis;
|
|
90
|
+
hashes: Record<string, string>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let fx: Fixture;
|
|
94
|
+
|
|
95
|
+
function segmentOf(hash: string): TemporalSegment {
|
|
96
|
+
const segment = fx.analysis.segments.find((s) => s.memberProofHashes.includes(hash));
|
|
97
|
+
assert.ok(segment !== undefined, "every partitioned proof lands in a segment");
|
|
98
|
+
return segment;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
before(async () => {
|
|
102
|
+
const hashes: Record<string, string> = {};
|
|
103
|
+
const files: Record<string, string> = {};
|
|
104
|
+
|
|
105
|
+
// Epoch E1 on one key: P1 <- A1(T1) <- P2 <- A2(T2) <- P3, plus P4 with
|
|
106
|
+
// a dangling prevB64 (counter evidence only).
|
|
107
|
+
const k1 = await makeKey();
|
|
108
|
+
const p1 = await userProof(k1, "E1", "1", "2");
|
|
109
|
+
const a1 = await makeAnchorProof({
|
|
110
|
+
key: k1,
|
|
111
|
+
blockHash: H1.hash,
|
|
112
|
+
blockNumber: "100",
|
|
113
|
+
epochId: "E1",
|
|
114
|
+
counter: "4",
|
|
115
|
+
slotCounter: "3",
|
|
116
|
+
prevB64: p1.chainHash,
|
|
117
|
+
chainId: "bitgraph:main",
|
|
118
|
+
});
|
|
119
|
+
const p2 = await userProof(k1, "E1", "5", "6", a1.chainHash);
|
|
120
|
+
const a2 = await makeAnchorProof({
|
|
121
|
+
key: k1,
|
|
122
|
+
blockHash: H2.hash,
|
|
123
|
+
blockNumber: "101",
|
|
124
|
+
epochId: "E1",
|
|
125
|
+
counter: "8",
|
|
126
|
+
slotCounter: "7",
|
|
127
|
+
prevB64: p2.chainHash,
|
|
128
|
+
chainId: "bitgraph:main",
|
|
129
|
+
});
|
|
130
|
+
const p3 = await userProof(k1, "E1", "9", "10", a2.chainHash);
|
|
131
|
+
const p4 = await userProof(k1, "E1", "11", "12", b64(sha256(utf8("dangling-predecessor"))));
|
|
132
|
+
|
|
133
|
+
hashes["P1"] = p1.proofHash;
|
|
134
|
+
hashes["A1"] = a1.proofHash;
|
|
135
|
+
hashes["P2"] = p2.proofHash;
|
|
136
|
+
hashes["A2"] = a2.proofHash;
|
|
137
|
+
hashes["P3"] = p3.proofHash;
|
|
138
|
+
hashes["P4"] = p4.proofHash;
|
|
139
|
+
files["proofs/e1-p1.json"] = proofJson(p1.proof);
|
|
140
|
+
files["proofs/e1-a1.json"] = proofJson(a1.proof);
|
|
141
|
+
files["proofs/e1-p2.json"] = proofJson(p2.proof);
|
|
142
|
+
files["proofs/e1-a2.json"] = proofJson(a2.proof);
|
|
143
|
+
files["proofs/e1-p3.json"] = proofJson(p3.proof);
|
|
144
|
+
files["proofs/e1-p4.json"] = proofJson(p4.proof);
|
|
145
|
+
files["witnesses/h1.json"] = witnessJson({ headerRlpHex: H1.rlpHex, blockNumber: 100, blockHash: H1.hash });
|
|
146
|
+
files["witnesses/h2.json"] = witnessJson({ headerRlpHex: H2.rlpHex, blockNumber: 101, blockHash: H2.hash });
|
|
147
|
+
|
|
148
|
+
// Epoch E2: linked chain, no anchors at all (ordered-but-unanchored).
|
|
149
|
+
const k2 = await makeKey();
|
|
150
|
+
const q1 = await userProof(k2, "E2", "1", "2");
|
|
151
|
+
const q2 = await userProof(k2, "E2", "3", "4", q1.chainHash);
|
|
152
|
+
hashes["Q1"] = q1.proofHash;
|
|
153
|
+
hashes["Q2"] = q2.proofHash;
|
|
154
|
+
files["proofs/e2-q1.json"] = proofJson(q1.proof);
|
|
155
|
+
files["proofs/e2-q2.json"] = proofJson(q2.proof);
|
|
156
|
+
|
|
157
|
+
// Epoch E3: an anchor WITHOUT a witness (causal only, no wall clock).
|
|
158
|
+
const k3 = await makeKey();
|
|
159
|
+
const a3 = await makeAnchorProof({
|
|
160
|
+
key: k3,
|
|
161
|
+
blockHash: `0x${"77".repeat(32)}`,
|
|
162
|
+
blockNumber: "999",
|
|
163
|
+
epochId: "E3",
|
|
164
|
+
counter: "2",
|
|
165
|
+
slotCounter: "1",
|
|
166
|
+
chainId: "bitgraph:main",
|
|
167
|
+
});
|
|
168
|
+
hashes["A3"] = a3.proofHash;
|
|
169
|
+
files["proofs/e3-a3.json"] = proofJson(a3.proof);
|
|
170
|
+
|
|
171
|
+
// Epoch EB: anchored at T3, all members at or after the anchor: its
|
|
172
|
+
// covered lower bound sits above E1's covered upper bound (T2 < T3).
|
|
173
|
+
const kb = await makeKey();
|
|
174
|
+
const ab = await makeAnchorProof({
|
|
175
|
+
key: kb,
|
|
176
|
+
blockHash: H3.hash,
|
|
177
|
+
blockNumber: "102",
|
|
178
|
+
epochId: "EB",
|
|
179
|
+
counter: "2",
|
|
180
|
+
slotCounter: "1",
|
|
181
|
+
chainId: "bitgraph:main",
|
|
182
|
+
});
|
|
183
|
+
const pb = await userProof(kb, "EB", "3", "4", ab.chainHash);
|
|
184
|
+
hashes["AB"] = ab.proofHash;
|
|
185
|
+
hashes["PB"] = pb.proofHash;
|
|
186
|
+
files["proofs/eb-ab.json"] = proofJson(ab.proof);
|
|
187
|
+
files["proofs/eb-pb.json"] = proofJson(pb.proof);
|
|
188
|
+
files["witnesses/h3.json"] = witnessJson({ headerRlpHex: H3.rlpHex, blockNumber: 102, blockHash: H3.hash });
|
|
189
|
+
|
|
190
|
+
// Epoch EC: anchored at T_OVERLAP (between T1 and T2): overlaps E1, so
|
|
191
|
+
// no ordering pair may be asserted between E1 and EC.
|
|
192
|
+
const kc = await makeKey();
|
|
193
|
+
const ac = await makeAnchorProof({
|
|
194
|
+
key: kc,
|
|
195
|
+
blockHash: H4.hash,
|
|
196
|
+
blockNumber: "103",
|
|
197
|
+
epochId: "EC",
|
|
198
|
+
counter: "2",
|
|
199
|
+
slotCounter: "1",
|
|
200
|
+
chainId: "bitgraph:main",
|
|
201
|
+
});
|
|
202
|
+
hashes["AC"] = ac.proofHash;
|
|
203
|
+
files["proofs/ec-ac.json"] = proofJson(ac.proof);
|
|
204
|
+
files["witnesses/h4.json"] = witnessJson({ headerRlpHex: H4.rlpHex, blockNumber: 103, blockHash: H4.hash });
|
|
205
|
+
|
|
206
|
+
const dir = await makeTempDir("bg-audit-temporal-");
|
|
207
|
+
await writeBundleDir(dir, files);
|
|
208
|
+
const ingest = await ingestBundle(dir);
|
|
209
|
+
await verifyObservedProofs(ingest);
|
|
210
|
+
const reconstruction = await reconstructChains(ingest);
|
|
211
|
+
const anchors = identifyAnchors(ingest);
|
|
212
|
+
const witnesses = await verifyAnchorWitnesses(ingest, anchors);
|
|
213
|
+
const analysis = deriveTemporalBounds(ingest, reconstruction, anchors, witnesses);
|
|
214
|
+
|
|
215
|
+
fx = { ingest, reconstruction, analysis, hashes };
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("audit temporal: verified anchors only", () => {
|
|
219
|
+
it("splits anchors into verified and unverified wall-clock sources", () => {
|
|
220
|
+
assert.deepEqual(
|
|
221
|
+
fx.analysis.verifiedAnchorProofHashes,
|
|
222
|
+
[fx.hashes["A1"], fx.hashes["A2"], fx.hashes["AB"], fx.hashes["AC"]].sort()
|
|
223
|
+
);
|
|
224
|
+
assert.deepEqual(fx.analysis.unverifiedAnchorProofHashes, [fx.hashes["A3"] as string]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("an anchor without a verified witness confers no bounds", () => {
|
|
228
|
+
const segment = segmentOf(fx.hashes["A3"] as string);
|
|
229
|
+
assert.equal(segment.status, "ordered-but-unanchored");
|
|
230
|
+
assert.equal(segment.lowerBounds.length, 0);
|
|
231
|
+
assert.equal(segment.upperBounds.length, 0);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("audit temporal: one-sided bounds and brackets along chain links", () => {
|
|
236
|
+
it("proofs before the first anchor get only a not-after bound", () => {
|
|
237
|
+
const segment = segmentOf(fx.hashes["P1"] as string);
|
|
238
|
+
assert.equal(segment.status, "upper-bounded");
|
|
239
|
+
assert.equal(segment.lowerBounds.length, 0);
|
|
240
|
+
assert.equal(segment.upperBounds.length, 1);
|
|
241
|
+
const bound = segment.upperBounds[0]!;
|
|
242
|
+
assert.equal(bound.kind, "not-after");
|
|
243
|
+
assert.equal(bound.anchorProofHash, fx.hashes["A1"]);
|
|
244
|
+
assert.equal(bound.timestamp, T1);
|
|
245
|
+
assert.equal(bound.evidence, "chain-link");
|
|
246
|
+
assert.equal(bound.weaker, false);
|
|
247
|
+
assert.equal(bound.basis, "causal-precedence");
|
|
248
|
+
// A following inbound anchor is an assumption about anchor latency, never a
|
|
249
|
+
// proof-carried upper bound, and the report now says so in a machine field.
|
|
250
|
+
assert.equal(bound.boundClass, "assumption");
|
|
251
|
+
assert.match(bound.claim, /^NOT a proof-carried upper bound\./);
|
|
252
|
+
assert.match(bound.claim, /assumes the anchor consumed a recently published block/);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("proofs between two verified anchors are lower-bounded with a following anchor, by the tightest pair", () => {
|
|
256
|
+
const segment = segmentOf(fx.hashes["P2"] as string);
|
|
257
|
+
assert.equal(segment.status, "lower-bounded-with-following-anchor");
|
|
258
|
+
// A1 shares the identical bound set (its own block for the lower
|
|
259
|
+
// side, A2 for the upper) and groups into the same segment.
|
|
260
|
+
assert.ok(segment.memberProofHashes.includes(fx.hashes["A1"] as string));
|
|
261
|
+
const lower = segment.lowerBounds[0]!;
|
|
262
|
+
assert.equal(lower.kind, "not-before");
|
|
263
|
+
assert.equal(lower.boundClass, "evidence");
|
|
264
|
+
assert.equal(lower.anchorProofHash, fx.hashes["A1"]);
|
|
265
|
+
assert.equal(lower.timestamp, T1);
|
|
266
|
+
assert.equal(lower.evidence, "chain-link");
|
|
267
|
+
assert.equal(lower.basis, "block-hash-unpredictability");
|
|
268
|
+
assert.match(lower.claim, /no earlier than/);
|
|
269
|
+
// The not-before claim carries the genuine-public-block assumption: the
|
|
270
|
+
// offline audit cannot confirm the anchored header is a real Ethereum
|
|
271
|
+
// block (no proof-of-work, consensus, or chain-membership check).
|
|
272
|
+
assert.match(lower.claim, /genuine, publicly published Ethereum block/);
|
|
273
|
+
const upper = segment.upperBounds[0]!;
|
|
274
|
+
assert.equal(upper.anchorProofHash, fx.hashes["A2"]);
|
|
275
|
+
assert.equal(upper.timestamp, T2);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("proofs after the last anchor get only the tightest not-before bound", () => {
|
|
279
|
+
const segment = segmentOf(fx.hashes["P3"] as string);
|
|
280
|
+
assert.equal(segment.status, "lower-bounded");
|
|
281
|
+
assert.equal(segment.upperBounds.length, 0);
|
|
282
|
+
const bound = segment.lowerBounds[0]!;
|
|
283
|
+
assert.equal(bound.anchorProofHash, fx.hashes["A2"], "tightest lower is the latest anchor");
|
|
284
|
+
assert.equal(bound.timestamp, T2);
|
|
285
|
+
assert.equal(bound.evidence, "chain-link");
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe("audit temporal: counter-only evidence is weaker and marked", () => {
|
|
290
|
+
it("a chain-broken proof falls back to counter ordering", () => {
|
|
291
|
+
const segment = segmentOf(fx.hashes["P4"] as string);
|
|
292
|
+
assert.equal(segment.status, "lower-bounded");
|
|
293
|
+
const bound = segment.lowerBounds[0]!;
|
|
294
|
+
assert.equal(bound.evidence, "counter-order");
|
|
295
|
+
assert.equal(bound.weaker, true);
|
|
296
|
+
assert.equal(bound.anchorProofHash, fx.hashes["A2"]);
|
|
297
|
+
assert.equal(bound.timestamp, T2);
|
|
298
|
+
assert.match(bound.claim, /counter discipline/);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("audit temporal: unanchored segments", () => {
|
|
303
|
+
it("a partition with no verified anchor evidence is ordered-but-unanchored", () => {
|
|
304
|
+
const segment = segmentOf(fx.hashes["Q1"] as string);
|
|
305
|
+
assert.equal(segment.status, "ordered-but-unanchored");
|
|
306
|
+
assert.ok(segment.memberProofHashes.includes(fx.hashes["Q2"] as string));
|
|
307
|
+
assert.equal(segment.lowerBounds.length, 0);
|
|
308
|
+
assert.equal(segment.upperBounds.length, 0);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("audit temporal: epoch bounds and cross-epoch ordering", () => {
|
|
313
|
+
it("populates EpochRecord.anchorBounds with covered one-sided bounds", () => {
|
|
314
|
+
const epochs = new Map(
|
|
315
|
+
fx.reconstruction.epochRelationships.epochs.map((e) => [e.epochId, e])
|
|
316
|
+
);
|
|
317
|
+
const e1 = epochs.get("E1")!;
|
|
318
|
+
assert.ok(e1.anchorBounds !== undefined);
|
|
319
|
+
const notBefore = e1.anchorBounds.find((b) => b.kind === "not-before")!;
|
|
320
|
+
assert.equal(notBefore.witnessTimestamp, T1, "conservative representative: minimum lower bound");
|
|
321
|
+
assert.equal(notBefore.coverage, "members-after-anchor");
|
|
322
|
+
assert.equal(notBefore.coveredProofCount, 5, "A1, P2, A2, P3 by chain, P4 by counter");
|
|
323
|
+
assert.equal(notBefore.totalProofCount, 6);
|
|
324
|
+
const notAfter = e1.anchorBounds.find((b) => b.kind === "not-after")!;
|
|
325
|
+
assert.equal(notAfter.witnessTimestamp, T2, "conservative representative: maximum upper bound");
|
|
326
|
+
assert.equal(notAfter.coverage, "members-before-anchor");
|
|
327
|
+
assert.equal(notAfter.coveredProofCount, 3, "P1, A1, P2");
|
|
328
|
+
assert.match(notAfter.claim as string, /assumes the anchor consumed a recently published block/);
|
|
329
|
+
|
|
330
|
+
const eb = epochs.get("EB")!;
|
|
331
|
+
assert.ok(eb.anchorBounds !== undefined);
|
|
332
|
+
assert.equal(eb.anchorBounds.length, 1);
|
|
333
|
+
assert.equal(eb.anchorBounds[0]!.kind, "not-before");
|
|
334
|
+
|
|
335
|
+
assert.equal(epochs.get("E2")!.anchorBounds, undefined, "no verified evidence, no bounds");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("derives covered-portion ordering only from non-overlapping bounds", () => {
|
|
339
|
+
// E1's covered upper bound (T2) sits strictly below EB's covered
|
|
340
|
+
// lower bound (T3): one assumption-dependent pair. EC overlaps E1
|
|
341
|
+
// (T_OVERLAP < T2) so no pair is asserted in either direction, and
|
|
342
|
+
// overlap is never divergence.
|
|
343
|
+
assert.equal(fx.analysis.anchorOrderedPairs.length, 1);
|
|
344
|
+
const pair = fx.analysis.anchorOrderedPairs[0]!;
|
|
345
|
+
assert.equal(pair.beforeEpochId, "E1");
|
|
346
|
+
assert.equal(pair.afterEpochId, "EB");
|
|
347
|
+
assert.equal(pair.upperBoundTimestamp, T2);
|
|
348
|
+
assert.equal(pair.lowerBoundTimestamp, T3);
|
|
349
|
+
assert.equal(pair.assumptionDependent, true);
|
|
350
|
+
assert.equal(pair.beforeCoveredProofCount, 3);
|
|
351
|
+
assert.equal(pair.beforeTotalProofCount, 6);
|
|
352
|
+
assert.equal(pair.afterCoveredProofCount, 2);
|
|
353
|
+
assert.equal(pair.afterTotalProofCount, 2);
|
|
354
|
+
assert.match(pair.note, /covered portions only/);
|
|
355
|
+
// The existing E1 -> EB pair rests on chain-link evidence on both sides.
|
|
356
|
+
assert.equal(pair.upperEvidence, "chain-link");
|
|
357
|
+
assert.equal(pair.lowerEvidence, "chain-link");
|
|
358
|
+
assert.equal(pair.weaker, false);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe("audit temporal: cross-epoch ordering carries its evidence class", () => {
|
|
363
|
+
it("a pair whose before-side rests on counter-order is marked weaker with the caveat", async () => {
|
|
364
|
+
const T_LOW = 2_000_000;
|
|
365
|
+
const T_HIGH = 3_000_000;
|
|
366
|
+
const hLow = header(200, T_LOW);
|
|
367
|
+
const hHigh = header(201, T_HIGH);
|
|
368
|
+
|
|
369
|
+
// Epoch WA: a user proof PW (lower counter, no chain link to the
|
|
370
|
+
// anchor) plus a genesis anchor AW. PW is bounded not-after AW only by
|
|
371
|
+
// commit-counter ordering, so WA's covered not-after rests on the
|
|
372
|
+
// weaker evidence class.
|
|
373
|
+
const ka = await makeKey();
|
|
374
|
+
const pw = await userProof(ka, "WA", "1", "2"); // genesis, counter 2
|
|
375
|
+
const aw = await makeAnchorProof({
|
|
376
|
+
key: ka,
|
|
377
|
+
blockHash: hLow.hash,
|
|
378
|
+
blockNumber: "200",
|
|
379
|
+
epochId: "WA",
|
|
380
|
+
counter: "10",
|
|
381
|
+
slotCounter: "9",
|
|
382
|
+
chainId: "bitgraph:main",
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Epoch WB: an anchor AB and its chain-linked successor PB, both
|
|
386
|
+
// bounded not-before AB by a verified hash-link path.
|
|
387
|
+
const kb = await makeKey();
|
|
388
|
+
const ab = await makeAnchorProof({
|
|
389
|
+
key: kb,
|
|
390
|
+
blockHash: hHigh.hash,
|
|
391
|
+
blockNumber: "201",
|
|
392
|
+
epochId: "WB",
|
|
393
|
+
counter: "2",
|
|
394
|
+
slotCounter: "1",
|
|
395
|
+
chainId: "bitgraph:main",
|
|
396
|
+
});
|
|
397
|
+
const pb = await userProof(kb, "WB", "3", "4", ab.chainHash);
|
|
398
|
+
|
|
399
|
+
const dir = await makeTempDir("bg-audit-temporal-weaker-");
|
|
400
|
+
await writeBundleDir(dir, {
|
|
401
|
+
"proofs/wa-pw.json": proofJson(pw.proof),
|
|
402
|
+
"proofs/wa-aw.json": proofJson(aw.proof),
|
|
403
|
+
"proofs/wb-ab.json": proofJson(ab.proof),
|
|
404
|
+
"proofs/wb-pb.json": proofJson(pb.proof),
|
|
405
|
+
"witnesses/low.json": witnessJson({
|
|
406
|
+
headerRlpHex: hLow.rlpHex,
|
|
407
|
+
blockNumber: 200,
|
|
408
|
+
blockHash: hLow.hash,
|
|
409
|
+
}),
|
|
410
|
+
"witnesses/high.json": witnessJson({
|
|
411
|
+
headerRlpHex: hHigh.rlpHex,
|
|
412
|
+
blockNumber: 201,
|
|
413
|
+
blockHash: hHigh.hash,
|
|
414
|
+
}),
|
|
415
|
+
});
|
|
416
|
+
const ingest = await ingestBundle(dir);
|
|
417
|
+
await verifyObservedProofs(ingest);
|
|
418
|
+
const reconstruction = await reconstructChains(ingest);
|
|
419
|
+
const anchors = identifyAnchors(ingest);
|
|
420
|
+
const witnesses = await verifyAnchorWitnesses(ingest, anchors);
|
|
421
|
+
const analysis = deriveTemporalBounds(ingest, reconstruction, anchors, witnesses);
|
|
422
|
+
|
|
423
|
+
// Exactly one cross-epoch ordering pair, WA before WB, marked weaker
|
|
424
|
+
// because the before side (WA's covered not-after) rests on
|
|
425
|
+
// counter-order evidence.
|
|
426
|
+
assert.equal(analysis.anchorOrderedPairs.length, 1);
|
|
427
|
+
const pair = analysis.anchorOrderedPairs[0]!;
|
|
428
|
+
assert.equal(pair.beforeEpochId, "WA");
|
|
429
|
+
assert.equal(pair.afterEpochId, "WB");
|
|
430
|
+
assert.equal(pair.upperEvidence, "counter-order");
|
|
431
|
+
assert.equal(pair.lowerEvidence, "chain-link");
|
|
432
|
+
assert.equal(pair.weaker, true);
|
|
433
|
+
assert.match(pair.note, /weaker evidence/);
|
|
434
|
+
|
|
435
|
+
// WA's covered not-after epoch bound carries the evidence class and the
|
|
436
|
+
// weaker-evidence caveat in its own claim string (so the JSON report
|
|
437
|
+
// and the markdown both state it).
|
|
438
|
+
const wa = reconstruction.epochRelationships.epochs.find((e) => e.epochId === "WA");
|
|
439
|
+
assert.ok(wa?.anchorBounds !== undefined);
|
|
440
|
+
const notAfter = wa.anchorBounds.find((b) => b.kind === "not-after");
|
|
441
|
+
assert.ok(notAfter !== undefined);
|
|
442
|
+
assert.equal(notAfter.evidence, "counter-order");
|
|
443
|
+
assert.equal(notAfter.weaker, true);
|
|
444
|
+
assert.match(notAfter.claim as string, /weaker evidence/);
|
|
445
|
+
});
|
|
446
|
+
});
|