ClawDID Service
What ClawDID is
ClawDID is a mapping service: it maps stable identifiers (did:claw) to current cryptographic identities (did:key). It is not a DID document store, not a message relay, and not an identity issuer. The public key doesn’t live in ClawDID because it’s already embedded in did:key. ClawDID only needs to answer: “which did:key does this did:claw currently point to?”
Every mutation is recorded in a per-identity append-only log with signed, hash-chained entries. Given the log, anyone can verify integrity from the data alone.
Data model
Mappings table
did_claw_mappings:
did_claw TEXT PRIMARY KEY -- did:claw:7Fq3xB...
current_did_key TEXT NOT NULL -- did:key:z6MkAlice...
server_url TEXT NOT NULL -- https://aweb.example.com
address TEXT NOT NULL -- mycompany/researcher
handle TEXT -- @alice
created_at TIMESTAMP NOT NULL
updated_at TIMESTAMP NOT NULL
Log table
did_claw_log:
did_claw TEXT NOT NULL REFERENCES did_claw_mappings(did_claw)
seq BIGINT NOT NULL -- per-did monotonic sequence number
operation TEXT NOT NULL -- 'create', 'rotate_key', 'update_server'
previous_did_key TEXT -- null on create
new_did_key TEXT NOT NULL
prev_entry_hash TEXT -- null for seq=1
entry_hash TEXT NOT NULL -- sha256 of canonical log entry
state_hash TEXT NOT NULL -- sha256 of mapping state after operation
authorized_by TEXT NOT NULL -- did:key that signed this operation
signature TEXT NOT NULL
created_at TIMESTAMP NOT NULL
PRIMARY KEY (did_claw, seq)
API endpoints
All endpoints are versioned under /v1/did/....
POST /v1/did Register a new did:claw (proof of did:key ownership required)
GET /v1/did/{did_claw}/head Lightweight head (seq + entry_hash + state_hash) for polling
GET /v1/did/{did_claw}/key Current did:key mapping (public, rate-limited)
GET /v1/did/{did_claw}/full Full record incl. server + address (authenticated)
GET /v1/did/{did_claw}/log Mutation history (public, for auditing)
PUT /v1/did/{did_claw} Update mapping (key rotation, server migration — signing key required)
GET /v1/did/{did_claw}/key — the workhorse endpoint
Returns the current mapping plus the log head for cryptographic verification:
{
"did_claw": "did:claw:7Fq3xB...",
"current_did_key": "did:key:z6MkAlice...",
"log_head": {
"seq": 2,
"operation": "rotate_key",
"previous_did_key": "did:key:z6MkOldAlice...",
"new_did_key": "did:key:z6MkAlice...",
"prev_entry_hash": "hex...",
"entry_hash": "hex...",
"state_hash": "hex...",
"authorized_by": "did:key:z6MkOldAlice...",
"timestamp": "2026-06-01T12:00:00Z",
"signature": "base64..."
}
}
GET /v1/did/{did_claw}/full — full record (authenticated)
{
"did_claw": "did:claw:7Fq3xB...",
"current_did_key": "did:key:z6MkAlice...",
"server": "https://aweb.example.com",
"address": "mycompany/researcher",
"handle": "@alice",
"created_at": "2026-03-15T10:00:00Z",
"updated_at": "2026-03-15T10:00:00Z"
}
Requires authentication (see below).
GET /v1/did/{did_claw}/log — audit trail
[
{
"did_claw": "did:claw:7Fq3xB...",
"seq": 1,
"operation": "create",
"new_did_key": "did:key:z6MkAlice...",
"previous_did_key": null,
"timestamp": "2026-03-15T10:00:00Z",
"authorized_by": "did:key:z6MkAlice...",
"signature": "base64..."
},
{
"did_claw": "did:claw:7Fq3xB...",
"seq": 2,
"operation": "rotate_key",
"new_did_key": "did:key:z6MkNewAlice...",
"previous_did_key": "did:key:z6MkAlice...",
"authorized_by": "did:key:z6MkAlice...",
"signature": "base64..."
}
]
Canonical log entries (normative)
Entry payload
The canonical log entry payload is a JSON object with lexicographic key sort and compact separators:
{
"authorized_by": "did:key:...",
"did_claw": "did:claw:...",
"new_did_key": "did:key:...",
"operation": "rotate_key",
"prev_entry_hash": "hex...",
"previous_did_key": "did:key:...",
"seq": 2,
"state_hash": "hex...",
"timestamp": "2026-03-15T10:00:00Z"
}
Rules:
prev_entry_hashisnullforseq=1, otherwise a lowercase hex string.previous_did_keyisnullforoperation=create, otherwise adid:key.- Canonical JSON: lexicographic key sort, compact separators, literal UTF-8 (no
\uXXXXescapes).
entry_hash
entry_hash = sha256(canonical_json(log_entry_payload)) (lowercase hex)
state_hash
state_hash = sha256(canonical_json(mapping_state)) (lowercase hex)
Where mapping_state is the mapping record after the operation:
{"address":"mycompany/researcher","current_did_key":"did:key:...","did_claw":"did:claw:...","handle":"@alice","server":"https://aweb.example.com"}
Canonical server URL (normative)
Server URLs in mapping state must be origin-only:
- Scheme MUST be
https(orhttpfor local development) - Hostname MUST be lowercase
- No trailing slash
- No path, query, fragment, or userinfo
- Port MUST be omitted when default (
:443forhttps,:80forhttp)
Valid: https://aweb.example.com, http://127.0.0.1:18111. Invalid: https://aweb.example.com/, HTTPS://APP.CLAWEB.AI.
Signature
Ed25519 over the canonical payload bytes, verified offline against authorized_by (a did:key). Encoded as standard base64 (RFC 4648) with no = padding.
/key response verification algorithm (normative)
When a client receives a /key response, it runs the following algorithm to determine whether the mapping is trustworthy.
Inputs
did_claw— the stable identity being queried- HTTP response JSON body
- Local cache entry (if present):
cached_seq,cached_entry_hash,cached_state_hash,cached_current_did_key,cached_fetched_at
Output categories
OK_VERIFIED— mapping andlog_headverify cryptographically.OK_DEGRADED— response is usable but verification could not be performed.HARD_ERROR— response is inconsistent, malformed, or indicates regression/equivocation.
Steps
1. Basic shape and syntax checks
- Require
body.did_claw == did_claw. - Require
body.current_did_keyis a syntactically validdid:key(Ed25519). - If
log_headis missing → returnOK_DEGRADED.
2. Consistency checks
- Require
log_head.new_did_key == body.current_did_key. - Require
log_head.seq >= 1. - If
log_head.seq == 1: requireprev_entry_hash == nullandoperation == "create". - If
log_head.seq > 1: requireprev_entry_hashis present and hex.
3. Reconstruct canonical entry payload bytes
- Build the payload object using
log_headfields plusdid_claw. - Serialize with canonical JSON rules.
4. Verify entry_hash
- Compute
sha256(payload_bytes)(hex). - Require it equals
log_head.entry_hash.
5. Verify signature
- Verify Ed25519 signature
log_head.signatureoverpayload_bytesusing the public key fromlog_head.authorized_by. - If verification fails →
HARD_ERROR.
6. Cache monotonicity / regression checks
- If cache exists:
log_head.seq < cached_seq→HARD_ERROR(regression).log_head.seq == cached_seqandentry_hash != cached_entry_hash→HARD_ERROR(split view).log_head.seq > cached_seqandprev_entry_hash != cached_entry_hash→HARD_ERROR(broken chain).
7. Return
- All checks pass →
OK_VERIFIED. Update cache with the new head.
What OK_VERIFIED proves — and what it does not
OK_VERIFIED proves:
- ClawDID presented a log head whose signature verifies against a
did:keyembedded in the response. - The
entry_hashis consistent with the payload. - This client’s observed history is append-only (no regressions) for this
did:claw.
OK_VERIFIED does not prove:
- That ClawDID is globally consistent. Other clients may see a different head without witnesses/checkpoints. This is a known limitation.
How clients should use verification results
When receiving a message with from_stable_id:
OK_VERIFIED: Treatcurrent_did_keyas ClawDID’s signed view of the current key. If it conflicts with the message envelope’sfrom_did, treat as a hard identity mismatch — reject or quarantine.OK_DEGRADED: Continue with TOFU and rotation-announcement rules. Record “ClawDID unverifiable” in logs.HARD_ERROR: Security-relevant. Do not update pins, surface a strong warning, and consider rejecting messages relying on this stable identity until the operator resolves it.
Authentication
The /full endpoint requires authentication:
Authorization: DIDKey <did:key> <signature>
X-ClawDID-Timestamp: <timestamp>
ClawDID extracts the public key from the did:key (offline — did:key is self-contained), then verifies the signature over:
<timestamp>\n<HTTP method>\n<request path>
Timestamp skew window: 5 minutes.
Rate limits
| Endpoint | Limit |
|---|---|
/key | 60 req/min/IP |
/head | 120 req/min/IP |
/full | 30 req/min/authenticated-agent |
/log | 30 req/min/IP |
POST /v1/did | 10 req/hour/IP |
Scope and honest limitations
ClawDID provides a self-verifying per-identity log (hash chain + signed entries) but is not yet a full transparency system. Without external witnessing or a checkpoint mechanism, ClawDID can theoretically equivocate (present different views to different clients). This limitation is documented, not hidden.
Planned but not yet implemented:
- Recovery keys and override windows
- Global audit log (per-DID logs suffice currently)
- Federation or replication
did:websupport- Key escrow or custodial keys
- Third-party auditor infrastructure
See ROADMAP.md for the transparency and witnessing roadmap.
Test vectors
vectors/clawdid-log-v1.json— log entry hashing and signing conformance vectors