Prove who you are without handing over your data

Keep identity in your wallet and reveal only what a verifier asks for, trust keys without a certificate authority (KeyZ), verify every request at the reverse proxy (KeyZ Gate), keep FTP, HTTP and LDAP clients working. Run the demo in your browser.

Your identity lives in your wallet. An issuer vouches once; every verifier after that sees only what it asks for, sealed to it and useless to anyone else. Keys are trusted because your wallet says so — and stop being trusted the moment you revoke them. Old systems keep working while you move them over.

KeyZ versus X.509 certificates

KeyZ is how CAPPZ trusts keys without a certificate authority: your wallet signs which keys speak for it, for what and until when, and revokes them with a signed record. KeyZ Gate is the small edge gateway that checks every request against those declarations, next to nginx.

The biggest difference is not speed: it is how long a key keeps working after you revoke it. A certificate is trusted until the verifier's revocation list or cached OCSP answer expires; a KeyZ revocation is a signed record the verifier applies as soon as it arrives.

How long a revoked key is still accepted

Run the demo to measure both on this device and see the revocation timeline.

Where each one is better — and what to keep

Where X.509 stays, the table says why. Mostly it is policy — a law, standard or browser root program names the certificate — not something KeyZ, KeyZ Gate and the WAS Protocol cannot do. Policies and standards can be revised; until they are, comply and collect the evidence.

  • X.509 certificates
  • KeyZ declarations
  • Better here
  • Revocation takes effect
  • When the verifier next fetches a CRL or its cached OCSP answer expires: hours to days; many verifiers fail open when the responder is down
  • When the signed revocation record reaches the verifier (replication: seconds); checked offline, nothing to fail open on
  • KeyZ
  • What is proven per request
  • mTLS proves who opened the connection; the request body is not signed
  • Each request is signed over method, path, host and a body digest (RFC 9421 + RFC 9530); replay is refused by nonce
  • KeyZ
  • Answer a verifier gets
  • Valid, or a reason code (expired, revoked, untrusted issuer)
  • Signature state, key trust (declared, delegated, operator-listed, undeclared, revoked, expired), content digest, ledger anchor — separately
  • KeyZ
  • Who vouches
  • One CA hierarchy; a CA compromise affects every certificate it issued
  • The wallet itself, plus any number of attestations; a compromise is one wallet (guardian recovery rotates it)
  • KeyZ
  • Running cost
  • Issuance and renewal per client, or a private CA (HSM, audits, staff)
  • No issuance: the wallet signs its own declarations; verification runs on your own node
  • KeyZ
  • Rotation and old signatures
  • After expiry, old signatures need a timestamp authority to stay verifiable
  • A new declaration supersedes the old one; earlier signatures stay verifiable against the key valid then
  • KeyZ
  • Public websites (browser HTTPS)
  • Required: browsers trust only public CA roots
  • Not a replacement today: keep a public-CA certificate on nginx/Caddy
  • Keep X.509 for now
  • Policy, not technologyBrowser root programs and the CA/Browser Forum Baseline Requirements decide which certificates browsers accept, and the visitor’s browser enforces it. Technically a browser can already trust a key by its hash (WebTransport certificate hashes, which KeyZ Gate offers). Way forward: Keep the public certificate for public pages; use KeyZ behind it and for WebTransport; propose key-declaration trust to the IETF, W3C and browser root programs, with the revocation measurements as evidence.
  • Regulated credentials
  • Required where law names it (eIDAS qualified certificates and seals, PSD2 QWAC/QSealC, PIV/CAC)
  • Keep them for compliance; KeyZ can carry the certificate hash in the declaration and add per-request signing beside it
  • Keep X.509 for now
  • Policy, not technologyThe rule names the instrument (eIDAS qualified certificates and seals, the PSD2 RTS for QWAC/QSealC, FIPS 201 for PIV/CAC). The limit is that the rule requires a certificate, not that a key declaration cannot do the job. Way forward: Comply today, and bring evidence (shadow reports, revocation windows) to the reviews: ETSI ESI standards, EBA reviews of the PSD2 RTS, NIST FIPS 201 revisions. EU digital identity wallets already accept SD-JWT VC, the format the identity vault uses.
  • Hardware and tooling
  • Every HSM, smart card, OS and language ships it
  • Needs the SDK or any RFC 9421 library (Python, PHP, Go, Java exist); keys can live in WebAuthn authenticators
  • Keep X.509 for now
  • Adoption, partly technologyHSMs and smart cards sign with P-256 and Ed25519 keys that KeyZ can declare; their enrolment, management tools and audits are built around certificates. Partly technology: many do not support the secp256k1 wallet key itself. Way forward: Keep the wallet key in a WebAuthn authenticator or software vault and delegate P-256/Ed25519 keys held in the HSM or card; add KeyZ support to the tools as vendors adopt RFC 9421.
  • Works with existing mTLS clients
  • Yes
  • Yes: a wallet-projected certificate is an ordinary X.509 client certificate, trusted as far as the wallet declared its key
  • Both

Retire PHP signature checks on evidence

Put KeyZ Gate in front of one PHP-verified route, verify in both, compare, and retire the PHP check only when the results match. Measured on Intel(R) Core(TM) i9-14900KS (x86_64, not ARM) with 12,000 requests per path at concurrency 32: mostly valid traffic plus tampered bodies, unknown keys, replays and a revoked key that PHP's key list still holds.

