About

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.

 

Sovrin DID Method

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.

Target System(s)

This DID method applies to the Sovrin network in all its incarnations, beginning with Sovrin's provisional launch on 31 July 2017.

Namespace Specific Identifier (NSI)

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.

Namestring Generation Method

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]*)*)$

Examples

A valid sov DID might be: did:sov:2wJPyULfLLnYTEFYzByfUR.

JSON-LD Context Definition

On Sovrin, public DIDs and their corresponding documents are stored on the ledger.

TODO

CRUD Operation Definitions

Create (Register)

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:

DID Service Endpoint

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.

Read (Resolve)

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.

DID Service Endpoint

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:

Resolver DID Document Format

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"
        

DID Document Notes

The following are notes about the example DID Document above:

Update (Replace)

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>
                            }]
                        }
                      }
                  }
                

This example shows how to update the DID document to require multiple signatures for authentication:
                {
                    "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"
                            }]
                        }
                      }
                  }
                

Delete (Revoke)

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
                    }
                }
            

Security Considerations

Attacks

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:

All confidential information such as cryptographic private keys are stored with end consumers of Sovrin.

Residual Risks

We don't believe to have any residual risks at this time.

Recovery From Key Compromise

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:

Privacy Considerations

Assume psuedonymous DID. What Sovrin is doing about privacy and addresses each of these.

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
            

Reference Implementations

The code at https://github.com/hyperledger/indy-node, stable and https://github.com/hyperledger/indy-sdk, master constitute a canonical implementation of Sovrin DIDs, and should be treated like an oracle.

Note that the code includes a test suite; any other implementations should ensure that all tests pass before they claim compatibility.

Resources

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.

Appendix A: Key Algorithm Types

This is extracted from RFC 5480 and 5639:
The NIST-named curves are:

-- Note that in [X9.62] the curves are referred to as 'ansiX9' as
-- opposed to 'sec'. For example, secp192r1 is the same curve as
-- ansix9p192r1.

-- Note that in [PKI-ALG] the secp192r1 curve was referred to as
-- prime192v1 and the secp256r1 curve was referred to as
-- prime256v1.

-- Note that [FIPS186-3] refers to secp192r1 as P-192, secp224r1 as
-- P-224, secp256r1 as P-256, secp384r1 as P-384, and secp521r1 as
-- P-521.

secp192r1 ... same as nistp192, prime192v1
secp224r1 ... same as nistp224, prime224v1
secp256k1 ... used by Bitcoin
secp256r1 ... same as nistp256, prime256v1
secp384r1 ... same as nistp384, prime384v1
secp521r1 ... same as nistp521, prime521v1
Curves from http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
nistp192 ... same as secp192r1, prime192v1
nistp224 ... same as secp224r1, prime224v1
nistp256 ... same as secp256r1, prime256v1
nistp384 ... same as secp384r1, prime384v1
nistp521 ... same as secp521r1, prime521v1
Curves from ANS X9.62
prime192v1 ... same as nistp192, secp192r1
prime192v2
prime192v3
prime224v1 ...same as nistp224, secp224r1
prime239v1
prime239v2
prime239v3
prime256v1 ... same as nistp256, secp256r1
prime384v1 … same as nistp384, secp384r1
prime521v1 ... same as nistp521, secp521r1

Barreto Naehring curves are defined at ISO/IEC 15946-5, DevScoDah2007, and FIDO ECDAA Section 4

The following are valid values for publicKey/type

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

Appendix B: Ed25519 Signature Suite

A description to the Ed25519 Signature Suite can be found here.

Appendix C: Public Key Authorizations

The following authorizations are supported:
  • 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