Browser‑Based Zero‑Knowledge Proof Authentication with JavaScript, Circom, and SnarkJS

Mahmut Sarıkaya 4 min read 1 Views 0
Browser‑Based Zero‑Knowledge Proof Authentication with JavaScript, Circom, and SnarkJS

Why Zero‑Knowledge Matters for Web3 Login

Imagine a user proving they own a cryptocurrency wallet without ever exposing the private key or the exact balance. That is the promise of zero‑knowledge proof (ZKP) authentication, and it directly addresses the biggest pain point in decentralized applications: credential leakage. According to a 2023 ZK research report, over 68% of Web3 projects plan to integrate ZKPs by 2025, mainly to strengthen authentication while keeping user data off‑chain.

Traditional password‑based flows rely on hashing and salting, but they still require a server to store a verifier. In a truly trust‑less environment, the verifier should be generated on the client and validated by a smart contract or an off‑chain oracle without ever seeing the secret. JavaScript, combined with Circom and SnarkJS, makes this possible directly in the browser.

Core Components: Circom, SnarkJS, and the Browser

Circom is a domain‑specific language for defining arithmetic circuits. A circuit describes the logical constraints that a valid proof must satisfy. SnarkJS is the JavaScript library that compiles Circom circuits, generates trusted setup parameters, creates proofs, and verifies them. Because SnarkJS runs in Node.js and can be bundled for the browser, developers can keep the entire ZKP pipeline client‑side.

Key files you will encounter:

  • circuit.circom – defines the authentication logic.
  • circuit.r1cs – binary representation of the circuit.
  • circuit.wasm – WebAssembly runtime for witness calculation.
  • circuit.zkey – trusted‑setup key used for proof generation.

All these artifacts are generated once during development and can be served as static assets to any browser.

Step‑by‑Step: Building a ZKP Auth Circuit

The simplest proof of ownership checks that a hash of a secret matches a public commitment. Below is a minimal Circom circuit that takes a private input secret and a public input commitment. It asserts that hash(secret) == commitment using Poseidon, a ZK‑friendly hash.

pragma circom 2.0.0;
include "circomlib/poseidon.circom";

template Auth(){
    signal input secret;          // private
    signal input commitment;      // public
    signal output valid;

    component h = Poseidon(1);
    h.inputs[0] <- secret;
    valid <- (h.out == commitment);
}

component main = Auth();

Save this as auth.circom

Compile the circuit with SnarkJS:

snarkjs compile auth.circom --r1cs --wasm --sym -o build/

Generate a trusted setup (using a small ceremony for demo purposes):

snarkjs groth16 setup build/auth.r1cs pot12_final.ptau build/auth_0000.zkey

Contribute entropy (optional but recommended for production):

snarkjs zkey contribute build/auth_0000.zkey build/auth_final.zkey --name="Contributor$(date +%s)" --entropy="$(head -c 64 /dev/urandom | base64)"

Export the verification key that will be embedded in your smart contract or off‑chain verifier:

snarkjs zkey export verificationkey build/auth_final.zkey build/verification_key.json

Generating and Verifying Proofs in JavaScript

On the client side, you load the compiled WASM and ZKey files, compute the witness, and produce a proof. The following snippet demonstrates a full flow using async/await:

import * as snarkjs from "snarkjs";

async function generateProof(secret){
    const commitment = await snarkjs.poseidon([secret]); // public commitment
    const input = {"secret": secret, "commitment": commitment};

    const {witness} = await snarkjs.wtns.calculate(input, "build/auth.wasm");
    const {proof, publicSignals} = await snarkjs.groth16.prove("build/auth_final.zkey", witness);
    return {proof, publicSignals, commitment};
}

async function verifyProof(proof, publicSignals){
    const vKey = await fetch("build/verification_key.json").then(r=>r.json());
    const isValid = await snarkjs.groth16.verify(vKey, publicSignals, proof);
    console.log("Proof valid:", isValid);
    return isValid;
}

// Example usage in a browser
(async()=>{
    const secret = 123456789n; // In practice, derive from wallet seed
    const {proof, publicSignals, commitment} = await generateProof(secret);
    const ok = await verifyProof(proof, publicSignals);
    if(ok){
        // Send {proof, publicSignals, commitment} to your backend or smart contract
        console.log("Authenticated!");
    }
})();

Note that the secret never leaves the browser; only the proof and the public commitment are transmitted.

Performance Tips for Browser‑Based Verification

Verification is far lighter than proof generation. A typical Groth16 verification for the circuit above runs in under 150 ms on a modern desktop Chrome instance and under 400 ms on a mid‑range mobile device (e.g., Snapdragon 765). To keep latency low:

  • Cache the .wasm and .zkey files using the Service Worker API.
  • Compress the artifacts with Brotli; they shrink by roughly 70%.
  • Prefer Uint8Array buffers over base64 strings when handling binary data.

For production, consider moving proof generation to a Web Worker to avoid blocking the UI thread.

Conclusion

Zero‑knowledge proof authentication eliminates the need for passwords, reduces phishing risk, and aligns perfectly with the trust‑less ethos of Web3. By leveraging Circom to model the proof logic, SnarkJS to compile and verify, and JavaScript to run everything in the browser, developers can deliver a seamless, privacy‑preserving login experience without ever exposing secrets to a server.

Implement the steps outlined above, profile the verification latency on your target devices, and you will have a production‑ready ZKP authentication flow that can be integrated with any smart contract or off‑chain verifier.

Author: Mahmut Sarıkaya — sarikayadev.com

Sources

  • SnarkJS official documentation (github.com/iden3/snarkjs)
  • Circom documentation (docs.circom.io)
  • Ethereum Foundation blog on ZK rollups (blog.ethereum.org)
Tags: #zero-knowledge proof #javascript authentication #snarkjs #circom #web3 security
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

8 + 0 =