Proving You Are Over 18 Without Revealing Your Birth Date
Imagine that a website needs to know only one fact:
The visitor is at least 18 years old.
Today, the usual solution is to upload a passport, driving licence, national ID card, or another government document. That reveals far more information than the website needs:
- full name;
- exact date of birth;
- photograph;
- document number;
- home address;
- nationality;
- signature;
- and sometimes biometric or machine-readable data.
A zero-knowledge proof can support a more privacy-preserving interaction:
"I can prove that I control a valid government-issued digital credential whose hidden date of birth satisfies the 18-year requirement, without revealing the date of birth or the credential."
This article explains the complete process from the beginning. It assumes no previous knowledge of cryptography.
1. The exact claim we want to prove
The informal claim is:
"I hold a government-signed document, and the date of birth in it is more than eighteen years ago."
For a cryptographic system, that sentence must be made precise.
A better formal statement is:
I know a credential and an issuer signature such that:
- the signature is valid under an accepted government public key;
- the credential contains a date of birth;
- that date of birth is on or before the verifier's 18-year cutoff date;
- the credential has the expected schema and issuer;
- the credential is not expired or revoked; and
- I control the private key or secret bound to that credential.
The verifier should learn only whether this statement is true.
"At least 18" versus "more than 18"
These are slightly different predicates.
If the verification date is 29 July 2026, the 18-year cutoff is:
29 July 2008For at least 18 years old:
dateOfBirth <= 2008-07-29A person born on 29 July 2008 passes.
For the strict phrase more than 18 years ago:
dateOfBirth < 2008-07-29Most age gates mean "18 or older", so this article uses <=.
2. What zero knowledge means
A zero-knowledge proof lets one party prove that a statement is true without revealing the secret information used to establish it.
There are three important properties.
Completeness
An honest holder with a valid credential should be able to create a proof that the verifier accepts.
Soundness
A person who does not have a valid credential satisfying the age condition should not be able to create an accepted proof, except with a negligible probability.
Zero knowledge
The proof should reveal nothing about the hidden credential beyond the facts deliberately exposed by the proof.
In this example, the verifier learns:
Age requirement satisfied: yes
Accepted government credential: yes
Credential currently valid: yes
Holder binding satisfied: yesThe verifier should not learn:
Exact date of birth
Name
Address
Document number
Complete credential
Issuer's original signature
Holder's private key3. The participants
There are normally three main participants.
3.1 Issuer
The issuer is the government department or another trusted authority that creates and signs the credential.
Examples could include:
- a passport authority;
- a driving-licence authority;
- a national identity authority;
- or an approved digital identity provider acting under government authority.
3.2 Holder, subject, and prover
The subject is the person described by the credential.
The holder is the entity storing and presenting the credential.
The prover is the software generating the zero-knowledge proof.
These may all represent the same person, but the words describe different roles.
For example:
Subject: Riya
Holder: Riya's digital wallet
Prover: The ZK software inside Riya's wallet3.3 Verifier
The verifier is the website, application, merchant, or service that needs to check the age requirement.
The verifier does not need to contact the issuer for every presentation if it already has:
- the trusted issuer public key;
- the credential schema;
- and sufficiently fresh status or revocation information.
3.4 Optional supporting systems
A real deployment might also have:
- a trust registry listing accepted issuers;
- a credential-status or revocation service;
- a wallet provider;
- a hardware-backed key store;
- and a proof-verification service.
4. The complete system at a glance
The government participates during issuance and status management. It does not necessarily participate every time the person proves their age.
Part I: Cryptographic foundations
5. Public keys and private keys
A cryptographic key pair contains:
Private key: kept secret by its owner
Public key: distributed to other peopleA private key can create a digital signature. The corresponding public key can verify it.
We will use the following notation:
sk_G = government private signing key
pk_G = government public verification key
sk_H = holder private key or secret
pk_H = holder public keysk means secret key.pk means public key.
The government must keep sk_G private. Everyone who needs to verify government credentials may know pk_G.
The holder must keep sk_H private. The corresponding pk_H, or a commitment derived from it, may be included in the credential.
6. Digital signatures
A digital signature answers two questions:
- Was this data approved by the owner of a particular private key?
- Has the data changed since it was signed?
Conceptually:
signature = Sign(sk_G, credentialData)Verification is:
Verify(pk_G, credentialData, signature)The output is either valid or invalid.
If an attacker changes the date of birth after issuance, signature verification should fail.
A signature does not encrypt the data
A common misunderstanding is that signing hides data.
It does not.
A normal signed credential can still expose every field. The signature protects authenticity and integrity. Zero knowledge is the mechanism that hides selected information during presentation.
7. Hash functions
A cryptographic hash function maps data of any size to a fixed-size value.
digest = H(data)For example:
H("credential data") -> a fixed-length digestA secure cryptographic hash is designed so that:
- changing the input changes the output unpredictably;
- it is impractical to recover the complete input from the digest;
- it is impractical to find two different inputs with the same digest;
- and the same input always produces the same output.
Hashes are used to:
- bind a proof to a request;
- derive a non-interactive challenge;
- identify schemas and policies;
- commit to a transcript;
- and create Merkle trees or other authenticated data structures.
A hash is not automatically a safe way to hide a date of birth. There are relatively few possible birth dates, so an attacker could hash likely dates and compare the results. Proper commitments or zero-knowledge constructions require randomness and well-designed cryptographic protocols.
8. Commitments
A commitment is similar to placing a value inside a locked box.
It has two main properties.
Hiding
Someone seeing the commitment should not learn the committed value.
Binding
After publishing the commitment, the creator should not be able to open it as a different value.
A common mathematical form is a Pedersen commitment:
C = g^m * h^rWhere:
C = commitment
m = secret message, such as an encoded date
r = fresh random blinding value
g, h = public group generatorsThe random value r hides m.
Later, a proof can show that the committed value satisfies a condition, such as being below a cutoff, without revealing the value.
A commitment is not the government signature
These are different objects:
Government signature:
Proves that the government approved particular credential data.
Commitment:
Hides a value while cryptographically binding the prover to it.The proof must connect both facts:
- the hidden date is the date signed by the government; and
- that same hidden date satisfies the age predicate.
Without this connection, a holder could use a real credential signature but prove the age condition using a different, invented date.
9. What is a nonce?
A nonce is a value intended to be used once.
The word is often explained as:
number used onceA nonce does not always have to be a mathematical number. It can be a random byte string.
For example:
N_V = 32 random bytes generated by the verifierA verifier might encode it as:
b7946f4f7a4f4e...The verifier includes this fresh nonce in the presentation request.
The holder's proof is bound to this nonce. Therefore, a proof produced for one session should not be reusable in another session.
What attack does the nonce prevent?
Suppose an attacker records a valid proof sent yesterday.
Without freshness binding, the attacker might replay the same proof today.
With a fresh verifier nonce:
Yesterday's proof was bound to N_V_1
Today's request contains N_V_2
N_V_1 != N_V_2The recorded proof should fail for the new request.
A nonce does not have to remain secret
A verifier nonce is normally public.
Its security property is freshness and unpredictability before the request, not secrecy after it is sent.
A nonce must be generated securely
Do not use values such as:
1
2
3
current second only
user ID onlyUse a cryptographically secure random-number generator and enough entropy, commonly at least 128 bits and often 256 bits.
10. What is a challenge?
A zero-knowledge protocol often follows this pattern:
Commit -> Challenge -> Response- The prover sends a temporary cryptographic commitment.
- The verifier supplies an unpredictable challenge.
- The prover calculates a response that is possible only with knowledge of the secret.
- The verifier checks a mathematical equation.
The challenge is often written as:
cThe challenge prevents a prover from preparing one reusable answer in advance.
11. Nonce and challenge are related, but not identical
This distinction is important.
Verifier nonce
The verifier nonce is application-level freshness data sent in the proof request.
We write it as:
N_VCryptographic challenge
The challenge is part of the proof mathematics.
We write it as:
cIn a non-interactive proof, c is often computed by hashing the proof transcript:
c = H(
protocolName,
publicStatement,
proverCommitment,
verifierNonce,
verifierDomain,
policyVersion
)Therefore:
N_V is an input to the challenge derivation.
c is the challenge derived from the complete transcript.Calling both values "the nonce" causes confusion and can lead to protocol mistakes.
12. A small challenge-response example
This example explains the intuition behind a proof of knowledge. It is not by itself the complete age-proof system.
Assume the prover has a secret number:
xThe public value is:
y = g^xThe prover wants to prove knowledge of x without revealing it.
Step 1: Prover creates a temporary commitment
The prover chooses fresh random r:
r <- randomThen calculates:
t = g^rThe prover sends t.
Step 2: Verifier sends challenge
The verifier chooses unpredictable challenge c and sends it.
Step 3: Prover calculates response
z = r + c*x mod qThe prover sends z.
Step 4: Verifier checks
The verifier checks:
g^z == t * y^cWhy does it work?
g^z
= g^(r + c*x)
= g^r * g^(c*x)
= t * (g^x)^c
= t * y^cThe verifier becomes convinced that the prover knows x, but x is not sent.
Why fresh r matters
The temporary secret r must not be reused.
If the same r is used with two different challenges:
z1 = r + c1*x
z2 = r + c2*xThen:
z1 - z2 = (c1 - c2)*xAn attacker may solve for x.
This temporary prover randomness is sometimes also called a nonce in cryptographic literature. To avoid confusion in this article, we call it:
r = prover's fresh ephemeral randomness
N_V = verifier's request nonce
c = cryptographic challenge13. Interactive and non-interactive proofs
Interactive proof
The verifier actively sends a random challenge:
Prover -> commitment
Verifier -> challenge
Prover -> responseNon-interactive proof
Web and mobile applications often prefer a single proof object.
The Fiat-Shamir approach derives the challenge from a hash of the complete transcript:
c = H(transcript)The transcript should include all security-relevant public context, such as:
protocol identifier
circuit or relation identifier
issuer public key or issuer identifier
public inputs
prover commitment
verifier nonce
verifier domain
policy identifier
expiry timeThe verifier reconstructs the same transcript and derives the same c.
If any bound value changes, the reconstructed challenge changes and verification fails.
14. What do n, C, c, q, and other symbols mean?
Cryptographic symbols are not globally standard. The same letter can mean different things in different papers.
C: usually a commitment
In this article:
C = cryptographic commitmentUppercase C and lowercase c are intentionally different.
c: usually a challenge
In this article:
c = challenge scalarSome protocols use c for another value. Always read the notation section of the protocol.
N_V: verifier nonce
In this article:
N_V = verifier-generated fresh request nonceUppercase N helps distinguish it from other meanings of n.
n
The meaning of n depends on context.
It might mean:
n_attrs = number of attributes in a credentialFor example, if a BBS signature signs ten messages:
m_0, m_1, ..., m_9
n_attrs = 10In RSA, n commonly means the public modulus:
n_RSA = p * qIn theoretical computer science, n may mean:
input size or security parameterIn this article, we avoid a bare n. We use explicit names such as:
n_attrs
n_RSA
nonceLengthp and q
These often describe finite mathematical structures.
Depending on the system:
p = prime defining a field
q = order of a cryptographic groupArithmetic such as:
z = r + c*x mod qwraps around after q.
r
In commitment and challenge-response examples:
r = fresh secret randomnessIt is sometimes called a blinding factor or ephemeral nonce.
m
m = a message or private valueA credential may be represented as a vector:
m_0, m_1, ..., m_(n_attrs-1)w
w = witnessThe witness is the complete assignment of private and intermediate values that satisfies the circuit.
pi or π
π = generated zero-knowledge proofpk and sk
pk = public key
sk = secret/private keypk_prove and vk
For some zk-SNARK systems:
pk_prove = proving key
vk = verification keyThe proving key is often public even though it is called a "key". It helps create proofs but does not contain the holder's secret witness.
Part II: Credentials and trust
15. What is a digital credential?
A digital credential is structured data containing claims about a subject.
A simplified government credential could look like this:
{
"schemaId": "gov.example/identity/v1",
"issuerId": "gov.example/passport-authority",
"subjectKey": "holder-public-key-or-commitment",
"dateOfBirth": 19940523,
"validUntil": 20300523,
"statusIndex": 482194
}The government signs a deterministic encoding of these fields.
Conceptually:
credentialMessage = CanonicalEncode(credentialFields)
issuerSignature = Sign(
sk_G,
credentialMessage
)The holder stores:
credentialFields
issuerSignature
holder private key or link secret
status witness, when the status system requires oneWhy deterministic encoding matters
The signer and verifier must agree on the exact bytes that were signed.
These JSON objects appear semantically similar:
{"a":1,"b":2}{"b":2,"a":1}But their raw bytes differ.
Real systems use canonicalisation, a defined binary encoding, or another deterministic representation so that everyone computes the same signed message.
16. Credential schema
A schema defines:
- which fields exist;
- their names;
- their data types;
- their encoding;
- and their meaning.
For example:
Field: dateOfBirth
Type: unsigned integer
Encoding: YYYYMMDD
Meaning: Gregorian calendar date of birthThe proof must bind itself to the correct schema. Otherwise, a number from an unrelated field could be misinterpreted as a birth date.
For example:
19940523must not be accepted merely because a credential contains that number somewhere. The circuit must establish that the signed value occupies the dateOfBirth field of an accepted schema.
17. How should the date be encoded?
Two practical options are common.
Option A: Fixed-width YYYYMMDD
Example:
23 May 1994 -> 19940523For valid, fixed-width dates:
19940523 < 20080729matches chronological order.
The circuit or credential-validation rules must prevent malformed values such as:
19941399Option B: Day number
The date is converted into the number of days since an agreed epoch.
For example:
dateOfBirthDay = daysSinceEpoch(1994-05-23)
cutoffDay = daysSinceEpoch(2008-07-29)Then:
dateOfBirthDay <= cutoffDayThis makes comparison simple but requires a precise, standard date-conversion algorithm.
Which is better?
YYYYMMDD is easier for people to inspect.
A day number can be easier for arithmetic and date differences.
Whichever representation is selected must be:
- specified in the schema;
- signed by the issuer;
- interpreted identically by the circuit;
- and validated consistently by all implementations.
18. Holder binding
A credential should not be usable merely because someone copied its file.
One approach is to bind the credential to a holder key:
pk_H = PublicKey(sk_H)The government includes pk_H, or a privacy-preserving commitment to the holder secret, in the signed credential.
During presentation, the holder proves:
I know sk_H corresponding to the holder value inside the signed credential.This is called holder binding, key binding, device binding, or link-secret binding, depending on the system.
What holder binding proves
It proves control of a secret associated with the credential.
It does not automatically prove that:
- the person presenting the proof is physically the credential subject;
- the key has never been copied;
- the wallet device has not been compromised;
- or a real person is currently present.
A wallet can strengthen assurance by protecting the holder key with:
- a hardware-backed secure element;
- a PIN;
- local biometric unlocking;
- device attestation;
- or in-person enrolment.
19. Expiry, revocation, and suspension
A valid issuer signature only shows that the credential was originally issued.
The credential might later become unacceptable because it was:
- expired;
- revoked;
- suspended;
- replaced;
- or issued under a compromised issuer key.
A complete proof policy should therefore address status.
Expiry
A hidden or disclosed expiry date can be checked:
validUntil >= verificationDateRevocation
Possible designs include:
- a signed status list;
- a Merkle tree;
- a cryptographic accumulator;
- a certificate revocation list;
- or an online status protocol.
A circuit-friendly design might expose a public status root:
R_status = current signed status-tree rootThe holder privately supplies:
statusIndex
Merkle path or non-revocation witnessThe circuit proves that the credential's hidden status entry is valid under R_status.
Privacy concern
If the verifier contacts the issuer and asks:
"Is credential 482194 still valid?"the issuer may learn where and when the credential is being used.
Privacy-preserving designs allow the verifier or holder to fetch a large, cacheable status structure rather than querying one credential directly.
Part III: Setup and issuance
20. Phase 0: System setup
Before anyone receives a credential, several things must be established.
Government setup
The government:
- generates an issuer signing key pair;
- protects the private key;
- publishes the public key or certificate chain;
- publishes the credential schema;
- defines status and revocation procedures;
- defines supported proof mechanisms;
- publishes key-rotation and compromise policies.
Private with government:
sk_G
Public:
pk_G
issuer identifier
schema
signature algorithm
status endpoints or status rootsVerifier trust setup
The verifier configures:
- accepted issuers;
- accepted credential schemas;
- accepted cryptographic algorithms;
- minimum key sizes or approved curves;
- age policy;
- status freshness rules;
- and proof protocol version.
It must not accept an arbitrary public key supplied by the holder as "the government key".
The public key must be resolved through a trusted registry, certificate chain, pinned configuration, or another authenticated mechanism.
ZK circuit setup
For a circuit-based design:
- developers define the relation to prove;
- they implement the circuit;
- they test it;
- the circuit is compiled;
- proving and verification parameters are generated;
- the verifier pins the expected circuit or verification-key identifier.
Some systems, such as Groth16, require a circuit-specific trusted setup. Others use a universal setup or are transparent and avoid trusted setup.
A compromised trusted setup can threaten soundness. Ceremony outputs and verification-key hashes must therefore be managed carefully.
Are proving keys secret?
Normally:
proving key: public
verification key: publicThe witness and holder secrets remain private.
The toxic setup randomness, where applicable, must not be retained by anyone.
21. Phase 1: Holder creates a secret
The wallet generates:
sk_H <- secure random generation
pk_H = PublicKey(sk_H)The wallet stores sk_H in protected storage.
Depending on the credential system, the holder may send:
pk_Hor a commitment to a link secret:
C_H = Commit(sk_H, r_H)to the issuer.
The holder secret must not be sent to the issuer.
22. Phase 2: Government verifies the person
Zero knowledge does not replace the issuer's identity-proofing process.
The government must still determine the real person's information using its normal procedures, which might involve:
- an existing government database;
- an in-person process;
- official source documents;
- biometrics;
- or another legally approved identity-proofing process.
This article starts after the issuer has established the date of birth it is willing to certify.
23. Phase 3: Government creates the credential
The issuer creates credential fields:
{
"schemaId": "gov.example/identity/v1",
"issuerId": "gov.example/identity-authority",
"subjectKey": "pk_H",
"dateOfBirth": 19940523,
"validFrom": 20260729,
"validUntil": 20360729,
"statusIndex": 482194
}The exact credential can contain more fields, but the ZK presentation does not have to reveal them.
The issuer:
- canonicalises or deterministically encodes the credential;
- signs the encoded data with
sk_G; - sends the credential and signature to the wallet;
- creates or updates its credential-status record.
Conceptually:
M_cred = CanonicalEncode(fields)
sigma_G = Sign(sk_G, M_cred)Where:
M_cred = encoded credential message
sigma_G = government signature24. Phase 4: Wallet verifies and stores the credential
The wallet should verify the issuer signature immediately:
Verify(pk_G, M_cred, sigma_G) == trueIt should also check:
- the expected schema;
- the expected holder binding;
- the issuer identifier;
- field encodings;
- validity dates;
- and status configuration.
The wallet stores privately:
credential fields
government signature
holder private key or link secret
credential-status witness, when applicableThe wallet does not publish the credential.
Part IV: Creating an age proof
25. Phase 5: Verifier creates the proof request
Suppose the current date is:
29 July 2026The verifier computes:
cutoffDate = 29 July 2008
cutoffEncoded = 20080729The verifier generates a fresh random nonce:
N_V <- 256 random bitsThe request might be:
{
"protocol": "zk-age-proof-v1",
"requestId": "58f314f1-98c0-4d81-a885-26356f547790",
"verifierDomain": "shop.example",
"purpose": "purchase-age-gate",
"acceptedIssuerIds": [
"gov.example/identity-authority"
],
"acceptedSchemaIds": [
"gov.example/identity/v1"
],
"predicate": {
"field": "dateOfBirth",
"operator": "<=",
"value": 20080729
},
"verificationDate": 20260729,
"nonce": "b7946f4f7a4f4e...",
"requestExpiresAt": "2026-07-29T18:10:00Z",
"statusPolicy": {
"maximumAgeSeconds": 3600
}
}Which values are public?
Every value in this request is public to the holder and verifier.
The nonce is not a password.
Why include the verifier domain?
Without domain binding, a malicious site could ask the user to produce a proof and forward it to another verifier.
Binding the proof to:
shop.examplehelps prevent cross-site reuse.
Why include purpose and policy version?
A proof request should be explicit about what the proof authorises.
For example:
purpose = enter age-restricted pageshould not automatically authorise:
open a bank accountBinding the purpose and policy identifier prevents a proof from being silently reused in a materially different context.
26. Phase 6: Wallet validates the request
Before producing a proof, the wallet checks:
- the request protocol is supported;
- the request has not expired;
- the requested predicate is understandable;
- the verifier domain matches the active origin;
- the requested disclosure is minimal;
- the accepted issuer and schema match an available credential;
- the cutoff is sensible for the stated policy;
- and the user has approved the presentation.
The wallet should display a human-readable prompt such as:
shop.example is requesting proof that:
- you are at least 18;
- the proof will be valid only for this request;
- your exact date of birth will not be shared.27. Phase 7: Wallet constructs the witness
In zero-knowledge terminology, the witness is the set of values that makes all circuit constraints true.
For this proof, the private witness might contain:
dateOfBirth
all hidden credential fields
government signature
holder private key
signature-verification intermediate values
status index
status Merkle path or accumulator witness
prover blinding randomnessThe witness exists only inside the prover environment.
It is not sent to the verifier.
The witness is more than the secret input
A circuit has:
- private inputs;
- public inputs;
- intermediate wire or signal values;
- outputs.
The complete valid assignment of these values is called the witness.
28. Public inputs and private inputs
A useful design is shown below.
Private witness values
| Value | Why it is private |
|---|---|
dateOfBirth | This is the main personal fact being hidden. |
| Hidden credential fields | The verifier does not need them. |
sigma_G | The original signature can be hidden to reduce linkability. |
sk_H | This is the holder's private key or link secret. |
statusIndex | Revealing it may create a stable identifier. |
| Revocation witness | It may contain credential-specific information. |
| Blinding randomness | Proof security requires it to remain private. |
Public inputs
| Value | Why it is public |
|---|---|
| Issuer public key or trusted issuer-key identifier | Needed to define which issuer signature is accepted. |
| Credential schema identifier | Defines the meaning and encoding of signed fields. |
cutoffDate | Defines the age predicate. |
| Verification date | Used for expiry and policy checks. |
| Request hash | Binds domain, nonce, purpose and request expiry. |
| Current status root | Defines the accepted revocation state. |
| Circuit or policy version | Prevents interpretation under the wrong rules. |
| Optional disclosed claims | Only when explicitly required. |
Generated proof
πThe proof is public and can be sent to the verifier.
A properly designed proof does not reveal the private witness.
29. Bind the proof to the complete request
Instead of making every request field a separate circuit input, the system can compute:
requestHash = H(CanonicalEncode(proofRequest))The circuit or proof transcript binds to:
requestHashThe verifier recomputes it from the exact request it issued.
The request hash should cover at least:
protocol version
verifier domain
purpose
issuer restrictions
schema restrictions
age predicate
cutoff
verification date
verifier nonce
request expiry
status policyIf even one bound field changes, the request hash changes.
Part V: The zero-knowledge circuit
30. What is a circuit?
A ZK circuit is not necessarily an electronic circuit.
It is a program-like collection of mathematical constraints.
The circuit defines the relation:
"There exists a private witness that satisfies all of these rules for these public inputs."
For example:
private a
public b
constraint: a * a = bA proof might show knowledge of a private square root of public b without revealing a.
In a real SNARK, the program is compiled into arithmetic constraints over a finite field.
The circuit cannot freely execute arbitrary code in the same way as a normal application. Operations such as:
- parsing;
- hashing;
- signature verification;
- comparisons;
- date validation;
- and Merkle verification
must be represented as supported constraints.
31. The age-credential relation
We want the proof system to establish:
There exists:
credential
governmentSignature
holderSecret
revocationWitness
such that:
government signature is valid
credential has the accepted schema
credential contains the hidden DOB
hidden DOB <= public cutoff
credential is not expired
credential is not revoked
holderSecret matches credential holder binding
proof is bound to the verifier requestThis is an existential statement.
The verifier does not learn the values after "there exists". It learns that suitable values exist and were known to the prover.
32. Conceptual circuit pseudocode
The following is educational pseudocode, not directly compilable Circom, Noir, Halo2, or RISC Zero code.
circuit GovernmentAgeProof {
// -----------------------------
// Public inputs
// -----------------------------
public issuerPublicKey
public acceptedSchemaHash
public cutoffDate
public verificationDate
public requestHash
public statusRoot
public policyVersion
// -----------------------------
// Private witness
// -----------------------------
private credentialFields
private issuerSignature
private holderSecret
private statusWitness
private signatureWitness
private randomBlinding
// -----------------------------
// 1. Parse a deterministic credential representation
// -----------------------------
credential = ParseCredential(credentialFields)
// -----------------------------
// 2. Check the signed schema
// -----------------------------
assert Hash(credential.schemaId) == acceptedSchemaHash
// -----------------------------
// 3. Recreate exactly what the issuer signed
// -----------------------------
credentialMessage = CanonicalEncode(credential)
// -----------------------------
// 4. Verify the issuer signature
// -----------------------------
assert VerifySignature(
issuerPublicKey,
credentialMessage,
issuerSignature,
signatureWitness
) == true
// -----------------------------
// 5. Check that the DOB field is a valid date encoding
// -----------------------------
assert IsValidYYYYMMDD(credential.dateOfBirth) == true
// -----------------------------
// 6. Apply the age predicate
// -----------------------------
assert credential.dateOfBirth <= cutoffDate
// -----------------------------
// 7. Check credential validity period
// -----------------------------
assert credential.validFrom <= verificationDate
assert verificationDate <= credential.validUntil
// -----------------------------
// 8. Prove non-revocation or acceptable status
// -----------------------------
assert VerifyStatus(
credential.statusIndex,
statusWitness,
statusRoot
) == VALID
// -----------------------------
// 9. Prove holder binding
// -----------------------------
derivedHolderPublicValue = DerivePublic(holderSecret)
assert derivedHolderPublicValue
== credential.subjectKeyOrCommitment
// -----------------------------
// 10. Bind the proof to this request and policy
// -----------------------------
assert requestHash is included as a public proof input
assert policyVersion is supported
// No DOB or credential field is made public.
}The circuit does not "return the DOB".
The verifier receives only a proof that all assertions were satisfied.
33. Why signature verification must be inside the proof relation
Suppose the holder privately claims:
dateOfBirth = 1990-01-01A range proof can show that this value is before the cutoff.
But without proving that the government signed that same value, the holder could invent any date.
The relation must therefore include:
VerifySignature(
pk_G,
credentialContainingTheSameDOB,
sigma_G
) == trueThe word same is the central requirement.
The date used in the comparison must be the exact date cryptographically bound into the government-signed credential.
34. Why field position and schema must be verified
Assume a credential signs these numbers:
documentIssueDate = 19940523
dateOfBirth = 20100101If the circuit merely proves that "some signed number is less than the cutoff", it could incorrectly use the issue date.
The circuit must prove that:
the signed field identified as dateOfBirthsatisfies the predicate.
This requires deterministic field ordering, canonicalisation, schema binding, or a message-index definition.
35. How an integer comparison works in a circuit
A normal programming language can evaluate:
dob <= cutoffA field-arithmetic circuit needs constraints proving that the comparison is valid.
A common approach is:
difference = cutoff - dobThen prove that difference fits into a permitted non-negative bit range.
For example, if dates are eight-digit positive integers:
0 <= difference < 2^32The circuit can decompose difference into bits:
difference = b0*2^0 + b1*2^1 + ... + b31*2^31and constrain every bi:
bi * (bi - 1) = 0That equation forces each bi to be either 0 or 1.
Why a range constraint is required
Finite-field arithmetic wraps around.
Without a range check, a negative integer can appear as a large positive field element.
Therefore, this is unsafe by itself:
difference = cutoff - dobThe circuit must prove that the interpreted integer lies in the intended range.
This is a common source of ZK circuit bugs.
36. Date validation
If the credential stores YYYYMMDD, the circuit or surrounding credential rules should establish:
year is in an accepted range
1 <= month <= 12
day is valid for the month
29 February is accepted only in a leap year
the encoding contains exactly the intended digitsIn some designs, the trusted issuer guarantees valid encoding and the circuit only verifies the signed value and comparison.
Even then, every implementation must define the encoding unambiguously. A careful circuit does not rely on undocumented assumptions.
37. The circuit has no trusted clock
A circuit cannot independently know today's date.
The verification date is supplied as a public input.
Therefore, the verifier must enforce that:
verificationDate matches the verifier's trusted clockand that the request expiry has not passed.
Similarly, a circuit cannot independently fetch the latest revocation list. It checks the status root or witness provided as input. The verifier must ensure that the root is authentic and sufficiently fresh.
This split is important:
Circuit:
Checks mathematical consistency.
Verifier application:
Checks external policy, trust, time, origin and freshness.38. Optional nullifier or pseudonym
Some applications need to prevent repeated use without learning identity.
The circuit can derive a context-specific value:
nullifier = H(holderSecret, verifierDomain, campaignId)The verifier can reject a repeated nullifier for the same campaign.
A domain-specific pseudonym might be:
pseudonym = H(holderSecret, verifierDomain)The same verifier can recognise repeat visits, while different verifiers receive different pseudonyms.
Privacy trade-off
A stable pseudonym improves account continuity but creates linkability at that verifier.
For a one-time age check, no stable pseudonym may be necessary. The system should expose one only when the product requirement justifies it.
Part VI: Proof generation and verification
39. Phase 8: Witness generation
The wallet converts the credential and request into circuit inputs.
It computes all intermediate values required by the circuit, such as:
- canonical credential encoding;
- message hashes;
- signature-verification intermediates;
- date decomposition;
- comparison bits;
- Merkle path hashes;
- holder-key derivation;
- request hash;
- and other constraint signals.
If every constraint is satisfied, the wallet has a valid witness.
If any condition fails, proof generation should fail.
Examples:
DOB after cutoff -> no valid witness
Wrong issuer signature -> no valid witness
Credential revoked -> no valid witness
Wrong holder private key -> no valid witness
Different request nonce -> proof will not verify for current request40. Phase 9: Proof generation
The prover runs:
π = Prove(
provingKey,
publicInputs,
privateWitness
)Conceptually:
publicInputs = {
issuer key identifier or key,
schema hash,
cutoff date,
verification date,
request hash,
status root,
policy version
}
privateWitness = {
credential,
government signature,
holder secret,
status witness,
blinding randomness
}The output π is usually much smaller than the complete witness.
Its size and generation cost depend on the chosen proof system.
41. What the wallet sends
A presentation package might look like:
{
"protocol": "zk-age-proof-v1",
"circuitId": "gov-age-proof-2026-01",
"verificationKeyId": "gov-age-proof-vk-2026-01",
"requestHash": "0f863e...",
"publicInputs": {
"issuerKeyId": "gov.example/keys/2026-01",
"schemaHash": "fd20c4...",
"cutoffDate": 20080729,
"verificationDate": 20260729,
"statusRoot": "927b1a...",
"policyVersion": 1
},
"proof": {
"pi": "base64-or-binary-proof-data"
}
}The following are not sent:
dateOfBirth
full credential
holder private key
hidden status index
other private attributesWhether the original issuer signature is sent depends on the proof construction. In a fully hidden circuit-based presentation, it remains private witness data.
42. Phase 10: Verifier reconstructs the statement
The verifier should not trust all public inputs merely because the holder supplied them.
It reconstructs or validates them from its own request and trust configuration.
For example:
expectedCutoff = ComputeCutoff(trustedCurrentDate, minimumAge=18)
expectedRequestHash = H(originalRequest)
trustedIssuerKey = ResolveTrustedKey(issuerKeyId)
trustedStatusRoot = ResolveFreshAuthenticatedStatusRoot()It checks that the proof's public inputs match these expected values.
A malicious holder must not be allowed to replace:
cutoffDate = 20080729with:
cutoffDate = 20260729because almost everyone would then pass.
43. Phase 11: Verifier checks freshness and context
Before or alongside cryptographic verification, the verifier checks:
request nonce belongs to an active request
request has not expired
request was created for this verifier domain
request purpose is correct
request has not already been consumed, where one-time use is required
protocol and circuit versions are accepted
status data is fresh
issuer key was valid at the relevant timeThe verifier should store the request state:
requestId
nonce hash
creation time
expiry time
consumed flag
expected policyAfter successful one-time verification, it can mark the request as consumed.
44. Phase 12: Cryptographic verification
The verifier runs:
valid = Verify(
verificationKey,
publicInputs,
π
)If the result is true, the verifier knows that a witness satisfying the circuit exists.
The verifier then applies application policy:
if cryptographicProofValid
and requestFresh
and issuerTrusted
and statusFresh
and policyAccepted:
allow
else:
rejectA proof library returning true is not the entire business decision.
45. What exactly has been proved?
After successful verification, the website may conclude:
A prover knew a credential and secrets satisfying the pinned circuit under the supplied public inputs.
If the circuit and surrounding verifier policy were designed correctly, this means:
- an accepted issuer signed the hidden credential;
- the signed date of birth is on or before the cutoff;
- the credential has the accepted schema;
- the credential is within its validity period;
- the status proof says it is not revoked or suspended;
- the prover controls the bound holder secret;
- and the proof is bound to this verifier request.
It does not necessarily mean:
- the human currently using the browser is the original subject;
- the device is uncompromised;
- the government's source database was correct;
- or the person has not shared access to the wallet.
Cryptography proves precisely encoded statements, not broader assumptions that were never encoded.
Part VII: Who holds what?
46. Long-term data ownership
| Item | Government | Holder wallet | Verifier | Public registry |
|---|---|---|---|---|
Government private key sk_G | Private | No | No | No |
Government public key pk_G | Yes | Yes | Yes | Public |
| Credential schema | Yes | Yes | Yes | Public |
Holder private key sk_H | No | Private | No | No |
| Holder public value or commitment | Possibly | Yes | Usually hidden during proof | Possibly inside hidden credential |
| Full credential | Issuer may retain records | Private | No | No |
| Government signature | Issuer may retain | Private | Often hidden by ZK | No |
| Status signing key | Private with status authority | No | No | No |
| Status root/list | Yes | Yes | Yes | Public/authenticated |
| Circuit definition | Yes/developer | Yes | Yes | Usually public |
| Proving key | No secret requirement | Yes | Optional | Usually public |
| Verification key | No secret requirement | Yes | Yes | Usually public |
47. Per-request data
| Item | Created by | Visibility | Purpose |
|---|---|---|---|
N_V verifier nonce | Verifier | Public to holder and verifier | Freshness and replay resistance |
| Cutoff date | Verifier | Public | Defines age requirement |
| Request expiry | Verifier | Public | Limits proof-request lifetime |
| Request hash | Holder and verifier compute | Public | Binds proof to complete request |
Prover randomness r | Holder prover | Private | Hides witness and randomises proof |
Challenge c | Verifier or transcript hash | Usually reconstructible/public | Makes proof bound to transcript |
Witness w | Holder prover | Private | Satisfies circuit |
Proof π | Holder prover | Public | Sent for verification |
| Verification result | Verifier | Verifier/application | Accept or reject |
48. Data-flow diagram
Part VIII: Understanding attacks
49. Attack: Change the date of birth
An attacker edits:
2010-01-01to:
1990-01-01The issuer signature no longer matches.
The circuit's signature-verification constraint fails.
Result:
No valid proof50. Attack: Use a real credential but a fake private DOB
The attacker has a real signed credential but supplies a separate private value:
fakeDOB = 1990-01-01If the circuit is correctly designed, the compared DOB must be part of the exact signed credential message.
The fake value is not bound to the issuer signature.
Result:
No valid proof51. Attack: Copy someone else's credential
The attacker obtains another person's credential and signature.
The circuit also requires:
DerivePublic(holderSecret)
==
credential.subjectKeyOrCommitmentWithout the victim's holder secret, this constraint fails.
Result:
No valid proofThis assumes the victim's holder secret has not also been copied or shared.
52. Attack: Replay an old proof
The attacker records proof π_old.
The old proof was bound to:
N_V_old
requestHash_oldThe verifier creates a new request:
N_V_new
requestHash_newThe old proof does not verify under the new public inputs.
Result:
RejectedThe verifier should also expire and optionally consume request nonces.
53. Attack: Substitute an attacker-controlled issuer key
The holder supplies:
pk_fakeand a credential signed by the corresponding fake private key.
If the verifier trusts the holder-supplied key, the attack succeeds.
Therefore, the verifier must resolve the issuer key through its own trust policy:
trustedIssuerKey = TrustRegistry[issuerId]not:
trustedIssuerKey = request.body.issuerPublicKeyunless it verifies a trusted certificate chain or equivalent authenticated delegation.
54. Attack: Use stale revocation data
A revoked credential may still appear valid under an old status root.
The verifier must enforce:
statusRootAge <= maximumAllowedAgeand verify the status authority's signature.
The circuit alone cannot decide whether a public root is fresh.
55. Attack: Weak or reused prover randomness
Many proof systems require fresh secret randomness.
Reusing ephemeral randomness can:
- reveal holder secrets;
- make presentations linkable;
- or invalidate zero-knowledge guarantees.
Use audited libraries and an operating-system cryptographically secure random-number generator.
Do not implement proof randomness with:
Math.random()
timestamp
incrementing counter
user ID56. Attack: Missing domain separation
Suppose two protocols hash similar values but do not include a protocol label.
A value intended for one context might be interpreted in another.
Challenge derivation should include a clear domain-separation label:
"zk-age-proof-v1/challenge"For example:
c = H(
"zk-age-proof-v1/challenge",
circuitId,
requestHash,
proverCommitment
)This makes it explicit which protocol created the challenge.
57. Attack: Metadata correlation
Even when the credential attributes remain hidden, verifiers may correlate users through:
- a stable proof value;
- a stable credential identifier;
- a stable holder public key;
- IP address;
- cookies;
- browser fingerprint;
- timing;
- issuer choice;
- uncommon credential schema;
- or status-query behaviour.
A privacy-preserving design should use randomised or unlinkable presentations and minimise stable public values.
Zero knowledge protects the values encoded in the proof relation. It does not automatically anonymise the entire network interaction.
Part IX: Choosing a credential and proof technology
58. Option A: AnonCreds-style credentials
AnonCreds is designed for privacy-preserving credentials and supports concepts such as:
- hidden attributes;
- proof of possession of an issuer signature;
- predicates over hidden values;
- link secrets;
- non-revocation proofs;
- and unlinkable presentations.
For a system that can influence how the issuer creates the credential, this is a natural architecture for age predicates.
The request can ask for a predicate such as:
dateOfBirth <= 20080729or use an equivalent encoded age value supported by the chosen profile.
Advantage
The credential system is designed around privacy-preserving presentation.
Limitation
The issuer, holder, verifier, schemas, and revocation system must all implement the compatible credential ecosystem.
59. Option B: BBS signatures plus a predicate proof
BBS signatures directly support:
- selective disclosure;
- proof of knowledge of an issuer signature;
- and unlinkable derived proofs.
A holder can reveal selected signed messages while hiding others.
However, selective disclosure alone answers:
"Reveal this field and hide those fields."It does not automatically provide every arithmetic predicate, such as:
hiddenDOB <= cutoffA full age solution may combine:
- a BBS proof of a signature over hidden messages;
- a commitment to the hidden DOB;
- and a range or comparison sub-proof tied to that same committed message.
Everything rests on the cryptographic binding between the BBS-signed message and the predicate proof.
Advantage
Strong selective and unlinkable disclosure properties.
Limitation
The range-predicate composition must be standardised, audited, and interoperable. BBS selective disclosure by itself is not the complete inequality proof.
60. Option C: Custom zk-SNARK or zk-STARK circuit
A custom circuit can verify:
- a conventional government signature;
- a certificate chain;
- a hidden credential;
- a hidden DOB;
- a range comparison;
- holder binding;
- expiry;
- and revocation.
This is useful when the government already issues a deterministic digital document but not a privacy-native credential.
Advantage
Flexible. The circuit can encode exactly the required relation.
Limitations
It may be expensive and difficult to:
- parse JSON, CBOR, ASN.1, PDF, or another complex format inside ZK;
- verify RSA or ECDSA signatures efficiently;
- validate certificate chains;
- handle variable-length documents;
- canonicalise data;
- and maintain circuits when formats change.
A government PDF with an RSA signature is cryptographically different from an AnonCreds or BBS credential. It can be proved in ZK, but the circuit may be much larger.
61. Option D: Government signs an ageOver18 boolean
The issuer can include:
{
"ageOver18": true
}Then a selective-disclosure credential can reveal only that signed boolean.
Advantage
Very simple verification. No comparison circuit is required during presentation.
Limitations
- A credential issued while the person is under 18 will not automatically change when they turn 18.
- It supports only the precomputed threshold.
- Different thresholds, such as 16, 18, 21, or 25, require additional claims or updated credentials.
- The verifier is trusting the issuer's calculation at issuance time.
A more flexible issuer could include privacy-preserving age bands or multiple signed threshold flags.
62. Option E: Selective-disclosure credentials without predicates
Technologies that reveal selected claims can hide unrelated fields, but if the holder must reveal the exact date of birth for the verifier to calculate age, the main privacy goal is not achieved.
The system needs either:
- a signed derived boolean;
- a native hidden-value predicate;
- or an attached zero-knowledge range/comparison proof.
Selective disclosure and predicate proof are related but different capabilities.
Part X: Existing documents
63. What if the government document is a normally signed PDF?
A digitally signed PDF may contain:
- visible text;
- embedded fonts and layout;
- certificate data;
- a signature dictionary;
- byte ranges;
- and an RSA or ECDSA signature.
A custom ZK circuit would have to establish something like:
1. These hidden bytes form the signed PDF content.
2. The PDF signature verifies under an accepted certificate.
3. The certificate chain reaches a trusted government root.
4. The hidden date text is parsed from an approved field.
5. The parsed date satisfies the cutoff.This is possible in principle, but usually much more complex than proving a native digital credential.
A practical system may use a trusted converter:
government-signed PDF
|
v
approved verification service
|
v
privacy-native digital credentialThe converter becomes an issuer or attester whose trust and security must be evaluated.
64. What if the document is a scan or photograph?
A scan or photograph does not contain a cryptographically verifiable government signature merely because the image looks official.
OCR can extract:
name
date of birth
document numberbut OCR cannot prove that:
- the physical document is authentic;
- the image was not edited;
- the document has not been revoked;
- or the presenter is its legitimate holder.
A trusted authority must first authenticate the physical document and issue a digital credential.
Zero knowledge preserves privacy after trustworthy digital facts exist. It cannot create trustworthy facts from an unauthenticated image.
Part XI: A practical application architecture
65. Components
A production implementation could contain:
Issuer service
Responsibilities:
- identity proofing;
- credential creation;
- signing;
- key rotation;
- status and revocation;
- audit logging;
- schema publication.
Holder wallet
Responsibilities:
- private-key generation and protection;
- credential storage;
- request display and consent;
- status-data refresh;
- witness generation;
- proof generation;
- disclosure minimisation.
Verifier frontend
Responsibilities:
- show why age proof is requested;
- invoke the wallet;
- preserve origin and request context;
- send the proof to the verifier backend.
Verifier backend
Responsibilities:
- generate secure request nonce;
- calculate cutoff;
- persist request state;
- resolve trusted keys;
- obtain fresh status data;
- verify proof;
- enforce one-time use;
- apply business policy.
Trust registry
Responsibilities:
- accepted issuer identifiers;
- issuer public keys;
- certificate chains;
- schema versions;
- algorithm constraints;
- key validity periods;
- compromise and deactivation information.
66. Suggested API flow
Create proof request
POST /age-proof/requestsResponse:
{
"requestId": "58f314f1-98c0-4d81-a885-26356f547790",
"protocol": "zk-age-proof-v1",
"verifierDomain": "shop.example",
"cutoffDate": 20080729,
"nonce": "b7946f4f7a4f4e...",
"expiresAt": "2026-07-29T18:10:00Z",
"requestHash": "0f863e..."
}The server stores:
requestId
hash(nonce)
requestHash
cutoff
createdAt
expiresAt
consumed = falseSubmit presentation
POST /age-proof/requests/{requestId}/presentationsRequest:
{
"protocol": "zk-age-proof-v1",
"circuitId": "gov-age-proof-2026-01",
"requestHash": "0f863e...",
"publicInputs": {
"issuerKeyId": "gov.example/keys/2026-01",
"schemaHash": "fd20c4...",
"cutoffDate": 20080729,
"verificationDate": 20260729,
"statusRoot": "927b1a...",
"policyVersion": 1
},
"proof": "base64-proof"
}Response:
{
"verified": true,
"claim": "ageAtLeast18"
}The response should not contain the DOB because the server never received it.
67. Verification pseudocode
function verifyAgePresentation(requestId, presentation):
request = database.getRequest(requestId)
if request does not exist:
reject("unknown request")
if request.consumed:
reject("request already used")
if currentTime > request.expiresAt:
reject("request expired")
if presentation.requestHash != request.requestHash:
reject("wrong request")
expectedCutoff = calculate18YearCutoff(trustedCurrentDate)
if presentation.publicInputs.cutoffDate != expectedCutoff:
reject("wrong cutoff")
issuerKey = trustRegistry.resolve(
presentation.publicInputs.issuerKeyId
)
if issuerKey is not trusted:
reject("untrusted issuer")
statusRoot = statusService.resolveFreshRoot(
presentation.publicInputs.statusRoot
)
if statusRoot is not authentic or fresh:
reject("invalid status state")
verificationKey = circuitRegistry.resolve(
presentation.circuitId
)
proofValid = zkVerify(
verificationKey,
presentation.publicInputs,
presentation.proof
)
if not proofValid:
reject("invalid proof")
database.markConsumed(requestId)
accept("ageAtLeast18")Part XII: A concrete end-to-end example
68. Issuance example
Government has
sk_G = private government signing key
pk_G = public government verification keyHolder wallet generates
sk_H = private holder key
pk_H = corresponding public keyGovernment creates
{
"schemaId": "national-id-zk-v1",
"issuerId": "national-id-authority",
"subjectKey": "pk_H",
"dateOfBirth": 19940523,
"validUntil": 20300523,
"statusIndex": 482194
}It signs the canonical message:
sigma_G = Sign(sk_G, credentialMessage)Holder stores privately
credential
sigma_G
sk_H69. Presentation example
The verifier's trusted date is:
29 July 2026It computes:
cutoff = 20080729It generates:
N_V = secureRandom(32 bytes)It sends a request bound to:
domain = shop.example
purpose = age gate
predicate = DOB <= 20080729
nonce = N_V
expiry = ten minutes from request creationThe wallet privately checks:
19940523 <= 20080729which is true.
The wallet generates proof π.
The verifier receives:
π
cutoff = 20080729
request hash
issuer-key identifier
schema hash
status root
policy versionIt does not receive:
19940523
23 May 1994
credential document
holder private keyThe verifier validates π and returns:
Age requirement satisfiedPart XIII: Common misunderstandings
70. "Can the holder simply hash the DOB?"
No.
A date of birth has a small search space. An attacker can try possible dates.
Also, a hash alone does not prove that:
- the government signed the date;
- the date is valid;
- the holder controls the credential;
- or the date is before the cutoff.
71. "Can the government sign the hash of the document?"
A signed hash can prove integrity of a hidden document only if the ZK circuit proves that:
- the hidden document hashes to the signed digest;
- the issuer signature on that digest is valid;
- and the DOB parsed from that same hidden document satisfies the predicate.
Signing a hash can be part of the construction, but it is not the entire solution.
72. "Does the verifier decrypt anything?"
Not necessarily.
Zero-knowledge proofs are not simply encryption followed by decryption.
The verifier checks a proof. It never needs the hidden DOB plaintext.
73. "Does the government see every verification?"
Not necessarily.
If public keys and privacy-preserving status data are available, the holder can present directly to the verifier.
However, network calls, online status checks, wallet telemetry, or centralised presentation services can still reveal activity. Privacy depends on the complete architecture, not only the proof algorithm.
74. "Can the proof be used forever?"
It should not be.
A presentation should be bound to:
- a fresh nonce;
- a verifier domain;
- a purpose;
- a short request-expiry time;
- and sufficiently fresh revocation state.
The underlying credential may be long-lived, but each presentation should be fresh.
75. "Does zero knowledge prove the document exists physically?"
No.
It proves knowledge of digital witness data satisfying a mathematical relation.
For a native digital credential, the meaningful claim is:
"I control the secret bound to a valid government-signed digital credential."
The word "hold" should be interpreted as cryptographic control, not physical possession of paper or plastic.
76. "Can a person lend the wallet to someone else?"
Cryptography alone cannot fully prevent voluntary credential sharing.
Controls can include:
- PIN or biometric wallet unlocking;
- hardware-bound keys;
- device binding;
- high-assurance enrolment;
- liveness checks for regulated cases;
- and legal or contractual deterrence.
These controls may reduce privacy and increase friction, so the assurance level should match the risk.
77. "Is the proof anonymous?"
Not automatically.
The proof may hide the DOB and credential, but the website could still know the user through:
- login;
- payment account;
- IP address;
- cookies;
- device fingerprint;
- delivery address;
- or stable pseudonym.
The correct claim is usually data minimisation, not complete anonymity.
Part XIV: Security and implementation checklist
78. Issuer checklist
- Protect issuer private keys in an HSM or equivalent high-assurance system.
- Publish authenticated issuer keys and key-validity periods.
- Define deterministic credential encoding.
- Version schemas.
- Bind the credential to a holder-controlled secret.
- Support secure key rotation.
- Publish privacy-preserving status information.
- Define revocation, suspension and compromise procedures.
- Avoid unnecessary stable identifiers in presentations.
- Use reviewed cryptographic suites rather than custom primitives.
79. Wallet checklist
- Use a cryptographically secure random-number generator.
- Protect holder secrets with hardware-backed storage where available.
- Verify credentials at receipt.
- Validate proof requests before generating proofs.
- Display meaningful consent information.
- Never reveal private witness files in logs.
- Do not reuse proof randomness.
- Minimise disclosed public inputs.
- Verify status data authenticity.
- Keep proof libraries updated and audited.
- Protect against malicious circuits or unexpected policy requests.
80. Verifier checklist
- Generate at least 128 bits of nonce entropy; 256 bits is a common conservative choice.
- Set short request-expiry periods.
- Bind domain, purpose, policy, nonce and cutoff.
- Store and consume request state when one-time use is required.
- Recompute the age cutoff from a trusted clock.
- Resolve issuer keys independently through a trust registry.
- Pin supported circuit and verification-key identifiers.
- Check issuer-key validity and algorithm policy.
- Enforce status freshness.
- Verify every public input against expected server-side values.
- Do not log proofs or identifiers unnecessarily.
- Apply rate limits and abuse controls.
- Treat cryptographic verification and business authorisation as separate steps.
81. Circuit checklist
- Verify the issuer signature over the exact hidden credential.
- Bind the age comparison to the exact signed DOB field.
- Bind the accepted schema and issuer.
- Validate encodings and integer ranges.
- Prevent finite-field wraparound bugs.
- Check expiry and status correctly.
- Prove holder-secret possession.
- Bind the proof to request context.
- Use domain separation in transcript hashes.
- Avoid unconstrained signals.
- Test negative cases and malformed inputs.
- Use independent audits and formal methods where practical.
- Version and hash the circuit.
- Never silently change the relation under the same circuit identifier.
Part XV: Glossary
Credential
Structured claims issued about a subject.
Issuer
The authority that creates and signs the credential.
Holder
The person or wallet that stores and presents the credential.
Subject
The person or entity described by the credential.
Prover
The party or software generating a zero-knowledge proof.
Verifier
The party checking the proof.
Claim
A statement such as:
dateOfBirth = 1994-05-23Predicate
A true-or-false condition over one or more values:
dateOfBirth <= 2008-07-29Private key
Secret cryptographic material used to sign, authenticate, decrypt, or prove control, depending on the scheme.
Public key
Public cryptographic material used to verify signatures or establish relationships with private keys.
Digital signature
A value proving that particular data was approved using a corresponding private key and has not been altered.
Hash
A fixed-size digest derived from data.
Commitment
A cryptographic value that hides a message while binding the creator to it.
Blinding factor
Fresh secret randomness used to hide a committed value or randomise a proof.
Nonce
A fresh value intended for one use, commonly included to prevent replay.
Challenge
An unpredictable scalar or value that binds a prover's response to a particular interaction or transcript.
Response
The prover's mathematical answer to the challenge.
Circuit
A collection of mathematical constraints defining the statement being proved.
Constraint
A mathematical rule that valid witness values must satisfy.
Witness
The private inputs and intermediate values that satisfy the circuit for the public inputs.
Public input
A value visible to the verifier and cryptographically bound into the proof.
Private input
A witness value kept secret by the prover.
Proof π
The compact cryptographic object generated from the witness and public inputs.
Proving key
Public parameters used by a prover in some proof systems.
Verification key
Public parameters used to verify proofs for a particular circuit or relation.
Trusted setup
A parameter-generation process required by some proof systems. Secret setup randomness must be destroyed.
Revocation
Permanent invalidation of a credential.
Suspension
Temporary prevention of credential acceptance.
Holder binding
Proof that the presenter controls a secret associated with the issued credential.
Selective disclosure
Revealing chosen signed fields while hiding others.
Predicate proof
Proving a condition over hidden data, such as an age threshold.
Unlinkability
The property that separate presentations cannot easily be recognised as coming from the same credential or holder.
Fiat-Shamir transform
A technique that derives a challenge by hashing the protocol transcript, turning many interactive proofs into non-interactive proofs.
Finite field
A finite set of numbers with defined addition, subtraction, multiplication and division rules used by many cryptographic systems.
Modulus
The number at which modular arithmetic wraps around.
Domain separation
Including a protocol-specific label or context in cryptographic hashes so values from one protocol cannot be confused with another.
Part XVI: Final mental model
The simplest way to remember the complete process is:
1. The government has a private signing key.
2. Everyone can know the government public key.
3. The holder wallet has its own private secret.
4. The government signs a credential containing the DOB
and a binding to the holder secret.
5. The verifier sends:
- the age cutoff,
- a fresh nonce,
- its domain,
- the purpose,
- and a short expiry.
6. The wallet builds a private witness containing:
- the credential,
- government signature,
- DOB,
- holder secret,
- and revocation proof.
7. The circuit checks:
- government signature,
- correct schema and DOB field,
- DOB <= cutoff,
- validity and revocation,
- holder-secret possession,
- and request binding.
8. The wallet sends only:
- proof π,
- required public inputs,
- and minimal metadata.
9. The verifier:
- reconstructs the expected public inputs,
- checks nonce and expiry,
- resolves trusted keys and fresh status,
- verifies π,
- and accepts or rejects.
10. The exact DOB and complete credential never need
to be disclosed to the verifier.The most important cryptographic connection is:
The hidden date used in the age comparison must be the exact same hidden date covered by the trusted government signature.
The most important protocol connection is:
The proof must be bound to the verifier's fresh nonce, domain, purpose, cutoff, and policy.
And the most important limitation is:
A zero-knowledge proof proves only the exact mathematical statement and trust assumptions encoded by the system. It does not automatically prove physical presence, prevent all credential sharing, or hide network metadata.
References and further reading
The following primary specifications and technical documentation are useful starting points:
IETF Internet-Draft: BBS and Modular Sub-proofs with JSON Web Proofs
IETF Internet-Draft: Anonymous Rate-Limited Credentials : ZK transcript and Fiat-Shamir discussion
Standards and Internet-Drafts evolve. A production implementation should pin exact specification versions and use maintained, independently reviewed cryptographic libraries.