diff --git a/did/document.go b/did/document.go index 6f5fb31..a3f31dc 100644 --- a/did/document.go +++ b/did/document.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/multiformats/go-multibase" "github.com/nuts-foundation/go-did" @@ -303,9 +304,11 @@ func (s Service) UnmarshalServiceEndpoint(target interface{}) error { // VerificationMethod represents a DID Verification Method as specified by the DID Core specification (https://www.w3.org/TR/did-core/#verification-methods). type VerificationMethod struct { - ID DID `json:"id"` - Type ssi.KeyType `json:"type,omitempty"` - Controller DID `json:"controller,omitempty"` + ID DID `json:"id"` + Type ssi.KeyType `json:"type,omitempty"` + Controller DID `json:"controller,omitempty"` + PublicKeyMultibase string `json:"publicKeyMultibase,omitempty"` + // PublicKeyBase58 is deprecated and should not be used anymore. Use PublicKeyMultibase or PublicKeyJwk instead. PublicKeyBase58 string `json:"publicKeyBase58,omitempty"` PublicKeyJwk map[string]interface{} `json:"publicKeyJwk,omitempty"` } @@ -343,8 +346,11 @@ func NewVerificationMethod(id DID, keyType ssi.KeyType, controller DID, key cryp if !ok { return nil, errors.New("wrong key type") } - encodedKey := base58.Encode(ed25519Key, base58.BitcoinAlphabet) - vm.PublicKeyBase58 = encodedKey + encodedKey, err := multibase.Encode(multibase.Base58BTC, ed25519Key) + if err != nil { + return nil, err + } + vm.PublicKeyMultibase = encodedKey } return vm, nil @@ -367,9 +373,20 @@ func (v VerificationMethod) PublicKey() (crypto.PublicKey, error) { var pubKey crypto.PublicKey switch v.Type { case ssi.ED25519VerificationKey2018: - keyBytes, err := base58.Decode(v.PublicKeyBase58, base58.BitcoinAlphabet) - if err != nil { - return nil, err + var keyBytes []byte + var err error + if v.PublicKeyMultibase != "" { + _, keyBytes, err = multibase.Decode(v.PublicKeyMultibase) + if err != nil { + return nil, fmt.Errorf("publicKeyMultibase decode error: %w", err) + } + } else if v.PublicKeyBase58 != "" { + keyBytes, err = base58.Decode(v.PublicKeyBase58, base58.BitcoinAlphabet) + if err != nil { + return nil, fmt.Errorf("publicKeyBase58 decode error: %w", err) + } + } else { + return nil, errors.New("expected either publicKeyMultibase or publicKeyBase58 to be set") } return ed25519.PublicKey(keyBytes), err case ssi.JsonWebKey2020: @@ -429,6 +446,20 @@ func (v *VerificationMethod) UnmarshalJSON(bytes []byte) error { if err != nil { return err } + // publicKeyJWK, publicKeyBase58 and publicKeyMultibase are all mutually exclusive + countPresent := 0 + if len(tmp.PublicKeyJwk) > 0 { + countPresent++ + } + if len(tmp.PublicKeyBase58) > 0 { + countPresent++ + } + if len(tmp.PublicKeyMultibase) > 0 { + countPresent++ + } + if countPresent > 1 { + return errors.New("only one of publicKeyJWK, publicKeyBase58 and publicKeyMultibase can be present") + } *v = (VerificationMethod)(tmp) return nil } diff --git a/did/document_test.go b/did/document_test.go index 37cafc1..5cc3ceb 100644 --- a/did/document_test.go +++ b/did/document_test.go @@ -425,6 +425,48 @@ func TestVerificationRelationship_UnmarshalJSON(t *testing.T) { }) } +func TestNewVerificationMethod(t *testing.T) { + t.Run("Ed25519VerificationKey2018", func(t *testing.T) { + id, _ := ParseDID("did:example:123") + expectedKey, _, _ := ed25519.GenerateKey(rand.Reader) + vm, err := NewVerificationMethod(*id, ssi.ED25519VerificationKey2018, *id, expectedKey) + require.NoError(t, err) + assert.Equal(t, ssi.ED25519VerificationKey2018, vm.Type) + assert.NotEmpty(t, vm.PublicKeyMultibase) + assert.Empty(t, vm.PublicKeyBase58) + // Unmarshal, check it's equal to the input key + actualKey, err := vm.PublicKey() + require.NoError(t, err) + assert.Equal(t, expectedKey, actualKey) + }) +} + +func TestVerificationMethod_UnmarshalJSON(t *testing.T) { + t.Run("both publicKeyJWK and publicKeyMultibase present", func(t *testing.T) { + input, _ := json.Marshal(VerificationMethod{ + ID: MustParseDIDURL("did:example:123#key-1"), + Controller: MustParseDIDURL("did:example:123"), + PublicKeyJwk: map[string]interface{}{"kty": "EC"}, + PublicKeyMultibase: "foobar", + }) + actual := VerificationMethod{} + err := json.Unmarshal(input, &actual) + assert.EqualError(t, err, "only one of publicKeyJWK, publicKeyBase58 and publicKeyMultibase can be present") + }) + t.Run("all of publicKeyJWK, publicKeyMultibase and publicKeyBase58 are present", func(t *testing.T) { + input, _ := json.Marshal(VerificationMethod{ + ID: MustParseDIDURL("did:example:123#key-1"), + Controller: MustParseDIDURL("did:example:123"), + PublicKeyJwk: map[string]interface{}{"kty": "EC"}, + PublicKeyMultibase: "foobar", + PublicKeyBase58: "foobar", + }) + actual := VerificationMethod{} + err := json.Unmarshal(input, &actual) + assert.EqualError(t, err, "only one of publicKeyJWK, publicKeyBase58 and publicKeyMultibase can be present") + }) +} + func TestService_UnmarshalJSON(t *testing.T) { t.Run("ok", func(t *testing.T) { actual := Service{} diff --git a/go.mod b/go.mod index 33c4f13..5d7680e 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,10 @@ require ( github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mr-tron/base58 v1.1.0 // indirect + github.com/multiformats/go-base32 v0.0.3 // indirect + github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.9.0 // indirect diff --git a/go.sum b/go.sum index 7646568..38f15cb 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,14 @@ github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDu github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=