// private set intersection · ristretto255

Find what you share. Keep the rest.

Status: Ticker (placeholder, no token exists):
// the working app

Two lists in. Only the overlap out.

Each item is hashed onto the ristretto255 group and raised to a secret only its owner knows. Blinded twice — once by each party — matching items become equal values, and nothing else can be compared. Run both sides on one page, or do it for real across two browsers.

Loading @noble/curves from jsDelivr…

Both parties run in this tab with independent secrets, so you can watch every message. The sample button makes random address-shaped strings (not real wallets) with a few planted in both lists.

A Alice's list

B Bob's list

Items in both lists

You start. Keep this tab open until the end: your secret exponent lives only in this tab's memory, and a reload discards it (you would have to start over).

1 Your list → message 1

Paste your items. Overlap hashes each to the group, raises it to your secret a, shuffles, and packs the result.

Optional helper: reads up to 15 recent transactions of that address through our read-only RPC proxy and adds their other signers to your list. The RPC learns which address you looked up.

2 Paste Party B's reply → result

B's reply holds your values blinded again by b, plus B's own blinded list. You raise B's values to a and compare.

Items you share with B

3 Optional: let B learn the result too

Send back B's values blinded by a (in B's order) so B can compute the same overlap. Skip this if only you should learn it.

You respond. Keep this tab open if you want to receive message 3 — your secret b lives only in this tab's memory.

1 Paste message 1 and your list → reply

2 Optional: paste message 3 → result

Only if A chooses to send it. You compare A's re-blinding of your values with what you computed in step 1.

Items you share with A
// how it works

Commutative blinding, in three messages

  1. Hash to the group. Each item x becomes a point H(x) on ristretto255 — SHA-512 of a domain tag and the item, mapped with the ristretto one-way map (RistrettoPoint.hashToCurve). Ristretto255 is a prime-order group, so there are no small-subgroup tricks.
  2. A blinds and shuffles. A picks a random secret scalar a, sends H(xᵢ)·a for every item in a random order (message 1). Without a, B cannot test any guess against these values.
  3. B blinds twice. B picks a secret b, returns A's values re-blinded H(xᵢ)·a·b in the order received, plus B's own items H(yⱼ)·b in a fresh random order (message 2).
  4. A compares. A computes H(yⱼ)·b·a. Because scalar multiplication commutes, x = y ⇔ H(x)·ab = H(y)·ba. A matches equal values back to her own items via her private ordering.
  5. Optional symmetry. A returns H(yⱼ)·b·a in B's order (message 3), and B runs the same comparison. Shuffling means positions never reveal which of B's items matched — only B, who knows his own order, can map them.

Group arithmetic: @noble/curves@1.4.0 (ristretto255). Hashing, randomness and fingerprints: the browser's Web Crypto API. Fingerprints shown next to each message are the first bytes of its SHA-256 — read them aloud to confirm you pasted the right blob.

// privacy model

What leaks, and what doesn't

Leaks by design

  • Set sizes. Each side learns how many items the other submitted (the message lengths).
  • The intersection — to A always, and to B only if A sends message 3. Its size follows.
  • Your chosen items are probes. Anything you put in your list, you learn whether the other side has it. A party can stuff its list with guesses; PSI cannot prevent that.
  • Message fingerprints and timing to whatever channel you use to exchange blobs.

Stays hidden

  • Every item not in the intersection — from the other party and from anyone who intercepts the blobs (under the decisional Diffie–Hellman assumption, with SHA-512 modelled as a random oracle).
  • Which position in a blob corresponds to which item (outputs are shuffled with a CSPRNG).
  • The secrets a and b — they never leave the tab and are discarded on reload.
  • Your lists from our server: it only serves this page and, if you use the helper, read-only RPC calls.

Known limitations — please read

  • Semi-honest security only. The protocol protects against a party who follows the steps but is curious. A malicious party can lie — e.g. A can send a wrong message 3 so B learns a false result, or either side can submit items they don't really hold.
  • Small or guessable universes are probeable. If both of you draw from a small known set (say, the top 500 token holders), each side can learn a lot just by choosing what to submit. Agree on list sizes in advance, and treat the intersection as disclosed information.
  • No authentication. Blobs carry no identity. Exchange them over a channel where you know who is on the other end, and compare fingerprints.
  • Exact matching only. Items must be byte-identical after trimming (and optional lowercasing). Typos and different formats won't match.
  • Reduces information exposure, not anonymity. Overlap minimises what two parties reveal to each other; it does not make anyone anonymous or hide on-chain activity.
  • Experimental and unaudited. No security review; the group library is a well-known open-source package, but this integration is new. Lists are capped at 5,000 items per side to keep the browser responsive.