Platforms and nodes
libID works with three platforms: GitHub, X and Google. Each has a key, and the contracts identify it by the hash of that key:
platformId('github') // keccak256("github")The keys are github, x and google.
Ids and handles
Section titled “Ids and handles”An identity, a platform account proved to a holder, has two things:
| GitHub | X | ||
|---|---|---|---|
| Id | the numeric user id | the numeric user id | a digest of the account’s sub, see below |
| Handle | the login, like octocat | the username, like jack | the email address |
The id never changes. The handle can: users rename themselves, and platforms give old handles to new users.
A Google id is not Google’s own id. It is this digest, written as 0x and
64 lowercase hex digits:
SHA256("libid.google-user-id" || sub)sub is the exact sub claim from Google’s sign-in token, not trimmed or
lowercased, and || joins the two byte strings. From a shell:
printf '%s%s' libid.google-user-id "$SUB" | sha256sum | sed 's/^/0x/; s/ .*//'For SUB=123456789012345678901 it prints
0x20078023c9d4bf6bffc2580ec36446075d10c8453cecbe4f1cb3d326b2b35560.
The real sub never reaches the chain.
The contracts do not store strings as keys. They hash each id and each
handle into a bytes32 key called a node:
idNode = keccak256(abi.encode(keccak256("libid.identity.id-node.v1"), platformId, keccak256(id)))handleNode = keccak256(abi.encode(keccak256("libid.identity.handle-node.v1"), platformId, keccak256(handle)))The platform is part of the node, so alice on X and alice on GitHub are
different nodes.
You rarely compute nodes yourself. IdentityRegistry.handleNodeOf(platformId, handle)
returns a handle’s node. The TypeScript package computes it locally in two
steps:
const rules = await rulesOf(registry, platformId('github'));const node = handleNode(platformId('github'), handleHash('@Octocat', rules));handleHash normalizes the handle with the platform’s rules and hashes it.
handleNode takes that hash, not the handle.
Normalization
Section titled “Normalization”Before a handle is hashed, it is normalized, so that the different ways of writing one handle reach the same node:
- Spaces at the start and end are removed. Other whitespace is refused.
- On GitHub and X, one leading
@is then removed. AtoZare lowercased.- Letters and digits are allowed everywhere. Beyond those, GitHub allows
-, X allows_, and Google allows.,+,-,_and one@. Anything else, including any non-ASCII byte, is refused. - On GitHub, a
-can never be first, last, or next to another-. - A Google address needs exactly one
@, with something on each side. Nothing else about its shape is checked, so-a@example.comanda--b@example.comare accepted. - Handles longer than 15 characters on X, 39 on GitHub, or 62 for Google are
refused. The length is counted after the spaces and the
@are removed.
So @Octocat, octocat and OCTOCAT are the same GitHub handle. Text that
breaks these rules is not a handle: resolveHandle returns the zero address
for it, and handleNodeOf reverts with UnusableHandle.
The exact rules are in a shared table of test vectors; see
handle normalization
in the spec. IdentityRegistry.rulesOf(platformId) returns the rules a network
uses.