kyaid

@kya-os/id / passkey-bound identity for the agent era

You already have a wallet.

It's the passkey on your phone. One touch turns it into a DID. One more gives your agent a signed, scoped, revocable permission slip, and the app it talks to just checks the math.

Makes a passkey called kya.id demo in your password manager. Every key on this page stays in this tab.

did waiting for a touch
backendnone
private keynone

Everything below runs @kya-os/id in your browser. Nothing you make leaves this tab.

01the problemAuthorization: Bearer

An access token can't prove who approved it.

An agent shows up with an access token. It can carry a subject, a scope and an expiry, but all of it is your authorization server's word: the server signed it, the user never did. A delegation is signed by the user's own key, so any server can check who approved what, and until when, without trusting whoever issued it.

OAuth access token

eyJhbGciOiJSUzI1NiIs…
Who approved it
sub: user_123, on the server's word
For what
notes.write
Where
if your scopes encode it
Until
exp, about an hour
Still valid
ask the server
Signed by the user
no
Who can check it
whoever trusts your server's keys

delegation credential VC-JWT

eyJhbGciOiJFZERTQSIs…
Who approved it
did:key:z6Mk… (you)
For what
vault:write
Where
notes/ and nothing beside it
Until
8 hours from your touch
Still valid
one bit in a signed public list
Signed by the user
yes: Ed25519, from your passkey
Who can check it
anyone, against your DID

Both are bearer credentials: whoever holds one can present it. Audience pinning, short lifetimes and one-bit revocation limit what a stolen one can do. Binding each call to the agent's own key is the next step: the router already verifies an agent's per-request proof, but doesn't require it yet.

02key@kya-os/id/wallet

One touch. One DID.

WebAuthn's PRF extension asks your passkey for a secret only it can compute. The wallet turns that secret into an Ed25519 key, signs with it, and lets it go. Your DID is the public half, written down.

  1. 1

    Passkey

    kya.id demo

    Lives in your password manager. Syncs where it syncs.

  2. 2

    PRF

    salt = sha256("kya-os-id-wallet/v1")

    Same passkey, same salt, same answer. Every time.

  3. 3

    Seed

    32 bytes. Exists for one signature, then goes out of scope. This page can't show it to you.

  4. 4

    Ed25519

    public key

    The only half anything stores.

  5. 5

    did:key

    did:key:z6Mk…

    Resolvable anywhere, no network, no registry.

Touch it again.

Nothing was stored but the public half. Ask the passkey for the same secret and the same DID comes back, on this device or any device your passkey syncs to.

// make a DID above, then poke at it here

04verify@kya-os/id/server

Your server checks the math.

This is verifyDelegationOnly(), the middleware Hobbsidian's MCP endpoint runs, running in your tab against your credential. Send the honest call. Then try to break in.

POST https://kya.id/api/mcp
authorization: Bearer mint a credential first
tools/call vault_write { "path": "notes/today.md" }
  1. 01bearera credential is presented
  2. 02parseVC-JWT envelope and schema
  3. 03windowissued before now, expires after
  4. 04signatureEd25519 against the issuer's DID
  5. 05statussigned list, then one bit
  6. 06audiencepinned to this server
  7. 07capabilitythe router: tool needs it
  8. 08scopethe tool: path inside the grant

no response yet

Try to break in.

0 of 6 tried / 0 got in

Stages 01 to 06 are one call to verifyDelegationOnly(). Stage 07 is the MCP router's capability gate, credentialHasCapability(). Stage 08 is the tool's own path check, matchesCrispScope(), both from @kya-os/id/enforce.

05step-up@kya-os/id/mcp

Read-only meets a write tool.

Most auth dead-ends here. createMcpRouter answers with a consent link and a resume token instead. You approve the bigger grant with one touch, and the agent's retry goes through.

agent session / replay of the router's wire format

    06revokeBitstring Status List v1.0

    One bit, pulled.

    Every credential carries a bit in a signed public list of 131,072. The verifier refuses any credential without one. Your bit sits at a random index, so the list never says whose is whose.

    list
    https://kya.id/status/1
    size
    131,072 bits / 16,384 bytes raw
    on the wire
    gzip, then signed
    your bit
    mint a credential first
    signed by
    the hub
    proof
    eddsa-jcs-2022

    07deviceslinkDevice()

    Lose a laptop, not your identity.

    Not a bag of equal keys. One portable root, and every device holds its own key under a root-to-device delegation. A device is an agent the account delegated to itself: same credential, same verifier, same revocation.

    account root did:web:hub:u:you passkey-PRF key / DID document: unchanged
    Phonelive
    Laptoplive
    Work browserlive

    3 devices, 3 delegations, 1 root key

    Why delegation, not a key list
    NeedKey listDelegation
    Add a deviceedit the DID documentroot signs a delegation
    Revoke a deviceedit the DID documentflip one bit
    Limit a devicenot expressiblescope it, for free
    Audit which devicekeys are anonymouseach device is a subject
    Verifya new code paththe agent path, unchanged

    08buildpnpm add @kya-os/id better-auth

    Stop being the identity provider.

    Every SaaS auth stack makes your app the place where identity lives, so your database is where the secrets are. Move the signing to the user and the checking to your server, and there's nothing left worth stealing.

    your app as an identity provider

    • users.password_hashargon2id$v=19$m=65536…
    • sessions.tokens3ss_9f1c…e20a
    • oauth.refresh_tokenrt_0b77…41cc
    • api_keys.secretsk_live_8f2c…a91d
    • jwks.private_key-----BEGIN PRIVATE KEY-----

    your app as a verifier

    • kya_os_user_did.diddid:key:z6Mk…
    • kya_os_user_did.public_keyz6Mk…
    • device_delegation.jwtsigned, scoped, public
    • status_list.bits131,072 bits, public
    • private keysnone

    
              

    Eleven entry points. One per concern.

      09standardsnot inventions

      Standards, not inventions.

      Built on @kya-os/mcp, the reference implementation of KYA-OS (formerly MCP-I) at the Decentralized Identity Foundation. Anything in @kya-os/id that duplicates the protocol is treated as a bug.

      10in the wildwhere it runs

      Born in a real app.

      Your users already have the key.

      Give their agents a permission slip instead of a password.