spd-lib 1.4.2 → 1.4.3

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.mts CHANGED
@@ -1367,111 +1367,185 @@ declare class SPDShamir {
1367
1367
  }
1368
1368
 
1369
1369
  /**
1370
- * SPDHandshake — Ephemeral X25519 ECDH key agreement for SPD sessions.
1370
+ * SPDHandshake — Hybrid post-quantum ephemeral key agreement for SPD sessions.
1371
1371
  *
1372
- * Aligns with SPD v29 security model:
1373
- * - X25519 ECDH ephemeral keypair — perfect forward secrecy
1374
- * - Private key blinded in memory (XOR mask) between create() and derive()
1375
- * - HKDF-SHA-512 with SHA3-512 transcript hash as salt (domain-separated,
1376
- * consistent with SPD's spd-aead-key-v1 / spd-mac-key-v1 pattern)
1377
- * - Session nonce mixed into HKDF — prevents cross-session reuse attacks
1378
- * - CMT-4 key commitment: SHA3-256(sessionKeysessionNonce) returned so
1379
- * both sides can verify they derived the same key without revealing it
1380
- * - timingSafeEqual for all public key comparisons
1381
- * - All key material zeroed immediately after use
1372
+ * ## Cryptographic design
1373
+ *
1374
+ * Uses a **X25519 + ML-KEM-768 hybrid** construction:
1375
+ *
1376
+ * x25519_ss = X25519(myPriv, theirPub) — classical ECDH
1377
+ * mlkem_ss = ML-KEM-768 encap/decap — post-quantum KEM (NIST FIPS 203)
1378
+ * transcript = SHA3-512(loNoncehiNonce) session-binding salt
1379
+ * sessionKey = HKDF-SHA-512(
1380
+ * IKM = x25519_ss mlkem_ss,
1381
+ * salt = transcript,
1382
+ * info = 'spd-hs-session-v1'
1383
+ * )
1384
+ *
1385
+ * Breaking the session key requires breaking **both** X25519 (hard classically)
1386
+ * **and** ML-KEM-768 (hard for quantum computers) simultaneously.
1387
+ *
1388
+ * ## Security properties
1389
+ *
1390
+ * | Property | Mechanism |
1391
+ * |---------------------------------|--------------------------------------------------------|
1392
+ * | Classical hardness | X25519 ECDH — ~2^128 classical operations |
1393
+ * | Quantum hardness | ML-KEM-768 — NIST FIPS 203, ~2^178 quantum ops |
1394
+ * | Perfect forward secrecy | All keypairs ephemeral, zeroed after derive() |
1395
+ * | Memory safety | Private keys XOR-blinded immediately after creation |
1396
+ * | Key commitment (CMT-4) | SHA3-256(sessionKey ∥ transcript) — verify before use |
1397
+ * | Cross-session binding | 32-byte CSPRNG nonce per session mixed into HKDF |
1398
+ * | Domain separation | Distinct HKDF info strings per key purpose |
1399
+ * | Timing safety | timingSafeEqual for all sensitive comparisons |
1400
+ * | Low-order point rejection | All-zero DH output rejected before HKDF |
1401
+ * | Input validation | Type + length checked on all inputs |
1402
+ * | Zero-on-free | All key material zeroed explicitly via sodium.memzero |
1403
+ *
1404
+ * ## Handshake flow
1405
+ *
1406
+ * The initiator (e.g. client) drives ML-KEM encapsulation:
1407
+ *
1408
+ * ```
1409
+ * INITIATOR RESPONDER
1410
+ * ─────────────────────────────────────────────────────
1411
+ * hs = SPDHandshake.createInitiator()
1412
+ * send → { x25519Pub, kemPub, nonce }
1413
+ * hs = await SPDHandshake.createResponder(
1414
+ * initiatorHello)
1415
+ * send → { x25519Pub, kemCt, nonce }
1416
+ * result = await hs.finalizeInitiator(
1417
+ * responderHello)
1418
+ * result = hs.responderResult()
1419
+ * // result.commitment === responderResult.commitment
1420
+ * // exchange commitments to confirm matching keys
1421
+ * ```
1382
1422
  *
1383
1423
  * ## Usage
1384
1424
  *
1385
1425
  * ```ts
1386
- * // ── Server ──────────────────────────────────────────────────────
1387
- * const server = SPDHandshake.create();
1388
- * // send to client: { publicKey: server.publicKey, nonce: server.sessionNonce }
1426
+ * import { SPDHandshake, SPD } from 'spd-lib';
1389
1427
  *
1390
- * // ── Client (after receiving server publicKey + nonce) ────────────
1391
- * const client = SPDHandshake.create();
1392
- * // send to server: { publicKey: client.publicKey, nonce: client.sessionNonce }
1393
- * const clientResult = client.derive(server.publicKey, server.sessionNonce);
1428
+ * // ── Initiator (client) ───────────────────────────────────────────────────────
1429
+ * const init = await SPDHandshake.createInitiator();
1430
+ * socket.send(JSON.stringify(init.hello)); // { x25519Pub, kemPub, nonce }
1394
1431
  *
1395
- * // ── Server (after receiving client publicKey + nonce) ────────────
1396
- * const serverResult = server.derive(client.publicKey, client.sessionNonce);
1432
+ * const respHello = JSON.parse(await socket.recv());
1433
+ * const initResult = await init.finalizeInitiator(respHello);
1397
1434
  *
1398
- * // Verify both sides derived the same key (compare commitments over the wire)
1399
- * // clientResult.commitment === serverResult.commitment → true
1435
+ * // ── Responder (server) ───────────────────────────────────────────────────────
1436
+ * const resp = await SPDHandshake.createResponder(initHello);
1437
+ * socket.send(JSON.stringify(resp.hello)); // { x25519Pub, kemCt, nonce }
1438
+ * const respResult = resp.responderResult();
1400
1439
  *
1401
- * // Use session passphrase directly with SPD (already 256-bit entropy)
1402
- * const spd = new SPD();
1403
- * spd.setKeyProfile('standard');
1404
- * await spd.setPassKey(serverResult.sessionKey);
1440
+ * // ── Both sides: verify commitment matches before using key ───────────────────
1441
+ * // exchange initResult.commitment <→ respResult.commitment over the wire
1442
+ * // if they match, both sides have the same session key
1405
1443
  *
1406
- * // Zero the passphrase from memory when done with setup
1407
- * serverResult.destroy();
1444
+ * const spd = new SPD();
1445
+ * spd.setKeyProfile('standard'); // session key is already 256-bit entropy
1446
+ * await spd.setPassKey(initResult.sessionKey);
1447
+ * initResult.destroy(); // zero raw key bytes from memory
1408
1448
  * ```
1409
1449
  */
1450
+ /** Message sent during the handshake exchange. */
1451
+ interface SPDHandshakeHello {
1452
+ /** Base64url-encoded X25519 public key (32 bytes). */
1453
+ x25519Pub: string;
1454
+ /**
1455
+ * Initiator hello: base64url-encoded ML-KEM-768 public key (1184 bytes).
1456
+ * Responder hello: base64url-encoded ML-KEM-768 ciphertext (1088 bytes).
1457
+ */
1458
+ kemData: string;
1459
+ /** Base64url-encoded random session nonce (32 bytes). */
1460
+ nonce: string;
1461
+ }
1410
1462
  /**
1411
- * Result of a completed handshake derivation.
1412
- * Holds the session passphrase and key commitment.
1413
- * Call `destroy()` once the passphrase has been handed to SPD's `setPassKey`.
1463
+ * Completed handshake result.
1464
+ * Call `destroy()` immediately after passing `sessionKey` to `spd.setPassKey()`.
1414
1465
  */
1415
1466
  declare class SPDHandshakeResult {
1416
- /** 64-char hex session passphrase (256 bits). Pass to `spd.setPassKey()`. */
1467
+ /**
1468
+ * 64-character hex session passphrase (256 bits of entropy).
1469
+ * Pass directly to `spd.setPassKey()`.
1470
+ */
1417
1471
  readonly sessionKey: string;
1418
1472
  /**
1419
- * CMT-4 key commitment: SHA3-256(sessionKeyBytes ∥ sessionNonce).
1420
- * Base64url-encoded, 32 bytes. Share over the wire so both parties can
1421
- * verify they derived the same secret without revealing the secret itself.
1473
+ * CMT-4 key commitment: SHA3-256(sessionKeyBytes ∥ transcriptHash).
1474
+ * Base64url-encoded (32 bytes).
1475
+ *
1476
+ * Exchange this with the other party and verify they match BEFORE using
1477
+ * `sessionKey`. If they differ, abort — a MITM substituted a key.
1422
1478
  */
1423
1479
  readonly commitment: string;
1424
1480
  private _raw;
1425
1481
  /** @internal */
1426
- constructor(raw: Buffer, nonce: Buffer);
1427
- /** Zero the raw session key bytes from memory. */
1482
+ constructor(raw: Buffer, transcript: Buffer);
1483
+ /**
1484
+ * Zero the raw session key bytes from memory.
1485
+ * The `sessionKey` hex string may still exist in the JS heap
1486
+ * (strings are immutable in V8); call this as soon as `setPassKey()` is done.
1487
+ */
1428
1488
  destroy(): void;
1429
1489
  }
1430
- /** An ephemeral X25519 handshake participant. */
1431
- declare class SPDHandshake {
1490
+ /** Initiator state returned by `SPDHandshake.createInitiator()`. */
1491
+ declare class SPDHandshakeInitiator {
1492
+ /** Send this to the responder. */
1493
+ readonly hello: SPDHandshakeHello;
1494
+ private _x25519Priv;
1495
+ private _kemSecretMask;
1496
+ private _blindedKemSK;
1497
+ private _nonceRaw;
1498
+ private _done;
1499
+ /** @internal */
1500
+ constructor(x25519PrivRaw: Buffer, x25519PubRaw: Buffer, kemSecretKey: Uint8Array, kemPublicKey: Uint8Array, nonce: Buffer);
1432
1501
  /**
1433
- * Base64url-encoded X25519 public key (32 bytes).
1434
- * Transmit this to the other party.
1502
+ * Complete the initiator side of the handshake using the responder's hello.
1503
+ *
1504
+ * @param responderHello The `hello` object received from the responder.
1505
+ * @returns `SPDHandshakeResult` — call `.destroy()` after `setPassKey()`.
1506
+ * @throws If already called, or if any input is malformed.
1435
1507
  */
1436
- readonly publicKey: string;
1508
+ finalizeInitiator(responderHello: SPDHandshakeHello): Promise<SPDHandshakeResult>;
1509
+ /** Zero all key material. Call if abandoning the handshake. */
1510
+ destroy(): void;
1511
+ }
1512
+ /** Responder state returned by `SPDHandshake.createResponder()`. */
1513
+ declare class SPDHandshakeResponder {
1514
+ /** Send this to the initiator. */
1515
+ readonly hello: SPDHandshakeHello;
1516
+ private _result;
1517
+ /** @internal */
1518
+ constructor(hello: SPDHandshakeHello, result: SPDHandshakeResult);
1437
1519
  /**
1438
- * Cryptographically random session nonce (32 bytes, base64url).
1439
- * Transmit this alongside `publicKey`. Mixed into HKDF to prevent
1440
- * cross-session key reuse even if the same ephemeral keypair were
1441
- * somehow reused.
1520
+ * Return the completed session result.
1521
+ * The responder derives its key during `createResponder()` call this
1522
+ * after sending `hello` to the initiator.
1442
1523
  */
1443
- readonly sessionNonce: string;
1444
- private _blindedPriv;
1445
- private _privMask;
1446
- private _nonceRaw;
1524
+ responderResult(): SPDHandshakeResult;
1525
+ }
1526
+ declare class SPDHandshake {
1447
1527
  private constructor();
1448
1528
  /**
1449
- * Create a new ephemeral participant with a freshly generated X25519 keypair
1450
- * and a random session nonce.
1451
- */
1452
- static create(): SPDHandshake;
1453
- /**
1454
- * Derive the shared session key from the other party's public key and nonce.
1529
+ * Create the initiator side of the handshake.
1455
1530
  *
1456
- * Both parties must call `derive()` with each other's `publicKey` and
1457
- * `sessionNonce`. The resulting `SPDHandshakeResult.commitment` values will
1458
- * match if and only if both sides derived the same key.
1531
+ * Generates an ephemeral X25519 keypair and ML-KEM-768 keypair.
1532
+ * Send `initiator.hello` to the responder.
1459
1533
  *
1460
- * @param theirPublicKey The other party's `publicKey` string.
1461
- * @param theirSessionNonce The other party's `sessionNonce` string.
1462
- * @returns `SPDHandshakeResult` containing the session passphrase and
1463
- * CMT-4 key commitment. Call `.destroy()` after handing the
1464
- * passphrase to `spd.setPassKey()`.
1465
- * @throws If `derive()` has already been called on this instance.
1466
- * @throws If the public key is malformed or the low-order point check fails.
1534
+ * @returns `SPDHandshakeInitiator` instance.
1467
1535
  */
1468
- derive(theirPublicKey: string, theirSessionNonce: string): SPDHandshakeResult;
1536
+ static createInitiator(): Promise<SPDHandshakeInitiator>;
1469
1537
  /**
1470
- * Zero and release all key material.
1471
- * Called automatically by `derive()`. Call manually if you abandon the
1472
- * handshake without completing it.
1538
+ * Create the responder side of the handshake from the initiator's hello.
1539
+ *
1540
+ * Generates an ephemeral X25519 keypair, encapsulates against the
1541
+ * initiator's ML-KEM public key, and derives the session key.
1542
+ * Send `responder.hello` to the initiator.
1543
+ *
1544
+ * @param initiatorHello The `hello` object received from the initiator.
1545
+ * @returns `SPDHandshakeResponder` instance.
1546
+ * @throws If any input is malformed or a low-order point is detected.
1473
1547
  */
1474
- destroy(): void;
1548
+ static createResponder(initiatorHello: SPDHandshakeHello): Promise<SPDHandshakeResponder>;
1475
1549
  }
1476
1550
 
1477
- 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, SPDHandshake, SPDHandshakeResult, 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 };
1551
+ 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, SPDHandshake, type SPDHandshakeHello, SPDHandshakeInitiator, SPDHandshakeResponder, SPDHandshakeResult, 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 };
package/index.d.ts CHANGED
@@ -1367,111 +1367,185 @@ declare class SPDShamir {
1367
1367
  }
