@mikeargento/bitgraph 1.9.1 → 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.
Files changed (239) hide show
  1. package/package.json +9 -6
  2. package/dist/__tests__/audit-anchors.test.d.ts +0 -2
  3. package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
  4. package/dist/__tests__/audit-anchors.test.js +0 -152
  5. package/dist/__tests__/audit-anchors.test.js.map +0 -1
  6. package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
  7. package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
  8. package/dist/__tests__/audit-anomalies.test.js +0 -663
  9. package/dist/__tests__/audit-anomalies.test.js.map +0 -1
  10. package/dist/__tests__/audit-attestation.test.d.ts +0 -2
  11. package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
  12. package/dist/__tests__/audit-attestation.test.js +0 -403
  13. package/dist/__tests__/audit-attestation.test.js.map +0 -1
  14. package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
  15. package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
  16. package/dist/__tests__/audit-bench-smoke.test.js +0 -87
  17. package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
  18. package/dist/__tests__/audit-boundary.test.d.ts +0 -20
  19. package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
  20. package/dist/__tests__/audit-boundary.test.js +0 -84
  21. package/dist/__tests__/audit-boundary.test.js.map +0 -1
  22. package/dist/__tests__/audit-cli.test.d.ts +0 -2
  23. package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
  24. package/dist/__tests__/audit-cli.test.js +0 -239
  25. package/dist/__tests__/audit-cli.test.js.map +0 -1
  26. package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
  27. package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
  28. package/dist/__tests__/audit-dependencies.test.js +0 -116
  29. package/dist/__tests__/audit-dependencies.test.js.map +0 -1
  30. package/dist/__tests__/audit-fixtures.d.ts +0 -265
  31. package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
  32. package/dist/__tests__/audit-fixtures.js +0 -511
  33. package/dist/__tests__/audit-fixtures.js.map +0 -1
  34. package/dist/__tests__/audit-ingest.test.d.ts +0 -2
  35. package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
  36. package/dist/__tests__/audit-ingest.test.js +0 -473
  37. package/dist/__tests__/audit-ingest.test.js.map +0 -1
  38. package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
  39. package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
  40. package/dist/__tests__/audit-memory-ingest.test.js +0 -135
  41. package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
  42. package/dist/__tests__/audit-property.test.d.ts +0 -2
  43. package/dist/__tests__/audit-property.test.d.ts.map +0 -1
  44. package/dist/__tests__/audit-property.test.js +0 -771
  45. package/dist/__tests__/audit-property.test.js.map +0 -1
  46. package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
  47. package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
  48. package/dist/__tests__/audit-real-fixtures.test.js +0 -132
  49. package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
  50. package/dist/__tests__/audit-real-live.test.d.ts +0 -2
  51. package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
  52. package/dist/__tests__/audit-real-live.test.js +0 -107
  53. package/dist/__tests__/audit-real-live.test.js.map +0 -1
  54. package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
  55. package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
  56. package/dist/__tests__/audit-reconstruct.test.js +0 -287
  57. package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
  58. package/dist/__tests__/audit-report.test.d.ts +0 -2
  59. package/dist/__tests__/audit-report.test.d.ts.map +0 -1
  60. package/dist/__tests__/audit-report.test.js +0 -186
  61. package/dist/__tests__/audit-report.test.js.map +0 -1
  62. package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
  63. package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
  64. package/dist/__tests__/audit-roundtrip.test.js +0 -368
  65. package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
  66. package/dist/__tests__/audit-temporal.test.d.ts +0 -2
  67. package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
  68. package/dist/__tests__/audit-temporal.test.js +0 -364
  69. package/dist/__tests__/audit-temporal.test.js.map +0 -1
  70. package/dist/__tests__/audit-tiers.test.d.ts +0 -2
  71. package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
  72. package/dist/__tests__/audit-tiers.test.js +0 -282
  73. package/dist/__tests__/audit-tiers.test.js.map +0 -1
  74. package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
  75. package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
  76. package/dist/__tests__/audit-witness-real.test.js +0 -82
  77. package/dist/__tests__/audit-witness-real.test.js.map +0 -1
  78. package/dist/__tests__/audit-witness.test.d.ts +0 -2
  79. package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
  80. package/dist/__tests__/audit-witness.test.js +0 -308
  81. package/dist/__tests__/audit-witness.test.js.map +0 -1
  82. package/dist/__tests__/canonical.test.d.ts +0 -2
  83. package/dist/__tests__/canonical.test.d.ts.map +0 -1
  84. package/dist/__tests__/canonical.test.js +0 -162
  85. package/dist/__tests__/canonical.test.js.map +0 -1
  86. package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
  87. package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
  88. package/dist/__tests__/chain-hash-anchor.test.js +0 -55
  89. package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
  90. package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
  91. package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
  92. package/dist/__tests__/commit-service.integration.test.js +0 -95
  93. package/dist/__tests__/commit-service.integration.test.js.map +0 -1
  94. package/dist/__tests__/constructor.test.d.ts +0 -2
  95. package/dist/__tests__/constructor.test.d.ts.map +0 -1
  96. package/dist/__tests__/constructor.test.js +0 -267
  97. package/dist/__tests__/constructor.test.js.map +0 -1
  98. package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
  99. package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
  100. package/dist/__tests__/declared-proof-fixture.js +0 -66
  101. package/dist/__tests__/declared-proof-fixture.js.map +0 -1
  102. package/dist/__tests__/fuse-inline.test.d.ts +0 -2
  103. package/dist/__tests__/fuse-inline.test.d.ts.map +0 -1
  104. package/dist/__tests__/fuse-inline.test.js +0 -181
  105. package/dist/__tests__/fuse-inline.test.js.map +0 -1
  106. package/dist/__tests__/fuse-placement.test.d.ts +0 -2
  107. package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
  108. package/dist/__tests__/fuse-placement.test.js +0 -31
  109. package/dist/__tests__/fuse-placement.test.js.map +0 -1
  110. package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
  111. package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
  112. package/dist/__tests__/fuse-roundtrip.test.js +0 -241
  113. package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
  114. package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
  115. package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
  116. package/dist/__tests__/fuse-sdk.test.js +0 -117
  117. package/dist/__tests__/fuse-sdk.test.js.map +0 -1
  118. package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
  119. package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
  120. package/dist/__tests__/fuse-set-sdk.test.js +0 -721
  121. package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
  122. package/dist/__tests__/fuse-set.test.d.ts +0 -2
  123. package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
  124. package/dist/__tests__/fuse-set.test.js +0 -889
  125. package/dist/__tests__/fuse-set.test.js.map +0 -1
  126. package/dist/__tests__/fuse-set2.test.d.ts +0 -2
  127. package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
  128. package/dist/__tests__/fuse-set2.test.js +0 -294
  129. package/dist/__tests__/fuse-set2.test.js.map +0 -1
  130. package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
  131. package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
  132. package/dist/__tests__/fuse-vectors.test.js +0 -175
  133. package/dist/__tests__/fuse-vectors.test.js.map +0 -1
  134. package/dist/__tests__/fuse-verify.test.d.ts +0 -2
  135. package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
  136. package/dist/__tests__/fuse-verify.test.js +0 -250
  137. package/dist/__tests__/fuse-verify.test.js.map +0 -1
  138. package/dist/__tests__/hello-world.test.d.ts +0 -2
  139. package/dist/__tests__/hello-world.test.d.ts.map +0 -1
  140. package/dist/__tests__/hello-world.test.js +0 -9
  141. package/dist/__tests__/hello-world.test.js.map +0 -1
  142. package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
  143. package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
  144. package/dist/__tests__/mock-proof-fixture.js +0 -34
  145. package/dist/__tests__/mock-proof-fixture.js.map +0 -1
  146. package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
  147. package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
  148. package/dist/__tests__/policy-enforcement.test.js +0 -188
  149. package/dist/__tests__/policy-enforcement.test.js.map +0 -1
  150. package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
  151. package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
  152. package/dist/__tests__/proof-hash-regression.test.js +0 -95
  153. package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
  154. package/dist/__tests__/proof-hash.test.d.ts +0 -2
  155. package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
  156. package/dist/__tests__/proof-hash.test.js +0 -116
  157. package/dist/__tests__/proof-hash.test.js.map +0 -1
  158. package/dist/__tests__/proof-integrity.test.d.ts +0 -2
  159. package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
  160. package/dist/__tests__/proof-integrity.test.js +0 -454
  161. package/dist/__tests__/proof-integrity.test.js.map +0 -1
  162. package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
  163. package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
  164. package/dist/__tests__/realistic-proof-fixture.js +0 -57
  165. package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
  166. package/dist/__tests__/run.test.d.ts +0 -2
  167. package/dist/__tests__/run.test.d.ts.map +0 -1
  168. package/dist/__tests__/run.test.js +0 -178
  169. package/dist/__tests__/run.test.js.map +0 -1
  170. package/dist/__tests__/verifier.test.d.ts +0 -2
  171. package/dist/__tests__/verifier.test.d.ts.map +0 -1
  172. package/dist/__tests__/verifier.test.js +0 -531
  173. package/dist/__tests__/verifier.test.js.map +0 -1
  174. package/dist/canonical.d.ts +0 -54
  175. package/dist/canonical.d.ts.map +0 -1
  176. package/dist/canonical.js +0 -119
  177. package/dist/canonical.js.map +0 -1
  178. package/dist/proof-hash.d.ts +0 -9
  179. package/dist/proof-hash.d.ts.map +0 -1
  180. package/dist/proof-hash.js +0 -75
  181. package/dist/proof-hash.js.map +0 -1
  182. package/dist/types.d.ts +0 -705
  183. package/dist/types.d.ts.map +0 -1
  184. package/dist/types.js +0 -3
  185. package/dist/types.js.map +0 -1
  186. package/dist/verifier.d.ts +0 -27
  187. package/dist/verifier.d.ts.map +0 -1
  188. package/dist/verifier.js +0 -844
  189. package/dist/verifier.js.map +0 -1
  190. package/src/__tests__/audit-anchors.test.ts +0 -188
  191. package/src/__tests__/audit-anomalies.test.ts +0 -815
  192. package/src/__tests__/audit-attestation.test.ts +0 -529
  193. package/src/__tests__/audit-bench-smoke.test.ts +0 -108
  194. package/src/__tests__/audit-boundary.test.ts +0 -96
  195. package/src/__tests__/audit-cli.test.ts +0 -288
  196. package/src/__tests__/audit-dependencies.test.ts +0 -141
  197. package/src/__tests__/audit-fixtures.ts +0 -736
  198. package/src/__tests__/audit-ingest.test.ts +0 -575
  199. package/src/__tests__/audit-memory-ingest.test.ts +0 -156
  200. package/src/__tests__/audit-property.test.ts +0 -892
  201. package/src/__tests__/audit-real-fixtures.test.ts +0 -153
  202. package/src/__tests__/audit-real-live.test.ts +0 -128
  203. package/src/__tests__/audit-reconstruct.test.ts +0 -353
  204. package/src/__tests__/audit-report.test.ts +0 -222
  205. package/src/__tests__/audit-roundtrip.test.ts +0 -464
  206. package/src/__tests__/audit-temporal.test.ts +0 -446
  207. package/src/__tests__/audit-tiers.test.ts +0 -343
  208. package/src/__tests__/audit-witness-real.test.ts +0 -92
  209. package/src/__tests__/audit-witness.test.ts +0 -345
  210. package/src/__tests__/canonical.test.ts +0 -253
  211. package/src/__tests__/chain-hash-anchor.test.ts +0 -67
  212. package/src/__tests__/commit-service.integration.test.ts +0 -117
  213. package/src/__tests__/constructor.test.ts +0 -340
  214. package/src/__tests__/declared-proof-fixture.ts +0 -65
  215. package/src/__tests__/fuse-inline.test.ts +0 -211
  216. package/src/__tests__/fuse-placement.test.ts +0 -32
  217. package/src/__tests__/fuse-roundtrip.test.ts +0 -243
  218. package/src/__tests__/fuse-sdk.test.ts +0 -142
  219. package/src/__tests__/fuse-set-sdk.test.ts +0 -852
  220. package/src/__tests__/fuse-set.test.ts +0 -1027
  221. package/src/__tests__/fuse-set2.test.ts +0 -343
  222. package/src/__tests__/fuse-vectors.test.ts +0 -218
  223. package/src/__tests__/fuse-verify.test.ts +0 -278
  224. package/src/__tests__/hello-world.test.ts +0 -10
  225. package/src/__tests__/mock-proof-fixture.ts +0 -36
  226. package/src/__tests__/policy-enforcement.test.ts +0 -226
  227. package/src/__tests__/proof-hash-regression.test.ts +0 -107
  228. package/src/__tests__/proof-hash.test.ts +0 -129
  229. package/src/__tests__/proof-integrity.test.ts +0 -576
  230. package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
  231. package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
  232. package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
  233. package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
  234. package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
  235. package/src/__tests__/real-fixtures/successor-112760.json +0 -38
  236. package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
  237. package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
  238. package/src/__tests__/realistic-proof-fixture.ts +0 -57
  239. 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
- });