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.
pip install git+https://github.com/waldiez/swid
swid create --hstp https://hstp.example/entity/abcThe 32-byte private seed is written to
keyFile(0600perms) — never printed to stdout. Choose the path with--key-out; pass that file toswid update/swid deactivatevia--key. Add--out genesis.jsonto also save the genesis event.
A self-certifying, CEL-anchored, registry-backed DID method.
- Syntax:
did:swid:z<scid>—zis the multibase base58-btc prefix;scidis 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; theoperationwrapper 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;Multikeyverification method;authentication;HSTPEndpointservice. - CRUD via a SWID Registry with signed CEL events; resolution replays the chain to produce the current document.
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)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)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://...
createmints locally (it never contacts a registry) —registeris the step that puts the genesis on a server soresolve --registrycan find it. To resolve without a server, useresolve --log genesis.json.
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 8000swid/
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
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
versionIdis just the log length. If rollback matters for your application, pin the highestversionIdyou 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 andVaultSigner); there is no in-band recovery path.
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,server]"
make full # lint + typecheck + pylint + testOr 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 aboveThe 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:
- SWF-STD-5 (
did:swidmethod) — https://github.com/spatial-web-foundation/swf-std-5-did-swid-spec (v0.1 draft, 2025) - SWF-STD-4 (SWID Documents — requires an
HSTPEndpointservice) — https://github.com/spatial-web-foundation/swf-std-4-swid-document-spec (v0.1 draft, 2025)
Note: the specs also render on GitHub Pages — STD-5 and STD-4 (the older
.../did-swid-specpath 404s). SWF-STD-3 (HSTP) has no public repo (PDF only) and is not implemented here;did:swidonly requires the document to carry anHSTPEndpointURL, which this library enforces.
Open standards the SWF profile builds on, which the tests/hardening suites also validate against:
- W3C DID Core 1.1 (document shapes, DID URL fragments): https://www.w3.org/TR/did-1.1/
- W3C
eddsa-jcs-2022Data Integrity (the proof construction): https://www.w3.org/TR/vc-di-eddsa/ - RFC 8785 JCS (JSON canonicalization used for hashing/signing): https://www.rfc-editor.org/rfc/rfc8785
- W3C CCG CEL (the Cryptographic Event Log): https://w3c-ccg.github.io/cel-spec/
- DIF did-registration (endpoint shapes): https://identity.foundation/did-registration/
See SPECIFICATIONS.md for the full citation map (which §
maps to what, and which claims rest on the W3C layers vs. the SWF profile).
{ "did": "did:swid:zQmVXokKqUqKqb7DC5P1BGANgJjeKJ2AjPCCC5ofpPjSRj4", "document": { "id": "did:swid:z…", "verificationMethod": [ … ], "service": [ … ] }, "celGenesis": { "operation": { "type": "create", "data": { … , "proof": { … } } } }, "keyFile": "did_swid_zQmVXok….key" }