
TL;DR
In 2018 a developer named Johan Nygren deployed a set of “proof-of-public-key” bounty contracts on Ethereum mainnet and funded them with real ETH. Submit the right public key and the contract pays you. Eight years later, three of them are still holding ~2 ETH, plus a fourth holding 1 ETH that is bricked.
I spent a long time trying to claim them. The result is not a key — it’s a proof, from about fifteen independent directions, that there is no findable key. But the interesting part isn’t the verdict; it’s the hunt: resurrecting two decommissioned testnets, a Shodan banner-search that found a node the rest of the internet forgot, a full sweep of every place this man ever published code, and the on-chain forensics tying it all together. This post is that methodology, start to finish.
Four contracts, ~3 ETH, one dead end
Everything traces back to one deployer, 0x4c5D24A7 (“bipedaljoe”), who created 18 contracts in a 16-day burst in early 2018 and then went silent. Four are the prizes:
| Bounty | Address | Value | Why it’s unrecoverable |
|---|---|---|---|
| A | 0x17e5e091… |
1.0 ETH | 256-bit random key, never published |
| B | 0xd7c6d542… |
0.5 ETH | same, two-layer “symmetric” variant |
| C | 0x973c2178… |
0.5 ETH | same, hash + commit-reveal (plus a real flaw — see below) |
| bricked | 0xaec7e8c2… |
1.0 ETH | key is known, but the contract has no payout opcode — pays nobody, ever |
Bounty C today, still holding 0.5 ETH under a long tail of failed authenticate() attempts:
Bounty C (0x973c2178) on Etherscan — funded, dormant, every solve attempt reverted.
The fourth one is the saddest. Its key is actually known and verified on-chain — but the contract was compiled without a CALLER opcode in its payout path, so even the correct solver gets nothing. One ETH, frozen by a typo.
How the lock works
To claim, you submit the 64-byte public key P. The contract stores a hardcoded proof, and the proof is the key’s own signature, XORed with the key itself:
|
|
P appears on both sides — it is its own fixed point, P = f(P): the public key you must submit is folded into the very signature it has to produce.

Forward (knowing P): trivial — sign, XOR, submit. Backward (knowing only the proof): no shortcut. XOR lives in a different algebra (GF(2)²⁵⁶) than the elliptic curve, so it never lifts into anything solvable. The cheapest real attack is a brute-force search for any P that hashes to the same address: 2¹⁶⁰ work — ~4.6 × 10¹⁹ years with the entire Bitcoin network’s hashpower.

And you can’t reverse it, either. The message hash e = keccak256(prefix ‖ P) sits inside the equation and depends on P, so every guess moves the target — the equation chases its own tail. That kills BSGS/meet-in-the-middle (no independent split), Pollard’s rho (no single iterated map), and Gröbner/algebraic inversion (that’s just keccak preimage resistance by another name). There is no fifth door.

So if the math is a wall, the only physically possible path is a leak — the key sitting in some log, repo, transaction, or post the author forgot about. The rest of this is the hunt for that leak.
Reading two dead testnets
The strongest lead: maybe Johan tested the bounties on a testnet in 2018 and left a key in the calldata. That meant reading Ropsten and Rinkeby — both long decommissioned, with their public RPCs shut down in 2023.
Ropsten — self-hosted archive node. I stood up an Erigon archive node on my own server to get complete Ropsten history. Erigon’s snapshots ship as pure BitTorrent (DHT + public trackers, no webseed), so this was a genuine sync — pulling and indexing segments, forcing early-segment availability. Once queryable, I scanned ~9.3 million blocks for all of Johan’s known addresses and pulled 220 authenticate calls to test against the engine. Zero hits. Ropsten: dead end, definitively.
Rinkeby — the hard one. Rinkeby is Clique proof-of-authority, which is excluded from every snapshot/era1/torrent system — so a local sync had nothing to sync from. Before the breakthrough, I exhausted the entire registered world looking for any surviving endpoint:
- ~80 RPC endpoints across every historical provider (Infura, Alchemy, Ankr, Cloudflare gateway, and ~70 more) — all dead.
- Source-code search for hardcoded live RPCs across GitHub (~60 endpoints in old configs/
.envfiles, all dead), GitLab (free-tier global code search disabled — 403), and Bitbucket. - Data indexers — Covalent/GoldRush and Bitquery (confirmed they’d dropped Rinkeby), Crystal Intelligence (mainnet-only), and Google BigQuery across 11 EVM chains (which is what flagged one address,
0x2513CF99, as Rinkeby-only — the thread that justified the whole hunt). - Censys — free tier blocks the search API entirely.
Every registered path was dead. Which forced the reframe that finally worked.
The Shodan reframe — finding a node nobody remembers
Stop asking “who still serves Rinkeby?” and ask “who accidentally left a Rinkeby node exposed and running?” Shodan indexes the JSON-RPC banner of every exposed :8545 host on the internet — including its eth_chainId response. So:
|
|
I then probed 3,979 candidate Geth/:8545 hosts Shodan knew of to confirm that match was unique for eth_chainId == 0x4. It was. And it was real:
|
|
A live, queryable Rinkeby archive node on no provider list anywhere — a relic someone forgot to switch off. This was the genuine technical achievement of the project, and the method generalizes: to read a dead chain, don’t look for infrastructure that preserved it — look for a node that never stopped. (I redact the host here so this writeup doesn’t point at a stranger’s misconfigured box.)

