spd-lib 1.3.3 → 1.3.5

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 CHANGED
@@ -219,17 +219,20 @@ const spd = await SPD.loadFromString(b64, 'MyStr0ng!Passphrase#2024');
219
219
  Split a payload into chunks for HTTP uploads or any transport with a body-size limit.
220
220
 
221
221
  ```ts
222
- // Sender
223
- const chunks = await spd.saveDataChunked('MyStr0ng!Passphrase#2024', 512 * 1024); // 512 KB chunks
222
+ // Sender — chunk size is chosen automatically
223
+ const chunks = await spd.saveDataChunked('MyStr0ng!Passphrase#2024');
224
224
  // chunks is string[] — send each element, then the manifest (last element) last
225
225
 
226
+ // Override chunk size when you have a strict body limit (e.g. 256 KB per request)
227
+ const chunks = await spd.saveDataChunked('MyStr0ng!Passphrase#2024', 256 * 1024);
228
+
226
229
  // Receiver — pass all chunks in order including the manifest
227
230
  const spd = await SPD.loadFromChunks(chunks, 'MyStr0ng!Passphrase#2024');
228
231
  ```
229
232
 
230
233
  The last element of the array is always a JSON manifest (`{ totalChunks, chunkSize, totalBytes, version }`). The receiver validates chunk count and byte count before decrypting.
231
234
 
232
- Default chunk size is **1 MB**. Tune down for strict API body limits.
235
+ **Auto chunk size** (when `chunkSize` is omitted): targets ~32 chunks, clamped between 64 KB and 8 MB, rounded to the nearest 64 KB boundary. Pass an explicit `chunkSize` to override (e.g. `256 * 1024` for strict API body limits).
233
236
 
234
237
  ---
235
238
 
package/index.d.mts CHANGED
@@ -25,6 +25,8 @@ interface SPDPayload {
25
25
  hashAlgorithm?: string;
26
26
  argon2Memory?: number;
27
27
  argon2Time?: number;
28
+ argon2HashLen?: number;
29
+ hashKeyLen?: number;
28
30
  }
29
31
  interface SPDLegacyPayload {
30
32
  data: SerializedDataEntry[];
@@ -65,9 +67,26 @@ interface SPDChunkManifest {
65
67
  totalBytes: number;
66
68
  version: number;
67
69
  }
70
+ interface SPDIndexEntry {
71
+ name: string;
72
+ decompressedOffset: bigint;
73
+ }
74
+ interface SPDWriterOptions {
75
+ compressionLevel?: number;
76
+ hashAlgorithm?: HashAlgorithm;
77
+ argon2Memory?: number;
78
+ argon2Time?: number;
79
+ }
80
+ interface SPDGetEntryResult {
81
+ value: unknown;
82
+ dataType: SupportedDataType;
83
+ name: string;
84
+ }
68
85
  type TypedArray = Uint8Array | Uint16Array | Uint32Array | BigInt64Array | BigUint64Array | Float32Array | Float64Array;
69
86
  type SupportedValue = string | number | boolean | unknown[] | TypedArray | Map<unknown, unknown> | Set<unknown> | Date | RegExp | Error | Record<string, unknown>;
70
87
 
88
+ declare const ARGON2_MEMORY_HIGH = 524288;
89
+ declare const ARGON2_TIME_HIGH = 8;
71
90
  declare class SPD {
72
91
  private data;
73
92
  private keyPair?;
@@ -80,11 +99,14 @@ declare class SPD {
80
99
  init(): Promise<void>;
81
100
  changePasscode(oldPasscode: string, newPasscode: string): Promise<void>;
82
101
  /**
83
- * Derives a 512-bit master secret via Argon2id and splits it into:
84
- * aeadKey (32 B) — XChaCha20-Poly1305 encryption key (256-bit, 128-bit PQ)
85
- * macKey (32 B) — HMAC-SHA3-512 authentication key (domain-separated)
102
+ * Derives a master secret via Argon2id and splits it into:
103
+ * aeadKey (32 B) — XChaCha20-Poly1305 encryption key
104
+ * macKey (32/64 B) — HMAC-SHA3-512 authentication key (domain-separated)
105
+ *
106
+ * v27+: hashLen=96, macKeyLen=64 (full SHA3-512 output width = 128-bit PQ forgery resistance)
107
+ * v26: hashLen=64, macKeyLen=32 (legacy — pass these explicitly when reading old files)
86
108
  */
87
- static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<{
109
+ static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number): Promise<{
88
110
  aeadKey: Uint8Array;
89
111
  macKey: Uint8Array;
90
112
  }>;
@@ -127,13 +149,38 @@ declare class SPD {
127
149
  /**
128
150
  * Saves to file using streaming I/O — supports files larger than 2 GB.
129
151
  * Format: [8B LE uint64 plaintext length][64B HMAC-SHA3-512][zlib(plaintext)]
152
+ *
153
+ * Also writes a `.spdi` index sidecar alongside the output file that allows
154
+ * `SPD.getEntry()` to locate entries without decrypting the whole file.
130
155
  */
131
156
  saveToFileStreaming(outputPath: string, passcode: string): Promise<void>;
157
+ /**
158
+ * Computes the decompressed byte offset of each entry within a binary plaintext buffer.
159
+ * The offset points to the start of that entry's nameLen byte.
160
+ */
161
+ private static buildOffsetIndex;
162
+ /**
163
+ * Writes a `.spdi` index sidecar file for fast on-demand entry lookup.
164
+ * Format:
165
+ * [4B magic "SPDI"][4B version][8B entryCount][32B HMAC-SHA3-256 of body]
166
+ * for each entry: [1B nameLen][name bytes][8B LE uint64 decompressedOffset]
167
+ */
168
+ private static writeIndexFile;
169
+ /**
170
+ * Reads and validates a `.spdi` index sidecar.
171
+ * Returns null if the file doesn't exist or the HMAC fails (falls back to scan).
172
+ */
173
+ private static readIndexFile;
132
174
  saveData(passcode?: string): Promise<string>;
133
175
  /**
134
176
  * Splits the payload into base64 chunks for chunked internet transfer.
135
177
  * The manifest (last element) records totalChunks/totalBytes for validation.
136
178
  * Reassemble with `SPD.loadFromChunks(chunks, passcode)`.
179
+ *
180
+ * If `chunkSize` is omitted (or 0), a size is computed automatically:
181
+ * - Targets ~32 chunks so the manifest stays small
182
+ * - Clamped between 64 KB (min) and 8 MB (max)
183
+ * - Rounded up to the nearest 64 KB boundary for alignment
137
184
  */
138
185
  saveDataChunked(passcode: string, chunkSize?: number): Promise<string[]>;
139
186
  static loadFromFile(filePath: string, passcode: string): Promise<SPD>;
@@ -165,9 +212,24 @@ declare class SPD {
165
212
  * });
166
213
  */
167
214
  static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
215
+ /**
216
+ * On-demand disk lookup — decrypts and returns a single named entry from a
217
+ * binary `.spd` file (written by `saveToFileStreaming`) without loading the
218
+ * whole file into RAM.
219
+ *
220
+ * Uses a `.spdi` index sidecar (written alongside by `saveToFileStreaming`)
221
+ * for fast offset lookup when present. Falls back to a sequential scan if
222
+ * the sidecar is missing or its HMAC fails.
223
+ *
224
+ * The full plaintext MAC is always verified before any entry is decrypted —
225
+ * unauthenticated reads are never returned.
226
+ *
227
+ * RAM usage is bounded to ~one entry's worth of bytes at any time.
228
+ */
229
+ static getEntry(filePath: string, passcode: string, name: string): Promise<SPDGetEntryResult>;
168
230
  static loadFromString(data: string, passcode: string): Promise<SPD>;
169
231
  static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
170
- static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<PBKResult>;
232
+ static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number): Promise<PBKResult>;
171
233
  static decryptSalt(encryptedSalt: number[], saltNonce: number[], wrapSalt: number[], passcode: string, memory?: number, time?: number): Promise<Uint8Array>;
172
234
  static encryptSalt(salt: Uint8Array, passcode: string): Promise<EncryptedSaltResult>;
173
235
  static toBase64(data: Uint8Array | Buffer): string;
@@ -260,4 +322,59 @@ declare class SPDVault {
260
322
  destroy(): void;
261
323
  }
262
324
 
263
- export { type DataInput, type EncryptedDataEntry, type EncryptedSaltResult, type HashAlgorithm, type PBKResult, type PQCKey, type PQCKeyResult, SPD, type SPDChunkManifest, SPDLegacy, type SPDLegacyPayload, type SPDPayload, SPDVault, SPDLegacy as SPD_LEG, SPDVault as SPD_Vault, type SerializedDataEntry, type SerializedWrappedPayload, type SupportedDataType, type SupportedValue, type TypedArray, type WrappedPayload };
325
+ /**
326
+ * SPDWriter — streaming, disk-backed binary SPD writer.
327
+ *
328
+ * Writes entries one at a time to disk as they are encrypted, so the full
329
+ * plaintext never exists in RAM simultaneously. Works on any device regardless
330
+ * of available memory.
331
+ *
332
+ * Usage:
333
+ * const writer = new SPDWriter('./output.spd', 'MyStr0ng!Passphrase#2024');
334
+ * await writer.init();
335
+ * await writer.addEntry('username', 'alice');
336
+ * await writer.addEntry('config', { theme: 'dark' });
337
+ * await writer.finalize();
338
+ *
339
+ * After finalize(), a `.spdi` index sidecar is written alongside the output
340
+ * file so that `SPD.getEntry()` can locate entries without a full scan.
341
+ */
342
+
343
+ declare class SPDWriter {
344
+ private outputPath;
345
+ private passcode;
346
+ private opts;
347
+ private aeadKey?;
348
+ private macKey?;
349
+ private salt?;
350
+ private deflate?;
351
+ private outStream?;
352
+ private hmac?;
353
+ private plaintextOffset;
354
+ private entryCount;
355
+ private indexEntries;
356
+ private finalized;
357
+ private initialized;
358
+ constructor(outputPath: string, passcode: string, opts?: SPDWriterOptions);
359
+ /**
360
+ * Derives keys, opens the output file, and writes the meta block.
361
+ * Must be called before addEntry().
362
+ */
363
+ init(): Promise<void>;
364
+ /**
365
+ * Encrypts one entry and streams it to disk.
366
+ */
367
+ addEntry(name: string, value: unknown): Promise<void>;
368
+ /**
369
+ * Flushes the deflate stream, computes the HMAC, patches the file header,
370
+ * and writes the `.spdi` index sidecar.
371
+ */
372
+ finalize(): Promise<void>;
373
+ /** Zero keys and remove the partial output file if finalize() was never called. */
374
+ destroy(): void;
375
+ /** Write bytes to the deflate stream and update the rolling HMAC + offset. */
376
+ private writeToPlaintext;
377
+ private writeIndexSidecar;
378
+ }
379
+
380
+ export { ARGON2_MEMORY_HIGH, ARGON2_TIME_HIGH, type DataInput, type EncryptedDataEntry, type EncryptedSaltResult, type HashAlgorithm, type PBKResult, type PQCKey, type PQCKeyResult, SPD, type SPDChunkManifest, type SPDGetEntryResult, type SPDIndexEntry, SPDLegacy, type SPDLegacyPayload, type SPDPayload, SPDVault, SPDWriter, type SPDWriterOptions, SPDLegacy as SPD_LEG, SPDVault as SPD_Vault, type SerializedDataEntry, type SerializedWrappedPayload, type SupportedDataType, type SupportedValue, type TypedArray, type WrappedPayload };
package/index.d.ts CHANGED
@@ -25,6 +25,8 @@ interface SPDPayload {
25
25
  hashAlgorithm?: string;
26
26
  argon2Memory?: number;
27
27
  argon2Time?: number;
28
+ argon2HashLen?: number;
29
+ hashKeyLen?: number;
28
30
  }
29
31
  interface SPDLegacyPayload {
30
32
  data: SerializedDataEntry[];
@@ -65,9 +67,26 @@ interface SPDChunkManifest {
65
67
  totalBytes: number;
66
68
  version: number;
67
69
  }
70
+ interface SPDIndexEntry {
71
+ name: string;
72
+ decompressedOffset: bigint;
73
+ }
74
+ interface SPDWriterOptions {
75
+ compressionLevel?: number;
76
+ hashAlgorithm?: HashAlgorithm;
77
+ argon2Memory?: number;
78
+ argon2Time?: number;
79
+ }
80
+ interface SPDGetEntryResult {
81
+ value: unknown;
82
+ dataType: SupportedDataType;
83
+ name: string;
84
+ }
68
85
  type TypedArray = Uint8Array | Uint16Array | Uint32Array | BigInt64Array | BigUint64Array | Float32Array | Float64Array;
69
86
  type SupportedValue = string | number | boolean | unknown[] | TypedArray | Map<unknown, unknown> | Set<unknown> | Date | RegExp | Error | Record<string, unknown>;
70
87
 
88
+ declare const ARGON2_MEMORY_HIGH = 524288;
89
+ declare const ARGON2_TIME_HIGH = 8;
71
90
  declare class SPD {
72
91
  private data;
73
92
  private keyPair?;
@@ -80,11 +99,14 @@ declare class SPD {
80
99
  init(): Promise<void>;
81
100
  changePasscode(oldPasscode: string, newPasscode: string): Promise<void>;
82
101
  /**
83
- * Derives a 512-bit master secret via Argon2id and splits it into:
84
- * aeadKey (32 B) — XChaCha20-Poly1305 encryption key (256-bit, 128-bit PQ)
85
- * macKey (32 B) — HMAC-SHA3-512 authentication key (domain-separated)
102
+ * Derives a master secret via Argon2id and splits it into:
103
+ * aeadKey (32 B) — XChaCha20-Poly1305 encryption key
104
+ * macKey (32/64 B) — HMAC-SHA3-512 authentication key (domain-separated)
105
+ *
106
+ * v27+: hashLen=96, macKeyLen=64 (full SHA3-512 output width = 128-bit PQ forgery resistance)
107
+ * v26: hashLen=64, macKeyLen=32 (legacy — pass these explicitly when reading old files)
86
108
  */
87
- static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<{
109
+ static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number): Promise<{
88
110
  aeadKey: Uint8Array;
89
111
  macKey: Uint8Array;
90
112
  }>;
@@ -127,13 +149,38 @@ declare class SPD {
127
149
  /**
128
150
  * Saves to file using streaming I/O — supports files larger than 2 GB.
129
151
  * Format: [8B LE uint64 plaintext length][64B HMAC-SHA3-512][zlib(plaintext)]
152
+ *
153
+ * Also writes a `.spdi` index sidecar alongside the output file that allows
154
+ * `SPD.getEntry()` to locate entries without decrypting the whole file.
130
155
  */
131
156
  saveToFileStreaming(outputPath: string, passcode: string): Promise<void>;
157
+ /**
158
+ * Computes the decompressed byte offset of each entry within a binary plaintext buffer.
159
+ * The offset points to the start of that entry's nameLen byte.
160
+ */
161
+ private static buildOffsetIndex;
162
+ /**
163
+ * Writes a `.spdi` index sidecar file for fast on-demand entry lookup.
164
+ * Format:
165
+ * [4B magic "SPDI"][4B version][8B entryCount][32B HMAC-SHA3-256 of body]
166
+ * for each entry: [1B nameLen][name bytes][8B LE uint64 decompressedOffset]
167
+ */
168
+ private static writeIndexFile;
169
+ /**
170
+ * Reads and validates a `.spdi` index sidecar.
171
+ * Returns null if the file doesn't exist or the HMAC fails (falls back to scan).
172
+ */
173
+ private static readIndexFile;
132
174
  saveData(passcode?: string): Promise<string>;
133
175
  /**
134
176
  * Splits the payload into base64 chunks for chunked internet transfer.
135
177
  * The manifest (last element) records totalChunks/totalBytes for validation.
136
178
  * Reassemble with `SPD.loadFromChunks(chunks, passcode)`.
179
+ *
180
+ * If `chunkSize` is omitted (or 0), a size is computed automatically:
181
+ * - Targets ~32 chunks so the manifest stays small
182
+ * - Clamped between 64 KB (min) and 8 MB (max)
183
+ * - Rounded up to the nearest 64 KB boundary for alignment
137
184
  */
138
185
  saveDataChunked(passcode: string, chunkSize?: number): Promise<string[]>;
139
186
  static loadFromFile(filePath: string, passcode: string): Promise<SPD>;
@@ -165,9 +212,24 @@ declare class SPD {
165
212
  * });
166
213
  */
167
214
  static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
215
+ /**
216
+ * On-demand disk lookup — decrypts and returns a single named entry from a
217
+ * binary `.spd` file (written by `saveToFileStreaming`) without loading the
218
+ * whole file into RAM.
219
+ *
220
+ * Uses a `.spdi` index sidecar (written alongside by `saveToFileStreaming`)
221
+ * for fast offset lookup when present. Falls back to a sequential scan if
222
+ * the sidecar is missing or its HMAC fails.
223
+ *
224
+ * The full plaintext MAC is always verified before any entry is decrypted —
225
+ * unauthenticated reads are never returned.
226
+ *
227
+ * RAM usage is bounded to ~one entry's worth of bytes at any time.
228
+ */
229
+ static getEntry(filePath: string, passcode: string, name: string): Promise<SPDGetEntryResult>;
168
230
  static loadFromString(data: string, passcode: string): Promise<SPD>;
169
231
  static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
170
- static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<PBKResult>;
232
+ static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number): Promise<PBKResult>;
171
233
  static decryptSalt(encryptedSalt: number[], saltNonce: number[], wrapSalt: number[], passcode: string, memory?: number, time?: number): Promise<Uint8Array>;
172
234
  static encryptSalt(salt: Uint8Array, passcode: string): Promise<EncryptedSaltResult>;
173
235
  static toBase64(data: Uint8Array | Buffer): string;
@@ -260,4 +322,59 @@ declare class SPDVault {
260
322
  destroy(): void;
261
323
  }
262
324
 
263
- export { type DataInput, type EncryptedDataEntry, type EncryptedSaltResult, type HashAlgorithm, type PBKResult, type PQCKey, type PQCKeyResult, SPD, type SPDChunkManifest, SPDLegacy, type SPDLegacyPayload, type SPDPayload, SPDVault, SPDLegacy as SPD_LEG, SPDVault as SPD_Vault, type SerializedDataEntry, type SerializedWrappedPayload, type SupportedDataType, type SupportedValue, type TypedArray, type WrappedPayload };
325
+ /**
326
+ * SPDWriter — streaming, disk-backed binary SPD writer.
327
+ *
328
+ * Writes entries one at a time to disk as they are encrypted, so the full
329
+ * plaintext never exists in RAM simultaneously. Works on any device regardless
330
+ * of available memory.
331
+ *
332
+ * Usage:
333
+ * const writer = new SPDWriter('./output.spd', 'MyStr0ng!Passphrase#2024');
334
+ * await writer.init();
335
+ * await writer.addEntry('username', 'alice');
336
+ * await writer.addEntry('config', { theme: 'dark' });
337
+ * await writer.finalize();
338
+ *
339
+ * After finalize(), a `.spdi` index sidecar is written alongside the output
340
+ * file so that `SPD.getEntry()` can locate entries without a full scan.
341
+ */
342
+
343
+ declare class SPDWriter {
344
+ private outputPath;
345
+ private passcode;
346
+ private opts;
347
+ private aeadKey?;
348
+ private macKey?;
349
+ private salt?;
350
+ private deflate?;
351
+ private outStream?;
352
+ private hmac?;
353
+ private plaintextOffset;
354
+ private entryCount;
355
+ private indexEntries;
356
+ private finalized;
357
+ private initialized;
358
+ constructor(outputPath: string, passcode: string, opts?: SPDWriterOptions);
359
+ /**
360
+ * Derives keys, opens the output file, and writes the meta block.
361
+ * Must be called before addEntry().
362
+ */
363
+ init(): Promise<void>;
364
+ /**
365
+ * Encrypts one entry and streams it to disk.
366
+ */
367
+ addEntry(name: string, value: unknown): Promise<void>;
368
+ /**
369
+ * Flushes the deflate stream, computes the HMAC, patches the file header,
370
+ * and writes the `.spdi` index sidecar.
371
+ */
372
+ finalize(): Promise<void>;
373
+ /** Zero keys and remove the partial output file if finalize() was never called. */
374
+ destroy(): void;
375
+ /** Write bytes to the deflate stream and update the rolling HMAC + offset. */
376
+ private writeToPlaintext;
377
+ private writeIndexSidecar;
378
+ }
379
+
380
+ export { ARGON2_MEMORY_HIGH, ARGON2_TIME_HIGH, type DataInput, type EncryptedDataEntry, type EncryptedSaltResult, type HashAlgorithm, type PBKResult, type PQCKey, type PQCKeyResult, SPD, type SPDChunkManifest, type SPDGetEntryResult, type SPDIndexEntry, SPDLegacy, type SPDLegacyPayload, type SPDPayload, SPDVault, SPDWriter, type SPDWriterOptions, SPDLegacy as SPD_LEG, SPDVault as SPD_Vault, type SerializedDataEntry, type SerializedWrappedPayload, type SupportedDataType, type SupportedValue, type TypedArray, type WrappedPayload };