JLINC DID Method Specification
version 2 - December 10, 2025
Abstract
This document specifies a method for creating and editing Decentralized IDs (DIDs). It conforms to the requirements specified in the DID specification currently published by the W3C Credentials Community Group.
Our goal is to provide a DID method that uses an open federated resolution service relying on the ActivityPub protocol. It provides for key rotation and agent delegation. We achieve compactness by relying on convention over configuration, with key types, hashing algorithms and signing procedures defined in the version specification, so as to eliminate the need for verbose and redundant stanzas in the individual DID documents.
We acknowledge with gratitude ideas from the ActivityPub community, as well as from did:web and KERI that partly inspired this effort.
Copyright Notice
© Portable Data Corporation 2025
Table of Contents
- Notation and Conventions
- Method Name
2.1 Format - Schema
3.1. verificationMethod object
3.2. service object
3.3. capabilityDelegation object
3.4. proof object - Example
- Operations
5.1. Create
5.2. Resolve
5.3. History
5.4. Update
5.5. Rotate
5.6. Deactivate - Security Considerations
- Privacy Considerations
1. Notation and Conventions
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
2. Method Name
The namestring that shall identify the JLINC DID method is: jlinc.
A DID that uses this method MUST begin with the following prefix: did:jlinc. Per the DID specification, this string MUST be in lowercase.
2.1 Format
The JLINC DID has the following ABNF format:
jlinc-did = "did:jlinc:" host ":" id-string
host = * host-char
id-string = 1* idchar
host-char = ALPHA / DIGIT / "-" / "."
idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded
pct-encoded = "%" HEXDIG HEXDIG
The idchar consists of the characters in the BASE64 UrlSafe character set defined in RFC 4648 as base64url without padding.
3. Schema
id: Identifies the subject of the DID. Format must be did:jlinc:{host}:{idstring} where {host} is the URI of the DID’s “home” resolver on inception, and {idstring} is a globally unique value generated by a SHA256 hash of the inception controller’s public key concatenated with the recovery hash in base64url format (defined in RFC 4648 as base64url without padding).
versionId: A monotonically increasing integer, initially 1, indicating the updating history of this DID.
@context: An array of JSON-LD context URIs, the first one of which MUST be the DID core context, currently “https://www.w3.org/ns/did/v1", and one of which MUST be the current JLINC DID method context, currently “https://didspec.jlinc.io/v2/ctx.jsonld".
created: A string representing the date and time the DID document was created, as an ISO 8601 formatted timestamp.
updated: A string representing the date and time the DID document was last updated, as an ISO 8601 formatted timestamp.
deactivated: A string representing the date and time the DID document was last deactivated, as an ISO 8601 formatted timestamp.
shortName: A local name in the format {name}@{resolver-hostname}, unique within the resolver-hostname namespace, which via resolver federation may become a permanent pointer to the globally unique DID id. *
recoveryHash: A SHA256 hash in base64url format of an ed25519 public key that the DID controller can use to verify key rotation, even if the current private key(s) have been lost or compromised. *
verificationMethod: An array of verificationMethod objects that define the valid controller public keys.
service: An array of service objects that define services that this DID subject wishes to publish.
capabilityDelegation: An array of capability delegation objects that define entities to which this DID subject is delegating particular authorities.
proof: A verifiable-credentials type proof object representing a signature by one of the controller keys over this instance of the DID document.
3.1. verificationMethod object
id: The id of this verification method. May be a relative URI, for example “#iPad1” in which case it MUST be expanded to the base URI value of the DID that is associated with the DID subject.
type: The type of this verification method, for example “device”.
controller: The DID URI of the controller of this verification method.
key: An ed25519 public key in base64url format. *
3.2. service object
id: The id of this service. May be a relative URI, for example “#login” in which case it MUST be expanded to the base URI value of the DID that is associated with the DID subject.
type: The type of service.
serviceEndpoint: URI of the service provider.
3.3. capabilityDelegation object
id: The id of this capabilityDelegation method. May be a relative URI, for example “#agent1” in which case it MUST be expanded to the base URI value of the DID that is associated with the DID subject.
type: The type of this capability delegation.
controller: The DID URI of the controller of this capability delegation.
archiveServers: A JLINC protocol specific array of archive server URIs. **
3.4. proof object
type: The string “JWS/CT”. *
created: An ISO 8601 formatted timestamp string.
verificationMethod: The URI of the verification method. May be a relative URI.
jws: A signature over this instance of the DID document using the JWS Clear Text JSON Signature Option (JWS/CT) method.
Notes:
* Indicates a method specific extension to the core DID protocol.
** Indicates a JLINC protocol specific DID protocol extension
4. Example
{
"id":"did:jlinc:did.domain.ext:R0uTFY292h1KmNiu6AIsMqCPmpO8RbiQwJ5IiveeVZc",
"versionId":1,
"@context":[
"https://www.w3.org/ns/did/v1",
"https://didspec.jlinc.io/v2/ctx.jsonld"
],
"verificationMethod":
[
{
"id":"#6e424a08be4dbbf96ee6bcc4245ad9c6",
"type":"device",
"controller":"did:jlinc:did.domain.ext:R0uTFY292h1KmNiu6AIsMqCPmpO8RbiQwJ5IiveeVZc",
"key":"Ls7mIZUevU9grWCzcwSNC1wvze0YFdY4GzIhWqSgDZ4"
}
],
"capabilityDelegation":[],
"service":[
{
"id": "did:jlinc:fedid.domain.ext:Wkun177yav6Jm4WVAsEbk6WFHKAMjkE-F_nsbeDYGAg",
"type": "login",
"serviceEndpoint": "https://fedid.domain.ext",
}
],
"created":"2024-11-20T01:18:24Z",
"updated":"2024-11-20T01:18:24Z",
"deactivated":false,
"shortName":"theuser@domain.ext",
"recoveryHash":"dyS_9O6y1vk3M56_d9fLC_sv5G4p1nRETxywlxD9KOY",
"proof": {
"type": "JWS/CT",
"created": "2024-11-20T01:18:28Z",
"proofPurpose": "controllerVerification",
"verificationMethod": "#6e424a08be4dbbf96ee6bcc4245ad9c6",
"jws":
}
}
5. Operations
Version 2.0.0 of this DID method requires signing and controller keys be of type Ed25519. Hashes must be of type SHA256. All hashes and keys are represented in base64url format. The ID string of the DID is formed as follows:
IDstring = hash(JSON.stringify([shortName, control, recoveryHash]))
That is, a hash of a JSON array consisting of the shortName, public key of the controller of the DID, and the recoveryHash.
5.1. Create
A new DID is created by the controlling entity of the DID providing a shortName, public key, and recoveryHash to a DID resolver.
Typically this is accompished by a software client under the controller’s command communicating with the resolver by transmitting (e.g. using an HTTPS POST request) a JSON object to the resolver’s /did/create
API target, as shown below.
{
"shortName": "user@domain.ext",
"control": "...",
"recoveryHash": "..."
}
The shortName
value must be include a name that is unique within the origin resolver’s URI namespace in the format {name}@{domain}
. If the submitted name is not unique the resolver will return an error message.
The control
value is the Public Key portion of an Ed25519 keypair, encoded as a base64url string. The submitter must retain the Secret Key portion of the keypair, and make every effort to keep it safely stored in a secure manner.
The recoveryHash
value is a SHA256 hash of the Public Key portion of another Ed25519 keypair. The controller SHOULD be encouraged to store that keypair safely, preferably offline, as it is the only way to accomplish key rotation and/or recover control of the DID in the event of a security compromise.
On success the resolver will return a response containing the newly created DID document (see example above), in the form:
{
"success": true,
"data": { "didDoc": { ... } }
}
If an error occurs preventing the creating of the new DID, an error message will be returned, in the form:
{
"success": false,
"error": "error message string"
}
5.2. Resolve
DID resolution is accomplished by first constructing a URI via one of the two operations:
a. If using the full DID id, assemble the URI from the DID host and id-string as
https://{resolverUri}/{host]:{id-string}
b. If using the short name, i.e. name@host, assemble the URI from the host and name as https://{resolverUri}/{name@host}
Transmit a GET request to the resolver with that resolution URI.
If successful the resolver will return an HTTP status 200 with a body containing the full DID document as a JSON string.
Is the requested DID cannot be retrieved, the resolver will return an HTTP status of 404 and no body.
In the event of an error the resolver will return an HTTP status in the 400 or 500 range as appropriate.
A request for the full DID id MAY append a relative URI string in the form #{relative-uri}
, in which case the resolver will return just the portion of the DID document with that ID.
For illustration, regarding the above example a GET request to (ignoring line breaks)
https://did.domain.ext:R0uTFY292h1KmNiu6AIsMqCPmpO8RbiQwJ5IiveeVZc#6e424a08be4dbbf96ee6bcc4245ad9c6
would return just:
{
"id":"#6e424a08be4dbbf96ee6bcc4245ad9c6",
"type":"device",
"controller":"did:jlinc:did.domain.ext:R0uTFY292h1KmNiu6AIsMqCPmpO8RbiQwJ5IiveeVZc",
"key":"Ls7mIZUevU9grWCzcwSNC1wvze0YFdY4GzIhWqSgDZ4"
}
TBD: Is there a use case for returning an HTTP redirect?
5.3. History
Construct a GET request to the resolver’s domain as elucidated in the above resolve method, excepting the path must be prepended with /did/history
and any relative URI appended will be ignored.
On success will return an ordered array of the published DID documents for this DID, starting with the intial instance, and then one for each update in creation order. This provides a chain of verifiable signatures for this DID serving to anchor the authenticity of the latest update.
5.4 Update
Send a POST request to the resolver URI with the path /did/update
. The request data MUST be a complete DID document containing the current DID document edited with the desired changes.
The new document MUST be well-formed according to the specifics of this DID method, and MUST contain the current versionId
incremented by one, a current updated
date string within the resolver’s configuration for the tolerance of “current”, and a new proof
object signed by one of the existing controller keys. An update signed by a key that is also new to this update will not be accepted. The recoveryHash
value MUST NOT be altered.
On success the new version of the DID document will be returned, and on error a JSON string containing an error explanation will be returned, and the DID document will not be updated.
5.5 Rotate
Send a POST request to the resolver URI with the path /did/rotate
. The request data MUST include the DID id, the public key portion of the recovery key in base64url format, and a SHA256 hash of a new recovery key (generated in the same manner as is done on creation of a new DID).
The resolver will confirm that the included recovery key hashes to the correct value of the existing recoveryHash
and construct a new DID document from the most recent version, without the proof section, and with the new public key as the only controller key in the verificationMethod
array, the new recoveryHash
, an incremented versionId
number, and a current updated
date string, to the client.
The client MUST append a new valid proof section to the DID document and return it to the server. Upon receipt and verification, the DID document for this DID will be updated.
5.6 Deactivate
Send a POST request to the resolver URI with the path /did/deactivate
. The request data MUST include the DID id, and the public key portion of the recovery key in base64url format.
The resolver will confirm that the included recovery key hashes to the correct value of the existing recoveryHash
. It will then publish a new version of the DID document with the versionId
incremented, a current updated
date string, and the deactivated
flag set to true
. The resolver MUST NOT make any other changes, and MUST NOT accept any further updates or key rotations for this DID.
N.B. This operation cannot be reversed or updated. No other operations on this DID will be accepted, although it may continue to be resolved, including resolving its history.
6. Security Considerations
All private keys must be held via secure methods, in particular the controller private keys which are used to make updates to the DID document, and most particularly the “recovery” keys which are required in order to rotate the DID’s keys in the event of a security breach. It is recommended that, if possible, the “recovery” keys be held securely offline until use.
It should be noted that as a basic principle of asymmetric key security, the private or secret key portion of any key pair should never be transmitted over the network. The security of this DID method rests on this principle, and experience has shown that implementers may inadvertently violate it in subtle ways.
Care must be taken that secret keys either do not and cannot leave the device on which they were created, or only do so in ways that do not involve remote networks (e.g. by printing out a representation of the key for archiving, or saving to local removable storage).
7. Privacy Considerations
In order to avoid cross-party correlation and subsequent breach of pseudonymity, it is recommended, use-case permitting, that a new pair-wise DID be created by each party for each relationship that they enter into using DIDs.
Privacy and confidentiality are often conflated, to the detriment of good design. Privacy measures such as access control as well as metadata control, which are implied in the anti-correlation measures mentioned above, are of course required, and implementers must carefully consider these requirements.
Additionally, this DID method was designed with the JLINC protocol in mind, and in particular to support the principle of Chain-Link Confidentiality as outlined in the seminal paper outlining that subject by Professor Woodrow Hartzog.