@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.
- package/README.md +134 -0
- package/dist/browser/ZipkitBrowser.d.ts +27 -0
- package/dist/browser/ZipkitBrowser.d.ts.map +1 -0
- package/dist/browser/ZipkitBrowser.js +303 -0
- package/dist/browser/ZipkitBrowser.js.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.esm.d.ts +12 -0
- package/dist/browser/index.esm.d.ts.map +1 -0
- package/dist/browser/index.esm.js +46 -0
- package/dist/browser/index.esm.js.map +1 -0
- package/dist/browser/index.js +38 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser-esm/index.d.ts +9 -0
- package/dist/browser-esm/index.js +50211 -0
- package/dist/browser-esm/index.js.map +7 -0
- package/dist/browser-umd/index.d.ts +9 -0
- package/dist/browser-umd/index.js +50221 -0
- package/dist/browser-umd/index.js.map +7 -0
- package/dist/browser-umd/index.min.js +39 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.js +38 -0
- package/dist/core/ZipCompress.d.ts +99 -0
- package/dist/core/ZipCompress.d.ts.map +1 -0
- package/dist/core/ZipCompress.js +287 -0
- package/dist/core/ZipCompress.js.map +1 -0
- package/dist/core/ZipCopy.d.ts +175 -0
- package/dist/core/ZipCopy.d.ts.map +1 -0
- package/dist/core/ZipCopy.js +310 -0
- package/dist/core/ZipCopy.js.map +1 -0
- package/dist/core/ZipDecompress.d.ts +57 -0
- package/dist/core/ZipDecompress.d.ts.map +1 -0
- package/dist/core/ZipDecompress.js +155 -0
- package/dist/core/ZipDecompress.js.map +1 -0
- package/dist/core/ZipEntry.d.ts +138 -0
- package/dist/core/ZipEntry.d.ts.map +1 -0
- package/dist/core/ZipEntry.js +829 -0
- package/dist/core/ZipEntry.js.map +1 -0
- package/dist/core/Zipkit.d.ts +315 -0
- package/dist/core/Zipkit.d.ts.map +1 -0
- package/dist/core/Zipkit.js +647 -0
- package/dist/core/Zipkit.js.map +1 -0
- package/dist/core/ZstdManager.d.ts +56 -0
- package/dist/core/ZstdManager.d.ts.map +1 -0
- package/dist/core/ZstdManager.js +144 -0
- package/dist/core/ZstdManager.js.map +1 -0
- package/dist/core/components/HashCalculator.d.ts +138 -0
- package/dist/core/components/HashCalculator.d.ts.map +1 -0
- package/dist/core/components/HashCalculator.js +360 -0
- package/dist/core/components/HashCalculator.js.map +1 -0
- package/dist/core/components/Logger.d.ts +73 -0
- package/dist/core/components/Logger.d.ts.map +1 -0
- package/dist/core/components/Logger.js +156 -0
- package/dist/core/components/Logger.js.map +1 -0
- package/dist/core/components/ProgressTracker.d.ts +43 -0
- package/dist/core/components/ProgressTracker.d.ts.map +1 -0
- package/dist/core/components/ProgressTracker.js +112 -0
- package/dist/core/components/ProgressTracker.js.map +1 -0
- package/dist/core/components/Support.d.ts +64 -0
- package/dist/core/components/Support.d.ts.map +1 -0
- package/dist/core/components/Support.js +71 -0
- package/dist/core/components/Support.js.map +1 -0
- package/dist/core/components/Util.d.ts +26 -0
- package/dist/core/components/Util.d.ts.map +1 -0
- package/dist/core/components/Util.js +95 -0
- package/dist/core/components/Util.js.map +1 -0
- package/dist/core/constants/Errors.d.ts +52 -0
- package/dist/core/constants/Errors.d.ts.map +1 -0
- package/dist/core/constants/Errors.js +67 -0
- package/dist/core/constants/Errors.js.map +1 -0
- package/dist/core/constants/Headers.d.ts +170 -0
- package/dist/core/constants/Headers.d.ts.map +1 -0
- package/dist/core/constants/Headers.js +194 -0
- package/dist/core/constants/Headers.js.map +1 -0
- package/dist/core/encryption/Manager.d.ts +58 -0
- package/dist/core/encryption/Manager.d.ts.map +1 -0
- package/dist/core/encryption/Manager.js +121 -0
- package/dist/core/encryption/Manager.js.map +1 -0
- package/dist/core/encryption/ZipCrypto.d.ts +172 -0
- package/dist/core/encryption/ZipCrypto.d.ts.map +1 -0
- package/dist/core/encryption/ZipCrypto.js +554 -0
- package/dist/core/encryption/ZipCrypto.js.map +1 -0
- package/dist/core/encryption/index.d.ts +9 -0
- package/dist/core/encryption/index.d.ts.map +1 -0
- package/dist/core/encryption/index.js +17 -0
- package/dist/core/encryption/index.js.map +1 -0
- package/dist/core/encryption/types.d.ts +29 -0
- package/dist/core/encryption/types.d.ts.map +1 -0
- package/dist/core/encryption/types.js +12 -0
- package/dist/core/encryption/types.js.map +1 -0
- package/dist/core/index.d.ts +27 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +59 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/version.d.ts +5 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +31 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/node/ZipCompressNode.d.ts +123 -0
- package/dist/node/ZipCompressNode.d.ts.map +1 -0
- package/dist/node/ZipCompressNode.js +565 -0
- package/dist/node/ZipCompressNode.js.map +1 -0
- package/dist/node/ZipCopyNode.d.ts +165 -0
- package/dist/node/ZipCopyNode.d.ts.map +1 -0
- package/dist/node/ZipCopyNode.js +347 -0
- package/dist/node/ZipCopyNode.js.map +1 -0
- package/dist/node/ZipDecompressNode.d.ts +197 -0
- package/dist/node/ZipDecompressNode.d.ts.map +1 -0
- package/dist/node/ZipDecompressNode.js +678 -0
- package/dist/node/ZipDecompressNode.js.map +1 -0
- package/dist/node/ZipkitNode.d.ts +466 -0
- package/dist/node/ZipkitNode.d.ts.map +1 -0
- package/dist/node/ZipkitNode.js +1426 -0
- package/dist/node/ZipkitNode.js.map +1 -0
- package/dist/node/index.d.ts +25 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +54 -0
- package/dist/node/index.js.map +1 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/examples/README.md +261 -0
- package/examples/append-data.json +44 -0
- package/examples/copy-zip-append.ts +139 -0
- package/examples/copy-zip.ts +152 -0
- package/examples/create-zip.ts +172 -0
- package/examples/extract-zip.ts +118 -0
- package/examples/list-zip.ts +161 -0
- package/examples/test-files/data.json +116 -0
- package/examples/test-files/document.md +80 -0
- package/examples/test-files/document.txt +6 -0
- package/examples/test-files/file1.txt +48 -0
- package/examples/test-files/file2.txt +80 -0
- package/examples/tsconfig.json +44 -0
- package/package.json +167 -0
- package/src/browser/ZipkitBrowser.ts +305 -0
- package/src/browser/index.esm.ts +32 -0
- package/src/browser/index.ts +19 -0
- package/src/core/ZipCompress.ts +370 -0
- package/src/core/ZipCopy.ts +434 -0
- package/src/core/ZipDecompress.ts +191 -0
- package/src/core/ZipEntry.ts +917 -0
- package/src/core/Zipkit.ts +794 -0
- package/src/core/ZstdManager.ts +165 -0
- package/src/core/components/HashCalculator.ts +384 -0
- package/src/core/components/Logger.ts +180 -0
- package/src/core/components/ProgressTracker.ts +134 -0
- package/src/core/components/Support.ts +77 -0
- package/src/core/components/Util.ts +91 -0
- package/src/core/constants/Errors.ts +78 -0
- package/src/core/constants/Headers.ts +205 -0
- package/src/core/encryption/Manager.ts +137 -0
- package/src/core/encryption/ZipCrypto.ts +650 -0
- package/src/core/encryption/index.ts +15 -0
- package/src/core/encryption/types.ts +33 -0
- package/src/core/index.ts +42 -0
- package/src/core/version.ts +33 -0
- package/src/index.ts +19 -0
- package/src/node/ZipCompressNode.ts +618 -0
- package/src/node/ZipCopyNode.ts +437 -0
- package/src/node/ZipDecompressNode.ts +793 -0
- package/src/node/ZipkitNode.ts +1706 -0
- package/src/node/index.ts +40 -0
- package/src/types/index.ts +68 -0
- package/src/types/modules.d.ts +22 -0
- 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
|
+
|