Skip to content

Browser v4 uniqueness flow: constraints(proof_of_human) emits mixed-mode bridge payload and falls back to legacy / verification_rejected #204

@0xpantera

Description

@0xpantera

Summary

We are integrating World ID 4.0 for an on-chain uniqueness-gated flow and hit two related problems in the browser path:

  1. @worldcoin/idkit-core@4.1.0 with IDKit.request(...).constraints(CredentialRequest('proof_of_human')) emitted a mixed-mode bridge request that included a top-level verification_level: "device", and the World App completed with a legacy protocol_version: "3.0" result.
  2. After bypassing the SDK request builder and sending a pure v4 bridge payload ourselves, the request no longer returned a legacy proof, but the World App/bridge completed with verification_rejected.

At this point we are no longer trying to prove that our app works; we have a minimal repro that seems to isolate the request semantics.

Environment

  • @worldcoin/idkit-core: 4.1.0
  • Browser integration, not React
  • Production environment
  • RP is registered for World ID 4.0 in the Developer Portal
  • app_id: app_969d38c8ba9adf22a4ac3b2d4400575a
  • rp_id: rp_6765160973bf7042

Repro 1: official SDK browser flow

Code shape:

const request = await IDKit.request({
  app_id,
  action: "create-auction",
  rp_context,
  allow_legacy_proofs: false,
  environment: "production",
}).constraints(
  CredentialRequest("proof_of_human")
)

const result = await request.pollUntilCompletion()

What we observed

Even though allow_legacy_proofs was false, the raw result was:

{
  "success": true,
  "result": {
    "action": "create-auction",
    "environment": "production",
    "nonce": "0x...",
    "protocol_version": "3.0",
    "responses": [
      {
        "identifier": "orb",
        "merkle_root": "0x...",
        "nullifier": "0x...",
        "proof": "0x...",
        "signal_hash": "0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4"
      }
    ]
  }
}

What we found by instrumenting the SDK request

We captured the browser POST to https://bridge.worldcoin.org/request, decrypted the payload, and found that the SDK-generated request was not pure v4. It included a top-level legacy-looking field:

{
  "allow_legacy_proofs": false,
  "proof_request": {
    "proof_requests": [
      {
        "identifier": "proof_of_human",
        "issuer_schema_id": 1,
        "genesis_issued_at_min": 1775260800
      }
    ],
    "rp_id": "rp_6765160973bf7042",
    "version": 1
  },
  "signal": "0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4",
  "verification_level": "device"
}

The presence of verification_level: "device" is what led us to suspect the browser request builder was emitting a mixed-mode payload.

Repro 2: pure v4 bridge request (no SDK request builder)

We then bypassed IDKit.request(...).constraints(...) and constructed the bridge request ourselves.

The payload we sent was:

{
  "action": "create-auction",
  "allow_legacy_proofs": false,
  "app_id": "app_969d38c8ba9adf22a4ac3b2d4400575a",
  "environment": "production",
  "proof_request": {
    "action": "0x00c13e38e42d2818b1b66349184c1ad52cce0d9f2d6e61b58264e3c47f592270",
    "created_at": 1775338401,
    "expires_at": 1775338701,
    "id": "82ef1792-c31c-47cb-a4d8-3c0fd97ab30b",
    "nonce": "0x0045e7472500630a9751a115500e9bd0b5fb5201e79bd19cd363c018c910bb10",
    "oprf_key_id": "0x6765160973bf7042",
    "proof_requests": [
      {
        "identifier": "proof_of_human",
        "issuer_schema_id": 1,
        "genesis_issued_at_min": null,
        "expires_at_min": null
      }
    ],
    "rp_id": "rp_6765160973bf7042",
    "session_id": null,
    "signature": "<redacted>",
    "version": 1
  }
}

Notes:

  • We omitted signal entirely in this repro to keep the request as simple as possible.
  • We also omitted genesis_issued_at_min to avoid unnecessarily excluding older credentials.
  • We did not include verification_level.

Bridge request ID for this clean repro:

  • 2bf813bf-43ec-4985-b6a6-7ab7efc25700

Result

This no longer returned a legacy 3.0 proof. Instead it completed with:

{
  "success": false,
  "error": "verification_rejected",
  "rawResult": {
    "error_code": "verification_rejected"
  }
}

So after removing the mixed-mode request shape, the behavior changed from “legacy proof returned” to “request rejected”.

Questions

  1. Is verification_level: "device" expected in the browser bridge payload for a v4 uniqueness request built from:
    • allow_legacy_proofs: false
    • CredentialRequest("proof_of_human")
      ?
  2. If not, is this a bug in the browser request builder / wasm bridge serialization for @worldcoin/idkit-core@4.1.0?
  3. For a pure v4 browser request, is the payload shape above correct, especially:
    • top-level action
    • proof_request.action = hashSignal(action)
    • proof_requests: [{ identifier: "proof_of_human", issuer_schema_id: 1 }]
    • omission of signal when no signal is intended
  4. What conditions cause verification_rejected at this stage for a pure v4 uniqueness request in production?

Why we are filing this here

The first issue appears squarely in IDKit itself: the browser request builder emitted a bridge payload that mixed v4 proof_request with a legacy-looking top-level verification_level, and that correlated with a legacy 3.0 result.

We can share more implementation details if useful, but the payloads and request IDs above should be enough to inspect bridge-side behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions