@mikeargento/bitgraph 1.9.1 → 1.10.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 +21 -21
- package/dist/fuse-cli.js +2 -2
- package/dist/fuse-cli.js.map +1 -1
- package/dist/fuse.d.ts +15 -1
- package/dist/fuse.d.ts.map +1 -1
- package/dist/fuse.js +29 -12
- package/dist/fuse.js.map +1 -1
- package/package.json +12 -8
- package/src/fuse-cli.ts +2 -2
- package/src/fuse.ts +46 -13
- 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-inline.test.d.ts +0 -2
- package/dist/__tests__/fuse-inline.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-inline.test.js +0 -181
- package/dist/__tests__/fuse-inline.test.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 -889
- 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 -454
- 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__/run.test.d.ts +0 -2
- package/dist/__tests__/run.test.d.ts.map +0 -1
- package/dist/__tests__/run.test.js +0 -178
- package/dist/__tests__/run.test.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-inline.test.ts +0 -211
- 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 -1027
- 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 -576
- 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,345 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Argento Computing Inc. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Anchor witness verification tests (bundle spec section 10.3).
|
|
5
|
-
*
|
|
6
|
-
* The synthetic header is a 20-item RLP list with the block number at
|
|
7
|
-
* index 8 and the timestamp at index 11; its Keccak-256 hash is the
|
|
8
|
-
* "block hash", the anchor's signed attribution.message is that hash,
|
|
9
|
-
* and the anchor's artifact digest is SHA-256 over the hash STRING,
|
|
10
|
-
* exactly like bitcoin-anchor.ts. Full verification yields the header
|
|
11
|
-
* timestamp; every single-point corruption fails with its own stable
|
|
12
|
-
* reason and confers nothing.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, it } from "node:test";
|
|
16
|
-
import * as assert from "node:assert/strict";
|
|
17
|
-
import { keccak_256 } from "@noble/hashes/sha3";
|
|
18
|
-
import { sha256 } from "@noble/hashes/sha256";
|
|
19
|
-
import {
|
|
20
|
-
identifyAnchors,
|
|
21
|
-
ingestBundle,
|
|
22
|
-
verifyAnchorWitness,
|
|
23
|
-
verifyAnchorWitnesses,
|
|
24
|
-
verifyObservedProofs,
|
|
25
|
-
} from "@mikeargento/bitgraph-audit";
|
|
26
|
-
import type { AnchorIdentification, IngestResult } from "@mikeargento/bitgraph-audit";
|
|
27
|
-
import {
|
|
28
|
-
b64,
|
|
29
|
-
makeAnchorProof,
|
|
30
|
-
makeEthereumHeader,
|
|
31
|
-
makeTempDir,
|
|
32
|
-
proofJson,
|
|
33
|
-
utf8,
|
|
34
|
-
witnessJson,
|
|
35
|
-
writeBundleDir,
|
|
36
|
-
} from "./audit-fixtures.js";
|
|
37
|
-
|
|
38
|
-
const BLOCK_NUMBER = 24800448;
|
|
39
|
-
const TIMESTAMP = 1750000000;
|
|
40
|
-
|
|
41
|
-
const HEADER = makeEthereumHeader({ blockNumber: BLOCK_NUMBER, timestamp: TIMESTAMP });
|
|
42
|
-
const BLOCK_HASH = `0x${Buffer.from(keccak_256(HEADER.headerBytes)).toString("hex")}`;
|
|
43
|
-
|
|
44
|
-
async function pipeline(
|
|
45
|
-
files: Record<string, string | Uint8Array>
|
|
46
|
-
): Promise<{ ingest: IngestResult; anchors: AnchorIdentification }> {
|
|
47
|
-
const dir = await makeTempDir("bg-audit-witness-");
|
|
48
|
-
await writeBundleDir(dir, files);
|
|
49
|
-
const ingest = await ingestBundle(dir);
|
|
50
|
-
await verifyObservedProofs(ingest);
|
|
51
|
-
return { ingest, anchors: identifyAnchors(ingest) };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function goodWitness(): string {
|
|
55
|
-
return witnessJson({
|
|
56
|
-
headerRlpHex: HEADER.headerRlpHex,
|
|
57
|
-
blockNumber: BLOCK_NUMBER,
|
|
58
|
-
blockHash: BLOCK_HASH,
|
|
59
|
-
network: "ethereum-mainnet",
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe("audit witness: full verification", () => {
|
|
64
|
-
it("passes every step and yields the header timestamp as the only wall-clock evidence", async () => {
|
|
65
|
-
const anchor = await makeAnchorProof({
|
|
66
|
-
blockHash: BLOCK_HASH,
|
|
67
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
68
|
-
epochId: "E-W1",
|
|
69
|
-
counter: "4",
|
|
70
|
-
slotCounter: "3",
|
|
71
|
-
});
|
|
72
|
-
const { ingest, anchors } = await pipeline({
|
|
73
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
74
|
-
"witnesses/w.json": goodWitness(),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
78
|
-
assert.equal(analysis.outcomes.length, 1);
|
|
79
|
-
const outcome = analysis.outcomes[0]!;
|
|
80
|
-
assert.equal(outcome.verified, true);
|
|
81
|
-
assert.equal(outcome.anchorProofHash, anchor.proofHash);
|
|
82
|
-
assert.equal(outcome.computedBlockHash, BLOCK_HASH);
|
|
83
|
-
assert.equal(outcome.blockNumber, String(BLOCK_NUMBER));
|
|
84
|
-
assert.equal(outcome.timestamp, TIMESTAMP);
|
|
85
|
-
assert.equal(outcome.reason, undefined);
|
|
86
|
-
assert.equal(analysis.findings.length, 0);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("compares the hash case-insensitively but binds the digest to the exact signed string", async () => {
|
|
90
|
-
// The anchor signed an UPPERCASE hash string; the digest covers that
|
|
91
|
-
// exact string. Step 3 lowercases for comparison, step 4 does not.
|
|
92
|
-
const upper = `0x${BLOCK_HASH.slice(2).toUpperCase()}`;
|
|
93
|
-
const anchor = await makeAnchorProof({
|
|
94
|
-
blockHash: upper,
|
|
95
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
96
|
-
epochId: "E-W2",
|
|
97
|
-
counter: "2",
|
|
98
|
-
});
|
|
99
|
-
const { ingest, anchors } = await pipeline({
|
|
100
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
101
|
-
"witnesses/w.json": goodWitness(),
|
|
102
|
-
});
|
|
103
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
104
|
-
assert.equal(analysis.outcomes.length, 1);
|
|
105
|
-
assert.equal(analysis.outcomes[0]!.verified, true);
|
|
106
|
-
assert.equal(analysis.outcomes[0]!.timestamp, TIMESTAMP);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe("audit witness: single-point corruptions fail with distinct stable reasons", () => {
|
|
111
|
-
it("wrong claimed blockHash: witness-claimed-hash-mismatch", async () => {
|
|
112
|
-
const anchor = await makeAnchorProof({
|
|
113
|
-
blockHash: BLOCK_HASH,
|
|
114
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
115
|
-
epochId: "E-W3",
|
|
116
|
-
counter: "2",
|
|
117
|
-
});
|
|
118
|
-
const wrongClaim = `0x${"ab".repeat(32)}`;
|
|
119
|
-
const { ingest, anchors } = await pipeline({
|
|
120
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
121
|
-
"witnesses/w.json": witnessJson({
|
|
122
|
-
headerRlpHex: HEADER.headerRlpHex,
|
|
123
|
-
blockNumber: BLOCK_NUMBER,
|
|
124
|
-
blockHash: wrongClaim,
|
|
125
|
-
}),
|
|
126
|
-
});
|
|
127
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
128
|
-
const outcome = analysis.outcomes[0]!;
|
|
129
|
-
assert.equal(outcome.verified, false);
|
|
130
|
-
assert.equal(outcome.reason, "witness-claimed-hash-mismatch");
|
|
131
|
-
assert.equal(outcome.timestamp, undefined, "a rejected witness confers nothing");
|
|
132
|
-
assert.ok(analysis.findings.some((f) => f.code === "witness-claimed-hash-mismatch"));
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("wrong artifact digest: witness-digest-mismatch", async () => {
|
|
136
|
-
// Validly signed anchor whose digest covers a different string, so the
|
|
137
|
-
// proof itself verifies but the message-to-digest binding fails.
|
|
138
|
-
const anchor = await makeAnchorProof({
|
|
139
|
-
blockHash: BLOCK_HASH,
|
|
140
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
141
|
-
epochId: "E-W4",
|
|
142
|
-
counter: "2",
|
|
143
|
-
digestB64: b64(sha256(utf8("some other committed artifact"))),
|
|
144
|
-
});
|
|
145
|
-
const { ingest, anchors } = await pipeline({
|
|
146
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
147
|
-
"witnesses/w.json": goodWitness(),
|
|
148
|
-
});
|
|
149
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
150
|
-
const outcome = analysis.outcomes[0]!;
|
|
151
|
-
assert.equal(outcome.verified, false);
|
|
152
|
-
assert.equal(outcome.reason, "witness-digest-mismatch");
|
|
153
|
-
assert.equal(outcome.timestamp, undefined);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("wrong claimed blockNumber: witness-block-number-mismatch", async () => {
|
|
157
|
-
const anchor = await makeAnchorProof({
|
|
158
|
-
blockHash: BLOCK_HASH,
|
|
159
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
160
|
-
epochId: "E-W5",
|
|
161
|
-
counter: "2",
|
|
162
|
-
});
|
|
163
|
-
const { ingest, anchors } = await pipeline({
|
|
164
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
165
|
-
"witnesses/w.json": witnessJson({
|
|
166
|
-
headerRlpHex: HEADER.headerRlpHex,
|
|
167
|
-
blockNumber: BLOCK_NUMBER + 1,
|
|
168
|
-
blockHash: BLOCK_HASH,
|
|
169
|
-
}),
|
|
170
|
-
});
|
|
171
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
172
|
-
const outcome = analysis.outcomes[0]!;
|
|
173
|
-
assert.equal(outcome.verified, false);
|
|
174
|
-
assert.equal(outcome.reason, "witness-block-number-mismatch");
|
|
175
|
-
assert.match(outcome.detail as string, /witness claims blockNumber/);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("signed Etherscan URL disagreeing with the header: witness-block-number-mismatch", async () => {
|
|
179
|
-
const anchor = await makeAnchorProof({
|
|
180
|
-
blockHash: BLOCK_HASH,
|
|
181
|
-
blockNumber: String(BLOCK_NUMBER + 7),
|
|
182
|
-
epochId: "E-W6",
|
|
183
|
-
counter: "2",
|
|
184
|
-
});
|
|
185
|
-
const { ingest, anchors } = await pipeline({
|
|
186
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
187
|
-
"witnesses/w.json": goodWitness(),
|
|
188
|
-
});
|
|
189
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
190
|
-
const outcome = analysis.outcomes[0]!;
|
|
191
|
-
assert.equal(outcome.verified, false);
|
|
192
|
-
assert.equal(outcome.reason, "witness-block-number-mismatch");
|
|
193
|
-
assert.match(outcome.detail as string, /Etherscan URL/);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("tampered RLP header bytes: witness-hash-mismatch", async () => {
|
|
197
|
-
const anchor = await makeAnchorProof({
|
|
198
|
-
blockHash: BLOCK_HASH,
|
|
199
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
200
|
-
epochId: "E-W7",
|
|
201
|
-
counter: "2",
|
|
202
|
-
});
|
|
203
|
-
// Flip content bytes in the middle: still well-formed RLP, different
|
|
204
|
-
// Keccak hash. The witness still reaches its intended anchor through
|
|
205
|
-
// the claimed hash and fails loudly at step 3.
|
|
206
|
-
const tampered =
|
|
207
|
-
HEADER.headerRlpHex.slice(0, 40) +
|
|
208
|
-
(HEADER.headerRlpHex[40] === "f" ? "e" : "f") +
|
|
209
|
-
HEADER.headerRlpHex.slice(41);
|
|
210
|
-
const { ingest, anchors } = await pipeline({
|
|
211
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
212
|
-
"witnesses/w.json": witnessJson({
|
|
213
|
-
headerRlpHex: tampered,
|
|
214
|
-
blockNumber: BLOCK_NUMBER,
|
|
215
|
-
blockHash: BLOCK_HASH,
|
|
216
|
-
}),
|
|
217
|
-
});
|
|
218
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
219
|
-
const outcome = analysis.outcomes[0]!;
|
|
220
|
-
assert.equal(outcome.verified, false);
|
|
221
|
-
assert.equal(outcome.reason, "witness-hash-mismatch");
|
|
222
|
-
assert.equal(outcome.timestamp, undefined);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe("audit witness: malformed and unusable witnesses", () => {
|
|
227
|
-
it("field violations fail with witness-malformed", async () => {
|
|
228
|
-
const anchor = await makeAnchorProof({
|
|
229
|
-
blockHash: BLOCK_HASH,
|
|
230
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
231
|
-
epochId: "E-W8",
|
|
232
|
-
counter: "2",
|
|
233
|
-
});
|
|
234
|
-
const { ingest, anchors } = await pipeline({
|
|
235
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
236
|
-
"witnesses/no-header.json": witnessJson({
|
|
237
|
-
blockNumber: BLOCK_NUMBER,
|
|
238
|
-
blockHash: BLOCK_HASH,
|
|
239
|
-
omit: ["headerRlpHex"],
|
|
240
|
-
}),
|
|
241
|
-
"witnesses/string-number.json": witnessJson({
|
|
242
|
-
headerRlpHex: HEADER.headerRlpHex,
|
|
243
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
244
|
-
blockHash: BLOCK_HASH,
|
|
245
|
-
}),
|
|
246
|
-
"witnesses/bad-hash-shape.json": witnessJson({
|
|
247
|
-
headerRlpHex: HEADER.headerRlpHex,
|
|
248
|
-
blockNumber: BLOCK_NUMBER,
|
|
249
|
-
blockHash: "28ed3639",
|
|
250
|
-
}),
|
|
251
|
-
});
|
|
252
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
253
|
-
assert.equal(analysis.outcomes.length, 3);
|
|
254
|
-
for (const outcome of analysis.outcomes) {
|
|
255
|
-
assert.equal(outcome.verified, false);
|
|
256
|
-
assert.equal(outcome.reason, "witness-malformed");
|
|
257
|
-
assert.equal(outcome.timestamp, undefined);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("structurally invalid RLP fails with witness-rlp-invalid", async () => {
|
|
262
|
-
const anchor = await makeAnchorProof({
|
|
263
|
-
blockHash: BLOCK_HASH,
|
|
264
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
265
|
-
epochId: "E-W9",
|
|
266
|
-
counter: "2",
|
|
267
|
-
});
|
|
268
|
-
const { ingest, anchors } = await pipeline({
|
|
269
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
270
|
-
// 0xf9 declares a long list with a 2-byte length that never arrives.
|
|
271
|
-
"witnesses/w.json": witnessJson({
|
|
272
|
-
headerRlpHex: "0xf901",
|
|
273
|
-
blockNumber: BLOCK_NUMBER,
|
|
274
|
-
blockHash: BLOCK_HASH,
|
|
275
|
-
}),
|
|
276
|
-
});
|
|
277
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
278
|
-
assert.equal(analysis.outcomes[0]!.reason, "witness-rlp-invalid");
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("a witness matching no anchor is reported witness-unmatched", async () => {
|
|
282
|
-
const otherHeader = makeEthereumHeader({ blockNumber: 5, timestamp: 6 });
|
|
283
|
-
const otherHash = `0x${Buffer.from(keccak_256(otherHeader.headerBytes)).toString("hex")}`;
|
|
284
|
-
const anchor = await makeAnchorProof({
|
|
285
|
-
blockHash: BLOCK_HASH,
|
|
286
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
287
|
-
epochId: "E-W10",
|
|
288
|
-
counter: "2",
|
|
289
|
-
});
|
|
290
|
-
const { ingest, anchors } = await pipeline({
|
|
291
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
292
|
-
"witnesses/w.json": witnessJson({
|
|
293
|
-
headerRlpHex: otherHeader.headerRlpHex,
|
|
294
|
-
blockNumber: 5,
|
|
295
|
-
blockHash: otherHash,
|
|
296
|
-
}),
|
|
297
|
-
});
|
|
298
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
299
|
-
const outcome = analysis.outcomes[0]!;
|
|
300
|
-
assert.equal(outcome.verified, false);
|
|
301
|
-
assert.equal(outcome.reason, "witness-unmatched");
|
|
302
|
-
assert.equal(outcome.anchorProofHash, undefined);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it("a witness cannot rescue an invalid anchor: witness-anchor-invalid", async () => {
|
|
306
|
-
const anchor = await makeAnchorProof({
|
|
307
|
-
blockHash: BLOCK_HASH,
|
|
308
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
309
|
-
epochId: "E-W11",
|
|
310
|
-
counter: "2",
|
|
311
|
-
});
|
|
312
|
-
// Tamper the Ed25519 signature: the canonical identity is unchanged
|
|
313
|
-
// (the signature is outside the signed body) but the proof is invalid.
|
|
314
|
-
anchor.proof.signer.signatureB64 = `AA${anchor.proof.signer.signatureB64.slice(2)}`;
|
|
315
|
-
const { ingest, anchors } = await pipeline({
|
|
316
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
317
|
-
"witnesses/w.json": goodWitness(),
|
|
318
|
-
});
|
|
319
|
-
const analysis = await verifyAnchorWitnesses(ingest, anchors);
|
|
320
|
-
const outcome = analysis.outcomes[0]!;
|
|
321
|
-
assert.equal(outcome.verified, false);
|
|
322
|
-
assert.equal(outcome.reason, "witness-anchor-invalid");
|
|
323
|
-
assert.equal(outcome.timestamp, undefined);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe("audit witness: direct single-pair API", () => {
|
|
328
|
-
it("verifyAnchorWitness runs the full procedure against one anchor", async () => {
|
|
329
|
-
const anchor = await makeAnchorProof({
|
|
330
|
-
blockHash: BLOCK_HASH,
|
|
331
|
-
blockNumber: String(BLOCK_NUMBER),
|
|
332
|
-
epochId: "E-W12",
|
|
333
|
-
counter: "2",
|
|
334
|
-
});
|
|
335
|
-
const { ingest } = await pipeline({
|
|
336
|
-
"proofs/anchor.json": proofJson(anchor.proof),
|
|
337
|
-
"witnesses/w.json": goodWitness(),
|
|
338
|
-
});
|
|
339
|
-
const observed = ingest.proofs.find((p) => p.proofHash === anchor.proofHash)!;
|
|
340
|
-
const witnessFile = ingest.witnesses[0]!;
|
|
341
|
-
const outcome = await verifyAnchorWitness(witnessFile, observed);
|
|
342
|
-
assert.equal(outcome.verified, true);
|
|
343
|
-
assert.equal(outcome.timestamp, TIMESTAMP);
|
|
344
|
-
});
|
|
345
|
-
});
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Argento Computing Inc. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
import { describe, test } from "node:test";
|
|
4
|
-
import * as assert from "node:assert/strict";
|
|
5
|
-
import {
|
|
6
|
-
canonicalize,
|
|
7
|
-
canonicalizeToString,
|
|
8
|
-
constantTimeEqual,
|
|
9
|
-
} from "../index.js";
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// canonicalizeToString
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
describe("canonicalizeToString", () => {
|
|
16
|
-
// Primitives
|
|
17
|
-
test("null", () => assert.equal(canonicalizeToString(null), "null"));
|
|
18
|
-
test("true", () => assert.equal(canonicalizeToString(true), "true"));
|
|
19
|
-
test("false", () => assert.equal(canonicalizeToString(false), "false"));
|
|
20
|
-
test("integer", () => assert.equal(canonicalizeToString(42), "42"));
|
|
21
|
-
test("float", () => assert.equal(canonicalizeToString(3.14), "3.14"));
|
|
22
|
-
test("negative", () => assert.equal(canonicalizeToString(-1), "-1"));
|
|
23
|
-
test("zero", () => assert.equal(canonicalizeToString(0), "0"));
|
|
24
|
-
test("string", () => assert.equal(canonicalizeToString("hello"), '"hello"'));
|
|
25
|
-
test("empty string", () => assert.equal(canonicalizeToString(""), '""'));
|
|
26
|
-
|
|
27
|
-
// Object key ordering
|
|
28
|
-
test("sorts keys lexicographically", () => {
|
|
29
|
-
assert.equal(
|
|
30
|
-
canonicalizeToString({ z: 1, a: 2, m: 3 }),
|
|
31
|
-
'{"a":2,"m":3,"z":1}',
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("uppercase sorts before lowercase (Unicode code-point order)", () => {
|
|
36
|
-
assert.equal(canonicalizeToString({ b: 2, A: 1 }), '{"A":1,"b":2}');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("sorts nested object keys recursively", () => {
|
|
40
|
-
assert.equal(
|
|
41
|
-
canonicalizeToString({ outer: { z: 1, a: 2 }, b: 3 }),
|
|
42
|
-
'{"b":3,"outer":{"a":2,"z":1}}',
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("empty object", () => assert.equal(canonicalizeToString({}), "{}"));
|
|
47
|
-
|
|
48
|
-
test("same output regardless of insertion order", () => {
|
|
49
|
-
const a = { z: 1, a: 2 };
|
|
50
|
-
const b = { a: 2, z: 1 };
|
|
51
|
-
assert.equal(canonicalizeToString(a), canonicalizeToString(b));
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Arrays
|
|
55
|
-
test("preserves array element order", () => {
|
|
56
|
-
assert.equal(canonicalizeToString([3, 1, 2]), "[3,1,2]");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("sorts keys inside array elements", () => {
|
|
60
|
-
assert.equal(canonicalizeToString([{ b: 2, a: 1 }]), '[{"a":1,"b":2}]');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("empty array", () => assert.equal(canonicalizeToString([]), "[]"));
|
|
64
|
-
|
|
65
|
-
test("nested arrays preserve order", () => {
|
|
66
|
-
assert.equal(
|
|
67
|
-
canonicalizeToString([[1, 2], [3, 4]]),
|
|
68
|
-
"[[1,2],[3,4]]",
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Undefined handling
|
|
73
|
-
test("skips undefined values in objects", () => {
|
|
74
|
-
const obj: Record<string, unknown> = { a: 1, c: 3 };
|
|
75
|
-
obj["b"] = undefined;
|
|
76
|
-
assert.equal(canonicalizeToString(obj), '{"a":1,"c":3}');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Rejection of non-serializable types
|
|
80
|
-
test("throws TypeError for undefined", () => {
|
|
81
|
-
assert.throws(
|
|
82
|
-
() => canonicalizeToString(undefined as unknown),
|
|
83
|
-
TypeError,
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("throws TypeError for function", () => {
|
|
88
|
-
assert.throws(
|
|
89
|
-
() => canonicalizeToString((() => {}) as unknown),
|
|
90
|
-
TypeError,
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("throws TypeError for symbol", () => {
|
|
95
|
-
assert.throws(
|
|
96
|
-
() => canonicalizeToString(Symbol("x") as unknown),
|
|
97
|
-
TypeError,
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("throws TypeError for BigInt", () => {
|
|
102
|
-
assert.throws(
|
|
103
|
-
() => canonicalizeToString(BigInt(42) as unknown),
|
|
104
|
-
TypeError,
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("throws TypeError for BigInt inside object", () => {
|
|
109
|
-
assert.throws(
|
|
110
|
-
() => canonicalizeToString({ n: BigInt(1) } as unknown),
|
|
111
|
-
TypeError,
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// NaN / Infinity → null (standard JSON.stringify behavior)
|
|
116
|
-
test("NaN serializes as null", () => {
|
|
117
|
-
assert.equal(canonicalizeToString(NaN), "null");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("Infinity serializes as null", () => {
|
|
121
|
-
assert.equal(canonicalizeToString(Infinity), "null");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("-Infinity serializes as null", () => {
|
|
125
|
-
assert.equal(canonicalizeToString(-Infinity), "null");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Determinism
|
|
129
|
-
test("is deterministic across repeated calls", () => {
|
|
130
|
-
const obj = { c: 3, b: 2, a: 1 };
|
|
131
|
-
assert.equal(canonicalizeToString(obj), canonicalizeToString(obj));
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// canonicalize
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
describe("canonicalize", () => {
|
|
140
|
-
test("returns a Uint8Array", () => {
|
|
141
|
-
assert.ok(canonicalize({}) instanceof Uint8Array);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("UTF-8 encodes the canonical JSON string", () => {
|
|
145
|
-
const bytes = canonicalize({ hello: "world" });
|
|
146
|
-
assert.equal(new TextDecoder().decode(bytes), '{"hello":"world"}');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("matches TextEncoder(canonicalizeToString(obj))", () => {
|
|
150
|
-
const obj = { z: 1, a: 2 };
|
|
151
|
-
const bytes = canonicalize(obj);
|
|
152
|
-
const str = canonicalizeToString(obj);
|
|
153
|
-
assert.deepEqual(bytes, new TextEncoder().encode(str));
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("empty object → two bytes ('{' and '}')", () => {
|
|
157
|
-
assert.deepEqual(canonicalize({}), new TextEncoder().encode("{}"));
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("large payload round-trips through canonicalizeToString", () => {
|
|
161
|
-
const large: Record<string, number> = {};
|
|
162
|
-
for (let i = 0; i < 1000; i++) large[`key_${i}`] = i;
|
|
163
|
-
const bytes = canonicalize(large);
|
|
164
|
-
const str = canonicalizeToString(large);
|
|
165
|
-
assert.deepEqual(bytes, new TextEncoder().encode(str));
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("throws TypeError for BigInt nested inside object", () => {
|
|
169
|
-
assert.throws(() => canonicalize({ n: BigInt(1) } as unknown), TypeError);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
// constantTimeEqual
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
|
|
177
|
-
describe("constantTimeEqual", () => {
|
|
178
|
-
test("equal non-empty arrays", () => {
|
|
179
|
-
assert.equal(
|
|
180
|
-
constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])),
|
|
181
|
-
true,
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("different content same length", () => {
|
|
186
|
-
assert.equal(
|
|
187
|
-
constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 4])),
|
|
188
|
-
false,
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("different lengths", () => {
|
|
193
|
-
assert.equal(
|
|
194
|
-
constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2])),
|
|
195
|
-
false,
|
|
196
|
-
);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("empty arrays are equal", () => {
|
|
200
|
-
assert.equal(
|
|
201
|
-
constantTimeEqual(new Uint8Array([]), new Uint8Array([])),
|
|
202
|
-
true,
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("empty vs non-empty", () => {
|
|
207
|
-
assert.equal(
|
|
208
|
-
constantTimeEqual(new Uint8Array([]), new Uint8Array([0])),
|
|
209
|
-
false,
|
|
210
|
-
);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test("single byte equal", () => {
|
|
214
|
-
assert.equal(
|
|
215
|
-
constantTimeEqual(new Uint8Array([0xff]), new Uint8Array([0xff])),
|
|
216
|
-
true,
|
|
217
|
-
);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("single byte different", () => {
|
|
221
|
-
assert.equal(
|
|
222
|
-
constantTimeEqual(new Uint8Array([0xff]), new Uint8Array([0xfe])),
|
|
223
|
-
false,
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("large equal arrays", () => {
|
|
228
|
-
const a = new Uint8Array(1024).fill(0xab);
|
|
229
|
-
const b = new Uint8Array(1024).fill(0xab);
|
|
230
|
-
assert.equal(constantTimeEqual(a, b), true);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("large arrays differing in last byte", () => {
|
|
234
|
-
const a = new Uint8Array(1024).fill(0xab);
|
|
235
|
-
const b = new Uint8Array(1024).fill(0xab);
|
|
236
|
-
b[1023] = 0xac;
|
|
237
|
-
assert.equal(constantTimeEqual(a, b), false);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("large arrays differing in first byte", () => {
|
|
241
|
-
const a = new Uint8Array(1024).fill(0xab);
|
|
242
|
-
const b = new Uint8Array(1024).fill(0xab);
|
|
243
|
-
b[0] = 0xac;
|
|
244
|
-
assert.equal(constantTimeEqual(a, b), false);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("all-zeros equal", () => {
|
|
248
|
-
assert.equal(
|
|
249
|
-
constantTimeEqual(new Uint8Array(32), new Uint8Array(32)),
|
|
250
|
-
true,
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression: computeChainHash must exclude the anchor service's `ethereum`
|
|
3
|
-
* field so that an Ethereum anchor can be resolved as a chain predecessor.
|
|
4
|
-
*
|
|
5
|
-
* Real-data fixture. `anchor-with-ethereum-112758.json` is a live Ethereum
|
|
6
|
-
* anchor (commit 112758) exactly as it ships in an export bundle: it carries a
|
|
7
|
-
* top-level `ethereum` { blockNumber, blockHash } field that the anchor service
|
|
8
|
-
* appended AFTER the enclave signed it. `successor-112760.json` is the next
|
|
9
|
-
* proof on the chain (the BitGraph whitepaper, commit 112760); its
|
|
10
|
-
* `commit.prevB64` was computed by the enclave over the anchor as the enclave
|
|
11
|
-
* saw it — before the `ethereum` field existed.
|
|
12
|
-
*
|
|
13
|
-
* The bug (latent since the anchor service began stamping `ethereum` on
|
|
14
|
-
* 2026-04-01, exposed once the audit tool resolved prevB64 via computeChainHash
|
|
15
|
-
* on 2026-07-10): computeChainHash hashed the whole proof minus only
|
|
16
|
-
* `proofHash`, so the appended `ethereum` field poisoned the hash and no
|
|
17
|
-
* successor's prevB64 could ever match. The anchor "vanished" from the chain
|
|
18
|
-
* even though it was present in the bundle. The fix excludes `ethereum` (and
|
|
19
|
-
* any ledger/service-added field) from the chain hash.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { describe, it } from "node:test";
|
|
23
|
-
import * as assert from "node:assert/strict";
|
|
24
|
-
import { readFileSync } from "node:fs";
|
|
25
|
-
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { computeChainHash, type BitGraphProof } from "@mikeargento/bitgraph-verify";
|
|
27
|
-
|
|
28
|
-
function loadFixture(name: string): Record<string, unknown> {
|
|
29
|
-
const url = new URL(`../../src/__tests__/real-fixtures/${name}`, import.meta.url);
|
|
30
|
-
return JSON.parse(readFileSync(fileURLToPath(url), "utf8"));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe("computeChainHash: Ethereum anchor chain link (ethereum-field regression)", () => {
|
|
34
|
-
const anchor = loadFixture("anchor-with-ethereum-112758.json");
|
|
35
|
-
const successor = loadFixture("successor-112760.json");
|
|
36
|
-
const expectedPrev = (successor.commit as { prevB64: string }).prevB64;
|
|
37
|
-
|
|
38
|
-
it("the fixture is the real shape: anchor carries a top-level ethereum field", () => {
|
|
39
|
-
assert.ok(Object.prototype.hasOwnProperty.call(anchor, "ethereum"));
|
|
40
|
-
assert.equal((anchor.commit as { counter: string }).counter, "112758");
|
|
41
|
-
assert.equal((successor.commit as { counter: string }).counter, "112760");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("resolves the successor's prevB64 to the anchor, as exported (with ethereum)", () => {
|
|
45
|
-
// The whole point: the anchor is exported WITH the ethereum field, and its
|
|
46
|
-
// chain hash must still equal the successor's prevB64.
|
|
47
|
-
assert.equal(computeChainHash(anchor as unknown as BitGraphProof), expectedPrev);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("ignores the ethereum field entirely (with === without)", () => {
|
|
51
|
-
const withoutEth: Record<string, unknown> = { ...anchor };
|
|
52
|
-
delete withoutEth.ethereum;
|
|
53
|
-
assert.equal(
|
|
54
|
-
computeChainHash(anchor as unknown as BitGraphProof),
|
|
55
|
-
computeChainHash(withoutEth as unknown as BitGraphProof)
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("also ignores the ledger-added proofHash field (with === without)", () => {
|
|
60
|
-
const withoutHash: Record<string, unknown> = { ...anchor };
|
|
61
|
-
delete withoutHash.proofHash;
|
|
62
|
-
assert.equal(
|
|
63
|
-
computeChainHash(anchor as unknown as BitGraphProof),
|
|
64
|
-
computeChainHash(withoutHash as unknown as BitGraphProof)
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
});
|