@forestrie/merklelog 0.0.1

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/index.d.ts +21 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +20 -0
  6. package/dist/massifs/indexformat.d.ts +146 -0
  7. package/dist/massifs/indexformat.d.ts.map +1 -0
  8. package/dist/massifs/indexformat.js +191 -0
  9. package/dist/massifs/logformat.d.ts +49 -0
  10. package/dist/massifs/logformat.d.ts.map +1 -0
  11. package/dist/massifs/logformat.js +49 -0
  12. package/dist/massifs/massif.d.ts +86 -0
  13. package/dist/massifs/massif.d.ts.map +1 -0
  14. package/dist/massifs/massif.js +143 -0
  15. package/dist/massifs/massiffull.d.ts +14 -0
  16. package/dist/massifs/massiffull.d.ts.map +1 -0
  17. package/dist/massifs/massiffull.js +21 -0
  18. package/dist/massifs/massiflogentries.d.ts +23 -0
  19. package/dist/massifs/massiflogentries.d.ts.map +1 -0
  20. package/dist/massifs/massiflogentries.js +31 -0
  21. package/dist/massifs/massifstart.d.ts +63 -0
  22. package/dist/massifs/massifstart.d.ts.map +1 -0
  23. package/dist/massifs/massifstart.js +39 -0
  24. package/dist/massifs/mmrindex.d.ts +15 -0
  25. package/dist/massifs/mmrindex.d.ts.map +1 -0
  26. package/dist/massifs/mmrindex.js +24 -0
  27. package/dist/massifs/peakstackend.d.ts +21 -0
  28. package/dist/massifs/peakstackend.d.ts.map +1 -0
  29. package/dist/massifs/peakstackend.js +29 -0
  30. package/dist/massifs/types.d.ts +20 -0
  31. package/dist/massifs/types.d.ts.map +1 -0
  32. package/dist/massifs/types.js +23 -0
  33. package/dist/massifs/urkleindex.d.ts +116 -0
  34. package/dist/massifs/urkleindex.d.ts.map +1 -0
  35. package/dist/massifs/urkleindex.js +169 -0
  36. package/dist/massifs/v2storagepaths.d.ts +17 -0
  37. package/dist/massifs/v2storagepaths.d.ts.map +1 -0
  38. package/dist/massifs/v2storagepaths.js +46 -0
  39. package/dist/mmr/algorithms-sync.d.ts +17 -0
  40. package/dist/mmr/algorithms-sync.d.ts.map +1 -0
  41. package/dist/mmr/algorithms-sync.js +42 -0
  42. package/dist/mmr/algorithms.d.ts +79 -0
  43. package/dist/mmr/algorithms.d.ts.map +1 -0
  44. package/dist/mmr/algorithms.js +155 -0
  45. package/dist/mmr/index.d.ts +37 -0
  46. package/dist/mmr/index.d.ts.map +1 -0
  47. package/dist/mmr/index.js +64 -0
  48. package/dist/mmr/math.d.ts +67 -0
  49. package/dist/mmr/math.d.ts.map +1 -0
  50. package/dist/mmr/math.js +172 -0
  51. package/dist/mmr/types.d.ts +37 -0
  52. package/dist/mmr/types.d.ts.map +1 -0
  53. package/dist/mmr/types.js +4 -0
  54. package/dist/uint64/index.d.ts +90 -0
  55. package/dist/uint64/index.d.ts.map +1 -0
  56. package/dist/uint64/index.js +149 -0
  57. package/dist/utils/arrays.d.ts +15 -0
  58. package/dist/utils/arrays.d.ts.map +1 -0
  59. package/dist/utils/arrays.js +24 -0
  60. package/package.json +41 -0
  61. package/src/index.ts +68 -0
  62. package/src/massifs/indexformat.ts +217 -0
  63. package/src/massifs/logformat.ts +54 -0
  64. package/src/massifs/massif.ts +182 -0
  65. package/src/massifs/massiffull.ts +28 -0
  66. package/src/massifs/massiflogentries.ts +40 -0
  67. package/src/massifs/massifstart.ts +67 -0
  68. package/src/massifs/mmrindex.ts +32 -0
  69. package/src/massifs/peakstackend.ts +34 -0
  70. package/src/massifs/types.ts +55 -0
  71. package/src/massifs/urkleindex.ts +273 -0
  72. package/src/massifs/v2storagepaths.ts +61 -0
  73. package/src/mmr/algorithms-sync.ts +46 -0
  74. package/src/mmr/algorithms.ts +194 -0
  75. package/src/mmr/index.ts +75 -0
  76. package/src/mmr/math.ts +190 -0
  77. package/src/mmr/types.ts +39 -0
  78. package/src/uint64/index.ts +164 -0
  79. package/src/utils/arrays.ts +25 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * MMR index calculation functions
3
+ *
4
+ * These functions are needed by the massifs module for computing
5
+ * firstIndex and peakStackLen.
6
+ *
7
+ * Also exports the core mmrIndex function for converting leaf indices to MMR indices.
8
+ */
9
+
10
+ /**
11
+ * Returns the number of peaks preceding iLeaf that the future tree requires.
12
+ *
13
+ * This corresponds to the number of preceding nodes that will be required to
14
+ * derive future interior nodes. If those preceding nodes are maintained in a
15
+ * stack, this is the current length of the stack.
16
+ *
17
+ * Translated from go-merklelog/mmr/spurs.go LeafMinusSpurSum
18
+ */
19
+ export function leafMinusSpurSum(leafIndex: bigint): bigint {
20
+ let sum = leafIndex;
21
+ let current = leafIndex >> 1n;
22
+ while (current > 0n) {
23
+ sum -= current;
24
+ current >>= 1n;
25
+ }
26
+ return sum;
27
+ }
28
+
29
+ /**
30
+ * Returns the MMR index of the first leaf in the massif blob identified by massifIndex
31
+ *
32
+ * Translated from go-merklelog/massifs/massifstart.go MassifFirstLeaf
33
+ */
34
+ export function massifFirstLeaf(
35
+ massifHeight: number,
36
+ massifIndex: number,
37
+ ): bigint {
38
+ // The number of nodes 'm' in a massif is: m = (1 << h) - 1
39
+ const m = BigInt((1 << massifHeight) - 1);
40
+
41
+ // The number of leaves 'f' in every massif: f = (m + 1) / 2
42
+ const f = (m + 1n) >> 1n;
43
+
44
+ // The first leaf index is then: leafIndex = f * massifIndex
45
+ const leafIndex = f * BigInt(massifIndex);
46
+
47
+ // Apply MMRIndex to the leaf index to get the MMR index
48
+ return mmrIndex(leafIndex);
49
+ }
50
+
51
+ /**
52
+ * Returns the node index for the leaf e
53
+ *
54
+ * Args:
55
+ * - leafIndex: the leaf index, where the leaves are numbered consecutively, ignoring interior nodes.
56
+ *
57
+ * Returns:
58
+ * The mmr index for the element leafIndex
59
+ *
60
+ * Translated from go-merklelog/mmr/mmrindex.go MMRIndex
61
+ */
62
+ export function mmrIndex(leafIndex: bigint): bigint {
63
+ let sum = 0n;
64
+ let current = leafIndex;
65
+
66
+ while (current > 0n) {
67
+ // Find the position of the most significant bit (height)
68
+ const h = BigInt(current.toString(2).length);
69
+ sum += (1n << h) - 1n;
70
+ const half = 1n << (h - 1n);
71
+ current -= half;
72
+ }
73
+
74
+ return sum;
75
+ }
@@ -0,0 +1,190 @@
1
+ import { Uint64 } from "../uint64/index.js";
2
+
3
+ /**
4
+ * MMR Math Functions
5
+ *
6
+ * Core mathematical functions for Merkle Mountain Range operations.
7
+ * Uses Uint64 wrapper for all arithmetic to ensure correctness.
8
+ */
9
+
10
+ /**
11
+ * Returns the zero-based height index (g) of an MMR index
12
+ *
13
+ * The height index of a leaf is 0.
14
+ * Translated from go-merklelog/mmr/indexheight.go IndexHeight
15
+ */
16
+ export function heightIndex(mmrIndex: Uint64): number {
17
+ // Convert from zero-based index to 1-based position
18
+ const pos = mmrIndex.add(new Uint64(1));
19
+ return posHeight(pos);
20
+ }
21
+
22
+ /**
23
+ * Returns the one-based height (h) of an MMR index
24
+ *
25
+ * h = g + 1 where g is the height index
26
+ */
27
+ export function height(mmrIndex: Uint64): number {
28
+ return heightIndex(mmrIndex) + 1;
29
+ }
30
+
31
+ /**
32
+ * PosHeight - obtains height from a 1-based position
33
+ *
34
+ * Translated from go-merklelog/mmr/indexheight.go PosHeight
35
+ */
36
+ function posHeight(pos: Uint64): number {
37
+ let current = pos;
38
+ while (!allOnes(current)) {
39
+ current = jumpLeftPerfect(current);
40
+ }
41
+ return bitLength(current) - 1;
42
+ }
43
+
44
+ /**
45
+ * JumpLeftPerfect - jumps left to the leftmost node at the same height
46
+ *
47
+ * Translated from go-merklelog/mmr/indexheight.go JumpLeftPerfect
48
+ */
49
+ function jumpLeftPerfect(pos: Uint64): Uint64 {
50
+ const bitLen = bitLength(pos);
51
+ const mostSignificantBit = new Uint64(1).shl(bitLen - 1);
52
+ return pos.sub(mostSignificantBit.sub(new Uint64(1)));
53
+ }
54
+
55
+ /**
56
+ * Checks if a value has all bits set (all ones in binary)
57
+ */
58
+ function allOnes(value: Uint64): boolean {
59
+ // A value has all ones if value + 1 is a power of 2
60
+ const next = value.add(new Uint64(1));
61
+ const bitLen = bitLength(next);
62
+ const powerOf2 = new Uint64(1).shl(bitLen - 1);
63
+ return next.equals(powerOf2);
64
+ }
65
+
66
+ /**
67
+ * Returns the number of bits needed to represent the value
68
+ */
69
+ function bitLength(value: Uint64): number {
70
+ const bigInt = value.toBigInt();
71
+ if (bigInt === 0n) {
72
+ return 0;
73
+ }
74
+ return bigInt.toString(2).length;
75
+ }
76
+
77
+ /**
78
+ * Converts leaf index to MMR index
79
+ *
80
+ * @param leafIndex - Zero-based leaf index
81
+ * @returns MMR index
82
+ */
83
+ export function mmrIndexFromLeafIndex(leafIndex: Uint64): Uint64 {
84
+ // Use the mmrIndex function from index.ts
85
+ // Import it dynamically to avoid circular dependency
86
+ return new Uint64(mmrIndexFromLeafIndexInternal(leafIndex.toBigInt()));
87
+ }
88
+
89
+ // Internal helper that matches the implementation in index.ts
90
+ function mmrIndexFromLeafIndexInternal(leafIndex: bigint): bigint {
91
+ let sum = 0n;
92
+ let current = leafIndex;
93
+
94
+ while (current > 0n) {
95
+ const h = BigInt(current.toString(2).length);
96
+ sum += (1n << h) - 1n;
97
+ const half = 1n << (h - 1n);
98
+ current -= half;
99
+ }
100
+
101
+ return sum;
102
+ }
103
+
104
+ /**
105
+ * Converts MMR index to leaf index
106
+ *
107
+ * @param mmrIndex - MMR index
108
+ * @returns Leaf index
109
+ */
110
+ export function leafIndex(mmrIndex: Uint64): Uint64 {
111
+ // This needs to be implemented based on the MMR structure
112
+ // For now, we'll use an iterative approach
113
+ let current = mmrIndex;
114
+ let leafCount = new Uint64(0);
115
+
116
+ while (current.toBigInt() > 0n) {
117
+ const h = heightIndex(current);
118
+ const peakSize = new Uint64(1).shl(h + 1).sub(new Uint64(1)); // (1 << (h+1)) - 1
119
+ leafCount = leafCount.add(new Uint64(1).shl(h)); // Add 2^h leaves
120
+ current = current.sub(peakSize);
121
+ }
122
+
123
+ return leafCount.sub(new Uint64(1)); // Convert to zero-based
124
+ }
125
+
126
+ /**
127
+ * Converts MMR index to MMR position (index + 1)
128
+ *
129
+ * @param mmrIndex - Zero-based MMR index
130
+ * @returns One-based MMR position
131
+ */
132
+ export function mmrPosition(mmrIndex: Uint64): Uint64 {
133
+ return mmrIndex.add(new Uint64(1));
134
+ }
135
+
136
+ /**
137
+ * Returns the number of nodes given a height index (g)
138
+ *
139
+ * n = 2^(g+1) - 1 = (2 << g) - 1
140
+ * Translated from go-merklelog/mmr/size.go HeightIndexSize
141
+ */
142
+ export function mmrSizeFromHeightIndex(heightIndex: number): Uint64 {
143
+ return new Uint64(1).shl(heightIndex + 1).sub(new Uint64(1));
144
+ }
145
+
146
+ /**
147
+ * Returns the number of leaves in the largest valid MMR whose size is
148
+ * <= `mmrSize`.
149
+ *
150
+ * Mirrors go-merklelog `LeafCount`, which is defined as `PeaksBitmap(mmrSize)`:
151
+ * the leaf count's binary representation maps directly onto the peak structure
152
+ * (each set bit is a perfect subtree). The naive `(n + 1) / 2` is only correct
153
+ * for single-peak (perfect) sizes — e.g. size 4 (3 leaves) gives 2 with the
154
+ * naive form but 3 here — so it must not be used. When `mmrSize` is not a valid
155
+ * MMR size this returns the leaf count of the highest valid size <= `mmrSize`.
156
+ */
157
+ export function leafCount(mmrSize: Uint64): Uint64 {
158
+ return new Uint64(peaksBitmap(mmrSize.toBigInt()));
159
+ }
160
+
161
+ /**
162
+ * Returns the peaks bitmap for an MMR size (go-merklelog `PeaksBitmap`).
163
+ *
164
+ * The numeric value equals the leaf count of the largest valid MMR with size
165
+ * <= `mmrSize`; each set bit marks a perfect subtree (peak) at that height.
166
+ */
167
+ function peaksBitmap(mmrSize: bigint): bigint {
168
+ if (mmrSize === 0n) return 0n;
169
+ let pos = mmrSize;
170
+ let peakSize = (1n << BigInt(bitLength(new Uint64(mmrSize)))) - 1n;
171
+ let peakMap = 0n;
172
+ while (peakSize > 0n) {
173
+ peakMap <<= 1n;
174
+ if (pos >= peakSize) {
175
+ pos -= peakSize;
176
+ peakMap |= 1n;
177
+ }
178
+ peakSize >>= 1n;
179
+ }
180
+ return peakMap;
181
+ }
182
+
183
+ /**
184
+ * Returns the number of leaves given a height index
185
+ *
186
+ * f = 2^g = 1 << g
187
+ */
188
+ export function leafCountFromHeightIndex(heightIndex: number): Uint64 {
189
+ return new Uint64(1).shl(heightIndex);
190
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Proof types for MMR inclusion and consistency proofs
3
+ */
4
+
5
+ /**
6
+ * Proof - Inclusion or consistency proof structure
7
+ */
8
+ export interface Proof {
9
+ /** The proof path (sibling hashes) */
10
+ path: Uint8Array[];
11
+ /** The leaf index being proven */
12
+ leafIndex?: bigint;
13
+ /** The MMR index being proven */
14
+ mmrIndex?: bigint;
15
+ }
16
+
17
+ /**
18
+ * Peak - A peak node in the MMR
19
+ */
20
+ export interface Peak {
21
+ /** The MMR index of the peak */
22
+ index: bigint;
23
+ /** The hash value of the peak */
24
+ hash: Uint8Array;
25
+ }
26
+
27
+ /**
28
+ * Hasher interface for cryptographic hashing operations.
29
+ * digest() is async so callers can use crypto.subtle (Workers, browser) or
30
+ * wrap Node's crypto.createHash (e.g. digest(): Promise.resolve(syncDigest())).
31
+ */
32
+ export interface Hasher {
33
+ /** Reset the hasher state */
34
+ reset(): void;
35
+ /** Update the hasher with data */
36
+ update(data: Uint8Array): void;
37
+ /** Finalize and return the hash */
38
+ digest(): Promise<Uint8Array>;
39
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Uint64 - A BigInt-based wrapper for 64-bit unsigned integer operations
3
+ *
4
+ * Provides arithmetic and bitwise operations for 64-bit unsigned integers,
5
+ * ensuring all operations stay within the 64-bit range using BigInt.asUintN(64, value).
6
+ */
7
+ export class Uint64 {
8
+ private readonly value: bigint;
9
+
10
+ /**
11
+ * Creates a new Uint64 instance
12
+ * @param value - The initial value as number, bigint, or string
13
+ */
14
+ constructor(value: number | bigint | string | Uint64) {
15
+ if (value instanceof Uint64) {
16
+ this.value = value.value;
17
+ } else {
18
+ const bigIntValue = typeof value === "bigint" ? value : BigInt(value);
19
+ this.value = BigInt.asUintN(64, bigIntValue);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Addition with overflow handling (wraps around at 2^64)
25
+ * @param other - The value to add
26
+ * @returns New Uint64 instance with the result
27
+ */
28
+ add(other: Uint64): Uint64 {
29
+ const result = this.value + other.value;
30
+ return new Uint64(BigInt.asUintN(64, result));
31
+ }
32
+
33
+ /**
34
+ * Subtraction with underflow handling (wraps around at 0)
35
+ * @param other - The value to subtract
36
+ * @returns New Uint64 instance with the result
37
+ */
38
+ sub(other: Uint64): Uint64 {
39
+ const result = this.value - other.value;
40
+ return new Uint64(BigInt.asUintN(64, result));
41
+ }
42
+
43
+ /**
44
+ * Left shift
45
+ * @param bits - Number of bits to shift left
46
+ * @returns New Uint64 instance with the result
47
+ */
48
+ shl(bits: number): Uint64 {
49
+ if (bits < 0 || bits > 63) {
50
+ throw new Error("Shift amount must be between 0 and 63");
51
+ }
52
+ const result = this.value << BigInt(bits);
53
+ return new Uint64(BigInt.asUintN(64, result));
54
+ }
55
+
56
+ /**
57
+ * Right shift (logical, zero-fill)
58
+ * @param bits - Number of bits to shift right
59
+ * @returns New Uint64 instance with the result
60
+ */
61
+ shr(bits: number): Uint64 {
62
+ if (bits < 0 || bits > 63) {
63
+ throw new Error("Shift amount must be between 0 and 63");
64
+ }
65
+ const result = this.value >> BigInt(bits);
66
+ return new Uint64(BigInt.asUintN(64, result));
67
+ }
68
+
69
+ /**
70
+ * Bitwise AND
71
+ * @param other - The value to AND with
72
+ * @returns New Uint64 instance with the result
73
+ */
74
+ and(other: Uint64): Uint64 {
75
+ const result = this.value & other.value;
76
+ return new Uint64(BigInt.asUintN(64, result));
77
+ }
78
+
79
+ /**
80
+ * Bitwise OR
81
+ * @param other - The value to OR with
82
+ * @returns New Uint64 instance with the result
83
+ */
84
+ or(other: Uint64): Uint64 {
85
+ const result = this.value | other.value;
86
+ return new Uint64(BigInt.asUintN(64, result));
87
+ }
88
+
89
+ /**
90
+ * Bitwise XOR
91
+ * @param other - The value to XOR with
92
+ * @returns New Uint64 instance with the result
93
+ */
94
+ xor(other: Uint64): Uint64 {
95
+ const result = this.value ^ other.value;
96
+ return new Uint64(BigInt.asUintN(64, result));
97
+ }
98
+
99
+ /**
100
+ * Bitwise complement (one's complement)
101
+ * @returns New Uint64 instance with the result
102
+ */
103
+ not(): Uint64 {
104
+ // For unsigned 64-bit, complement is ~value masked to 64 bits
105
+ const result = ~this.value;
106
+ return new Uint64(BigInt.asUintN(64, result));
107
+ }
108
+
109
+ /**
110
+ * Mask lower bits (equivalent to value & ((1 << bits) - 1))
111
+ * @param bits - Number of lower bits to keep
112
+ * @returns New Uint64 instance with the result
113
+ */
114
+ mask(bits: number): Uint64 {
115
+ if (bits < 0 || bits > 64) {
116
+ throw new Error("Mask bits must be between 0 and 64");
117
+ }
118
+ if (bits === 64) {
119
+ return new Uint64(this.value);
120
+ }
121
+ const maskValue = (BigInt(1) << BigInt(bits)) - BigInt(1);
122
+ const result = this.value & maskValue;
123
+ return new Uint64(BigInt.asUintN(64, result));
124
+ }
125
+
126
+ /**
127
+ * Returns the value as a BigInt
128
+ * @returns The 64-bit unsigned value as BigInt
129
+ */
130
+ toBigInt(): bigint {
131
+ return this.value;
132
+ }
133
+
134
+ /**
135
+ * Returns the value as a number (with range check)
136
+ * @returns The value as number if it fits in Number.MAX_SAFE_INTEGER
137
+ * @throws Error if value exceeds Number.MAX_SAFE_INTEGER
138
+ */
139
+ toNumber(): number {
140
+ if (this.value > BigInt(Number.MAX_SAFE_INTEGER)) {
141
+ throw new Error(
142
+ `Value ${this.value} exceeds Number.MAX_SAFE_INTEGER (${Number.MAX_SAFE_INTEGER})`,
143
+ );
144
+ }
145
+ return Number(this.value);
146
+ }
147
+
148
+ /**
149
+ * Returns the value as a string
150
+ * @returns String representation of the value
151
+ */
152
+ toString(): string {
153
+ return this.value.toString();
154
+ }
155
+
156
+ /**
157
+ * Equality comparison
158
+ * @param other - The value to compare with
159
+ * @returns True if values are equal
160
+ */
161
+ equals(other: Uint64): boolean {
162
+ return this.value === other.value;
163
+ }
164
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Utility functions for Uint8Array operations
3
+ */
4
+
5
+ /**
6
+ * Compares two Uint8Arrays for equality
7
+ *
8
+ * Simple and efficient byte-by-byte comparison. Optimized for small
9
+ * fixed-size arrays like trie keys (32 bytes) and extraBytes (24 bytes).
10
+ *
11
+ * @param a - First array to compare
12
+ * @param b - Second array to compare
13
+ * @returns true if arrays are equal, false otherwise
14
+ */
15
+ export function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {
16
+ if (a.length !== b.length) {
17
+ return false;
18
+ }
19
+ for (let i = 0; i < a.length; i++) {
20
+ if (a[i] !== b[i]) {
21
+ return false;
22
+ }
23
+ }
24
+ return true;
25
+ }