Note
This spec outlines a draft functionality for beta testing. Changes are expected.
1. Abstract
This spec introduces a ZK circuit (“Proof of Embedding Similarity”) which proves that a WIP-201 Flamingo Verifier matched a live capture and an RP-supplied challenge to the image committed in a user’s PoH Credential. The proof reveals no image, no Flamingo Token and no Credential.
The circuit neither extracts embeddings nor compares them. It verifies the Flamingo Token, binds its compared entries to the Credential image and the RP’s challenge, and checks the Issuer’s Credential and an Authenticator’s authorization of the result. This first iteration supports only the PoH Credential; it shall be generalized to other Credentials in the future (Future Considerations).
2. Motivation
Deepfakes make it hard to know who is on the other side of a video call or behind an online interaction in general. An RP that can capture an image of that party, the challenge, wants to know whether the image stems from a verified real human.
A WIP-201 Flamingo Verifier can be used to compare a live capture and the challenge with the face image of a user with a valid Orb Credential, and run fraud detection models on the live capture images. Its Flamingo Token alone does not answer the RP’s question:
- No Credential. The Verifier compares any images, so the token does not show the compared image is the one in an issued (Orb) Credential.
- No owner. It does not show that the user controls the World ID the Credential was issued to.
- Linkable. The Credential image’s hash is the same in every token of a user, so revealing tokens would link all their proofs.
This proof closes those gaps in zero knowledge. It binds the compared image to the Issuer’s PoH Credential and the RP’s challenge, and the result to a registered Authenticator. It reveals only the challenge hash, the Flamingo configuration and the AAT values. An optional session lets the RP check that a later proof comes from the same World ID.
3. Background
This section is non-normative.
- Personal Custody Package (PCP). At verification, the Orb packages the user’s images and stores them on the user’s device (World implements Personal Custody). The package includes
hashes.json, which the Orb signs: a JSON object mapping each file name, e.g.thumbnail.pngfor the face thumbnail, to the lowercase hexSHA-256of the file, plus aversion. orb-core writes it as compact JSON (no insignificant whitespace) with sorted keys; constraint 3 relies on this format. - PoH Credential. The PoH Credential commits in claim 0 to the Orb-signed
hashes.jsonas $H_\text{var}$ with labelCLAIMS_HASH_V1. This chain lets the circuit bind the compared image to the Issuer’s Credential without hashing the image.
4. Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
- The terms Authenticator, Issuer and Credential are as defined in the World ID Protocol. Verifier, Flamingo Token and Engine are as defined in WIP-201.
- The Field ($F$), the Curve (
BabyJubJub), the hashes ($H_t$, $H_\text{var}$) and EdDSA MUST follow WIP-100.R(d)is as defined in WIP-201.
4.1 Supported Credentials
This first iteration supports only the PoH Credential, comparing the PCP’s thumbnail.png (Background).
4.2 Constants
| Constant | Value | Description |
|---|---|---|
CLAIM_INDEX | 0 | Credential claim holding the hashes.json commitment. |
MIN_HASHES_JSON_BYTES, MAX_HASHES_JSON_BYTES | 3072, 5120 | Accepted hashes.json lengths. |
DS_WIP_202_AUTH | 127603488023523044162070750169730750023380107613256 (b"WORLD-ID/WIP-202/AUTH") | Domain separator for the Authenticator’s authorization. |
The following constants are re-used, but are not defined by this spec:
| Name | Value | Defined in |
|---|---|---|
NUM_KEYS | 7 | WIP-100 §3.8 |
MAX_DEPTH | 30 | WIP-100 §3.8 |
MAX_CLAIMS | 15 | world-id-primitives Credential::MAX_CLAIMS |
MERKLE_LEAF_DS | b"World ID PK" | WIP-100 §3.8 |
DS_CREDENTIAL_V1 | b"POSEIDON2+EDDSA-BJJ" | world-id-primitives ds::CREDENTIAL_V1 |
DS_CREDENTIAL_SUB | b"H_CS(id, r)" | world-id-primitives ds::CREDENTIAL_SUB |
DS_SESSION_COMMITMENT | b"H(id, r)" | world-id-primitives ds::SESSION_COMMITMENT |
CLAIMS_HASH_V1 | b"CLAIMS_HASH_V1" | world-id-primitives ds::CLAIMS_HASH_V1 |
4.3 Flamingo Token Entry Layout
The circuit binds the Flamingo Token’s compared_entry_hash_k (WIP-201) by their index k:
k | Entry | Bound to |
|---|---|---|
0 | Credential image | The Credential image, through constraint 3. |
1 | Live capture | Not bound in the circuit; the AAT, if present, vouches for it through cdh. |
2 | Challenge | The public challenge_hash. |
3 | Unused | Zero, through constraint 1. |
- When choosing the Flamingo configurations it offers (Proof Request), the RP MUST include only pipelines whose release manifest
descriptionplaces the Credential image, the live capture and the challenge at compared entries0,1and2. Another order would make the Engine treat entries in roles the circuit does not bind. challenge_hashMUST beR(SHA-256(challenge))over the exact challenge bytes. The RP MUST compute it itself from the challenge image it checked against the party it interacts with (Reference Verification).
4.4 Proof Request
An RP requesting this proof MUST include the following values in its signed proof request. Their encoding is outside this WIP.
| Value | Description |
|---|---|
aud, nonce | As for every World ID proof; nonce as in WIP-201 Request. |
challenge_hash | R(SHA-256(challenge)) of the challenge image (Flamingo Token Entry Layout). |
| Flamingo configurations | The (release_hash, pipeline, match_strictness) tuples the RP accepts, in order of preference (WIP-201 Engine Selection). Each pipeline MUST follow the Entry Layout. |
| AAT requirements | Optional: whether an AAT is required, and optional hints for platform, sec_level, sec_meta, authenticator_meta and min_build_version. |
| Credential policy | issuer_schema_id, issuer_version, the accepted Issuer keys and genesis_issued_at_min. |
session_id | Optional. An existing session commitment, if the proof must concern the same World ID as an earlier one. |
The Authenticator builds the Flamingo request and the circuit inputs from these values. The RP then checks the proof against the same values (Reference Verification).
4.5 Circuit Inputs
All inputs not marked Public MUST remain private.
| Input | Visibility | Type | Description |
|---|---|---|---|
| Flamingo Token inputs | — | — | All inputs of WIP-201 Flamingo Token Verification, with their visibility. |
challenge_hash | Public | Field element | R(SHA-256(challenge)), see Entry Layout. |
merkle_root | Public | Field element | The root hash of the Merkle tree used for inclusion (representing the WorldIDRegistry). |
issuer_schema_id | Public | Field element | The Credential type. |
issuer_version | Public | Field element | The Credential schema version, < 2^8. |
cred_pk | Public | PublicKey | The Issuer’s key. |
genesis_issued_at_min | Public | Field element | Minimum time the Credential was first issued. |
session_id | Public | Field element | Session commitment, or 0. |
user_pk | Private | [PublicKey; NUM_KEYS] | The list of authenticator public keys registered in the WorldIDRegistry. |
pk_index | Private | Field element | The index in user_pk of the key used for signature. |
auth_s | Private | Field element | The s component from the EdDSA signature on the authorization message. |
auth_r | Private | [Field element; 2] | The r component from the EdDSA signature on the authorization message. |
leaf_index | Private | Field element | The index of the user’s World ID in the WorldIDRegistry. |
siblings | Private | [Field element; MAX_DEPTH] | The sequence of all siblings from the leaf_index to the merkle_root. |
claims | Private | [Field element; MAX_CLAIMS] | The Credential’s claims. |
cred_s | Private | Field element | The s component from the Issuer’s EdDSA signature on the Credential. |
cred_r | Private | [Field element; 2] | The r component from the Issuer’s EdDSA signature on the Credential. |
cred_associated_data_hash | Private | Field element | The Credential’s associated data hash. |
cred_genesis_issued_at | Private | Field element | When the Credential was first issued. |
cred_expires_at | Private | Field element | When the Credential expires. |
cred_sub_blinding_factor | Private | Field element | The blinding factor of the Credential’s sub. |
cred_id | Private | Field element | The Credential’s identifier. |
hashes_json | Private | [Field element; ⌈MAX_HASHES_JSON_BYTES / 31⌉] | hashes.json as the 31-byte big-endian chunks that $H_\text{var}$ absorbs (WIP-100 §3.3.3); unused chunks are zero. |
hashes_json_len | Private | Field element | The length of hashes.json in bytes. |
digest_offset | Private | uint32 | Position in hashes_json where the thumbnail.png digest starts. |
session_id_r | Private | Field element | The blinding factor of the session commitment, re-derived through the OPRF from the sessionId’s oprf_seed as for Session Proofs (World ID 4.0). |
4.6 Circuit Constraints
The circuit MUST enforce the following constraints. They hold jointly; the numbering only groups them. Every inequality (<, <=) is over integers range-checked to the stated width.
-
Flamingo Token Verification
- Enforce WIP-201 Flamingo Token Verification, yielding its signed
digestandcompared_entry_hash. compared_entry_hash[3] == 0.
- Enforce WIP-201 Flamingo Token Verification, yielding its signed
-
Challenge Binding
challenge_hash != 0.compared_entry_hash[2] == challenge_hash.
-
Credential Image Binding
hashes.jsonis the Credential’s.MIN_HASHES_JSON_BYTES <= hashes_json_len <= MAX_HASHES_JSON_BYTES, and the chunks beyondhashes_json_lenare zero.claims[CLAIM_INDEX]equals $H_\text{var}$ (WIP-100 §3.3.3) with labelCLAIMS_HASH_V1over the firsthashes_json_lenbytes. The capacity tag bindshashes_json_len, notMAX_HASHES_JSON_BYTES; the last chunk holds only the remaining bytes, and only the $\lceil$hashes_json_len/31$\rceil$ chunks are absorbed.- Only the chunks around
digest_offsetare split into bytes; other chunks with the same commitment would be a Poseidon2 collision.
- Extract the image digest.
18 <= digest_offsetanddigest_offset + 64 < hashes_json_len.- The 64 bytes at
digest_offsetare lowercase hexadecimal digits, followed by", all within the firsthashes_json_lenbytes. - The 17 bytes before
digest_offsetare"thumbnail.png":", and the byte before them is{or,. - The digits decode as the 32-byte big-endian digest
credential_digest.
- Bind it to the Flamingo Token.
compared_entry_hash[0] == R(credential_digest).
-
World ID Authentication
- Select the key.
- Range-check
pk_indexas 3-bit and constrainpk_index < NUM_KEYS. pk = user_pk[pk_index]. Inactive slots are the identity $(0,1)$, which the signature check in step 3 rejects.
- Range-check
- Prove registration.
- Compute
offchainSignerCommitment = H_16(MERKLE_LEAF_DS; user_pk[0].x, user_pk[0].y, …, user_pk[6].x, user_pk[6].y)per WIP-100 §3.8, including inactive slots. - Range-check
0 < leaf_index < 2^MAX_DEPTH. - Recompute the root from
offchainSignerCommitmentatleaf_indexwithsiblingsper WIP-100 §5.3, and constrain it to equalmerkle_root.
- Compute
- Verify the authorization.
- Compute
message = H_4(DS_WIP_202_AUTH; digest, verifier_key.x, verifier_key.y), withdigestandverifier_keyfrom item 1. - Verify
(auth_r, auth_s)bypkovermessage, following all requirements of WIP-100 §3.5.1.
- Compute
- Select the key.
-
Session Commitment
session_id * (session_id - H_3(DS_SESSION_COMMITMENT; leaf_index, session_id_r)) == 0.- This makes the session optional:
session_id = 0means no session, and any other value MUST be this World ID’s commitment.
-
Credential Verification
- Verify the Issuer’s signature.
- Compute
sub = H_3(DS_CREDENTIAL_SUB; leaf_index, cred_sub_blinding_factor). - Compute
claims_hash, the Poseidon2 $t=16$ permutation over[claims[0], …, claims[14], 0]with output element1. - Range-check
cred_idas 64-bit and computecred_id_packed = cred_id + issuer_version * 2^64. - Verify
(cred_r, cred_s)bycred_pkoverH_8(DS_CREDENTIAL_V1; issuer_schema_id, sub, cred_genesis_issued_at, cred_expires_at, claims_hash, cred_associated_data_hash, cred_id_packed).
- Compute
- Check validity.
- Range-check
nowas 32-bit and the Credential times as 64-bit. now < cred_expires_atandgenesis_issued_at_min <= cred_genesis_issued_at.
- Range-check
- Verify the Issuer’s signature.
Steps 4.2, 5 and 6.1 MUST use the same leaf_index, which binds the Credential to the World ID whose Authenticator authorized the result.
4.7 Verification Requirements
- Root Freshness. Verifiers MUST check that the provided public input
merkle_rootis valid and non-expired. Verifiers MAY checkWorldIDRegistry.isValidRoot(merkle_root)on every verification or cache a valid result until the root’s actual expiration (e.g. fromWorldIDRegistryV2.getRootExpiration). The configuredgetRootValidityWindow()is not the root’s remaining lifetime and MUST NOT, by itself, determine the cache duration. AgetRootExpirationresult of0denotes the latest root, not an unlimited cache lifetime. Cache access MUST NOT extend the expiration. - Proving System. Proofs of Embedding Similarity MUST use the ProveKit proving system. Verifiers MUST NOT use verification artifacts supplied by the prover.
4.8 Reference Verification
An RP accepts a proof if the following hold. It MUST check items 2–7 before verifying the proof (item 1).
- The proof verifies according to Proving System.
- The Flamingo Token inputs pass WIP-201 Reference Verification, with an
engine_config_hashthat follows Entry Layout. nowis within 5 minutes of the RP’s own clock.- The World ID and Credential are trusted:
merkle_rootsatisfies Root Freshness at verification time;cred_pkis a trusted Issuer key forissuer_schema_idandissuer_version, for the PoH Credential.
- The request matches:
genesis_issued_at_minis the RP’s own;challenge_hashisR(SHA-256(challenge))of a challenge image the RP checked against the party it interacts with.
- If the RP needs this proof to concern the same World ID as another proof,
session_idis nonzero and equals that proof’s session commitment. - AAT values. If
has_aat = 1,trust_anchor_key_hash,platform,sec_level,sec_meta,authenticator_metaandmin_build_versionare within the RP’s own policy (WIP-106 §4), whether or not its request stated them. Ifhas_aat = 0, the RP accepts proofs without an AAT.
5. Rationale
- Authenticator authorization in the circuit. As in other World ID proofs, a signature by a registered Authenticator key shows the prover controls the World ID the Credential was issued to. A copy of the Credential and
hashes.jsonalone cannot produce a proof, revoking an Authenticator stops its proofs, and even a compromised Verifier cannot authorize results for a World ID whose Authenticators it does not control. - Binding the compared image to the PoH Credential. The PoH Credential commits to
hashes.json, which lists theSHA-256of the face thumbnail underthumbnail.png. The circuit reads that digest at a prover-supplied offset and requires it to equalcompared_entry_hash[0], so the live capture and challenge were compared with the Credential image. The offset is safe because in the compact format orb-core writes (Background) the checked pattern occurs only once. - Fixed entry layout in the Flamingo Token. Positions bind entries to roles without parsing the Engine release manifest in the circuit.
- Optional session ID. An RP can check that a later proof comes from the same World ID as an earlier one, e.g. so a different person cannot take over an account. It is optional so proofs stay unlinkable by default.
- Credential verification In-circuit credential verification allows the RP to verify that the World ID user holds a valid Orb-verified PoH credential.
- Only the challenge hash is public. A public
challenge_hashis what lets the RP verify that the proof concerns its own challenge, and with it the party it interacts with; without this check the proof is meaningless. The Credential image’s hash is constant per user and would link all their proofs, so it stays private, and so does the live capture’s.
6. Alternatives Considered
- Comparing embeddings in the circuit. Rejected: extraction and fraud detection would still run outside, and the circuit would need fixed-point arithmetic, a fixed embedding dimension and a hash per embedding.
- Verifying the AAT in the circuit. Rejected: verification in the Flamingo Verifier per WIP-201 saves the TAKT’s EdDSA verification and the AAT’s
ES256verification on the device, which circuits can not do efficiently yet. - Verifying more in the Flamingo Verifier. EdDSA verifications dominate the circuit’s cost, so moving more of them into the TEE is tempting. Rejected: World ID authentication there would reveal the leaf index, a permanent identifier, to the TEE, require the Authenticator key to leave the device, and remove the check a compromised Verifier cannot forge. Credential verification there would reveal the Credential and tie the Verifier to one Credential format, while it currently compares any images.
7. Security
- Privacy. The proof hides the media, the Flamingo Token, the Credential and
hashes.json. The AAT’s public values,engine_config_hash,challenge_hashandnoware public; an uncommon combination can narrow the set of possible users. - No uniqueness. An Embedding Similarity Proof provides no uniqueness guarantees by design. Its use case is different. An RP that needs uniqueness MUST also request a Uniqueness Proof with the same nonzero
session_id. - Relay and stream integrity. A proof shows the challenge image matches the Credential’s subject. It does not establish that a video stream is unmodified or prevent a relayed interaction.
8. Future Considerations
- Other Credentials. The circuit shall be generalized to other Credentials, likely through a circuit-friendly, general format for Credentials and custody packages.
9. Backwards Compatibility
This WIP introduces a new interface; no existing contracts or functionality are affected.