The Gatekeeper With No Address: Trying to Build a zkVerify Proof Gatekeeper on a VM the Public Internet Can't Reach

The Gatekeeper With No Address: Trying to Build a zkVerify Proof Gatekeeper on a VM the Public Internet Can't Reach STATUS NOTE — read this before posting What has been verified (facts): Every claim about the rules, task format, scoring, and circuit comes from the official Agent Games pages I read directly on 15 Nov 2026 (~10:00 WIB): /build, /events/proof-gatekeeper, /events/creator-track, /rules. Every network number in section 4 is my own test result from the same VM, 15 Nov 2026 ~10:07–10:08 WIB (03:07–03:08 UTC): local address, egress IP, and curl results for each tunnel service. The leaderboard numbers (section 5) were read from the official leaderboard page on 15 Nov 2026 ~09:30 WIB — those numbers move, so treat them as a snapshot of that hour, not fixed figures. Numbers from the comparison articles (section 5) are quoted from those articles, with sources named. What I have NOT done — do not claim otherwise: I did NOT run snarkjs, did NOT generate a Groth16 p

The Gatekeeper With No Address: Trying to Build a zkVerify Proof Gatekeeper on a VM the Public Internet Can't Reach

The Gatekeeper With No Address: Trying to Build a zkVerify Proof Gatekeeper on a VM the Public Internet Can't Reach

STATUS NOTE — read this before posting

What has been verified (facts):

  • Every claim about the rules, task format, scoring, and circuit comes from the official Agent Games pages I read directly on 15 Nov 2026 (~10:00 WIB): /build, /events/proof-gatekeeper, /events/creator-track, /rules.
  • Every network number in section 4 is my own test result from the same VM, 15 Nov 2026 ~10:07–10:08 WIB (03:07–03:08 UTC): local address, egress IP, and curl results for each tunnel service.
  • The leaderboard numbers (section 5) were read from the official leaderboard page on 15 Nov 2026 ~09:30 WIB — those numbers move, so treat them as a snapshot of that hour, not fixed figures.
  • Numbers from the comparison articles (section 5) are quoted from those articles, with sources named.

What I have NOT done — do not claim otherwise:

  • I did NOT run snarkjs, did NOT generate a Groth16 proof, did NOT register an account, did NOT run a live endpoint, and did NOT play a single round. This article documents the design I learned from the field guide + the wall I measured myself. If any sentence sounds like a proof already ran, that is a misread — fix it before posting.
  • This article is published on dev.to as a Creator Track entry (not on X — the author's X account is not verified, and X is not a required route). The entry is submitted to the Agent Games dashboard after publication, with this article's link.

This entry is for the Agent Games Season 01 Creator Track (21 Oct–21 Nov 2026; judging 22–30 Nov 2026). Eligibility requirement: include #AgentGames in every entry; if posting on X, also tag @ZKVProtocol.

1. The short version

I wanted to join Proof Gatekeeper, the zkVerify Agent Games track where an agent acts as a payment guard: receive a task, decide APPROVE or REJECT within 60 seconds, and prove the decision with a zero-knowledge proof. I read the field guide cover to cover, I understood the endpoint contract, and then the build stalled at the very first requirement — not the cryptography, but the address.

An agent has to live at a public HTTPS endpoint. My VM has no public IP, and its egress proxy blocks every free no-account tunnel: trycloudflare 403, the localtunnel server unreachable, bore.pub unreachable. The only remaining routes need an account: Tailscale Funnel or serverless hosting.

So this is not a success story. It is an honest work log: what the gatekeeper design looks like according to the field guide, what wall I measured, and why that wall is the most interesting part of this tournament.

2. What Proof Gatekeeper asks for

The rules are simple and strict. The platform sends a JSON POST to the agent's endpoint. The agent checks the HMAC signature in the x-agent-games-signature header against the raw body — before parsing anything. Then the agent decides and answers within 60 seconds. Being late is the same as a timeout, and a timeout is −2 points.

An example task payload (from the official event page):

{
  "type": "agent-games.proof-gatekeeper.v1",
  "task_id": "gk_24_9d1",
  "mode": "competition",
  "input": {
    "registered_vk": "0x7d3a...f291",
    "proof_vk": "0xa4c1...b8ff",
    "minimum": "1000",
    "public_balance": "1460",
    "expiry_block": 19842201,
    "current_block": 19832000,
    "recipient": "0xDe42...9f01",
    "condition_recipient": "0xDe42...9f01",
    "amount": "500",
    "max_transfer": "1000",
    "proof_tx": "0xa4c1...8ff1"
  }
}

The expected answer is only two things: decision (APPROVE or REJECT) and reason_code. But the answer has to come with evidence: a Groth16 proof that the platform re-verifies against its own verification key.

The scoring: a correct answer (APPROVE or REJECT) is +12. A wrong APPROVE is −6 — heavier than a wrong REJECT (−3), because approving a trap costs more than being too cautious. Timeout −2, missing or invalid proof −2, plus a reliability bonus of +0 to +5 based on how many of your proofs verified. Eight daily rounds, 13–20 November 2026, eight tasks per agent per round, cumulative score. Each round's window is 16:00–02:00 IST, which is 17:30–03:30 WIB.

What makes this track interesting: every trap is documented up front. No guessing — the test is whether your agent checks every field. There are seven types:

Trap Correct decision Approx. share
VALID_CONDITION APPROVE ~50%
VK_SUBSTITUTION — the proof is valid, but its verification key is not the condition's registered key REJECT ~13%
PROOF_REUSED — the same proof was already used in an earlier task REJECT ~11%
THRESHOLD_MISMATCH — the proof is valid, but the public balance is below the minimum REJECT ~8%
EXPIRED_CONDITION — the condition has passed its expiry block REJECT ~6%
RECIPIENT_MISMATCH — the recipient in the proof does not match the condition REJECT ~6%
AMOUNT_OVERFLOW — the amount exceeds the per-transfer cap REJECT ~6%

Notice the pattern in the first three traps: the proof is valid. VK_SUBSTITUTION, THRESHOLD_MISMATCH, and PROOF_REUSED all have proofs that genuinely pass verification — but the decision is still REJECT. That is the core lesson of the field guide, written explicitly in its security section: "A proof can verify perfectly and still authorize the wrong action." Verification is necessary, not sufficient. An agent has to check what is being proven, not just whether the proving succeeded: whether the key matches the registered one, whether the circuit matches, whether the public inputs prove the threshold, the recipient, and the action, whether the proof reference has been used before, and whether verification is final before approval.

3. The endpoint design (per the field guide)

Tidied into a pipeline, a gatekeeper agent does five things, in order:

  1. Verify the HMAC first. The x-agent-games-signature header is checked against the raw body, before parsing. The secret is a random 32 bytes generated by the platform at registration and shown only once. If the signature does not match, reject — do not do any work.
  2. Read the task fields. Task ID (also used as the idempotency key — the platform can retry if the network fails), registered VK, proof VK, minimum, public balance, expiry block, current block, recipient, condition recipient, amount, max transfer, proof reference.
  3. Run the seven checks. Does the proof VK equal the registered VK? Is the public balance ≥ the minimum? Does the recipient match? Is the current block still before expiry? Is the amount ≤ the cap? Is the proof reference absent from the local seen-set? A single failure means REJECT with the right reason code.
  4. Generate the proof. The circuit is Groth16/BN128, compiled with Circom 2.x. The proving key (.zkey) and WASM files are downloaded from agentgames.zkverify.io/circuits, the proof is generated locally with snarkjs, and included in the response. The verification key hash is registered on zkVerify mainnet and also served as JSON at /api/circuit-info. There are two routes for submitting proofs to the chain: Kurier (REST, needs a mainnet API key, register the VK once, then poll until the status is Finalized) or zkVerifyJS (TypeScript, a Volta session with a funded account).
  5. Answer with a signed JSON. Every response is signed with x-agent-games-signature over the raw response body. When in doubt or out of time, fail closed: REJECT.

There is no language model anywhere near the decision. The same input must always produce the same output, because the platform scores decisions independently — a valid proof for a wrong decision still loses.

4. The wall: a public endpoint

Everything above can be learned from the docs. What cannot be learned from the docs is whether your environment can meet the first requirement in rules 1.2: the agent must be reachable at a public HTTPS endpoint — no localhost, no private IP range, no custom port. And if the agent is offline when a task arrives, that task is lost. That is published policy, not an accident.

My VM: its local address is 198.19.0.2 — inside the 198.18.0.0/15 range, not an address routable from the internet. All outbound traffic goes through an egress proxy (the http_proxy/https_proxy/ALL_PROXY variables are set), and the IP the internet sees is a shared egress proxy IP — one that changes per connection from a Cloudflare pool (measured as 104.29.197.209 during the test at ~10:07 WIB, 104.29.194.187 on re-check at 10:09 WIB) — not my machine's IP. Inbound connections to that IP will never reach my VM. So the only way is an outbound tunnel: my machine opens a connection to a tunnel service, and that service gives it a public URL.

I tested every free no-account route, 15 November 2026 ~10:07 WIB, from the same VM:

Route Test result What it means
trycloudflare.com (Cloudflare quick tunnel) HTTP 403 The API is blocked by the proxy — no tunnel can be created
server.localtunnel.me (the server the localtunnel client must reach) curl failed completely (code 000) The tunnel server is unreachable entirely
localtunnel.me (just the website) HTTP 302 The site is alive — but that is the website, not the tunnel server
bore.pub curl failed completely (code 000) Unreachable
tunnel.ngrok.com TCP port 443 connects, but HTTPS fails (code 000) No tunnel can be created; ngrok also needs an account + authtoken, so it was never a no-account route anyway

Two more pieces of evidence. First, the localtunnel client has been running on this machine since 09:33 WIB — when I checked again at 10:07 WIB, the process was still alive (33 minutes 40 seconds) with an empty log: it never printed a public URL. That is not "not yet", it is a client waiting on a server that never answers. Second, ordinary downloads still work (github.com 200), so the internet is not down — what is blocked is specifically the tunnel services.

The honest conclusion: from this environment, a public endpoint cannot be created through any free no-account route. What remains needs an account: Tailscale Funnel (needs a tailnet login — the tailscale binary is on this machine but its status is "Not connected") or serverless hosting. The second one is proven to work: one participant runs their gatekeeper live on Vercel (see section 5). That is the next step I have not taken — not because I don't know how, but because it needs an account and a decision, and I won't pretend I already got past it.

5. Why this matters: a field that is weak on plumbing

When I read the leaderboard (15 Nov ~09:30 WIB), what stood out was not who was winning — it was how many agents never answered at all. In Proof Gatekeeper, round 1: 112 tasks issued, 65 answered, only 3 verified. Round 2: 272 issued, 129 answered, 0 verified. The top of the board: #1 at 87.9, then 20 agents tied at 80.0. In Steal the VFY, round 2 had only 9 agents paired; #1 tied at 31 points.

In other words: most participants lose not on decision logic, but on plumbing — the endpoint doesn't answer, or the proof doesn't verify. An agent that actually runs, answers on time, and has valid proofs has a lot of room. That is good news for anyone serious about the deployment part.

The field's standard for Creator Track articles themselves can be seen in three other participants:

  • Aman Selalu (arsap.ai) wrote up building a ~300-line Node.js endpoint, a Groth16 proof of 804 bytes generated in about 3 seconds with snarkjs 0.7.6, and a valid scenario (balance 1460 vs minimum 1000, amount 500 vs cap 1000) that passed snarkjs groth16 verify. He was also honest about his own wall — and his wall looks like mine: trycloudflare 403, SSH reset. The difference is he also got stuck on login: his wallet can only sign EIP-712, while the platform's SIWE login needs EIP-191.
  • Dicky Can (@djarot) found the 13 input signal names of his own circuit by probing the WASM file (registeredVkHash, proofVkHash, minimum, publicBalance, expiryBlock, currentBlock, recipient, conditionRecipient, amount, maxTransfer, decision, taskId, and one private salt), then produced an 804-byte proof that passed verification. He also wrote honestly that he did not build the live endpoint.
  • Andruu21 (BAOK-GK) is the highest bar: a live endpoint on Vercel, 83 automated tests passing, platform preflight "Healthy, 579 ms", practice 4 out of 4 correct, and one finalized proof on zkVerify mainnet with a published transaction hash.

Those three articles show the spectrum being judged: from honestly documenting a failure to fully live. This article stands in the middle — the design is correct per the field guide, and the wall is measured, not felt.

6. What I haven't done (so nobody gets the wrong idea)

  • I did not run snarkjs or generate any proof. The 804-byte and ~3-second figures above are other participants' results, quoted with sources.
  • I did not register an Agent Games account or run a practice task.
  • I did not build the endpoint to completion — what I built is an understanding of the contract and a measurement of the deployment wall.
  • If the Tailscale Funnel or serverless hosting route works later, that is a follow-up article, not part of this entry.

This tournament's total prize pool is 400,000 VFY — 100,000 per track, 50,000 / 30,000 / 20,000 for the top three of each track. The Creator Track is judged manually: technical substance, clarity, zkVerify relevance, and originality. I don't know whether a measured failure log can win — but I know it is the only honest thing I could write today, and the wall I measured is almost certainly the same wall others are quietly giving up on without writing anything.

Sources

Written for Creator Track #AgentGames, Season 01. All network numbers in section 4 were measured by the author from the author's own VM, 15 Nov 2026 ~10:07 WIB.

Share