Zero-Knowledge Proofs in Javascript: A Practical Guide with Circom and SnarkJS
Step-by-step developer tutorial on compiling ZK circuits with Circom and generating cryptographic proofs on client-side JS using SnarkJS.

Step-by-step developer tutorial on compiling ZK circuits with Circom and generating cryptographic proofs on client-side JS using SnarkJS.
Zero-Knowledge Proofs in Javascript: A Practical Guide with Circom and SnarkJS
Zero-Knowledge Proofs (ZKPs) are transforming the way we handle identity, security, and privacy on the web. A ZKP allows a prover to convince a verifier that a statement is true (e.g., "I know the password to this account" or "I am over 18 years old") without revealing any underlying information (the actual password, or my exact birthdate).
Historically, compiling these mathematical proofs required deep academic expertise in cryptographic primitives and low-level C++ engines.
In 2026, the developer ecosystem has matured completely. Using Circom (to write arithmetic circuits) and SnarkJS (to generate and verify proofs in pure Javascript), web developers can build privacy-preserving authentication and private databases that execute directly inside a standard browser tab.
Here is a practical guide to building your first zero-knowledge application using Circom and SnarkJS in Javascript.
⚡ 1. The Core Architecture of ZK-SNARKs
To create a Zero-Knowledge Succinct Non-Interactive Argument of Knowledge (ZK-SNARK), we follow a structured four-stage pipeline:
[1. Design Circuit (Circom)] ──> [2. Compile & Setup (Proving/Verifying Keys)]
│
[4. Verify Proof (SnarkJS)] <─── [3. Generate Proof (Browser + SnarkJS + Inputs)]
- 2.Arithmetic Circuit: Define the computational logic in Circom. This mathematical description specifies public inputs, private inputs (the secret witness), and the output constraints.
- 4.Trusted Setup: Generate proving and verification keys using a multi-party computation ceremony (or local developer keys for testing).
- 6.Prover: The user runs SnarkJS on their local machine, feeding their secret witness and public parameters to generate a cryptographic proof file.
- 8.Verifier: The server (or a smart contract) runs a verification algorithm. It checks the proof file against the public inputs. The verification completes instantly, confirming the user knows the secret without exposing it.
🏗️ 2. Step 1: Writing the Circuit in Circom
Let's build a simple circuit: proving we know two prime factors of a public number without revealing the factors themselves.
Create a file named multiplier2.circom:
circompragma circom 2.0.0; template Multiplier2() { // Private Inputs (the secret witness) signal input a; signal input b; // Public Output signal output c; // Constraints (assertions that must hold true) c <== a * b; } component main {public [a]} = Multiplier2();
Compile the circuit using the circom CLI:
bashcircom multiplier2.circom --r1cs --wasm --sym
This produces a WebAssembly file that can execute the circuit calculations inside Node.js or a web browser.
🛠️ 3. Step 2: Running Setup Ceremonies and Compiling Keys
To perform proving and verifying, we need a set of keys. SnarkJS uses the Groth16 protocol, which requires a "Powers of Tau" ceremony for trusted setups.
Run these steps locally to generate your developer keys:
bash# 1. Start a Powers of Tau ceremony npx snarkjs powersoftau new bn128 12 pot12_0000.ptau -v npx snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="Contrib 1" -v -e="some random text" # 2. Prepare Phase 2 of setup npx snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v # 3. Generate Proving and Verifying Keys npx snarkjs groth16 setup multiplier2.r1cs pot12_final.ptau multiplier2_0000.zkey npx snarkjs zkey contribute multiplier2_0000.zkey multiplier2_final.zkey --name="Contributor 2" -v -e="another random text" npx snarkjs zkey export verificationkey multiplier2_final.zkey verification_key.json
📱 4. Step 3: Generating the ZK Proof in JavaScript
Now that we have our compiled WebAssembly circuit (multiplier2.wasm) and the proving key (multiplier2_final.zkey), we can run the prover directly in client-side Javascript.
typescriptimport * as snarkjs from "snarkjs"; async function generateProof() { // Define inputs: secret 'b' is kept safe, public output 'c' is verified const input = { a: "7", // Public input b: "13" // Secret input (we prove we know this factor) }; console.log("Generating Zero-Knowledge Proof locally..."); // Generate proof using Groth16 protocol const { proof, publicSignals } = await snarkjs.groth16.fullProve( input, "multiplier2.wasm", "multiplier2_final.zkey" ); console.log("Proof successfully generated!"); console.log("Cryptographic Proof JSON:", JSON.stringify(proof, null, 2)); console.log("Public Signals (Output):", publicSignals); return { proof, publicSignals }; }
🛡️ 5. Step 4: Verifying the Proof
Verification takes milliseconds and can be executed on a remote server or directly inside a client svelte/react component using the verification_key.json:
typescriptasync function verifyProof(proof: any, publicSignals: any) { // Load the public verification key const vKey = await fetch("/verification_key.json").then(res => res.json()); // Verify the proof validity const isValid = await snarkjs.groth16.verify(vKey, publicSignals, proof); if (isValid) { console.log("Verification Success! The user holds the valid secret."); } else { console.warn("Verification Failed! The proof is mathematically invalid."); } }
🏁 6. Conclusion: The Privacy Revolution
Circom and SnarkJS democratize zero-knowledge cryptography for general software engineers. By shifting computationally intensive proof generation directly to client-side browsers and running instant mathematical verification on backend Node servers, we can build applications that enforce trust without compromising data privacy.

Bun 1.2 vs. Node.js 22 vs. Deno 2.0: The Ultimate 2026 HTTP Throughput & Memory Benchmark
A rigorous, standardized developer-focused comparison of the three primary JavaScript runtimes of 2026, measuring raw throughput, memory leaks, and package manager overhead.

Postgres Row Level Security (RLS): Building Multi-tenant SaaS Backends Safely
Ditch manual tenant filters. Learn how to secure multi-tenant SaaS applications at the database level using Postgres Row Level Security (RLS) policies.