///
import type { Duplex } from 'node:stream'
import type { EventEmitter } from 'node:events'
export interface PqIdentity {
publicKey: Uint8Array | Buffer
secretKey: Uint8Array | Buffer
}
export interface ChannelOptions {
role: 'initiator' | 'responder'
/**
* ML-DSA-65 keypair for mutual authentication. Optional. A responder given
* one refuses an initiator that does not authenticate too.
*/
identity?: PqIdentity
/**
* The ML-DSA-65 public key (1952 bytes) the peer must prove. Optional, and
* needs an `identity` on this side, because the peer proves its key only in
* a mutual handshake. A peer that proves any other key fails the handshake
* with `KxcoPqTlsError`.
*
* Mutual authentication proves the peer holds the private half of the key
* it presented, not that it is the key you expect: a relay holding a key of
* its own also completes the handshake. Pin the key here, or compare the
* channel's `peerPublicKey` yourself.
*/
peerPublicKey?: Uint8Array | Buffer
/**
* Total deadline for the handshake, in milliseconds. Defaults to 30000.
* Set to 0 to wait indefinitely.
*
* An initiator configured with an `identity` expects a Finished frame, and a
* responder with no identity of its own never sends one, so that mismatch
* stalls rather than failing. This bounds that wait.
*/
handshakeTimeoutMs?: number
}
export interface HandshakeOptions {
identity?: PqIdentity
/** The ML-DSA-65 public key the peer must prove. See `ChannelOptions`. */
peerPublicKey?: Uint8Array | Buffer
/**
* Total deadline for the handshake, in milliseconds. Defaults to 30000.
* Set to 0 to wait indefinitely.
*/
handshakeTimeoutMs?: number
}
export interface SessionKeys {
/**
* After mutual authentication, each side's Finished frame was sealed under
* these keys as sequence 0. A record layer of your own starts at 1.
*/
txKey: Uint8Array
rxKey: Uint8Array
/** The ML-DSA-65 public key the peer proved, or `undefined` without mutual authentication. */
peerPublicKey: Uint8Array | undefined
}
/** The encrypted `Duplex` returned by `wrapStream`. */
export interface PqTlsStream extends Duplex {
/** The ML-DSA-65 public key the peer proved, or `undefined` without mutual authentication. */
readonly peerPublicKey: Uint8Array | undefined
}
/**
* Wrap a Node.js Duplex stream (e.g. `net.Socket`) with a post-quantum secure
* channel. Resolves once the handshake completes. If the handshake fails, the
* socket is destroyed before the promise rejects.
*
* Key exchange: ML-KEM-768 + X25519. Session encryption: AES-256-GCM.
* Optional mutual auth via ML-DSA-65 Finished frames.
*/
export function wrapStream(socket: Duplex, options: ChannelOptions): Promise
/**
* Wrap a WebSocket (native API or `ws` package) with a post-quantum secure
* channel. Resolves once the handshake completes. If the handshake fails, the
* WebSocket is closed before the promise rejects. A native WebSocket whose
* `binaryType` is `'blob'` is switched to `'arraybuffer'`.
*/
export function wrapWebSocket(ws: unknown, options: ChannelOptions): Promise
/**
* Encrypted WebSocket wrapper returned by `wrapWebSocket`.
* Emits `message`, `close`, and `error` events.
*/
export declare class PqTlsWebSocket extends EventEmitter {
/** The ML-DSA-65 public key the peer proved, or `undefined` without mutual authentication. */
readonly peerPublicKey: Uint8Array | undefined
send(data: string | Buffer | Uint8Array): void
close(code?: number, reason?: string | Buffer): void
}
// ── Low-level handshake API ───────────────────────────────────────────────────
type SendFn = (data: Buffer) => Promise
type RecvFn = (n: number) => Promise
/** Run the initiator side of the PQ-TLS handshake over custom send/recv functions. */
export function initiatorHandshake(
send: SendFn,
recv: RecvFn,
options?: HandshakeOptions,
): Promise
/** Run the responder side of the PQ-TLS handshake over custom send/recv functions. */
export function responderHandshake(
send: SendFn,
recv: RecvFn,
options?: HandshakeOptions,
): Promise
export class KxcoPqTlsError extends Error {
name: 'KxcoPqTlsError'
/**
* Present only where a caller may reasonably branch on the failure. Currently
* the handshake deadline, `ERR_KXCO_PQ_TLS_HANDSHAKE_TIMEOUT`.
*/
code?: string
}
/** `err.code` on a handshake that exceeded its deadline. */
export const ERR_HANDSHAKE_TIMEOUT: 'ERR_KXCO_PQ_TLS_HANDSHAKE_TIMEOUT'