Passport-Based Sybil Resistance Without a Biometric Database

RuonID lets any app verify that a user is a unique real person, using only their existing government passport. No custom hardware, no biometric database. The user gets a deterministic pseudonymous ID that is unlinkable across apps.

How it works

User's phone                          OPRF Nodes (2-of-3, AMD SEV-SNP)
─────────────                         ─────────────────────────────────
1. NFC-read passport chip
2. Verify passport signature (CSCA)
3. Face match: live camera vs
   passport photo (ArcFace, on-device)
4. Blind(nationality ∥ documentNumber)
   ──── blinded point ──────────────→  5. Each node computes partial
                                          OPRF evaluation on its key share
                                       6. DLEQ proofs attached
   ←── partial evaluations ─────────
7. Verify DLEQ proofs
8. Combine + unblind
9. ruonId = keccak256(OPRF output)

The server never sees the passport data. The client never sees the key.

Same passport → same ruonId every time (sybil resistance). Different apps receive SHA256(ruonId ∥ developerId), deterministic but unlinkable across apps.

Security model

LayerGuarantee
Key custodyMaster key is split via Shamir (2-of-3). Each share is sealed to AMD SEV-SNP hardware via MSG_KEY_REQ.
Node compromiseThreshold scheme requires 2 nodes to collude. Each node has independent attestation and hardware-sealed key shares.
Node rotationReshare protocol replaces a node's share without changing the master key. New share is sealed to fresh hardware, verified via SNP attestation before any key material is transmitted.
Passport verificationPassive authentication: RSA/ECDSA signature on chip data verified against ICAO CSCA master list. Detects cloned/forged passports.
BiometricArcFace face embedding (ONNX, on-device). Passport photo vs live camera. 5-point landmark alignment, CLAHE preprocessing. Embedding never leaves the device.
Device integrityiOS App Attest / Android Play Integrity. Server verifies attestation before issuing a receipt. Prevents emulators and modified apps.
ReplayOPRF input is blinded with a fresh random scalar per request. The blinding factor never leaves the client.

What integrators receive

A developer generates their own secp256k1 keypair and registers their public key. To verify a user:

  1. Generate a signed QR code or deeplink containing a session ID, callback URL, and signature.
  2. User scans it in RuonID → sees a consent screen → authenticates with biometrics.
  3. RuonID POSTs to the callback URL:
{
  "appSpecificId": "0x...",    // SHA256(ruonId ∥ developerId), unique per user per app
  "identityTier": "passport-bound",
  "deviceVerified": true,
  "timestamp": "2026-03-30T...",
  "receipt": { ... }           // server-signed attestation, verifiable with SDK
}

The appSpecificId is deterministic. Same user always produces the same ID for the same app. Different apps get different IDs. No PII is transmitted.

For apps that need PII (KYC): a separate identity flow sends ECIES-encrypted passport fields (name, DOB, nationality, etc.) encrypted to the developer's public key. The user explicitly consents to each field.

Why a server-side key is necessary

Deterministic sybil resistance requires that the same person always produces the same ID. This means the output must be a function of (1) the person's identity data and (2) nothing else that varies. We show that a server-side secret is unavoidable for this to work securely.

Claim: A deterministic, sybil-resistant identity scheme cannot be fully client-side.

Case 1: Client-only, no secret

The function is ID = f(passport_data) where f is public. This is deterministic, but it is trivially reversible via glossary attack. Passport numbers are structured, short, and enumerable, and there are roughly 109 possible values per country. An attacker who observes a pseudonymous ID on-chain or in a database can compute f(candidate) for every plausible passport number until they find a match, deanonymizing the user. Without a secret key gating the evaluation of f, there is no way to rate-limit or prevent this offline enumeration.

Case 2: Client-only, with a client secret

The function is ID = f(passport_data, client_secret). The secret prevents glossary attacks, so an attacker cannot evaluate f without the secret. But client_secret is device-bound. If the user loses their phone, gets a new device, or reinstalls the app, the secret changes and the same person produces a different ID. Determinism is lost.

Case 3: Server-side secret

The function is ID = f(passport_data, server_key). The server_key is persistent and independent of the user's device, so the same person always produces the same ID (deterministic). The secret is held server-side behind device attestation and rate limiting, so an attacker cannot freely evaluate f to mount a glossary attack. Additionally, the actual passport must be NFC-scanned to initiate the flow, adding a physical possession requirement. This is the only case that satisfies determinism, glossary-attack resistance, and sybil resistance simultaneously.

The privacy problem: If the server sees passport_data in the clear, it learns the user's identity. This is where the OPRF comes in. The client blinds the input before sending it, the server evaluates the function on the blinded input, and the client unblinds the result. The server never sees the raw input; the client never sees the key.

The trust problem: A single server holding the key is a central point of failure. This is where threshold cryptography comes in. The key is split across multiple independent nodes (2-of-3), each sealed to hardware (AMD SEV-SNP).

Summary: Determinism requires a persistent secret. Device-bound secrets break determinism. A public function with no secret is vulnerable to glossary attacks. Therefore the secret must live server-side, protected by device attestation and rate limiting. OPRF eliminates the privacy cost. Threshold splitting eliminates the trust cost.

Why not existing solutions?

World ID requires custom iris-scanning hardware (the Orb) and has been banned or restricted in multiple countries over biometric data concerns, including Spain, Kenya, Brazil, Indonesia, Thailand, Hong Kong, and the Philippines. Its coverage is limited to physical Orb locations.

ZK passport solutions like Rarimo and Self (formerly OpenPassport) use ZK-SNARKs to prove passport validity without revealing personal data. However, they rely on a device-bound secret to generate the nullifier. This means the user's identity is tied to a specific device. If they lose their phone, switch devices, or reinstall the app, the secret changes and they produce a different nullifier. The identity is not deterministic across devices, which breaks sybil resistance unless the user goes through a migration or re-registration process.

RuonID's approach avoids both problems. No custom hardware: any NFC phone works with any ICAO ePassport (150+ countries). No device-bound secret: the deterministic output comes from the server-side OPRF key, so the same passport produces the same ID regardless of which device the user is on. No biometric database: face matching is purely on-device. And the OPRF key is threshold-split across hardware-sealed TEE nodes, with each node's share independently attested.

Integration

Open questions for partners