@interop/did-method-key
v7.3.2
Published
A did:key method resolver.
Readme
did:key method driver (@interop/did-method-key)
A DID (Decentralized Identifier) method driver for the
did-iolibrary and for standalone use
Table of Contents
Background
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:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktHThat DID would correspond to the following DID Document:
Example 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"
]
}Security
By default this driver represents verification keys as W3C
Multikey verification methods, and
does not derive a separate keyAgreement (X25519) key. Reusing a single key
for both signing (Ed25519) and key agreement (X25519) is a weaker security
posture, so derivation is off by default.
If you need to encrypt to plain Ed25519 did:key identities (for example,
JWE/DIDComm/EDV recipients), you can opt in per registration by passing
enableEncryptionKeyDerivation: true to use(). When enabled, resolving
an Ed25519 did:key additionally derives a Curve25519 keyAgreement public key
(suitable for Diffie-Hellman key exchange) from the source Ed25519 key, matching
the did:key spec's Ed25519 expansion. Note that this derived key is optional --
there's at least one proof that deriving it
is safe to do.
Install
Requires Node.js 24+.
To install from npm:
npm install --save @interop/did-method-keyTo install locally (for development), this project uses
pnpm:
git clone https://github.com/interop-alliance/did-method-key.git
cd did-method-key
pnpm installUsage
use()
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
})Deriving a keyAgreement key (opt-in)
By default, resolving an Ed25519 did:key does not produce a keyAgreement
(X25519) key. To have the driver derive one from the Ed25519 verification key
and add it to the DID Document, pass enableEncryptionKeyDerivation: true when
registering the Ed25519 suite:
import { driver } from '@interop/did-method-key'
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
const didKeyDriver = driver()
didKeyDriver.use({
keyPairClass: Ed25519VerificationKey,
enableEncryptionKeyDerivation: true
})
const did = 'did:key:z6MkwLz9d2sa3FJjni9A7rXmicf9NN3e5xgJPUmdqaFMTgoE'
const didDocument = await didKeyDriver.get({ did })
didDocument.keyAgreement
// => [
// {
// id: 'did:key:z6MkwLz9d2sa3FJjni9A7rXmicf9NN3e5xgJPUmdqaFMTgoE#z6LSmgLugoC8vUoK1ouCTGKdqFdpg5jb3H193L6wFJucX14U',
// type: 'X25519KeyAgreementKey2020',
// controller: 'did:key:z6MkwLz9d2sa3FJjni9A7rXmicf9NN3e5xgJPUmdqaFMTgoE',
// publicKeyMultibase: 'z6LSmgLugoC8vUoK1ouCTGKdqFdpg5jb3H193L6wFJucX14U'
// }
// ]The flag also works with the lower-level registration form (use({
multibaseMultikeyHeader: 'z6Mk', fromMultibase, enableEncryptionKeyDerivation:
true })). It is detected by the Ed25519 z6Mk multibase-multikey header, so it
has no effect when set on a non-Ed25519 suite. See Security for the
rationale behind keeping derivation off by default.
createFromMultibase()
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)
})generate()
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.
fromKeyPair()
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(), or
registered the Ed25519 suite with
enableEncryptionKeyDerivation: true.
publicKeyToDidDoc()
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
})get()
Getting a full DID Document from a did:key DID
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).
Getting the DID Document from key id
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))publicMethodFor()
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.
Contribute
PRs accepted.
If editing the Readme, please conform to the standard-readme specification.
License
New BSD License (3-clause) © Interop Alliance and Digital Bazaar
