Inspiration

A grandmother picks up the phone and hears her grandson crying: "Grandma, it's me. I'm in jail. Please don't tell Mom." The voice is perfect, because it was cloned from a few seconds of a social-media video.

Every piece of scam advice I grew up with assumed the fake would slip: a typo, a strange accent, a voice that sounds a little off. Generative AI removed those tells. Phishing has perfect grammar now, and a voice clone sounds exactly like the person. Detectors are stuck in an arms race with generators that improve every month.

So I asked a different question: instead of detecting the fake, what if we verify the real person? Banks solved this years ago with security tokens that show a new code every 30 seconds. I wanted that idea, spoken out loud and simple enough for a grandparent. That became Countersign: AI can fake a voice. It can't fake our secret.

What it does

  • Family Countersign. A family scans one QR code, once, in person. From then on, each person's phone shows their own three words, changing every minute (for example COPPER · LANTERN · RIVER). When a call asks for money, Grandma taps Who's calling?, taps the name, and asks for the countersign. The real grandson reads the words off his phone. A clone can't. No account, no server, and it works with no signal.
  • Call Shield. With the call on speaker, it spots the scam script as it unfolds (an emergency, "don't tell Mom", gift cards), tells you exactly what to ask, and offers a one-tap text to someone you trust, because scams depend on secrecy. It can also keep the whole call on the phone.
  • Message Investigator. Paste an email or drop a screenshot. An AI agent runs real lookups (domain age, DNS, email authentication, lookalike domains, redirect chains, QR codes), and fixed rules, not the model, decide the verdict. Or just forward the email and get the case file back.
  • Built for the habit. Practice scam calls, a weekly two-minute drill in your calendar, a printable card for the phone table, an optional Face ID lock on the words, and five languages.

How we built it

Stack. Next.js 16 (App Router, streaming Server-Sent Events), TypeScript, Tailwind CSS, Web Crypto, Web Speech, WebAuthn and a service worker, deployed on Vercel. The code is split into modules with boundaries enforced by lint rules. The family module can't import any AI or server code, so the most important protection never depends on something that can fail.

The family protocol. Each circle shares a 256-bit secret $K$. A member's words for the current minute are

$$ \text{code}(K, n, t) = \text{BIP39}_3\left(\operatorname{HMAC\text{-}SHA256}\left(K,\ \texttt{"countersign/v2|member|"} \Vert n \Vert \texttt{"|"} \Vert \lfloor t/60 \rfloor\right)\right) $$

where $n$ is the member's normalized name and $\text{BIP39}_3$ maps the first 33 bits to three words from a 2048-word list. A single guess succeeds with probability

$$ P(\text{guess}) = 2^{-33} \approx \frac{1}{8.6 \times 10^{9}}, $$

and the code expires within a minute. Phones accept the neighbouring minute's code for 20 seconds to absorb clock drift. The secret travels only in the URL fragment, which browsers never send to servers. Every screen says to read your words only when you placed the call. For the closest pairs, a two-person mode uses directional codes, so a scammer calling the real grandson at the same time can't relay Grandma's expected words. The protocol is published with test vectors, so anyone can build a compatible app.

The verdict. The AI gathers evidence; code decides. Each finding has a fixed weight, and a noisy-OR combines independent risk signals $w_i$, discounted by trust signals $t_j$:

$$ \text{risk} = \Bigl(1 - \prod_i (1 - w_i)\Bigr) \cdot \prod_j (1 - t_j) $$

with $\text{risk} \ge 0.70$ meaning FORGERY, $\ge 0.35$ UNVERIFIED, and anything lower COUNTERSIGNED. When there's impersonation evidence, trust that can't vouch for an impersonator (an old domain, links on the brand's site, the model's "looks genuine") is ignored. A defense agent argues the message is genuine and a judge explains the ruling, but neither can change the band. If the model is down, the deterministic checks still produce a verdict.

Testing. 189 unit tests, including the protocol's test vectors. 23 browser tests, including two separate browser contexts acting as two phones and checking that the words one shows match what the other expects. CI runs on every push. Even the demo video was recorded by a script driving the real app.

