Protocol / Identity / Session Keys
shippedLayer 1 · Protocol

Session Keys

An ephemeral Ed25519 did:key the SDK generates and the owner delegates a scoped capability set to — so apps and agents hold the session key, never the owner key.

Session Keys

A session key is an ephemeral Ed25519 keypair, identified by a did:key:z6Mk… DID, that the SDK generates client-side and the owner DID delegates a scoped capability set to at sign-in. The app — or agent — holds the session key and signs every UCAN sub-delegation and invocation with it, so the owner's wallet key is signed with exactly once (the SIWE root grant) and never held by the application.

Role

Session keys are Layer 1 identity — the working half of the two-DID model. They are why TinyCloud's authority model is safe to embed in untrusted clients: the only thing an app possesses is a short-lived key whose entire reach is bounded by the capability set the owner granted it (see attenuation, trust-model). Compromising a session key exposes only its scoped grant for its remaining lifetime, never the root.

Shape

  • Key material: an Ed25519 JWK (crv: "Ed25519"), generated by JWK::generate_ed25519() in the WASM SessionManager (packages/sdk-rs/src/session/manager.rs). Each key carries a string key_id (the JWK kid); the default id is "default", renamed at sign-in to session-{timestamp}.
  • DID: DIDKey::generate(jwk)did:key:z6Mk…; the DID URL appends the key as a fragment: did:key:z6Mk…#z6Mk… (manager.rs:get_did). This DID is the audience of the owner's root delegation and the issuer of everything the session signs.
  • Lifetime: bounded by the session's SIWE/ReCap expirationTime (the SDK clamps sub-delegations to the session expiry; an expired session throws SessionExpiredError).

Mechanics

The WASM session manager

SessionManager (packages/sdk-rs/src/session/manager.rs, exposed to JS as TCWSessionManager) holds an in-memory HashMap<key_id, SessionInfo> of session keypairs plus the in-progress ReCap Capability. Its surface:

  • new() — generates the initial "default" Ed25519 key.
  • create_session_key(id) / import_session_key(jwk, id, override) — add keys; duplicate ids error unless overridden.
  • rename_session_key_id(old, new) — the SDK renames "default"session-{ts} per sign-in so each session has a distinct key (NodeUserAuthorization.signIn).
  • get_did(id) → the did:key…#… URL; jwk(id) → the serialized private JWK; list_session_keys().
  • add_targeted_actions(target, actions) / build(config, …) — accumulate ReCap abilities and emit the SIWE string the wallet signs.

The Ed25519 signing of sub-delegations and invocations happens in Rust/WASM (the upstream tinycloud-sdk-wasm Session); for raw encryption-network invocations the audience is re-signed in JS against the session JWK (rewriteInvocationAudience, see SDK map). The private key never crosses to the server — only the resulting signed UCAN / CACAO does.

Where it sits in sign-in

At sign-in the SDK: generates the session key → builds a SIWE-ReCap message naming the requested abilities with the did:key as delegatee → the wallet signs it → completeSessionSetup mints the session UCAN delegation header. From then on the session key is the principal the node sees.

Relationships

A session key is a did:key DID; it is the audience of the owner's root capability delegation (granted in SIWE/CACAO carrying a ReCap); it issues UCAN sub-delegations and invocations; it is generated/managed by the WASM session manager invoked during the sign-in-flow; its reach is bounded by attenuation; it is produced so apps never hold the owner key minted by OpenKey.

Example

Signing in to a notes app: the SDK generates did:key:z6MkpTHR… (renamed session-1718000000). The owner did:pkh:eip155:1:0xf39F…2266 signs one SIWE message delegating tinycloud.kv/{get,put,list,del,metadata} over tinycloud:pkh:eip155:1:0xf39F…2266:default/kv/ to that did:key. The app now signs each tinycloud.kv/put invocation with the session key; the wallet is never prompted again until the session expires. (See sign-in-flow, capabilities.)

Status & drift

Shipped. The session manager generates a fresh Ed25519 key per sign-in and is the only place app-side signing keys live. Note the did:key audience is case-sensitive (dids) — session-key matching does not checksum-canonicalize the way did:pkh does, so the exact base58 string must round-trip.

Sources

  • js-sdk: packages/sdk-rs/src/session/manager.rs (SessionManager/TCWSessionManager: key gen, get_did, jwk, build, add_targeted_actions), packages/sdk-core/src/identity.ts (did:key canonicalization), packages/node-sdk/src/authorization/NodeUserAuthorization.ts (renameSessionKeyId, session expiry clamping)