惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Cyberwarzone
Cyberwarzone
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
小众软件
小众软件
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
Security Archives - TechRepublic
Security Archives - TechRepublic
W
WeLiveSecurity
Cloudbric
Cloudbric
博客园 - 司徒正美
美团技术团队
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
PCI Perspectives
PCI Perspectives
宝玉的分享
宝玉的分享
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
Last Week in AI
Last Week in AI
S
Schneier on Security
N
News | PayPal Newsroom
B
Blog RSS Feed
L
LINUX DO - 最新话题
T
Troy Hunt's Blog
S
Secure Thoughts
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
L
Lohrmann on Cybersecurity
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tenable Blog
S
Securelist
L
LangChain Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
I
InfoQ
H
Heimdal Security Blog
Cisco Talos Blog
Cisco Talos Blog
F
Full Disclosure
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
K
Kaspersky official blog
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
C
Cisco Blogs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Mathematically Prohibiting 'Cheating' in On-Chain RPS: A Midnight ZK dApp Case Study
Haruki Kondo · 2026-05-04 · via DEV Community

Introduction

Implementing Rock-Paper-Scissors (RPS) on-chain is surprisingly tricky.

The moment you choose "Rock" and send a transaction, your opponent can read your move from the public ledger. The game is over before it even starts.

I tried implementing a commit-reveal pattern manually, but managing salts, preventing front-running, and ensuring fair judging logic... it quickly became a rabbit hole.

That's when I turned to Midnight. I built a full-stack RPS dApp where "cheating" (looking at the opponent's move before playing) is mathematically impossible!

In this article, I'll share the ZK circuit design using Midnight's smart contract language, Compact, and the hurdles I faced during development.

Note
What ZK protects: The ZK proof ensures "confidentiality of the move during the commit phase" (fairness). After the reveal, both moves are recorded on the public ledger.

Here is what the dApp looks like:

RPS dApp Hero

What You'll Learn

  • Designing the "Hidden" vs. "Visible" split in Midnight.
  • Mastering the commit-reveal pattern with the Compact language.
  • Frontend UX strategies for handling ZK proof generation time (the 10-second hurdle).
  • Robust provider configuration with the Midnight JS SDK.

Repository

You can find the full source code here:

midnight-rps-sample-app

Midnight RPS sample dApp

Overview

midnight-rps-sample-app is a sample Rock-Paper-Scissors dApp project built on Midnight, a privacy-focused blockchain.

Key Features

  • Fair Gameplay via Zero-Knowledge Proofs (ZK Proofs)
    The app utilizes a "commit/reveal" scheme powered by Compact smart contracts. Players commit to their move (Rock, Paper, or Scissors) by submitting a hashed value. Once all players have committed, the moves are revealed. This ensures a tamper-proof gaming experience where "sniping" or reacting to an opponent's move is impossible.

  • Powered by the Midnight Blockchain
    Contracts are deployed on the Midnight PreProd testnet, with all transactions recorded on-chain.

  • Full-Stack Architecture

    Package Role
    pkgs/contract Smart contracts written in the Compact language
    pkgs/cli CLI tools for contract deployment and interaction
    pkgs/app Frontend UI built with React + Vite
  • Lace Wallet Integration
    Connects with Lace Wallet via the @midnight-ntwrk/dapp-connector-api for secure signing and transaction processing.

Game Flow

  1. Commit Phase — Each player commits their move…

Quick Start

Here are the steps to get the app running locally.

To be honest, it takes about 15-20 minutes including ZK circuit compilation and the initial Docker image pull. Get your coffee ready!

Environment

Tested with the following:

Docker version 27.4.0
compact 0.2.0
bun 1.3.13
node 23.3.0

Enter fullscreen mode Exit fullscreen mode

Install Lace Wallet to your browser

If you have not yet installed Lace Wallet, you must go to below page & need to install Lace Wallet

Lace | The light wallet platform to explore Web3

Discover the new light wallet platform from Input Output Global. Manage digital assets, and access NFTs, DApps, and DeFi services. Start your Web3 journey.

favicon lace.io

Next, you need to create wallet account of Midnight

Please switch to PreProd Network

0. Clone the Repository

git clone https://github.com/mashharuki/midnight-rps-sample-app.git

Enter fullscreen mode Exit fullscreen mode

1. Install Dependencies

bun install

Enter fullscreen mode Exit fullscreen mode

2. Compile and Build Contracts

# Generate ZK circuit assets (This takes the most time)
bun contract compact  

# Build CLI and Frontend
bun cli build
bun app build

Enter fullscreen mode Exit fullscreen mode

The bun contract compact step is where the Compact compiler generates the ZK proving and verification keys.

3. Start the Proof Server (Initial Pull: ~3 mins)

We need to run the server that handles ZK proof generation via Docker.

Without this server, you won't be able to deploy contracts or send commit/reveal transactions.

Version 8.0.3 is verified with Compact 0.2.0.
Ensure your SDK and Proof Server versions match, or proof generation will fail.

docker run -d -p 127.0.0.1:6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server

Enter fullscreen mode Exit fullscreen mode

4. Deploy Contract to PreProd Network

If you don't have testnet NIGHT Token, you can get some token from below site.

https://faucet.preprod.midnight.network/

bun cli preprod-pts

Enter fullscreen mode Exit fullscreen mode

If you deploy successfull, contract address shows like below.

[19:24:25.997] INFO (40223): Deploying RPS contract...
  ⠇ Deploying RPS contract[19:24:51.416] INFO (40223): Deployed RPS contract at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8
  ✓ Deploying RPS contract
  Contract deployed at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8

Enter fullscreen mode Exit fullscreen mode


The Essence of Midnight Architecture: "Localizing" Information

Developing with Midnight requires a different mindset compared to Solidity.

As mentioned in my previous articles, Midnight has two types of states:

  • Public State: Visible to everyone (on-chain ledger).
  • Private State: Visible only to you (local storage).

The Compact ZK circuits act as the bridge between these two.

App Architecture


Code Deep Dive: Mathematically Preventing Cheating

1. Security Core: Domain Separation

When deriving a public key from a secret key, Midnight recommends using domain separation:

pure circuit derive_pk(sk: Bytes<32>): Bytes<32> {
  // Domain separation with a fixed string "rps:pk:v1"
  return persistentHash<Vector<2, Bytes<32>>>([pad(32, "rps:pk:v1"), sk]);
}

Enter fullscreen mode Exit fullscreen mode

If you use the secret key as-is, using the same key in another app would result in the same public key, risking data leakage. By adding a prefix like "app name + version", we ensure that the public keys derived from the same secret key are unique to each app.

2. The Commit-Reveal Circuits

Commit Phase (Declaring a move while keeping it hidden)

export circuit commit(): [] {
  assert(!game_over,                 "Game is already over");
  assert(state == GameState.waiting, "Not in waiting state");

  const sk         = local_secret_key(); // witness: private input
  const pk         = derive_pk(sk);
  const my_move    = get_my_move();
  const my_salt    = get_my_salt();
  const commitment = make_commit(my_move, my_salt);
  store_move_and_salt(my_move, my_salt); // ⭐ Save move and salt to private state

  if (!p1_joined) {
    p1_key    = disclose(pk);
    p1_commit = disclose(commitment);
    p1_joined = true;
  } else {
    assert(!p2_joined, "Both players already committed");
    p2_key    = disclose(pk);
    p2_commit = disclose(commitment);
    p2_joined = true;
    state     = GameState.committed; // Transition to 'committed' when both joined
  }
}

Enter fullscreen mode Exit fullscreen mode

Pay close attention to store_move_and_salt.

This is a witness function that saves the selected move and salt to the browser's IndexedDB (private state). To prove that the move revealed later is the same one committed earlier, this step is absolutely necessary.

The use of disclose() is also critical. It allows developers to explicitly control which results of private computations are written to the public ledger.

Game State Transitions

The coordination between commit and reveal phases is managed by these states:

State Transitions

3. Reveal Phase: Where ZK Proofs Shine

If the commit is the "declaration," the reveal is the "proof."

This is where Midnight's ZK stack really pays off.

export circuit reveal(): [] {
  assert(!game_over,                   "Game is already over");
  assert(state == GameState.committed, "Not in committed state");

  const sk      = local_secret_key();
  const pk      = derive_pk(sk);
  const my_move = get_my_move();   // Restore from private state
  const my_salt = get_my_salt();   // Restore from private state
  const computed = make_commit(my_move, my_salt);

  const is_p1 = disclose(p1_key == pk);
  const is_p2 = disclose(p2_key == pk);
  assert(is_p1 || is_p2, "Caller is not a registered player");

  if (is_p1) {
    assert(!p1_revealed, "Player 1 already revealed");
    assert(disclose(computed == p1_commit), "Commitment mismatch for P1");
    p1_move     = disclose(my_move); // Move is written to ledger only now
    p1_revealed = true;
  }
  if (is_p2) {
    assert(!p2_revealed, "Player 2 already revealed");
    assert(disclose(computed == p2_commit), "Commitment mismatch for P2");
    p2_move     = disclose(my_move);
    p2_revealed = true;
  }

  if (disclose(p1_revealed && p2_revealed)) {
    result    = who_wins(p1_move, p2_move); // Judge winner
    game_over = true;
    state     = GameState.finished;
  }
}

Enter fullscreen mode Exit fullscreen mode

The line assert(disclose(computed == p1_commit), "Commitment mismatch for P1") is the heart of the logic:

  • my_move and my_salt exist only in your local private state.
  • make_commit(my_move, my_salt) is re-calculated, and the ZK circuit verifies it matches the p1_commit recorded on-chain during the commit phase.
  • You mathematically prove that "this move is the one I committed to" without revealing the move itself until the proof is valid.

Once the reveal is successful, p1_move and p2_move are finally written on-chain, and who_wins() determines the outcome.

During the commit phase, your move is hidden by ZK and remains unknown to the opponent until it is revealed. This is the mechanism that "mathematically prohibits cheating."


Overcoming the ZK dApp UX Hurdle

The biggest challenge during implementation was handling the "ZK proof generation time."

ZK Proof Loading

Filling the 10-Second Silence

In Midnight, before sending a transaction, you must generate a proof on your machine (or the Proof Server). This takes about 5-10 seconds.

UX improvements are essential to prevent users from thinking the app has frozen:

  • Optimistic UI Updates: Switch the UI state to "Generating Proof..." as soon as the process starts to communicate clearly what is happening.
  • Persistent Providers: Use levelPrivateStateProvider to ensure that even if the user reloads the browser during proof generation, the generated data and chosen moves are not lost.

Waiting for Opponent

// Example levelPrivateStateProvider configuration
privateStateProvider: levelPrivateStateProvider({ 
  accountId, 
  namespace: "rpsPrivateState", // Separate namespace for each game
  privateStoragePasswordProvider: () => storagePassword // Secure storage
}),

Enter fullscreen mode Exit fullscreen mode


Sequence: From Commit to Game End

Let's recap the full flow:


Remaining Challenges

There's still a lot to learn by building with Midnight and Compact. Here are some issues I'm still working on:

1. The Abandonment Problem (Griefing Attack)

If Player 1 commits and Player 2 never joins, the contract stays locked. I need to implement a timeout/cancel mechanism in a future update.

2. Version Management of Build Assets

ZK circuit assets (proving keys) generated by bun contract compact can become invalid with even minor updates to the Compact version or compiler.

I actually ran into this during development. If you're building a similar app, double-check your library and Proof Server versions!

Solving these two will open up even more possibilities for ZK dApps on Midnight.


Conclusion

Implementing this "fair" RPS app taught me three key lessons:

  1. Verify Versions: Proof generation can fail due to mismatched Proof Server versions.
  2. Don't Forget store_move_and_salt: Saving private state is mandatory for the reveal phase.
  3. UX is Priority #1: Designing a UI that handles the unique "waiting time" of ZK is critical.

Next, I'm planning to tackle multi-round support and timeout handling!

If you're interested in Midnight's privacy tech, check out the repository and my other technical blogs!

Thanks for reading!

Follow me on X!


References