You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OpenChat's "verified unique person" concept was suspended in #9060 because the DecideAI verifiable-credential integration stopped working and generated constant support noise. Today every user is treated as verified: unique-person access gates silently pass, prize messages cannot be restricted to verified humans, and the verified badge is hidden. Group owners have lost a real tool against bots and multi-accounting, prize senders are exposed to sybil farming, and honest users have no way to distinguish themselves from bots.
We want the concept back — but without depending on any third-party issuer, so that the feature cannot be broken again by an external company changing direction, and so that verification is credibly "OpenChat/IC-native" end to end.
Solution
An in-house, IC-native proof of unique personhood based on face-embedding uniqueness:
A user opens a "Verify humanity" flow, consents to biometric processing, and completes a short camera challenge (a random sequence of head poses within a deadline).
The captured frames are uploaded to a new OpenChat canister which computes a face embedding on-chain (the client is fully untrusted — it cannot forge a vector), verifies the pose challenge on-chain, and compares the embedding against all previously enrolled embeddings.
If no similar face is already enrolled, the user's embedding is stored and a UniquePersonProof (new provider: OpenChat) is recorded through the existing, dormant propagation plumbing — restoring the verified badge, unique-person access gates, and human-only prize eligibility.
Raw frames are never persisted; only the embedding is kept, and it is deleted when the account is deleted.
The whole mechanism — model weights included — is governed by the SNS DAO. No external issuer, no off-chain service.
User Stories
As an OpenChat user, I want to verify that I am a unique human from within the app, so that I can access human-only groups and prizes.
As an OpenChat user, I want the verification to be a short guided camera flow with live feedback, so that I can complete it in under a minute without reading instructions.
As an OpenChat user, I want an explicit consent screen before my camera activates, so that I understand what biometric data is processed, where it is stored, and how to delete it.
As a privacy-conscious user, I want my raw photos/video frames discarded immediately after processing, so that no images of me persist anywhere.
As a privacy-conscious user, I want my stored face embedding deleted when I delete my OpenChat account, so that I can exercise my right to erasure.
As a verified user, I want a verified badge shown against my profile and messages, so that others know I am a unique human.
As a group/community owner, I want to gate membership with a unique-person access gate, so that my community is bot- and sybil-resistant.
As a group/community owner, I want unique-person gates composable with other gates (AND/OR), so that I can express richer access rules.
As a prize-message sender, I want to restrict prize claims to verified unique humans, so that airdrops are not farmed by multi-account operators.
As a user who hits a unique-person gate while unverified, I want to be offered the verification flow inline at join time, so that I don't have to hunt for it in settings.
As a user whose capture was inconclusive, I want a clear "one more take" retry round with tips, so that poor lighting doesn't permanently block me.
As a user who cannot be verified (e.g. my face matches an existing enrollment), I want an honest failure message that does not reveal whose account matched, so that verification failures don't leak other users' identity.
As a user, I want verification to be free, so that proving I am human doesn't cost me money.
As a user on iOS/Android (Tauri) or any desktop browser, I want the flow to work on my platform, so that I am not forced onto a specific device.
As a user on a device where face tracking cannot load, I want a manual capture fallback, so that the flow still works (the canister re-verifies everything anyway).
As a mobile user on a slow connection, I want to see loading/progress states for model assets and uploads, so that the flow doesn't appear frozen.
As a user, I want to see the processing status (queued/processing/result) after submitting, so that I know my verification is progressing.
As a previously DecideAI-verified user, I want to be told I need to re-verify under the new system, so that I understand why my badge disappeared.
As a user who earned the "Proved unique personhood" achievement under DecideAI, I want to keep my earned CHIT, so that the migration doesn't punish me retroactively.
As the platform, I want embeddings computed and challenge compliance checked on-chain, so that a scripted attacker cannot submit forged vectors or bypass liveness client-side.
As the platform, I want per-user attempt limits and a global processing-concurrency cap, so that verification cannot be used to DoS the canister or drain cycles.
As the platform, I want the uniqueness check to compare against every enrolled embedding of the current model version, so that one human cannot hold two concurrently verified accounts.
As the SNS DAO, I want ML model weights activated only via hash-pinned governance proposals, so that the verification mechanism itself is decentralized and auditable.
As the SNS DAO, I want a model-upgrade path with an announced re-verification window, so that we can improve accuracy or fix weaknesses without stranding users.
As the treasury, I want per-verification cycle costs measured and monitored, so that we can activate cost-control levers (Diamond-only, or a CHAT charge refundable on failure) if the feature becomes too expensive.
As a support agent, I want failure reasons expressed as clear user-facing categories (challenge failed, no face, not unique, expired, rate-limited), so that support tickets are tractable without any access to biometric data.
Implementation Decisions
Decisions locked during the requirements interview:
Threat model: strong sybil resistance. The client is fully untrusted. Client-side embedding computation with "signed vectors" was explicitly rejected: any key in the browser runtime can be extracted and used to sign forged vectors. Trust is achieved by moving computation on-chain.
On-chain inference. A new SNS-controlled canister (working name personhood_verifier) runs the full pipeline via a Rust ONNX runtime: face detection → landmark/pose estimation (challenge verification) → 512-dim face embedding. Long computations are split one-inference-per-timer-execution using deterministic time slicing; each single inference must fit the per-message instruction ceiling. Wasm SIMD is enabled for the verifier build.
Liveness = canister-issued challenge. A random head-pose sequence with a short session deadline, verified on-chain from the uploaded frames, plus a same-face-across-frames consistency check. Accepted residual risk: real-time deepfake puppeteering (no depth/PAD signal available on-chain); the realistic bar is per-identity human effort.
Session protocol.start_verification (issues challenge, session id, caps, deadline) → chunked JPEG frame upload bound to the session and caller principal → submit_verification (single-use) → status polling (queued/processing/verified/retry-required/failed). Sessions are heap-only and voided by upgrades.
Privacy invariants. Raw frames live only in canister heap for the lifetime of a session (~2 minutes) and are structurally excluded from stable memory and upgrade serialization. Only the quantized embedding persists, keyed by (model version, user id). Account deletion triggers embedding deletion via a queue from the user index. Consent screen precedes any camera access. Embeddings remain GDPR special-category biometric data — legal review is part of rollout.
Uniqueness matching. Brute-force cosine scan over i8-quantized, L2-normalized 512-dim embeddings of the current model version (memory and instruction budgets comfortably support 1M+ users, chunked scanning keeps behavior scale-independent). Three-band threshold policy: clear-unique → enroll; clear-duplicate → fail; gray zone → one stricter retry round with a longer challenge, then hard fail. Thresholds calibrated offline before launch.
False-match policy. Retry-hard then fail. No human appeal (there are no retained images to review) and no weaker fallback path. Failure responses never identify the matching account.
Reuse of existing plumbing. On success the verifier makes a guarded c2c call to the user index, which records the proof and reuses the existing intact fan-out (user index → local user indexes → user canister → is_unique_person at gate checks, achievement award included). The proof type gains an OpenChat provider variant and an optional model-version field (additive, serde/candid safe).
Legacy reset. All DecideAI-provider proofs are wiped by a one-off migration (a "proof removed" notification event is added — it doesn't currently exist — and is reused later for model-version lapses). The dormant DecideAI JWT submission endpoint and the join-time JWT acceptance path are removed so no new DecideAI proofs can be minted. Achievements/CHIT already earned are not clawed back. Gate checks are then un-stubbed to enforce for real.
Model lifecycle. Proofs record the model version; no time-based expiry. A model upgrade (activated by hash-pinned SNS proposal over chunk-uploaded weights) starts an announced ~90-day re-verification window; old-version proofs lapse at the deadline via the same removal fan-out. Known accepted hole: uniqueness is only enforced within a model version during the window; it self-heals at lapse.
Cost & abuse. Free to users; per-user attempt limit (~5 per rolling 48h, one active session), global concurrent-processing cap, strict upload caps (frame count/size, JPEG validation, dimension cap). Cycle cost estimated at a few cents per verification — to be measured, not assumed. Contingency levers reserved: Diamond-membership requirement, or a CHAT charge refundable on failed attempts (semantics finalized only if activated, noting refund-on-failure weakens DoS economics).
Frontend. One new dependency (@mediapipe/tasks-vision) used only for UX: live face framing, pose guidance, auto-capture of good frames (protecting rate-limited attempts). All assets self-hosted; loaded lazily; a degraded manual-capture mode covers load failures since security never depends on the client. A shared headless state machine (consent → camera → challenge → upload → processing → result) drives both the desktop and mobile component trees. The join-time gate evaluator no longer produces a credential — it checks/initiates verification directly. All sunset display-strips are reverted; DecideAI-specific constants and copy are removed.
Platforms at launch. Desktop web, mobile web, and Tauri native. Android camera permission plumbing already exists; iOS/macOS need camera-usage plist entries; Linux webkit2gtk gets a "verify in browser/mobile" fallback.
Release-order coupling. Backend gate enforcement, the legacy wipe, and the frontend un-strip + new flow must land in the same release window.
Testing Decisions
Good tests exercise external behavior through public interfaces — canister endpoints and propagated state — not implementation internals.
Backend integration tests (PocketIC, following the existing integration-test suite patterns): the verifier runs a deterministic test-mode stub engine (embedding derived from a marker byte in the upload) so duplicate / gray-zone / retry / not-unique paths are testable without real ML. Coverage: happy-path enrollment and proof propagation (including single achievement award), duplicate rejection, retry mechanics, rate limits, session deadline expiry, embedding deletion on account deletion, the DecideAI wipe migration, revived unique-person gate checks (prior art: existing gated-group tests), and model-version lapse sweeps.
Feasibility benchmarks: canbench measurements of instructions per model inference (prior art: existing backend benchmarks) are a Phase 0 exit criterion, not an afterthought.
Frontend: a mock verifier client (used when no verifier canister is configured) drives every state of the capture machine in both component trees; real-device QA matrix for camera + face tracking across platforms; degraded-mode path tested by blocking asset loads.
Out of Scope
Human appeal / manual review of failed verifications (no images are retained to review).
Presentation-attack detection beyond the pose challenge (no depth sensing, texture/screen-glare analysis, blink/liveness model, or PAD of any kind at launch). The pose challenge is a cost bar, not an anti-spoof wall. In casual testing a phone-screen photo failed the challenge, but that is incidental (small on-screen face, insufficient pose deltas), not evidence of spoof resistance: a determined attacker with a large high-resolution display, a precisely-tilted printed face, or a pre-rendered video of the pose sequence can in principle satisfy it. The real anti-sybil guarantee is the on-chain uniqueness check (one face enrolls once), not liveness. Adding genuine presentation-attack detection (on-device or on-chain) is a distinct future feature, out of scope here.
Defeating real-time deepfake camera injection — explicitly accepted residual risk.
Cross-model-version uniqueness enforcement during a re-verification window.
Sharding the verifier across canisters/subnets (design leaves the door open; not needed at current scale).
Activating the cost-control levers (Diamond-only, CHAT charge) — designed as contingencies only.
Any alternative verification path (payment-based, social-graph, government ID).
Clawing back CHIT/achievements earned under DecideAI.
Further Notes
Phase 0 is a hard gate. Nothing beyond a feasibility spike is built until on-chain inference is measured (every single inference under the per-message instruction ceiling; full verification within budget) and offline threshold calibration shows acceptable false-match/false-reject rates at ~1M-user scale. Fallback if accuracy is insufficient: a larger embedding model (~4x cost) remains feasible on-chain.
Suggested phases after the spike: backend skeleton end-to-end with the stub engine (in parallel with the frontend capture UX built against the mock client) → real ML pipeline + real client plumbing → coordinated cutover (types, wipe, gate un-stub, UI un-strip) → SNS registration, model-activation proposal, soak, and rollout comms (pre-existing unique-person gates in production data become enforced again).
Cycle-cost ballpark from design estimates: a few cents per verification (~$3–6k per 100k verifications); the estimate drives the reserved cost levers but must be validated by the spike.
Problem Statement
OpenChat's "verified unique person" concept was suspended in #9060 because the DecideAI verifiable-credential integration stopped working and generated constant support noise. Today every user is treated as verified: unique-person access gates silently pass, prize messages cannot be restricted to verified humans, and the verified badge is hidden. Group owners have lost a real tool against bots and multi-accounting, prize senders are exposed to sybil farming, and honest users have no way to distinguish themselves from bots.
We want the concept back — but without depending on any third-party issuer, so that the feature cannot be broken again by an external company changing direction, and so that verification is credibly "OpenChat/IC-native" end to end.
Solution
An in-house, IC-native proof of unique personhood based on face-embedding uniqueness:
UniquePersonProof(new provider: OpenChat) is recorded through the existing, dormant propagation plumbing — restoring the verified badge, unique-person access gates, and human-only prize eligibility.The whole mechanism — model weights included — is governed by the SNS DAO. No external issuer, no off-chain service.
User Stories
Implementation Decisions
Decisions locked during the requirements interview:
personhood_verifier) runs the full pipeline via a Rust ONNX runtime: face detection → landmark/pose estimation (challenge verification) → 512-dim face embedding. Long computations are split one-inference-per-timer-execution using deterministic time slicing; each single inference must fit the per-message instruction ceiling. Wasm SIMD is enabled for the verifier build.start_verification(issues challenge, session id, caps, deadline) → chunked JPEG frame upload bound to the session and caller principal →submit_verification(single-use) → status polling (queued/processing/verified/retry-required/failed). Sessions are heap-only and voided by upgrades.is_unique_personat gate checks, achievement award included). The proof type gains anOpenChatprovider variant and an optional model-version field (additive, serde/candid safe).@mediapipe/tasks-vision) used only for UX: live face framing, pose guidance, auto-capture of good frames (protecting rate-limited attempts). All assets self-hosted; loaded lazily; a degraded manual-capture mode covers load failures since security never depends on the client. A shared headless state machine (consent → camera → challenge → upload → processing → result) drives both the desktop and mobile component trees. The join-time gate evaluator no longer produces a credential — it checks/initiates verification directly. All sunset display-strips are reverted; DecideAI-specific constants and copy are removed.Testing Decisions
Out of Scope
Further Notes
🤖 Generated with Claude Code