A DID (Decentralized Identifier) method driver for the
did-iolibrary and for standalone use
See also (related specs):
A did:key method driver for the @interop/did-io
client library and for standalone use.
The did:key method is used to express public keys in a way that doesn't
require a DID Registry of any kind. Its general format is:
did:key:<multibase encoded, multicodec identified, public key>
So, for example, the following DID would be derived from a multibase encoded ed25519 public key:
did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH
That DID would correspond to the following DID Document:
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1"
],
"id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"verificationMethod": [
{
"id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"type": "Multikey",
"controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"publicKeyMultibase": "z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
}
],
"authentication": [
"did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
],
"assertionMethod": [
"did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
],
"capabilityDelegation": [
"did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
],
"capabilityInvocation": [
"did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
]
}By default this driver represents verification keys as W3C
Multikey verification methods, and
does not derive a separate keyAgreement (X25519) key.
If you register a verification suite that exports the legacy
Ed25519VerificationKey2020 shape, the driver will additionally derive a
Curve25519 keyAgreement public key (suitable for Diffie-Hellman key exchange)
from the source Ed25519 key, using
ed2curve-js. Note that this derived
key is optional -- there's at least
one proof that this is safe to do.
Requires Node.js 24+.
To install from npm:
npm install --save @interop/did-method-key
To install locally (for development), this project uses
pnpm:
git clone https://github.com/interop-alliance/did-method-key.git
cd did-method-key
pnpm install
This method registers a key type that the driver is allowed to handle, for
both resolution (get()) and generation (generate()).
The preferred form passes a keyPairClass -- a KeyPair suite class. The
driver reads the multibase-multikey header, deserializer, and key generator off
the class, so you never have to look up or hard-code the literal multibase
prefix:
import { driver } from '@interop/did-method-key'
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
const didKeyDriver = driver()
didKeyDriver.use({ keyPairClass: Ed25519VerificationKey })For a suite that does not expose the keyPairClass contract (no static
multibaseHeader/from/generate), use the lower-level form: register the
multibase-multikey header and a fromMultibase deserializer directly. Key
types registered this way support resolution but not generate().
import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey'
import { driver } from '@interop/did-method-key'
const didKeyDriverMultikey = driver()
didKeyDriverMultikey.use({
multibaseMultikeyHeader: 'zDna',
fromMultibase: EcdsaMultikey.from
})This utility function adapts a verification suite that exposes a
fromFingerprint() static method (rather than a fromMultibase() method) so
that it works with DidKeyDriver.
import { driver, createFromMultibase } from '@interop/did-method-key'
import { SomeVerificationKey } from 'some-verification-suite'
const didKeyDriver = driver()
didKeyDriver.use({
multibaseMultikeyHeader: header,
fromMultibase: createFromMultibase(SomeVerificationKey)
})The simplest way to mint a brand-new did:key DID Document: register a suite
via use({ keyPairClass }), then call generate(). The driver generates a
fresh key pair using the registered suite and returns the DID Document along
with the corresponding key pairs (for storage in a KMS).
import { driver } from '@interop/did-method-key'
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
const didKeyDriver = driver()
didKeyDriver.use({ keyPairClass: Ed25519VerificationKey })
const { didDocument, keyPairs, methodFor } = await didKeyDriver.generate()
// serialize the public DID Document, e.g. for storage on disk
console.log(JSON.stringify(didDocument, null, 2))When exactly one suite is registered, generate() uses it automatically. If
more than one is registered, pass keyType (a KeyPair class or its multibase
header) to disambiguate:
const { didDocument } = await didKeyDriver.generate({
keyType: Ed25519VerificationKey
})To deterministically derive the key pair from a secret-key seed (rather than
generating a random one), pass a seed (a Uint8Array). The same seed always
produces the same DID Document:
const { didDocument } = await didKeyDriver.generate({ seed })The seed is passed through to the registered suite's generate(), so its
required length and format are determined by that suite (for ed25519, a 32-byte
Uint8Array).
Note that generate() requires a suite registered via use({ keyPairClass });
the lower-level use({ multibaseMultikeyHeader, fromMultibase }) form has no
key generator and supports resolution only.
To get the did:key method DID Document that corresponds to an existing
verification keypair (for example, one you generated or loaded yourself).
import { driver } from '@interop/did-method-key'
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
const didKeyDriver = driver()
didKeyDriver.use({ keyPairClass: Ed25519VerificationKey })
const publicKeyMultibase = 'z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'
const verificationKeyPair = await Ed25519VerificationKey.from({
publicKeyMultibase
})
// or perhaps:
// const verificationKeyPair = await Ed25519VerificationKey.generate();
const { didDocument, keyPairs, methodFor } = await didKeyDriver.fromKeyPair({
verificationKeyPair
})
// print the DID Document above
console.log(JSON.stringify(didDocument, null, 2))
// keyPairs will be set like so =>
Map(1) {
'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' => Ed25519VerificationKey {
id: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
controller: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
type: 'Ed25519VerificationKey2020',
publicKeyMultibase: 'z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'
}
}methodFor is a convenience function that returns a key pair instance that
contains publicKeyMultibase for a given purpose. For example, a verification
key (containing signer() and verifier() functions) is frequently useful for
jsonld-signatures
operations. After generating a new did:key DID, you can do:
// For signing Verifiable Credentials
const assertionKeyPair = methodFor({ purpose: 'assertionMethod' })
// For Authorization Capabilities (zCaps)
const invocationKeyPair = methodFor({ purpose: 'capabilityInvocation' })Note that methodFor returns a key pair that contains a publicKeyMultibase.
This makes it useful for verifying and encrypting operations.
Because the default Multikey representation does not derive a keyAgreement
key, methodFor({ purpose: 'keyAgreement' }) will throw for an ed25519 DID
unless you explicitly supplied a keyAgreementKeyPair to fromKeyPair().
If you already have a public key object (as an LDKeyPair instance, or a plain key description object), you can turn it into a DID Document:
const { didDocument } = await didKeyDriver.publicKeyToDidDoc({
publicKeyDescription
})To get a DID Document for an existing did:key DID:
const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'
const didDocument = await didKeyDriver.get({ did })(Results in the example DID Doc above).
You can also use a .get() to retrieve an individual key, if you know its id
already (this is useful for constructing documentLoaders for JSON-LD Signature
libs, and the resulting key does include the appropriate @context).
const verificationKeyId =
'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'
const keyData = await didKeyDriver.get({ url: verificationKeyId })
// key node ->
console.log(JSON.stringify(keyData, null, 2))Often, you have just a did:key DID, and you need to get a key for a
particular purpose from it, such as an assertionMethod key to verify a
VC signature.
For that purpose, you can use a combination of get() and publicMethodFor:
// Start with the DID
const didDocument = await didKeyDriver.get({ did })
// This lets you use `publicMethodFor()` to get a key for a specific purpose
const assertionMethod = didKeyDriver.publicMethodFor({
didDocument,
purpose: 'assertionMethod'
})
// If you have a known key type, you can create a key instance which gives you
// access to a `verify()` function.
const assertionMethodPublicKey =
await Ed25519VerificationKey.from(assertionMethod)
const { verify } = assertionMethodPublicKey.verifier()publicMethodFor will throw an error if no key is found for a given purpose.
PRs accepted.
If editing the Readme, please conform to the standard-readme specification.
New BSD License (3-clause) © Interop Alliance and Digital Bazaar