1368
1368
 
1369
1369
  /**
1370
- * SPDHandshake — Ephemeral X25519 ECDH key agreement for SPD sessions.
1370
+ * SPDHandshake — Hybrid post-quantum ephemeral key agreement for SPD sessions.
1371
1371
  *
1372
- * Aligns with SPD v29 security model:
1373
- * - X25519 ECDH ephemeral keypair — perfect forward secrecy
1374
- * - Private key blinded in memory (XOR mask) between create() and derive()
1375
- * - HKDF-SHA-512 with SHA3-512 transcript hash as salt (domain-separated,
1376
- * consistent with SPD's spd-aead-key-v1 / spd-mac-key-v1 pattern)
1377
- * - Session nonce mixed into HKDF — prevents cross-session reuse attacks
1378
- * - CMT-4 key commitment: SHA3-256(sessionKeysessionNonce) returned so
1379
- * both sides can verify they derived the same key without revealing it
1380
- * - timingSafeEqual for all public key comparisons
1381
- * - All key material zeroed immediately after use
1372
+ * ## Cryptographic design
1373
+ *
1374
+ * Uses a **X25519 + ML-KEM-768 hybrid** construction:
1375
+ *
1376
+ * x25519_ss = X25519(myPriv, theirPub) — classical ECDH
1377
+ * mlkem_ss = ML-KEM-768 encap/decap — post-quantum KEM (NIST FIPS 203)
1378
+ * transcript = SHA3-512(loNoncehiNonce) session-binding salt
1379
+ * sessionKey = HKDF-SHA-512(
1380
+ * IKM = x25519_ss mlkem_ss,
1381
+ * salt = transcript,
1382
+ * info = 'spd-hs-session-v1'
1383
+ * )
1384
+ *
1385
+ * Breaking the session key requires breaking **both** X25519 (hard classically)
1386
+ * **and** ML-KEM-768 (hard for quantum computers) simultaneously.
1387
+ *
1388
+ * ## Security properties
1389
+ *
1390
+ * | Property | Mechanism |
1391
+ * |---------------------------------|--------------------------------------------------------|
1392
+ * | Classical hardness | X25519 ECDH — ~2^128 classical operations |
1393
+ * | Quantum hardness | ML-KEM-768 — NIST FIPS 203, ~2^178 quantum ops |
1394
+ * | Perfect forward secrecy | All keypairs ephemeral, zeroed after derive() |
1395
+ * | Memory safety | Private keys XOR-blinded immediately after creation |
1396
+ * | Key commitment (CMT-4) | SHA3-256(sessionKey ∥ transcript) — verify before use |
1397
+ * | Cross-session binding | 32-byte CSPRNG nonce per session mixed into HKDF |
1398
+ * | Domain separation | Distinct HKDF info strings per key purpose |
1399
+ * | Timing safety | timingSafeEqual for all sensitive comparisons |
1400
+ * | Low-order point rejection | All-zero DH output rejected before HKDF |
1401
+ * | Input validation | Type + length checked on all inputs |
1402
+ * | Zero-on-free | All key material zeroed explicitly via sodium.memzero |
1403
+ *
1404
+ * ## Handshake flow
1405
+ *
1406
+ * The initiator (e.g. client) drives ML-KEM encapsulation:
1407
+ *
1408
+ * ```
1409
+ * INITIATOR RESPONDER
1410
+ * ─────────────────────────────────────────────────────
1411
+ * hs = SPDHandshake.createInitiator()
1412
+ * send → { x25519Pub, kemPub, nonce }
1413
+ * hs = await SPDHandshake.createResponder(
1414
+ * initiatorHello)
1415
+ * send → { x25519Pub, kemCt, nonce }
1416
+ * result = await hs.finalizeInitiator(
1417
+ * responderHello)
1418
+ * result = hs.responderResult()
1419
+ * // result.commitment === responderResult.commitment
1420
+ * // exchange commitments to confirm matching keys
1421
+ * ```
1382
1422
  *
1383
1423
  * ## Usage
1384
1424
  *
1385
1425
  * ```ts
1386
- * // ── Server ──────────────────────────────────────────────────────
1387
- * const server = SPDHandshake.create();
1388
- * // send to client: { publicKey: server.publicKey, nonce: server.sessionNonce }
1426
+ * import { SPDHandshake, SPD } from 'spd-lib';
1389
1427
  *
1390
- * // ── Client (after receiving server publicKey + nonce) ────────────
1391
- * const client = SPDHandshake.create();
1392
- * // send to server: { publicKey: client.publicKey, nonce: client.sessionNonce }
1393
- * const clientResult = client.derive(server.publicKey, server.sessionNonce);
1428
+ * // ── Initiator (client) ───────────────────────────────────────────────────────
1429
+ * const init = await SPDHandshake.createInitiator();
1430
+ * socket.send(JSON.stringify(init.hello)); // { x25519Pub, kemPub, nonce }
1394
1431
  *
1395
- * // ── Server (after receiving client publicKey + nonce) ────────────
1396
- * const serverResult = server.derive(client.publicKey, client.sessionNonce);
1432
+ * const respHello = JSON.parse(await socket.recv());
1433
+ * const initResult = await init.finalizeInitiator(respHello);
1397
1434
  *
1398
- * // Verify both sides derived the same key (compare commitments over the wire)
1399
- * // clientResult.commitment === serverResult.commitment → true
1435
+ * // ── Responder (server) ───────────────────────────────────────────────────────
1436
+ * const resp = await SPDHandshake.createResponder(initHello);
1437
+ * socket.send(JSON.stringify(resp.hello)); // { x25519Pub, kemCt, nonce }
1438
+ * const respResult = resp.responderResult();
1400
1439
  *
1401
- * // Use session passphrase directly with SPD (already 256-bit entropy)
1402
- * const spd = new SPD();
1403
- * spd.setKeyProfile('standard');
1404
- * await spd.setPassKey(serverResult.sessionKey);
1440
+ * // ── Both sides: verify commitment matches before using key ───────────────────
1441
+ * // exchange initResult.commitment <→ respResult.commitment over the wire
1442
+ * // if they match, both sides have the same session key
1405
1443
  *
1406
- * // Zero the passphrase from memory when done with setup
1407
- * serverResult.destroy();
1444
+ * const spd = new SPD();
1445
+ * spd.setKeyProfile('standard'); // session key is already 256-bit entropy
1446
+ * await spd.setPassKey(initResult.sessionKey);
1447
+ * initResult.destroy(); // zero raw key bytes from memory
1408
1448
  * ```
1409
1449
  */
1450
+ /** Message sent during the handshake exchange. */
1451
+ interface SPDHandshakeHello {
1452
+ /** Base64url-encoded X25519 public key (32 bytes). */
1453
+ x25519Pub: string;
1454
+ /**
1455
+ * Initiator hello: base64url-encoded ML-KEM-768 public key (1184 bytes).
1456
+ * Responder hello: base64url-encoded ML-KEM-768 ciphertext (1088 bytes).
1457
+ */
1458
+ kemData: string;
1459
+ /** Base64url-encoded random session nonce (32 bytes). */
1460
+ nonce: string;
1461
+ }
1410
1462
  /**
1411
- * Result of a completed handshake derivation.
1412
- * Holds the session passphrase and key commitment.
1413
- * Call `destroy()` once the passphrase has been handed to SPD's `setPassKey`.
1463
+ * Completed handshake result.
1464
+ * Call `destroy()` immediately after passing `sessionKey` to `spd.setPassKey()`.
1414
1465
  */
1415
1466
  declare class SPDHandshakeResult {
1416
- /** 64-char hex session passphrase (256 bits). Pass to `spd.setPassKey()`. */
1467
+ /**
1468
+ * 64-character hex session passphrase (256 bits of entropy).
1469
+ * Pass directly to `spd.setPassKey()`.
1470
+ */
1417
1471
  readonly sessionKey: string;
1418
1472
  /**
1419
- * CMT-4 key commitment: SHA3-256(sessionKeyBytes ∥ sessionNonce).
1420
- * Base64url-encoded, 32 bytes. Share over the wire so both parties can
1421
- * verify they derived the same secret without revealing the secret itself.
1473
+ * CMT-4 key commitment: SHA3-256(sessionKeyBytes ∥ transcriptHash).
1474
+ * Base64url-encoded (32 bytes).
1475
+ *
1476
+ * Exchange this with the other party and verify they match BEFORE using
1477
+ * `sessionKey`. If they differ, abort — a MITM substituted a key.
1422
1478
  */
1423
1479
  readonly commitment: string;
1424
1480
  private _raw;
1425
1481
  /** @internal */
1426
- constructor(raw: Buffer, nonce: Buffer);
1427
- /** Zero the raw session key bytes from memory. */
1482
+ constructor(raw: Buffer, transcript: Buffer);
1483
+ /**
1484
+ * Zero the raw session key bytes from memory.
1485
+ * The `sessionKey` hex string may still exist in the JS heap
1486
+ * (strings are immutable in V8); call this as soon as `setPassKey()` is done.
1487
+ */
1428
1488
  destroy(): void;
1429
1489
  }
