Skip to content

q-uint/belpic-zkp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

belpic-zkp

Demo -- SP1 zero-knowledge proof for Belgian eID selective disclosure.

Proves that identity data was signed by a Belgian government RRN key, selectively disclosing only chosen fields (e.g. surname, first names, nationality) without revealing the full identity.

Note: This is a demo. The RRN certificate is not verified against the root CA. A production system must verify the full certificate chain (Root CA -> RRN) before trusting the public key.

Architecture

belpic-zkp/
  lib/                         # shared types (no_std compatible)
  program/                     # SP1 guest (RISC-V) - the proven computation
  script/                      # host/prover CLI
  tools/generate-fixtures/     # mock cert + identity generator for testing

Data flow:

  1. Card reader exports: root CA cert, RRN cert, identity TLV, identity signature
  2. Host verifies the certificate chain (Root CA -> RRN) in plaintext - no privacy concern
  3. Host extracts the RRN public key and passes it + identity data to the SP1 guest
  4. Guest verifies the identity signature, computes identity hash, extracts disclosed fields
  5. Host generates a proof and checks the guest's rrn_pubkey_hash matches the cert chain

The verifier only needs to confirm that rrn_pubkey_hash in the proof corresponds to a legitimate Belgian RRN key.

Prerequisites

  • Nix with flakes enabled
  • rustup (the flake relies on rustup-managed toolchains)

Getting started

# Enter the dev shell (installs SP1 toolchain on first run)
nix develop

# Generate mock test fixtures (requires openssl, provided by the flake)
cd tools/generate-fixtures && cargo run && cd ../..

# Build
cargo build -p belpic-script --release

Usage

Execute (fast, no proof)

Runs the guest logic without generating a proof. Useful for testing.

cargo run -p belpic-script --release -- --execute \
  --root-cert fixtures/root_cert.der \
  --rrn-cert fixtures/rrn_cert.der \
  --identity fixtures/identity.bin \
  --identity-sig fixtures/identity_sig.bin \
  --disclose surname,first_names,nationality

Generate + verify proof

cargo run -p belpic-script --release -- --prove \
  --root-cert fixtures/root_cert.der \
  --rrn-cert fixtures/rrn_cert.der \
  --identity fixtures/identity.bin \
  --identity-sig fixtures/identity_sig.bin \
  --disclose surname,first_names,nationality

Outputs proof.bin and vkey.bin (bincode-serialized). Use --output and --vkey-output to change paths.

Third-party verification

A third party can verify the proof without the original identity data. They only need the proof, the verification key, and the RRN certificate(s) they trust:

cargo run -p belpic-script --release --bin belpic-verify -- \
  --proof proof.bin \
  --vkey vkey.bin \
  --trusted-rrn rrn_cert.der

The verifier:

  1. Verifies the SP1 proof cryptographically
  2. Checks that the rrn_pubkey_hash in the proof matches the trusted RRN certificate's public key
  3. Prints the disclosed fields if everything checks out

If the hash doesn't match any trusted certificate, verification fails.

Disclosable fields

surname, first_names, nationality, birth_date, birth_location, sex, national_number, card_number, card_validity_begin, card_validity_end, document_type

Testing

# Unit + integration tests (lib crate)
cargo test -p belpic-lib

Integration tests parse the generated mock certificates and identity TLV data. Run generate-fixtures first.

How it works

Host (outside ZKP):

  1. Checks root CA fingerprint against known Belgian roots (RCA, RCA2, RCA3, RCA4)
  2. Parses the RRN certificate and extracts its RSA public key (demo: does NOT verify RRN cert signature against root CA)
  3. Passes the RRN public key components + identity data + signature to the guest

Guest (inside ZKP):

  1. Reconstructs the RRN RSA public key and hashes it (so the verifier can confirm which key was used)
  2. Verifies the identity data signature (RSA PKCS#1 v1.5 + SHA-256)
  3. Computes SHA-256 of the full identity file (pseudonymous linkage / unique registry key)
  4. Parses the identity TLV and extracts only the fields matching the disclosure mask
  5. Commits public outputs: rrn_pubkey_hash, identity_hash, disclosed fields

Public outputs in the proof:

  • rrn_pubkey_hash - SHA-256 of the RRN public key used; verifier checks this matches a known Belgian RRN key
  • identity_hash - SHA-256 of the full identity file; unique per person, no PII
  • disclosed_fields - only the requested tag/value pairs

SP1 patched crates

The guest uses SP1-patched versions of sha2, rsa, and crypto-bigint that route cryptographic operations through SP1 precompiles for performance:

[patch.crates-io]
sha2 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", tag = "patch-sha2-0.10.8-sp1-6.0.0" }
crypto-bigint = { git = "https://github.com/sp1-patches/RustCrypto-bigint", tag = "patch-0.5.5-sp1-6.0.0" }
rsa = { git = "https://github.com/sp1-patches/RustCrypto-RSA", tag = "patch-0.9.6-sp1-6.0.0" }

About

SP1 Zero-Knowledge Proof for Belgian eID Selective Disclosure

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors