verifyhash 0.1.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.
Files changed (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +883 -0
  3. package/cli/abi/ContributionRegistry.json +881 -0
  4. package/cli/agent.js +2173 -0
  5. package/cli/anchor-artifact.js +853 -0
  6. package/cli/anchor.js +400 -0
  7. package/cli/claim.js +881 -0
  8. package/cli/core/agent-commit.js +448 -0
  9. package/cli/core/agent-session.js +598 -0
  10. package/cli/core/anchor-binding.js +663 -0
  11. package/cli/core/attestation.js +580 -0
  12. package/cli/core/evidence-plans.js +495 -0
  13. package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
  14. package/cli/core/fulfill-intake.js +1082 -0
  15. package/cli/core/go-live-preflight.js +481 -0
  16. package/cli/core/license.js +534 -0
  17. package/cli/core/manifest.js +243 -0
  18. package/cli/core/packetseal.js +591 -0
  19. package/cli/core/registryArtifact.js +49 -0
  20. package/cli/core/revocation.js +539 -0
  21. package/cli/core/rfc3161.js +389 -0
  22. package/cli/core/timestamp.js +482 -0
  23. package/cli/core/trust-asof.js +479 -0
  24. package/cli/dataset.js +2950 -0
  25. package/cli/evidence.js +2227 -0
  26. package/cli/fulfill-webhook-http.js +438 -0
  27. package/cli/git.js +220 -0
  28. package/cli/hash.js +550 -0
  29. package/cli/identity.js +1072 -0
  30. package/cli/journal-cli.js +1110 -0
  31. package/cli/journal-log.js +454 -0
  32. package/cli/journal.js +334 -0
  33. package/cli/lineage.js +447 -0
  34. package/cli/list.js +287 -0
  35. package/cli/parcel.js +1509 -0
  36. package/cli/proof.js +578 -0
  37. package/cli/prove.js +300 -0
  38. package/cli/receipt.js +631 -0
  39. package/cli/registry.js +331 -0
  40. package/cli/reputation.js +344 -0
  41. package/cli/revocation.js +495 -0
  42. package/cli/serve-verify-http.js +298 -0
  43. package/cli/serve-verify.js +333 -0
  44. package/cli/show.js +339 -0
  45. package/cli/verify.js +383 -0
  46. package/cli/vh.js +3927 -0
  47. package/docs/ADOPT.md +183 -0
  48. package/docs/ADOPTION.json +11 -0
  49. package/docs/AGENTTRACE.md +247 -0
  50. package/docs/ANCHORING.md +167 -0
  51. package/docs/AUDIT.md +55 -0
  52. package/docs/CONFORMANCE.md +107 -0
  53. package/docs/DATALEDGER.md +638 -0
  54. package/docs/DECIDE.md +47 -0
  55. package/docs/DECISIONS-PENDING.md +27 -0
  56. package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
  57. package/docs/ENGINE-LEDGER.json +12 -0
  58. package/docs/EVIDENCE.md +519 -0
  59. package/docs/GO-LIVE.md +66 -0
  60. package/docs/IDENTITY.md +123 -0
  61. package/docs/INDEPENDENT-VERIFICATION.md +377 -0
  62. package/docs/INTEGRITY-JOURNAL.md +337 -0
  63. package/docs/KEY-LIFECYCLE.md +179 -0
  64. package/docs/LICENSING.md +46 -0
  65. package/docs/LINEAGE.md +307 -0
  66. package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
  67. package/docs/LOOP-HARDENING-PLAN.md +44 -0
  68. package/docs/MERKLE-LEAVES.md +113 -0
  69. package/docs/METRICS.jsonl +31 -0
  70. package/docs/MORNING.md +204 -0
  71. package/docs/PILOT.md +444 -0
  72. package/docs/PROOFPARCEL.md +227 -0
  73. package/docs/PROOFS.md +262 -0
  74. package/docs/RECEIPTS.md +341 -0
  75. package/docs/REPUTATION.md +158 -0
  76. package/docs/SDK.md +301 -0
  77. package/docs/STRATEGY-ARCHIVE.md +5055 -0
  78. package/docs/SUPERVISOR-RUNBOOK.md +52 -0
  79. package/docs/TRUST-BOUNDARIES.md +335 -0
  80. package/docs/TRUSTLEDGER.md +1976 -0
  81. package/docs/USAGE-BUDGET.json +121 -0
  82. package/docs/VERIFY-SERVICE.md +168 -0
  83. package/index.js +160 -0
  84. package/package.json +41 -0
  85. package/trustledger/build-standalone.js +796 -0
  86. package/trustledger/cli.js +3179 -0
  87. package/trustledger/close.js +391 -0
  88. package/trustledger/corpus.js +159 -0
  89. package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
  90. package/trustledger/dist/trustledger-standalone.html +6197 -0
  91. package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
  92. package/trustledger/door-core.js +442 -0
  93. package/trustledger/fixtures/bank.csv +7 -0
  94. package/trustledger/fixtures/bank.malformed.csv +3 -0
  95. package/trustledger/fixtures/bank.noalias.csv +5 -0
  96. package/trustledger/fixtures/bank.ofx +34 -0
  97. package/trustledger/fixtures/bank.real.csv +5 -0
  98. package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
  99. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
  100. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
  101. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
  102. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
  103. package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
  104. package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
  105. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
  106. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
  107. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
  108. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
  109. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
  110. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
  111. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
  112. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
  113. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
  114. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
  115. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
  116. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
  117. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
  118. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
  119. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
  120. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
  121. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
  122. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
  123. package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
  124. package/trustledger/fixtures/e2e/bank.csv +4 -0
  125. package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
  126. package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
  127. package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
  128. package/trustledger/fixtures/e2e/rentroll.csv +6 -0
  129. package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
  130. package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
  131. package/trustledger/fixtures/plans/baseline.json +25 -0
  132. package/trustledger/fixtures/plans/price-binding.example.json +27 -0
  133. package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
  134. package/trustledger/fixtures/policy/baseline.json +19 -0
  135. package/trustledger/fixtures/policy/ca-example.json +12 -0
  136. package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
  137. package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
  138. package/trustledger/fixtures/quickbooks.csv +7 -0
  139. package/trustledger/fixtures/quickbooks.real.csv +5 -0
  140. package/trustledger/fixtures/rentroll.csv +6 -0
  141. package/trustledger/fixtures/rentroll.real.csv +4 -0
  142. package/trustledger/ingest.js +1163 -0
  143. package/trustledger/lib/policy-bundled-loader.js +44 -0
  144. package/trustledger/lib/sha256-vendored.js +227 -0
  145. package/trustledger/license.js +563 -0
  146. package/trustledger/match.js +551 -0
  147. package/trustledger/plans.js +551 -0
  148. package/trustledger/policy.js +398 -0
  149. package/trustledger/public/index.html +512 -0
  150. package/trustledger/reconcile.js +1486 -0
  151. package/trustledger/report.js +887 -0
  152. package/trustledger/seal.js +854 -0
  153. package/trustledger/server.js +391 -0
  154. package/trustledger/valueproof.js +350 -0
@@ -0,0 +1,454 @@
1
+ "use strict";
2
+
3
+ // cli/journal-log.js — the pure, transport/filesystem-agnostic ORDERED MERKLE-LOG CORE (T-63.1).
4
+ //
5
+ // WHY THIS EXISTS (EPIC-63, an AUDITABLE transparency log over the journal)
6
+ // cli/journal.js gives an APPEND-ONLY hash-chain: verifyJournal walks the whole chain and localizes
7
+ // the first break. That proves continuity, but a consumer must hold (or re-walk) the ENTIRE log to
8
+ // check any one fact. This module adds the second half a real transparency log needs: an RFC-6962 /
9
+ // Certificate-Transparency-style ORDERED Merkle tree over the journal's entry hashes, so that:
10
+ // - a single tree HEAD (root + size) commits to the whole ordered log;
11
+ // - INCLUSION can be proven for one entry with an O(log n) path — the consumer never needs the log;
12
+ // - CONSISTENCY between an old head (size m) and a new head (size n) can be proven with an O(log n)
13
+ // path — proving the new log is an APPEND-ONLY EXTENSION of the old one WITHOUT the full log.
14
+ // Together these let an auditor who saw an old signed head later confirm the operator only ever
15
+ // APPENDED — the exact "no history was rewritten" guarantee a hash-chain alone cannot prove compactly.
16
+ //
17
+ // POSITION-PRESERVING, NOT SORTED (the crucial difference from cli/hash.js buildTree)
18
+ // cli/hash.js builds a *sorted-leaf, sorted-pair* Merkle root: it commits to a SET of files and is
19
+ // deliberately order-independent. A journal is the opposite: ORDER IS MEANING. So this tree keeps
20
+ // leaves in their given position and folds interior nodes as HASH(0x01 || left || right) with the
21
+ // children in TREE ORDER (NOT min/max). Reordering the log therefore changes the root — which is
22
+ // exactly what makes an append-only/consistency proof possible. The two trees intentionally produce
23
+ // DIFFERENT roots for the same leaves when order matters (a test demonstrates this).
24
+ //
25
+ // CRYPTO REUSED VERBATIM (no new primitive invented here)
26
+ // The only hash is `hashBytes` (keccak256) from cli/hash.js — the SAME primitive the project already
27
+ // trusts for seals and Merkle roots. Domain separation follows RFC 6962:
28
+ // - leaf hash: HASH(0x00 || leafData) -> leafHash()
29
+ // - node hash: HASH(0x01 || left || right) -> nodeHash() (children in ORDER, not sorted)
30
+ // The 0x00 / 0x01 prefixes keep a leaf value and an interior node in disjoint spaces, so a node can
31
+ // never be replayed as a leaf (second-preimage resistance).
32
+ //
33
+ // PURITY (a hard acceptance criterion)
34
+ // This file does NO disk I/O, opens NO socket, and holds no signing material. It requires ONLY:
35
+ // - `hashBytes` from cli/hash.js, and
36
+ // - the pure byte helpers `concat` / `toUtf8Bytes` from ethers (NOT network or signing primitives).
37
+ // A grep in test/journal-log.core.test.js asserts it requires NONE of fs/http/https/net/dns and does
38
+ // no signer/keyfile work. Every exported function is TOTAL: it NEVER throws on malformed/adversarial
39
+ // input — generators return `null`, verifiers return `false`, and there is NO clock and NO randomness,
40
+ // so results are fully deterministic.
41
+ //
42
+ // ALGORITHMS
43
+ // The tree head (MTH), inclusion PATH and consistency PROOF/SUBPROOF are the recursive definitions
44
+ // from RFC 6962 §2.1. The verifiers are the iterative, index-arithmetic reference algorithm used by
45
+ // Certificate Transparency (decompose the index into an "inner" run + a "border" of carried subtrees,
46
+ // then fold) — an INDEPENDENT reconstruction path from the generator, so a generate→verify round-trip
47
+ // over every (size, index) and every (m ≤ n) pair is a strong correctness check (the test does this).
48
+
49
+ const { concat, toUtf8Bytes } = require("ethers");
50
+ const { hashBytes } = require("./hash");
51
+
52
+ // ---------------------------------------------------------------------------------------------------
53
+ // Domain-separated hashing (RFC 6962). leaf = 0x00-prefixed, node = 0x01-prefixed, children NOT sorted.
54
+ // ---------------------------------------------------------------------------------------------------
55
+
56
+ const LEAF_PREFIX = "0x00";
57
+ const NODE_PREFIX = "0x01";
58
+
59
+ // The documented root of the EMPTY log. RFC 6962 hashes the empty string; we use a domain-separated
60
+ // constant instead so an empty-tree root can never collide with a real leaf/node hash. Deterministic
61
+ // and recomputable: keccak256 of the fixed ASCII domain below.
62
+ const EMPTY_ROOT_DOMAIN = "vh.journal-log/v1:empty-root";
63
+ const EMPTY_ROOT = hashBytes(toUtf8Bytes(EMPTY_ROOT_DOMAIN));
64
+
65
+ const HEX32_RE = /^0x[0-9a-fA-F]{64}$/;
66
+
67
+ function _isHex32(x) {
68
+ return typeof x === "string" && HEX32_RE.test(x);
69
+ }
70
+
71
+ function _allHex32(arr) {
72
+ return Array.isArray(arr) && arr.every(_isHex32);
73
+ }
74
+
75
+ /**
76
+ * RFC-6962 leaf hash: HASH(0x00 || leafData). `leafData` is a 0x-prefixed 32-byte hex value (here, a
77
+ * journal entry hash). Single-leaf trees have root === leafHash(leaf0).
78
+ * @param {string} leaf 0x bytes32
79
+ * @returns {string} 0x bytes32
80
+ */
81
+ function leafHash(leaf) {
82
+ return hashBytes(concat([LEAF_PREFIX, leaf]));
83
+ }
84
+
85
+ /**
86
+ * RFC-6962 interior node hash: HASH(0x01 || left || right). Children are folded in TREE ORDER — NOT
87
+ * min/max sorted — which is what makes the tree position-preserving.
88
+ * @param {string} left 0x bytes32
89
+ * @param {string} right 0x bytes32
90
+ * @returns {string} 0x bytes32
91
+ */
92
+ function nodeHash(left, right) {
93
+ return hashBytes(concat([NODE_PREFIX, left, right]));
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------------------------------
97
+ // Small non-negative-integer bit helpers. Implemented with division/modulo (not JS 32-bit bitwise
98
+ // operators) so they stay correct for any safe integer and never surprise on large sizes. Pure.
99
+ // ---------------------------------------------------------------------------------------------------
100
+
101
+ // Number of bits to represent x (x >= 0): bitLength(0)=0, bitLength(1)=1, bitLength(5)=3.
102
+ function _bitLength(x) {
103
+ let n = 0;
104
+ while (x > 0) {
105
+ x = Math.floor(x / 2);
106
+ n++;
107
+ }
108
+ return n;
109
+ }
110
+
111
+ // Population count (number of set bits) of x (x >= 0).
112
+ function _onesCount(x) {
113
+ let c = 0;
114
+ while (x > 0) {
115
+ c += x % 2;
116
+ x = Math.floor(x / 2);
117
+ }
118
+ return c;
119
+ }
120
+
121
+ // Number of trailing zero bits of x (x >= 1): trailingZeros(1)=0, trailingZeros(4)=2, trailingZeros(6)=1.
122
+ function _trailingZeros(x) {
123
+ let n = 0;
124
+ while (x > 0 && x % 2 === 0) {
125
+ x = Math.floor(x / 2);
126
+ n++;
127
+ }
128
+ return n;
129
+ }
130
+
131
+ // Bitwise XOR of two non-negative integers, without 32-bit truncation.
132
+ function _xor(a, b) {
133
+ let res = 0;
134
+ let bit = 1;
135
+ while (a > 0 || b > 0) {
136
+ if (a % 2 !== b % 2) res += bit;
137
+ a = Math.floor(a / 2);
138
+ b = Math.floor(b / 2);
139
+ bit *= 2;
140
+ }
141
+ return res;
142
+ }
143
+
144
+ // Logical right shift: floor(x / 2^k).
145
+ function _shr(x, k) {
146
+ return Math.floor(x / Math.pow(2, k));
147
+ }
148
+
149
+ // The i-th bit of x (0 = least significant).
150
+ function _bitAt(x, i) {
151
+ return _shr(x, i) % 2;
152
+ }
153
+
154
+ // Largest power of two STRICTLY less than n (RFC 6962 "k"), for n >= 2. e.g. 2->1, 3->2, 4->2, 5->4.
155
+ function _largestPowerOfTwoLessThan(n) {
156
+ let k = 1;
157
+ while (k * 2 < n) k *= 2;
158
+ return k;
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------------------------------
162
+ // Merkle Tree Hash (MTH) — RFC 6962 §2.1, recursive. Operates on the ORDERED raw leaf values.
163
+ // MTH({}) = EMPTY_ROOT
164
+ // MTH({d0}) = leafHash(d0)
165
+ // MTH(D[0:n]) = nodeHash(MTH(D[0:k]), MTH(D[k:n])), k = largest power of two < n
166
+ // Assumes `data` is a validated array of hex32; callers validate first.
167
+ // ---------------------------------------------------------------------------------------------------
168
+
169
+ function _mth(data) {
170
+ const n = data.length;
171
+ if (n === 0) return EMPTY_ROOT;
172
+ if (n === 1) return leafHash(data[0]);
173
+ const k = _largestPowerOfTwoLessThan(n);
174
+ return nodeHash(_mth(data.slice(0, k)), _mth(data.slice(k)));
175
+ }
176
+
177
+ /**
178
+ * Compute the tree HEAD over an ORDERED list of leaf values.
179
+ *
180
+ * @param {string[]} leaves ordered 0x-bytes32 leaf values (e.g. journal entry hashes).
181
+ * @returns {{ size: number, root: string|null }}
182
+ * `root` is the RFC-6962 MTH of the leaves (EMPTY_ROOT for []). On malformed input (not an
183
+ * array, or any non-hex32 leaf) `root` is null and `size` is the array length (or 0) — TOTAL,
184
+ * never throws.
185
+ */
186
+ function treeHead(leaves) {
187
+ if (!Array.isArray(leaves)) return { size: 0, root: null };
188
+ if (!_allHex32(leaves)) return { size: leaves.length, root: null };
189
+ return { size: leaves.length, root: _mth(leaves) };
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------------------------------
193
+ // Inclusion proof — RFC 6962 §2.1.1 PATH(m, D[n]). Returns the audit path (sibling subtree roots) from
194
+ // leaf m up to the root, bottom-first.
195
+ // ---------------------------------------------------------------------------------------------------
196
+
197
+ function _path(m, data) {
198
+ const n = data.length;
199
+ if (n === 1) return []; // m must be 0 here; the lone leaf has no sibling.
200
+ const k = _largestPowerOfTwoLessThan(n);
201
+ if (m < k) {
202
+ // Leaf is in the left subtree; sibling is the whole right subtree root.
203
+ return _path(m, data.slice(0, k)).concat([_mth(data.slice(k))]);
204
+ }
205
+ // Leaf is in the right subtree; sibling is the whole left subtree root.
206
+ return _path(m - k, data.slice(k)).concat([_mth(data.slice(0, k))]);
207
+ }
208
+
209
+ /**
210
+ * Build an inclusion proof for the leaf at index `i` in the ordered `leaves`.
211
+ *
212
+ * @param {string[]} leaves ordered 0x-bytes32 leaf values.
213
+ * @param {number} i leaf index, 0 <= i < leaves.length.
214
+ * @returns {{ leaf: string, leafIndex: number, treeSize: number, path: string[] } | null}
215
+ * A self-describing proof, or `null` on malformed input (bad leaves, non-integer/out-of-range
216
+ * index). TOTAL — never throws.
217
+ */
218
+ function inclusionProof(leaves, i) {
219
+ if (!_allHex32(leaves)) return null;
220
+ const n = leaves.length;
221
+ if (!Number.isInteger(i) || i < 0 || i >= n) return null;
222
+ return {
223
+ leaf: leaves[i],
224
+ leafIndex: i,
225
+ treeSize: n,
226
+ path: _path(i, leaves),
227
+ };
228
+ }
229
+
230
+ // ---------------------------------------------------------------------------------------------------
231
+ // Inclusion verification — iterative CT reference algorithm. Reconstructs the root from the leaf hash,
232
+ // index and path, then compares. Independent of the recursive generator above.
233
+ // ---------------------------------------------------------------------------------------------------
234
+
235
+ // Fold the "inner" portion of the path: at level i, the sibling is on the right if bit i of `index` is
236
+ // 0 (seed is the left child), else on the left.
237
+ function _chainInner(seed, path, index) {
238
+ let acc = seed;
239
+ for (let i = 0; i < path.length; i++) {
240
+ acc = _bitAt(index, i) === 0 ? nodeHash(acc, path[i]) : nodeHash(path[i], acc);
241
+ }
242
+ return acc;
243
+ }
244
+
245
+ // Like _chainInner but only folds the levels where bit i of `index` is 1 (used by consistency).
246
+ function _chainInnerRight(seed, path, index) {
247
+ let acc = seed;
248
+ for (let i = 0; i < path.length; i++) {
249
+ if (_bitAt(index, i) === 1) acc = nodeHash(path[i], acc);
250
+ }
251
+ return acc;
252
+ }
253
+
254
+ // Fold the "border" portion: each remaining sibling is always on the left (a carried left subtree).
255
+ function _chainBorderRight(seed, path) {
256
+ let acc = seed;
257
+ for (const h of path) acc = nodeHash(h, acc);
258
+ return acc;
259
+ }
260
+
261
+ // Reconstruct the root implied by (index, size, leafHash, path), or null if shapes are inconsistent.
262
+ function _rootFromInclusionPath(index, size, leafHashVal, path) {
263
+ if (!Number.isInteger(index) || !Number.isInteger(size)) return null;
264
+ if (size < 1 || index < 0 || index >= size) return null;
265
+ if (!_isHex32(leafHashVal) || !_allHex32(path)) return null;
266
+ const inner = _bitLength(_xor(index, size - 1));
267
+ const border = _onesCount(_shr(index, inner));
268
+ if (path.length !== inner + border) return null;
269
+ let res = _chainInner(leafHashVal, path.slice(0, inner), index);
270
+ res = _chainBorderRight(res, path.slice(inner));
271
+ return res;
272
+ }
273
+
274
+ /**
275
+ * Verify an inclusion proof against a tree head.
276
+ *
277
+ * Recomputes the leaf hash from `proof.leaf` (so the proof is bound to the actual leaf DATA, not a
278
+ * caller-supplied leaf hash), reconstructs the root from (leafIndex, treeSize, path), and checks it
279
+ * equals the head's root. Returns a plain boolean and NEVER throws.
280
+ *
281
+ * `head` may be either:
282
+ * - a bare 0x-bytes32 root string (e.g. `treeHead(leaves).root`), or
283
+ * - the full head object `{ size, root }` (e.g. `treeHead(leaves)` — the RECOMMENDED form).
284
+ * In a real Signed Tree Head the size and root are trusted TOGETHER, so passing the full head also
285
+ * BINDS the size: a proof whose self-asserted `treeSize` disagrees with the trusted `head.size` is
286
+ * rejected even if its path happens to reconstruct the same root. (With a bare root the size is not
287
+ * independently known, so only the root is checked — exactly the RFC 6962 §2.1.1 contract.)
288
+ *
289
+ * @param {{ leaf: string, leafIndex: number, treeSize: number, path: string[] }} proof
290
+ * @param {string|{size:number,root:string}} head the trusted tree root, or the full `{size,root}` head.
291
+ * @returns {boolean} true iff the proof is valid for `head`; false on any tampering or malformed input.
292
+ */
293
+ function verifyInclusion(proof, head) {
294
+ // Accept either a bare root string or a { size, root } head. Binding size (when present) is what
295
+ // lets a "replayed against a different size" proof be rejected outright.
296
+ let root = head;
297
+ let expectedSize = null;
298
+ if (head !== null && typeof head === "object" && !Array.isArray(head)) {
299
+ root = head.root;
300
+ if (Number.isInteger(head.size)) expectedSize = head.size;
301
+ }
302
+ if (proof === null || typeof proof !== "object" || Array.isArray(proof)) return false;
303
+ if (!_isHex32(root)) return false;
304
+ if (expectedSize !== null && proof.treeSize !== expectedSize) return false;
305
+ if (!_isHex32(proof.leaf) || !_allHex32(proof.path)) return false;
306
+ const recomputed = _rootFromInclusionPath(
307
+ proof.leafIndex,
308
+ proof.treeSize,
309
+ leafHash(proof.leaf),
310
+ proof.path
311
+ );
312
+ return recomputed !== null && recomputed === root;
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------------------------------
316
+ // Consistency proof — RFC 6962 §2.1.2 PROOF(m, D[n]) = SUBPROOF(m, D[n], true). Proves that the tree of
317
+ // size n is an APPEND-ONLY extension of the tree of size m (0 < m <= n).
318
+ // ---------------------------------------------------------------------------------------------------
319
+
320
+ function _subproof(m, data, b) {
321
+ const n = data.length;
322
+ if (m === n) {
323
+ // The old tree is exactly this subtree. If it is a complete subtree the verifier already knows
324
+ // (b === true, first call side), omit its root; otherwise include it so the verifier can seed.
325
+ return b ? [] : [_mth(data)];
326
+ }
327
+ const k = _largestPowerOfTwoLessThan(n);
328
+ if (m <= k) {
329
+ // Old tree lives entirely in the left subtree; carry the right subtree root.
330
+ return _subproof(m, data.slice(0, k), b).concat([_mth(data.slice(k))]);
331
+ }
332
+ // Old tree spans the whole left subtree plus part of the right; the left subtree root is now a
333
+ // "known" complete subtree, so recurse into the right with b = false.
334
+ return _subproof(m - k, data.slice(k), false).concat([_mth(data.slice(0, k))]);
335
+ }
336
+
337
+ /**
338
+ * Build a consistency proof between the size-`m` prefix and the size-`n` prefix of `leaves`.
339
+ *
340
+ * @param {string[]} leaves ordered 0x-bytes32 leaf values (length >= n).
341
+ * @param {number} m first (older) tree size, 0 < m <= n.
342
+ * @param {number} n second (newer) tree size, m <= n <= leaves.length.
343
+ * @returns {{ firstSize: number, secondSize: number, path: string[] } | null}
344
+ * A self-describing proof, or `null` on malformed input. TOTAL — never throws.
345
+ */
346
+ function consistencyProof(leaves, m, n) {
347
+ if (!_allHex32(leaves)) return null;
348
+ if (!Number.isInteger(m) || !Number.isInteger(n)) return null;
349
+ if (m <= 0 || n < m || n > leaves.length) return null;
350
+ const data = leaves.slice(0, n);
351
+ // SUBPROOF is defined for 0 < m < n; when m === n the proof is empty (the roots are compared directly).
352
+ const path = m === n ? [] : _subproof(m, data, true);
353
+ return { firstSize: m, secondSize: n, path };
354
+ }
355
+
356
+ /**
357
+ * Verify a consistency proof: that the tree with root `secondRoot` (size proof.secondSize) is an
358
+ * append-only extension of the tree with root `firstRoot` (size proof.firstSize).
359
+ *
360
+ * Iterative CT reference algorithm: it folds the shared path twice — once biased to reproduce the OLD
361
+ * root and once the NEW root — and both must match. Returns a plain boolean and NEVER throws. A proof
362
+ * that rewrote/reordered any of the first `firstSize` leaves cannot make BOTH folds match, so it is
363
+ * rejected: append-only-ness is verifiable WITHOUT the full log.
364
+ *
365
+ * `firstRoot` / `secondRoot` may each be a bare 0x-bytes32 root string OR the full head object
366
+ * `{ size, root }` (the RECOMMENDED form). Passing the full heads also BINDS the sizes: a proof whose
367
+ * self-asserted `firstSize`/`secondSize` disagrees with a supplied head size is rejected outright.
368
+ *
369
+ * @param {{ firstSize: number, secondSize: number, path: string[] }} proof
370
+ * @param {string|{size:number,root:string}} firstRoot root (or head) of the older tree.
371
+ * @param {string|{size:number,root:string}} secondRoot root (or head) of the newer tree.
372
+ * @returns {boolean}
373
+ */
374
+ function verifyConsistency(proof, firstRoot, secondRoot) {
375
+ if (proof === null || typeof proof !== "object" || Array.isArray(proof)) return false;
376
+ const m = proof.firstSize;
377
+ const n = proof.secondSize;
378
+ const path = proof.path;
379
+ if (!Number.isInteger(m) || !Number.isInteger(n)) return false;
380
+ // Accept either bare root strings or { size, root } heads; bind sizes when heads are supplied.
381
+ const _head = (h, size) => {
382
+ if (h !== null && typeof h === "object" && !Array.isArray(h)) {
383
+ if (Number.isInteger(h.size) && h.size !== size) return null; // size mismatch => reject
384
+ return h.root;
385
+ }
386
+ return h;
387
+ };
388
+ firstRoot = _head(firstRoot, m);
389
+ secondRoot = _head(secondRoot, n);
390
+ if (!_isHex32(firstRoot) || !_isHex32(secondRoot)) return false;
391
+ if (!_allHex32(path)) return false;
392
+
393
+ if (n < m) return false;
394
+ if (m === n) {
395
+ // Same size ⇒ same tree ⇒ empty proof and identical roots.
396
+ return path.length === 0 && firstRoot === secondRoot;
397
+ }
398
+ if (m === 0) {
399
+ // The empty tree is trivially consistent with any tree; the proof carries nothing.
400
+ return path.length === 0;
401
+ }
402
+ // 0 < m < n
403
+ if (path.length === 0) return false;
404
+
405
+ // Decompose as if proving inclusion of leaf (m-1) in a tree of size n, then strip the trailing zeros
406
+ // of m (the old tree's right border is a run of complete subtrees the verifier folds itself).
407
+ let inner = _bitLength(_xor(m - 1, n - 1));
408
+ const border = _onesCount(_shr(m - 1, inner));
409
+ const shift = _trailingZeros(m);
410
+ inner -= shift;
411
+
412
+ // When m is an exact power of two the old root IS a complete subtree, so it seeds both folds and is
413
+ // NOT transmitted; otherwise the first path element is the shared seed.
414
+ let seed;
415
+ let start;
416
+ if (m === Math.pow(2, shift)) {
417
+ seed = firstRoot;
418
+ start = 0;
419
+ } else {
420
+ seed = path[0];
421
+ start = 1;
422
+ }
423
+ if (path.length !== start + inner + border) return false;
424
+ const p = path.slice(start);
425
+ const mask = _shr(m - 1, shift);
426
+
427
+ // Fold biased to the OLD tree: only the right-hand carries participate in the inner run.
428
+ let hash1 = _chainInnerRight(seed, p.slice(0, inner), mask);
429
+ hash1 = _chainBorderRight(hash1, p.slice(inner));
430
+ if (hash1 !== firstRoot) return false;
431
+
432
+ // Fold biased to the NEW tree: the full inner run participates.
433
+ let hash2 = _chainInner(seed, p.slice(0, inner), mask);
434
+ hash2 = _chainBorderRight(hash2, p.slice(inner));
435
+ if (hash2 !== secondRoot) return false;
436
+
437
+ return true;
438
+ }
439
+
440
+ module.exports = {
441
+ // The 5 required exports.
442
+ treeHead,
443
+ inclusionProof,
444
+ verifyInclusion,
445
+ consistencyProof,
446
+ verifyConsistency,
447
+ // Building blocks / documented constants (useful to callers and tests).
448
+ leafHash,
449
+ nodeHash,
450
+ EMPTY_ROOT,
451
+ EMPTY_ROOT_DOMAIN,
452
+ LEAF_PREFIX,
453
+ NODE_PREFIX,
454
+ };