<?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: Hein Dauven</title>
    <description>The latest articles on DEV Community by Hein Dauven (@heindauven).</description>
    <link>https://dev.to/heindauven</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%2F231232%2F17c226ff-44cb-48be-9908-d29311f4a6bb.jpg</url>
      <title>DEV Community: Hein Dauven</title>
      <link>https://dev.to/heindauven</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/heindauven"/>
    <language>en</language>
    <item>
      <title>Building a browser game with client-side Groth16 proofs</title>
      <dc:creator>Hein Dauven</dc:creator>
      <pubDate>Sun, 26 Jul 2026 21:18:44 +0000</pubDate>
      <link>https://dev.to/heindauven/building-a-browser-game-with-client-side-groth16-proofs-26g9</link>
      <guid>https://dev.to/heindauven/building-a-browser-game-with-client-side-groth16-proofs-26g9</guid>
      <description>&lt;p&gt;A smart contract can't tell whether a submitted score came from a valid game or was simply made up. Dario Dash handles that by proving the run itself.&lt;/p&gt;

&lt;p&gt;I have been building &lt;a href="https://hdauven.github.io/dario/" rel="noopener noreferrer"&gt;Dario Dash&lt;/a&gt;, a small endless runner on Dusk. The game runs in the browser and does not require a wallet to play. After a ranked run, the browser can generate a Groth16 proof locally and submit the score to a smart contract.&lt;/p&gt;

&lt;p&gt;The contract does not trust the submitted score. It accepts it only after verifying the proof, binding it to the transaction sender and checking that the run seed has not already been used.&lt;/p&gt;

&lt;p&gt;The source is available on &lt;a href="https://github.com/HDauven/dario" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually needs to be proven?
&lt;/h2&gt;

&lt;p&gt;A score by itself says almost nothing. A client could simply submit any number it wants.&lt;/p&gt;

&lt;p&gt;For Dario Dash, a valid run includes much more than the final score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the player movement and jump timing&lt;/li&gt;
&lt;li&gt;the seed-derived obstacle schedule&lt;/li&gt;
&lt;li&gt;obstacle clearance and collision windows&lt;/li&gt;
&lt;li&gt;item pickups&lt;/li&gt;
&lt;li&gt;damage and game-over conditions&lt;/li&gt;
&lt;li&gt;fireball kills&lt;/li&gt;
&lt;li&gt;transitions between Regular, Super, Fire and Cape forms&lt;/li&gt;
&lt;li&gt;the number of ticks played&lt;/li&gt;
&lt;li&gt;the resulting score&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The proof must establish that these rules were followed from the initial state until the claimed final state. It also needs to bind the run to the account submitting it, otherwise somebody could copy another player's proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The repository is split into a few layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dash_zk&lt;/strong&gt; contains the deterministic game simulation used by the browser proving path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dash_core&lt;/strong&gt; contains a separate 60 Hz simulation used by the RISC Zero path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dash_web&lt;/strong&gt; exposes the Rust simulation to the browser through WebAssembly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;zk_browser&lt;/strong&gt; contains the Circom circuit and the JavaScript proof conversion code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;contract&lt;/strong&gt; verifies the proof and maintains the leaderboard on Dusk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;web&lt;/strong&gt; contains the playable Vite application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important boundary is that the game logic is deterministic and integer-only. Floating point physics would be a mess to reproduce consistently across JavaScript, WebAssembly, the proof circuit and the contract.&lt;/p&gt;

&lt;p&gt;The browser and contract also derive the obstacle schedule from the same seed. The complete schedule therefore does not have to be trusted as client-supplied input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the proof in the browser
&lt;/h2&gt;

&lt;p&gt;The browser path uses a Circom circuit of roughly 421,000 constraints. It covers the complete ranked run, including the movement, collision, item and scoring rules.&lt;/p&gt;

&lt;p&gt;When a run finishes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The game serializes the witness data and public inputs.&lt;/li&gt;
&lt;li&gt;A Web Worker downloads the circuit WebAssembly and the proving key.&lt;/li&gt;
&lt;li&gt;snarkjs groth16.fullProve generates the proof locally.&lt;/li&gt;
&lt;li&gt;The proof is converted into the compressed arkworks format expected by Dusk's BN254 verification host function.&lt;/li&gt;
&lt;li&gt;The browser submits the score, tick count, seed and proof to the contract.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Proof generation currently takes roughly 10 to 60 seconds depending on the machine. The proving key is around 200 MB, so it is published as an immutable release artifact and cached by the browser.&lt;/p&gt;

&lt;p&gt;That is a fairly ugly download for a small game, but it is also an honest representation of the current trade-off. Browser-native proving works, but it is not free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the contract verifies
&lt;/h2&gt;

