Skip to Content
All memories

CPZKp - Building Practical Zero-Knowledge Proofs in Rust from Scratch

 — #Rust#cryptography#zero-knowledge#chaum-pedersen#ecc#curve25519#security#portfolio

CPZKp Banner

“You don’t start with ZK. ZK starts with you.” — someone probably

Introduction

In late 2023, the seed for CPZKp was planted: a lightweight, modular, and no-bullshit Rust library for zero-knowledge proofs using the Chaum-Pedersen protocol. The motivation was personal and practical — to build a foundation that respects cryptographic rigor, while remaining usable in real-world systems, especially those relying on elliptic curve cryptography (ECC).

This post is not a “hello world.” It’s a journey — from group theory to Curve25519 bindings, from low-level proof serialization to full WASM exports. If you're looking for a project that goes from first principles to full-stack cryptography, buckle up.


Motivation

By 2023, a few realities had become clear:

  1. Most zero-knowledge implementations are either academic toys or tightly bound to specific use cases (blockchains, zkSNARKs, etc).
  2. Libraries like bulletproofs or zkcrypto are excellent, but bloated when you need just authentication proofs.
  3. There was no ergonomic, extensible, and no_std-capable Chaum-Pedersen implementation in Rust.

CPZKp was born from frustration — and fascination.


What is the Chaum-Pedersen ZKP?

It’s a proof of equality of discrete logs: If you know x such that g^x = A and h^x = B, you can prove knowledge of x without revealing it.

This is essential in:

  • Secure authentication (no password ever transmitted)
  • Key exchange validation
  • Voting and threshold cryptography

The challenge was: how do we express this cleanly across scalar groups and ECC, and still support Curve25519?


Designing the Library

We started with a few key design principles:

Protocols as Traits

pub trait ZkpProtocol {
    type Secret;
    type Public;
    type Proof;

    fn prove(secret: &Self::Secret, pub_input: &Self::Public) -> Self::Proof;
    fn verify(public: &Self::Public, proof: &Self::Proof) -> bool;
}

Protocols are swappable. This allows for different backends (secp256k1, Ristretto, ScalarGroup) and experimentation with variants.

Scalar and ECC Support

mod scalar;
mod ecc;

Internally, both conform to common traits like GroupElement, enabling unified logic in proof generators and verifiers.


Serialization: Making Proofs Portable

One key requirement was to serialize proofs for transmission.

We used serde and implemented robust custom serialization for scalar and ECC formats:

#[derive(Serialize, Deserialize)]
pub struct ChaumPedersenProof {
    pub t1: GroupElement,
    pub t2: GroupElement,
    pub challenge: Scalar,
    pub response: Scalar,
}

This allowed JSON/web compatibility from day one.


Testing the Unprovable

We didn’t stop at unit tests. CPZKp includes:

  • 🔁 Property-based tests (proptest)
  • 🧪 Negative tests (e.g., corrupt challenge / invalid response)
  • 🧬 Deterministic regression seeds for CI stability
  • 🔍 Manual validation of group assumptions
proptest! {
    #[test]
    fn prove_and_verify_should_hold(ref s in any::<Scalar>()) {
        let (pk, proof) = ChaumPedersen::prove(&s);
        prop_assert!(ChaumPedersen::verify(&pk, &proof));
    }
}

WASM and Python Bindings

We wanted this lib usable in:

  • dApps (via WASM)
  • Python systems (via pyo3)

Result:

  • wasm_bindgen wrapper in wasm.rs
  • maturin build in bindings/python
#[wasm_bindgen]
pub fn prove_json(sk: &str, pk: &str) -> String {
    ...
}

Now CPZKp runs in browsers and Jupyter notebooks.


CLI Tool

We implemented a command-line utility for quick usage:

cpzkp gen-key
cpzkp prove --msg "authenticate me"
cpzkp verify --proof proof.json

Backed by clap, this made it ideal for scripting, automation, or even classroom demos.


Performance

Benchmarks were done using criterion. Example:

group                           time
ChaumPedersen_scalar_prove     1.2 µs
ChaumPedersen_ecc_prove        4.8 µs
ChaumPedersen_verify_scalar    0.9 µs
ChaumPedersen_verify_ecc       3.7 µs

Enough for embedded use and authentication services.


What We Learned

  • Traits + generic cryptographic algebra = superpowers.
  • Testing edge cases in ZKP is not optional — it’s life.
  • Targeting WASM early saves time later.
  • Your build scripts are part of your UX.

Roadmap

  • 🔒 Formal audit and fuzz testing
  • 📦 Publish to crates.io and PyPI
  • 🧱 Add Bulletproofs-style range proofs
  • 🔄 Add MPC-friendly APIs
  • 🌐 Playground (CPZKp + Monaco + WebWasm)

Conclusion

CPZKp isn’t another toy crypto lib. It’s a usable, modular ZKP toolkit built from real-world needs, shaped by frustration, and delivered with love — in Rust.

Try it. Break it. Extend it.


GitHub