Skip to main content
โšก Calmops

ZK Proofs and zkRollups Complete Guide 2026: Zero-Knowledge Cryptography

Introduction

Zero-knowledge proofs represent one of the most profound cryptographic innovations of the past decade. In blockchain, they enable privacy, scalability, and interoperabilityโ€”solving critical challenges that have limited adoption.

By 2026, ZK proofs have moved from theoretical constructs to production-ready systems powering layer 2 scaling solutions processing billions of dollars in value. This guide explores the mathematics, implementations, and applications of ZK proofs in modern blockchain systems.

Understanding Zero-Knowledge Proofs

The Core Concept

A zero-knowledge proof allows one party (the prover) to convince another party (the verifier) that a statement is true, without revealing any information beyond the validity of the statement itself.

Key Properties:

  1. Completeness: If the statement is true, an honest prover can convince the verifier.
  2. Soundness: If the statement is false, no cheating prover can convince the verifier.
  3. Zero-knowledge: The verifier learns nothing beyond the truth of the statement.

A Simple Example

Imagine you want to prove you know a secret password without revealing the password itself:

Verifier: "Tell me a hash of your password"
Prover: "Here's the hash: 5e884898da28047d..."
Verifier: "Now prove you know the original password"
Prover: (provides zero-knowledge proof)
Verifier: "Convinced! You know the password without me learning it."

Mathematical Foundation

# Simplified zero-knowledge proof of knowledge
class ZKProof:
    """
    Basic zero-knowledge proof structure
    """
    
    def __init__(self, statement, witness, challenge, response):
        self.statement = statement  # What we're proving
        self.witness = witness      # Secret information
        self.challenge = challenge  # Random challenge from verifier
        self.response = response    # Prover's response
    
    def verify(self, public_key):
        """
        Verify the proof without learning the witness
        """
        # Verify that response = challenge * secret + random
        computed_commitment = self.compute_commitment()
        return computed_commitment == self.challenge

# Interactive Zero-Knowledge Proof
def interactive_zk_proof(prover_secret):
    """
    Interactive ZK proof: Prover convinces Verifier 
    they know a secret without revealing it
    """
    
    # Step 1: Commitment
    random_value = generate_random()
    commitment = hash(random_value, prover_secret)
    print(f"Prover sends commitment: {commitment}")
    
    # Step 2: Challenge
    challenge = generate_random_challenge()
    print(f"Verifier sends challenge: {challenge}")
    
    # Step 3: Response
    response = random_value + challenge * prover_secret
    print(f"Prover sends response: {response}")
    
    # Step 4: Verification
    expected_commitment = hash(response - challenge * prover_secret, prover_secret)
    is_valid = (commitment == expected_commitment)
    print(f"Verification: {'SUCCESS' if is_valid else 'FAILED'}")
    
    return is_valid

Types of ZK Proofs

zkSNARKs

Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge (zkSNARKs) are the most widely deployed ZK proof system in blockchain.

Characteristics:

  • Succinct: Proofs are small (hundreds of bytes)
  • Non-interactive: No back-and-forth communication needed
  • Arguments: Computational soundness (relies on assumptions)
  • Knowledge: Proof requires knowing a secret witness
// zkSNARK verifier contract (simplified)
contract ZKSNARKVerifier {
    // Verification key
    uint256[2] public vk_alpha;
    uint256[2][2] public vk_beta;
    uint256[2] public vk_gamma;
    uint256[2] public vk_delta;
    uint256[2][] public vk_ic; // Verification circuit
    
    function verifyProof(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[] memory input
    ) public view returns (bool) {
        // Validate inputs
        require(input.length + 1 == vk_ic.length, "Invalid input length");
        
        // Compute linear combination of public inputs
        uint256[2 memory vk_x = vk_ic[0];
        for (uint i = 0; i < input.length; i++) {
            // vk_x += input[i] * vk_ic[i+1]
            vk_x[0] = addmod(vk_x[0], mulmod(input[i], vk_ic[i+1][0], BN128_Q));
            vk_x[1] = addmod(vk_x[1], mulmod(input[i], vk_ic[i+1][1], BN128_Q));
        }
        
        // Pairing check: e(a, b) = e(alpha, beta) * e(vk_x, gamma) * e(c, delta)
        // This is simplified; real implementations use more complex pairing math
        return pairingCheck(a, b, vk_alpha, vk_beta, vk_x, vk_gamma, c, vk_delta);
    }
    
    function pairingCheck(
        uint256[2] memory a1, uint256[2][2] memory a2,
        uint256[2] memory b1, uint256[2][2] memory b2,
        uint256[2] memory c1, uint256[2] memory c2,
        uint256[2] memory d1, uint256[2][2] memory d2
    ) internal view returns (bool) {
        // Perform cryptographic pairing check
        // This is where the actual verification happens
    }
}

Trusted Setup: zkSNARKs require a trusted setup ceremony where initial parameters are generated. If compromised, proofs can be forged.

zkSTARKs

Zero-Knowledge Scalable Transparent Arguments of Knowledge (zkSTARKs) address some limitations of SNARKs.

Characteristics:

  • Transparent: No trusted setup required
  • Quantum-resistant: Based on collision-resistant hashes
  • Scalable: Verification time is polylogarithmic
  • Post-quantum secure: Not broken by quantum computers
# Simplified FRI (Fast Reed-Solomon Interactive Oracle Proof of Proximity)
class STARK:
    """
    STARK proof system - transparent and quantum-resistant
    """
    
    def __init__(self, field, expansion_factor=4):
        self.field = field
        self.expansion_factor = expansion_factor
    
    def commit(self, polynomial):
        """
        Commit to a polynomial using Merkle tree
        """
        evaluations = polynomial.evaluate_at_field()
        merkle_root = self.build_merkle_tree(evaluations)
        return merkle_root
    
    def prove_low_degree(self, polynomial, boundary):
        """
        Prove polynomial has degree < boundary
        """
        # Divide into chunks
        chunks = self.chunk_polynomial(polynomial, boundary)
        
        # Build Merkle tree of chunks
        chunk_hashes = [self.hash(chunk) for chunk in chunks]
        tree = self.build_merkle_tree(chunk_hashes)
        
        # Sample random challenges
        challenges = self.generate_challenges(tree.root)
        
        # For each challenge, prove relevant data
        proofs = []
        for challenge in challenges:
            proof = self.generate_merkle_proof(tree, challenge)
            proofs.append(proof)
        
        return {
            'tree': tree,
            'challenges': challenges,
            'proofs': proofs
        }
    
    def verify_proof(self, proof, boundary):
        """
        Verify the low-degree proof
        """
        # Re-derive challenges from tree root
        challenges = self.generate_challenges(proof['tree'].root)
        
        # Verify each proof
        for i, challenge in enumerate(challenges):
            merkle_proof = proof['proofs'][i]
            if not self.verify_merkle_proof(merkle_proof, proof['tree'].root):
                return False
        
        return True

Comparison

Feature zkSNARK zkSTARK
Proof Size ~200 bytes ~100-400 KB
Verification Time ~1-5 ms ~10-50 ms
Trusted Setup Required Not required
Quantum Resistance No Yes
Setup Type Ceremony Transparent
Gas Cost (Ethereum) Lower Higher

zkRollups: Scaling Ethereum with ZK Proofs

What are zkRollups?

zkRollups batch hundreds of transactions off-chain and generate a single ZK proof that attests to the validity of all transactions in the batch. This enables:

  • ~2,000 TPS (vs ~15 TPS on L1)
  • Immediate finality (no 7-day challenge period)
  • Lower gas costs (shared proof verification)
  • Inherent security (validity proofs)

How zkRollups Work

zkRollup Architecture:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  User Transactions                   โ”‚
โ”‚  (Alice sends 1 ETH to Bob, Carol swaps tokens...)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Sequencer (Batch Processor)            โ”‚
โ”‚  1. Collects transactions                           โ”‚
โ”‚  2. Executes off-chain                             โ”‚
โ”‚  3. Generates state changes                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Prover (Proof Generator)               โ”‚
โ”‚  1. Creates zero-knowledge proof                   โ”‚
โ”‚  2. Proves state transition is valid               โ”‚
โ”‚  3. Generates succinct proof                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚
                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          L1 Contract (Ethereum Mainnet)             โ”‚
โ”‚  1. Receives proof and compressed data             โ”‚
โ”‚  2. Verifies proof (efficient!)                   โ”‚
โ”‚  3. Updates rollup state root                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Major zkRollup Projects

zkSync Era

// zkSync Era - zkEVM rollup
interface IExecutor {
    function executeBatches(Batch[] memory _newBatches) external;
    function proveBatches(
        uint256 _batchNumber,
        uint256[] memory _quickVerifyGas,
        uint256[] memory _classicProofs,
        bytes[] memory _signatures
    ) external;
}

struct Batch {
    uint256 batchNumber;
    bytes32 batchHash;
    uint256 timestamp;
    uint256 l1GasPrice;
    address proposer;
    bytes32[] contractAddresses;
    bytes compressedTxData;
}

// Processing a withdrawal
function finalizeWithdrawal(
    uint256 _batchNumber,
    uint256 _withdrawalId,
    address _l2Sender,
    address _l1Recipient,
    uint256 _amount,
    bytes calldata _proof
) external {
    // Verify the Merkle proof that withdrawal was included
    require(
        verifyWithdrawalProof(
            _batchNumber,
            _withdrawalId,
            _l2Sender,
            _l1Recipient,
            _amount,
            _proof
        ),
        "Invalid withdrawal proof"
    );
    
    // Mark withdrawal as finalized
    _finalizedWithdrawalHashes[keccak256(abi.encode(_withdrawalId))] = true;
    
    // Transfer funds to L1 recipient
    IERC20(governance).transfer(_l1Recipient, _amount);
}

StarkNet

// StarkNet Cairo program (simplified)
fn main(
    // Public inputs
    pedersen_ptr: HashBuiltin*,
    range_check_ptr: RangeCheck*,
    // Account state
    from_address: felt,
    to_address: felt,
    amount: felt,
    // Previous account state
    from_nonce: felt,
    to_nonce: felt,
) -> (
    // Updated state
    felt, felt
) {
    // Verify balance
    let from_balance = get_balance(from_address);
    assert(from_balance >= amount, 'Insufficient balance');
    
    // Verify nonce (prevents replay)
    let from_nonce_check = get_nonce(from_address);
    assert(from_nonce_check == from_nonce, 'Invalid nonce');
    
    // Update balances
    let new_from_balance = from_balance - amount;
    let new_to_balance = get_balance(to_address) + amount;
    
    // Update nonces
    let new_from_nonce = from_nonce + 1;
    let new_to_nonce = to_nonce;
    
    // Return updated state
    return (new_from_balance, new_to_balance);
}

Polygon zkEVM

// Polygon zkEVM contract
contract PolygonZkEVM is IPolygonZkEVM {
    // Batch verification
    function verifyBatch(
        bytes32 currentAccInputHash,
        bytes32 newAccInputHash,
        uint256 currentNumBatch,
        bytes32[4] memory proofA,
        uint256[8] memory proofB,
        bytes32[2] memory proofC,
        uint256[24] memory publicInputs
    ) external {
        // Verify the ZK proof
        require(
            verifier.verify(
                [proofA[0], proofA[1]],
                [[proofB[0], proofB[1]], [proofB[2], proofB[3]], [proofB[4], proofB[5]], [proofB[6], proofB[7]]],
                [proofC[0], proofC[1]],
                publicInputs
            ),
            "Invalid proof"
        );
        
        // Update batch count
        lastBatchNumber = currentNumBatch;
        
        emit VerifyBatch(currentNumBatch, newAccInputHash);
    }
    
    // Force batch inclusion
    function forceBatch(
        bytes calldata transactions,
        uint256 maticReward
    ) external {
        require(msg.sender == trustedSequencer, "Only sequencer");
        
        // Compress and hash transactions
        bytes32 transactionsHash = keccak256(transactions);
        
        // Create batch
        batches[currentBatch].transactionsHash = transactionsHash;
        batches[currentBatch].sequencer = msg.sender;
        batches[currentBatch].maticReward = maticReward;
        
        emit ForceBatch(currentBatch, transactionsHash);
        currentBatch++;
    }
}

Comparison Table

Feature zkSync Era StarkNet Polygon zkEVM
EVM Compatible Yes (zkEVM) No (Cairo) Yes
Type ZK Rollup ZK Rollup ZK Rollup
TPS ~2,000 ~1,000 ~2,000
TVL $1B+ $800M+ $500M+
Native Token ZK STRK MATIC
Status Mainnet Mainnet Mainnet

Privacy-Preserving Applications

Private Transactions

ZK proofs enable private transactions where amount and sender/recipient are hidden.

// Privacy pool contract
contract PrivacyPool {
    mapping(bytes32 => uint256) public commitments;
    mapping(bytes32 => bool) public nullifierHashes;
    
    event Deposit(bytes32 commitment, uint256 leafIndex);
    event Withdrawal(address recipient, bytes32 nullifierHash);
    
    function deposit(bytes32 _commitment) external payable {
        require(msg.value >= MIN_DEPOSIT, "Insufficient deposit");
        
        // Store commitment (hash of secret + nullifier)
        uint256 leafIndex = insertCommitment(_commitment);
        
        emit Deposit(_commitment, leafIndex);
    }
    
    function withdraw(
        address payable _recipient,
        bytes32 _nullifierHash,
        bytes32 _root,
        bytes32[] memory _proof
    ) external {
        // Verify the Merkle proof
        require(
            verifyProof(_root, _nullifierHash, _proof),
            "Invalid proof"
        );
        
        // Prevent double-spending
        require(
            !nullifierHashes[_nullifierHash],
            "Already withdrawn"
        );
        
        // Mark as spent
        nullifierHashes[_nullifierHash] = true;
        
        // Transfer funds
        _recipient.transfer(msg.value);
        
        emit Withdrawal(_recipient, _nullifierHash);
    }
}

Private DeFi

# Private AMM (simplified concept)
class PrivateAMM:
    """
    AMM with ZK proofs for privacy
    """
    
    def __init__(self, token_a, token_b):
        self.token_a = token_a
        self.token_b = token_b
        self.reserve_a = 0
        self.reserve_b = 0
    
    def generate_swap_proof(self, swap_request):
        """
        Generate ZK proof for swap without revealing amounts
        """
        # 1. Verify sender has sufficient balance (private)
        balance_proof = ZKProof.balance_proof(
            swap_request.sender,
            swap_request.amount_in
        )
        
        # 2. Verify swap is valid (within slippage)
        amount_out = self.calculate_output(swap_request.amount_in)
        swap_proof = ZKProof.swap_proof(
            amount_in=swap_request.amount_in,
            amount_out=amount_out,
            min_amount_out=swap_request.min_amount_out,
            reserve_a=self.reserve_a,
            reserve_b=self.reserve_b
        )
        
        # 3. Generate nullifier for privacy
        nullifier = ZKProof.generate_nullifier(
            swap_request.secret
        )
        
        return {
            'balance_proof': balance_proof,
            'swap_proof': swap_proof,
            'nullifier': nullifier
        }

Building with ZK Proofs

Setting Up a ZK Development Environment

# Install Circom (ZK circuit compiler)
git clone https://github.com/iden3/circom.git
cd circom
cargo build --release
export PATH=$PATH:./target/release

# Install SnarkJS (ZK proof library)
npm install -g snarkjs

# Create a new ZK circuit project
circom my_circuit.circom --r1cs --wasm --sym --c

# Generate trusted setup
snarkjs powersoftau new bn128 15 pot15_0000.ptau -v
snarkjs powersoftau contribute pot15_0000.ptau pot15_final.ptau --name="First contribution" -v

# Generate zkey
snarkjs groth16 setup my_circuit.r1cs pot15_final.ptau my_circuit_0000.zkey
snarkjs zkey contribute my_circuit_0000.zkey my_circuit_final.zkey --name="Contributor 1" -v

# Export verification key
snarkjs zkey export verificationkey my_circuit_final.zkey verification_key.json

Writing a Simple ZK Circuit

// Simple circom circuit: Prove you know the square root
// Input: x (number whose square root you know)
// Private input: root (the square root)
// Output: x (public)

template SquareRoot() {
    // Declare signals
    signal private input root;
    signal output x;
    
    // Constraint: x must equal root * root
    x <== root * root;
    
    // Additional constraint to prevent trivial solution
    // (optional, ensures root > 1)
    signal root_squared <== root * root;
    root_squared === x;
}

component main {public [x]} = SquareRoot();

Generating and Verifying Proofs

// JavaScript to generate and verify proof
const circom = require('circom');
const snarkjs = require('snarkjs');

async function generateProof(input) {
    // Generate witness
    const { witness } = await circom.witness(
        'my_circuit.wasm',
        { root: input.root }
    );
    
    // Generate proof
    const { proof, publicSignals } = await snarkjs.groth16.fullProve(
        witness,
        'my_circuit_final.zkey'
    );
    
    return { proof, publicSignals };
}

async function verifyProof(proof, publicSignals) {
    const vKey = JSON.parse(
        fs.readFileSync('verification_key.json')
    );
    
    return await snarkjs.groth16.verify(
        vKey,
        publicSignals,
        proof
    );
}

// Usage
async function main() {
    // Generate proof (secret: root = 5, so x = 25)
    const { proof, publicSignals } = await generateProof({ root: 5 });
    
    console.log('Public output (x):', publicSignals[0]); // 25
    
    // Verify - verifier only sees x = 25, never learns root = 5
    const isValid = await verifyProof(proof, publicSignals);
    console.log('Proof valid:', isValid); // true
}

main();

The Future of ZK in 2026+

1. ZK Coprocessors

  • Compute heavy workloads off-chain with ZK proofs
  • Examples: Giza, Risc Zero, Succinct

2. zkEVM Wars

  • Multiple teams competing for EVM equivalence -zkSync, Polygon, Scroll, StarkNet

3. Privacy Layers

  • Private smart contracts
  • Privacy as a service

4. Cross-Chain ZK

  • ZK bridges between chains
  • Lightweight verification

5. Hardware Acceleration

  • GPU-accelerated proving
  • ASIC development

Upcoming Projects

Project Type Status Focus
zkSync 3.0 zkEVM In Development Full EVM equivalence
Polygon Miden zkEVM Testnet Privacy features
Scroll zkEVM Mainnet EVM equivalence
StarkNet zkRollup Mainnet Decentralized
Aztec zkRollup Testnet Privacy

Conclusion

Zero-knowledge proofs represent a fundamental shift in how we think about privacy and scalability in blockchain systems. From zkSNARKs powering zkRollups to zkSTARKs providing transparency and quantum resistance, the ZK ecosystem has grown remarkably.

In 2026, ZK technology is production-ready. Major zkRollups process billions in TVL, privacy applications are becoming practical, and the tooling ecosystem has matured significantly. For developers, now is the time to learn ZK developmentโ€”it’s one of the most valuable skills in the blockchain space.

Whether you’re building scaling solutions, privacy applications, or exploring new cryptographic possibilities, ZK proofs provide powerful primitives that will shape the future of decentralized systems.

Resources

Comments