What each deployment let through

  • Traffic
  • PHP verifies
  • KeyZ Gate (proxy)
  • KeyZ Gate (auth_request)
  • Valid request
  • served (200×10561)
  • Tampered body
  • all refused (401×360)
  • all refused (422×360)
  • Unknown key
  • all refused (401×360)
  • all refused (403×360)
  • Replayed request
  • 359 let through (200×359)
  • all refused (401×359)
  • Revoked key
  • 360 let through (200×360)
  • all refused (403×360)

Shadow report: 11,332 verdicts agree, 719 KeyZ stricter (revoked ×360, replayed ×359), 0 CAPPZ looser. Retire gate: ready (report 1a8c696618e9…).

Throughput on the same machine (requests per second)

  • Deployment
  • req/s
  • p50 ms
  • p95 ms
  • PHP verifies (today)
  • 8,566
  • 2.28
  • 3.63
  • Shadow comparison (temporary)
  • 2,883
  • 10.61
  • 12.76
  • KeyZ Gate proxies to the app
  • 6,298
  • 3.48
  • 8.65
  • auth_request + 4 KeyZ Gate processes
  • 8,915
  • 2.78
  • 3.99

Honest reading: PHP's own check is native C and very fast (verify 0.083 ms). One KeyZ Gate process started at 3,028 req/s; buffering upstream responses, native Ed25519, a per-wallet evidence index and four processes hashed on cappz-wallet (so each signer's replay cache stays exact) brought the auth_request deployment to 8,915 req/s against PHP's 8,566. Cost per million verifications is a fraction of a cent either way ($0.0083 vs $0.0004 at an assumed $0.034/hour); the difference that matters is the revoked keys and replays.

Run the comparison on your own instance

PHP security note (CVE-2026-109618 / CVE-2026-109619)

Try it with your own passkey

Biometrics stay on your device. Your phone or laptop matches your face or fingerprint; CAPPZ records only a hash of the passkey's id, which an issuer can cite when it vouches for you.

How it works

Identity vault

Each claim is a salted digest plus ciphertext in your identity wallet. Issuers sign digests (SD-JWT VC, ES256K, did:key) at an evidence level. You reveal claims one by one; the answer is sealed to the verifier and bound to its nonce.

KeyZ: keys, not certificates

Your wallet signs which keys speak for it, for what, until when — and revokes early with a record every verifier replays offline. A certificate is only an envelope around such a key.

KeyZ Gate: checked at the edge

A small gateway next to nginx verifies the signature, the key and the body hash for every request — microseconds, no database. The app only sees traffic that passed, with the reason attached.

Costs and value

CAPPZ lines come from the published price sheets. The inputs below are assumptions — change them to yours.

Run the demo to price what it moved.

Use it in your system

Install @cappz/sdk (subpaths /identity, /edge, /transports), or run the edge node next to your proxy. Every sample below is a file in packages/cappz-sdk/examples/; the TypeScript and Python ones run in the test suite.

1 · Run the edge node (KeyZ Gate, DMZ drop box, FTPS, LDAP)

git clone https://github.com/CAL-CAPPZ-Attribution-Liberation/graph-ledger-notary cappz && cd cappz node -e 'crypto.subtle.generateKey({name:"Ed25519"},true,["sign"]).then(async k=>{ console.log("CAPPZ_NODE_KEY="+Buffer.from(await crypto.subtle.exportKey("pkcs8",k.privateKey)).toString("base64")); console.log("CAPPZ_NODE_PUB="+Buffer.from(await crypto.subtle.exportKey("raw",k.publicKey)).toString("hex"))})' > node.env set -a; . ./node.env; set +a CAPPZ_POW=<your node POW address> CAPPZ_NODE_DATA=./node-data CAPPZ_TLS_AUTO=1 \ deno run --allow-net --allow-read --allow-write --allow-env --allow-run=curl \ --allow-sys=networkInterfaces --unstable-net packages/cappz-edge-node/main.ts \ --relay 41445 --verify 41446 --legacy 41447 --ftps 41449 --ldap 41389 # try it curl -T statement.xml http://127.0.0.1:41447/legacy/v1/drop/statement.xml

2 · Identity flow in TypeScript (browser, Node, Deno, Bun)

3 · nginx: verify every request at the edge

4 · Legacy clients: curl drops, FTPS, LDAP

5 · Sign requests from Python

6 · Sign drops from PHP (replaces PHP-side checks)

Keep what you have; move when ready

  • Today
  • Works unchanged
  • Next step
  • Native
  • FTP drop folder
  • inbox/ keeps working
  • FTPS push or FTPS/SFTP pull into quarantine, with a receipt
  • CPZ1 replication
  • Raw HTTP POST
  • unsigned drop: sender check "not-evaluated"
  • sign with HTTP Message Signatures (Python, PHP, any language)
  • CPZ1 relay
  • Signature checks in PHP / the app
  • nginx auth_request to KeyZ Gate
  • verified headers only
  • CA-issued client certificates
  • wallet-projected client certificate (mTLS)
  • KeyZ declarations
  • Operator key list
  • honoured, shown as "operator-listed"
  • wallet declarations
  • presented with each request
  • TLS PEM files
  • still win; reloaded when renewed
  • CAPPZ_TLS_AUTO=1, rotated every 6 days
  • delegated TLS key
  • LDAP address book
  • read-only LDAP face of the directory
  • the ontology directory

What it does not do

  • Public websites still need a certificate from a public CA: browsers trust nothing else. Wallet-projected certificates are for peers, mTLS and enterprise clients.
  • "Verified" means the issuer's signature, the digests, the key binding and the nonce all hold — not that the issuer checked well. The evidence level says how it checked; ledger anchoring is a separate check.
  • Multicast discovery stays on the local network; across networks copies use the member table and the relay (replicate through firewalls).