Enrollment and Identity Proofing
Enrollment is how a person becomes a verified human in not.bot™. The user scans a government passport, the system confirms the passport is authentic and has not enrolled before, and the Chia blockchain receives a new root DID that anchors the person's identity. This document describes the enrollment flow in production today, the identity-proofing strength each verification level attests to, and what a relying party learns from those levels.
One verification level ships today: notbot0, at-home enrollment via NFC passport scan. Two stronger levels, notbot1 and notbot2, are planned.
The notbot0 enrollment flow
A user enrolls from home with the not.bot app and a passport. The app reads the passport's NFC chip, a passport-validation provider (Signicat) confirms the chip data is authentic, and the Escrow Server (software Julia Social wrote, operated by Praxis, the independent escrow agent) confirms the passport has not enrolled before and stores an encrypted identity record that only Julia Social can later decrypt. The cleartext passport data is discarded. No party holds the user's data after enrollment completes.
Authentication and passport scan
The user scans the passport's NFC chip, sending the data to Signicat. The app reads only DG1, the machine-readable-zone text: full name, date of birth, gender, and nationality. The app does not read DG2, the facial image. No biometric data leaves the passport chip.
Signicat validates the chip data, confirms the issuing government's digital signatures on it, and confirms the passport has not expired. On success, Signicat holds the validated data for up to five minutes, keyed by a session ID. Signicat holds ISO/IEC 27001:2022 certification (DNV certificate C849517) and is a Qualified Trust Service Provider under eIDAS, listed on the EU Trusted List. Signicat's identity and document proofing is certified to ETSI TS 119 461, the trust-service identity-proofing standard.
Duplicate detection and root DID creation
The app requests a new root DID from the Julia Social backend and passes the session ID. Julia Social asks the Escrow Server whether the session ID belongs to a new person or a returning one. The Escrow Server downloads the data from Signicat, tells Signicat to delete it (Signicat complies within seconds), and extracts the personal text: full name, birthdate, gender, nationality. It hashes that text with a pepper and checks the hash against the hashes it has seen before.
A new hash means a new person. The Escrow Server, operated by Praxis Escrow, preprocesses and anonymizes the data into inputs for the multiparty computations that later produce credentials, and then encrypts the data, stores the encrypted record, stores the hash for future duplicate detection, and discards the cleartext. Praxis does not have the decryption key. Julia Social has the decryption key on air-gapped hardware. Julia Social has no access to the Escrow Server or the encrypted data, enforced by contract. The Escrow Server never writes cleartext identity data to persistent storage. Julia Social then creates a new root DID on the Chia blockchain, associates it with the new person at the Escrow Server, and returns it to the app. The app creates an initial alias with a full set of credentials, and the user sets a recovery password.
A known hash means a returning person. The app offers the recovery flow, the path for a user who lost their device and needs to move onto a new one. Recovery (Doc #6D) covers that sequence.
Duplicate detection runs on personal-information fields, not on the passport number. A user who obtains a new passport after a legal name change presents fields that do not match the stored hash, so the system treats them as a new person. That user creates a new root DID and re-enrolls; the prior identity and its aliases, credentials, and signature history cannot be carried over. Julia Social plans to provide support for name changes and similar changes to a person's identity data in a future release.
Faucet-funded transactions
Creating the root DID is an on-chain operation, and so are alias creation, recovery, and rekey. Julia Social funds these through a faucet coin. Users never hold XCH and never pay a blockchain fee. A Julia DID cannot be melted; the root DID persists as the user's anchor.
What each party holds after enrollment
| Party | Holds | Can decrypt |
|---|---|---|
| Signicat | Nothing (deleted within seconds of the Escrow Server download) | n/a |
| Praxis (Escrow Server) | Peppered duplicate-detection hash, anonymized MPC inputs, encrypted identity record | No |
| Julia Social | The root DID | No (holds the record decryption key on air-gapped hardware, not the record) |
| User's device | Root DID, private keys in the secure element | Yes (its own keys) |
Julia Social never sees the passport data at any point in this flow. The Privacy Architecture (Doc #7) document specifies the full data flow and the architectural impossibilities it enforces.
What the verification levels attest to
A verification level is a claim about identity-proofing strength, issued as a distinct verifiable credential with its own URI. The levels do not gate other claims. Every enrolled user holds the same collection of presentable claims regardless of level: age thresholds, age brackets, personal-information fields, Site Pass, and account or domain names. The level tells a relying party how much confidence to place in the person behind those claims.
notbot0 (current state). The user scanned a government passport's NFC chip. Signicat validated the chip's digital signatures and confirmed the data against the ICAO Public Key Directory. Praxis confirmed the passport had not enrolled another not.bot identity. notbot0 includes no liveness check. notbot0 attests that an authentic, unexpired passport was present and unique, not that the person scanning it was the rightful holder.
notbot1 (planned). The user completed identity proofing at a contracted third-party enrollment partner: a bank, hospital, or enterprise that enrolls users into not.bot for its own purposes. The partner contract requires NIST SP 800-63A Identity Assurance Level 2 In-Person (IAL2) procedures, which add in-person proofing against the live person. Julia Social does not audit these partners. Enforcement is consequence-based: on suspicion of substantial fraudulent issuance, Julia Social melts the partner's issuer key singleton, which invalidates every credential that singleton signed in one on-chain transaction, and affected users re-enroll elsewhere. Delegation and Organizational Identity (Doc #6C) covers the singleton mechanism.
notbot2 (planned). The user completed identity proofing at a first-party facility contracting with Julia Social to provide enrollment. The proofing process and facility design are not public at this stage.
The levels are cumulative. A notbot1 holder also holds notbot0. A notbot2 holder holds all three. The Roadmap (Doc #20) tracks notbot1 and notbot2 as planned work.
A verification level reflects a point-in-time check. Julia Social does no ongoing monitoring of the document a user enrolled with. If a passport is revoked inside the three-year window, for example when the holder naturalizes into another country, or if the holder's name, gender, or other passport data changes, Julia Social does not learn of it and makes no change to the credentials in the app. The passport must be unexpired at enrollment; expiration after enrollment has no effect on a not.bot identity. A user whose passport data has changed can re-enroll with the new document, which the system treats as a new person.
The relying-party perspective
A relying party requests the lowest level its use case needs. Requesting a higher level than the situation calls for shrinks the pool of users who can respond, since most enrolled users hold notbot0 alone. Most relying parties request notbot0.
The user presents the claim or claims the relying party requested, and the default is to present only what was asked. A user can volunteer additional claims, but a relying party should neither expect nor require that. The response carries the claim property URI and an encoded value. A relying party does not learn the user's maximum verification level unless the user volunteers it: a notbot2 holder can answer a notbot0 request with a notbot0 response and reveal nothing higher.
A relying party reads two separate things from a presentation: the verification level, which says how the person was proofed, and the claim itself, which says what the proofed data shows. The two do not interact. The level is the relying party's measure of confidence in the person behind the claim, whatever the claim happens to be.
The personal-information credentials, first name, family name, gender, and nationality, are issued from the passport at enrollment and expire three years later. They state what the passport showed on the day the user enrolled. Credentials (Doc #6B) specifies the full claims catalog, how a user enables or disables each credential per alias, and why the same value on two aliases cannot be correlated.
The birthdate-related credentials, the birthdate, its components, exact age, age thresholds, and age brackets, are computed through a three-party MPC among the not.bot app, Julia Social, and Praxis (the independent escrow agent that operates the Escrow Server), in which neither Julia Social nor Praxis learns the birthdate. Each carries a validity window of one calendar month: Valid-From is the first of the month, and Valid-To is the last day. The app requests fresh credentials for an alias whenever a presentation needs them and it does not already have them. The user can refresh them by hand with a button in the app. When the user crosses an age threshold and needs to present updated age credentials, the app requests new credentials that reflect the new age. Every request produces credentials with a new encoding, so neither Julia Social nor Praxis can tell from two requests in the same month that the user crossed a threshold between them. All birthdate-related credentials refresh together, and every threshold and bracket is issued each time, including the ones that do not apply to the user, so the set of credentials requested never reveals which ones apply. Human Verification (Doc #3) covers how a relying party requests and consumes these through not.bot Verify.