spd-lib 1.3.5 → 1.3.6

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/index.d.ts CHANGED
@@ -22,11 +22,45 @@ interface SPDPayload {
22
22
  saltNonce: number[];
23
23
  wrapSalt: number[];
24
24
  version: number;
25
+ writeCounter?: number;
25
26
  hashAlgorithm?: string;
26
27
  argon2Memory?: number;
27
28
  argon2Time?: number;
28
29
  argon2HashLen?: number;
29
30
  hashKeyLen?: number;
31
+ entryRoles?: Record<string, string[]>;
32
+ maxOpenCount?: number;
33
+ openCount?: number;
34
+ keyProfile?: string;
35
+ keyCommitment?: string;
36
+ kdfChain?: boolean;
37
+ scryptN?: number;
38
+ scryptR?: number;
39
+ scryptP?: number;
40
+ wrapArgon2Memory?: number;
41
+ wrapArgon2Time?: number;
42
+ fileSignature?: number[];
43
+ signingPubKey?: number[];
44
+ ratchetEnabled?: boolean;
45
+ ratchetCounter?: number;
46
+ epochNumber?: number;
47
+ epochSize?: number;
48
+ merkleRoot?: string;
49
+ counterHMAC?: string;
50
+ }
51
+ /** PKCS#11 / HSM key provider abstraction. Implement this interface to use hardware keys. */
52
+ interface SPDKeyProvider {
53
+ /** Derive the 96-byte master secret from a passcode and salt (replaces Argon2id). */
54
+ deriveKey(passcode: string, salt: Uint8Array, memory: number, time: number): Promise<Uint8Array>;
55
+ /** Optional: wrap (encrypt) a key for storage — used when exporting key material. */
56
+ wrapKey?(key: Uint8Array): Promise<Uint8Array>;
57
+ /** Optional: unwrap (decrypt) a key retrieved from storage. */
58
+ unwrapKey?(wrapped: Uint8Array): Promise<Uint8Array>;
59
+ }
60
+ /** ML-DSA-65 signing key pair for file authentication. Signatures are 3309 bytes. */
61
+ interface SPDSigningKeyPair {
62
+ privateKey: Uint8Array;
63
+ publicKey: Uint8Array;
30
64
  }
31
65
  interface SPDLegacyPayload {
32
66
  data: SerializedDataEntry[];
@@ -82,11 +116,62 @@ interface SPDGetEntryResult {
82
116
  dataType: SupportedDataType;
83
117
  name: string;
84
118
  }
119
+ interface SPDInspectResult {
120
+ version: number;
121
+ entryCount: number;
122
+ writeCounter: number;
123
+ hashAlgorithm: string;
124
+ argon2Memory: number;
125
+ argon2Time: number;
126
+ fileSize: number;
127
+ hasIndex: boolean;
128
+ format: 'binary' | 'json';
129
+ }
130
+ interface SPDVerifyResult {
131
+ valid: boolean;
132
+ entryCount: number;
133
+ indexPresent: boolean;
134
+ writeCounter: number;
135
+ }
136
+ interface SPDDiffResult {
137
+ added: string[];
138
+ removed: string[];
139
+ modified: string[];
140
+ }
141
+ interface SPDMergeOptions {
142
+ strategy: 'a-wins' | 'b-wins' | 'newer';
143
+ }
144
+ interface SPDSnapshot {
145
+ /** Encrypted entries captured at snapshot time (deep copies). */
146
+ data: EncryptedDataEntry[];
147
+ writeCounter: number;
148
+ ts: number;
149
+ }
150
+ interface SPDLogEvent {
151
+ event: 'key_derived' | 'key_cache_hit' | 'entry_added' | 'entry_updated' | 'entry_deleted' | 'save_start' | 'save_end' | 'load_start' | 'load_end' | 'rollback_detected' | 'lock_acquired' | 'lock_released' | 'wal_flushed' | 'auto_destruct' | 'repair_complete' | 'benchmark_complete' | 'keyfile_generated' | 'keyfile_loaded';
152
+ ts: number;
153
+ detail?: Record<string, unknown>;
154
+ }
155
+ interface SPDBenchmarkResult {
156
+ argon2Ms: number;
157
+ encryptMs: number;
158
+ decryptMs: number;
159
+ macMs: number;
160
+ entriesPerSecond: number;
161
+ }
162
+ interface SPDRepairResult {
163
+ recovered: number;
164
+ skipped: number;
165
+ }
85
166
  type TypedArray = Uint8Array | Uint16Array | Uint32Array | BigInt64Array | BigUint64Array | Float32Array | Float64Array;
86
167
  type SupportedValue = string | number | boolean | unknown[] | TypedArray | Map<unknown, unknown> | Set<unknown> | Date | RegExp | Error | Record<string, unknown>;
87
168
 
88
169
  declare const ARGON2_MEMORY_HIGH = 524288;
89
170
  declare const ARGON2_TIME_HIGH = 8;
171
+ declare const ARGON2_MEMORY_PARANOID = 1048576;
172
+ declare const ARGON2_TIME_PARANOID = 12;
173
+ /** Key stretching profile names */
174
+ type SPDKeyProfile = 'standard' | 'high' | 'paranoid';
90
175
  declare class SPD {
91
176
  private data;
92
177
  private keyPair?;
@@ -95,8 +180,118 @@ declare class SPD {
95
180
  private salt?;
96
181
  private hash;
97
182
  private compressionLevel;
183
+ private fileVersion;
184
+ private writeCounter;
185
+ private walPath?;
186
+ private walRecordCount;
187
+ private static readonly WAL_COMPACT_RECORDS;
188
+ private static readonly WAL_COMPACT_BYTES;
189
+ private static globalPepper;
190
+ private static keyfileBytes;
191
+ private entryRoles;
192
+ private currentRole;
193
+ private maxOpenCount;
194
+ private openCount;
195
+ private schemas;
196
+ private _plogPath?;
197
+ private keyProfile;
198
+ private static machineBindingEnabled;
199
+ private _keyMask?;
200
+ private _blindedKey?;
201
+ private _blindedMacKey?;
202
+ private _keyBlindingEnabled;
203
+ private _signingKey?;
204
+ private _verifyKey?;
205
+ private _ratchetEnabled;
206
+ private _epochSize;
207
+ private _epochNumber;
208
+ private static _keyProvider?;
209
+ private _kdfChain;
210
+ private _scryptN;
211
+ private _scryptR;
212
+ private _scryptP;
213
+ private _wrapArgon2Memory;
214
+ private _wrapArgon2Time;
215
+ private static globalLogger;
216
+ private static keyCache;
217
+ private static keyCacheGet;
218
+ private static keyCacheSet;
219
+ /** Build a non-secret cache key from the passcode and wrapSalt bytes. */
220
+ private static buildCacheKey;
221
+ /** Evict all cached key material immediately (e.g. on pepper change). */
222
+ static clearKeyCache(): void;
223
+ private static nameCollisionWarnings;
224
+ /**
225
+ * Enable developer warnings when two raw entry names map to the same
226
+ * sanitized name (e.g. "user.id" and "user_id" → both become "user_id").
227
+ * Logs to console.warn. Off by default to avoid noise in production.
228
+ */
229
+ static enableNameCollisionWarnings(): void;
230
+ static disableNameCollisionWarnings(): void;
231
+ /**
232
+ * Install a global log handler. Called for key lifecycle events so the host
233
+ * app can feed SPD activity into its own observability pipeline.
234
+ * Pass `undefined` to remove the handler.
235
+ */
236
+ static setLogger(fn: ((e: SPDLogEvent) => void) | undefined): void;
237
+ private static log;
98
238
  private assertReady;
99
239
  init(): Promise<void>;
240
+ /**
241
+ * Enable in-memory key blinding: keys are stored XOR'd with a random mask.
242
+ * This reduces the risk of key extraction from memory dumps or cold-boot attacks.
243
+ * Must be called before `setPassKey()`.
244
+ */
245
+ enableKeyBlinding(): void;
246
+ disableKeyBlinding(): void;
247
+ private static _xorBufs;
248
+ /** Blind the current userKey and macKey with a fresh random mask. */
249
+ private _blindKeys;
250
+ /** Unblind keys into temporaries, run fn, then re-blind. */
251
+ private _withKeys;
252
+ private _withKeysAsync;
253
+ /**
254
+ * Set an ML-DSA-65 signing private key. When set, every `saveToFile` /
255
+ * `saveToFileStreaming` call will embed an ML-DSA-65 signature over the file MAC.
256
+ */
257
+ setSigningKey(privateKey: Uint8Array): void;
258
+ /**
259
+ * Set an ML-DSA-65 verify public key. When set, `loadFromFile` /
260
+ * `loadFromFileStreaming` will verify the embedded signature and reject
261
+ * files that fail verification.
262
+ */
263
+ setVerifyKey(publicKey: Uint8Array): void;
264
+ /** Generate a fresh ML-DSA-65 signing key pair. */
265
+ static generateSigningKeyPair(): SPDSigningKeyPair;
266
+ /** Enable the per-save ratchet. Each save derives a new sub-key from the previous. */
267
+ enableRatchet(): void;
268
+ disableRatchet(): void;
269
+ /**
270
+ * Set the epoch size: every `n` saves, a new random salt is derived and all
271
+ * entries are re-encrypted under a fresh key. Pass 0 to disable.
272
+ */
273
+ setEpochSize(n: number): void;
274
+ /**
275
+ * Enable KDF chaining: after Argon2id, pipe output through scrypt for
276
+ * double-KDF protection. Enabled by default for `high` / `paranoid` profiles.
277
+ * @param n scrypt CPU/memory cost (power of 2, default 16384)
278
+ * @param r block size (default 8)
279
+ * @param p parallelism (default 1)
280
+ */
281
+ enableKdfChain(n?: number, r?: number, p?: number): void;
282
+ disableKdfChain(): void;
283
+ /**
284
+ * Override the Argon2id parameters used to derive the salt-wrapping key.
285
+ * By default, standard params (128 MiB / 6 passes) are used for backward compatibility.
286
+ * Changing these breaks compatibility with existing files unless you also change the reader.
287
+ */
288
+ setWrapKeyProfile(memory: number, time: number): void;
289
+ /**
290
+ * Install a global PKCS#11 / HSM key provider. When set, SPD delegates
291
+ * all key derivation to `provider.deriveKey()` instead of Argon2id+scrypt.
292
+ * Pass `undefined` to revert to the built-in KDF.
293
+ */
294
+ static setKeyProvider(provider: SPDKeyProvider | undefined): void;
100
295
  changePasscode(oldPasscode: string, newPasscode: string): Promise<void>;
101
296
  /**
102
297
  * Derives a master secret via Argon2id and splits it into:
@@ -106,20 +301,191 @@ declare class SPD {
106
301
  * v27+: hashLen=96, macKeyLen=64 (full SHA3-512 output width = 128-bit PQ forgery resistance)
107
302
  * v26: hashLen=64, macKeyLen=32 (legacy — pass these explicitly when reading old files)
108
303
  */
109
- static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number): Promise<{
304
+ static deriveKeys(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number, macKeyLen?: number, kdfChain?: boolean, scryptN?: number, scryptR?: number, scryptP?: number): Promise<{
110
305
  aeadKey: Uint8Array;
111
306
  macKey: Uint8Array;
112
307
  }>;
308
+ /**
309
+ * Compute a key commitment token: HMAC-SHA3-256(aeadKey, "spd-key-commit-v1").
310
+ * Stored in the file meta and verified on load before decryption begins.
311
+ * Closes key-commitment attacks where an adversary crafts a ciphertext that
312
+ * "decrypts" under two different keys.
313
+ */
314
+ static computeKeyCommitment(aeadKey: Uint8Array): string;
113
315
  private static computeMAC;
316
+ /**
317
+ * Derives a per-entry 32-byte AEAD key from the master AEAD key using HKDF-SHA3-512.
318
+ *
319
+ * Each entry is encrypted under a unique key = HKDF(masterKey, salt=entryName, info="spd-entry-v27").
320
+ * This means:
321
+ * - Compromise of one entry's ciphertext reveals nothing about other entries' keys
322
+ * - The master key never touches AEAD directly
323
+ * - Zero performance impact: HKDF is ~microseconds vs ~1s for Argon2id
324
+ */
325
+ static deriveEntryKey(masterAeadKey: Uint8Array, entryName: string): Uint8Array;
114
326
  checkPasscodeStrength(passcode: string): boolean;
115
327
  setPassKey(passcode: string): Promise<void>;
116
328
  setHash(hash?: HashAlgorithm): void;
117
329
  setCompressionLevel(level?: number): void;
118
330
  getSodium(): typeof sodium;
331
+ /**
332
+ * Restrict an entry to one or more roles. `extractData`, `getEntry`, and the
333
+ * lazy `entries()` iterator will throw if `setRole()` has not been called with
334
+ * a matching role. Pass an empty array to remove all restrictions.
335
+ */
336
+ restrictEntry(name: string, roles: string[]): void;
337
+ /**
338
+ * Set the active role for this instance. Entries restricted to roles that
339
+ * don't include this value will be inaccessible until the role is changed.
340
+ * Pass `undefined` to clear the role (only unrestricted entries will be readable).
341
+ */
342
+ setRole(role: string | undefined): void;
343
+ /**
344
+ * Set the Argon2id cost profile for this instance.
345
+ * - `'standard'` — 128 MiB / 6 passes (default, ~500 ms on modern hardware)
346
+ * - `'high'` — 512 MiB / 8 passes (~2 s)
347
+ * - `'paranoid'` — 1 GiB / 12 passes + machine-secret post-KDF (~4–8 s)
348
+ *
349
+ * Must be set before `setPassKey()`. The chosen profile's params are embedded
350
+ * in the file so loads always re-derive with the same cost.
351
+ */
352
+ setKeyProfile(profile: SPDKeyProfile): void;
353
+ /**
354
+ * Enable machine binding: mixes a stable machine-specific secret (derived
355
+ * from `/etc/machine-id`, `hostname`, or a UUID sidecar) into every key
356
+ * derivation via an extra HMAC step. The resulting key is only reproducible
357
+ * on the originating machine, dramatically raising the cost of offline attacks
358
+ * with a stolen file.
359
+ *
360
+ * Enabling this globally affects all SPD instances in the process.
361
+ * Must be called before `setPassKey()` / `loadFromFile()` / `getEntry()`.
362
+ */
363
+ static enableMachineBinding(): void;
364
+ static disableMachineBinding(): void;
365
+ /** Reads or creates the machine secret (16-byte hex stored in ~/.spd-machine-id). */
366
+ private static getMachineSecret;
367
+ /**
368
+ * Post-KDF machine binding: HMAC-SHA3-512(machineSecret, rawKey)[0..keyLen].
369
+ * Applied to both aeadKey and macKey when machine binding is enabled.
370
+ */
371
+ private static applyMachineBinding;
372
+ /** Resolve Argon2id params from a profile name. */
373
+ static profileParams(profile: SPDKeyProfile): {
374
+ memory: number;
375
+ time: number;
376
+ };
377
+ /** Returns true if the current role is allowed to read `name`. */
378
+ private canReadEntry;
379
+ /**
380
+ * Configure a maximum open count for auto-destruct.
381
+ * After this many successful `loadFromFile` / `loadFromFileStreaming` calls the
382
+ * file is securely wiped and an error is thrown on the exceeding open.
383
+ * Set to 0 to disable (default).
384
+ */
385
+ setMaxOpenCount(n: number): void;
386
+ /**
387
+ * Persist the max-open-count limit to a `.maxcount` sidecar alongside `filePath`.
388
+ * Must be called after `saveToFile`/`saveToFileStreaming` so the sidecar is
389
+ * co-located with the file that was actually written.
390
+ * Callers who set a limit should call this immediately after saving.
391
+ */
392
+ static setFileMaxOpenCount(filePath: string, n: number): void;
393
+ /**
394
+ * Set the path to the passcode rotation audit log (.plog) sidecar file.
395
+ * A JSON-lines record `{ts, event}` is appended whenever `changePasscode` is
396
+ * called. The file is created if it does not exist (mode 0600).
397
+ * Pass `undefined` to disable.
398
+ */
399
+ setPlogPath(p: string | undefined): void;
400
+ /**
401
+ * Attach a validator function to an entry name.
402
+ * Called before encryption in `addData` and `updateEntry`.
403
+ * Throw or return `false` to reject the value.
404
+ */
405
+ setSchema(name: string, validator: (v: unknown) => boolean): void;
406
+ /** Remove a previously registered schema validator. */
407
+ removeSchema(name: string): void;
408
+ private applySchema;
409
+ /**
410
+ * Returns an immutable snapshot of the current encrypted entries.
411
+ * No key material is included — use `restoreSnapshot` to apply.
412
+ */
413
+ snapshot(): SPDSnapshot;
414
+ /**
415
+ * Replaces the current encrypted entries with those from a snapshot.
416
+ * Key material is unchanged — the snapshot must have been taken from an
417
+ * instance with the same passcode.
418
+ */
419
+ restoreSnapshot(snap: SPDSnapshot): void;
119
420
  sanitizeName(dataName: string): string;
120
421
  addData(dataName: string, value: unknown): Promise<void>;
121
422
  addMany(items: DataInput[]): Promise<void>;
122
423
  extractData(): Promise<Record<string, unknown>>;
424
+ /** Returns true if an entry with the given name exists. No decryption. */
425
+ has(name: string): boolean;
426
+ /** Returns all entry names in insertion order. No decryption. */
427
+ keys(): string[];
428
+ /**
429
+ * Lazy decryption iterator — yields `{name, value, dataType}` one entry at a
430
+ * time without loading all entries into memory simultaneously. Respects entry
431
+ * ACL roles; skips (does not throw) decoy entries silently.
432
+ *
433
+ * @example
434
+ * for await (const {name, value} of spd.entries()) {
435
+ * console.log(name, value);
436
+ * }
437
+ */
438
+ entries(): AsyncGenerator<{
439
+ name: string;
440
+ value: unknown;
441
+ dataType: string;
442
+ }>;
443
+ /**
444
+ * Insert `count` fake encrypted entries whose names are derived from `prefix`.
445
+ * Decoys are indistinguishable from real entries in the binary/JSON file.
446
+ * They are silently skipped by `entries()` and flagged as `_decoy` type so
447
+ * callers that inspect `dataType` can filter them.
448
+ */
449
+ addDecoy(prefix: string, count?: number): Promise<void>;
450
+ /**
451
+ * Removes an entry by name. Throws if not found.
452
+ * Does NOT re-save to disk — call saveToFile/saveToFileStreaming after.
453
+ */
454
+ deleteEntry(name: string): void;
455
+ /**
456
+ * Renames an entry. Re-encrypts the blob under the new name's derived key + AAD.
457
+ * Throws if `oldName` is not found or `newName` already exists.
458
+ * Does NOT re-save to disk — call saveToFile/saveToFileStreaming after.
459
+ */
460
+ renameEntry(oldName: string, newName: string): Promise<void>;
461
+ /**
462
+ * Replaces an entry's value in place. Equivalent to deleteEntry + addData
463
+ * but preserves insertion order.
464
+ * Throws if the entry is not found.
465
+ * Does NOT re-save to disk — call saveToFile/saveToFileStreaming after.
466
+ */
467
+ updateEntry(name: string, value: unknown): Promise<void>;
468
+ /**
469
+ * Returns a new SPD instance (with the same key material) containing only the
470
+ * entries whose sanitized names match `pattern`. The returned instance is
471
+ * ready for `saveToFile` / `saveToFileStreaming` — it shares no mutable state
472
+ * with the original.
473
+ *
474
+ * @param pattern RegExp tested against each sanitized entry name.
475
+ * @returns A new SPD instance carrying the matching encrypted entries.
476
+ */
477
+ exportEntries(pattern: RegExp): SPD;
478
+ /**
479
+ * Copies all entries from `source` into this instance.
480
+ * - Both instances must use the same passcode / key material.
481
+ * - If an entry name already exists it is overwritten.
482
+ * - Does NOT re-save to disk.
483
+ *
484
+ * @param source Another SPD instance (must share the same AEAD / HMAC keys).
485
+ */
486
+ importEntries(source: SPD): void;
487
+ /** Returns the number of stored entries. */
488
+ count(): number;
123
489
  /**
124
490
  * Memory-efficient streaming extraction.
125
491
  *
@@ -143,32 +509,174 @@ declare class SPD {
143
509
  * });
144
510
  */
145
511
  extractDataStreaming(tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
512
+ /**
513
+ * Enables the write-ahead log. Once enabled, every `addData` / `addMany` call
514
+ * appends the encrypted entry to `walPath` atomically. On the next
515
+ * `loadFromFile` / `loadFromFileStreaming`, any existing WAL is replayed
516
+ * automatically before returning. `saveToFile` / `saveToFileStreaming` merges
517
+ * and then deletes the WAL.
518
+ *
519
+ * WAL record format (binary, appended):
520
+ * [4B magic "SPDW"][1B version][1B nameLen][nameLen B name]
521
+ * [1B typeLen][typeLen B type][2B hashLen][hashLen B hash]
522
+ * [3B nonceLen][nonceLen B nonce][8B dataLen][dataLen B encData]
523
+ */
524
+ enableWal(walPath: string): void;
525
+ /** Disable WAL without deleting the file. */
526
+ disableWal(): void;
527
+ /** Append a single encrypted entry to the WAL file. Auto-compacts when thresholds are exceeded. */
528
+ private appendToWal;
529
+ /**
530
+ * Internal: called by setImmediate when WAL thresholds are exceeded.
531
+ * Requires a `walCompactPath` to be set; does nothing otherwise.
532
+ * Users should call `flushDelta(outputPath, passcode)` explicitly —
533
+ * auto-compaction only fires when `walCompactPath` is configured via `enableWal`.
534
+ */
535
+ private _walAutoCompact;
536
+ private _walCompactPath?;
537
+ private _walCompactPasscode?;
538
+ /**
539
+ * Configures the output path and passcode used for WAL auto-compaction.
540
+ * Must be called after `enableWal()` for auto-compaction to fire.
541
+ */
542
+ setWalCompactTarget(outputPath: string, passcode: string): void;
543
+ /**
544
+ * Reads and replays all entries from an existing WAL file into `this.data`.
545
+ * Skips duplicate names (WAL entry wins over existing). Returns entry count replayed.
546
+ */
547
+ static replayWal(walPath: string, target: SPD): number;
548
+ /**
549
+ * Merges any pending WAL entries, saves the full file, then deletes the WAL.
550
+ * Use this instead of `saveToFile` when WAL is enabled to ensure the WAL
551
+ * is atomically cleared only after a successful save.
552
+ */
553
+ flushDelta(outputPath: string, passcode: string, streaming?: boolean): Promise<void>;
146
554
  destroy(): void;
147
555
  clearCache(): void;
556
+ /**
557
+ * Sets a global pepper mixed into all Argon2id key derivations.
558
+ * Never stored in the file — both save and load sides must set the same pepper.
559
+ * Must be set before setPassKey / loadFromFile / loadFromString / getEntry.
560
+ */
561
+ static setPepper(pepper: string | Buffer): void;
562
+ /**
563
+ * Generate a cryptographically random 64-byte (512-bit) keyfile and write it
564
+ * to `filePath`. The keyfile should be stored separately from the SPD file
565
+ * (e.g. USB drive, password manager, or HSM) so that an attacker who obtains
566
+ * the SPD file alone cannot attempt password guessing — the keyfile bytes are
567
+ * required as a second factor and are mixed into every Argon2id derivation.
568
+ *
569
+ * Never store the keyfile in the same directory as the SPD file.
570
+ *
571
+ * @example
572
+ * await SPD.generateKeyfile('/mnt/usb/my.key');
573
+ * SPD.loadKeyfile('/mnt/usb/my.key');
574
+ * const spd = new SPD();
575
+ * await spd.setPassKey('anything'); // passcode quality no longer matters
576
+ */
577
+ static generateKeyfile(filePath: string): void;
578
+ /**
579
+ * Load a keyfile previously created with `generateKeyfile()`.
580
+ * Must be called before `setPassKey()`, `loadFromFile()`, and any other
581
+ * operation that derives key material.
582
+ *
583
+ * The keyfile bytes are mixed into every Argon2id derivation via
584
+ * HMAC-SHA3-512(keyfileBytes, fileSalt || passcode), so:
585
+ * - The same keyfile + any passcode ≠ the same keyfile + different passcode
586
+ * - The same keyfile + same passcode on a different SPD file (different salt) ≠ same key
587
+ * - Without the keyfile, no amount of passcode guessing succeeds
588
+ *
589
+ * @param filePath Path to the keyfile (64 bytes of random data).
590
+ */
591
+ static loadKeyfile(filePath: string): void;
592
+ /**
593
+ * Unload the current keyfile so subsequent operations use passcode-only derivation.
594
+ * Call this when you are done with the SPD instance and want to remove the
595
+ * keyfile material from memory.
596
+ */
597
+ static unloadKeyfile(): void;
598
+ /**
599
+ * Generate a cryptographically random diceware passphrase from the EFF short
600
+ * wordlist (1296 words, ~10.3 bits of entropy per word).
601
+ *
602
+ * Default: 9 words → ~72 bits of entropy (256-word list, 8 bits/word) — resistant
603
+ * Argon2id-capable ASICs, and **centuries** at the `high` or `paranoid` profile.
604
+ *
605
+ * With a keyfile loaded alongside, the passphrase entropy is irrelevant — the
606
+ * keyfile provides 512 bits regardless. The passphrase then acts only as a
607
+ * second authentication factor.
608
+ *
609
+ * @param words Number of words (minimum 5 = ~51 bits; default 7 = ~72 bits).
610
+ * @returns A space-separated passphrase string, e.g. "apple cargo drum fence grain helm iris"
611
+ *
612
+ * @example
613
+ * const pass = SPD.generatePassphrase();
614
+ * // → "timber rivet angle plumb comet drift spark"
615
+ * const spd = new SPD();
616
+ * await spd.setPassKey(pass);
617
+ */
618
+ static generatePassphrase(words?: number): string;
619
+ /**
620
+
621
+ /**
622
+ * Acquires an advisory lock for `filePath` by creating `filePath.lock`.
623
+ * The lock file contains JSON with PID and timestamp so stale locks
624
+ * (process dead or >30 s old) are automatically broken.
625
+ *
626
+ * If the lock is held by a live process, retries every 50 ms until
627
+ * `lockTimeoutMs` has elapsed (default 5 000 ms) before throwing `ESPD_LOCKED`.
628
+ */
629
+ static acquireLock(filePath: string, lockTimeoutMs?: number): void;
630
+ /** Releases the advisory lock for `filePath` (deletes `filePath.lock`). */
631
+ static releaseLock(filePath: string): void;
632
+ /** Path of the sequence sidecar file for `filePath`. */
633
+ private static seqPath;
634
+ /**
635
+ * Reads the last-seen write counter from the `.seq` sidecar.
636
+ * Returns -1 if no sidecar exists (first load — any counter is accepted).
637
+ */
638
+ private static readSeqCounter;
639
+ /** Writes the new write counter to the `.seq` sidecar (atomic rename). */
640
+ private static writeSeqCounter;
641
+ /**
642
+ * Verifies that a file's embedded write counter is ≥ the last-seen counter
643
+ * stored in the `.seq` sidecar. Call after key derivation (needs plaintext
644
+ * meta) but before returning the loaded SPD instance.
645
+ * Throws if the counter indicates a rollback attack.
646
+ */
647
+ private static checkRollback;
148
648
  saveToFile(outputPath: string, passcode: string): Promise<void>;
149
649
  /**
150
650
  * Saves to file using streaming I/O — supports files larger than 2 GB.
151
651
  * Format: [8B LE uint64 plaintext length][64B HMAC-SHA3-512][zlib(plaintext)]
152
652
  *
153
- * Also writes a `.spdi` index sidecar alongside the output file that allows
154
- * `SPD.getEntry()` to locate entries without decrypting the whole file.
653
+ * Embeds a tail index (SPDx) for fast on-demand `getEntry()` lookups.
654
+ * Acquires an advisory lock on `outputPath` and releases it after save.
655
+ *
656
+ * @param onProgress Optional callback — called with (bytesWritten, totalBytes)
657
+ * as plaintext bytes are fed through the deflate pipeline.
155
658
  */
156
- saveToFileStreaming(outputPath: string, passcode: string): Promise<void>;
659
+ saveToFileStreaming(outputPath: string, passcode: string, onProgress?: (bytesWritten: number, totalBytes: number) => void): Promise<void>;
157
660
  /**
158
661
  * Computes the decompressed byte offset of each entry within a binary plaintext buffer.
159
662
  * The offset points to the start of that entry's nameLen byte.
160
663
  */
161
664
  private static buildOffsetIndex;
162
665
  /**
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]
666
+ * Appends the entry offset index to the end of an already-written .spd file.
667
+ * Tail format: [32B HMAC-SHA3-256 of body][body: N×(1B nameLen + name + 8B offset)][8B indexStart][4B "SPDx"]
668
+ * The indexStart is the byte offset from the start of the file to the 32B HMAC.
167
669
  */
168
- private static writeIndexFile;
670
+ private static embedIndexInFile;
169
671
  /**
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).
672
+ * Writes a `.spdi` sidecar index file (same body format as the embedded tail
673
+ * but stored externally so it can be pre-warmed without modifying the `.spd`).
674
+ * Format: [4B "SPDI"][32B HMAC-SHA3-256 of body][body]
675
+ */
676
+ private static writeSidecarIndex;
677
+ /**
678
+ * Reads and validates the embedded tail index from a `.spd` file.
679
+ * Returns null if the file doesn't exist, has no embedded index, or the HMAC fails (falls back to scan).
172
680
  */
173
681
  private static readIndexFile;
174
682
  saveData(passcode?: string): Promise<string>;
@@ -183,8 +691,13 @@ declare class SPD {
183
691
  * - Rounded up to the nearest 64 KB boundary for alignment
184
692
  */
185
693
  saveDataChunked(passcode: string, chunkSize?: number): Promise<string[]>;
186
- static loadFromFile(filePath: string, passcode: string): Promise<SPD>;
187
- static loadFromFileStreaming(filePath: string, passcode: string): Promise<SPD>;
694
+ static loadFromFile(filePath: string, passcode: string, verifyKey?: Uint8Array): Promise<SPD>;
695
+ static loadFromFileStreaming(filePath: string, passcode: string, onProgress?: (bytesRead: number, totalBytes: number) => void, verifyKey?: Uint8Array): Promise<SPD>;
696
+ /**
697
+ * Auto-destruct helper: reads/increments the open count sidecar (.ocount).
698
+ * If `maxOpenCount` is set and the new count exceeds it, wipes the file and throws.
699
+ */
700
+ private static checkAndIncrementOpenCount;
188
701
  /**
189
702
  * True constant-memory streaming extract from a binary SPD file.
190
703
  *
@@ -211,7 +724,7 @@ declare class SPD {
211
724
  * console.log(name, typeof value);
212
725
  * });
213
726
  */
214
- static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
727
+ static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void, onProgress?: (bytesRead: number, totalBytes: number) => void): Promise<void>;
215
728
  /**
216
729
  * On-demand disk lookup — decrypts and returns a single named entry from a
217
730
  * binary `.spd` file (written by `saveToFileStreaming`) without loading the
@@ -227,13 +740,48 @@ declare class SPD {
227
740
  * RAM usage is bounded to ~one entry's worth of bytes at any time.
228
741
  */
229
742
  static getEntry(filePath: string, passcode: string, name: string): Promise<SPDGetEntryResult>;
743
+ /**
744
+ * Reads file metadata without any key derivation or decryption.
745
+ *
746
+ * For binary `.spd` files (written by `saveToFileStreaming` / `SPDWriter`):
747
+ * reads the 72-byte header + decompresses just the meta block.
748
+ * For JSON `.spd` files (written by `saveToFile`):
749
+ * decompresses and parses the outer JSON + payload JSON.
750
+ *
751
+ * No passcode required. Returns format, version, entry count, Argon2 params,
752
+ * file size, and whether a tail index (SPDx) is embedded.
753
+ */
754
+ static inspect(filePath: string): Promise<SPDInspectResult>;
755
+ /**
756
+ * Verifies file integrity (MAC check) without decrypting any entry values.
757
+ * Derives keys from passcode, verifies the HMAC over the entire plaintext,
758
+ * and returns a result object. Does not call any decrypt operation on entries.
759
+ *
760
+ * Useful for backup health checks: fast, safe, no plaintext produced.
761
+ */
762
+ static verify(filePath: string, passcode: string): Promise<SPDVerifyResult>;
230
763
  static loadFromString(data: string, passcode: string): Promise<SPD>;
231
764
  static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
232
765
  static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number, hashLen?: number): Promise<PBKResult>;
233
766
  static decryptSalt(encryptedSalt: number[], saltNonce: number[], wrapSalt: number[], passcode: string, memory?: number, time?: number): Promise<Uint8Array>;
234
- static encryptSalt(salt: Uint8Array, passcode: string): Promise<EncryptedSaltResult>;
767
+ static encryptSalt(salt: Uint8Array, passcode: string, memory?: number, time?: number): Promise<EncryptedSaltResult>;
235
768
  static toBase64(data: Uint8Array | Buffer): string;
236
769
  static fromBase64(data: string): Uint8Array;
770
+ /**
771
+ * Build a SHA3-256 Merkle tree over an array of leaf hashes (hex strings).
772
+ * Returns the root hash as a hex string. Empty input returns the hash of "".
773
+ */
774
+ private static buildMerkleRoot;
775
+ private static computeCounterHMAC;
776
+ private _applyRatchet;
777
+ /** When ratchet is enabled, rotate to a fresh salt+keys before each save,
778
+ * re-encrypting all entries under the new key.
779
+ * Forward secrecy is achieved because each save uses a different salt.
780
+ */
781
+ private _maybeRatchetSalt;
782
+ private _maybeRotateEpoch;
783
+ /** Get the effective AEAD and MAC keys (handles blinding). */
784
+ private _getKeys;
237
785
  private buildSerializedPayload;
238
786
  private static parseSerializedPayload;
239
787
  private buildBinaryPayload;
@@ -243,6 +791,64 @@ declare class SPD {
243
791
  private isCollectionType;
244
792
  private convertInputToString;
245
793
  private convertStringToInput;
794
+ /**
795
+ * Compares two SPD files by entry name and hash — no entry decryption.
796
+ * Only key derivation is performed (to verify MACs before trusting hashes).
797
+ *
798
+ * @returns `{added, removed, modified}` arrays of sanitized entry names.
799
+ * `added` — names present in `fileB` but not `fileA`
800
+ * `removed` — names present in `fileA` but not `fileB`
801
+ * `modified` — names present in both but with different ciphertext hashes
802
+ */
803
+ static diff(fileA: string, fileB: string, passcode: string): Promise<SPDDiffResult>;
804
+ /**
805
+ * Merges two SPD files into a new in-memory instance.
806
+ * - `'a-wins'` : conflicting entries keep file A's version.
807
+ * - `'b-wins'` : conflicting entries keep file B's version.
808
+ * - `'newer'` : keeps the version from the file with the higher `writeCounter`
809
+ * (file-level granularity, not per-entry).
810
+ *
811
+ * The returned instance uses file A's key material. Call `saveToFile` /
812
+ * `saveToFileStreaming` to persist.
813
+ */
814
+ static merge(fileA: string, fileB: string, passcode: string, options?: SPDMergeOptions): Promise<SPD>;
815
+ /**
816
+ * Attempts partial recovery of a corrupt SPD file.
817
+ * Reads entries sequentially until the first parse/decrypt failure, then saves
818
+ * whatever was recovered to `outputPath`.
819
+ *
820
+ * Returns `{recovered, skipped}` counts. If the file is a valid JSON payload
821
+ * it uses the JSON loader; otherwise it attempts binary streaming entry-by-entry.
822
+ */
823
+ static repair(filePath: string, passcode: string, outputPath: string): Promise<SPDRepairResult>;
824
+ /**
825
+ * Runs an in-memory self-test and returns timing/throughput numbers.
826
+ * Useful for choosing `argon2Memory`/`argon2Time` on the target hardware.
827
+ *
828
+ * Does NOT write any files or use any persistent state.
829
+ */
830
+ static benchmark(): Promise<SPDBenchmarkResult>;
831
+ /**
832
+ * Watches `filePath` for external changes and calls `cb` with the freshly
833
+ * loaded SPD instance and a diff report each time the file is modified.
834
+ *
835
+ * ```ts
836
+ * const stop = SPD.watch('/data/secrets.spd', 'pass', (spd, diff) => {
837
+ * console.log('added:', diff.added);
838
+ * });
839
+ * // … later:
840
+ * stop();
841
+ * ```
842
+ *
843
+ * - Uses `fs.watchFile` (stat polling, 1 s interval) so it works on NFS /
844
+ * Docker volumes where `fs.watch` inotify events are unreliable.
845
+ * - Re-loads via `SPD.loadFromFile` (JSON) with a `SPD.loadFromFileStreaming`
846
+ * fallback for binary files.
847
+ * - If the reload throws, the error is swallowed and `cb` is **not** called
848
+ * (avoids crashing the host process on transient FS errors).
849
+ * - Returns a `stop()` function; call it to un-watch and stop polling.
850
+ */
851
+ static watch(filePath: string, passcode: string, cb: (spd: SPD, diff: SPDDiffResult) => void): () => void;
246
852
  }
247
853
 
248
854
  /**
@@ -336,8 +942,8 @@ declare class SPDVault {
336
942
  * await writer.addEntry('config', { theme: 'dark' });
337
943
  * await writer.finalize();
338
944
  *
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.
945
+ * After finalize(), an index tail is embedded in the file itself so that
946
+ * `SPD.getEntry()` can locate entries without a full scan (no sidecar file).
341
947
  */
342
948
 
343
949
  declare class SPDWriter {
@@ -374,7 +980,307 @@ declare class SPDWriter {
374
980
  destroy(): void;
375
981
  /** Write bytes to the deflate stream and update the rolling HMAC + offset. */
376
982
  private writeToPlaintext;
377
- private writeIndexSidecar;
983
+ /**
984
+ * Appends the entry offset index to the end of the output file.
985
+ * Format: [32B HMAC-SHA3-256 of body][body][8B indexStart][4B "SPDx"]
986
+ * NOTE: macKey is still valid here — zeroed by finalize() AFTER this call.
987
+ */
988
+ private embedIndexTail;
989
+ }
990
+
991
+ /**
992
+ * SPDTransport — authenticated, forward-secret client↔server secure channel.
993
+ *
994
+ * ## Protocol versions
995
+ *
996
+ * V1 (original): ephemeral Curve25519 ECDH only
997
+ * V2 (v29+): hybrid Curve25519 + ML-KEM-768, ML-DSA-65 certificate pinning,
998
+ * PSK session resumption
999
+ *
1000
+ * ## What it provides
1001
+ *
1002
+ * - **Ephemeral ECDH handshake** (Curve25519 via libsodium `crypto_kx`) so
1003
+ * every session produces a fresh pair of symmetric session keys.
1004
+ * - **ML-KEM-768 hybrid**: client additionally encapsulates to the server's
1005
+ * ML-KEM public key; the KEM shared secret is XOR-combined with the ECDH
1006
+ * secret so that breaking EITHER primitive does not compromise the session.
1007
+ * - **ML-DSA-65 certificate pinning**: the server's long-term identity can
1008
+ * include an ML-DSA-65 signing key pair; clients pin the ML-DSA public key
1009
+ * and the server signs each ServerHello, preventing MitM.
1010
+ * - **PSK session resumption**: a 32-byte pre-shared key can be mixed into
1011
+ * the session key derivation, allowing clients to resume a prior session
1012
+ * with an additional secret layer.
1013
+ * - **Forward secrecy**: all ephemeral key material is zeroed after handshake.
1014
+ * - **Two independent session streams**: `clientToServer` and `serverToClient`,
1015
+ * each with a monotonic 64-bit counter mixed into the XChaCha20-Poly1305
1016
+ * nonce — prevents nonce reuse and blocks replay attacks.
1017
+ * - **Message authentication**: every frame carries a Poly1305 tag.
1018
+ * - **Optional SPD payload wrapping**: `wrapSPD` / `unwrapSPD` let you
1019
+ * transmit an in-memory SPD binary payload over the channel.
1020
+ *
1021
+ * ## Threat model
1022
+ *
1023
+ * ✅ Network eavesdropper — cannot read any session payload
1024
+ * ✅ Replay attack — monotonic counter prevents replaying old frames
1025
+ * ✅ Past-session decryption after key compromise — forward secrecy
1026
+ * ✅ Active MitM — ML-DSA-65 certificate pinning + ECDH server auth
1027
+ * ✅ Harvest-now-decrypt-later (quantum) — ML-KEM-768 hybrid
1028
+ * ✅ PSK gives additional session binding layer for high-security deployments
1029
+ *
1030
+ * ## V2 ClientHello wire format (1249 bytes)
1031
+ *
1032
+ * [1B version ] = TRANSPORT_VERSION_V2 (2)
1033
+ * [32B ecdhEphemeralPub] client's ephemeral Curve25519 public key
1034
+ * [32B sessionSalt ] random HKDF salt (public)
1035
+ * [1184B kemEphemeralPub] client's ephemeral ML-KEM-768 public key
1036
+ *
1037
+ * ## V2 ServerHello wire format (1 + 1088 + 3309 = 4398 bytes)
1038
+ *
1039
+ * [1B version ] = TRANSPORT_VERSION_V2 (2)
1040
+ * [1088B kemCiphertext ] ML-KEM-768 ciphertext (encapsulated by server to client's kemPub)
1041
+ * [3309B signature ] ML-DSA-65 signature over (kemCiphertext || sessionSalt || ecdhEphemeralPub)
1042
+ * — present only if server has a signing key; all zeros if absent
1043
+ *
1044
+ * ## Usage
1045
+ *
1046
+ * ```ts
1047
+ * // ── Server side ──────────────────────────────────────────────────────────
1048
+ * const serverIdentity = await SPDTransport.generateServerIdentity();
1049
+ * // Optional: add ML-DSA-65 signing
1050
+ * const signingPair = SPDTransport.generateCertKeyPair();
1051
+ * serverIdentity.signingPrivKey = signingPair.privateKey;
1052
+ * serverIdentity.signingPubKey = signingPair.publicKey;
1053
+ *
1054
+ * const { session, serverHello } = await SPDTransport.serverAccept(serverIdentity, clientHello);
1055
+ * session.decrypt(encryptedFrame);
1056
+ * session.encrypt(responseBytes);
1057
+ *
1058
+ * // ── Client side ──────────────────────────────────────────────────────────
1059
+ * const { session, clientHello } = await SPDTransport.clientConnect(serverPublicKey, {
1060
+ * pinnedSigningKey: serverSigningPubKey, // optional cert pin
1061
+ * psk: sharedSecret32, // optional PSK
1062
+ * });
1063
+ * send(clientHello);
1064
+ * const serverHello = receive(); // get serverHello from server
1065
+ * await session.processServerHello(serverHello);
1066
+ * session.encrypt(payload);
1067
+ *
1068
+ * // ── PSK session resumption ────────────────────────────────────────────────
1069
+ * const psk = session.exportPSK(); // save after first handshake
1070
+ * // next connection:
1071
+ * const { session: session2, clientHello: ch2 } = await SPDTransport.clientConnect(
1072
+ * serverPublicKey, { psk }
1073
+ * );
1074
+ * ```
1075
+ */
1076
+
1077
+ interface SPDServerIdentity {
1078
+ /** Curve25519 public key — distribute to clients out-of-band. */
1079
+ publicKey: Uint8Array;
1080
+ /** Curve25519 private key — keep secret, never transmit. */
1081
+ privateKey: Uint8Array;
1082
+ /** Optional ML-DSA-65 signing private key for certificate authentication. */
1083
+ signingPrivKey?: Uint8Array;
1084
+ /** Optional ML-DSA-65 signing public key — pin this on the client side. */
1085
+ signingPubKey?: Uint8Array;
1086
+ }
1087
+ /** Options for `clientConnect`. */
1088
+ interface SPDClientConnectOptions {
1089
+ /**
1090
+ * Use V2 protocol (ML-KEM hybrid). Default: true when server supports it.
1091
+ * Set to false to force V1 (legacy interop).
1092
+ */
1093
+ v2?: boolean;
1094
+ /**
1095
+ * Pinned ML-DSA-65 public key. When set, the client will verify the
1096
+ * server's signature in the ServerHello and reject if verification fails.
1097
+ */
1098
+ pinnedSigningKey?: Uint8Array;
1099
+ /**
1100
+ * Pre-shared key (32 bytes) mixed into session key derivation.
1101
+ * Provides an extra secret layer for PSK session resumption.
1102
+ */
1103
+ psk?: Uint8Array;
1104
+ }
1105
+ /**
1106
+ * A live, established session between two peers.
1107
+ * Maintains independent monotonic counters for each direction.
1108
+ */
1109
+ interface SPDSession {
1110
+ /**
1111
+ * Encrypt `plaintext` for transmission in this session's direction.
1112
+ * Returns a self-contained frame: [1B version][8B counter][24B nonce][ciphertext+tag]
1113
+ */
1114
+ encrypt(plaintext: Uint8Array | Buffer): Buffer;
1115
+ /**
1116
+ * Decrypt a frame received from the remote peer.
1117
+ * Throws if the tag is invalid, the version is wrong, or the counter is not
1118
+ * strictly greater than the last seen counter (replay protection).
1119
+ */
1120
+ decrypt(frame: Buffer): Buffer;
1121
+ /**
1122
+ * Serialize `spd` to its binary format in memory and encrypt the result as a
1123
+ * single transport frame. The SPD is NOT written to disk.
1124
+ */
1125
+ wrapSPD(spd: SPD, passcode: string): Promise<Buffer>;
1126
+ /**
1127
+ * Decrypt a frame produced by `wrapSPD` and load the embedded SPD binary.
1128
+ * Returns a ready-to-use SPD instance.
1129
+ */
1130
+ unwrapSPD(frame: Buffer, passcode: string): Promise<SPD>;
1131
+ /**
1132
+ * Export a 32-byte PSK derived from the session key material.
1133
+ * Store this to enable fast PSK resumption on the next connection.
1134
+ */
1135
+ exportPSK(): Uint8Array;
1136
+ /** Zero all session key material. Call when the session is complete. */
1137
+ destroy(): void;
1138
+ }
1139
+ declare class SPDTransport {
1140
+ /**
1141
+ * Generate a long-term Curve25519 server identity key pair.
1142
+ * Store `privateKey` securely (e.g. in a hardware key store or SPD vault).
1143
+ * Distribute `publicKey` to clients out-of-band.
1144
+ */
1145
+ static generateServerIdentity(): Promise<SPDServerIdentity>;
1146
+ /**
1147
+ * Generate an ML-DSA-65 certificate key pair for server signing.
1148
+ * Set `signingPrivKey` + `signingPubKey` on the server identity, and
1149
+ * distribute `publicKey` to clients as a pinned cert.
1150
+ */
1151
+ static generateCertKeyPair(): {
1152
+ privateKey: Uint8Array;
1153
+ publicKey: Uint8Array;
1154
+ };
1155
+ /**
1156
+ * Save a server identity to files:
1157
+ * `<path>.pub` (Curve25519 public key, safe to distribute)
1158
+ * `<path>.key` (Curve25519 private key, mode 0600)
1159
+ * `<path>.cert` (ML-DSA-65 public key, safe to distribute; optional)
1160
+ * `<path>.csig` (ML-DSA-65 private key, mode 0600; optional)
1161
+ */
1162
+ static saveServerIdentity(basePath: string, identity: SPDServerIdentity): void;
1163
+ /**
1164
+ * Load a server identity previously saved with `saveServerIdentity`.
1165
+ */
1166
+ static loadServerIdentity(basePath: string): SPDServerIdentity;
1167
+ /**
1168
+ * Initiate a new V2 session as the client (ML-KEM-768 hybrid + optional PSK + cert pinning).
1169
+ *
1170
+ * Returns `{ session, clientHello }`. After receiving the ServerHello, call
1171
+ * `await session.processServerHello(serverHello)` to complete the handshake.
1172
+ *
1173
+ * @param serverPublicKey The server's Curve25519 long-term public key.
1174
+ * @param options Optional: pinnedSigningKey, psk, v2 flag.
1175
+ */
1176
+ static clientConnect(serverPublicKey: Uint8Array, options?: SPDClientConnectOptions): Promise<{
1177
+ session: SPDClientHandshake;
1178
+ clientHello: Buffer;
1179
+ }>;
1180
+ /** V1 legacy clientConnect */
1181
+ private static _clientConnectV1;
1182
+ /**
1183
+ * Accept an incoming client connection (supports both V1 and V2 ClientHello).
1184
+ *
1185
+ * For V2: returns `{ session, serverHello }`. Send `serverHello` to the client
1186
+ * so it can finalize key derivation.
1187
+ *
1188
+ * @param serverIdentity The server's long-term key pair.
1189
+ * @param clientHello The raw bytes sent by the client.
1190
+ * @param psk Optional PSK (must match client's PSK for session to work).
1191
+ */
1192
+ static serverAccept(serverIdentity: SPDServerIdentity, clientHello: Buffer, psk?: Uint8Array): Promise<{
1193
+ session: SPDSession;
1194
+ serverHello?: Buffer;
1195
+ }>;
1196
+ /** V1 legacy serverAccept */
1197
+ private static _serverAcceptV1;
1198
+ /** V2 serverAccept: hybrid ECDH + ML-KEM, optional ML-DSA-65 signing */
1199
+ private static _serverAcceptV2;
1200
+ /**
1201
+ * Convenience: generate a random 32-byte session token suitable for use as
1202
+ * an application-level session ID (e.g. HTTP cookie, WebSocket session key).
1203
+ */
1204
+ static generateSessionToken(): string;
1205
+ /**
1206
+ * Verify that a ClientHello buffer has the correct structure and version
1207
+ * without performing the full key exchange.
1208
+ */
1209
+ static validateClientHello(clientHello: Buffer): boolean;
1210
+ }
1211
+ /**
1212
+ * Intermediate object returned by `clientConnect` (V2).
1213
+ * Call `processServerHello(serverHello)` to finalize key derivation.
1214
+ * After that, use `encrypt` / `decrypt` normally.
1215
+ */
1216
+ interface SPDClientHandshake extends SPDSession {
1217
+ /**
1218
+ * Process the ServerHello received from the server and finalize session keys.
1219
+ * Must be called before `encrypt` or `decrypt`.
1220
+ * @param serverHello Raw bytes received from `serverAccept`.
1221
+ */
1222
+ processServerHello(serverHello: Buffer): void;
1223
+ }
1224
+
1225
+ /**
1226
+ * SPDShamir — Shamir's Secret Sharing over GF(256).
1227
+ *
1228
+ * Splits a byte-array secret into `n` shares, any `k` of which can
1229
+ * reconstruct the original (k-of-n threshold scheme).
1230
+ *
1231
+ * ## Security properties
1232
+ * - Information-theoretically secure: fewer than `k` shares reveal
1233
+ * nothing about the secret.
1234
+ * - Each share is the same length as the secret + 1 byte (x-coordinate).
1235
+ * - Arithmetic is done in GF(2^8) with the irreducible polynomial
1236
+ * x^8 + x^4 + x^3 + x + 1 (0x11b — same as AES).
1237
+ *
1238
+ * ## Usage
1239
+ *
1240
+ * ```ts
1241
+ * import { SPDShamir } from 'spd-lib-ts';
1242
+ *
1243
+ * const secret = Buffer.from('my 32-byte secret key material!');
1244
+ * const shares = SPDShamir.split(secret, 5, 3); // 5 shares, 3 required
1245
+ * const rebuilt = SPDShamir.combine(shares.slice(1, 4)); // any 3
1246
+ * // rebuilt.equals(secret) === true
1247
+ * ```
1248
+ *
1249
+ * ## Wire format
1250
+ *
1251
+ * Each `SPDShamirShare` is a `Uint8Array` where:
1252
+ * - `share[0]` = x-coordinate (1..255, never 0)
1253
+ * - `share[1..]` = f(x) evaluated for each byte of the secret
1254
+ *
1255
+ * Shares may be base64-encoded for storage / transmission.
1256
+ */
1257
+ type SPDShamirShare = Uint8Array;
1258
+ declare class SPDShamir {
1259
+ /**
1260
+ * Split `secret` into `n` shares with threshold `k`.
1261
+ * Any `k` shares can reconstruct the secret; fewer than `k` reveal nothing.
1262
+ *
1263
+ * @param secret The byte-array to protect (any length > 0).
1264
+ * @param n Total number of shares to generate (2 ≤ n ≤ 255).
1265
+ * @param k Reconstruction threshold (2 ≤ k ≤ n).
1266
+ * @returns Array of `n` shares, each with `x` in `[0]` and `f(x)` in `[1..]`.
1267
+ */
1268
+ static split(secret: Uint8Array | Buffer, n: number, k: number): SPDShamirShare[];
1269
+ /**
1270
+ * Combine `k` (or more) shares to reconstruct the original secret.
1271
+ *
1272
+ * @param shares Array of at least `k` shares. Order does not matter.
1273
+ * @returns The reconstructed secret as a `Uint8Array`.
1274
+ */
1275
+ static combine(shares: SPDShamirShare[]): Uint8Array;
1276
+ /**
1277
+ * Encode a share to a base64url string for safe storage/transmission.
1278
+ */
1279
+ static encodeShare(share: SPDShamirShare): string;
1280
+ /**
1281
+ * Decode a base64url-encoded share back to `Uint8Array`.
1282
+ */
1283
+ static decodeShare(encoded: string): SPDShamirShare;
378
1284
  }
379
1285
 
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 };
1286
+ export { ARGON2_MEMORY_HIGH, ARGON2_MEMORY_PARANOID, ARGON2_TIME_HIGH, ARGON2_TIME_PARANOID, type DataInput, type EncryptedDataEntry, type EncryptedSaltResult, type HashAlgorithm, type PBKResult, type PQCKey, type PQCKeyResult, SPD, type SPDBenchmarkResult, type SPDChunkManifest, type SPDClientConnectOptions, type SPDClientHandshake, type SPDDiffResult, type SPDGetEntryResult, type SPDIndexEntry, type SPDInspectResult, type SPDKeyProfile, type SPDKeyProvider, SPDLegacy, type SPDLegacyPayload, type SPDLogEvent, type SPDMergeOptions, type SPDPayload, type SPDRepairResult, type SPDServerIdentity, type SPDSession, SPDShamir, type SPDShamirShare, type SPDSigningKeyPair, type SPDSnapshot, SPDTransport, SPDVault, type SPDVerifyResult, SPDWriter, type SPDWriterOptions, SPDLegacy as SPD_LEG, SPDVault as SPD_Vault, type SerializedDataEntry, type SerializedWrappedPayload, type SupportedDataType, type SupportedValue, type TypedArray, type WrappedPayload };