1. Choose an implementation profile
You do not need every protocol role to begin. Decide which observable behaviors your software intends to provide, then test those behaviors against the normative specifications.
| Profile | Minimum useful scope |
|---|---|
| Resolver | Parse did:sizuq, retrieve operation history, verify it, derive current state, and return defined resolution errors. |
| Resource client | Everything needed to resolve the identity root, plus sq: parsing, service selection, and safe dereferencing. |
| Controller / writer | Create key material and valid sequence-0 records, then construct and sign update, recovery, and deactivation operations. |
| Directory | Validate submitted records before acceptance, preserve accepted history, and expose the interoperable read representation. |
| Full application | Combine protocol capabilities with product-specific accounts, resources, moderation, UI, and storage. |
2. Implement deterministic primitives
Most interoperability failures will occur below the UI: canonical JSON, hashes, encodings, signature inputs, or state transition details. Keep these operations small, pure, and directly testable.
- JCS canonicalization. Use RFC 8785 JSON Canonicalization Scheme for every signed or hashed JSON payload required by the method.
- SHA-256. Hash exactly the canonical bytes required by the specification.
- Multibase base58btc. Preserve case. Reject malformed or non-canonical method-specific identifiers.
- Ed25519 Multikey. Version 0.1 fixes the signing suite rather than negotiating algorithms implicitly.
- Exact signing input. For creation and operation envelopes, canonicalize the record with the
proofmember omitted, as specified.
// conceptual only — follow the specification for exact fields
canonical = JCS(recordWithoutProof)
signature = Ed25519.sign(privateKey, UTF8(canonical))
record.proof.signatureMultibase = base58btc(signature)Do not rely on ordinary JSON.stringify output as a cryptographic serialization contract. Likewise, do not normalize identifiers, reorder arrays, coerce timestamps, or discard unknown bytes unless the specification explicitly requires it.
3. Build the resolver first
A resolver takes an identifier and operation history and produces either derived state or a defined failure. Keep transport separate from verification so the same verifier can consume records from a directory, mirror, fixture, or local cache.
resolve(did):
validateDidSyntax(did)
records = fetchOperations(did)
creation = records[0]
verifyGenesisDigest(did, creation.genesis)
verifyCreationProof(creation)
state = stateFromGenesis(creation.genesis)
previousDigest = digest(creation)
for record in records[1:]:
require(record.sequence == prior.sequence + 1)
require(record.previous == previousDigest)
verifyAuthorizedProof(record, state)
state = applyOperation(record.operation, state)
previousDigest = digest(record)
return projectDidDocument(state)The pseudocode is intentionally incomplete. The normative method specification decides exact field validation, allowed state transitions, key authority, deactivation semantics, and error names.
3.1 Fail closed
Return explicit errors for malformed identifiers, invalid genesis, invalid signatures, invalid sequence, conflicting history, and deactivation as defined by the method. Do not silently skip an operation and continue from a later one.
3.2 Keep network status distinct from identity status
A timeout, DNS failure, or unreachable directory is not notFound. The resolver API should preserve enough distinction for applications to decide whether to retry, use another mirror, show cached state, or report an identifier-level error.
3.3 Cache verified facts, not unchecked responses
If you cache history or derived state, retain the sequence and digest associated with the verified result. A later response that rolls back below previously verified state should be treated as suspicious rather than automatically replacing the cache.
4. Add creation and writing
4.1 Creation
Generate independent rotation and recovery key pairs. Construct the complete genesis payload, canonicalize it, compute its SHA-256 digest, encode that digest as multibase base58btc, and use it as the method-specific identifier. Then construct and sign the sequence-0 creation record.
Immediately pass the resulting record through your independent resolver/verifier. A writer should not consider an operation ready for submission merely because signing returned successfully.
4.2 Update
An update carries the complete next rotationKeys, verificationMethods, and services arrays. Version 0.1 deliberately does not use an implementation-defined patch format. Sign the envelope with a currently authorized rotation key and reference the immediately preceding accepted record.
4.3 Recovery
A recovery operation uses a currently authorized recovery key and supplies complete replacements for rotation keys, recovery keys, verification methods, and services. Treat this path as exceptional in product UX and key custody; it has stronger authority than an ordinary update.
4.4 Deactivation
Deactivation is recovery-authorized and terminal. After valid deactivation, the resolver must not derive an active identity from later operations.
5. Implement a directory
A directory is an append-oriented distribution role for signed operations. It must validate records before acceptance and preserve enough history for an independent resolver to reconstruct state.
5.1 Read profile
Version 0.1 defines an interoperable HTTPS read profile:
GET /.well-known/sizuq/did/{method-specific-id}/operations
Accept: application/jsonThe successful representation is the ordered JSON array beginning with the creation record and followed by accepted operation envelopes.
5.2 Write transport
The current method specification defines the validity of submitted operations but does not standardize a generic HTTP POST endpoint as part of the v0.1 interoperable read profile. An implementation can expose a product-specific write API, queue, or local interface without inventing a new normative endpoint. If a common write transport is standardized later, it should be documented explicitly rather than inferred from the read URL.
5.3 Acceptance discipline
Validate the DID/genesis relationship, authorization, predecessor, sequence, and state transition before appending a record. Preserve accepted record semantics exactly. Abuse controls may restrict who can submit or how often, but they must not alter what makes an accepted operation cryptographically valid.
6. Implement sq:
Keep parsing, identity resolution, service discovery, and HTTP dereferencing as separate steps. This makes it possible to test normalization independently and to swap resolver or transport implementations without changing the canonical URI.
dereference(uri):
parsed = parseSq(uri)
did = "did:sizuq:" + parsed.root
didResult = resolve(did)
if parsed.path is empty:
return identityRootResult(didResult)
service = selectSizuqResourceService(didResult)
response = fetchResource(service.endpoint, parsed.path)
return { canonicalUri: uri, representation: response }- Compare the URI scheme case-insensitively, but emit lowercase
sq. - Never lowercase the base58btc identity root.
- Preserve unknown path segments; generic clients should not invent application semantics.
- Keep the original
sq:URI as the canonical identifier through redirects and gateways. - Do not treat successful resource delivery as proof of identity control.
7. Harden network behavior
Correct cryptography does not make arbitrary network destinations safe. A DID controller can intentionally publish a service endpoint that targets infrastructure your server should never contact.
- Validate TLS for HTTPS endpoints.
- Apply SSRF defenses to server-side dereferencing, including local and link-local address policy.
- Set redirect, response-size, and timeout limits.
- Escape attacker-controlled paths before using them in HTML, logs, databases, or file-system operations.
- Do not send URI fragments to a remote service when they can be processed locally.
- Allow resolver and mirror configuration where the product’s threat model benefits from independent views.
Also separate telemetry from protocol correctness. Logging full DIDs or sq: URIs can create durable correlation data even when the protocol itself avoids embedding human-readable personal attributes.
8. Release checklist
- Your canonicalization output matches the published deterministic vectors byte-for-byte.
- Changing object insertion order does not change a correctly canonicalized signature input.
- Malformed base58btc, wrong digest lengths, bad signatures, broken hash links, and skipped sequences are rejected.
- Update and recovery authority are not interchangeable.
- Deactivation is terminal.
- Network failure is not reported as identifier
notFound. sq:parsing preserves root case and canonical identity through HTTP redirects.- Resource dereferencing applies untrusted-endpoint protections.
- Application-specific fields or policies do not silently change generic protocol semantics.
Next: Conformance & Test Vectors → · Normative detail: did:sizuq and sq:.