The scanner. The node had no tx index, no debug/trace, and only head state — but full block bodies, which is all you need to read historical calldata. So I wrote rinkeby_scan.py:
- Parallel batched JSON-RPC (
eth_getBlockByNumberwith full transactions), resumable per-range checkpoints, and a recovery loop that waits out node blips instead of dying (it was a single fragile box). - Two detectors per transaction: the
authenticateselector0xee0d605c(extract the 64-byte public key → live-test against A/B/C), and any mainnet proof value showing up in calldata (a “twin-hunt” for a Rinkeby copy of A/B/C). - A bug I caught: the first extractor read the ABI offset/length words instead of the key bytes (
bytesargs areselector‖offset‖length‖data) — fixed and re-ran every hit.
I scanned the full 2018 window and then the whole chain. 0 mainnet twins, 0 solving public keys.
The red herring. What the scan did surface was a cluster of real TeikhosBounty activity — resolved by CREATE-address arithmetic: a hobbyist (0x6daa5ca5…) had deployed 34 of the 41 testnet bounty contracts and self-claimed a dozen (one with an off-curve junk “key”). And 0x2513CF99 — the address that sent me to Rinkeby in the first place — is exactly CREATE(0x6daa5ca5, nonce 85): that hobbyist’s Rinkeby SHA3-512 helper, confirmed by bounty C’s own source comments. Johan’s mainnet deployer had never transacted on Rinkeby (nonce 0). The node was a triumph; the lead was a ghost.
The bulk census — BigQuery, Etherscan, and the deployment map
Two tools did the heavy on-chain forensics without running a full mainnet node:
- Etherscan gave verified source for all 18 deployed contracts, their ABIs, the internal-transaction graph, and the deployer cluster (
0x4c5d24a7/0x125d657d/0x4f6816a7/0x80028f80). It also confirmed the bricked contract on a live transaction. - Google BigQuery (
bigquery-public-data.crypto_ethereum, via Application Default Credentials) ran the census: every contract the deployer ever touched, transaction counts, the full deployment timeline, and the one solver transaction at block 14,513,678. For census questions this is faster and more reliable than a node. The same approach, run across six chains (ETH/ETC/BTC/LTC/DOGE/BCH), covered the cross-chain angle.
That census draws the whole operation. The only contracts Johan ever solved himself are the tutorials; the four he never touched again are exactly A, B, C, and the bricked one:
All 18 contracts from one deployer in 16 days — the tutorials he answered himself, the vaults he never did, and the silent twins.
The source hunt — finding the original code he wrote
If the key ever leaked, the likeliest place was the author’s own published code. So I swept every host he ever used, in full git history.
- GitHub (
resilience-me): 51 repos and 139 gists, back to 2014. Crucially, the keygen source was found only via gist enumeration (api.github.com/users/<handle>/gists), not in any repo:g_6.sol— a fully-worked C-type demo that publishes the public key, message hash, signature, proof, and address (I verified it end-to-end against the engine) — andall_30.txt, the actual key-generation script. - Dependency-date pinning:
all_30.txtuses[email protected]→[email protected]. Fetching that exact version from unpkg pinned the keygen precisely:Account.create()=keccak256of fourcrypto.randomBytes(32)draws — a 256-bit OpenSSL CSPRNG key with RFC-6979 nonces. The author’s own generator rules out a weak seed. - GitLab (
bipedaljoe): 33 repos, mirrors and experiments — no extra deployments. - Bitbucket (
bipedaljoe): 14 repos visible via the API; the web profile 404s; theteikhosandbitpeopleworkspaces had been deleted. priv.prv— a tantalizing filename — traces to hisVanityreumrepo: it’s the.gitignore’d output of a vanity-address generator built onos.urandom, never committed. No key file exists on any disk, repo, or leak.- Archive recovery: Software Heritage had preserved 40
resilience-meorigins — enough to confirm the keygen and contract provenance independent of live GitHub, ruling out post-hoc edits. The Wayback Machine held 0 captures of the deleted Bitbucket workspaces. That development history is gone — but it doesn’t matter, because the deployed bytecode and the npm dependency chain are fully verified on their own.
The gists (resilience-me) — g_6.sol and all_30.txt live here, not in any repo.
The author’s own words — the forum posts
The last surface was his prose, read straight off the chains and threads where he published it.
- Steemit (
johan-nygren,teikhostag), pulled via the Steem blockchain API (condenser_api.get_discussions_by_blog) rather than the web frontend. His framing is explicit: “approximate perfect security”, and — settling the weak-RNG question in his own words — “truly random keys.” - EIP-935, the 2018 GitHub thread where he proposed the scheme. An Ethereum developer (
@3esmit) reviewed it and reached the exact conclusion I did eight years later: it’s “just one more hashing on top of address private key bruteforce” — the 2¹⁶⁰ wall, on the public record since 2018.
EIP-935 — @3esmit characterizing the security as one hash on top of address-bruteforce, with Johan (resilience-me) replying.
Here is every surface swept, with its yield:
| Host / source | Handle | What it gave | Key-relevant? |
|---|---|---|---|
| Etherscan | — | verified source for all 18 contracts, deployer cluster, bricked-contract proof | ground truth |
| Google BigQuery | — | mainnet + cross-chain census, the 2022 solver tx | census, no key |
| Ropsten Erigon archive | — | 9.3M blocks, 220 authenticate calls |
0 hits |
| Rinkeby node (Shodan) | — | only surviving Rinkeby archive; full 2018 calldata | 0 twins, 0 keys |
| GitHub | resilience-me | 51 repos + 139 gists; g_6.sol, all_30.txt |
source & keygen, no key |
| npm / unpkg | (dependency) | [email protected] → [email protected] keygen |
entropy proof |
| GitLab | bipedaljoe | 33 repos, mirrors | handle linkage |
| Bitbucket | bipedaljoe | 14 repos (API only); teikhos/bitpeople deleted | unrecoverable |
| Steemit | johan-nygren | “approximate perfect security”, “truly random keys” | author intent |
| EIP-935 | — | @3esmit: 2¹⁶⁰ wall, in 2018 |
third-party confirmation |
| Software Heritage | resilience-me | 40 preserved origins | provenance |
| Wayback Machine | bipedaljoe | 0 captures of deleted repos | unrecoverable |
And the cryptographic battery that ran in parallel — every attack draining to zero:

The one that was solved — and the flaw hiding in it
One sibling contract — a 0.5 ETH demo — was drained back in 2022. Here it is, balance zero:

It was solvable for one reason: its answer key was published off-chain. And how it drained exposes a genuine, previously-unreported flaw in the C-type contract — so I checked whether that flaw could be turned against the live 0.5 ETH bounty. (Spoiler: no, and the “why not” is the interesting part.)
The flaw. C picks its winner in reveal() as whoever committed the earliest signature that recovers to their own address over isSolved.msgHash — not whoever submitted the solving key in authenticate(). And isSolved.msgHash is written to public storage the instant a solve lands. So if two parties both know the key, the one who committed first wins, regardless of who did the work. On the demo, exactly that happened: address 0x83e4b2a5 (earlier committer) walked off with the ETH while 0x9c739dfa — the address that actually called authenticate() with the valid key — got nothing.
Can it touch the live bounty C? I ran six independent adversarial analyses across every attack class — reward-path reachability, the commit-race, ecrecover edge cases (zero-address tricks, malleability), the external SHA3-512 helper, and state-machine griefing. Unanimous: no. Every path that moves the 0.5 ETH out — reveal(), reward(), the selfdestruct — is gated on isSolved.timestamp != 0, written in exactly one place: inside authenticate(), inside the if that only fires when ecrecover matches address(keccak256(P)). That’s the 2¹⁶⁰ wall again. No valid key → no solve → the contract is frozen in its Commit state forever, and the front-running flaw never becomes reachable. It’s a steal-from-a-future-solver bug, not a claim-without-a-key bug — and since C can never be solved, there’s no future solver to steal from. Real flaw, permanently dormant. (One subtlety: you can’t “commit garbage now and fix it later” — reveal() reads the signature stored at commit time and then deletes it, so a winning attacker must already know the key before the commit window closes. On the live bounty that window never opens.)
Proof it was deliberate, not a mistake
A natural last hope is that the author was sloppy — a weak RNG, a reused nonce, a wrong key in the XOR. His own git history kills that idea. Cloning the C bounty’s gist surfaced six distinct proof iterations he cycled through. Testing every key I hold against all six draws a perfectly clean line:

