#!/usr/bin/env python3 """ Generate demo credentials under creds/: issuer, server transport key, two member credentials. Run once before starting the server. """ from __future__ import annotations import argparse import base64 import json from pathlib import Path import zkac # Human-readable role names; each becomes a 32-byte opaque role_id via zkac.role_id(). # Must stay in sync with server.py (registry + api_body_for_role). ROLES = ("analyst", "operator") def main() -> None: ap = argparse.ArgumentParser(description="Generate ZKAC demo credential files.") ap.add_argument( "--output-dir", type=Path, default=Path(__file__).resolve().parent / "creds", help="Directory to write files (default: demo/creds)", ) args = ap.parse_args() out: Path = args.output_dir out.mkdir(parents=True, exist_ok=True) # BBS+ issuer: signs blind credentials; server only needs the public key in RoleRegistry. issuer = zkac.BbsIssuer() issuer_pk = issuer.public_key() epoch = 1 # Long-term Ristretto identity for the TCP server (X25519 handshake + Schnorr identity proof). server_kp = zkac.Keypair() server_pk = server_kp.public_key() issuer_payload = { "issuer_secret_key_b64": base64.b64encode(issuer.secret_key_bytes()).decode(), "issuer_public_key_b64": base64.b64encode(issuer_pk.to_bytes()).decode(), } (out / "issuer.json").write_text(json.dumps(issuer_payload, indent=2), encoding="utf-8") transport_payload = { "server_secret_key_b64": base64.b64encode(server_kp.secret_key_bytes()).decode(), "server_public_key_b64": base64.b64encode(server_pk.to_bytes()).decode(), } (out / "transport.json").write_text(json.dumps(transport_payload, indent=2), encoding="utf-8") # One blind issuance per role: issuer never learns member_secret. for role_name in ROLES: rid = zkac.role_id(role_name) req = zkac.prepare_blind_request() blind_sig = issuer.issue_blind(req.commitment_with_proof(), rid, epoch) member = { "role_name": role_name, "role_id_hex": rid.hex(), "epoch": epoch, "blind_sig_b64": base64.b64encode(blind_sig).decode(), "member_secret_b64": base64.b64encode(req.member_secret()).decode(), "prover_blind_b64": base64.b64encode(req.prover_blind()).decode(), "issuer_public_key_b64": base64.b64encode(issuer_pk.to_bytes()).decode(), } (out / f"member_{role_name}.json").write_text( json.dumps(member, indent=2), encoding="utf-8" ) print(f"Wrote issuer, transport, and member files to {out}") print( f"Roles: {', '.join(ROLES)} — use member_{ROLES[0]}.json / member_{ROLES[1]}.json with client_cli.py" ) if __name__ == "__main__": main()