diff --git a/go.mod b/go.mod
index 1ffee72031..33bc3ed0f7 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ require (
github.com/lestrrat-go/jwx v1.2.26
github.com/magiconair/properties v1.8.7
github.com/mdp/qrterminal/v3 v3.1.1
+ github.com/multiformats/go-multicodec v0.9.0
github.com/nats-io/nats-server/v2 v2.10.2
github.com/nats-io/nats.go v1.30.2
github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b
diff --git a/go.sum b/go.sum
index 051b959a35..bd88053e33 100644
--- a/go.sum
+++ b/go.sum
@@ -417,6 +417,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
+github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.11 h1:yEyBxwoR/7vBM5NfLVXRnpQNVLrMhpS6MRb7Z/1pnzc=
github.com/multiformats/go-multihash v0.0.11/go.mod h1:LXRDJcYYY+9BjlsFe6i5LV7uekf0OoEJdnRmitUshxk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
diff --git a/vdr/didkey/resolver.go b/vdr/didkey/resolver.go
new file mode 100644
index 0000000000..2b3fc40478
--- /dev/null
+++ b/vdr/didkey/resolver.go
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 Nuts community
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package didkey
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/elliptic"
+ "crypto/x509"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "github.com/lestrrat-go/jwx/x25519"
+ "github.com/multiformats/go-multicodec"
+ ssi "github.com/nuts-foundation/go-did"
+ "github.com/nuts-foundation/go-did/did"
+ "github.com/nuts-foundation/nuts-node/vdr/resolver"
+ "github.com/shengdoushi/base58"
+ "io"
+)
+
+// MethodName is the name of this DID method.
+const MethodName = "key"
+
+var _ resolver.DIDResolver = &Resolver{}
+
+var errInvalidPublicKeyLength = errors.New("invalid did:key: invalid public key length")
+
+// NewResolver creates a new Resolver.
+func NewResolver() *Resolver {
+ return &Resolver{}
+}
+
+type Resolver struct {
+}
+
+func (r Resolver) Resolve(id did.DID, _ *resolver.ResolveMetadata) (*did.Document, *resolver.DocumentMetadata, error) {
+ if id.Method != MethodName {
+ return nil, nil, fmt.Errorf("unsupported DID method: %s", id.Method)
+ }
+ encodedKey := id.ID
+ if len(encodedKey) == 0 || encodedKey[0] != 'z' {
+ return nil, nil, errors.New("did:key does not start with 'z'")
+ }
+ mcBytes, err := base58.Decode(encodedKey[1:], base58.BitcoinAlphabet)
+ if err != nil {
+ return nil, nil, fmt.Errorf("did:key: invalid base58btc: %w", err)
+ }
+ reader := bytes.NewReader(mcBytes)
+ keyType, err := binary.ReadUvarint(reader)
+ if err != nil {
+ return nil, nil, fmt.Errorf("did:key: invalid multicodec value: %w", err)
+ }
+ // See https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
+ var key crypto.PublicKey
+ mcBytes, _ = io.ReadAll(reader)
+ keyLength := len(mcBytes)
+
+ switch multicodec.Code(keyType) {
+ case multicodec.Bls12_381G2Pub:
+ return nil, nil, errors.New("did:key: bls12381 public keys are not supported")
+ case multicodec.X25519Pub:
+ if keyLength != 32 {
+ return nil, nil, errInvalidPublicKeyLength
+ }
+ key = x25519.PublicKey(mcBytes)
+ case multicodec.Ed25519Pub:
+ if keyLength != 32 {
+ return nil, nil, errInvalidPublicKeyLength
+ }
+ key = ed25519.PublicKey(mcBytes)
+ case multicodec.Secp256k1Pub:
+ // lestrrat/jwk.New() is missing support for secp256k1
+ return nil, nil, errors.New("did:key: secp256k1 public keys are not supported")
+ case multicodec.P256Pub:
+ if key, err = unmarshalEC(elliptic.P256(), 33, mcBytes); err != nil {
+ return nil, nil, err
+ }
+ case multicodec.P384Pub:
+ if key, err = unmarshalEC(elliptic.P384(), 49, mcBytes); err != nil {
+ return nil, nil, err
+ }
+ case multicodec.P521Pub:
+ key, _ = unmarshalEC(elliptic.P521(), -1, mcBytes)
+ case multicodec.RsaPub:
+ rsaKey, err := x509.ParsePKCS1PublicKey(mcBytes)
+ if err != nil {
+ return nil, nil, fmt.Errorf("did:key: invalid PKCS#1 encoded RSA public key: %w", err)
+ }
+ // Safe RSA keys must be at least 2048 bits
+ if rsaKey.Size() < 256 {
+ return nil, nil, errors.New("did:key: RSA public key is too small (must be at least 2048 bits)")
+ }
+ key = rsaKey
+ default:
+ return nil, nil, fmt.Errorf("did:key: unsupported public key type: 0x%x", keyType)
+ }
+
+ document := did.Document{
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json"),
+ did.DIDContextV1URI(),
+ },
+ ID: id,
+ }
+ keyID := id
+ keyID.Fragment = id.ID
+ vm, err := did.NewVerificationMethod(keyID, ssi.JsonWebKey2020, id, key)
+ if err != nil {
+ return nil, nil, err
+ }
+ document.AddAssertionMethod(vm)
+ document.AddAuthenticationMethod(vm)
+ document.AddKeyAgreement(vm)
+ document.AddCapabilityDelegation(vm)
+ document.AddCapabilityInvocation(vm)
+ return &document, &resolver.DocumentMetadata{}, nil
+}
+
+func unmarshalEC(curve elliptic.Curve, expectedLen int, pubKeyBytes []byte) (ecdsa.PublicKey, error) {
+ if expectedLen != -1 && len(pubKeyBytes) != expectedLen {
+ return ecdsa.PublicKey{}, errInvalidPublicKeyLength
+ }
+ x, y := elliptic.UnmarshalCompressed(curve, pubKeyBytes)
+ return ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
+}
diff --git a/vdr/didkey/resolver_test.go b/vdr/didkey/resolver_test.go
new file mode 100644
index 0000000000..8168569631
--- /dev/null
+++ b/vdr/didkey/resolver_test.go
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2023 Nuts community
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package didkey
+
+import (
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/binary"
+ "encoding/json"
+ "github.com/multiformats/go-multicodec"
+ "github.com/nuts-foundation/go-did/did"
+ "github.com/shengdoushi/base58"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestTestVectors(t *testing.T) {
+ resolver := Resolver{}
+ type testCase struct {
+ name string
+ did string
+ jwk map[string]interface{}
+ error string
+ }
+
+ unsafeRSAKey, _ := rsa.GenerateKey(rand.Reader, 1024)
+ unsafeRSAKeyBytes := x509.MarshalPKCS1PublicKey(&unsafeRSAKey.PublicKey)
+
+ testCases := []testCase{
+ // Taken from https://w3c-ccg.github.io/did-method-key/#ed25519-x25519
+ {
+ name: "ed25519",
+ did: "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
+ jwk: map[string]interface{}{
+ "kty": "OKP",
+ "crv": "Ed25519",
+ "x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
+ },
+ },
+ {
+ name: "ed25519 (invalid length)",
+ did: createDIDKey(multicodec.Ed25519Pub, []byte{1, 2, 3}),
+ error: "invalid did:key: invalid public key length",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#x25519
+ {
+ name: "x25519",
+ did: "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
+ jwk: map[string]interface{}{
+ "crv": "X25519",
+ "kty": "OKP",
+ "x": "L-V9o0fNYkMVKNqsX7spBzD_9oSvxM_C7ZCZX1jLO3Q",
+ },
+ },
+ {
+ name: "x25519 (invalid length)",
+ did: createDIDKey(multicodec.X25519Pub, []byte{1, 2, 3}),
+ error: "invalid did:key: invalid public key length",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#secp256k1
+ {
+ name: "secp256k1",
+ did: "did:key:zQ3shbgnTGcgBpXPdBjDur3ATMDWhS7aPs6FRFkWR19Lb9Zwz",
+ error: "did:key: secp256k1 public keys are not supported",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#bls-12381
+ {
+ name: "bls12381",
+ did: "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
+ error: "did:key: bls12381 public keys are not supported",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#p-256
+ {
+ name: "secp256",
+ did: "did:key:zDnaeucDGfhXHoJVqot3p21RuupNJ2fZrs8Lb1GV83VnSo2jR",
+ jwk: map[string]interface{}{
+ "kty": "EC",
+ "crv": "P-256",
+ "x": "sYLQHOy9TNAWwFcAlpxkqRA5OutpWCrVPEWsgeli_KA",
+ "y": "l5Jr9_48oPJWHwuVmH_VZVquGe-U8RtnR-McN4tdYhs",
+ },
+ },
+ {
+ name: "secp256 (invalid length)",
+ did: createDIDKey(multicodec.P256Pub, []byte{1, 2, 3}),
+ error: "invalid did:key: invalid public key length",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#p-384
+ {
+ name: "secp384",
+ did: "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
+ jwk: map[string]interface{}{
+ "kty": "EC",
+ "crv": "P-384",
+ "x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
+ "y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv",
+ },
+ },
+ {
+ name: "secp384 (invalid length)",
+ did: createDIDKey(multicodec.P384Pub, []byte{1, 2, 3}),
+ error: "invalid did:key: invalid public key length",
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#p-521
+ {
+ name: "secp521",
+ did: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
+ jwk: map[string]interface{}{
+ "kty": "EC",
+ "crv": "P-521",
+ "x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
+ "y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC",
+ },
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#rsa-2048
+ {
+ name: "rsa2048",
+ did: "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
+ jwk: map[string]interface{}{
+ "kty": "RSA",
+ "e": "AQAB",
+ "n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
+ },
+ },
+ // Taken from https://w3c-ccg.github.io/did-method-key/#rsa-4096
+ {
+ name: "rsa4096",
+ did: "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
+ jwk: map[string]interface{}{
+ "kty": "RSA",
+ "e": "AQAB",
+ "n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
+ },
+ },
+ {
+ name: "rsa (invalid key)",
+ did: createDIDKey(multicodec.RsaPub, []byte{1, 2, 3}),
+ error: "did:key: invalid PKCS#1 encoded RSA public key: asn1: structure error: tags don't match (16 vs {class:0 tag:1 length:2 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} pkcs1PublicKey @2",
+ },
+ {
+ name: "rsa (key too small)",
+ did: createDIDKey(multicodec.RsaPub, unsafeRSAKeyBytes),
+ error: "did:key: RSA public key is too small (must be at least 2048 bits)",
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ doc, md, err := resolver.Resolve(did.MustParseDID(tc.did), nil)
+ if tc.error != "" {
+ require.EqualError(t, err, tc.error)
+ return
+ }
+ require.NoError(t, err)
+ require.NotNil(t, doc)
+ require.NotNil(t, md)
+ // Assert getting the public key
+ vm := doc.VerificationMethod[0]
+ publicKey, err := vm.PublicKey()
+ require.NoError(t, err, "failed to get public key")
+ require.NotNil(t, publicKey, "public key is nil")
+ // Assert JWK type
+ jwk, err := vm.JWK()
+
+ require.NoError(t, err, "failed to get JWK")
+ jwkJSON, _ := json.Marshal(jwk)
+ var jwkAsMap map[string]interface{}
+ _ = json.Unmarshal(jwkJSON, &jwkAsMap)
+ assert.Equal(t, tc.jwk, jwkAsMap)
+ })
+ }
+}
+
+func TestResolver_Resolve(t *testing.T) {
+ t.Run("did:key ID does not start with 'z' (invalid multibase encoding)", func(t *testing.T) {
+ _, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:foo"), nil)
+ require.EqualError(t, err, "did:key does not start with 'z'")
+ })
+ t.Run("did:key ID is not valid base58btc encoded 'z'", func(t *testing.T) {
+ _, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:z291830129"), nil)
+ require.EqualError(t, err, "did:key: invalid base58btc: invalid base58 string")
+ })
+ t.Run("invalid multicodec key type", func(t *testing.T) {
+ _, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:z"), nil)
+ require.EqualError(t, err, "did:key: invalid multicodec value: EOF")
+ })
+ t.Run("unsupported key type", func(t *testing.T) {
+ didKey := createDIDKey(multicodec.Aes256, []byte{1, 2, 3})
+ _, _, err := Resolver{}.Resolve(did.MustParseDID(didKey), nil)
+ require.EqualError(t, err, "did:key: unsupported public key type: 0xa2")
+ })
+ t.Run("verify created DID document", func(t *testing.T) {
+ const expected = `
+{
+ "@context": [
+ "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "assertionMethod": [
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
+ ],
+ "authentication": [
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
+ ],
+ "capabilityDelegation": [
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
+ ],
+ "capabilityInvocation": [
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
+ ],
+ "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+ "keyAgreement": [
+ "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
+ ],
+ "verificationMethod": [
+ {
+ "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+ "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+ "publicKeyJwk": {
+ "crv": "Ed25519",
+ "kty": "OKP",
+ "x": "Lm_M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY"
+ },
+ "type": "JsonWebKey2020"
+ }
+ ]
+}
+`
+ doc, md, err := Resolver{}.Resolve(did.MustParseDID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), nil)
+ require.NoError(t, err)
+ require.NotNil(t, doc)
+ require.NotNil(t, md)
+ docJSON, _ := doc.MarshalJSON()
+ assert.JSONEq(t, expected, string(docJSON))
+ // Test the public key
+ publicKey, err := doc.VerificationMethod[0].PublicKey()
+ require.NoError(t, err)
+ require.IsType(t, ed25519.PublicKey{}, publicKey)
+ })
+}
+
+func TestNewResolver(t *testing.T) {
+ assert.NotNil(t, NewResolver())
+}
+
+func createDIDKey(keyType multicodec.Code, data []byte) string {
+ mcBytes := append(binary.AppendUvarint([]byte{}, uint64(keyType)), data...)
+ return "did:key:z" + string(base58.Encode(mcBytes, base58.BitcoinAlphabet))
+}
+
+func TestRoundTrip(t *testing.T) {
+ t.Run("secp384", func(t *testing.T) {
+ keyPair, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+ data := elliptic.MarshalCompressed(elliptic.P384(), keyPair.PublicKey.X, keyPair.PublicKey.Y)
+ key := createDIDKey(multicodec.P384Pub, data)
+ _, _, err := Resolver{}.Resolve(did.MustParseDID(key), nil)
+ require.NoError(t, err)
+ })
+}
diff --git a/vdr/vdr.go b/vdr/vdr.go
index 257f146d77..341532ac0a 100644
--- a/vdr/vdr.go
+++ b/vdr/vdr.go
@@ -38,6 +38,7 @@ import (
"github.com/nuts-foundation/nuts-node/network"
"github.com/nuts-foundation/nuts-node/storage"
"github.com/nuts-foundation/nuts-node/vdr/didjwk"
+ "github.com/nuts-foundation/nuts-node/vdr/didkey"
"github.com/nuts-foundation/nuts-node/vdr/didnuts"
didnutsStore "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore"
"github.com/nuts-foundation/nuts-node/vdr/didweb"
@@ -139,6 +140,7 @@ func (r *Module) Configure(_ core.ServerConfig) error {
r.didResolver.Register(didnuts.MethodName, &didnuts.Resolver{Store: r.store})
r.didResolver.Register(didweb.MethodName, didweb.NewResolver())
r.didResolver.Register(didjwk.MethodName, didjwk.NewResolver())
+ r.didResolver.Register(didkey.MethodName, didkey.NewResolver())
// Initiate the routines for auto-updating the data.
r.networkAmbassador.Configure()
diff --git a/vdr/vdr_test.go b/vdr/vdr_test.go
index 30cab85a79..09c6778d67 100644
--- a/vdr/vdr_test.go
+++ b/vdr/vdr_test.go
@@ -555,6 +555,17 @@ func TestVDR_Configure(t *testing.T) {
require.Len(t, doc.VerificationMethod, 1)
assert.Equal(t, "P-256", doc.VerificationMethod[0].PublicKeyJwk["crv"])
})
+ t.Run("it can resolve using did:key", func(t *testing.T) {
+ instance := NewVDR(nil, nil, nil, nil)
+ err := instance.Configure(core.ServerConfig{})
+ require.NoError(t, err)
+
+ doc, md, err := instance.Resolver().Resolve(did.MustParseDID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), nil)
+
+ assert.NoError(t, err)
+ assert.NotNil(t, doc)
+ assert.NotNil(t, md)
+ })
}
type roundTripperFunc func(*http.Request) (*http.Response, error)