integration guide · phase 1: sign-in

# Integrate Sign in with qID

Post-quantum wallet login for your BTX app in one sitting. One server middleware, one button. The user proves control of a BTX address and that address is their account — no email, no password, no relay server, and a login proof that cannot move funds, by construction.

⬇ Download the release pack (.zip) Try it live first

The release pack is everything you need in one zip: the server SDK, the widget, runnable Express / Next.js / static-site examples, the test signer, and an `INSTALL.md` your developer (or their AI assistant) follows to wire it in — about an hour, most of it reading. Prefer the terminal? Everything below works from the unzipped pack. Prefer zero maintenance? Skip the download and use the hosted widget in step 04.

## 01 · How it works

1. Your server issues a one-time challenge `{nonce, rp_origin, ts}` bound to your exact origin.
2. The user's wallet shows them your site and signs the challenge with their post-quantum login key (ML-DSA-44) — by QR scan from a phone, copy-paste, or a `btxqid://` deep link into the installed desktop wallet.
3. Your server verifies the proof with the frozen qID Sign-In v1 verifier, rebuilds the address from the proof, and the verified address becomes (or finds) the account. Registration and login are the same action.
4. A signed HttpOnly session cookie keeps them signed in. Sessions, logout, and account-by-address are included.

There is no relay server anywhere, unlike WalletConnect: your own backend is the rendezvous. The QR carries a proof URL on your server; the phone posts the proof there and the browser that showed the QR claims the session with a secret only it holds.

## 02 · Get the code

    # download the release pack (button above), then:
    unzip qid-connect-latest.zip && cd qid-connect-*
    bun install
    bun test packages          # pins, frozen v1 vectors, e2e, QR + rotation flows
    bun examples/express-demo/server.js
    open http://localhost:8788 # a complete working login site, running on your machine

Everything is MIT. Two pieces matter: `packages/server` (@qid/connect-server — challenge, verify, accounts, sessions, SQLite stores) and `packages/widget` (@qid/connect-widget — the button, dialog, and account chip, one vanilla JS file, zero dependencies). Vendor them or workspace them; the examples show both ends wired.

During the private-preview window we ship the release pack as a download (the button above) rather than a public repo — its contents are identical to the repo, so everything on this page applies unchanged. Prefer git? Ask through qid.dev for read access.

## 03 · Wire your server

Express is one middleware line:

    import express from "express";
    import { createQidConnect } from "@qid/connect-server";
    import { qidMiddleware, requireQidSession } from "@qid/connect-server/express";

    const qid = createQidConnect({
      origin: "https://yourapp.com",                 // your exact web origin
      sessionSecret: process.env.QID_SESSION_SECRET, // 32+ random chars
    });

    const app = express();
    app.use(express.json());
    app.use("/qid", qidMiddleware(qid));             // the whole login system

    app.get("/api/me", requireQidSession(qid), (req, res) => {
      res.json(req.qidSession);                      // { address, account }
    });

Next.js is one catch-all route file (see `examples/next-demo/`):

    // app/api/qid/[action]/route.js
    import { createQidConnect } from "@qid/connect-server";
    import { qidNextHandlers } from "@qid/connect-server/next";

    const qid = createQidConnect({
      origin: process.env.QID_ORIGIN,
      sessionSecret: process.env.QID_SESSION_SECRET,
      apiPath: "/api/qid",
    });
    export const { GET, POST } = qidNextHandlers(qid);

## 04 · Mount the button — and the account chip

    <div id="signin"></div>   <!-- the Sign in button -->
    <div id="account"></div>  <!-- the signed-in chip (put it in your header) -->
    <script type="module">
      import { mountQidConnect, mountQidAccount } from "/qid-connect-widget.js";

      // The signed-in state: a chip (green dot + address) that opens a dropdown
      // with copy-address and a two-step Log out. Renders nothing when signed out.
      const account = mountQidAccount(document.getElementById("account"), {
        api: "/qid",                        // "/api/qid" on Next
        onSignedOut() { location.reload(); },
      });

      mountQidConnect(document.getElementById("signin"), {
        api: "/qid",
        appName: "Your app",
        onSignedIn() { account.refresh(); } // reveal the chip, no reload needed
      });                                   // dialog opens QR-first by default
    </script>

That's the complete login experience: the dialog is dynamic out of the box (countdown, self-rotating requests, server-side burn — a screenshotted QR is worthless minutes later), and the account chip keeps the signed-in state consistent on every BTX app: full address click-to-copy, extensible menu items, and a deliberate two-step Log out. Nothing to configure.

Prefer zero maintenance? Import the hosted widget instead of vendoring it: `import { mountQidConnect } from "https://qid.dev/connect/widget.js"` — wallet-list and UX updates then reach your users automatically when we deploy, while the signed protocol underneath stays frozen v1. A version-pinned copy (`widget-<version>.js`) exists for teams that want to review every change first.

## 05 · Test without a wallet

The repo ships the frozen v1 reference signer as a CLI, so you can fake a wallet during development. Click your button, copy `nonce` and `ts` from the shown request, then:

    bun tools/signer/btx-sign-ownership.mjs --random \
      --origin http://localhost:8788 --nonce <nonce> --ts <ts>

Paste the printed proof into the dialog's desktop tab — or POST it to `/qid/proof` to simulate a phone scanning the QR; the open dialog signs itself in within a second or two. With a real PQ Wallet for BTX (v0.21+): Settings → qID Sign-In, or one click via the deep link on v0.27+.

## 06 · What your server now speaks

route | what it does
--- | ---
`POST /qid/challenge` | fresh one-time challenge bound to your origin (+ poll secret for QR); optional `{ retire }` burns a superseded nonce on rotation
`POST /qid/verify` | verifies a pasted proof, creates/finds the account by address, sets the session cookie
`POST /qid/proof` | a remote signer (phone wallet) submits its proof; gets ok or a reason, never a session
`GET /qid/poll` | the browser that issued the challenge claims the session once the remote proof lands
`GET /qid/session` | `{ address, account, expiresAt }` for the current session, else 401
`POST /qid/logout` | clears the session cookie

## 07 · The security you inherit

- A login proof can never move funds. Sign-in signs under the `BTX-qID/login-v1` domain tag; transactions sign under `TapSighash`. The domains cannot collide. Structural, not policy.
- Origin-bound. The wallet shows the user your origin and signs exactly it; a proof for another site is worthless on yours.
- Single-use, short-lived, rotating. Nonces are consumed atomically, expire in minutes, and rotation burns whatever left the screen.
- Phishing-resistant QR. Conforming signers refuse any request whose proof URL is not the same origin as the site being shown.
- Login-CSRF guarded. A cross-site page cannot plant a session in a visitor's browser; both `/verify` and `/poll` check.
- No third-party infrastructure. Nothing to go down, nothing to trust, nothing to subpoena.

Claims we make and none we do not: resistant to quantum attacks, built on NIST-standardized post-quantum cryptography (FIPS 204 ML-DSA-44), pending independent audit. Nothing here is "unhackable" and we never say so.

## 08 · Wallets your users can bring

PQ Wallet for BTX · desktopavailable now — copy-paste since v0.21, one-click `btxqid://` connect since v0.27
bonuz Wallet · mobileavailable now — scan the qID QR with the bonuz app, pick your wallet, and you are in
Building a wallet? The wallet-side signer SDK (request parsing, the normative signer rules, a reference mobile approval screen) is available to wallet builders — reach out through qid.dev.

## 09 · The fine print

The v1 proof format and verifier are frozen; future capability arrives as new versions verified alongside v1, so a login you ship today keeps working forever. Phase 1 is deliberately login-only: no transaction requests, no permissions, no persistent connections — that is what makes it safe to adopt now (later phases are gated behind an independent audit). The code is MIT; the qID name and mark are not — forks take their own name. Keep the button and dialog stock (`docs/DESIGN.md`) so "Sign in with qID" looks the same on every BTX app, and never proxy or rewrite the challenge.