# Security model and audit notes (ZKAC 0.2) This document summarizes the design, residual risks, and recommendations for operators integrating **ZKAC**. It is not a substitute for independent review before high-assurance deployment. ## Goals - **Authentication:** Only holders of a valid BBS+ credential for a registered role can complete `verify_auth` for that role. - **Server identity:** The server proves its long-term identity to the client via a Schnorr signature over the session transcript; clients verify against a pinned public key. This prevents MITM attacks without requiring TLS. - **Confidentiality & integrity:** Session payloads are authenticated-encrypted (ChaCha20-Poly1305) with a key derived from an ephemeral X25519 handshake. - **Replay resistance:** Duplicate ciphertexts in a direction are rejected (sliding window + monotonic counter). - **Unlinkability (credential layer):** BBS+ presentations are unlinkable across sessions when the presentation header (the session transcript hash) differs; the verifier learns only the disclosed attributes (opaque `role_id`, epoch) and validity. Client anonymity is preserved: the client never reveals its long-term key during the handshake. - **Server cannot forge credentials:** The server stores only the issuer **public** key per role; forging requires the issuer secret key. ## Cryptographic components | Layer | Primitive | Purpose | |-------|-----------|---------| | Transport | X25519 ephemeral DH, HKDF-SHA256, ChaCha20-Poly1305 | Session keys, AEAD | | Identity | Schnorr on Ristretto255, BLAKE2b-512 challenge | Server identity binding | | Credentials | BBS+ on BLS12-381 (zkryptium), SHAKE256 ciphersuite | Blind issuance, ZK presentations | | Role IDs | BLAKE2b-512 (truncated to 32 bytes) | Opaque role identifiers | ## Protocol flow ``` Client Server |--- init_msg (eph_pk) ------------>| | | accept() | | prove_identity() → sign(transcript) |<-- response_msg + identity_pkt ---| | complete DH | | decrypt + verify server sig | | encrypt BBS+ auth | |--- encrypted BBS+ auth ---------> | | | verify_auth() |===== encrypted session ===========>| ``` ## Threats considered ### Network attacker (passive) - Observes ciphertexts; cannot break ChaCha20-Poly1305 or derive session keys without breaking X25519 / HKDF under standard assumptions. ### Network attacker (active / MITM) - **Server impersonation:** The server signs the session transcript hash with its long-term Ristretto255 key (`prove_identity`). The client verifies this signature against the **pinned** server public key. A MITM running a separate DH exchange produces a different transcript; it cannot forge the server's signature. The client aborts on mismatch. - **Client impersonation:** The BBS+ presentation is bound to the session transcript hash. A MITM cannot relay a presentation from one session to another (different transcripts) or forge one (requires a valid credential from the issuer). - **Relay attack:** A MITM that relays the real server's identity proof to a client fails because the proof is encrypted under the MITM-to-server session keys (not the client-to-MITM keys), and the signature is over the wrong transcript. ### Malicious server - Can **learn** opaque `role_id`, current epoch, and that *some* valid member authenticated. - **Cannot** forge BBS+ credentials without the issuer secret key. - **Cannot** learn `member_secret` from presentations under the BBS+ security assumptions. - **Cannot** distinguish which specific member authenticated among valid credential holders (unlinkability holds against the verifier for distinct presentation headers). - **Cannot** learn the client's long-term public key — it is never transmitted. ### Malicious client - Cannot decrypt others' traffic without session keys. - Cannot produce valid auth for a role without a valid credential + correct epoch + registry entry. ### Denial of service - **Auth packet size:** Proof length is capped (`MAX_BBS_AUTH_PROOF_BYTES`, 256 KiB) to bound allocations. - **Handshake:** Fixed 32-byte messages; no variable-length handshake parsing. - General packet limits should still be enforced at the application layer (total message size, rate limits). ## Key distribution The server's long-term `PublicKey` (32-byte Ristretto255 point) functions as a **self-authenticating identity** — no certificate authority is required. The client must obtain and pin this key before connecting. Recommended strategies: 1. **Static configuration** (default): embed the server public key in client config, environment variable, or CLI flag. Equivalent to WireGuard's `[Peer] PublicKey = ...`. 2. **Trust On First Use (TOFU):** accept the server's key on first connection, pin it for subsequent sessions. Risk: first connection is vulnerable. 3. **Out-of-band verification:** compare public key fingerprints over a trusted side channel (phone, in-person, encrypted messaging). 4. **Key registry / directory:** a trusted service maps names to public keys. Shifts trust to the registry and its authentication channel. ## Operational requirements 1. **Issuer secret key:** Protect `BbsIssuer` secret material (HSM, KMS, or encrypted at rest). Compromise = ability to issue arbitrary credentials for that role. 2. **Server long-term key:** Protect the `Node` `Keypair` secret. Compromise = ability to impersonate the server. Rotate the key and distribute the new public key to clients if compromised. 3. **Member storage:** `member_secret` and finalized `Credential` material must be protected; loss = re-enrollment required. 4. **Epoch revocation:** On compromise or policy change, call `set_epoch` and re-issue credentials only to legitimate members; old credentials become invalid at verification time. 5. **Registry integrity:** The server's `(role_id → public key, epoch)` mapping must be integrity-protected (trusted storage or signed updates), or attackers could swap keys or epochs. 6. **Role ID privacy:** `role_id` is a hash of the role name only if you use `role_id("myrole")`; treat role names as secrets if enumeration is a concern, or derive role IDs with an additional secret salt known to members. ## Implementation notes (audit checklist) - [x] BBS+ proof verification uses the same header and presentation binding as proof generation (`verify_presentation` in Rust). - [x] Session transcript is included in the presentation via `present(transcript_hash)`. - [x] Server identity proof: Schnorr signature over `transcript_hash`, verified against pinned public key before BBS+ auth proceeds. - [x] Schnorr nonce is deterministic (`H(sk || msg)`) — no dependence on RNG quality at signing time. - [x] Replay protection is symmetric per direction in `Session`. - [x] Constant-time comparisons are used where critical in transport/replay paths (`subtle` crate). - [x] Client long-term key is never transmitted, preserving BBS+ unlinkability. - [ ] **External:** Python bindings surface raw bytes; callers must not log secrets (`secret_key_bytes`, `member_secret`, `prover_blind`). - [ ] **External:** Use secure randomness from the OS (library uses OS RNG for key generation paths exposed in Rust). ## Design decisions - **Server-only identity proof:** Only the server signs the transcript. Adding client long-term signing would break BBS+ unlinkability (the server could correlate sessions by client public key). Client authentication is handled entirely by the anonymous BBS+ credential. - **Deterministic Schnorr nonces:** The signing nonce is derived as `H("zkac-schnorr-nonce" || sk || msg)`, eliminating a class of RNG-failure attacks (cf. PS3 ECDSA, Sony 2010). Same key + same message = same signature. ## Known limitations - **No post-quantum** primitives: classical security assumptions only. - **Epoch granularity:** Revocation is coarse (epoch bump); plan issuance and rotation policy accordingly. - **zkryptium dependency:** Security follows the underlying crate and BLS12-381/BBS+ standards; keep dependencies updated. - **Key distribution:** The library provides the cryptographic mechanism; initial key distribution is an application-layer responsibility. ## Reporting issues Report security-sensitive findings through your project's private disclosure channel (configure `SECURITY.md` contact or GitHub security advisories when the repository is public).