QNSP

Build Pattern · Browser SDK

Build Browser-to-Server End-to-End PQC

@noble/post-quantum in the browser → ML-KEM-768 handshake → QNSP edge-gateway → tenant vault.

End-to-end PQC from a user's browser to QNSP vault using @noble/post-quantum (pure-JS, no WASM/native) for ML-KEM-768 + ML-DSA-65, with hybrid X25519MLKEM768 TLS at the edge. No server in your stack ever sees plaintext.

20 minTime to first PQC
Browser SDKPrimary SDK
3Services used

QNSP services used

Stack

Code

Real code, real SDK calls

Snippets reference the published @cuilabs/qnsp SDK surface (TypeScript, Python, Go, Rust mirror byte-for-byte).

Client-side ML-KEM-768 + ML-DSA-65 (pure JS)typescript
// In your React / Vue / Svelte component (zero native deps)
import { ml_kem768 } from "@noble/post-quantum/ml-kem";
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa";

// 1. Browser fetches the server's ML-KEM-768 public key
const serverPubKey = await fetch("/api/qnsp/pubkey").then(r => r.bytes());

// 2. Browser does the encapsulation locally
const { cipherText, sharedSecret } = ml_kem768.encapsulate(serverPubKey);

// 3. Encrypt the payload locally with the shared secret (AES-256-GCM)
const ciphertext = await encryptAesGcm(sharedSecret, sensitivePayload);

// 4. POST to your server — server never sees plaintext
await fetch("/api/qnsp/vault", {
  method: "POST",
  body: JSON.stringify({ cipherText, payloadCipher: ciphertext }),
});
Server-side: forward the wrapped payload to QNSP vaulttypescript
import { QnspClient } from "@cuilabs/qnsp";

// In your Next.js / Express / Fastify route
export async function POST(req: Request) {
  const { cipherText, payloadCipher } = await req.json();
  const qnsp = new QnspClient({ apiKey: process.env.QNSP_API_KEY! });

  // QNSP unwraps via tenant key (cipherText) → decrypts payloadCipher → stores
  const secret = await qnsp.vault.createSecretFromKemHandshake({
    cipherText,
    payloadCipher,
  });

  return Response.json({ secretId: secret.id });
}

Ship it

Get an API key and start building