Skip to content

waldiez/swid

waldiez-swid

license

SWID logo

An implementation of the Spatial Web did:swid DID method (SWF-STD-5): generation, resolution, verification, and lifecycle (update / deactivate) for Spatial Web Identifiers. Built and tested against the public SWF-STD-4/5 draft specs and the open W3C/IETF standards they compose (DID Core, eddsa-jcs-2022 Data Integrity, RFC 8785 JCS, the CEL) — see Specs.

Quickstart

pip install git+https://github.com/waldiez/swid
swid create --hstp https://hstp.example/entity/abc
{
  "did": "did:swid:zQmVXokKqUqKqb7DC5P1BGANgJjeKJ2AjPCCC5ofpPjSRj4",
  "document":   { "id": "did:swid:z…", "verificationMethod": [  ], "service": [  ] },
  "celGenesis": { "operation": { "type": "create", "data": {  , "proof": {  } } } },
  "keyFile": "did_swid_zQmVXok….key"
}

The 32-byte private seed is written to keyFile (0600 perms) — never printed to stdout. Choose the path with --key-out; pass that file to swid update / swid deactivate via --key. Add --out genesis.json to also save the genesis event.

What a did:swid is (from SWF-STD-5)

A self-certifying, CEL-anchored, registry-backed DID method.

  • Syntax: did:swid:z<scid>z is the multibase base58-btc prefix; scid is 46 base58-btc characters.
  • SCID: multibase(base58btc, multihash(sha2-256, JCS(genesis_document))) — the digest is over the initial DID document (operation.data) before the proof is added, with the "did:swid:" placeholder still in place; the operation wrapper and the proof are not hashed. The SCID is then substituted for the placeholder. (Per the Digital Bazaar CEL DID-Document profile.)
  • DID Document (MUST): did/v1.1 + SWF DID contexts; Multikey verification method; authentication; HSTPEndpoint service.
  • CRUD via a SWID Registry with signed CEL events; resolution replays the chain to produce the current document.

Public API

import swid

# Create
res = swid.create("https://hstp.example/entity/abc")
# res.did, res.document, res.keypair, res.cel_genesis

# Verify self-certification
swid.verify(res.did, res.cel_genesis)  # True

# Register (with remote or local registry)
from swid import RemoteSWIDRegistry, InMemoryRegistry
reg = RemoteSWIDRegistry("http://localhost:8000")
did = reg.register([res.cel_genesis])

# Resolve
result = swid.resolve(did, reg)
# result.did_document, result.did_resolution_metadata, result.did_document_metadata

# Update (key rotation)
swid.update(did, new_verification_method, signer=res.keypair, registry=reg)

# Deactivate
swid.deactivate(did, signer=new_key, registry=reg)

Async

Every entry point has an _async counterpart, plus AsyncInMemoryRegistry / AsyncRemoteSWIDRegistry. The I/O ops (resolve_async, update_async, deactivate_async) await the registry directly; the CPU-bound ones (create_async, verify_async) run off the event loop via asyncio.to_thread.

import swid
from swid import AsyncRemoteSWIDRegistry

async with AsyncRemoteSWIDRegistry("http://localhost:8000") as reg:
    res = await swid.create_async("https://hstp.example/entity/abc")
    did = await reg.register([res.cel_genesis])
    result = await swid.resolve_async(did, reg)
    await swid.update_async(did, new_verification_method, signer=res.keypair, registry=reg)
    await swid.deactivate_async(did, signer=new_key, registry=reg)

CLI

swid create --hstp https://hstp.example/entity/abc --out genesis.json
swid verify did:swid:z... --genesis genesis.json
swid register --genesis genesis.json --registry http://localhost:8000   # push it to a registry
swid resolve did:swid:z... --registry http://localhost:8000             # (or: --log genesis.json, offline)
swid update did:swid:z... --vm '{"id":"#keys-2","type":"Multikey",...}' --key seed.bin --registry http://...
swid deactivate did:swid:z... --key seed.bin --registry http://...

create mints locally (it never contacts a registry) — register is the step that puts the genesis on a server so resolve --registry can find it. To resolve without a server, use resolve --log genesis.json.

