<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 0xarihunter</title>
    <description>The latest articles on DEV Community by 0xarihunter (@0xarihunter).</description>
    <link>https://dev.to/0xarihunter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4245374%2F29a29ad2-bcd7-4cae-8d88-2e128ab80142.png</url>
      <title>DEV Community: 0xarihunter</title>
      <link>https://dev.to/0xarihunter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xarihunter"/>
    <language>en</language>
    <item>
      <title>The Gatekeeper That Cannot Lie: Building a Proof-Checking Agent for zkVerify Agent Games</title>
      <dc:creator>0xarihunter</dc:creator>
      <pubDate>Sun, 15 Nov 2026 04:24:12 +0000</pubDate>
      <link>https://dev.to/0xarihunter/the-gatekeeper-that-cannot-lie-building-a-proof-checking-agent-for-zkverify-agent-games-3ol4</link>
      <guid>https://dev.to/0xarihunter/the-gatekeeper-that-cannot-lie-building-a-proof-checking-agent-for-zkverify-agent-games-3ol4</guid>
      <description>&lt;p&gt;&lt;em&gt;Creator Track entry — Agent Games Season 01 · #AgentGames · @ZKVProtocol&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most payment approvals on the internet work on trust: a server says "this looks fine" and everyone hopes it checked everything. Proof Gatekeeper, one of the four tracks in the zkVerify Agent Games, removes the hope. My agent receives a proposed payment wrapped in a zero-knowledge proof, and its job is to decide — APPROVE or REJECT — in a way that can be verified by anyone, without trusting my server at all.&lt;/p&gt;

&lt;p&gt;This is the story of how I built that agent, what the circuit taught me, and why the most interesting property of the system is that &lt;strong&gt;my agent cryptographically cannot cheat&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The task
&lt;/h2&gt;

&lt;p&gt;Eight scored rounds, eight tasks per round. Every task is a signed JSON envelope describing a payment condition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a registered verification key (VK) for the condition,&lt;/li&gt;
&lt;li&gt;the VK the presented proof was actually generated with,&lt;/li&gt;
&lt;li&gt;a minimum balance the proof must demonstrate,&lt;/li&gt;
&lt;li&gt;an expiry block and the current block,&lt;/li&gt;
&lt;li&gt;a recipient and the recipient the condition demands,&lt;/li&gt;
&lt;li&gt;an amount and a single-transfer cap,&lt;/li&gt;
&lt;li&gt;a proof reference (&lt;code&gt;proof_tx&lt;/code&gt;) that must never be accepted twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roughly half of the tasks are valid. The rest are traps, and every trap type is documented in advance — the competition is not about guessing, it is about checking &lt;em&gt;every&lt;/em&gt; field, every time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trap&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;The mistake it punishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VK substitution&lt;/td&gt;
&lt;td&gt;~13%&lt;/td&gt;
&lt;td&gt;Confusing "the proof is valid" with "the proof is for this condition"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof reused&lt;/td&gt;
&lt;td&gt;~11%&lt;/td&gt;
&lt;td&gt;Accepting a genuine proof a second time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threshold mismatch&lt;/td&gt;
&lt;td&gt;~8%&lt;/td&gt;
&lt;td&gt;Approving a real proof that proves too little&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired condition&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;td&gt;Letting a later confirmation revive a dead payment window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recipient mismatch&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;td&gt;Paying the wrong party with a perfect proof&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amount overflow&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;td&gt;Ignoring the single-transfer cap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A correct decision scores +12. Approving a trap costs −6, rejecting a valid payment −3, and answering a provable task without a valid Groth16 proof costs an extra −2. Caution is cheap; negligence is expensive. That asymmetry shaped the whole design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: boring on purpose
&lt;/h2&gt;

&lt;p&gt;The agent is a small Node.js service with exactly one public endpoint. Three rules governed the build:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate before you think.&lt;/strong&gt; Every inbound request is verified with HMAC-SHA256 over the &lt;em&gt;raw&lt;/em&gt; body before any JSON parsing happens. A bad signature gets a signed 401 and nothing else — the decision code never sees unauthenticated input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic code decides, never a language model.&lt;/strong&gt; The decision is a fixed checklist, evaluated in a fixed order: reused proof → VK substitution → threshold → expiry → recipient → amount cap → approve. Same input, same answer, every time — which the competition demands anyway, because final trials are replayed and must reproduce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail closed.&lt;/strong&gt; Any internal error, timeout, or uncertainty produces a signed REJECT, never a default approval. The 60-second response deadline is generous next to our measured latency (below), but the rule stands: when in doubt, the gate stays shut.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every response is signed with the same HMAC scheme over its exact raw body, and every decision (except a reused-proof rejection, which the rules exclude) carries a Groth16 proof generated locally with snarkjs against the official gatekeeper circuit, plus its public signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the circuit taught me
&lt;/h2&gt;

&lt;p&gt;The part I did not expect: &lt;strong&gt;the circuit enforces the decision itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I probed the published &lt;code&gt;gatekeeper.wasm&lt;/code&gt; directly to enumerate its input signals — 13 of them: the two VK hashes, minimum, public balance, expiry and current block, both recipients, amount, transfer cap, the decision, a task identifier, and a private salt. Then I tried to misbehave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Valid condition + APPROVE → proof generates, verifies. ✅&lt;/li&gt;
&lt;li&gt;VK-substitution trap + REJECT → proof generates, verifies. ✅&lt;/li&gt;
&lt;li&gt;VK-substitution trap + APPROVE → &lt;strong&gt;witness generation assert-fails.&lt;/strong&gt; No proof exists for the wrong answer. ❌&lt;/li&gt;
&lt;li&gt;Valid condition + REJECT → &lt;strong&gt;assert-fails&lt;/strong&gt; the same way. ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read that again: it is not merely that the platform re-checks my proof and would catch a lie. For a wrong decision, &lt;em&gt;there is no proof to submit&lt;/em&gt;. The circuit is not a witness to my decision — it is a referee that only signs correct ones. "Verification is necessary, not sufficient," the builder guide warns; in practice the circuit goes further and makes sufficiency the only provable state.&lt;/p&gt;

&lt;p&gt;Probing also settled the boundary semantics that documentation leaves implicit, and each answer came from the circuit, not from opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;balance &lt;strong&gt;equal&lt;/strong&gt; to the minimum is valid;&lt;/li&gt;
&lt;li&gt;amount &lt;strong&gt;equal&lt;/strong&gt; to the transfer cap is valid;&lt;/li&gt;
&lt;li&gt;current block &lt;strong&gt;equal&lt;/strong&gt; to the expiry block is already &lt;strong&gt;expired&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one changed my server code (the expiry comparison is &lt;code&gt;&amp;gt;=&lt;/code&gt;, not &lt;code&gt;&amp;gt;&lt;/code&gt;), and my test harness gained an equality case for every boundary. This is what building against a proof system feels like at its best: the spec's ambiguities have objectively correct answers, and you can extract them by experiment instead of by argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measured, not claimed
&lt;/h2&gt;

&lt;p&gt;Local test results for the finished agent (signed harness replaying the official envelope format; every attached proof verified locally against the exported verification key):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;11/11 checks pass&lt;/strong&gt; — preflight handshake, valid approval, all six trap types (including both expiry boundaries), proof-reuse on a duplicate reference, and the bad-signature path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response time 0.24–0.94 s per task&lt;/strong&gt;, proof generation included — roughly 1–2% of the 60-second deadline, which buys an enormous safety margin for the tighter practice-mode window.&lt;/li&gt;
&lt;li&gt;The seen-set of proof references is persisted to disk, so a restart cannot amnesia the agent into accepting a replayed proof.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;Two things only the live platform can settle, and I am flagging them rather than papering over them. First, the circuit treats the task identifier as an opaque field element; my encoding (keccak256 of the task id string) proves and verifies locally, but whether the platform re-derives the identical value will only be proven by a real practice task. Second, the salt's semantics are undocumented — I use a fixed salt from configuration, which proves fine, but a scored round is the real exam. Both are exactly what practice mode is for, and both are reasons the agent's checklist mirrors the circuit's logic instead of inventing its own: where my encoding choices end, the deterministic checks still stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this track matters
&lt;/h2&gt;

&lt;p&gt;Strip away the competition and what remains is a pattern the wider ecosystem needs: an autonomous agent whose consequential decisions are not trusted but &lt;em&gt;proven&lt;/em&gt;. A gatekeeper that cannot lie about why it opened the gate — because the proof of a wrong decision does not exist — is a stronger primitive than a gatekeeper with a perfect audit log. Audit logs tell you what an agent claims it did. This circuit tells you what it could possibly have done. That difference is the whole point of zkVerify, and it is why I built for this track.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Agent: "Bansur Gatekeeper", competing in Proof Gatekeeper, Agent Games Season 01 (handle: arihunter). Built with Node.js, snarkjs, and the official gatekeeper circuit (Groth16 / BN254). #AgentGames&lt;/em&gt;&lt;/p&gt;

</description>
      <category>zkverify</category>
      <category>ai</category>
      <category>web3</category>
      <category>cryptography</category>
    </item>
  </channel>
</rss>
