@neoware_inc/neozipkit 0.5.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 (171) hide show
  1. package/README.md +134 -0
  2. package/dist/browser/ZipkitBrowser.d.ts +27 -0
  3. package/dist/browser/ZipkitBrowser.d.ts.map +1 -0
  4. package/dist/browser/ZipkitBrowser.js +303 -0
  5. package/dist/browser/ZipkitBrowser.js.map +1 -0
  6. package/dist/browser/index.d.ts +9 -0
  7. package/dist/browser/index.d.ts.map +1 -0
  8. package/dist/browser/index.esm.d.ts +12 -0
  9. package/dist/browser/index.esm.d.ts.map +1 -0
  10. package/dist/browser/index.esm.js +46 -0
  11. package/dist/browser/index.esm.js.map +1 -0
  12. package/dist/browser/index.js +38 -0
  13. package/dist/browser/index.js.map +1 -0
  14. package/dist/browser-esm/index.d.ts +9 -0
  15. package/dist/browser-esm/index.js +50211 -0
  16. package/dist/browser-esm/index.js.map +7 -0
  17. package/dist/browser-umd/index.d.ts +9 -0
  18. package/dist/browser-umd/index.js +50221 -0
  19. package/dist/browser-umd/index.js.map +7 -0
  20. package/dist/browser-umd/index.min.js +39 -0
  21. package/dist/browser.d.ts +9 -0
  22. package/dist/browser.js +38 -0
  23. package/dist/core/ZipCompress.d.ts +99 -0
  24. package/dist/core/ZipCompress.d.ts.map +1 -0
  25. package/dist/core/ZipCompress.js +287 -0
  26. package/dist/core/ZipCompress.js.map +1 -0
  27. package/dist/core/ZipCopy.d.ts +175 -0
  28. package/dist/core/ZipCopy.d.ts.map +1 -0
  29. package/dist/core/ZipCopy.js +310 -0
  30. package/dist/core/ZipCopy.js.map +1 -0
  31. package/dist/core/ZipDecompress.d.ts +57 -0
  32. package/dist/core/ZipDecompress.d.ts.map +1 -0
  33. package/dist/core/ZipDecompress.js +155 -0
  34. package/dist/core/ZipDecompress.js.map +1 -0
  35. package/dist/core/ZipEntry.d.ts +138 -0
  36. package/dist/core/ZipEntry.d.ts.map +1 -0
  37. package/dist/core/ZipEntry.js +829 -0
  38. package/dist/core/ZipEntry.js.map +1 -0
  39. package/dist/core/Zipkit.d.ts +315 -0
  40. package/dist/core/Zipkit.d.ts.map +1 -0
  41. package/dist/core/Zipkit.js +647 -0
  42. package/dist/core/Zipkit.js.map +1 -0
  43. package/dist/core/ZstdManager.d.ts +56 -0
  44. package/dist/core/ZstdManager.d.ts.map +1 -0
  45. package/dist/core/ZstdManager.js +144 -0
  46. package/dist/core/ZstdManager.js.map +1 -0
  47. package/dist/core/components/HashCalculator.d.ts +138 -0
  48. package/dist/core/components/HashCalculator.d.ts.map +1 -0
  49. package/dist/core/components/HashCalculator.js +360 -0
  50. package/dist/core/components/HashCalculator.js.map +1 -0
  51. package/dist/core/components/Logger.d.ts +73 -0
  52. package/dist/core/components/Logger.d.ts.map +1 -0
  53. package/dist/core/components/Logger.js +156 -0
  54. package/dist/core/components/Logger.js.map +1 -0
  55. package/dist/core/components/ProgressTracker.d.ts +43 -0
  56. package/dist/core/components/ProgressTracker.d.ts.map +1 -0
  57. package/dist/core/components/ProgressTracker.js +112 -0
  58. package/dist/core/components/ProgressTracker.js.map +1 -0
  59. package/dist/core/components/Support.d.ts +64 -0
  60. package/dist/core/components/Support.d.ts.map +1 -0
  61. package/dist/core/components/Support.js +71 -0
  62. package/dist/core/components/Support.js.map +1 -0
  63. package/dist/core/components/Util.d.ts +26 -0
  64. package/dist/core/components/Util.d.ts.map +1 -0
  65. package/dist/core/components/Util.js +95 -0
  66. package/dist/core/components/Util.js.map +1 -0
  67. package/dist/core/constants/Errors.d.ts +52 -0
  68. package/dist/core/constants/Errors.d.ts.map +1 -0
  69. package/dist/core/constants/Errors.js +67 -0
  70. package/dist/core/constants/Errors.js.map +1 -0
  71. package/dist/core/constants/Headers.d.ts +170 -0
  72. package/dist/core/constants/Headers.d.ts.map +1 -0
  73. package/dist/core/constants/Headers.js +194 -0
  74. package/dist/core/constants/Headers.js.map +1 -0
  75. package/dist/core/encryption/Manager.d.ts +58 -0
  76. package/dist/core/encryption/Manager.d.ts.map +1 -0
  77. package/dist/core/encryption/Manager.js +121 -0
  78. package/dist/core/encryption/Manager.js.map +1 -0
  79. package/dist/core/encryption/ZipCrypto.d.ts +172 -0
  80. package/dist/core/encryption/ZipCrypto.d.ts.map +1 -0
  81. package/dist/core/encryption/ZipCrypto.js +554 -0
  82. package/dist/core/encryption/ZipCrypto.js.map +1 -0
  83. package/dist/core/encryption/index.d.ts +9 -0
  84. package/dist/core/encryption/index.d.ts.map +1 -0
  85. package/dist/core/encryption/index.js +17 -0
  86. package/dist/core/encryption/index.js.map +1 -0
  87. package/dist/core/encryption/types.d.ts +29 -0
  88. package/dist/core/encryption/types.d.ts.map +1 -0
  89. package/dist/core/encryption/types.js +12 -0
  90. package/dist/core/encryption/types.js.map +1 -0
  91. package/dist/core/index.d.ts +27 -0
  92. package/dist/core/index.d.ts.map +1 -0
  93. package/dist/core/index.js +59 -0
  94. package/dist/core/index.js.map +1 -0
  95. package/dist/core/version.d.ts +5 -0
  96. package/dist/core/version.d.ts.map +1 -0
  97. package/dist/core/version.js +31 -0
  98. package/dist/core/version.js.map +1 -0
  99. package/dist/index.d.ts +9 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +38 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/node/ZipCompressNode.d.ts +123 -0
  104. package/dist/node/ZipCompressNode.d.ts.map +1 -0
  105. package/dist/node/ZipCompressNode.js +565 -0
  106. package/dist/node/ZipCompressNode.js.map +1 -0
  107. package/dist/node/ZipCopyNode.d.ts +165 -0
  108. package/dist/node/ZipCopyNode.d.ts.map +1 -0
  109. package/dist/node/ZipCopyNode.js +347 -0
  110. package/dist/node/ZipCopyNode.js.map +1 -0
  111. package/dist/node/ZipDecompressNode.d.ts +197 -0
  112. package/dist/node/ZipDecompressNode.d.ts.map +1 -0
  113. package/dist/node/ZipDecompressNode.js +678 -0
  114. package/dist/node/ZipDecompressNode.js.map +1 -0
  115. package/dist/node/ZipkitNode.d.ts +466 -0
  116. package/dist/node/ZipkitNode.d.ts.map +1 -0
  117. package/dist/node/ZipkitNode.js +1426 -0
  118. package/dist/node/ZipkitNode.js.map +1 -0
  119. package/dist/node/index.d.ts +25 -0
  120. package/dist/node/index.d.ts.map +1 -0
  121. package/dist/node/index.js +54 -0
  122. package/dist/node/index.js.map +1 -0
  123. package/dist/types/index.d.ts +45 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +11 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/examples/README.md +261 -0
  128. package/examples/append-data.json +44 -0
  129. package/examples/copy-zip-append.ts +139 -0
  130. package/examples/copy-zip.ts +152 -0
  131. package/examples/create-zip.ts +172 -0
  132. package/examples/extract-zip.ts +118 -0
  133. package/examples/list-zip.ts +161 -0
  134. package/examples/test-files/data.json +116 -0
  135. package/examples/test-files/document.md +80 -0
  136. package/examples/test-files/document.txt +6 -0
  137. package/examples/test-files/file1.txt +48 -0
  138. package/examples/test-files/file2.txt +80 -0
  139. package/examples/tsconfig.json +44 -0
  140. package/package.json +167 -0
  141. package/src/browser/ZipkitBrowser.ts +305 -0
  142. package/src/browser/index.esm.ts +32 -0
  143. package/src/browser/index.ts +19 -0
  144. package/src/core/ZipCompress.ts +370 -0
  145. package/src/core/ZipCopy.ts +434 -0
  146. package/src/core/ZipDecompress.ts +191 -0
  147. package/src/core/ZipEntry.ts +917 -0
  148. package/src/core/Zipkit.ts +794 -0
  149. package/src/core/ZstdManager.ts +165 -0
  150. package/src/core/components/HashCalculator.ts +384 -0
  151. package/src/core/components/Logger.ts +180 -0
  152. package/src/core/components/ProgressTracker.ts +134 -0
  153. package/src/core/components/Support.ts +77 -0
  154. package/src/core/components/Util.ts +91 -0
  155. package/src/core/constants/Errors.ts +78 -0
  156. package/src/core/constants/Headers.ts +205 -0
  157. package/src/core/encryption/Manager.ts +137 -0
  158. package/src/core/encryption/ZipCrypto.ts +650 -0
  159. package/src/core/encryption/index.ts +15 -0
  160. package/src/core/encryption/types.ts +33 -0
  161. package/src/core/index.ts +42 -0
  162. package/src/core/version.ts +33 -0
  163. package/src/index.ts +19 -0
  164. package/src/node/ZipCompressNode.ts +618 -0
  165. package/src/node/ZipCopyNode.ts +437 -0
  166. package/src/node/ZipDecompressNode.ts +793 -0
  167. package/src/node/ZipkitNode.ts +1706 -0
  168. package/src/node/index.ts +40 -0
  169. package/src/types/index.ts +68 -0
  170. package/src/types/modules.d.ts +22 -0
  171. package/src/types/opentimestamps.d.ts +1 -0
@@ -0,0 +1,165 @@
1
+ /**
2
+ * ZstdManager - Global Zstd Codec Manager
3
+ *
4
+ * This manager ensures safe usage of the Zstd WASM module across multiple instances.
5
+ *
6
+ * ## Why This Is Necessary
7
+ *
8
+ * The `@oneidentity/zstd-js` library uses a shared WASM module internally.
9
+ * While `ZstdInit()` returns different object references, they all share the same
10
+ * `ZstdSimple` implementation and WASM memory/heap.
11
+ *
12
+ * This means concurrent or improperly managed sequential operations can cause
13
+ * memory corruption because they're all operating on the same underlying WASM state.
14
+ *
15
+ * ## How It Works
16
+ *
17
+ * 1. Single initialization: WASM module is initialized once globally
18
+ * 2. Operation queuing: All compress/decompress operations are queued
19
+ * 3. Sequential execution: JavaScript's single-threaded nature ensures safe execution
20
+ * 4. Automatic initialization: Lazy initialization on first use
21
+ *
22
+ * ## Usage
23
+ *
24
+ * ```typescript
25
+ * import { ZstdManager } from './ZstdManager';
26
+ *
27
+ * // Compress
28
+ * const compressed = await ZstdManager.compress(data, level);
29
+ *
30
+ * // Decompress
31
+ * const decompressed = await ZstdManager.decompress(compressedData);
32
+ * ```
33
+ */
34
+
35
+ import { ZstdInit, ZstdSimple } from '@oneidentity/zstd-js';
36
+
37
+ class ZstdCodecManager {
38
+ private static instance: ZstdCodecManager | null = null;
39
+ private codec: { ZstdSimple: typeof ZstdSimple } | null = null;
40
+ private initPromise: Promise<void> | null = null;
41
+ private operationQueue: Promise<any> = Promise.resolve();
42
+
43
+ private constructor() {}
44
+
45
+ /**
46
+ * Get the singleton instance of ZstdCodecManager
47
+ */
48
+ public static getInstance(): ZstdCodecManager {
49
+ if (!ZstdCodecManager.instance) {
50
+ ZstdCodecManager.instance = new ZstdCodecManager();
51
+ }
52
+ return ZstdCodecManager.instance;
53
+ }
54
+
55
+ /**
56
+ * Ensure the Zstd codec is initialized
57
+ * This is called automatically before any compress/decompress operation
58
+ */
59
+ private async ensureInitialized(): Promise<void> {
60
+ if (this.codec) {
61
+ return;
62
+ }
63
+
64
+ if (this.initPromise) {
65
+ return this.initPromise;
66
+ }
67
+
68
+ this.initPromise = (async () => {
69
+ this.codec = await ZstdInit();
70
+ })();
71
+
72
+ return this.initPromise;
73
+ }
74
+
75
+ /**
76
+ * Queue an operation to ensure sequential execution
77
+ * This prevents concurrent operations from interfering with each other
78
+ */
79
+ private async queueOperation<T>(operation: () => Promise<T> | T): Promise<T> {
80
+ // Chain this operation after the previous one
81
+ const promise = this.operationQueue.then(operation, operation);
82
+
83
+ // Update the queue to point to this operation
84
+ this.operationQueue = promise.catch(() => {}); // Catch to prevent unhandled rejections in queue
85
+
86
+ return promise;
87
+ }
88
+
89
+ /**
90
+ * Compress data using Zstd
91
+ * @param data - Data to compress (Uint8Array)
92
+ * @param level - Compression level (1-22, default 6)
93
+ * @returns Compressed data as Uint8Array
94
+ */
95
+ public async compress(data: Uint8Array, level: number = 6): Promise<Uint8Array> {
96
+ return this.queueOperation(async () => {
97
+ await this.ensureInitialized();
98
+
99
+ if (!this.codec) {
100
+ throw new Error('Zstd codec not initialized');
101
+ }
102
+
103
+ return this.codec.ZstdSimple.compress(data, level);
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Decompress data using Zstd
109
+ * @param data - Compressed data (Uint8Array or Buffer)
110
+ * @returns Decompressed data as Uint8Array
111
+ */
112
+ public async decompress(data: Uint8Array | Buffer): Promise<Uint8Array> {
113
+ return this.queueOperation(async () => {
114
+ await this.ensureInitialized();
115
+
116
+ if (!this.codec) {
117
+ throw new Error('Zstd codec not initialized');
118
+ }
119
+
120
+ // Convert Buffer to Uint8Array if needed
121
+ const inputData = data instanceof Buffer
122
+ ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
123
+ : data;
124
+
125
+ return this.codec.ZstdSimple.decompress(inputData);
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Reset the manager (for testing purposes)
131
+ * @internal
132
+ */
133
+ public static reset(): void {
134
+ ZstdCodecManager.instance = null;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Global Zstd Manager
140
+ * Use this for all Zstd compression/decompression operations
141
+ */
142
+ export const ZstdManager = {
143
+ /**
144
+ * Compress data using Zstd
145
+ * Operations are automatically queued to prevent interference
146
+ */
147
+ compress: (data: Uint8Array, level?: number) =>
148
+ ZstdCodecManager.getInstance().compress(data, level),
149
+
150
+ /**
151
+ * Decompress data using Zstd
152
+ * Operations are automatically queued to prevent interference
153
+ */
154
+ decompress: (data: Uint8Array | Buffer) =>
155
+ ZstdCodecManager.getInstance().decompress(data),
156
+
157
+ /**
158
+ * Reset the manager (for testing)
159
+ * @internal
160
+ */
161
+ reset: () => ZstdCodecManager.reset(),
162
+ };
163
+
164
+ export default ZstdManager;
165
+
@@ -0,0 +1,384 @@
1
+ // ======================================
2
+ // HashCalculator.ts - Unified Hash Component
3
+ // Combines StreamHashCalculator, HashAccumulator, and MerkleTree functionality
4
+ // ======================================
5
+
6
+ import * as crypto from 'crypto';
7
+ import { crc32update } from '../encryption/ZipCrypto';
8
+
9
+ /**
10
+ * Configuration options for Merkle tree construction
11
+ */
12
+ interface MerkleTreeOptions {
13
+ hashLeaves: boolean; // Whether to hash the leaf nodes before adding to tree
14
+ sortLeaves: boolean; // Whether to sort leaf nodes for consistent trees
15
+ sortPairs: boolean; // Whether to sort each pair before hashing
16
+ duplicateOdd: boolean; // Whether to duplicate last leaf when odd number of leaves
17
+ }
18
+
19
+ /**
20
+ * Unified hash calculator supporting:
21
+ * - Incremental CRC-32 and SHA-256 calculation from raw data chunks
22
+ * - Accumulation of pre-computed SHA-256 hashes with XOR and Merkle tree operations
23
+ * - Merkle tree construction, proof generation, and verification
24
+ *
25
+ * Usage examples:
26
+ *
27
+ * Incremental hash calculation:
28
+ * ```typescript
29
+ * const calculator = new HashCalculator({ useSHA256: true });
30
+ * calculator.update(dataChunk);
31
+ * const crc32 = calculator.finalizeCRC32();
32
+ * const sha256 = calculator.finalizeSHA256();
33
+ * ```
34
+ *
35
+ * Hash accumulation:
36
+ * ```typescript
37
+ * const calculator = new HashCalculator({ enableAccumulation: true });
38
+ * calculator.addHash(sha256Hash1);
39
+ * calculator.addHash(sha256Hash2);
40
+ * const xor = calculator.xorHash();
41
+ * const merkle = calculator.merkleRoot();
42
+ * ```
43
+ */
44
+ export class HashCalculator {
45
+ // Incremental hash calculation state (from StreamHashCalculator)
46
+ private crc32State: number = ~0;
47
+ private sha256Hash: crypto.Hash | null = null;
48
+ private useSHA256: boolean = false;
49
+
50
+ // Hash accumulation state (from HashAccumulator)
51
+ private hashes: Buffer[] = [];
52
+ private xorResult: Buffer = Buffer.alloc(32, 0);
53
+ private enableAccumulation: boolean = false;
54
+
55
+ // Merkle tree state (from MerkleTree)
56
+ private merkleLeaves: Buffer[] = [];
57
+ private merkleLayers: Buffer[][] = [];
58
+ private merkleOptions: MerkleTreeOptions = {
59
+ hashLeaves: false,
60
+ sortLeaves: true,
61
+ sortPairs: true,
62
+ duplicateOdd: true
63
+ };
64
+
65
+ /**
66
+ * Creates a new HashCalculator instance
67
+ * @param options - Configuration options:
68
+ * - useSHA256: Enable SHA-256 calculation for incremental mode (default: false)
69
+ * - enableAccumulation: Enable hash accumulation mode (default: false)
70
+ */
71
+ constructor(options?: {
72
+ useSHA256?: boolean;
73
+ enableAccumulation?: boolean;
74
+ }) {
75
+ this.useSHA256 = options?.useSHA256 || false;
76
+ this.enableAccumulation = options?.enableAccumulation || false;
77
+
78
+ if (this.useSHA256) {
79
+ this.sha256Hash = crypto.createHash('sha256');
80
+ }
81
+ }
82
+
83
+ // ============================================================================
84
+ // Incremental Hash Calculation Methods (from StreamHashCalculator)
85
+ // ============================================================================
86
+
87
+ /**
88
+ * Update hash state with a new chunk of data
89
+ * Updates both CRC-32 and SHA-256 (if enabled) incrementally
90
+ * @param chunk - Data chunk to process
91
+ */
92
+ update(chunk: Buffer): void {
93
+ // Update CRC-32 incrementally using existing crc32update function
94
+ for (let i = 0; i < chunk.length; i++) {
95
+ this.crc32State = crc32update(this.crc32State, chunk[i]);
96
+ }
97
+
98
+ // Update SHA-256 incrementally
99
+ if (this.sha256Hash) {
100
+ this.sha256Hash.update(chunk);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get final CRC-32 value
106
+ * @returns Final CRC-32 value as unsigned 32-bit integer
107
+ */
108
+ finalizeCRC32(): number {
109
+ return ~this.crc32State >>> 0; // Finalize with ~0 like the existing crc32 function
110
+ }
111
+
112
+ /**
113
+ * Get final SHA-256 hash as hex string
114
+ * @returns SHA-256 hash as hex string, or null if SHA-256 not enabled
115
+ */
116
+ finalizeSHA256(): string | null {
117
+ if (this.sha256Hash) {
118
+ return this.sha256Hash.digest('hex');
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Reset the incremental hash calculation state
125
+ */
126
+ reset(): void {
127
+ this.crc32State = ~0;
128
+ if (this.sha256Hash) {
129
+ this.sha256Hash = crypto.createHash('sha256');
130
+ }
131
+ }
132
+
133
+ // ============================================================================
134
+ // Hash Accumulation Methods (from HashAccumulator)
135
+ // ============================================================================
136
+
137
+ /**
138
+ * Adds a pre-computed SHA-256 hash to both the XOR accumulation and hash array
139
+ * @param hash - Hash value as hex string or Buffer (must be 32 bytes for SHA-256)
140
+ */
141
+ addHash(hash: string | Buffer): void {
142
+ if (!this.enableAccumulation) {
143
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
144
+ }
145
+
146
+ // Convert hash to Buffer if it's a string
147
+ const hashBuffer = typeof hash === 'string'
148
+ ? Buffer.from(hash, 'hex')
149
+ : hash;
150
+
151
+ // Add to array for Merkle tree
152
+ this.hashes.push(hashBuffer);
153
+ this.merkleLeaves.push(hashBuffer);
154
+
155
+ // XOR accumulation
156
+ for (let i = 0; i < 32; i++) {
157
+ this.xorResult[i] ^= hashBuffer[i];
158
+ }
159
+
160
+ // Rebuild Merkle tree when hash is added
161
+ this.rebuildMerkleTree();
162
+ }
163
+
164
+ /**
165
+ * Gets the accumulated XOR of all added hashes
166
+ * @returns Hex string of XOR result
167
+ */
168
+ xorHash(): string {
169
+ if (!this.enableAccumulation) {
170
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
171
+ }
172
+ return this.xorResult.toString('hex');
173
+ }
174
+
175
+ /**
176
+ * Creates a Merkle tree from accumulated hashes and returns the root
177
+ * @returns Hex string of Merkle root, or null if no hashes added
178
+ */
179
+ merkleRoot(): string | null {
180
+ if (!this.enableAccumulation) {
181
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
182
+ }
183
+
184
+ if (this.merkleLeaves.length === 0) {
185
+ return null;
186
+ }
187
+
188
+ // If Merkle tree not built yet, build it
189
+ if (this.merkleLayers.length === 0) {
190
+ this.rebuildMerkleTree();
191
+ }
192
+
193
+ const root = this.merkleLayers[this.merkleLayers.length - 1];
194
+ if (root && root.length > 0) {
195
+ return root[0].toString('hex');
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ /**
202
+ * Gets the number of hashes accumulated
203
+ * @returns Number of accumulated hashes
204
+ */
205
+ leafCount(): number {
206
+ if (!this.enableAccumulation) {
207
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
208
+ }
209
+ return this.hashes.length;
210
+ }
211
+
212
+ /**
213
+ * Clears the accumulated hashes and resets XOR buffer
214
+ */
215
+ clear(): void {
216
+ if (!this.enableAccumulation) {
217
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
218
+ }
219
+ this.hashes = [];
220
+ this.merkleLeaves = [];
221
+ this.merkleLayers = [];
222
+ this.xorResult = Buffer.alloc(32, 0);
223
+ }
224
+
225
+ /**
226
+ * Combines multiple hash results into a single Merkle root
227
+ * Static method for combining results from multiple HashCalculator instances
228
+ * @param results - Array of objects containing merkleRoot properties
229
+ * @returns Combined Merkle root as hex string, or null if no valid roots
230
+ */
231
+ static combineResults(results: { merkleRoot?: string }[]): string | null {
232
+ const calculator = new HashCalculator({ enableAccumulation: true });
233
+
234
+ // Combine all hashes
235
+ for (const result of results) {
236
+ if (result.merkleRoot) {
237
+ calculator.addHash(result.merkleRoot);
238
+ }
239
+ }
240
+
241
+ return calculator.merkleRoot();
242
+ }
243
+
244
+ // ============================================================================
245
+ // Merkle Tree Methods (from MerkleTree)
246
+ // ============================================================================
247
+
248
+ /**
249
+ * Computes SHA-256 hash of input data
250
+ * @param data - Data to hash
251
+ * @returns Buffer containing hash
252
+ */
253
+ private hash(data: Buffer): Buffer {
254
+ return crypto.createHash('sha256').update(data).digest();
255
+ }
256
+
257
+ /**
258
+ * Combines two child hashes to create parent hash
259
+ * @param left - Left child hash
260
+ * @param right - Right child hash
261
+ * @returns Combined hash of children
262
+ */
263
+ private combinedHash(left: Buffer, right: Buffer): Buffer {
264
+ if (this.merkleOptions.sortPairs && Buffer.compare(left, right) > 0) {
265
+ [left, right] = [right, left];
266
+ }
267
+ return this.hash(Buffer.concat([left, right]));
268
+ }
269
+
270
+ /**
271
+ * Builds the Merkle tree by creating successive layers of hashes
272
+ * @param nodes - Array of nodes in current layer
273
+ */
274
+ private createHashes(nodes: Buffer[]): void {
275
+ while (nodes.length > 1) {
276
+ const layerIndex = this.merkleLayers.length;
277
+ this.merkleLayers.push([]);
278
+
279
+ // Process pairs of nodes
280
+ for (let i = 0; i < nodes.length - 1; i += 2) {
281
+ const left = nodes[i];
282
+ const right = nodes[i + 1];
283
+ const hash = this.combinedHash(left, right);
284
+ this.merkleLayers[layerIndex].push(hash);
285
+ }
286
+
287
+ // Handle odd number of nodes
288
+ if (nodes.length % 2 === 1) {
289
+ const last = nodes[nodes.length - 1];
290
+ if (this.merkleOptions.duplicateOdd) {
291
+ // Duplicate the last node if odd
292
+ const hash = this.combinedHash(last, last);
293
+ this.merkleLayers[layerIndex].push(hash);
294
+ } else {
295
+ // Push last node up to next layer
296
+ this.merkleLayers[layerIndex].push(last);
297
+ }
298
+ }
299
+
300
+ nodes = this.merkleLayers[layerIndex];
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Rebuilds the Merkle tree from current leaves
306
+ */
307
+ private rebuildMerkleTree(): void {
308
+ if (this.merkleLeaves.length === 0) {
309
+ this.merkleLayers = [];
310
+ return;
311
+ }
312
+
313
+ // Process leaves according to options
314
+ const processedLeaves = this.merkleLeaves.map(leaf => {
315
+ if (this.merkleOptions.hashLeaves) {
316
+ return this.hash(leaf);
317
+ }
318
+ return Buffer.isBuffer(leaf) ? leaf : Buffer.from(leaf);
319
+ });
320
+
321
+ if (this.merkleOptions.sortLeaves) {
322
+ processedLeaves.sort(Buffer.compare);
323
+ }
324
+
325
+ this.merkleLayers = [processedLeaves];
326
+ this.createHashes(processedLeaves);
327
+ }
328
+
329
+ /**
330
+ * Generates a proof of inclusion for a leaf node
331
+ * @param leaf - The leaf node to generate proof for
332
+ * @returns Array of sibling hashes needed to reconstruct root
333
+ * @throws Error if leaf not found in tree or accumulation not enabled
334
+ */
335
+ getProof(leaf: Buffer): Buffer[] {
336
+ if (!this.enableAccumulation) {
337
+ throw new Error('Hash accumulation not enabled. Set enableAccumulation: true in constructor.');
338
+ }
339
+
340
+ if (this.merkleLayers.length === 0) {
341
+ this.rebuildMerkleTree();
342
+ }
343
+
344
+ let index = this.merkleLeaves.findIndex(item => item.equals(leaf));
345
+ if (index === -1) {
346
+ throw new Error('Leaf not found in tree');
347
+ }
348
+
349
+ return this.merkleLayers.reduce((proof, layer) => {
350
+ if (layer.length === 1) return proof;
351
+
352
+ const pairIndex = index % 2 === 0 ? index + 1 : index - 1;
353
+ if (pairIndex < layer.length) {
354
+ proof.push(layer[pairIndex]);
355
+ }
356
+
357
+ index = Math.floor(index / 2);
358
+ return proof;
359
+ }, [] as Buffer[]);
360
+ }
361
+
362
+ /**
363
+ * Verifies a proof of inclusion for a leaf node
364
+ * @param proof - Array of sibling hashes from getProof()
365
+ * @param targetHash - Hash of the leaf node being verified
366
+ * @param root - Expected root hash
367
+ * @returns boolean indicating if proof is valid
368
+ */
369
+ verify(proof: Buffer[], targetHash: Buffer, root: Buffer): boolean {
370
+ let computedHash = targetHash;
371
+
372
+ for (const proofElement of proof) {
373
+ computedHash = Buffer.compare(computedHash, proofElement) <= 0
374
+ ? this.combinedHash(computedHash, proofElement)
375
+ : this.combinedHash(proofElement, computedHash);
376
+ }
377
+
378
+ return computedHash.equals(root);
379
+ }
380
+ }
381
+
382
+ // Default export
383
+ export default HashCalculator;
384
+