&lt;p&gt;The contract does more than call a generic proof verifier.&lt;/p&gt;

&lt;p&gt;It reconstructs the public inputs using the transaction sender, recomputes the obstacle schedule from the seed and verifies the Groth16 proof through Dusk's verify_groth16_bn254 host function.&lt;/p&gt;

&lt;p&gt;Only after that does it update the account's best score and proven-run count. Used seeds are rejected to prevent straightforward proof replay.&lt;/p&gt;

&lt;p&gt;This means the browser is free to be malicious. It can change the displayed score, patch the JavaScript or construct its own request. None of that matters unless it can produce a proof for a run that satisfies the circuit and matches the public inputs reconstructed by the contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second proving path
&lt;/h2&gt;

&lt;p&gt;The repository also contains a RISC Zero path.&lt;/p&gt;

&lt;p&gt;Instead of proving the closed-form Circom simulation directly, a RISC Zero guest replays a recorded input trace using the 60 Hz Rust simulation. The execution proof is then wrapped into Groth16 and converted to the same format the Dusk contract expects.&lt;/p&gt;

&lt;p&gt;The two paths have different characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Circom is fast enough to prove directly in modern browsers, but the circuit has to model the game rules explicitly.&lt;/li&gt;
&lt;li&gt;RISC Zero proves execution of the Rust guest program, which makes more general logic easier to express, but the native proving workflow is considerably heavier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting both has been useful because it makes the trade-off concrete rather than theoretical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not solve
&lt;/h2&gt;

&lt;p&gt;A gameplay proof is not the same thing as a complete anti-cheat system.&lt;/p&gt;

&lt;p&gt;The circuit proves that a valid sequence of inputs exists. It does not prove that a human generated those inputs, that the user did not inspect future obstacles or that they were not running an automated player.&lt;/p&gt;

&lt;p&gt;Those are separate problems. The useful guarantee here is narrower: the on-chain score must correspond to a valid execution of the published game rules, for the given account and seed.&lt;/p&gt;

&lt;p&gt;That is already much stronger than trusting a web client or centralized leaderboard API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The game is playable without a wallet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hdauven.github.io/dario/" rel="noopener noreferrer"&gt;Dario Dash live demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full Rust, Circom, WebAssembly, contract and browser implementation is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/HDauven/dario" rel="noopener noreferrer"&gt;Dario source code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dario Dash is a proof of concept for the proving architecture. I'm interested in collaborating on other practical ZK use cases, particularly on Dusk. Feedback on the proof boundary and browser proving trade-offs is welcome too.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>blockchain</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built Blobster, a local-first structured data workbench for Chrome</title>
      <dc:creator>Hein Dauven</dc:creator>
      <pubDate>Sun, 21 Jun 2026 16:53:21 +0000</pubDate>
      <link>https://dev.to/heindauven/i-built-blobster-a-local-first-structured-data-workbench-for-chrome-3dhm</link>
      <guid>https://dev.to/heindauven/i-built-blobster-a-local-first-structured-data-workbench-for-chrome-3dhm</guid>
      <description>&lt;p&gt;I built Blobster as a small side project because reviewing and editing CSV, Markdown, JSON and other structured files in the browser was more annoying than it should be.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9pppcu7d2veymceybkk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9pppcu7d2veymceybkk.gif" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I kept running into the same workflow: open a raw API response, inspect a CSV export, preview Markdown, search through a blob, copy a path or value, maybe redact something sensitive before sharing it.&lt;/p&gt;

&lt;p&gt;Most tools either felt too narrow, too paste-site-like or required sending data somewhere I did not want to send it.&lt;/p&gt;

&lt;p&gt;So Blobster is a local-first extension for inspecting structured data directly in the browser.&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV / TSV table viewing and editing&lt;/li&gt;
&lt;li&gt;JSON tree, table and raw views&lt;/li&gt;
&lt;li&gt;Markdown preview and editing&lt;/li&gt;
&lt;li&gt;Search across keys, values, and cells&lt;/li&gt;
&lt;li&gt;Copying values, JSONPath, jq and JS paths&lt;/li&gt;
&lt;li&gt;Redacted copy for sensitive-looking output&lt;/li&gt;
&lt;li&gt;Local browser usage with no login&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything stays local. No uploads, no account, no telemetry.&lt;/p&gt;

&lt;p&gt;Chrome Web Store: &lt;a href="https://chromewebstore.google.com/detail/blobster/epddolhfilamhbnmgglenceabjbbeeho" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/blobster/epddolhfilamhbnmgglenceabjbbeeho&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would love feedback from people who often inspect API responses, data exports, configs, logs, HAR files or random structured blobs in the browser.&lt;/p&gt;

</description>
      <category>showdev</category>
    </item>
  </channel>
</rss>
