BLS12-381 signatures, Pedersen commitments, Groth16 zero knowledge proofs, and RedJubjub credential signing. One Rust workspace, compiled to native, WASM, mobile, and UniFFI bindings.
Eight library crates, one integration test crate, and two CLI tools. All share a single version (0.1.0), edition, MSRV, and licence through the workspace Cargo.toml.
| Crate | Purpose |
|---|---|
provii-crypto-commons |
Shared error types, domain separation constants, credential message structures, and serialisation helpers. Supports no_std via the std feature gate. |
provii-crypto-commit |
Pedersen commitments over the Jubjub curve using Sapling generators. Provides pedersen_commit_dob_validated for date of birth binding and pedersen_nullifier for deterministic credential identifiers. |
provii-crypto-sig-redjubjub |
Custom RedJubjub signature scheme on the Jubjub prime order subgroup. Signs credential prehashes with BLAKE2s domain separation. Not Zcash compatible. |
provii-crypto-circuit-age |
Groth16 arithmetic circuit (BLS12-381) that proves age eligibility without revealing date of birth. Encodes Pedersen commitment opening, RedJubjub signature verification, nullifier derivation, and relying party binding as R1CS constraints. |
provii-crypto-public-inputs |
Canonical bit packing of the 8 BLS12-381 scalar field elements the circuit exposes: direction, cutoff, RP hash, issuer verification key, credential nullifier. |
provii-crypto-prover |
High level Groth16 proof generation. Loads proving parameters, synthesises the age circuit, serialises the output, and returns it as bytes. Multicore on desktop, single threaded on WASM and mobile. |
provii-crypto-verifier |
Groth16 proof verification against a prepared verifying key registry. Assembles public inputs canonically and delegates to Bellman. |
provii-crypto-protocol |
PKCE S256 challenges, nonce generation (platform aware for WASM via getrandom), relying party challenge binding with SHA-256, and origin hashing. |
provii-crypto-e2e-tests |
Cross crate integration tests exercising the full issuance and verification pipeline. Not published. |
provii-keygen-tool |
CLI for generating RedJubjub issuer key pairs. Located in tools/keygen. |
issuer-gen |
Interactive CLI for provisioning issuer credentials with metadata. Located in tools/issuer-gen. |
API documentation for each published crate is on docs.rs.
Add the crates you need to your Cargo.toml. Most consumers want the prover, verifier, protocol, or signature crate.
[dependencies]
provii-crypto-commons = "0.1"
provii-crypto-commit = "0.1"
provii-crypto-sig-redjubjub = "0.1"
provii-crypto-prover = "0.1"
provii-crypto-verifier = "0.1"
provii-crypto-protocol = "0.1"Generating a Pedersen commitment and its nullifier:
use provii_crypto_commit::{
generate_commitment_randomness, pedersen_commit_dob_validated, pedersen_nullifier,
};
use rand::rngs::OsRng;
fn main() -> Result<(), provii_crypto_commons::Error> {
let dob_days: i32 = 7300; // ~20 years from epoch
let r_bits = generate_commitment_randomness(&mut OsRng, 192);
let commitment = pedersen_commit_dob_validated(dob_days, &r_bits)?;
let nullifier = pedersen_nullifier(&commitment);
assert_eq!(commitment.len(), 32);
assert_eq!(nullifier.len(), 32);
Ok(())
}Several crates compile to wasm32-unknown-unknown for use in Cloudflare Workers. The workspace includes a [profile.worker] optimised for minimal binary size (opt-level = "z", fat LTO, panic abort, single codegen unit).
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --profile worker -p provii-crypto-protocolOn WASM targets, provii-crypto-protocol uses getrandom with the js feature for nonce generation, and provii-crypto-prover disables Bellman's multicore feature to avoid thread dependencies. These switches are automatic via cfg(target_arch = "wasm32") in each crate's Cargo.toml.
The workspace includes a [profile.mobile] with thin LTO and opt-level = 2 for a balance of proof generation speed and binary size. The provii-mobile-sdk repository wraps these crates through UniFFI to produce Swift and Kotlin bindings. This repository does not contain UniFFI definitions itself.
# iOS (aarch64)
cargo build --target aarch64-apple-ios --profile mobile --workspace
# Android (aarch64)
cargo build --target aarch64-linux-android --profile mobile --workspaceOn Android and iOS targets, Bellman's multicore feature is disabled automatically via conditional dependencies in provii-crypto-prover and provii-crypto-circuit-age.
Secret data never touches a comparison operator. All equality checks on secret material go through subtle::ConstantTimeEq::ct_eq(), and the signing and proving paths contain no branches or array indexing driven by secret values.
Key material is zeroed on drop. Witness and circuit types (AgeWitness, AgeCircuit) along with credential attestation structures derive both Zeroize and ZeroizeOnDrop. SigningKey in the RedJubjub crate implements Zeroize with a manual Drop that calls zeroize(). Signing nonces are zeroed via volatile writes after use.
#[deny(unsafe_code)] is set at the workspace level. Every library crate goes further with #![forbid(unsafe_code)] at the crate root. One exception exists: provii-crypto-sig-redjubjub uses #![deny(unsafe_code)] instead, because two #[allow(unsafe_code)] blocks perform from_raw_parts_mut volatile writes to zeroize JubjubScalar fields. The upstream jubjub crate does not implement Zeroize on its scalar type, so we do it ourselves. Both blocks carry written safety invariants explaining why.
Clippy denies unwrap_used, expect_used, panic, indexing_slicing, arithmetic_side_effects, and cast_possible_truncation among other lints across the entire workspace. Library functions return Result.
The fuzz/ directory contains libFuzzer targets for the cryptographic crates.
The MSRV is 1.85, enforced by rust-version = "1.85" in the workspace Cargo.toml. Bumping the MSRV is a semver minor change and will be noted in CHANGELOG.md.
Licensed under Apache-2.0.