Skip to content

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.

An identity, a platform account proved to a holder, has two things:

GitHubXGoogle
Idthe numeric user idthe numeric user ida digest of the account’s sub, see below
Handlethe login, like octocatthe username, like jackthe 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:

Terminal window
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.

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.
  • A to Z are 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.com and a--b@example.com are 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.