Announcing OONI's New Anonymous Credential System

In our previous posts, we outlined why OONI requires an anonymous credential system and detailed the security and privacy requirements such a system must satisfy. We highlighted the core challenge: OONI needs to establish trust in submitted measurements without creating identifiers that could reveal user identity or allow cross-network tracking.

Anonymous credentials offer a cryptographic mechanism to authenticate certain properties of a probe — such as long-term participation or measurement volume — without exposing who the user is, where they are, or linking their activity across networks.

OONI’s requirements

To meet OONI’s threat model, a credential system must satisfy several constraints:

Existing credential ecosystems (blind-signature-based, zk-friendly signatures, SNARK-based constructions) each satisfy some of these requirements but not all. In particular, OONI needs issuer-local verification, efficient range proofs, network-dependent pseudonyms, and credential update protocols. No off-the-shelf system met these constraints without significant compromises.

Building a modular, expressive credential framework

In collaboration with Ian Goldberg (University of Waterloo), Lindsey Tulloch (Tor Project), and Victor Graf (Risc Zero), we built a layered system for building zero-knowledge credentials and protocols. Three Rust crates (cmz, sigma-compiler, and sigma-proofs) implementing well-studied sigma-protocols and compile high-level statements into efficient linear-algebraic proof statements. In parallel, CFRG drafts for these protocols are being built (draft-irtf-cfrg-sigma-protocols, draft-irtf-cfrg-fiat-shamir).

Some of the features provided that are particularly useful for OONI are:

  1. Multi-show, issuer-local verification. OONI’s usage pattern resembles KVAC credentials (issuer=verifier), which the CMZ and μCMZ schemes support out of the box.
  2. Efficient range proofs. Coarse ranges for probe_age and measurement_count (e.g. >1 week, >1k measurements) are essential to avoid creating near-unique tuples. The sigma-rs stack supports these range proofs compactly.
  3. Network-local pseudonyms. A probe must appear as one identity per network, but remain unlinkable across networks. This maps naturally onto the attribute and constraint system used by sigma-rs (e.g., pseudonym = secret * DOMAIN).
  4. Issuance + update protocols. Our model requires showing an old credential and receiving a new updated one (incrementing measurement_count) without revealing underlying values — exactly the pattern described in the UserAuth example.

What OONI’s credential looks like

OONI’s anonymous credential includes the following attributes:

CMZ! { UserAuthCredential:
    nym_id, // a stable per-installation secret
    age, // coarse bucket
    measurement_count // measurements submitted
}

These match the additional metadata we described in our requirements post while respecting anonymity constraints.

A high-level view of our “submit measurement” protocol

Using the sigma-rs μCMZ API, the protocol for submitting a new measurement looks schematically like:

muCMZProtocol!(submit<min_age_today, max_age, min_measurement_count, max_measurement_count, @DOMAIN, @NYM>,
    Old: UserAuthCredential { nym_id: H, age: H, measurement_count: H},
    New: UserAuthCredential { nym_id: H, age: H, measurement_count: H},
    // the per-installation secret is the same
    Old.nym_id = New.nym_id,
    // the age is the same
    Old.age = New.age,
    // the new measurement count is increased
    New.measurement_count = Old.measurement_count + 1,
    // the submitted NYM is correct
    NYM = Old.nym_id * DOMAIN,
    // the age and measurement counts are within the desired range
    (min_age_today..=max_age).contains(Old.age),
    (min_measurement_count..=max_measurement_count).contains(Old.measurement_count) );

This ensures:

The server sees only the proof, not the underlying attributes. The attribute measurement_count is used to assess a user’s participation in the network, not to rate-limit users.

Integration in the OONI codebase

The OONI Probe engine is primarily written in Go, while the anonymous credential system is implemented in Rust. To integrate the two cleanly, we expose a small C-compatible API from Rust and generate Go bindings using rust2go. The design separates responsibilities: Rust owns all cryptography (credential issuance, zero-knowledge proofs, verification, and updates), while the client handles orchestration, persistence, and state machines.

The Rust library acts as a self-contained “crypto engine” that consumes opaque inputs (the current credential and protocol parameters), produces protocol messages for the server, and processes responses to return an updated credential, with all state management intentionally delegated to the calling application to keep the API minimal and portable.

The client is responsible for persisting and reloading:

This keeps the boundary clean: the Go side treats credentials and protocol messages as opaque bytes, while Rust enforces all correctness and privacy properties internally.

Data exchange model

Across the Rust↔Go boundary we only pass:

This approach lets Go integrate the system without re-implementing cryptography, while still keeping networking and application logic consistent with the rest of OONI Probe.

The long term keys will be managed and stored relying on the native OS capabilities. In practice this will mean using on Android the Android Keystore, while on iOS the Keychain services.

What’s next

OONI users will soon be able to filter or annotate measurements by:

While full Sybil-resistance remains an application-layer problem (e.g. registration rate-limiting), authenticated measurement_count and probe_age buckets provide meaningful friction to would-be attackers.

If you’re a cryptographer, implementer, or just curious, we welcome feedback — this work is meant to serve the global OONI community safely and transparently.

Stay tuned for deeper dives into the protocol design, performance benchmarks, and how sigma-rs will ship inside the next major OONI Probe release.