🎭 Rendezvous

The Private Matching Engine

Two parties can discover mutual interest without revealing who they selectedβ€”unless the feeling is reciprocated.

The problem

Every mutual selection scenario has the same uncomfortable asymmetry: someone has to go first, and going first means risking rejection.

Dating apps solve this with swipingβ€”but they're surveillance capitalism dressed up as romance. They know who you're attracted to, and they sell that data. When matching fails, they know why it failed. Every unrequited interest is logged.

Professional contexts are worse. Imagine expressing interest in a job candidate, a business partner, or a mentorβ€”only to find out they weren't interested, and now they know you wanted them. The power dynamic shifts permanently.

There's no safe way to say "I'm interested" without becoming vulnerable to rejection, manipulation, or surveillance.

Rendezvous's answer

Cryptographic mutual matching. You submit encrypted preferences. The system detects only mutual matches. If you selected someone who didn't select you back, nobody learns thisβ€”not even the server.

The key insight: Diffie-Hellman key exchange produces the same shared secret from either direction.

Alice wants Bob:
  shared = DH(alice_private, bob_public)
  token = H(shared || pool_id || "match")

Bob wants Alice:
  shared = DH(bob_private, alice_public)  // Same secret!
  token = H(shared || pool_id || "match")  // Same token!

If both submit the same token, we have a match. If only one submits, the token appears onceβ€”indistinguishable from noise. The server learns nothing about unrequited interest.

Privacy guarantees

Party Learns Does NOT learn
Server Token counts, timing Who you selected, who rejected you
You Your mutual matches only Others' non-matching selections
Your match That you matched Your other selections
Federation Pool metadata Participant identities (Freebird tokens)

Screenshots

[ Screenshot: Pool browser showing active matching pools ] pool-browser.png
[ Screenshot: Participant selection interface with pseudonymous profiles ] selection-interface.png
[ Screenshot: Match discovery screen with revealed contact info ] match-reveal.png

Use cases

Domain Problem solved
Dating Express interest without risk of unreciprocated exposure
Recruiting Candidates and employers match without revealing failed interests
Mentorship Pair mentors and mentees based on mutual selection
Roommates Find compatible living situations without awkward rejections
Collaboration Researchers find co-authors who also want to work with them
Investment Founders and investors match without signaling desperation

Technical architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         RENDEZVOUS SERVER                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚  β”‚ Pool Manager β”‚  β”‚  Submission  β”‚  β”‚    Match     β”‚              β”‚
β”‚  β”‚              β”‚  β”‚   Manager    β”‚  β”‚   Detector   β”‚              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚         β”‚                 β”‚                 β”‚                       β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                       β”‚
β”‚                           β–Ό                                         β”‚
β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                 β”‚
β”‚                   β”‚ SQLite Store  β”‚                                 β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚  β”‚   Freebird   β”‚  β”‚   Witness    β”‚  β”‚  Federation  β”‚              β”‚
β”‚  β”‚  (auth/gate) β”‚  β”‚ (timestamp)  β”‚  β”‚ (cross-inst) β”‚              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                    PSI Layer (Private Set Intersection)             β”‚
β”‚         Query matches without revealing your token set              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                   β–Ό                   β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Web UI  β”‚        β”‚   CLI    β”‚        β”‚ REST API β”‚
        β”‚  (PWA)   β”‚        β”‚          β”‚        β”‚          β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Protocol flow

1. POOL CREATION
   └─ Operator defines: eligibility rules, deadlines, max selections

2. COMMIT PHASE (optional, prevents timing attacks)
   └─ Participants submit: H(tokens) β€” commitment only

3. REVEAL PHASE
   └─ Participants submit: tokens + encrypted contact info
   └─ Decoy tokens added to hide true selection count

4. PRIVACY DELAY (30s–3min random)
   └─ Prevents timing correlation between submission and results

5. MATCH DETECTION
   └─ Server counts token occurrences
   └─ Tokens appearing 2+ times = mutual match

6. MATCH DISCOVERY
   └─ Participant queries via PSI (server learns nothing)
   └─ Local computation reveals which selections matched

Privacy features

Private Set Intersection β€” When you query for matches, the server processes your request without learning which tokens you submitted. Only you learn your matches.

Decoy tokens β€” Clients automatically pad submissions with random tokens. The server can't tell if you selected 1 person or 10.

Response padding β€” All API responses are padded to 8KB block boundaries. Attackers can't infer information from response sizes.

Pseudonym rotation β€” Generate a fresh keypair for each pool. Even if someone participates in multiple pools with you, they can't link your identities.

Ephemeral mode β€” Pool creators can auto-delete participant profiles after matching. Only anonymous tokens remain.

Federation

Rendezvous instances federate via WebSocket, syncing pool metadata with Automerge CRDTs. Cross-instance queries use Freebird tokens for unlinkable authenticationβ€”neither instance learns who is querying from where.

Centralized: Single point of control, surveillance, shutdown
Federated: Community-run instances, no single point of failure

Integrations

Freebird β€” Anonymous eligibility proofs. Prove you belong to a group without revealing your identity within it.

Witness β€” Timestamp attestation. Cryptographic proof that matches were computed at a specific time.

What it actually does

Try it

git clone https://github.com/flammafex/rendezvous
cd rendezvous
npm install
npm run build
npm run seed    # Create demo pools
npm run server  # Start at localhost:3000

Why this matters for the open web

The ability to express interest without risking asymmetric exposure is fundamental to human dignity. Current platforms exploit this vulnerabilityβ€”they profit from knowing who wants whom, and they use that knowledge to manipulate behavior.

Rendezvous makes mutual discovery possible without surveillance. It's the difference between a trusted friend introducing two people who both expressed interest, and a dating app that logs every swipe to train an engagement-maximizing algorithm.

This isn't just about dating. It's about restoring symmetry to human connectionβ€”professional, personal, and everything in between.

Funding goals

We're seeking support to advance Rendezvous as open infrastructure for privacy-preserving mutual matching:

Goal Purpose
Documentation & tutorials Lower barrier for operators running their own instances
Security audit Third-party review of cryptographic protocol
Browser sandbox Try Rendezvous without installing anything
Mobile apps Native iOS/Android for broader accessibility
Federation hardening Improve cross-instance reliability and privacy guarantees
Academic publication Formal analysis of protocol security properties

Status