DIDs
A DID (Decentralized Identifier) is how TinyCloud names a principal — the entity that holds, delegates, and exercises authority. TinyCloud uses exactly two DID methods: did:pkh:eip155:{chain}:{addr} for the owner (an Ethereum wallet, the root authority of a space), and did:key:z… for an ephemeral Ed25519 session key. Every capability chain traces, through delegation, from a did:key audience back to the owner did:pkh that owns the target space.
Role
DIDs are Layer 1 identity — the substrate OpenKey produces and that capabilities, delegation, and invocation are all keyed on. Because authority is rooted in a DID (not an account row), a node can authorize a request from the DIDs and signatures it carries alone, with no lookup (see thesis). The owner did:pkh is the single root every chain must terminate at; the session did:key is what apps actually hold, so the owner key is never exposed (see session-keys).
Shape
Two methods, canonicalized identically on both sides of the WASM boundary:
- Owner / root —
did:pkh:eip155:{chainId}:{address}. A CAIP-10 EVM account as a DID.chainIdis a positive integer (e.g.1);addressis a0x-prefixed, 40-hex-char, EIP-55 checksum Ethereum address. Example:did:pkh:eip155:1:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266. This is the DID an space is owned by (tinycloud:pkh:eip155:1:0x…:default), and the principal a wallet signs as in SIWE/CACAO. - Session —
did:key:z6Mk…. An Ed25519 public key encoded as adid:key(multicodec0xed01, base58btc). Created fresh by the WASM session manager. The DID URL form carries a fragment equal to the key id:did:key:z6Mk…#z6Mk…(manager.rs:get_did).
A DID URL appends #fragment (a verification-method id) to either form. The principal DID is the DID URL with its fragment stripped — matching is always done on principal DIDs, so a delegation to did:…#a is satisfied by an invocation from did:…#b.
Mechanics
Canonicalization (the matching contract)
Identity comparison is the bedrock of the auth model: a delegation is only valid if its delegatee DID matches the next link's signer. Both the node and the SDK normalize before comparing (tinycloud-auth/src/identity.rs, packages/sdk-core/src/identity.ts):
did:pkh:eip155is parsed (parse_pkh_did/parsePkhDid, regex/^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/), the address is EIP-55 checksummed (canonicalize_eip155_address/ viemgetAddress), and re-emitted asdid:pkh:eip155:{chainId}:{checksumAddr}. So two owner DIDs differing only by address case resolve equal (identity.rstest: lower-case input → checksum output).- Other methods (chiefly
did:key) are passed through unchanged —did:keyis case-sensitive (identity.rstest assertsdid:key:…Abcd≠did:key:…abcd). did_principal_matches/principalDidEqualsstrip any#fragment, canonicalize both sides, and compare. This is exactly what root-authority and parent-delegatee checks call (see capabilities verification).
chainId must be > 0; a malformed address length errors (InvalidAddressLength). Note the owner DID is chain-scoped — the same address on a different chainId is a different principal and a different space owner.
How a DID becomes a space address
An owner DID is folded into a space URI by dropping the leading did: (4 chars) to form the did-suffix: did:pkh:eip155:1:0x… → tinycloud:pkh:eip155:1:0x…:default. makePkhSpaceId(address, chainId, name) builds this directly (identity.ts).
Relationships
The owner did:pkh is the root authority of an space and the signer of root capabilities via SIWE/CACAO; the session did:key is the audience that root delegation grants to, and the signer of UCAN sub-delegations and invocations; both are produced/managed by OpenKey; the did-suffix of a DID is the prefix of every resource URI; canonicalized DID matching powers the capability chain checks.
Example
A wallet 0xf39F…2266 on Ethereum mainnet is the owner DID did:pkh:eip155:1:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266. At sign-in it grants, via one SIWE signature, a capability set to a freshly generated session DID did:key:z6MkpTHR… — so the app holds did:key:z6MkpTHR… and can tinycloud.kv/get on tinycloud:pkh:eip155:1:0xf39F…2266:default, while the owner key stays in the wallet. (See session-keys, sign-in-flow.)
Status & drift
Shipped. The two methods and EIP-55 canonicalization are mirrored in Rust and TypeScript with matching test vectors. Node DID resolution during verification currently uses AnyDidMethod::default() with a // TODO go back to static DID_METHODS note (models/delegation.rs, models/invocation.rs) — a cleanup, not a behavior gap. did:pkh is restricted to the eip155 namespace in code; other CAIP-2 namespaces are not parsed.
Sources
tinycloud-node:tinycloud-auth/src/identity.rs(parse_pkh_did,canonicalize_eip155_address,canonicalize_did,principal_did,did_principal_matches, test vectors)js-sdk:packages/sdk-core/src/identity.ts(pkhDid,parsePkhDid,canonicalizeDid,principalDid,principalDidEquals,makePkhSpaceId,PKH_DID_RE)