Three are demos he solved himself — keys public, I have them. Three were never solved and no key exists anywhere, including 7b5f8ddd…, the exact proof sitting in the live bounty C. The same hand drew both kinds and treated them oppositely: every demo got a published answer; no real bounty ever did. That’s not negligence — it’s intent.
Four investigators, one wall
By the end I wasn’t the only one who’d tried. Comparing notes, four independent efforts — mine, two other AI systems run cold, and a public bounty-hunter (@rebel0x0) still racing for the same money in 2026 — converged on the same verdict with different toolkits. The hunter, the one with the most to gain, solved only the published demo and reported “0 hits” on the real bounties after recovering 1,600+ sender pubkeys and testing 20,000+ candidates.

When independent searches with different tools all return zero, the convergence stops being four failures and becomes the proof: there is no shortcut, no reconstruction, and no leak — because there is nothing to find.
Verdict
The ~3 ETH cannot be claimed by anyone. Three bounties are sealed by sound cryptography; the fourth by the author’s own bug. This was never a failure to find a key — it’s a proof, from every direction I could devise, that there is no key to find.
What the hunt produced instead:
- A from-scratch, validated reimplementation of the whole cryptosystem, anchored to a real on-chain solve.
- A reusable method for reading a dead chain: Shodan banner-search to resurrect a forgotten archive node, BigQuery census in place of a full node, dependency-date pinning, gist enumeration, CREATE-address arithmetic, and archival recovery via Software Heritage.
- An on-chain proof, for about $0.05, that the fourth bounty is bricked.
- A responsible disclosure of a real, live commit-reveal flaw (below).
Sometimes the deliverable of a treasure hunt isn’t the treasure — it’s the toolkit you built to reach it, and a proof, beyond doubt, that no one else can reach it either.
Responsible disclosure — the C-type commit-reveal flaw
For completeness: the C-type TeikhosBounty contract binds its reward to the earliest committer who can produce a self-signature over the solution’s message hash, rather than to the account that submits the solving key. Because isSolved.msgHash becomes public the moment a solve lands, any party who already knows the solution key can win the reward out from under the legitimate solver by having committed earlier. This was demonstrated in the wild on the demo contract 0x735ba26f (winner ≠ solver). It does not affect the funds in the live A/B/C bounties, which can never be solved and are therefore never exposed to it. The fix, for anyone building a similar scheme: commit to keccak256(publicKey, msg.sender) rather than a bare signature, and bind the payout to the authenticate() caller — not to a separate, front-runnable reveal.
The contract author, Johan Nygren, remains publicly reachable, and all data used here is public — no private keys, private messages, or non-public systems were accessed. Every source was a public API or banner: Etherscan, GitHub, Google BigQuery (ADC), Shodan, Software Heritage, the Steem and Ethereum chains themselves.