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.
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:
- Card reader exports: root CA cert, RRN cert, identity TLV, identity signature
- Host verifies the certificate chain (Root CA -> RRN) in plaintext - no privacy concern
- Host extracts the RRN public key and passes it + identity data to the SP1 guest
- Guest verifies the identity signature, computes identity hash, extracts disclosed fields
- Host generates a proof and checks the guest's
rrn_pubkey_hashmatches the cert chain
The verifier only needs to confirm that rrn_pubkey_hash in the proof corresponds to a legitimate Belgian RRN key.
# 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 --releaseRuns 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,nationalitycargo 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,nationalityOutputs proof.bin and vkey.bin (bincode-serialized). Use --output and --vkey-output to change paths.
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.derThe verifier:
- Verifies the SP1 proof cryptographically
- Checks that the
rrn_pubkey_hashin the proof matches the trusted RRN certificate's public key - Prints the disclosed fields if everything checks out
If the hash doesn't match any trusted certificate, verification fails.
surname, first_names, nationality, birth_date, birth_location, sex, national_number, card_number, card_validity_begin, card_validity_end, document_type
# Unit + integration tests (lib crate)
cargo test -p belpic-libIntegration tests parse the generated mock certificates and identity TLV data. Run generate-fixtures first.
Host (outside ZKP):
- Checks root CA fingerprint against known Belgian roots (RCA, RCA2, RCA3, RCA4)
- Parses the RRN certificate and extracts its RSA public key (demo: does NOT verify RRN cert signature against root CA)
- Passes the RRN public key components + identity data + signature to the guest
Guest (inside ZKP):
- Reconstructs the RRN RSA public key and hashes it (so the verifier can confirm which key was used)
- Verifies the identity data signature (RSA PKCS#1 v1.5 + SHA-256)
- Computes SHA-256 of the full identity file (pseudonymous linkage / unique registry key)
- Parses the identity TLV and extracts only the fields matching the disclosure mask
- 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 keyidentity_hash- SHA-256 of the full identity file; unique per person, no PIIdisclosed_fields- only the requested tag/value pairs
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" }