/**
 * Pair protocol v6 — trust bootstrap for new devices.
 *
 * Threat model: the pairing URL (QR or curl token) is assumed to travel over
 * a partially-trusted channel. Passive eavesdroppers must not learn the
 * cluster secret; active MitM must be detectable. v6 builds on v5 by
 * replacing the single `{host, proxyPort, relayUrl}` triple with an
 * ordered list of endpoints (direct + relay), so the joining device can
 * happy-eyeballs probe whichever path actually reaches the issuer.
 *
 * Keys involved on the issuer (A) side:
 *   - ed25519 identity keypair — signs the transcript (prevents MitM).
 *   - X25519 static kex keypair — performs ECDH with the redeemer's
 *     ephemeral pubkey (derives the AEAD key that encrypts the secret).
 *
 * On the redeemer (D) side:
 *   - one-shot X25519 ephemeral keypair per redeem attempt.
 *
 * Transcript binding covers every material in the exchange — token,
 * both static pubkeys, ephemeral pubkey, ciphertext/nonce/tag, nodeId,
 * listenPort, and the full endpoint list — so an attacker swapping any
 * field (including injecting a malicious endpoint) invalidates the
 * ed25519 signature.
 *
 * AEAD (AES-256-GCM) is reused from crypto.ts to keep platform parity
 * with the iOS client (CryptoKit) and avoid introducing ChaCha20.
 */
import { type X25519KeyPair } from "./crypto.ts";
export declare const PAIR_PROTOCOL_VERSION = 6;
/**
 * A single dialable endpoint advertised in the pair offer.
 *
 * `direct` — full base URL the joiner POSTs `/api/pair/redeem` to.
 *            Covers LAN IPs (`http://192.168.x.x:23513`) and public
 *            tunnel URLs (`https://x.trycloudflare.com`).
 * `relay`  — full WebSocket base URL of a Feno relay. The joiner dials
 *            `${url}/v1/room/<computePairingRoomId(token)>` and sends a
 *            framed `pair_redeem` message over the pair-room.
 */
export interface Endpoint {
    kind: "direct" | "relay";
    url: string;
}
/** Parsed pair offer (from QR URL). Version is always PAIR_PROTOCOL_VERSION. */
export interface PairOffer {
    version: typeof PAIR_PROTOCOL_VERSION;
    token: string;
    signingPublicKey: Buffer;
    kexPublicKey: Buffer;
    endpoints: Endpoint[];
}
/** Request body D sends to POST /api/pair/redeem. */
export interface RedeemRequest {
    token: string;
    ephemeralKexPubkey: Buffer;
    nodeId?: string;
    publicKey?: string;
    name?: string;
}
/** Sealed response A sends back. All Buffer fields are raw bytes. */
export interface SealedRedeemResponse {
    version: typeof PAIR_PROTOCOL_VERSION;
    nodeId: string;
    listenPort: number;
    signingPublicKey: Buffer;
    kexPublicKey: Buffer;
    ciphertext: Buffer;
    nonce: Buffer;
    tag: Buffer;
    sig: Buffer;
    endpoints: Endpoint[];
}
/** What D gets after successfully opening the sealed response. */
export interface RedeemResult {
    secret: string;
    nodeId: string;
    listenPort: number;
    signingPublicKey: Buffer;
    endpoints: Endpoint[];
}
/**
 * Build the feno://pair?d=... URL. Only v6 is emitted — callers must
 * always supply both signing and kex public keys plus at least one
 * endpoint.
 */
export declare function buildOfferUrl(params: {
    token: string;
    signingPublicKey: Buffer;
    kexPublicKey: Buffer;
    endpoints: Endpoint[];
}): {
    url: string;
    raw: Record<string, unknown>;
};
/** Parse feno://pair?d=<base64url(JSON)>. Rejects older protocol versions. */
export declare function parseOfferUrl(input: string): PairOffer;
/** Fresh X25519 ephemeral keypair for a single redeem attempt. */
export declare function generateEphemeralKexKeyPair(): X25519KeyPair;
/**
 * Deterministic 6-char hex fingerprint derived from the request's
 * ephemeral pubkey bound to the offer's static identities. Both sides
 * can compute it independently without exchanging extra data — the
 * human compares the code shown on D's screen with the code shown in
 * the approval prompt on A.
 *
 * 3 bytes = 24 bits ≈ 16M combinations — enough for eyeball compare,
 * not enough for offline brute force of the pubkey (which is 256 bits).
 */
export declare function deriveFingerprint(input: {
    token: string;
    signingPublicKey: Buffer;
    kexPublicKey: Buffer;
    ephemeralPublicKey: Buffer;
}): string;
/**
 * A accepts a RedeemRequest, encrypts the secret under an AEAD key
 * derived from ECDH(A_static_priv, D_ephemeral_pub), and signs the
 * full transcript with A's ed25519 identity key.
 */
export declare function sealRedeem(params: {
    secret: string;
    token: string;
    ephemeralKexPubkey: Buffer;
    staticKexKeyPair: X25519KeyPair;
    /** Sign arbitrary data with the node's ed25519 identity key. */
    sign: (data: Buffer) => Buffer;
    signingPublicKey: Buffer;
    nodeId: string;
    listenPort: number;
    endpoints: Endpoint[];
}): SealedRedeemResponse;
/**
 * D verifies the sealed response and decrypts the secret. Throws on any
 * tamper or key mismatch. MUST be called with the exact offer D used to
 * initiate the redeem (so MitM swapping pubkeys is caught).
 */
export declare function openRedeem(params: {
    offer: PairOffer;
    ephemeralKexKeyPair: X25519KeyPair;
    sealed: SealedRedeemResponse;
}): RedeemResult;
/** Serialize a SealedRedeemResponse to a JSON-safe object (base64url buffers). */
export declare function serializeSealed(sealed: SealedRedeemResponse): Record<string, unknown>;
/** Parse a wire-format sealed response back into a SealedRedeemResponse. */
export declare function deserializeSealed(raw: unknown): SealedRedeemResponse;
