Meeting where AI eyes can't follow
I applied to the Community Privacy Residency, and the past projects reminded me of a cryptographic exploration I come back to every once in a while, just for fun.
What if people walking the same path through content on the internet could find each other, without profiles, sign-ups, or any server watching?
Visit the same pages in the same sequence, and you're in a chatroom together. Like recognising a familiar face on the train home from a conference, and starting a conversation.
A true shared secret can't emerge from a deterministic function of public inputs, right? You always need random private inputs for keys, right? Well, sorta.
But this is slightly different. You could build a rendezvous scheme where shared movement plus costly derivation makes it impractical for an attacker to occupy every possible location.
Let's call it a cost-asymmetric anonymous rendezvous protocol because that sounds smart.
It's probably impossible, but it's been a fun driver for me to explore cryptographic primitives that don't get much attention β a lot of paths outside the current ZK/blockchain world that seem left unexplored.
V1: The Hackathon
Ephemerant v1 was a Mina Protocol hackathon entry. It was my first time learning about zero-knowledge proofs, and for the first few days I just couldn't get anything to work. I assumed I just wasn't smart enough.
Towards the end, I realized, after the community folks escalated my questions, that a lot of the underlying library actually hadn't been built.
So I dropped the actual ZK proofs and worked with what the library did have: Poseidon hashes. I tried to build something that felt ZK-ish: a system where your browsing path generates a chain of hashes, and only someone who walked the same path could produce the same chain. No personal data shared, no central authority. Just matching hashes.
Then I remembered William Gibson's Neuromancer. A self-aware AI recruits and manipulates humans to carry out a plan they barely understand. The "matrix" β his networked cyberspace β is where AIs locate, probe, and manipulate targets by intercepting their activity. Hackers use encryption, proxying, dead channels, air-gaps β but these only buy temporary concealment. A determined AI with node-level access generally wins.
It's like the Byzantine Generals problem, except against an AI dragnet.
Ephemerant asks: what if the human connections themselves were unfindable, not just protected by encrypting what's inside them, but by making the address something only shared experience can produce?
This was way funner than saying "educational cohorts," so I ran with it.
It was enough to win a prize. Which I never collected, because they required KYC to pay out. A zero-knowledge chain demanding you reveal your identity to receive a prize for a privacy tool. Sometimes the irony writes itself.
But the problem stayed with me. With generative AI getting cheaper and more capable every month, the question became real: could you actually build this, and could it survive an AI attacker that can impersonate humans and never sleeps?
"I Am The Attacker In Every Room"
Let's define the problem with some math. Picture a site with 20 pages. An attacker can scrape the site, then can compute every possible path permutation and generate every possible key. For a path of 2 pages, that's 380 permutations. For 3 pages, 6,840. An AI scraper runs through all of them, generates all the keys, joins every possible room, and waits silently for any activity to observe.
The v1 design has no defence against this. The hashes are fast to compute. An attacker with a laptop could cover a small site in seconds.
Making each hash hurt
I just had a question - how could I increase the defenders' advantage, making it harder for an attacker to run permutations while cheap enough for a regular user to use this from a phone?
The first thing I learned about was memory-hard hash functions. Argon2 and scrypt are designed so each computation demands not just CPU time but a large block of RAM that can't be shared across parallel operations.
I decided to blindly trust this post I found in an internet forum:
Argon2 was specifically crafted to fix the inherent flaws of compute bounded key derivation functions like pbkdf2. With pbkdf2, you can double your iterations, which will double the time you have to wait to unlock your vault, but also double the time an attacker will take to crack your password (if they have your masterpasswordhash). In contrast to this, argon2 is not just compute-bounded but also memory bounded. This means that - for the same unlock time for you - password cracking will be significantly slower for an attacker attempting to crack your password on a GPU or ASIC, because an attacker cannot run as many instances of argon2 in parallel on a GPU, as they could with PBKDF2 due to memory limitations.
Quick napkin math
With Argon2 set to 512 MB memory cost and 0.5 seconds per computation on a single thread, the numbers for a 20-page site shift:
For 3-page paths (6,840 permutations): ~57 minutes of sequential computation, ~3.4 TB of total memory. For 5-page paths (1.86 million permutations): ~10.8 days, nearly a petabyte of memory.
That's not nothing. But for 3-page paths, 57 minutes is still within reach for a motivated attacker with cloud resources.
What a VDF adds (and why Argon2 isn't enough)
I thought this was a pretty clever idea for a while.
Then I realized I'd missed the point altogether.
Even with Argon2's parallelism set to 1, each individual computation is single-threaded, but nothing stops an attacker from running thousands of them at once on different path permutations. Duh!
And with an FPGA, this might be really easy.
This led me to Verifiable Delay Functions. A VDF requires a fixed number of truly sequential steps (like repeated squaring in an RSA group) that cannot be parallelized no matter what hardware you throw at them.
Both Verifiable Delay Functions (VDFs) and Argon2 use deliberate computational effort to secure systems, but they achieve completely different cryptographic goals. VDFs are designed to enforce a strict time delay that cannot be sped up by parallel processing, yet their results can be verified almost instantly. In contrast, Argon2 is a memory-hard key derivation function built to defend passwords against brute-force attacks by making the computation expensive in both time and memory, but verifying an Argon2 hash takes the exact same effort as computing it.
Don't worry if you're confused too. I needed to ask an AI to explain this to me:
| Feature | Verifiable Delay Functions (VDFs) | Argon2 |
|---|---|---|
| Primary Goal | Delay | Password hashing |
| Enforce a prescribed sequential delay while producing an output that is efficiently and publicly verifiable; common applications include randomness beacons and leader election. | Password hashing and password-based key derivation; RFC 9106 also includes proof-of-work applications. | |
| Parallelism | Sequential | Parallelizable |
| Sequential by design: the delay function must take time even on a parallel computer. Some proof-related work can be parallelized, but the core delay remains the sequential bottleneck. | Parallelizable by design: Argon2 aims at effective use of multiple computing units, and its parallelism degree is the number of parallel threads. | |
| Resource Bottleneck | Compute | Memory |
| Dominated by sequential serial work; in common RSA-group VDFs this is repeated squaring, and concrete hardware work focuses on low latency modular squaring. | Memory-hard: Argon2 is explicitly a memory-hard function with tunable memory cost, plus time and parallelism parameters. | |
| Verification Speed | Asymmetric | Symmetric |
| Asymmetric: evaluation requires the delay, while verification is fast; the original paper says its constructions achieve an βexponential gap between evaluation and verification timeβ. | Verification is ordinary password checking against an encoded string containing the parameters, salt, and hash; there is no separate VDF-style succinct proof. | |
| Hardware Resistance | Parallel processing | Specialised hardware |
| Resists parallel speedup, but custom low-latency hardware can still reduce wall-clock time; that is why VDF hardware projects optimize modular-squaring latency. | Raises the cost of GPU and ASIC attacks by making attackers pay heavily for memory: the reference implementation says Argon2d is highly resistant against GPU cracking attacks, and the spec says Argon2 increases the time-area product for ASIC-equipped adversaries. |
Replacing Argon2 with a VDF gives an attacker using a Field-Programmable Gate Array (FPGA) a massive advantage. The attacker's objective is not to compute one path faster than the user, but to compute thousands of possible path permutations to discover all active chatrooms. Because standard VDFs have a tiny memory footprint, an attacker can program a single FPGA to house thousands of independent VDF-evaluating circuits without facing a physical memory bandwidth bottleneck.
I'd just met ArticMine, the Monero developer, at CCC. He'd told me all about their RandomX algorithm that uses ALL CPU instructions making specialised hardware like ASICs pointless.
I looked at him like Bill and Ted look at Rufus.
"Whoa, you can do that?"
That somehow made things click.
So what about both? Chaining a VDF before the Argon2 hash? Each permutation would cost real, non-negotiable wall-clock time plus memory. Two different bottlenecks: single-threadedness and RAM.
This is one of those beginners' mind things: either it's something useful an outsider sees, or it's completely dumb for reasons I don't understand yet...
It felt like a weird idea that comes from staring at the properties of two primitives rather than their intended use cases. They have opposing parallelism models β why would you chain them? Could you even?
Later, when I could ask an LLM to run this research for me, I learned people already do:
The Chia blockchain (founded by the creator of Bittorrent, Bram Cohen) did something similar with Proof of Space (storage-hard, like Argon2 is memory-hard) chained with a VDF to prevent attackers from instantly simulating alternate histories. The principle is the same: force the attacker to pay in two different physical resources that can't substitute for each other. It was an alternative to Proof of Work and Proof of Stake consensus systems.
The IETF's recent draft "Proof of Process" protocol is a way of verifying a document was created by a human, not an AI.
It introduces Proof of Biological Space-Time (PoBST) to enforce temporal monotonicity and Cross-Domain Constraint Entanglement (CDCE) to bind behavioral entropy (human jitter) and physical state (thermodynamics) to the document's evolution.
It combines VDFs with Argon2id specifically to create multi-layered constraints: temporal ordering from the VDF, memory-bandwidth bottlenecking from Argon2.
So the combination isn't totally novel. What might be novel β or might be naive β is applying it to this specific problem: making path-hash computation expensive enough to deter permutation attacks while staying cheap enough for a single honest user on a phone. The parameters are very different from blockchain consensus, and I haven't found anyone doing exactly this.
Which either means there's a gap, or there's a reason nobody bothers.
I might have stumbled on this same cool combination, but I'm no Bram Cohen.
Time windows
Onto the next problem. Time.
An attacker can pre-compute hashes at leisure, then wait for people to show up.
The obvious approach: include the current hour (as hours since epoch) in the hash input. Different hour, different hashes. So an attacker would have to repeat their work every hour.
But this has two problems. First, the edge case: at 2:01, a hash generated at 1:55 won't match one from 2:05. So, each client needs to compute hashes for both the current and previous hour.
Second, and more interesting: hours since epoch are predictable. An attacker can still pre-compute next hour's and all of tomorrow's and next year's hashes. It's more work, but still possible.
This is where I found the NIST Randomness Beacon β a service that publishes cryptographically random values every 60 seconds.
By including each minute's beacon value in the hash input, you bind computation to real-world time. The attacker can't run ahead because next minute's value doesn't exist yet.
It also gives you a sliding window of one hour, adjusting every minute.
The trade-off: honest users now generate 60 hashes per path segment (one per minute of the sliding window). At two page visits per minute, that's 120 hashes per minute. The Argon2 parameters have to come down to stay feasible on a phone β 64 MB memory, time cost of 1, single-threaded.
Even with lighter parameters, an attacker trying all 3-page paths across 60 minutes faces 410,400 hash computations at ~700ms each (VDF + Argon2). Roughly 80 hours of sequential work per attempt window.
Maybe we'd be better off sticking to two one-hour hashes (current and previous) using the NIST on-the-hour beacon value.
I should note: the NIST beacon is a single point of trust. Alternatives exist β drand, blockchain block hashes β each with its own trust model.
And again, I ask myself, why not combine a few? Say, drand's beacon and Bitcoin's latest blockhash, for example. The difference in time windows would be an issue, but that's more of an implementation engineering problem than a cryptography one.
Finding each other without a server
If there were action movies about computer programming, "it's just an engineering problem" is what you'd hear a character say before dying a slow, painful, and totally predictable death.
Real life brings up real constraints.
And in real life, all of this is useless without a way to compare hashes. Say each client produces 120 hashes per minute. They need to find anyone in the network with a match.
Broadcasting everything to everyone falls apart fast. At 1,000 users, each client would check 120 hashes against 120 Γ 999 incoming hashes per minute, about 14 million comparisons. At 100,000 users, 1.4 billion.
Okay, using hourly hashes instead of minutely would help but there'd still be a scaling issue eventually. And the wonderful thing about these nerd-snipe explorations is you can let yourself get recursively nerd-sniped. It led me to more wonderful stuff:
Bloom filters compress a client's hash set into a fixed-size bit array (~8 KB). Instead of broadcasting 120 raw hashes, you broadcast the filter. Other clients test their hashes against it in constant time per check. False positives happen (the filter might say "maybe" when the answer is "no"), but false negatives never do. A potential match triggers a direct hash exchange to confirm.
Domain-hash clustering reduces who you talk to. Each visited domain gets hashed, and clients form gossip groups by domain. You only exchange Bloom filters with peers who've visited similar sites in the last hour. If someone hasn't visited any of your domains, they can't share your path. No point exchanging data.
With both mechanisms, the estimated load at 1,000 users is about 880 KB of bandwidth and 6 seconds of CPU per minute per client. At 100,000 users, assuming gossip reaches about 1,000 relevant peers, roughly 8 MB bandwidth and 60 seconds of CPU. That means the CPU is continuously running on a mobile device, so that's way beyond a practical maximum.
And clustering means most real scenarios involve far fewer relevant peers. Intuitively, there's a bunch of observability problems this creates.
Still more questions
Here's where I've left the exploration. I'll have more fun later, and discover new things with these questions:
The Bloom filter attack surface. Broadcasting Bloom filters across the gossip network might itself create the attack vector. If an AI agent can collect Bloom filters from every cluster, it can test pre-computed hashes against them without ever joining a room β just listening to the gossip layer. The Bloom filter is meant to reduce bandwidth, but it might also reduce the attacker's work. Will domain clustering make this even worse? Do the Bloom filters need their own layer of protection?
Gossip bootstrapping. The network needs seed nodes. Domain clustering assumes enough users to form meaningful clusters. Won't this create a separate attack surface?
The mobile computation budgeting is way off. 120 VDF+Argon2 computations per minute at 700ms each is 84 seconds of work per minute. That's more time than there is in a minute. Something has to give β fewer hashes, lighter parameters, background processing that runs behind real-time. Or probably a fundamentally different approach to the time windows.
VDF implementation is non-trivial. RSA-based VDFs require a trusted setup. Getting this wrong doesn't just reduce security β it can break the whole construction. I've participated in various ceremonies before, but just as a good citizen. This is another category I can explore with a fun use case driving my curiosity.
I'm deep in the Dunning-Kruger well. I'm a coder that learns about cryptography as a black box (though I like to rip open the box and poke at the math to try to understand it.) So when something feels easy, alarm bells ring.
If you see something wrong, I'd genuinely like to know.
Silly, but it matters
There's a dystopian version of the internet where every space is indexed by default β where walking through content leaves traces that get aggregated, correlated, sold, trained on models that are used against us. I think we're in that timeline.
Ephemerant is an experiment in the opposite direction: spaces that exist only because of shared movement, that dissolve when the movement passes, that can't be mapped by anyone who wasn't walking the same path.
The engineering is hard. The attack model is unforgiving. The math might not work (yet). But the core idea feels like something worth building toward: that shared experience could produce shared secrets, without anyone in the middle.
The finite game is getting the cryptography right.
The infinite game is enabling the people who share a path to be the only ones who decide on it.