The Sovrin DID method specification conforms to the requirements specified in the DID specification currently published by the W3C Credentials Community Group. For more information about DIDs and DID method specifications, please see the DID Primer and DID Spec.
Sovrin is a public ledger designed specifically and only for privacy-preserving self-sovereign identity. The Sovrin Ledger is governed by the international non-profit Sovrin Foundation. As the only public ledger designed exclusively for self-sovereign identity, Sovrin is optimized for DIDs and DID Documents. DIDs are created, stored, and used with verifiable claims. This specification covers how these DIDs are managed. It also describes related features of Sovrin of particular interest to DID owners, guardians, and developers.
The namestring that shall identify this DID method is: sov
A DID that uses this method MUST begin with the following prefix: did:sov
.
Per the DID specification, this string MUST be in lowercase. The remainder of the DID, after the prefix,
is the NSI specified below.
This DID method applies to the Sovrin network in all its incarnations, beginning with Sovrin's provisional launch on 31 July 2017.
The Sovrin DID scheme is defined by the following ABNF:
sovrin-did = "did:sov:" idstring *(":" subnamespace)
idstring = 21*22(base58char)
subnamespace = ALPHA *(ALPHA / DIGIT / "_" / "-")
base58char = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "A" / "B" / "C"
/ "D" / "E" / "F" / "G" / "H" / "J" / "K" / "L" / "M" / "N" / "P" / "Q"
/ "R" / "S" / "T" / "U" / "V" / "W" / "X" / "Y" / "Z" / "a" / "b" / "c"
/ "d" / "e" / "f" / "g" / "h" / "i" / "j" / "k" / "m" / "n" / "o" / "p"
/ "q" / "r" / "s" / "t" / "u" / "v" / "w" / "x" / "y" / "z"
All Sovrin DIDs are base58 encoded using the Bitcoin/IPFS alphabets of a 16-byte uuid.
The encoding uses most alphas and digits, omitting 0OIl to avoid readability problems. This
gives an NSI length of either 21 or 22 characters, and it means that DIDs are case-sensitive and may not
be case-normalized, even though the prefix is always lower-case.
Optional one or more sub namespaces may be specified to indicate which Sovrin system the DID should reference.
The 16-byte uuid underlying a Sovrin DID can be generated in various ways--using standard uuid methods, for example, or by selecting the first 16 bytes of a 256 bit Ed25519 verification key (the public portion of the key pair). In the latter case, there may or may not be an active verification key for the identity owner; that information is only available in the key descriptions in the owner section of the DID Document.
A convenient regex to match sov
DIDs is:
^[1-9A-HJ-NP-Za-km-z]{21,22}$
A convenient regex to match the entire did string is:
^did:sov:[1-9A-HJ-NP-Za-km-z]{21,22}(?<namespace>(?::\w[-\w]*)*)$
A valid sov
DID might be: did:sov:2wJPyULfLLnYTEFYzByfUR
.
On Sovrin, public DIDs and their corresponding documents are stored on the ledger.
TODO
The DID method specification must specify how a client creates a DID record—the combination of a DID and its associated document—on the
target system, including all cryptographic operations necessary to establish proof of ownership.
The Sovrin spec adds a new entry under publicKey
called authorizations
.
This is used to indicate possible authorizations the public key is supposed to have. The keyword ALL
means that the public key can make whatever change it wants to the DID doc.
While creating a new DID doc, the field authorizations
cannot be left empty. It has to either contain the keyword ALL
or be omitted,
in that case it is assumed that the key has all authorizations.
For example:
To create a DID, you must submit a "NYM" transaction that looks like this:
{ 'operation': { 'type': <Transaction type -- NYM >, 'dest': <new DID that is being registered>, 'role': <Role given to the new DID -- based on network config>, 'verkey': <Key material encoded>, }, 'identifier': 'L5AD5g65TDQr1PPHHRoiGf' <Trust Anchor DID>, 'reqId': <A nonce for this transaction>, 'protocolVersion': 2, 'signature': <signature over this transaction from the Trust Anchor> }The transaction must be signed by a Trust Anchor and must provide an un-registered DID and a document of data about that DID. Possible outcomes from the create operation include:
By convention, Indy DID Controllers often create an Indy ATTRIB object (using the "ATTRIB" transaction) related to the NYM with a raw value of "endpoint", and a URL that represents the service endpoint for the DID. As noted in the "Read" section of this specification such an "endpoint" ATTRIB is read when resolving a DID and its data included in the resulting DID Document.
A Sovrin DID record can be looked up directly by the DID using a standard Sovrin GET_NYM transaction that simply takes the DID and returns the DID record. The client trusts the DID record because it either receives the same DID record from sufficient number of validators or it receives a proof of the DID record from a single validator that the DID record is part of a merkle tree whose root has an aggregate signature from sufficient number of validators.
This is what the DID record query looks like
{ "submitterId": <Optional; DID of the author of this query>, "reqId": <Optional; a nonce for this query>, "operation": { "did": <DID to be queried>, "type": "GET_NYM" } }
Anyone can query a DID record by sending the above request. The response contains the data that can be used to create a DID document, as noted below.
When reading (resolving) a Sovrin DID, in addition to querying the DID record, a query MUST be performed for a related DID Service endpoint. This is done by executing the "GET_ATTRIB" transaction. The format of the GET_ATTRIB request looks like
{ "submitterId": <Optional; DID of the author of this query>, "reqId": <Optional; a nonce for this query>, "identifier": <Required; The DID being read/resolved>, "operation": { "raw": "{\"endpoint\":{\"endpoint\":\"https://example.com\"}}" <Required; the value must be endpoint> } }
The result may be a "Not Found" error, indicating the NYM does not have an optional corresponding ATTRIB endpoint record. If the call is successful, the relevant part of the response of such a GET_ATTRIB request is:
{ ... 'data': '{"endpoint":{"endpoint":"https://example.com/endpoint"}}', 'dest': 'AH4RRiPR78DUrCWatnCW2w' < The DID being read/resolved>, 'raw': 'endpoint', ... }
The value of the inner endpoint item (https://example.com/endpoint in the example above) is the service endpoint for the DID.
Historically on the Sovrin MainNet (through June 2021), the format of the endpoint ATTRIB has been simply a single name ("endpoint") value (a URL) pair, as shown above. An extended version of the endpoint ATTRIB MAY be written to an Indy ledger by a DID Controller to provide additional control over the generation of the DID Document. The format is as follows:
{ "endpoint": "https://example.com", "types": [ "endpoint", "did-communication", "DIDComm" ], "routingKeys": [ "did:key12345", "did:key12345" ] }
This corresponds to the raw value in the "ATTRIB" transaction as:
"raw": "{\"endpoint\":{\"endpoint\":\"https://example.com\",\"types\":[\"endpoint\",\"did-communication\",\"DIDComm\" ],\"routingKeys\":[\"did:key12345\",\"did:key12345\"]}}"
A did:sov resolver MUST process the endpoint ATTRIB (extended or not) as follows:
The DID Document is generated by a resolver (not by the Indy/Sovrin ledger) by taking the GET_NYM and optional GET_ATTRIB/endpoint data and rendering a DID Document as follows. See the notes following the rendering.
{ "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1" ], "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM", "verificationMethod": [ { "type": "Ed25519VerificationKey2018", "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1", "publicKeyBase58": "9wvq2i4xUa5umXoThe83CDgx1e5bsjZKJL4DEWvTP9qe" }, { "type": "X25519KeyAgreementKey2019", "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1", "publicKeyBase58": "3mHtKcQFEzqeUcnce5BAuzAgLEbqKaV542pUf9xQ5Pf8" } ], "authentication": [ "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1" ], "assertionMethod": [ "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1" ], "keyAgreement": [ "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" ], "service": [ { "type": "endpoint", "serviceEndpoint": "https://example.com/endpoint" }, { "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication", "type": "did-communication", "priority": 0, "recipientKeys": [ "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" ], "routingKeys": [], "accept": [ "didcomm/aip2;env=rfc19" ], "serviceEndpoint": "https://example.com/endpoint" } ] }
If an endpoint ATTRIB does not exist for the NYM, the service block is an empty array (e.g. "service": []
If the endpoint has a types item and that types item contains an entry "DIDComm", then the following extra service block is included:
{ "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#didcomm-1", "type": "DIDComm", "serviceEndpoint": "https://example.com/endpoint", "accept": [ "didcomm/v2" ], "routingKeys": [] }In addition the service block, the following entry needs to be added to the @context array:
"https://didcomm.org/messaging/contexts/v2"
The following are notes about the example DID Document above:
To replace the DID document, the owner or guardian (guardianship ends once ownership begins) of the DID should send the following transaction using a key referenced in the authentication property.
{ "submitterId": <DID of the author of this query>, "signature": [<signature over this transaction from the author>,] "reqId": <a nonce for this query>, "operation": { "type": "NYM", "did": <DID whose record needs to be updated>, "document": { "publicKey": [{ "id": <a valid unique identifier> "type": <ED25519> "publicKeyBase58": <Key material encoded in format>, "authorizations": ["all"] },{ "id": <a valid unique identifier> "type": <ED25519> "publicKeyBase58": <Key material encoded in format> },{ "id": <a valid unique identifier> "type": <ED25519> "publicKeyBase58": <Key material encoded in format> }], "authentication": [{ "type": "ED25519SigningAuthenticationThreshold", "threshold": <an integer>, "publicKey": [<key references>] }], "service": [{ "type": <agentService, emailService, apiService, dnsService, authenticationService, etc>, "serviceEndpoint": <A URI for the endpoint> }] } } }
{ "submitterId": "did:sov:qazWSX3erfcY459iLMh7", "signature": ["1qaz2wsx3eeciguytGGVDR990ik=="] "reqId": "897432983746n43g" "operation": { "type": "NYM", "did": "did:sov:c432cDSGUASJN987jnJKb", "document": { "publicKey": [{ "id": "#key1", "type": "ED25519VerificationKey", "publicKeyBase58": ... },{ "id": "#key2", "type": "ED25519VerificationKey", "publicKeyBase58": ... },{ "id": "#key3", "type": "ED25519VerificationKey", "publicKeyBase58": ... }], "authentication": [{ "type": "ED25519SigningAuthenticationThreshold", "threshold": 2, "publicKey": ["#key1","#key2","#key3"] }], "service": [{ "type": "agentService", "serviceEndpoint": "https://www.sovrin.org/agents" }] } } }
Deleting or revoking a verification key is not to be confused with temporary suspension or rotation. Deletion sets an identity's verification key to null; this permanently terminates the identity's ability to operate on the network because there is no key that the identity can use to authenticate itself--even to submit a new key rotation request. It is irreversible.
Revocation may be appropriate when a person dies or a business is legally dissolved. It does not remove any record or history of the identity--it simply prevents any new history from accruing. This guarantees that no malicious actor can recover and reactivate an identity that's dead.
The scope of deletion in Sovrin is one DID only. This does not prevent an entity from creating new DIDs; it simply prevents an entity from reusing the old DID that has been terminated.
To revoke the document of the DID, the owner of the DID should send the following transaction.
{ "submitterId": <DID of the owner>, "signature": <signature over this transaction from the DID in identifier>, "reqId": <a nonce for this transaction>, "operation": { "type": "NYM", "did": <DID whose key is being revoked>, "document": null } }
Secure communication to the Sovrin Ledger uses CurveZMQ to mitigate attacks like eavesdropping, replay, message insertion, deletion, modification, impersonation, and man-in-the-middle. Any vulnerabilities in that protocol will apply to Sovrin.
Sovrin uses a modified version of the Redundant Byzantinne Fault Tolerace protocol called Plenum to reach consensus.
Sovrin currently stores only the following data on the ledger:
We don't believe to have any residual risks at this time.
This section MUST provide integrity protection and update authentication for all operations required by Section 7 of this specification (DID Operations).
Confused Deputy Problem when attempting separation of writeAuthorization from authenticationCredential.
Other sections from DID spec to incorporate into the outline:
Considering all four types of privacy, explore issues from these sections of the DID spec:
10. Privacy Considerations 10.1 Requirements of DID Method Specifications 10.2 Keep Personally-Identifiable Information (PII) Off-Ledger 10.3 DID Correlation Risks and Pseudonymous DIDs 10.4 Document Correlation Risks 10.5 Herd Privacy
Note that the code includes a test suite; any other implementations should ensure that all tests pass before they claim compatibility.
Many developers maintaining the code and spec tend to hang out in RocketChat at chat.hyperledger.org, #indy and #indy-sdk. You might also connect with us in Hyperledger and W3C working groups or at the semi-annual Internet Identity Workshop conferences.
Key Type |
---|
rsa |
Twisted Edwards/Montgomery Curves |
m-221, e-222, ed1174, ed25519, ed383187, ed41417, e-382, m-383, ed448, e-521, m-511 |
BrainPool Curves |
brainpoolp160r1, brainpoolp192r1, brainpoolp224r1, brainpoolp256r1, brainpoolp320r1, brainpoolp384r1, brainpoolp512r1 |
SEC2 Curves |
secp112r1, secp128r1, secp160r1, secp192r1, secp224r1, secp256r1, secp384r1, secp521r1 |
secp160k1, secp163k1, secp192k1, secp224k1, secp256k1 |
sect163r2, sect233r1, sect239r1, sect283r1, sect409r1, sect571r1 |
Barreto Naehrig Curves |
fp224bn, fp254bna, fp254bnb, fp256bn, fp384bn, fp512bn, fp638bn |
ADD_KEY
: Add new key to the DID doc. The new key can only have ADD_KEY authorization.
REM_KEY
: Remove existing keys from the DID doc.
ALL
: All possible authorizations. The key submitted during creation of DID doc has this authorisations.
The key with this authorization can add keys with ADD_KEY, REM_KEY and ALL authorization