Challenges we ran into

  • Being honest about accuracy. My first evaluation showed a single AI prompt matching me on textbook scams. So I pre-registered test sets (committing them before any system ran), published every run, and lost one: 10/12 against the prompt's 12/12 on an adversarial set, because I flagged genuine messages that quoted prompt-injection text. I fixed the cause and tested the fix on a fresh hold-out nobody had seen. Because the samples are small, I report 95% Wilson intervals:

$$ \frac{\hat{p} + \frac{z^2}{2n}}{1 + \frac{z^2}{n}} \;\pm\; \frac{z}{1 + \frac{z^2}{n}}\sqrt{\frac{\hat{p}(1-\hat{p})}{n} + \frac{z^2}{4n^2}}, \qquad z = 1.96 $$

  • Prompt injection inside the evidence. Scams now carry text aimed at AI screeners: fake "security scan: SAFE" reports, fake assistant transcripts, instructions in another language. I detect them deterministically and count them as evidence. Text quoted as an example counts only as weak evidence, so a security newsletter isn't flagged.
  • QR codes in real screenshots. While recording the demo, I found that the QR library missed anti-aliased codes in real screenshots, so the hidden link silently never reached the investigator. It now uses the browser's native barcode detector first, then the library at several scales, and a browser test pins the exact screenshot that failed.
  • Real life breaks protocols. Phones get lost, people leave, and scammers say "my phone died, I can't see the words." I added:
    • re-keying ("start fresh")
    • an opt-in replacement rule, so a removed member can't push a fake update
    • a practice scenario for the dead-phone trick
    • an optional biometric lock
  • A final security review on deadline day caught three real issues:
    • "Call this number" scams that link to the real brand's site
    • legitimate domains that merely contain a brand name
    • a family QR code screenshot that would have been uploaded

All three are fixed, with tests.

  • Designing for grandparents. Big type, one decision per screen, words read aloud, and the whole check one tap from the home screen.

Accomplishments that we're proud of

  • A defence against voice clones that doesn't depend on detecting them. It's a working, offline, cryptographic identity check for a whole family, in five languages, with an open protocol and test vectors.
  • On a hold-out set committed before any system saw it, Countersign got 17/18 and a single AI prompt got 8/18. Most of those scams hide their link in a QR code, which a prompt can't read.
  • Six evaluation runs, four pre-registered test sets, and every loss published with confidence intervals at /evidence.
  • The AI never decides alone. Fixed rules set every verdict, and the checker keeps working without the model: the deterministic checks alone scored 17/18 on the hold-out.
  • Privacy by design. The family secret never leaves the phones, a screenshot of a family QR code is refused before anything is sent, Call Shield can stay entirely on the device, and analytics see only page paths, never a secret or a message.
  • It's built like a product: 200+ tests, CI, enforced module boundaries, and an architecture doc.

What we learned

  • A strong AI model is very hard to beat at reading scam text. The real gap is what it can't see (QR codes, domain age, email authentication, redirects) and what happens when it's wrong, manipulated or down.
  • Verifying identity beats detecting fakes. Against a perfect voice clone, no detector wins, but a shared secret still does.
  • Pre-registering tests keeps you honest. Losing a test I'd already published taught me more than the ones I won.
  • Adoption is the hardest problem in security. The cryptography took a day; making the habit stick (practice calls, drills, a card by the phone) took the rest.

What's next for CounterSign

  • Native iOS and Android apps built on the open protocol.
  • A family dashboard for whoever sets up protection.
  • Bank partnerships that prompt for the countersign before large transfers.
  • Testing with real families, to measure whether the habit sticks.

Built With

  • ai-agents
  • bip39
  • claude
  • d3.js
  • dns
  • email-authentication
  • github-actions
  • hmac-sha256
  • jsqr
  • llm
  • next.js
  • node.js
  • playwright
  • pwa
  • rdap
  • react
  • service-worker
  • tailwindcss
  • typescript
  • vercel
  • vitest
  • web-crypto
  • web-speech-api
  • webauthn
  • zod
Share this project:

Updates

Submission history