diff --git a/world-id/4-0-migration.mdx b/world-id/4-0-migration.mdx index 85788d5..4821bfa 100644 --- a/world-id/4-0-migration.mdx +++ b/world-id/4-0-migration.mdx @@ -123,7 +123,7 @@ proofs. At the app's cutover, it begins accepting only v4 proofs. **Old Contract - Disable minting here:** -```jsx Mint.sol +```solidity Mint.sol mapping(uint256 => bool) internal oldNullifierHashes; mapping(address => bool) public oldHasMinted; @@ -136,7 +136,7 @@ function mint(){ **New Contract - Check both old and new nullifiers:** -```ts Mintv4.sol +```solidity Mintv4.sol mapping(uint256 => bool) internal nullifierHashes; mapping(address => bool) public hasMinted; diff --git a/world-id/SKILL.md b/world-id/SKILL.md index ad7884f..ba331aa 100644 --- a/world-id/SKILL.md +++ b/world-id/SKILL.md @@ -108,7 +108,7 @@ Other legacy presets exist (`documentLegacy`, `deviceLegacy`); reach for them on ### Selfie Check For repeated Selfie Check verification, use `IDKit.createSession` followed by -`IDKit.proveSession` with `.preset(selfieCheck())`. +`IDKit.proveSession` with `.constraints(CredentialRequest("selfie"))`. Save the verified `session_id` against the application account and require that same session on later checks; enforce per-proof replay protection. @@ -132,7 +132,7 @@ The full code for each step is at [https://docs.world.org/world-id/idkit/integra The checklist below describes uniqueness requests. For session integrations, adapt it using `/world-id/idkit/session-proofs`: omit action setup and omit the action from RP signing, -use `IDKitSessionWidget` or the session builders with presets, and replace +use `IDKitSessionWidget` with a `constraints` prop or session builders with `.constraints(...)`, and replace uniqueness-nullifier storage with verified account-to-session binding and per-proof replay protection. Test both creation and proving the saved session. diff --git a/world-id/credentials/1.mdx b/world-id/credentials/1.mdx index 00070d1..cc7e181 100644 --- a/world-id/credentials/1.mdx +++ b/world-id/credentials/1.mdx @@ -86,7 +86,7 @@ This credential implements the following attributes beyond the defaults in the [ - associated_data_hash + associated_data_commitment The PoH credential has no associated data, so this field is always{" "} diff --git a/world-id/credentials/9303.mdx b/world-id/credentials/9303.mdx index 11bedcb..6823a46 100644 --- a/world-id/credentials/9303.mdx +++ b/world-id/credentials/9303.mdx @@ -53,7 +53,7 @@ import { CredentialHero } from "/snippets/credential-hero.jsx"; ## Introduction -The NFC Credential represents a unique government-issued document. It supports passports and eIDs. Availability varies by country and continues to expand over time. An NFC Credential is **guaranteed to be issued to a single World ID per unique document**. In addition to ICAO-9303 compliant documents (such as passports or eIDs), the Japanese [My Number Card](https://en.wikipedia.org/wiki/My_Number_Card) (MNC) is also supported. The MNC flow uses different enrollment handling internally, but it issues the same credential. +The NFC Credential represents a unique government-issued document. It supports passports and eIDs. Availability varies by country and continues to expand over time. An NFC Credential is **guaranteed to be issued to a single World ID per unique document**. The Japanese [My Number Card](https://en.wikipedia.org/wiki/My_Number_Card) (MNC) has its own issuer schema ID, `9310`, and its own enrollment handling, distinct from passport/eID (`9303`), but it still counts as the user's one NFC credential. Request MNC and passport/eID as alternatives, never together. ## Use Cases @@ -98,7 +98,7 @@ This credential implements the following attributes beyond the defaults in the [ -In addition, the credential implements the following claims: +The following shared issuer claim definitions cover passport/eID and MNC enrollment. MNC-specific entries describe the MNC flow; they do not mean a passport/eID request accepts MNC. In IDKit, request passport/eID with `passport` (schema `9303`) and MNC with `mnc` (schema `9310`); to accept either, use `any(CredentialRequest("passport"), CredentialRequest("mnc"))`. ### Claim 0 - Authentication Claim diff --git a/world-id/from-idkit-standalone.mdx b/world-id/from-idkit-standalone.mdx index c1819f3..b38b835 100644 --- a/world-id/from-idkit-standalone.mdx +++ b/world-id/from-idkit-standalone.mdx @@ -101,19 +101,23 @@ await fetch("/api/verify-proof", { ## Credential Mapping -Standalone used `verification_level` to choose what the user proves. In `idkit-core`, use a legacy preset instead. These presets are drop-in replacements for the old verification levels. +Standalone used `verification_level` to choose what the user proves. In `idkit-core`, use a legacy preset instead. These presets only return World ID 3.0 proofs — a compatibility step, not the final migration target. Legacy presets return the maximum credential a user has. For example, if a user has an Orb credential but you request `documentLegacy`, they verify with their Orb credential. -| Standalone | `idkit-core` preset | +| Standalone | `idkit-core` preset (compatibility-only) | | --- | --- | | `verification_level: "orb"` | `orbLegacy({ signal })` | | `verification_level: "secure_document"` | `secureDocumentLegacy({ signal })` | | `verification_level: "document"` | `documentLegacy({ signal })` | | `verification_level: "device"` | `deviceLegacy({ signal })` | + + For World ID 4.0, use the v4-native presets: `proofOfHuman` (replaces `orbLegacy`), `passport` (NFC passports/eIDs), or `mnc` (Japanese My Number Card). They always keep World ID 3.0 fallback on, regardless of `allow_legacy_proofs`. A single preset narrows a `document` integration, since `documentLegacy` accepted Orb, passport/eID, and MNC holders alike; to keep all of them, use `.constraints(any(CredentialRequest("proof_of_human"), CredentialRequest("passport"), CredentialRequest("mnc")))`. Constraint requests have no credential-specific 3.0 fallback (any 3.0 fallback is Device-level), so use them with `allow_legacy_proofs: false`, which is also how you drop World ID 3.0 entirely. See [Configure Credentials](/world-id/idkit/credentials). + + ```ts import { IDKit, diff --git a/world-id/idkit/credentials.mdx b/world-id/idkit/credentials.mdx index 17d92e9..e1a96c1 100644 --- a/world-id/idkit/credentials.mdx +++ b/world-id/idkit/credentials.mdx @@ -14,6 +14,7 @@ A [Credential](https://docs.rs/world-id-primitives/latest/world_id_primitives/cr | --- | --- | --- | | Proof of Human | The user is a unique human, backed by anonymous biometric verification by the Orb. | `proofOfHuman` | | Passport | The user holds a verified NFC passport credential. | `passport` | +| My Number Card | The user holds a verified Japanese My Number Card (MNC) credential. | `mnc` | | Selfie Check | A medium-assurance biometric credential using the device camera for liveness and facial similarity. | `selfieCheck` | | Identity Check | An attestation that a document-backed property about the user matches your requested attributes. | `identityCheck` with attributes like `minimum_age`, `nationality`, or `document_type`. | @@ -25,7 +26,7 @@ You can also add a liveness check to any preset with `require_user_presence`. # SDK presets -Preset helpers cover common credential requests: `proofOfHuman`, `passport`, `selfieCheck`, `identityCheck`, and the legacy presets. +Preset helpers cover common credential requests: `proofOfHuman`, `passport`, `mnc`, `selfieCheck`, `identityCheck`, and the legacy presets. The snippets below assume `rp_context` was already generated and signed by your backend. See [Integrate IDKit](/world-id/idkit/integrate) for the full RP-signing flow. @@ -103,9 +104,46 @@ const preset = passport({ signal: "user-123" }); ``` +## My Number Card + +Use `mnc` for the current World ID 4.0 My Number Card (MNC) credential. The preset also includes legacy document fallback for users without a World ID 4.0 proof. + + +```typescript title="JavaScript" +import { IDKit, mnc } from "@worldcoin/idkit-core"; + +const preset = mnc({ signal: "user-123" }); + +const request = await IDKit.request({ + app_id: "app_xxxxx", + action: "my-action", + rp_context, + allow_legacy_proofs: true, +}).preset(preset); +``` + +```tsx title="React" +import { IDKitRequestWidget, mnc } from "@worldcoin/idkit"; + +const preset = mnc({ signal: "user-123" }); + + { /* ... */ }} +/>; +``` + + ## Selfie Check -Use `selfieCheck()` with [session proofs](/world-id/idkit/session-proofs) to verify +Use `CredentialRequest("selfie")` with [session proofs](/world-id/idkit/session-proofs) to verify users when they first arrive and when they return. Create a session once, save its verified `session_id`, then prove that same session on later checks. @@ -113,12 +151,12 @@ Anyone with World ID App can complete the flow—no Orb or document credential i required. ```typescript -import { IDKit, selfieCheck } from "@worldcoin/idkit-core"; +import { IDKit, CredentialRequest } from "@worldcoin/idkit-core"; const request = await IDKit.createSession({ app_id: "app_xxxxx", rp_context, -}).preset(selfieCheck()); +}).constraints(CredentialRequest("selfie")); ``` Follow the [session guide](/world-id/idkit/session-proofs) for the complete @@ -286,7 +324,7 @@ These presets only return World ID 3.0 proofs. Use them for existing integration allow_legacy_proofs Request config and widgets - Required for request flows. Set to true while accepting World ID 3.0 fallback proofs; set to false for World ID 4.0-only requests. + Required for request flows. Set to true while accepting World ID 3.0 fallback proofs; set to false for World ID 4.0-only requests. Presets override it: proofOfHuman, passport, mnc, and identityCheck always accept 3.0 fallback, and selfieCheck never does. require_user_presence diff --git a/world-id/idkit/error-codes.mdx b/world-id/idkit/error-codes.mdx index 4d6d9be..20ecc73 100644 --- a/world-id/idkit/error-codes.mdx +++ b/world-id/idkit/error-codes.mdx @@ -33,6 +33,11 @@ This page documents the IDKit SDK and bridge error codes returned during request Requested credential type is not available for that user. Offer fallback credential policy or explain requirement. + + feature_unavailable + Requested feature is not enabled for the app. JS/React only until the next Kotlin and Swift releases. + Get the feature enabled for your app, or fall back to a request that doesn't use it. + world_id_4_not_available World ID 4.0 credential is not available for that user. @@ -145,7 +150,7 @@ This page documents the IDKit SDK and bridge error codes returned during request invalid_rp_id_format - RP ID is malformed. + RP ID is malformed. JS/React only — Kotlin and Swift have no equivalent code. Use the registered rp_... ID from your app configuration. @@ -169,7 +174,7 @@ In JS and React, failed requests expose an `IDKitDebugReport` for triage. The wi Treat version availability errors such as `world_id_4_not_available` and `world_id_3_not_available` as terminal for the current user and request. Retrying the same request usually returns the same result; change the requested credential policy or show a user-facing fallback instead. -In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the same raw values through their `IDKitErrorCode` enums. +In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the same raw values through their `IDKitErrorCode` enums, except `invalid_rp_id_format` (JS/React-only) and `feature_unavailable` (JS/React only until the next Kotlin and Swift releases). ```tsx ") } @@ -136,7 +138,7 @@ func handleRPSignature(w http.ResponseWriter, r *http.Request) { # Step 4: Generate the connect URL and collect proof -To test during development, use the [simulator](https://simulator.worldcoin.org/) and set `environment` to `"staging"`. +To test during development, use the [simulator](https://simulator.worldcoin.org/) and set `environment` to `"staging"`. To test with the sandbox build of World ID instead, see [Sandbox](/world-id/sandbox/what-is-sandbox). ```typescript title="JavaScript" @@ -162,7 +164,7 @@ const request = await IDKit.request({ signature: rpSig.sig, }, allow_legacy_proofs: true, - environment: "production", // Only set this to staging for testing with the simulator + environment: "production", // "staging" (simulator) or "sandbox" (sandbox build of World ID) return_to: "myapp://verify-done", // Optional: mobile deep-link callback URL // Signal (optional): Bind specific context into the requested proof. // Examples: user ID, wallet address. Your backend should enforce the same value. @@ -298,6 +300,8 @@ val completion = request.pollUntilCompletion() ``` +> Advanced (JS/React): for custom multi-credential requests (e.g. Proof of Human AND (Passport OR MNC)), use `.constraints(...)` instead of `.preset(...)` (in React, the `constraints` prop instead of `preset`), built with the `any`/`all`/`enumerate`/`CredentialRequest` helpers from `@worldcoin/idkit-core` (also exported by `@worldcoin/idkit`). A user holds at most one [NFC credential](/world-id/credentials/9303), so passport and MNC are alternatives: combine them with `any`, never `all`. The Kotlin and Swift SDKs don't expose these helpers. + ### IDKit response After the user completes the verification flow, IDKit returns one of the following response shapes, depending on the protocol version and proof type. diff --git a/world-id/idkit/javascript.mdx b/world-id/idkit/javascript.mdx index 03d8ce6..0d83ce7 100644 --- a/world-id/idkit/javascript.mdx +++ b/world-id/idkit/javascript.mdx @@ -28,10 +28,11 @@ yarn add @worldcoin/idkit-core - `IDKit.request(config)` for uniqueness proofs - `IDKit.requestWithInviteCode(config)` for invite-code mode -- `proofOfHuman`, `passport`, `selfieCheck`, and `identityCheck` for current presets +- `IDKit.createSession(config)` and `IDKit.proveSession(sessionId, config)` for [session proofs](/world-id/idkit/session-proofs) +- `proofOfHuman`, `passport`, `mnc`, `selfieCheck`, and `identityCheck` for current presets - `orbLegacy`, `secureDocumentLegacy`, `documentLegacy`, and `deviceLegacy` for legacy presets -Each entry point returns a builder. Finalize it with `.preset(...)`. +Each entry point returns a builder. Finalize it with `.preset(...)` for single-credential requests, or `.constraints(...)` for multi-credential and session requests — see [Constraints](#constraints). ## Request config @@ -49,7 +50,7 @@ const builder = IDKit.request({ signature: "0x...", }, allow_legacy_proofs: true, - environment: "production", // Only set this to staging for testing with the simulator + environment: "production", // "production" | "staging" (simulator) | "sandbox" (sandbox build of World ID, for integration testing) return_to: "myapp://verify-done", // Optional: mobile deep-link callback URL bridge_url: undefined, // Optional: custom bridge URL }); @@ -72,9 +73,29 @@ const request = await IDKit.request({ }).preset(orbLegacy({ signal: "user-123" })); ``` +## Constraints + +`.constraints(...)` builds custom World ID 4.0 credential requests, including composite trees (e.g. Proof of Human AND (Passport OR MNC)), using `CredentialRequest`, `any`, `all`, and `enumerate`. It's the only finalizer `IDKit.createSession()`/`IDKit.proveSession()` builders accept — `.preset()` throws on a session builder. + +```ts +import { IDKit, CredentialRequest, any, all } from "@worldcoin/idkit-core"; + +const request = await IDKit.request({ + app_id: "app_xxxxx", + action: "my-action", + rp_context, + allow_legacy_proofs: false, +}).constraints( + all( + CredentialRequest("proof_of_human"), + any(CredentialRequest("passport"), CredentialRequest("mnc")), + ), +); +``` + ## Polling and status -After `.preset(...)`, you get an `IDKitRequest` object: +After `.preset(...)` or `.constraints(...)`, you get an `IDKitRequest` object: - `connectorURI` - `requestId` diff --git a/world-id/idkit/mini-apps.mdx b/world-id/idkit/mini-apps.mdx index b8166c3..af6baec 100644 --- a/world-id/idkit/mini-apps.mdx +++ b/world-id/idkit/mini-apps.mdx @@ -41,7 +41,7 @@ The Mini-App-specific details: | Passport-backed checks | `passport` | For repeated Selfie Check verification of returning users, use -[session proofs](/world-id/idkit/session-proofs) with the `selfieCheck()` preset. +[session proofs](/world-id/idkit/session-proofs) with `.constraints(CredentialRequest("selfie"))`. Check out this [page](/world-id/idkit/credentials) to learn about the different World ID credentials and which preset to use for each. diff --git a/world-id/idkit/react.mdx b/world-id/idkit/react.mdx index 331a54b..273812c 100644 --- a/world-id/idkit/react.mdx +++ b/world-id/idkit/react.mdx @@ -108,15 +108,19 @@ Hook result fields: - `open()` - `reset()` +- `isOpen` - `isAwaitingUserConnection` - `isAwaitingUserConfirmation` - `isSuccess` - `isError` +- `isInWorldApp` — true when running inside the World App (mini app context) - `connectorURI` - `result` - `errorCode` - `getDebugReport()` +`useIDKitRequest` and `useIDKitSession` return `IDKitHookResult`. `useIDKitInviteCodeRequest` returns `IDKitInviteCodeHookResult`, which includes these common fields plus `codeExpiresAt`, the invite code's expiration timestamp in Unix seconds (or `null` before a code is available). + ## Invite-code mode For invite-code flows, use `IDKitInviteCodeRequestWidget` (controlled) or `useIDKitInviteCodeRequest` (headless). Config matches `IDKitRequestWidget` / `useIDKitRequest` — invite-code mode adds no new required fields. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. @@ -141,10 +145,12 @@ import { IDKitInviteCodeRequestWidget, proofOfHuman } from "@worldcoin/idkit"; - `open()` - `reset()` +- `isOpen` - `isAwaitingUserConnection` - `isAwaitingUserConfirmation` - `isSuccess` - `isError` +- `isInWorldApp` - `connectorURI` - `codeExpiresAt` - `result` @@ -195,7 +201,7 @@ Session verification uses `IDKitSessionWidget` (controlled) or `useIDKitSession` ## Presets -React hooks/widgets take `preset` directly in config. +React hooks/widgets take `preset` or `constraints` in config — never both — for single- or multi-credential requests respectively. Session hooks/widgets (`useIDKitSession`, `IDKitSessionWidget`) require `constraints`; they have no `preset` option. ## Localization and UX notes diff --git a/world-id/idkit/session-proofs.mdx b/world-id/idkit/session-proofs.mdx index a8d9404..927e6bc 100644 --- a/world-id/idkit/session-proofs.mdx +++ b/world-id/idkit/session-proofs.mdx @@ -38,13 +38,13 @@ each example invocation. ## Create a session ```typescript -import { IDKit, selfieCheck } from "@worldcoin/idkit-core"; +import { IDKit, CredentialRequest } from "@worldcoin/idkit-core"; const request = await IDKit.createSession({ app_id, rp_context, environment: "production", -}).preset(selfieCheck()); +}).constraints(CredentialRequest("selfie")); // Display request.connectorURI as a link or QR code, then wait for the user. const completion = await request.pollUntilCompletion({ timeout: 120_000 }); @@ -54,7 +54,8 @@ if (!completion.success) throw new Error(completion.error); // Save its session_id against the account only after verification succeeds. ``` -The `selfieCheck()` preset selects the Selfie Check credential. See +`CredentialRequest("selfie")` selects the Selfie Check credential. Session builders +accept `.constraints(...)`; `.preset(...)` is only supported for uniqueness requests. See [Configure Credentials](/world-id/idkit/credentials) for credential options. Your backend must verify the complete result before saving its `session_id`. @@ -68,13 +69,13 @@ Load the saved session ID from your backend for the account being verified, and obtain a fresh RP signature. Prove that existing session: ```typescript -import { IDKit, selfieCheck } from "@worldcoin/idkit-core"; +import { IDKit, CredentialRequest } from "@worldcoin/idkit-core"; const request = await IDKit.proveSession(savedSessionId, { app_id, rp_context, environment: "production", -}).preset(selfieCheck()); +}).constraints(CredentialRequest("selfie")); const completion = await request.pollUntilCompletion({ timeout: 120_000 }); if (!completion.success) throw new Error(completion.error); @@ -87,7 +88,7 @@ if (!completion.success) throw new Error(completion.error); Preserve it unchanged. Creating a new session on each visit does not establish continuity with the account's existing session. -For React, use `IDKitSessionWidget` with -`preset={selfieCheck()}`. Omit `existing_session_id` for +For React, import `IDKitSessionWidget` and `CredentialRequest` from +`@worldcoin/idkit`, then use `constraints={CredentialRequest("selfie")}`. Omit `existing_session_id` for creation and set it to the saved session ID for subsequent checks. The widget's `handleVerify` callback should call your backend before `onSuccess` grants access. diff --git a/world-id/idkit/signatures.mdx b/world-id/idkit/signatures.mdx index b4c4a08..89ff94a 100644 --- a/world-id/idkit/signatures.mdx +++ b/world-id/idkit/signatures.mdx @@ -101,6 +101,13 @@ sig, err = signer.SignRequest(idkit.WithAction("my-action")) ``` +## Other exports + +`@worldcoin/idkit-server` exports two more functions alongside `signRequest`: + +- `computeRpSignatureMessage(...)` — builds the message bytes described in [`compute_rp_signature_message`](#algorithm) above, if you need the raw message instead of a full signature. Also exported from `@worldcoin/idkit-core/signing`. +- `getSessionCommitment(sessionId: string): bigint` — converts a session ID into the on-chain commitment value, for session-proof flows (see [Session proofs](/world-id/idkit/session-proofs)). Also exported from `@worldcoin/idkit-core/session` and `@worldcoin/idkit/session`. + ## Test vectors Verify your implementation against these. All vectors use deterministic inputs. diff --git a/world-id/reference/authenticator.mdx b/world-id/reference/authenticator.mdx index ddff0c8..e73ce22 100644 --- a/world-id/reference/authenticator.mdx +++ b/world-id/reference/authenticator.mdx @@ -2,6 +2,8 @@ title: "Authenticator Reference" --- +{/* cspell:ignore oprf */} + ## Authenticator Requests & Responses This section describes how authenticators receive and respond to requests from RPs. This also reflects what is implemented in the Authenticator SDK. @@ -20,17 +22,18 @@ The schema for the request is defined in the `world-id-primitives` crate as [`Pr { "id": "req_18c0f7f03e7d", "version": 1, - "created_at": "1771612953", - "expires_at": "1771613013", + "created_at": 1771612953, + "expires_at": 1771613013, "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", - "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", - "requests": [ + "signature": "0x111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221c", + "oprf_key_id": "0x1", + "proof_requests": [ { "identifier": "passport", "issuer_schema_id": 9303, - "signal": "abcd-efgh-ijkl" + "signal": "0xdeadbeef" } ] } @@ -41,22 +44,23 @@ The schema for the request is defined in the `world-id-primitives` crate as [`Pr { "id": "req_18c0f7f03e7d", "version": 1, - "created_at": "1771612953", - "expires_at": "1771613013", + "created_at": 1771612953, + "expires_at": 1771613013, "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", - "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", - "requests": [ + "signature": "0x111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221c", + "oprf_key_id": "0x1", + "proof_requests": [ { "identifier": "passport", "issuer_schema_id": 9303, - "signal": "abcd-efgh-ijkl" + "signal": "0xdeadbeef" }, { "identifier": "poh", "issuer_schema_id": 1, - "signal": "abcd-efgh-ijkl" + "signal": "0xdeadbeef" } ], "constraints": { @@ -70,27 +74,28 @@ The schema for the request is defined in the `world-id-primitives` crate as [`Pr { "id": "req_18c0f7f03e7d", "version": 1, - "created_at": "1771612953", - "expires_at": "1771613013", + "created_at": 1771612953, + "expires_at": 1771613013, "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", - "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", - "requests": [ + "signature": "0x111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221c", + "oprf_key_id": "0x1", + "proof_requests": [ { "identifier": "passport", "issuer_schema_id": 9303, - "signal": "abcd-efgh-ijkl" + "signal": "0xdeadbeef" }, { "identifier": "my-number-card", "issuer_schema_id": 9310, - "signal": "mnop-qrst-uvwx" + "signal": "0xfeedface" }, { "identifier": "orb", "issuer_schema_id": 1, - "signal": "abcd-efgh-ijkl" + "signal": "0xdeadbeef" } ], "constraints": { @@ -135,6 +140,8 @@ This priority mechanism allows RPs to request their preferred credential type wh The schema for the response is defined in the `world-id-primitives` crate as [`ProofResponse`](https://docs.rs/world-id-primitives/latest/world_id_primitives/request/struct.ProofResponse.html). +Each item in `responses` also includes a required `expires_at_min` field: the minimum expiration required for the credential used in the proof, needed when verifying the proof on-chain. + ### Response Examples @@ -147,8 +154,9 @@ The schema for the response is defined in the `world-id-primitives` crate as [`P { "identifier": "orb", "issuer_schema_id": 1, - "proof": "0x0000000000000000000000000000000000000000000000000000000000000000000000000", - "nullifier": "nil_00000000000000000000000000000000000000000000000001" + "proof": "00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "nullifier": "nil_0000000000000000000000000000000000000000000000000000000000000001", + "expires_at_min": 1771613013 } ] } @@ -169,12 +177,14 @@ The schema for the response is defined in the `world-id-primitives` crate as [`P { "id": "req_18c0f7f03e7d", "version": 1, + "session_id": "session_00000000000000000000000000000000000000000000000000000000000003ea0100000000000000000000000000000000000000000000000000000000000001", "responses": [ { "identifier": "orb", "issuer_schema_id": 1, - "proof": "0x0000000000000000000000000000000000000000000000000000000000000000000000000", - "session_nullifier": "00000000000000000000000000000000000000000000000001" + "proof": "00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "session_nullifier": "snil_00000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000001", + "expires_at_min": 1771613013 } ] } diff --git a/world-id/reference/contracts.mdx b/world-id/reference/contracts.mdx index 97af716..bfb0ad0 100644 --- a/world-id/reference/contracts.mdx +++ b/world-id/reference/contracts.mdx @@ -41,7 +41,7 @@ All of our smart contracts are available on GitHub: /> World Chain - World Chain + World Chain Sepolia Bridged ~5 Minutes after Ethereum @@ -80,7 +80,7 @@ All of our smart contracts are available on GitHub: /> Polygon - Polygon + — Bridged ~40 Minutes after Ethereum @@ -93,7 +93,7 @@ All of our smart contracts are available on GitHub: /> Base - Base + Base Sepolia Bridged ~5 Minutes after Ethereum @@ -107,7 +107,7 @@ All of our smart contracts are available on GitHub: ## Architecture -This section offers a high-level overview of the various smart contracts that make up World ID. This structure (including state bridging) is replicated on testnets -- currently Sepolia, Optimism Sepolia, and Base Sepolia. +This section offers a high-level overview of the various smart contracts that make up World ID. This structure (including state bridging) is replicated on testnets -- currently Sepolia, World Chain Sepolia, Optimism Sepolia, and Base Sepolia. Polygon has no testnet deployment. ### Identity Managers: `WorldIdIdentityManager` @@ -133,7 +133,7 @@ One World ID contract is deployed on each bridged chain, with an associated Stat The World ID Router will route your call to the correct Identity Manager contract (Ethereum) or World ID contract (L2 Chains) based on the `groupId` argument. This contract is proxied, so you will not need to update your code if the underlying contracts are upgraded. - Only Orb credentials are supported on-chain, so the `groupId` must be `1`. + Only `groupId` `1` (Orb credentials) is supported for RPs. ## Usage @@ -168,8 +168,8 @@ The `verifyProof` method of the **World ID Router** is used to verify proofs on- uint256 - Determines which Credential Type to verify against. As only Orb - credentials are supported on-chain, this must be 1. + Determines which Credential Type to verify against. Only{" "} + 1 (Orb credentials) is supported for RPs. @@ -191,9 +191,10 @@ The `verifyProof` method of the **World ID Router** is used to verify proofs on- uint256 - The root of the merkle tree to verify against. This is obtained from the - IDKit widget as a hex string nullifier_hash, and must be - converted to a uint256 before passing it to the{" "} + The nullifier for this proof, preventing double signaling. This is + obtained from IDKit as a hex string (nullifier in IDKit + 4.x v3 responses, nullifier_hash in older versions), and + must be converted to a uint256 before passing it to the{" "} verifyProof method. @@ -259,7 +260,7 @@ const unpackedProof = decodeAbiParameters([{ type: 'uint256[8]' }], proof)[0] ```` ```ts title="ethers.js" -import { defaultAbiCoder as abi } from '@ethers/utils' +import { defaultAbiCoder as abi } from '@ethersproject/abi' const unpackedProof = abi.decode(['uint256[8]'], proof)[0] ```` diff --git a/world-id/reference/poh-issuer.mdx b/world-id/reference/poh-issuer.mdx index 8fbedc7..21facef 100644 --- a/world-id/reference/poh-issuer.mdx +++ b/world-id/reference/poh-issuer.mdx @@ -142,11 +142,12 @@ The `credential` response field is a base64-encoded JSON representation of the W "id": 123456789, "version": "V1", "issuer_schema_id": 42, + "issuer_version": 1, "sub": "", "genesis_issued_at": 1733241600, "expires_at": 1764777600, "claims": ["", "", ""], - "associated_data_hash": "", + "associated_data_commitment": "", "signature": "", "issuer": { "pk": ["", ""] @@ -161,17 +162,18 @@ The `credential` response field is a base64-encoded JSON representation of the W | `id` | `uint64` | Issuer-scoped reference identifier for the credential. | | `version` | `string` | Credential version. Current value is `V1`. | | `issuer_schema_id` | `uint64` | Identifier for the (issuer, schema) pair registered in `CredentialSchemaIssuerRegistry`. | +| `issuer_version` | `uint8` | Issuer-specific versioning for the credential format. | | `sub` | `FieldElement` | Blinded subject identifier derived from the World ID leaf index and an issuer-specific blinding factor. | | `genesis_issued_at` | `uint64` | Unix timestamp (seconds) of the first issuance of this credential. | | `expires_at` | `uint64` | Unix timestamp (seconds) for expiration. | -| `claims` | `FieldElement[]` | Up to 16 claim commitments. Unused indices are the zero field element. | -| `associated_data_hash` | `FieldElement` | Poseidon2 hash of issuer-defined associated data. The associated data itself is not included. | +| `claims` | `FieldElement[]` | Up to 15 claim commitments. Unused indices are the zero field element. The 16th hash slot is reserved: a trailing zero 16th element is stripped on deserialization, and a nonzero one is rejected. | +| `associated_data_commitment` | `FieldElement` | Poseidon2 commitment to issuer-defined associated data (not included). Previously `associated_data_hash` — deprecated alias, slated for removal. | | `signature` | `string` | 64-byte compressed EdDSA signature over the credential hash, hex-encoded (128 hex chars, no `0x`). | | `issuer` | `EdDSAPublicKey` | Issuer public key that signed the credential. | ### Field representations -- **FieldElement** values (`sub`, `claims`, `associated_data_hash`) are hex strings with a `0x` prefix and 64 hex characters. +- **FieldElement** values (`sub`, `claims`, `associated_data_commitment`) are hex strings with a `0x` prefix and 64 hex characters. - **Issuer public key** (`issuer.pk`) is serialized as `[x, y]` decimal strings for BabyJubJub affine coordinates. - **Signature** is hex-encoded compressed bytes (no `0x` prefix). @@ -180,7 +182,7 @@ The `credential` response field is a base64-encoded JSON representation of the W - The user first obtains an Orb credential (currently a PCP in v2.3 format). - `claim[0]` is a commitment to the Orb credential, currently `H(hashes.json)`. - When refreshing without a PCP, `claims[0]` is derived from issuer-defined refresh data (a hash of `idCommitment`, `sub`, and a timestamp). -- The PoH credential has no associated data, so `associated_data_hash` is the zero field element. +- The PoH credential has no associated data, so `associated_data_commitment` is the zero field element. - The PoH credential subject is blinded and differs from the Orb credential subject. - The issuer may require a proof for the requested `sub` to prevent bricking an identity. diff --git a/world-id/sandbox/sandbox-access.mdx b/world-id/sandbox/sandbox-access.mdx index d7c3930..ac193ea 100644 --- a/world-id/sandbox/sandbox-access.mdx +++ b/world-id/sandbox/sandbox-access.mdx @@ -45,7 +45,7 @@ request. TestFlight, not any other email. - If your request was rejected or your access was revoked, submitting the same email again won't create a new request. Contact Sandbox Support at - sandbox.access@toolsforhumanity.org. + sandbox.access@toolsforhumanity.com. ### Android — Google Play diff --git a/world-id/sandbox/testing-selfie-check.mdx b/world-id/sandbox/testing-selfie-check.mdx index c35ef71..88cc0a5 100644 --- a/world-id/sandbox/testing-selfie-check.mdx +++ b/world-id/sandbox/testing-selfie-check.mdx @@ -30,9 +30,8 @@ Semi-cold states](/world-id/idkit/verification-flows) as the rest of World ID: - **Hot** — the user already has World ID installed. If they're already Selfie Check enrolled, they go straight to face match; if not, World ID walks them through - enrollment first, then match. (Selfie Check has no distinct Warm flow — enrollment - happens inline within Hot, same as [Verification Flows](/world-id/idkit/verification-flows) - describes.) + enrollment first, then match. (Enrollment happens inline within Hot rather than as a + separate flow.) - **Cold** — a new user with no World ID app: the full funnel, including install, account creation, date of birth, invite code (iOS), enrollment, and Selfie Check. - **Semi-cold** — an existing user without World ID on this device: reinstall and