Reference registry server (swid_server) — optional

The library needs no server — it works with any SWIDRegistry (e.g. InMemoryRegistry). swid_server is an optional FastAPI wrapper kept for three reasons: it demonstrates a spec-shaped SWID Registry end-to-end, it is the HTTP counterpart that RemoteSWIDRegistry (and its integration tests) talk to, and it is a standalone/deployable reference registry. It is not required by, nor the registry of, any particular consuming application.

Endpoint Exactly what it is
GET /identifiers/{did} DID Resolution (W3C/DIF shape; STD-5 §Read)
GET /logs/{did} Raw CEL as STD-5 §6 log entries ({"log":[{"event":…}]})
POST /register Persist a pre-built genesis CEL; DIF-shaped response (not the full DIF create flow)
POST /{did}/log Append a signed CEL event (convenience, not a standard DIF endpoint)
GET /health Liveness (not spec-defined)
GET /.well-known/swid Non-normative, off unless configured — a project-invented domain-verification sketch, not SWF HSTP (STD-3)
pip install "waldiez-swid[server] @ git+https://github.com/waldiez/swid"
uvicorn swid_server.app:create_app --factory --port 8000

Architecture

swid/
  scid.py       did:swid:z<scid> codec
  document.py   SWF DID-document profile builder + validator
  keys.py       Ed25519 keygen, Multikey, sign/verify, Signer protocol
  keystore/     KeyStore protocol + backends: in-memory, file, encrypted file
                (scrypt+AES-GCM), env/mounted-secret; VaultSigner (HashiCorp
                Vault Transit — key never leaves Vault; `[vault]` extra)
  cel.py        Cryptographic Event Log: genesis / append / verify / replay
  registry.py   SWIDRegistry protocol, InMemoryRegistry, RemoteSWIDRegistry
  resolver.py   DIF Resolution: fetch CEL → verify → replay → result
  cli.py        CLI tool
  __init__.py   Public API surface
tests/          pytest suite + crypto hardening battery

Residual trust assumptions

Chain verification makes a resolved document authentic — every event is signed by the then-current controller key, the SCID binds the genesis, and a registry cannot forge or alter any event. Two things it does not give you, stated plainly:

  • Completeness / freshness. A registry (or an attacker between you and it) can serve a truncated CEL — hiding a later key rotation or a deactivation — and the truncated log still verifies. There is no witnessing or anchoring layer in SWF-STD-5 as drafted, and versionId is just the log length. If rollback matters for your application, pin the highest versionId you have seen per DID and treat a regression as suspect, or resolve through more than one registry and compare.
  • Key-compromise recovery. The controller model is single-key with no pre-rotation commitment (no next-key hash à la KERI/did:webvh). An attacker holding the current key can rotate the legitimate controller out or deactivate the DID — both irrevocably, since deactivation is terminal. Protect the current key accordingly (see the keystores and VaultSigner); there is no in-band recovery path.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,server]"
make full    # lint + typecheck + pylint + test

Or individually:

make test       # pytest --cov
make lint       # ruff check
make lint-fix   # ruff check --fix
make typecheck  # basedpyright
make pylint     # pylint
make full       # all of the above

Specs

The SWF-STD-4/5 specs are public, open-source AsciiDoc in the Spatial Web Foundation GitHub org — that is the source we implement and cite against, so the § citations throughout this code are verifiable:

Note: the specs also render on GitHub Pages — STD-5 and STD-4 (the older .../did-swid-spec path 404s). SWF-STD-3 (HSTP) has no public repo (PDF only) and is not implemented here; did:swid only requires the document to carry an HSTPEndpoint URL, which this library enforces.

Open standards the SWF profile builds on, which the tests/hardening suites also validate against:

See SPECIFICATIONS.md for the full citation map (which § maps to what, and which claims rest on the W3C layers vs. the SWF profile).

About

Python implementation of the did:swid DID method (SWF-STD-5); self-certifying Spatial Web identifiers, verifiable via a Cryptographic Event Log.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages