The threat, concretely
If the nullifier were just a function of the secret, the same value would surface in every app — a permanent, cross-app identifier. That’s the exact tracking harm private credentials are supposed to avoid.The mechanism
The nullifier folds in anapp_domain term:
app_domain, and the nullifier is a completely different, uncorrelatable field element — even for the identical credential. Given only two nullifiers, an observer cannot tell whether they came from one credential or two.
The
app_domain also does double duty for replay protection: because it’s folded into the nullifier alongside action_id, each app maintains its own independent spent-set. A nullifier spent in App A has no bearing on App B.Proven, not asserted
This is a real, reproducible result — the same credential used in the remittance app and the RWA-access app yields two different nullifiers:| App | Policy | app_domain | Nullifier |
|---|---|---|---|
| Remittance | 777 | 44240 | 0x09e01e5e…436ba08c |
| RWA access | 888 | 48879 | 0x0532e4a7…357f2911 |
credentialCommitment, two unrelated nullifiers. Two apps, one engine, cryptographically unlinkable at the proof and nullifier layer.
The scope of the claim
This precision is itself a feature: Nullis tells you exactly where its privacy guarantee ends, so you can reason about the rest of your stack — wallets, RPC, funding — honestly, instead of assuming a blanket “anonymous.”Next: revocation
How access is revoked without touching anyone’s identity.