1430
- /** An ephemeral X25519 handshake participant. */
1431
- declare class SPDHandshake {
1490
+ /** Initiator state returned by `SPDHandshake.createInitiator()`. */
1491
+ declare class SPDHandshakeInitiator {
1492
+ /** Send this to the responder. */
1493
+ readonly hello: SPDHandshakeHello;
1494
+ private _x25519Priv;
1495
+ private _kemSecretMask;
1496
+ private _blindedKemSK;
1497
+ private _nonceRaw;
1498
+ private _done;
1499
+ /** @internal */
1500
+ constructor(x25519PrivRaw: Buffer, x25519PubRaw: Buffer, kemSecretKey: Uint8Array, kemPublicKey: Uint8Array, nonce: Buffer);
1432
1501
  /**
1433
- * Base64url-encoded X25519 public key (32 bytes).
1434
- * Transmit this to the other party.
1502
+ * Complete the initiator side of the handshake using the responder's hello.
1503
+ *
1504
+ * @param responderHello The `hello` object received from the responder.
1505
+ * @returns `SPDHandshakeResult` — call `.destroy()` after `setPassKey()`.
1506
+ * @throws If already called, or if any input is malformed.
1435
1507
  */
1436
- readonly publicKey: string;
1508
+ finalizeInitiator(responderHello: SPDHandshakeHello): Promise<SPDHandshakeResult>;
1509
+ /** Zero all key material. Call if abandoning the handshake. */
1510
+ destroy(): void;
1511
+ }
1512
+ /** Responder state returned by `SPDHandshake.createResponder()`. */
1513
+ declare class SPDHandshakeResponder {
1514
+ /** Send this to the initiator. */
1515
+ readonly hello: SPDHandshakeHello;
1516
+ private _result;
1517
+ /** @internal */
1518
+ constructor(hello: SPDHandshakeHello, result: SPDHandshakeResult);
1437
1519
  /**
1438
- * Cryptographically random session nonce (32 bytes, base64url).
1439
- * Transmit this alongside `publicKey`. Mixed into HKDF to prevent
1440
- * cross-session key reuse even if the same ephemeral keypair were
1441
- * somehow reused.
1520
+ * Return the completed session result.
1521
+ * The responder derives its key during `createResponder()` call this
1522
+ * after sending `hello` to the initiator.
1442
1523
  */
1443
- readonly sessionNonce: string;
1444
- private _blindedPriv;
1445
- private _privMask;
1446
- private _nonceRaw;
1524
+ responderResult(): SPDHandshakeResult;
1525
+ }
1526
+ declare class SPDHandshake {
1447
1527
  private constructor();
1448
1528
  /**
1449
- * Create a new ephemeral participant with a freshly generated X25519 keypair
1450
- * and a random session nonce.
1451
- */
1452
- static create(): SPDHandshake;
1453
- /**
1454
- * Derive the shared session key from the other party's public key and nonce.
1529
+ * Create the initiator side of the handshake.
1455
1530
  *
1456
- * Both parties must call `derive()` with each other's `publicKey` and
1457
- * `sessionNonce`. The resulting `SPDHandshakeResult.commitment` values will
1458
- * match if and only if both sides derived the same key.
1531
+ * Generates an ephemeral X25519 keypair and ML-KEM-768 keypair.
1532
+ * Send `initiator.hello` to the responder.
1459
1533
  *
1460
- * @param theirPublicKey The other party's `publicKey` string.
1461
- * @param theirSessionNonce The other party's `sessionNonce` string.
1462
- * @returns `SPDHandshakeResult` containing the session passphrase and
1463
- * CMT-4 key commitment. Call `.destroy()` after handing the
1464
- * passphrase to `spd.setPassKey()`.
1465
- * @throws If `derive()` has already been called on this instance.
1466
- * @throws If the public key is malformed or the low-order point check fails.
1534
+ * @returns `SPDHandshakeInitiator` instance.
1467
1535
  */
1468
- derive(theirPublicKey: string, theirSessionNonce: string): SPDHandshakeResult;
1536
+ static createInitiator(): Promise<SPDHandshakeInitiator>;
1469
1537
  /**
1470
- * Zero and release all key material.
1471
- * Called automatically by `derive()`. Call manually if you abandon the
1472
- * handshake without completing it.
1538
+ * Create the responder side of the handshake from the initiator's hello.
1539
+ *
1540
+ * Generates an ephemeral X25519 keypair, encapsulates against the
1541
+ * initiator's ML-KEM public key, and derives the session key.
1542
+ * Send `responder.hello` to the initiator.
1543
+ *
1544
+ * @param initiatorHello The `hello` object received from the initiator.
1545
+ * @returns `SPDHandshakeResponder` instance.
1546
+ * @throws If any input is malformed or a low-order point is detected.
1473
1547
  */
1474
- destroy(): void;
1548
+ static createResponder(initiatorHello: SPDHandshakeHello): Promise<SPDHandshakeResponder>;
1475
1549
  }
1476
1550
 
1477
- 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, SPDHandshake, SPDHandshakeResult, 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 };
1551
+ 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, SPDHandshake, type SPDHandshakeHello, SPDHandshakeInitiator, SPDHandshakeResponder, SPDHandshakeResult, 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 };