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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions