OYD DID Method

Living Document,

This version:
https://ownyourdata.github.io/oydid/
Issue Tracking:
GitHub
Editors:
Christoph Fabianek (OwnYourData)
(OwnYourData)

Abstract

Draft recommendations for use of a self-sustained environment to manage decentralized identifiers

This specification is published by OwnYourData as the defining document for the did:oyd DID method. It is not a W3C Standard, is not on the W3C Standards Track, and is not a work item of the W3C Credentials Community Group or any other W3C group. The did:oyd method is registered in the W3C Decentralized Identifier Extensions registry; registration is a purely informative listing and does not imply review or endorsement by W3C. GitHub Issues are preferred for discussion of this specification.

History

1. Introduction

Decentralized Identifiers (DIDs) [DID-CORE] are a new type of identifier for verifiable, decentralized digital identity. These new identifiers are designed to enable the controller of a DID to prove control over it and to be implemented independently of any centralized registry, identity provider, or certificate authority. These sorts of identifiers often utilize a heavy-weight registry, such as ones utilizing Decentralized Ledger Technologies (DLT), to create, read, update, and deactivate DIDs.

While DLT-based DID Methods have great decentralization characteristics, and some of the more centralized DID Methods provide strong system control guarantees, the general approaches tend to be expensive to set up and operate. OYDID takes the approach to not maintain DID and DID Document on a public ledger but on one or more local storages (that might be publicly available). Through cryptographically linking the DID Identifier to the DID Document, and furthermore linking the DID Document to a chained provenance trail the same security and validation properties as a traditional DID can be maintained while avoiding highly redundant storage and also works in settings without general public access.

The rest of this document outlines the syntax for the did:oyd method, the operations it supports, and some security and privacy considerations as well as ehtical aspects that implementers might want to be aware of when using this DID method.

2. The did:oyd Format

The format for the did:oyd method conforms to the DID core specification [DID-CORE] as outlined by W3C. It consists of the did:oyd prefix, followed by a Multibase [MULTIBASE] (default: base58-btc) encoded value that is a Multihash [MULTIHASH] (default: sha2-256) value of composed of information from the payload, keys, and log. The digest algorithms an implementation has to recognise, and the codes identifying them, are listed in § 4.2.1 Digest Algorithms.

The detailed calculation of the DID identifier from the internal document and log information via ABNF is described here:

; !syntax("abnf")
did-oyd-format                     = "did:oyd:" identifier
identifier                         = MULTIBASE ( base-identifier did-information-hash )
did-information-hash               = MULTIHASH ( hash-identifier did-canonical-information )
did-canonical-information          = <create JSON canonical representation according to RFC 8785 from did-information>
did-information                    = "{doc:" payload ",key:" key-value ",log:" log-value "}"
payload                            = <json-grammar-rules>
key-value                          = public-doc-key-encoded ":" public-revocation-key-encoded
log-value                          = termination-hash-encoded
public-doc-key-encoded             = MULTIBASE ( base-identifier public-doc-key-codec )
public-doc-key-codec               = MULTICODEC ( codec-identifier public-doc-key )
public-doc-key                     = <public key from key pair used for verifying payload>
public-revocation-key-encoded      = MULTIBASE ( base-identifier public-revocation-key-codec )
public-revocation-key-codec        = MULTICODEC ( codec-identifier public-revocation-key )
public-revocation-key              = <public key from key pair used for verifying revocations>
termination-hash-encoded           = MULTIBASE ( base-identifier termination-log-entry-hash )
termination-log-entry-hash         = MULTIHASH ( hash-identifier termination-canonical-log-entry )
termination-canonical-log-entry    = <create JSON canonical representation according to RFC 8785 from termination-log-entry>
termination-log-entry              = "{op:" DIGIT ",ts:" DIGIT ",doc:" revoke-encoded ",sig:" sig-revoke-encoded ",previous: []}"
revoke-encoded                     = MULTIBASE ( base-identifier revoke-hash )
revoke-hash                        = MULTIHASH ( hash-identifier sub-revocation-canonical-log-entry )
sub-revocation-canonical-log-entry = <create JSON canonical representation according to RFC 8785 from sub-revocation-log-entry>
sub-revocation-log-entry           = "{op:" DIGIT ", ts:" DIGIT ", doc:" sub-doc-encoded ", sig:" sig-sub-doc-encoded "}"
sig-revoke-encoded                 = MULTIBASE ( base-identifier sig-revoke )
sig-revoke                         = <signed revoke-encoded with private key corresponding to public-revocation-key in payload>
sub-doc-encoded                    = MULTIBASE ( base-identifier sub-doc-hash )
sub-doc-hash                       = MULTIHASH ( hash-identifier sub-doc-canonical )
sub-doc-canonical                  = <create JSON canonical representation according to RFC 8785 from sub-doc>
sub-doc                            = "{doc:" payload ",key:" key-value "}"
sig-sub-doc-encoded                = MULTIBASE ( base-identifier sig-sub-doc )
sig-sub-doc                        = <sign sub-doc-encoded with private key corresponding to public-doc-key in payload>
base-identifier                    = DEFAULT base58btc
hash-identifier                    = DEFAULT sha2-256
codec-identifier                   = DEFAULT ed25519-pub

3. DID Operations

The following section outlines the DID operations for the did:oyd method.

3.1. Create (Register)

Creating a did:oyd identifier requires

in the format described in Section § 2 The did:oyd Format.

The DID document and associated log entries are stored in a registry accessible by relevant stakeholders. An example is given below (EXAMPLE 1) listing a payload, internal and W3C conform DID document, log entries, private keys, and private revocation document.

An example DID with all associated information and representations:

DID: did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz

Payload:

{
  "foo": "bar"
}

Internal DID document:

{
  "doc": {
    "foo": "bar"
  },
  "key": "z6MusWcRn7TWW3XAa96vrST5Fx8n3xndSEVEfMCQBDAMrQpP:z6Mv1rQ3ZTsLwfV8neZ1zKp17NHeb1Xv3KK5EB4D8t5mxJ3V",
  "log": "zQmSkagv16yPWiLAsUXRX5RKmejXuHTMTNodZewXpFCFYvj"
}

W3C conform DID document:

{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
  "verificationMethod": [
    {
      "id": "did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
      "publicKeyBase58": "z6MusWcRn7TWW3XAa96vrST5Fx8n3xndSEVEfMCQBDAMrQpP"
    }
  ],
  "keyAgreement": [
    {
      "id": "did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
      "publicKeyBase58": "z6Mv1rQ3ZTsLwfV8neZ1zKp17NHeb1Xv3KK5EB4D8t5mxJ3V"
    }
  ],
  "service": [
    {
      "id":"did:oyd:zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz#payload",
      "type": "Custom", 
      "serviceEndpoint": "https://oydid.ownyourdata.eu",
      "payload": {
        "foo": "bar"
      }
    }
  ]
}

Log entries:

[
  {
    "ts": 1641122585,
    "op": 2,
    "doc": "zQmNXp9zTLQECaLwWcXy4ctdKctnPLghHWftwdJsfGd8Erz",
    "sig": "z2BQRPP8e26kFRXoQxBB2ERFZgFVu1Eov7VdTjt9diSipRxWnx8rm4knHMsdERteE7Ry66GkVmp9Ghnp9d5cigA26",
    "previous": []
  },
  {
    "ts": 1641122585,
    "op": 0,
    "doc": "zQmeugQwCyHSdSQPbPBYmMV4qhitnJ6m4ZHTvH4GJZN3SRh",
    "sig": "zYo8FztfFvRpctfifYjgL4uswEMWagRELDqBpwMvMjLBR5dfsL4WzZc3hmG4HVKp6dhmVy4dGbfGot5kYLV2LgD9",
    "previous": []
  }
]

Private doc key: z1S5YqYg92n7q9g581jNsiMs7mwLzPtZ6tzBZTGNCwxxj2Hf

Private revocation key: z1S5WvZDDNAGQEnjSET1NA7MuMdVGZ12ZW2SqED5dxcLbLt8

Private revocation document:

{
  "ts": 1641122585,
  "op": 1,
  "doc": "zQmQ8pDd2gkWAu5tNpm9JtMBpPZ9eYSwKycobTVVszjdoMc",
  "sig": "z5ZbPS243WSimHJzVigJ9Bb3AKn6DtHTXHaAc13X7uUVFpbhgmCSp8v9vZXScj9zJP4sdouip6KckSM3P26Nw3p2"
}

3.1.1. Control Keys and Content Keys

The key member of the internal DID document is the control field of a did:oyd. It carries exactly two values, separated by a colon:

No further positions are defined. An implementation reading this field MUST address the two values by position, and MUST NOT take the last colon-separated value as the revocation key. A reader written that way returns the wrong key without any error should the field ever be extended, and the document it produces fails only at revocation time — years after the mistake was made.

Keys that do not control the log are content rather than control, and belong in the payload as verification methods; key agreement above all. The payload enters the identifier hash exactly as the key member does, so a key placed there carries the same integrity guarantee, can be rotated by an ordinary update, and leaves the two positions above untouched.

Note: an implementation MAY derive a key agreement key from the private document key instead of holding one of its own, because an Ed25519 key converts to X25519. Key types that do not permit that conversion — post-quantum signature schemes in particular — require a separate key agreement key in the payload.

3.2. Read (Resolve)

Reading a did:oyd is a matter of searching for a DID document that either can be directly linked (through hashing and encoding) to the provided identifier or through traversing through the log to unambiguously link the provided identifier to the latest version of the DID document. Cryptographic proof for the link between DID Identifier and DID Document, ownership, and completeness is performed in the following steps (here the example for a newly created DID without any updates so far):

  1. the identifier is the encoded hash value of the internal DID document (the hashing algorithm is encoded in the identifier using MULTIHASH, default: SHA2-256, the encoding algorithm is also encoded in the identifier using MULTIBASE, default: base58-btc)

  2. the DID document includes public keys (encoded with MULTIBASE, default: base58-btc and specified with MULTICODECS, default: ed25519-pub) and the hash value of a DID log entry (the hashing algorithm is encoded in the identifier using MULTIHASH, default: SHA2-256, the encoding algorithm is also encoded in the identifier using MULTIBASE, default: base58-btc)

  3. the log entry (create, op=2) for the DID document provides a signature ("sig" - encoded with MULTIBASE, default: base58-btc) of the identifier ("doc") to prove possession of the private key, i.e., use the public payload key in the DID document to verify this signature

  4. the other log entry (terminate, op=0) provides revocation info; in case a log entry with the stated hash in doc exists the DID resolving process must continue

An example is given below (EXAMPLE 2) depicting the log entries to be evaluated for linking an old DID identifier (DID v1) to the content of DID v2.

Visualization and process of linked log entries to resolve a DID identifier


Figure 1: Visualization of log entries

Process:

  1. User wants to resolves DID v1 and queries an OYDID registry with identifier

  2. OYDID registry responds with (outdated) DID document for DID v1 and log entries "create" and "terminate" (shown on the very left in the picture above)

  3. Resolver (automatically) queries the OYDID registry for a revocation log entry with the hash stored in the terminate entry

  4. OYDID repository returns revoke entry

  5. Resolver (automatically) queries the OYDID registry for log entries that hold the hash value of the revoke entry

  6. OYDID repository must retrieve exactly one update entry (and associated terminate entry) plus associated DID document (DID v2) as linked by the update entry

  7. Resolver validates linked hash entries and queries again OYDID registry for a revocation log entry from the second terminate entry (shown on the very right in the picture above)

  8. OYDID repository finds no information of such a revocation entry and therefore the resolver can return DID v2 as the latest document

Note: it is in the interest of the owner to publish revocation entries and make those easily accessible to ensure that even outdated DID identifier resolve to the latest DID document

Note: see section § 4.2 Verification Steps for a detailed description of using the OYDID command line tool to output step-wise verification information for independent proof.

3.2.1. Resolution Result

A resolver answers either with the DID document alone or with a DID Resolution Result — the document accompanied by didResolutionMetadata and didDocumentMetadata as defined in [DID-RESOLUTION]. Which of the two is returned is negotiated over the Accept header, see § 3.2.5 HTTP(S) Binding.

The id of the returned document is the identifier that was requested, not necessarily the one the log resolves to. Every update mints a new identifier, so an identifier printed on a product, embedded in a credential or handed to a business partner is regularly an earlier version of the DID the document now belongs to. Answering with the requested identifier keeps that reference intact — and it is what [DID-CORE] requires: the value of id in the retrieved document always matches the DID that was resolved. How that identifier relates to the other versions is stated in didDocumentMetadata:

Beyond these, didDocumentMetadata carries method-specific properties — log, log_hash, registry, document_log_id and termination_log_id — that expose the record a resolver based its answer on. Consumers MUST NOT treat an unknown property in this structure as an error.

Note: versionId names a version but cannot be used to retrieve that version’s document. Resolving did:oyd: + versionId returns the current document with id set to the identifier that was asked for, because resolution always follows the log to its end. The historical document remains retrievable through the log, whose entries name each version by hash. This method defines no DID URL query parameters (see § 3.2.5 HTTP(S) Binding), so ?versionId= is not accepted; a future version of this specification may define it.

Resolving an earlier version of a DID that has been updated once:
{
  "didDocument": {
    "id": "did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
    "alsoKnownAs": ["did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W"],
    ...
  },
  "didResolutionMetadata": { "contentType": "application/did" },
  "didDocumentMetadata": {
    "canonicalId": "did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W",
    "equivalentId": ["did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W"],
    "versionId": "zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W",
    "created": "2022-01-03T15:49:00Z",
    "updated": "2022-01-03T15:50:32Z",
    ...
  }
}

3.2.2. Equivalence

[DID-CORE] requires the DID method — not the resolver — to guarantee that every equivalentId value and the canonicalId value are logically equivalent to the id of the document they accompany. For did:oyd that guarantee follows from the log, and this section states the argument rather than leaving it implicit in an implementation.

A did:oyd identifier is the hash over its own DID document. An update publishes a new document, mints the identifier of that document, and records an UPDATE entry that references the TERMINATE entry of the document being replaced; the entry is signed with the document key of that earlier version. Resolution starts at the requested identifier and follows this chain of references to its end. Two identifiers therefore appear in one resolution result exactly when a chain of signed entries connects them, and every such chain ends in the same current document. Equivalence is not asserted by whoever wrote the document — it is recomputed by every resolver from records that cannot be altered without invalidating a hash or a signature.

Because the guarantee rests on the chain, both properties are also complete in the direction they claim: equivalentId lists all other versions reachable through the log, not merely the neighbouring one.

canonicalId is not a durable key. In did:oyd the identifier is the hash over the document, so the canonical identifier changes with every update. [DID-CORE] permits this — the property is defined "in the scope of the containing DID document" — but a consumer that stores canonicalId as its primary reference will find that reference stale after the next update, and will not be notified. The identifier that stays valid is the one the consumer already holds and asked with: it keeps resolving, keeps being returned as id, and its relation to the current version is restated on every resolution. Store that one, and read canonicalId as the answer to "which version is current right now".

Note: a canonicalId equal to id is not an omission. It states that the identifier which was asked for is itself the current version. [DID-CORE] permits the two to be equal, and leaving the property out in that case would be indistinguishable from a method that does not support it at all.

3.2.2.1. Location is not Identity

A did:oyd MAY carry a location suffix (see § 3.2 Read (Resolve)) naming the repository that holds the document. The suffix is a resolution hint, not part of the identity: the same document can be mirrored at any number of repositories, and the set of location-bound forms of one DID is therefore open and cannot be enumerated. canonicalId and equivalentId consequently name identifiers without a location, and equivalentId never attempts to list mirrors.

id is the exception, because it echoes what was requested: a client that resolved a location-bound form receives it back unchanged, and the location-free form of that same version then appears in equivalentId.

Where a location appears inside an identifier it MUST be percent-encoded: [DID-CORE] admits only ALPHA, DIGIT, ., -, _ and percent-encoded octets in a method-specific identifier, so did:oyd:<hash>@<location> is not a conforming DID and only did:oyd:<hash>%40<location> is.

3.2.2.2. alsoKnownAs

The document itself lists other identifiers in alsoKnownAs. That property is informative: [DID-CORE] states that identifiers linked through alsoKnownAs are not to be considered equivalent without an inverse relationship, which a did:oyd version chain cannot express —​all versions resolve to the same document. Implementations MUST rely on canonicalId and equivalentId when they need to decide equivalence.

alsoKnownAs carries two kinds of statement, and an entry does not say which of the two it is: the other versions of the DID, and the location-bound form of a version. It MUST NOT be read as a list of repositories.

3.2.3. Deactivation

A revoked DID MUST NOT be served as if it were current: answering with the last document the DID carried before its revocation is not permitted — the revocation record is published precisely so that consumers stop trusting that document.

A revoked DID is not, however, an unknown one, and the two MUST stay distinguishable. Where a resolver answers with a DID Resolution Result it therefore reports the revocation as [DID-CORE] defines it: HTTP status 200, didDocument null, and didDocumentMetadata containing deactivated with the value true. didResolutionMetadata carries no error in this case — an error states that the identifier could not be resolved, which is reserved for identifiers that were never registered.

Where a resolver answers with the bare DID document it reports the revocation with HTTP status 410 Gone; a deployment MAY report 404 Not Found instead where an intermediary treats 410 in a way that loses the distinction. Both bindings are needed: 410 is the correct statement towards a client that asked for a document, while a consumer behind a resolver driver never sees the HTTP status at all and can learn of the revocation only from deactivated.

3.2.4. Public Key Identifier

Inspired by EBSI DID v2 it is now also possible for the did:oyd method to use the document public key as identifier.

Both DIDs resolve to the same DID Document:

Because the public key does not change when a DID is updated without key rotation, one key can be carried by more than one stored version. A repository resolving this form MUST answer with the newest version that has not been revoked. For the intended case — one DID plus its update history — that is exactly the current document: key rotation on update is optional, so a non-rotating update leaves the earlier, now revoked version next to the new one. When every version carrying the key has been revoked, the newest one is returned nonetheless, so that the caller learns about the revocation instead of receiving "not found".

To keep that answer meaningful, a repository MUST reject a create whose document key is already carried by an active DID, with HTTP status 400 Bad Request. Reusing a key after the DID it controlled has been revoked stays permitted, and an update is not affected — a non-rotating update legitimately carries the key of the version it replaces.

Note: the public key identifier is resolved by the repository holding the DID and is not a global lookup. A repository can only answer for keys it has recorded; the same key published in another repository is not found through it.

3.2.5. HTTP(S) Binding

[DID-RESOLUTION] addresses a resolver through a base URL to which the DID is appended; the path of that URL is not prescribed. Implementations of this method offer the following endpoints, each taking the DID as the last path segment:

path function answer
/1.0/identifiers/ resolve always a DID Resolution Result
/1.0/resolve/ resolve negotiated, Resolution Result by default
/1.0/resolveRepresentation/ resolve negotiated, DID document by default
/1.0/dereference/ dereference a DID URL dereferencing result

/1.0/identifiers/ answers with a Resolution Result regardless of the Accept header, because that is the shape the Universal Resolver expects from a driver. On the other endpoints the header decides:

A response whose shape depends on the Accept header MUST carry Vary: Accept; without it a cache may hand one form to a client that asked for the other. Status codes follow [DID-RESOLUTION]: 200 on success, 404 when the DID is unknown, 410 when it has been revoked (see § 3.2.1 Resolution Result), and 501 for a DID URL this method does not define —​did:oyd has neither path nor query parameters, and reporting them as unsupported is preferable to ignoring them silently.

Resolvers are expected to be reachable from browser-based clients and SHOULD therefore permit cross-origin requests.

A publicly available resolver for this method:
https://resolver.ownyourdata.eu/1.0/resolve/did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh

3.2.6. did:web Bridge

A repository publishes every DID it holds a second time as a did:web identifier [DID-WEB], so that clients which support did:web but not did:oyd can read the document at all. A DID stored as did:oyd:<hash> in a repository reachable under <host> is served at https://<host>/<hash>/did.json, which is where a did:web resolver looks for did:web:<host>:<hash>.

The document returned there is the same document, with every occurrence of did:oyd: replaced by did:web:<host>: — the identifier, the controller, the identifiers of the verification methods and the entries in alsoKnownAs. Only the document is returned, not a Resolution Result.

https://oydid.ownyourdata.eu/zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh/did.json answers with:
{
  "id": "did:web:oydid.ownyourdata.eu:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
  "verificationMethod": [{
    "id": "did:web:oydid.ownyourdata.eu:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh#key-doc",
    "controller": "did:web:oydid.ownyourdata.eu:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
    ...
  }],
  ...
}

What the bridge cannot carry over is the property this method exists for: under did:web the identifier is a host name and a path, and it no longer commits to the content of the document. Whoever controls the host can serve a different document, and no log, hash chain or signature contradicts them. Trust rests on the host, not on the identifier. Consumers able to resolve did:oyd SHOULD do so, and SHOULD treat the did:web form as a fallback for interoperability.

The rules of § 3.2.1 Resolution Result apply on this path as well. In particular a revoked DID MUST NOT be served through the bridge as if it were current — a resolution path that skips the revocation check would otherwise reinstate exactly the document the revocation withdrew.

3.3. Update

Updating in did:oyd is providing a new DID document and/or new keys for key rotation while also publishing the revocation document and signing with original keys to prove ownership. An example is given below (EXAMPLE 3) of all log entries for an updated DID.

Log information of an update DID.
[{
    "ts": 1633300869,
    "op": 2,
    "doc": "zQmdxfGRfFEnLoJ9RTiyzG9TPBhK7q6zsqrGFVzuxVPv4dq",
    "sig": "ziY1pSb9irVMykHotX1UDsNXP8V6iF4REVqfk2trQP9rtooAKcMkXHgjz8VTC3FEDTjKk6D2Z3ywLer1i9NKaLo4",
    "previous": []
}, {
    "ts": 1633300869,
    "op": 0,
    "doc": "zQmVnLPgFYYNf4GEzzD3gLbSR4xhpdk7dU4BWQR4pJr7R4Z",
    "sig": "z2gJqK2bGP5VfZdCVijBeuUtzxiNaZm4NNMH6zm4EVG2LD4rqZg62c5craaQwEPEjxSbgjH4kE79DPwddghiNjirk",
    "previous": []
}, {
    "ts": 1633300869,
    "op": 1,
    "doc": "zQmVYqZoGSncYGybUnZi514niKgju68B6AVp7KJAdVifsYU",
    "sig": "z461mfYfCvLvkeeN9vdd1yNoqFBpKAwFHD1xLRQUb4evBwEXCJLZXT9qcfQo54VTrr3eow3mJHZ3aGa7inuP29ZXg",
    "previous": ["zQmVDzKVeyj4k8RYWHtKdML6MXUZX8Lu4LiiS46SfhQkZfG", 
                 "zQmSjHywvbeHV1JzsLwexzXS9246vzHkyBUkwSbXDH5f1gg"]
}, {
    "ts": 1633300910,
    "op": 3,
    "doc": "zQmNUV1MJ5xKkFm6Lc9EKqAauGzbKP7amvbyxsx79mKwqPB",
    "sig": "z2UMg3AhegwzuMgrJ1bTRDyu9W9Kp8G7ERZi2Kin3JG8UhwJqf1yivNEYY5JvB9aPDfH8GEEw9Mbvscqhk1LJH8cp",
    "previous": ["zQmetSQY5UZd91ubdWyQTh2gqVuKwkUVtjamh33p7spaYVR"]
}, {
    "ts": 1633300910,
    "op": 0,
    "doc": "zQmQTdAmqxuQQB2yBj4qZoS4ooNUnL7PzwUydEepDqjJHd4",
    "sig": "z5Bh8VwnUUQzFruVJosySBDfJJy5CRADbpYHjNH2qFjcCx71J6QJxttVhfnh7ZgfZpir9ghsNk24g3vRrDkvhR3jZ",
    "previous": []
}]

3.4. Deactivate

Deactivating an OYDID is simply done by publishing the private revocation entry without providing any update record. The figure below depicts the log representing a deactivated DID.

How a resolver reports a deactivated DID — deactivated in the metadata, 410 Gone on the bare document — is described in § 3.2.3 Deactivation.


Figure 3: Visualization of log graph for a deactivated DID

3.5. Client-Managed Secret Mode

In the operations described so far the party performing them holds the private keys. That is not always possible: a key generated inside a hardware secure element cannot be exported, and an element typically offers no more than three operations — create a key pair under a name, return its public key, and sign a payload with it.

The Client-Managed Secret Mode (CMSM) covers that case. The client contributes public keys and signatures; no private key ever reaches the repository, and none is generated there.

A client that can compute a canonical JSON serialisation and the digests of Section § 4.2.2 Hash value calculation does not need CMSM — it builds the log entries itself and publishes them, and its keys stay local anyway. CMSM addresses the other case: a client that can only sign and call an endpoint. Because such a client cannot compute the hashes, the repository computes them, which means the values to be signed can only be handed out one at a time.

3.5.1. Ordering of Signatures

The order is not a matter of protocol design; it follows from the data. Each value that has to be signed is the hash of a structure that already contains the previous signature:

subDidHash = hash({doc, key})           signed with the revocation key  -> r1
l2_doc     = hash(r1)                   signed with the document key    -> l2
l1_doc     = hash({doc, key, log})      signed with the document key    -> l1

The termination entry l2 cannot be built before the revocation entry r1 is signed, and the DID document is not complete — and therefore l1_doc not known — before l2 is signed. On an update the last signature is made with the document key of the DID being replaced, since that is what authorises the transition.

This yields three signatures for a create and three for an update, each collected in its own exchange.

3.5.2. Flow

A flow is identified by a session handle. Each incomplete step is answered with the value to be signed and the key to sign it with:

{
    "cmsm": true,
    "session": string,
    "phase": int,
    "pk": string (multibase),
    "sign": string (hash),
    "with": "key-rev" | "key-doc" | "key-doc-old"
}

with names a key, not a slot in a particular product; a client maps it onto whatever names its secure element uses. Every following request carries the session and one signature, nothing else. Implementations should expire an unfinished session and MUST discard it once the flow completes — until then it holds material that authorises operations on the DID being built.

If the client contributes only a document key, the revocation key is generated by the repository. The DID is then created without the repository ever seeing the document key, but the revocation secret is not client-managed, and the party holding it can revoke the DID. A client that intends to keep sole control contributes both keys.

3.5.3. Retaining the Revocation Entry

The revocation entry r1 of a document is signed when the document is created, and its hash is fixed in the termination entry. Revoking the document later means publishing exactly that entry.

A signer that produces deterministic signatures — as specified in [RFC6979] for ECDSA, and inherent to Ed25519 — can reproduce it at any time from the revocation key. A signer that draws a random nonce cannot: a fresh signature yields a different entry, whose hash no longer matches what the termination entry committed, and the revocation is rejected. Secure elements commonly fall into the latter group.

A client in this mode therefore has to retain the revocation entry it received. It is not a secret — it is published on revocation — but without it the DID can be neither updated nor revoked. Implementations MUST return it to the client when a create or update completes.

3.5.4. Verifying a Supplied Revocation Entry

Where the private revocation key is not available — revoking a document during an update, and a client-managed deactivation — the client supplies the entry instead of the repository rebuilding it. Three conditions have to hold, and an implementation MUST check all of them:

The third condition is the binding one. The first two are satisfied by any entry belonging to that document; only the third ties the entry to the document as it was actually published, so an entry taken from a different DID, or a re-signed one, does not pass.

3.5.5. Signatures on Creation Entries

Before this mode gained a separate exchange for it, the creation entry of a CMSM DID carried no signature, and implementations accepted such entries. Resolvers SHOULD verify the signature of a creation entry and MAY reject entries without one; they should be aware that DIDs created by earlier implementations may not carry it, and that the signature cannot be added afterwards without the private document key.

The omission is less consequential than it appears — the DID is the hash of a document that contains the document key, so an unsigned creation entry cannot introduce a different document — but the guarantee an update entry gives, namely that the previous controller authorised the change, has no counterpart at creation time without it.

4. DID Log

The log is a directed acyclic graph (DAG) and represents the life cycle of a DID. Each element in this graph holds the following attributes:

{
    "ts": int,
    "op": int,
    "doc": string (hash),
    "sig": string (signature of hash),
    "previous": [ array of strings (hashes) ]
}

4.1. Log Operations

The following list describes all available log operations (op) and specific use of attributes:

4.2. Verification Steps

Processing the log provides unambiguous evidence for authenticity, correctness, and timeliness of resolving a DID to the DID document. This section describes all operations to perform independent verification when resolving a DID.


Figure 4: Types of verification

Resolving a DID requires 3 different types of verifications as depicted in Figure 4:

  1. Hash Verifiction: within a DID version (e.g., DID v1 or DID v2 in the figure above) all associated log entries and the identifier are linked through hash values for a given JSON object

  2. Signature Verification: between 2 DID versions authenticity is proven throuogh signing the new log entry with the private key from the previous version (the update log record in the figure above is signed with the primary key from DIV v1)

  3. Log Record Retrieval: hash and signature verification in an untrusted repository guarantee imutability and it is up to the DID owner to ensure that DID documents and log records are publicly available

The following sub-sections describes those verification methods in detail.

Note: Use the --show-verification flag in the oydid command line utility to retrieve raw data for performing verification. EXAMPLE 4 is the output of such a verification.

Output of command
oydid read --show-verification did:oyd:zQmYhESMRSvN9BkrCf7YcBfxNzigVphyBUbFMfJpEm1fdPF
identifier: zQmYhESMRSvN9BkrCf7YcBfxNzigVphyBUbFMfJpEm1fdPF
✅ is hash of DID Document:
{
  "doc": [
    "payload"
  ],
  "key": "z6Mv8rFnUqnHArmaGJCHTxBCtM7GcNXpi8DWWKN1qEbeWdvP:z6MuztHorNR1h7Ds79sYsxeFF7vSu7eFcLdYsU99qyJPWJyB",
  "log": "zQmYmirw77JK4SX34zwmjgjE3F8BXnsDoBuPPbgjSQNcnZN"
}
(Details: https://ownyourdata.github.io/oydid/#calculate_hash)

'log' reference in DID Document: zQmYmirw77JK4SX34zwmjgjE3F8BXnsDoBuPPbgjSQNcnZN
✅ is hash of TERMINATE log record:
{
  "ts": 1641208742,
  "op": 0,
  "doc": "zQmW3EpDiEmDnk2epFQ2t8hXuygmivW2LJDZyUn3v9G4pj2",
  "sig": "z4Fq84y8Mg1Z52A5XUXdHES5fzGwBEGVTP6Nx2gz1Zvy7XuA8UH77f7wP7g7Dh2WtbAW2oThg2VmZSN5YySytNiGj",
  "previous": [

  ]
}
(Details: https://ownyourdata.github.io/oydid/#calculate_hash)

'doc' reference in TERMINATE log record: zQmW3EpDiEmDnk2epFQ2t8hXuygmivW2LJDZyUn3v9G4pj2
✅ is hash of REVOCATION log record (without 'previous' attribute):
{
  "ts": 1641208742,
  "op": 1,
  "doc": "zQmSswGSXLgjqBZ9f2RdRkK4AQvJEpxXVCNQNoGYr2grpmZ",
  "sig": "z2umhsnyM27JyLf3k15JyhPKh7ctKWZLgajrc7wWNJtjwUgrswoGkmVqFAyLbM8GfNDB6xHNLf1mo6rNMtDHSGc8V",
  "previous": [
    "zQmbcKwoqHbNBwNduTGFdsFb4MCLFayBNxdC6x5qUTVwKU8",
    "zQmYmirw77JK4SX34zwmjgjE3F8BXnsDoBuPPbgjSQNcnZN"
  ]
}
(Details: https://ownyourdata.github.io/oydid/#calculate_hash)

found UPDATE log record:
{
  "ts": 1641208760,
  "op": 3,
  "doc": "zQmfDsSChwWjAxz8FYXXjJCdUqq9ZiUCtBiXnZVGUTGku72",
  "sig": "zCQKpYAK1erKjqVwPExRCzBQkxHTbvaKkq9FAo4zD5JXgmzZJz6UVKSYVJvk43vmkaYcZ38eesEG5YUZ2f5pzr8q",
  "previous": [
    "zQmW8MKzWJ3dSfRgVqaq2PYQEksnry1BBocaXhxtfRf1jjv"
  ]
}
✅ public key from last DID Document: z6Mv8rFnUqnHArmaGJCHTxBCtM7GcNXpi8DWWKN1qEbeWdvP
verifies 'doc' reference of new DID Document: zQmfDsSChwWjAxz8FYXXjJCdUqq9ZiUCtBiXnZVGUTGku72
zCQKpYAK1erKjqVwPExRCzBQkxHTbvaKkq9FAo4zD5JXgmzZJz6UVKSYVJvk43vmkaYcZ38eesEG5YUZ2f5pzr8q
of next DID Document (Details: https://ownyourdata.github.io/oydid/#verify_signature)

identifier: zQmfDsSChwWjAxz8FYXXjJCdUqq9ZiUCtBiXnZVGUTGku72
✅ is hash of DID Document:
{
  "doc": [
    "new payload"
  ],
  "key": "z6Muztkm7Nrf6uEPCPhe5Cu1vL4NfXJwaCKxWd5tgHbvk6qm:z6Mv1Zdz5SqYx94YmG7UrpPVSXNQSJKCMBanHvLx1SdPUgfH",
  "log": "zQmaRxpQFsvP9i1ZyZhT5X4V2h7pNqZ4an7vgwpwK3yW7x8"
}
(Details: https://ownyourdata.github.io/oydid/#calculate_hash)

'log' reference in DID Document: zQmaRxpQFsvP9i1ZyZhT5X4V2h7pNqZ4an7vgwpwK3yW7x8
✅ is hash of TERMINATE log record:
{
  "ts": 1641208760,
  "op": 0,
  "doc": "zQmdAyJZ3P1LEn8Ldc8fwGC3jv2fT9r2tRjmU2wZfmJS2Zo",
  "sig": "z5HGPMqoi83q5GvGQjr2c2xRvqJJGtpxEjpNtGjCUixGWV8tbeKg7sBGdp1qsZYfKNFhFxfQnG67NRoraVsEMi8DL",
  "previous": [

  ]
}
(Details: https://ownyourdata.github.io/oydid/#calculate_hash)

Revocation reference in log record: zQmdAyJZ3P1LEn8Ldc8fwGC3jv2fT9r2tRjmU2wZfmJS2Zo
✅ cannot find revocation record searching at
- https://oydid.ownyourdata.eu
(Details: https://ownyourdata.github.io/oydid/#retrieve_log)

=== end of verification output ===

{"doc":["new payload"],"key":"z6Muztkm7Nrf6uEPCPhe5Cu1vL4NfXJwaCKxWd5tgHbvk6qm:z6Mv1Zdz5SqYx94YmG7UrpPVSXNQSJKCMBanHvLx1SdPUgfH","log":"zQmaRxpQFsvP9i1ZyZhT5X4V2h7pNqZ4an7vgwpwK3yW7x8"}

4.2.1. Digest Algorithms

Every hash value in OYDID is a Multihash [MULTIHASH]: a one byte code identifying the digest algorithm, a one byte digest length, and the digest itself. The following algorithms are supported, with sha2-256 as the default.

algorithm digest size code identifier length
sha2-256 32 bytes 0x12 47
sha2-512 64 bytes 0x13 91
sha3-224 28 bytes 0x17 42
sha3-256 32 bytes 0x16 47
sha3-384 48 bytes 0x15 69
sha3-512 64 bytes 0x14 91
blake2b-16 16 bytes 0x02 25
blake2b-17 17 bytes 0x0B27
blake2b-18 18 bytes 0x0C28
blake2b-19 19 bytes 0x0D29
blake2b-20 20 bytes 0x0E31
blake2b-21 21 bytes 0x0F32
blake2b-22 22 bytes 0x1034
blake2b-23 23 bytes 0x1135
blake2b-32 32 bytes 0x04 47
blake2b-64 64 bytes 0x08 91

The identifier length is the maximum length of the base58btc encoded Multihash. A complete DID is 8 characters longer because of the did:oyd: prefix.

Naming. The BLAKE2b algorithms are named by digest size in bytes, not in bits. This deviates from the Multicodec registry, where blake2b-256 denotes a 256 bit digest, and follows the naming OYDID has used for blake2b-16, blake2b-32 and blake2b-64 since its first release.

Codes marked † are specific to this DID method and MUST NOT be looked up in the Multicodec registry. OYDID writes the algorithm code as a single byte, while the registry assigns BLAKE2b two byte codes; truncated to their low byte these collide with SHA codes already in use — 0xb212 (BLAKE2b, 18 bytes) would become 0x12, which is sha2-256. The intermediate sizes therefore use the otherwise unassigned range 0x0B to 0x11, where the code is the digest size in bytes minus six. Keeping these codes low and one byte wide also keeps the identifier short, because the leading byte enters the base58btc encoding.

Note: 0x11 is sha1 in the Multicodec registry. OYDID never produces sha1 digests, and the two cannot be confused because the length byte distinguishes them — a sha1 digest is always 20 bytes, a blake2b-23 digest always 23. An implementation MUST verify the length byte before resolving a code from the range above.

The intermediate sizes exist for deployments that have to carry a did:oyd inside a length-constrained URL. The EU Digital Product Passport registry, for instance, limits that URL to 50 characters, which a 55 character sha2-256 identifier cannot meet, while blake2b-16 offers only 128 bits of digest —​too little for a document expected to remain verifiable for decades.

4.2.2. Hash value calculation

Perform the following steps to verify that a hash value conforms to a message:

  1. identify encoding algorithm in multibase string (first character)
    you can use the information here: https://github.com/multiformats/multibase
    example: if the string starts with "z" it is base58btc-encoding

  2. decode the multibase string and identify the hashing algorithm from the first byte of the resulting Multihash, verifying it against the second byte holding the digest length
    the codes are listed in § 4.2.1 Digest Algorithms; the BLAKE2b codes for 17 to 23 byte digests are specific to this method and are not part of the Multicodec table
    example: a value starting with "zQm" decodes to code 0x12, the sha2-256 hash algorithm

  3. if the message is a JSON is must be transformed to its canonical form based on this standard: https://datatracker.ietf.org/doc/html/rfc8785

  4. calculate hash value from message (check if pre-processing step #3 applies!) using the algorithm from step #2

  5. encode hash value using encoding algorithm from step #1

✅ This verification step passes iif presented hash value and message match.

4.2.3. Signature verification

Perform the following steps to verify the signature in a log record:

  1. collect necesary artefacts for verification

    • payload to sign: this is the doc attribute in a log record; make sure to check that this is the correct hash value of the referenced document/record using § 4.2.2 Hash value calculation

    • signature: this is the sig attribute in a log record

    • public key used for verification: this is provided in the corresponding DID Document; for UPDATE log records it must be the public-doc-key of the old DID Document to proof original ownership

  2. identify cryptographic algorithm in multibase encoded string of key
    you can use the information here: https://github.com/multiformats/multicodec#multicodec-table
    example: if the string start with "z6M" it is an Ed25519 public key

  3. use the public key to verify the validity of the payload’s signature; adhere to the respective encodings for each attribute

✅ This verification step passes iif payload’s signature is valid.

4.2.4. Log record retrieval

Perform the following steps to retrieve a log record:

  1. identify location and type of a repository for storing DID Documents and DID Log records:

    • location is indicted with @ at the end of an identifier; if no location is specified the default online repository at https://oydid.ownyourdata.eu is used

    • type "online repository" (indicated by an URL) with Swagger API documentation

    • type "file storage" (indicatred by the identifier local) uses the local working directory

  2. based on repository type retrieve log information:

    • online: via http(s):HOST/log/{:id}

    • file storage: use the file named {:did_identifer}.log

    Log records are an array of JSON objects in the format described in § 4 DID Log

  3. calculate the hash value for a record according to § 4.2.2 Hash value calculation and compare it to the reference

✅ This verification step passes if a reference can be established.

Note: For TERMINATE log records the reference to a REVOKE record indicates if there are updates available. Being unable to find a REVOKE record with a given hash indicates that the DID resolve process successfully completes.

5. Test Vectors

5.1. Samples

This section provides samples of typical OYDID DIDs.

A DID created with a simple payload:

did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh

A DID created and updated can be resolved using the following two identifier:

did:oyd:zQmdXNRiMWEYTiYF58a9BaiUkfB2xWUgL7G7ozyCCNPqjKV

did:oyd:zQmeArtmfxJ1JB6CXvoFdcQCyxPcYii5DUTBR44g4xYpCLR

A DID created at a non-default location:

did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W@did2.data-container.net

Note: a W3C conform representation is the percent-encoded @
did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W%40did2.data-container.net

5.2. Command line tool

Prerequisite: install the oydid command from https://github.com/OwnYourData/oydid/tree/main/cli or use the following Docker command to run the OYDID command line tool without installation (based on public Docker image oydeu\oydid-cli):

docker run -it --rm oydeu/oydid-cli
Various DIDs and useful commands.

Note: find more examples and usage scenarios in the OYDID Tutorial at https://github.com/OwnYourData/oydid/tree/main/tutorial

6. Security and Privacy Considerations

There are a number of security and privacy considerations that implementers will want to take into consideration upon working with this specification.

6.1. Security Aspects

6.1.1. Key Rotation

Key Rotation is supported and enabled by default. Different keys are used when publishing an update and a warning message is displayed in case a key is re-used.

6.1.2. Scaling

To scale access for frequently requested DIDs, it is recommended to clone a DID to multiple locations.

6.1.3. Updating Clones

It is in the interest of the DID author to provide consistent and highly available information. The recommended approach when updating DIDs with clones available is therefore:

6.1.4. Long-term Usage

For long-term usage the following preventive measures have been taken:

6.1.5. Cryptographic Agility

Key material in this method is identified by Multicodec, so a further key type is added by recognising its code; ed25519 and p256 are supported today. The identifier does not change shape when the key type changes, because it is a hash of the document rather than a container for the key. A did:oyd therefore keeps its length whatever key it carries — the practical difference to methods that encode the key into the identifier itself, where a post-quantum key inflates the identifier by three orders of magnitude.

6.1.5.1. Reach of a Quantum Adversary

The identifier, the log and the links between log entries are hash-based. Grover’s algorithm halves their security level, which the digest sizes in § 4.2.1 Digest Algorithms absorb; for the smaller BLAKE2b sizes the binding limit is their collision resistance, which applies without any quantum consideration.

Not hash-based are the signatures and the encryption. Both public keys of a document are published, so an adversary able to solve the discrete logarithm recovers both private keys and can forge an update as well as a revocation. Migration to a quantum-resistant key type is an ordinary update (§ 3.3 Update) and is authorised by the existing key — it is effective only while that key still holds, not afterwards.

6.1.5.2. Requirements on a Post-Quantum Key Type

This specification does not define a post-quantum key type yet. Any key type added to this method MUST satisfy the three requirements below.

Deterministic signatures. Signing MUST be deterministic. The termination entry commits to the hash of the revocation entry before that entry is published, so the revocation entry has to be reproducible byte for byte from the revocation key alone; the same condition that § 3.5.3 Retaining the Revocation Entry describes for secure elements. ML-DSA [FIPS204] specifies a deterministic variant alongside its default hedged one and an implementation MUST select it. A hedged signature verifies just as well, so the mistake is not detectable when signing and surfaces only when a revocation is rejected. Implementations SHOULD assert that signing one message twice yields identical bytes.

Fixed domain separation. The context string, and the choice between the pure and the pre-hashed variant, enter the signature. Both MUST be fixed when the key type is specified, otherwise two conforming implementations produce different bytes for the same document. Absent a statement for a given key type, the context string is empty and the pure variant applies.

No downgrade in a mixed document. A document MUST NOT be verifiable under either of two signature algorithms interchangeably, because its security is then that of the weaker one. A document carries one signature key type, or it carries a composite signature that MUST verify under every one of its components.

Key agreement differs in this last respect: a hybrid key agreement combines both shared secrets in a single key derivation and holds as long as either component holds. A post-quantum key agreement key SHOULD therefore be hybrid, and resides in the payload as § 3.1.1 Control Keys and Content Keys requires.

6.2. Privacy Aspects

6.2.1. Centralized Storage

Since the did:oyd method does not rely on a distributed ledger (although it would be possible to implement OYDID with DLT as storage medium!) special measures are taken to ensure immutability and availability:

6.2.2. Correlation

The source for generating OYDID identifiers are hashing algorithms which have as intrinsic property as much entropy as possible and therefore offer no direct means of correlation.

7. Ethical Aspects

DIDs provide a standardization to share specific information (public keys, service endpoints) publicly. However, storing this information on a distributed ledger requires resources (storage and processing capacities) that may seem unjustified for certain use cases where the immediate benefit is not obvious. As a result, in some cases the DID concept is already excluded in the design phase, which hinders adoption and further growth of using DIDs and Verifiable Credentials. Another challenge for DIDs can be privacy-preserving data exchanges and the simple fact of resolving a DID at a public ledger might allow it to infer certain information.

Therefore, OYDID takes the approach to not maintain DID and DID Document on a public ledger but on one or more local storages (that usually are publicly available). Through cryptographically linking the DID identifier to the DID Document, and furthermore linking the DID Document to a chained provenance trail (log), the same security and validation properties as a traditional DID are maintained while avoiding highly redundant storage and general public access.

OYDIDs are therefore excellently suited for local settings with a limited number of stakeholders interested in resolving those DIDs. Example use cases are:

8. Reference Implementation

Work in progress as part of a research project funded by the “IKT der Zukunft” program from the Federal Ministry for Transport, Innovation and Technology in Austria – FFG Projekt 887052.

The following implementation artefacts are available (published under the open source Apache 2.0 License):

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

References

Normative References

[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119

Non-Normative References

[DID-CORE]
Manu Sporny; et al. Decentralized Identifiers (DIDs) v1.0. W3C Proposed Recommendation. URL: https://www.w3.org/TR/did-core/
[DID-RESOLUTION]
DID Resolution v1.0. URL: https://www.w3.org/TR/did-resolution/
[DID-WEB]
did:web Method Specification. URL: https://w3c-ccg.github.io/did-method-web/
[FIPS204]
FIPS 204: Module-Lattice-Based Digital Signature Standard. URL: https://csrc.nist.gov/pubs/fips/204/final
[MULTIBASE]
IETF Multibase Data Format specification. URL: https://tools.ietf.org/html/draft-multiformats-multibase
[MULTIHASH]
Multihash - protocol for differentiating outputs from various well-established cryptographic hash functions. URL: https://github.com/multiformats/multihash
[RFC6979]
T. Pornin. Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). August 2013. Informational. URL: https://www.rfc-editor.org/info/rfc6979/