Zero-Knowledge Proof application development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Zero-Knowledge Proof application development
Complex
from 1 week to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Zero-Knowledge Proof Applications

Most projects requesting ZKP support face one of two problems: either they need to prove a fact without revealing data (age, balance, set membership), or they need to move heavy computations off-chain with on-chain verification. These are different tasks with different tooling stacks, and confusing them is the first and most costly mistake at the start.

Proof-system selection: this is not an academic question

The choice between Groth16, PLONK, STARK, Halo2 and FRI determines everything: proof size, generation time, trusted setup presence, on-chain verification cost.

Comparison of major systems

System Trusted setup Proof size Verification cost (EVM) Prover time Recursion
Groth16 Yes (per-circuit) ~200 bytes ~270k gas Fast Complex
PLONK (KZG) Yes (universal) ~800 bytes ~400k gas Medium Simpler
PLONK (IPA) No ~1.5KB Expensive Slow Good
STARK No 40–200KB Very expensive in EVM Slow Excellent
Halo2 No ~1–5KB Non-native Medium Built-in

Groth16 — the choice for production systems with fixed schemes and minimal gas requirements. Used by: Tornado Cash (former), Zcash Sapling, most zkSNARK bridges. Downside: every circuit change requires a new ceremony.

PLONK with KZG — de facto standard for zkRollup-like systems. Gnosis, zkSync Lite, Polygon Hermez use PLONK variants. Universal trusted setup (Powers of Tau) is reused — no ceremony needed for each scheme.

STARKs — the choice for tasks without trusted setup and requiring recursion: StarkNet, Cairo VM. Enormous proof size — EVM verification is natively impractical, requiring a separate verifier contract or L3 approach.

Halo2 — used by Zcash Orchard, Scroll. No trusted setup required, built-in recursion. Tooling is less mature, smaller ecosystem, but actively developing.

For most practical tasks (private voting, proof of membership, zkKYC, age verification) — Groth16 via circom/snarkjs or PLONK via gnark/noir — is the correct starting point.

Circuit development: where real bugs hide

Circom and its pitfalls

Circom is a DSL for writing arithmetic circuits. A circuit compiles to R1CS (Rank-1 Constraint System), then through snarkjs or rapidsnark a proof is generated.

Basic scheme for proving knowledge of a preimage hash:

pragma circom 2.1.4;

include "circomlib/circuits/poseidon.circom";
include "circomlib/circuits/comparators.circom";

template ProveBalance() {
    signal input balance;         // private
    signal input salt;            // private
    signal input commitment;      // public (stored on-chain)
    signal input threshold;       // public

    // Prove: hash(balance, salt) == commitment
    component hasher = Poseidon(2);
    hasher.inputs[0] <== balance;
    hasher.inputs[1] <== salt;
    hasher.out === commitment;

    // Prove: balance >= threshold (without revealing balance)
    component gte = GreaterEqThan(64);
    gte.in[0] <== balance;
    gte.in[1] <== threshold;
    gte.out === 1;
}

component main {public [commitment, threshold]} = ProveBalance();

Critical vulnerability: insufficiently constrained signals. This is the most common class of bugs in ZK circuits. If a signal is used in computation but lacks sufficient constraints — the prover can pass an arbitrary value and the verifier will accept the proof.

Example of vulnerable code:

// VULNERABLE: no constraint that out is a bit
template IsZero() {
    signal input in;
    signal output out;
    signal inv;

    inv <-- in != 0 ? 1/in : 0;
    out <-- in == 0 ? 1 : 0;
    // FORGOT: in * out === 0  and  (in * inv - 1 + out) === 0
}

The verifier accepts any out because there are no constraints linking out to in.

Overflow in field arithmetic. Circom operates in a prime field p = 21888242871839275222246405745257275088548364400416034343698204186575808495617. Every operation is modulo p. If input data is from the real world (age, timestamp), the range is safe. But when multiplying large numbers, explicit range checking is needed via Num2Bits:

component rangeCheck = Num2Bits(64);
rangeCheck.in <== balance;
// Now balance is guaranteed < 2^64

Gnark (Go) for more complex schemes

When the scheme is too complex for circom (recursive proofs, BLS signature verification, zkEVM-like components) — use gnark:

type Circuit struct {
    PreImage frontend.Variable `gnark:",secret"`
    Hash     frontend.Variable `gnark:",public"`
}

func (c *Circuit) Define(api frontend.API) error {
    mimc, err := mimc.NewMiMC(api)
    if err != nil {
        return err
    }
    mimc.Write(c.PreImage)
    result := mimc.Sum()
    api.AssertIsEqual(result, c.Hash)
    return nil
}

gnark is 10–30x faster than snarkjs in prover time for equivalent schemes. For production with real users this matters: proof generation in browser via WASM takes 3–15 seconds on a moderate Groth16 circuit, in a Go server — 0.1–1 second.

On-chain verification

The Solidity verifier is generated automatically — snarkjs does this via snarkjs zkey export solidityverifier. But the production contract needs adaptation:

contract BalanceProofVerifier {
    IGroth16Verifier public immutable verifier;

    // Nullifier storage for replay protection
    mapping(bytes32 => bool) public usedNullifiers;

    function verifyAndExecute(
        uint[2] calldata a,
        uint[2][2] calldata b,
        uint[2] calldata c,
        uint[2] calldata publicInputs  // [commitment, threshold]
    ) external {
        bytes32 nullifier = keccak256(abi.encodePacked(a, b, c));
        require(!usedNullifiers[nullifier], "Proof already used");

        require(verifier.verifyProof(a, b, c, publicInputs), "Invalid proof");

        usedNullifiers[nullifier] = true;
        // ... main logic
    }
}

Nullifier pattern — mandatory for proof of membership and any systems where a single proof should not be used twice. Nullifier = deterministic hash of a secret input that cannot be linked to identity but can be checked for uniqueness.

Gas cost for Groth16 verification — about 270k gas. On Ethereum mainnet at 20 gwei this is ~$2–5 per verification. For high-frequency systems — deploy to L2 (Arbitrum, Base) reduces cost 10–50x.

Infrastructure for proof generation

Client-side generation (browser)

Suitable for: wallet-level operations, single proofs. Uses WebAssembly snarkjs build:

import { groth16 } from "snarkjs";

const { proof, publicSignals } = await groth16.fullProve(
    { balance: "5000", salt: randomSalt, commitment: onChainCommitment, threshold: "1000" },
    "/circuits/balance_proof.wasm",
    "/circuits/balance_proof_final.zkey"
);

.zkey files for complex schemes weigh 10–500MB — this is a problem for the browser. Solution: split into chunks (chunked zkey) or use streaming download.

Server-side generation (proving service)

For schemes with a large number of constraints (>1M) — client won't cope. Architecture:

Client → API (task creation) → Queue (Bull/RabbitMQ) → Prover Worker → S3 (proof) → Webhook

Prover worker — Go service with gnark or Rust with bellman/arkworks. Horizontal scaling: each worker is independent, tasks are idempotent.

For zkEVM-level schemes (billions of constraints) — GPU proving via CUDA. Acceleration 100–1000x compared to CPU. Providers: Ingonyama, Ulvetanna.

Trusted Setup Ceremony

For Groth16 — mandatory. Process:

  1. Universal Powers of Tau (take ready-made from Hermez/EthSnarks — these are publicly verified parameters up to a certain size)
  2. Phase 2 ceremony specific to your scheme: each participant adds their randomness
  3. Final beacon — public random source (Bitcoin block hash)

If at least one participant is honest — the parameters are secure. For production projects: minimum 10–20 participants, public transcript verification.

Timelines and scope

Phase Content Duration
Scheme specification Formalize task, choose proof-system, design public/private inputs 1 week
Circuit development Write circom/gnark/noir, unit test constraints 2–4 weeks
Circuit audit Find under-constrained signals, check soundness 1–2 weeks
Verifier contract Solidity verifier + nullifier logic + protocol integration 1–2 weeks
Prover infrastructure WASM build or server-side prover, API 1–2 weeks
Trusted setup Organize ceremony (if Groth16/PLONK-KZG) 1 week

Total for typical ZKP application (proof of membership, zkKYC, private transactions): 6–12 weeks from specification to mainnet. Complex zkRollup-like systems — 6–18 months for a team.