add support for the Multikey verification method
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -3,10 +3,10 @@ package ed25519
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
"github.com/INFURA/go-did/crypto/_testsuite"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[PublicKey, PrivateKey]{
|
||||
var harness = testsuite.TestHarness[PublicKey, PrivateKey]{
|
||||
Name: "ed25519",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
@@ -23,9 +23,9 @@ var harness = helpers.TestHarness[PublicKey, PrivateKey]{
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
testsuite.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
testsuite.BenchSuite(b, harness)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
"github.com/INFURA/go-did/crypto/_helpers"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPublicKey = &PublicKey{}
|
||||
|
||||
@@ -3,10 +3,10 @@ package p256
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
"github.com/INFURA/go-did/crypto/_testsuite"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
|
||||
Name: "p256",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
@@ -23,9 +23,9 @@ var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
testsuite.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
testsuite.BenchSuite(b, harness)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
helpers "github.com/INFURA/go-did/crypto/_helpers"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPublicKey = (*PublicKey)(nil)
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/_testsuite"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
|
||||
Name: "x25519",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
@@ -26,11 +26,11 @@ var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
testsuite.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
testsuite.BenchSuite(b, harness)
|
||||
}
|
||||
|
||||
func TestEd25519ToX25519(t *testing.T) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
helpers "github.com/INFURA/go-did/crypto/_helpers"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var _ crypto.PublicKey = (*PublicKey)(nil)
|
||||
|
||||
@@ -102,7 +102,7 @@ type VerificationMethodSignature interface {
|
||||
VerificationMethod
|
||||
|
||||
// Verify checks that 'sig' is a valid signature of 'data'.
|
||||
Verify(data []byte, sig []byte) bool
|
||||
Verify(data []byte, sig []byte) (bool, error)
|
||||
}
|
||||
|
||||
// VerificationMethodKeyAgreement is a VerificationMethod implementing a shared key agreement.
|
||||
|
||||
@@ -4,15 +4,14 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-varint"
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/_helpers"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/p256"
|
||||
"github.com/INFURA/go-did/crypto/x25519"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
"github.com/INFURA/go-did/verifications/multikey"
|
||||
"github.com/INFURA/go-did/verifications/x25519"
|
||||
)
|
||||
|
||||
@@ -34,66 +33,54 @@ func Decode(identifier string) (did.DID, error) {
|
||||
const keyPrefix = "did:key:"
|
||||
|
||||
if !strings.HasPrefix(identifier, keyPrefix) {
|
||||
return nil, fmt.Errorf("must start with 'did:key'")
|
||||
return nil, fmt.Errorf("%w: must start with 'did:key'", did.ErrInvalidDid)
|
||||
}
|
||||
|
||||
msi := identifier[len(keyPrefix):]
|
||||
|
||||
baseCodec, bytes, err := mbase.Decode(msi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
// the specification enforces that encoding
|
||||
if baseCodec != mbase.Base58BTC {
|
||||
return nil, fmt.Errorf("%w: not Base58BTC encoded", did.ErrInvalidDid)
|
||||
}
|
||||
code, read, err := varint.FromUvarint(bytes)
|
||||
code, bytes, err := helpers.PublicKeyMultibaseDecode(msi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
|
||||
switch code {
|
||||
case ed25519.MultibaseCode:
|
||||
pub, err := ed25519.PublicKeyFromBytes(bytes[read:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
return FromPublicKey(pub)
|
||||
case p256.MultibaseCode:
|
||||
pub, err := p256.PublicKeyFromBytes(bytes[read:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
return FromPublicKey(pub)
|
||||
|
||||
// case Secp256k1: // TODO
|
||||
// case RSA: // TODO
|
||||
decoder, ok := map[uint64]func(b []byte) (crypto.PublicKey, error){
|
||||
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
|
||||
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
|
||||
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
|
||||
}[code]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
|
||||
pub, err := decoder(bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
d, err := FromPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func FromPublicKey(pub crypto.PublicKey) (did.DID, error) {
|
||||
var err error
|
||||
switch pub := pub.(type) {
|
||||
case ed25519.PublicKey:
|
||||
d := DidKey{msi: pub.ToPublicKeyMultibase()}
|
||||
d.signature, err = ed25519vm.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
|
||||
d.signature = ed25519vm.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
|
||||
xpub, err := x25519.PublicKeyFromEd25519(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xpub, err := x25519.PublicKeyFromEd25519(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
xmsi := xpub.ToPublicKeyMultibase()
|
||||
d.keyAgreement, err = x25519vm.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, xmsi), xpub, d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
|
||||
}
|
||||
d.keyAgreement = x25519vm.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, xmsi), xpub, d)
|
||||
return d, nil
|
||||
case *p256.PublicKey:
|
||||
d := DidKey{msi: pub.ToPublicKeyMultibase()}
|
||||
mk := multikey.NewMultiKey(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
|
||||
d.signature = mk
|
||||
d.keyAgreement = mk
|
||||
return d, nil
|
||||
// case *p256.PublicKey:
|
||||
// d := DidKey{msi: pub.ToPublicKeyMultibase()}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported public key: %T", pub)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// If no method verifies the signature, it returns false and nil.
|
||||
func TryAllVerify(methods []VerificationMethodSignature, data []byte, sig []byte) (bool, VerificationMethodSignature) {
|
||||
for _, method := range methods {
|
||||
if method.Verify(data, sig) {
|
||||
if valid, err := method.Verify(data, sig); err == nil && valid {
|
||||
return true, method
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ type VerificationKey2020 struct {
|
||||
controller string
|
||||
}
|
||||
|
||||
func NewVerificationKey2020(id string, pubkey ed25519.PublicKey, controller did.DID) (*VerificationKey2020, error) {
|
||||
func NewVerificationKey2020(id string, pubkey ed25519.PublicKey, controller did.DID) *VerificationKey2020 {
|
||||
return &VerificationKey2020{
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
controller: controller.String(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v VerificationKey2020) MarshalJSON() ([]byte, error) {
|
||||
@@ -91,6 +91,6 @@ func (v VerificationKey2020) JsonLdContext() string {
|
||||
return JsonLdContext
|
||||
}
|
||||
|
||||
func (v VerificationKey2020) Verify(data []byte, sig []byte) bool {
|
||||
return v.pubkey.VerifyBytes(data, sig)
|
||||
func (v VerificationKey2020) Verify(data []byte, sig []byte) (bool, error) {
|
||||
return v.pubkey.VerifyBytes(data, sig), nil
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@ func TestSignature(t *testing.T) {
|
||||
|
||||
contDid := "did:key:" + pk.ToPublicKeyMultibase()
|
||||
controller := did.MustParse(contDid)
|
||||
vk, err := ed25519vm.NewVerificationKey2020("foo", pk, controller)
|
||||
require.NoError(t, err)
|
||||
vk := ed25519vm.NewVerificationKey2020("foo", pk, controller)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
@@ -78,7 +77,9 @@ func TestSignature(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.valid, vk.Verify(tc.data, tc.signature))
|
||||
valid, err := vk.Verify(tc.data, tc.signature)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.valid, valid)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
"github.com/INFURA/go-did/verifications/multikey"
|
||||
"github.com/INFURA/go-did/verifications/x25519"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,8 @@ func UnmarshalJSON(data []byte) (did.VerificationMethod, error) {
|
||||
switch aux.Type {
|
||||
case ed25519vm.Type:
|
||||
res = &ed25519vm.VerificationKey2020{}
|
||||
case multikey.Type:
|
||||
res = &multikey.MultiKey{}
|
||||
case x25519vm.Type:
|
||||
res = &x25519vm.KeyAgreementKey2020{}
|
||||
default:
|
||||
|
||||
126
verifications/multikey/multikey.go
Normal file
126
verifications/multikey/multikey.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package multikey
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
helpers "github.com/INFURA/go-did/crypto/_helpers"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/p256"
|
||||
"github.com/INFURA/go-did/crypto/x25519"
|
||||
)
|
||||
|
||||
// Specification: https://www.w3.org/TR/cid-1.0/#Multikey
|
||||
|
||||
const (
|
||||
JsonLdContext = "https://www.w3.org/ns/cid/v1"
|
||||
Type = "Multikey"
|
||||
)
|
||||
|
||||
var _ did.VerificationMethodSignature = &MultiKey{}
|
||||
var _ did.VerificationMethodKeyAgreement = &MultiKey{}
|
||||
|
||||
type MultiKey struct {
|
||||
id string
|
||||
pubkey crypto.PublicKey
|
||||
controller string
|
||||
}
|
||||
|
||||
func NewMultiKey(id string, pubkey crypto.PublicKey, controller did.DID) *MultiKey {
|
||||
return &MultiKey{
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
controller: controller.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m MultiKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Controller string `json:"controller"`
|
||||
PublicKeyMultibase string `json:"publicKeyMultibase"`
|
||||
}{
|
||||
ID: m.ID(),
|
||||
Type: m.Type(),
|
||||
Controller: m.Controller(),
|
||||
PublicKeyMultibase: m.pubkey.ToPublicKeyMultibase(),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *MultiKey) UnmarshalJSON(bytes []byte) error {
|
||||
aux := struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Controller string `json:"controller"`
|
||||
PublicKeyMultibase string `json:"publicKeyMultibase"`
|
||||
}{}
|
||||
err := json.Unmarshal(bytes, &aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if aux.Type != m.Type() {
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
m.id = aux.ID
|
||||
if len(m.id) == 0 {
|
||||
return errors.New("invalid id")
|
||||
}
|
||||
|
||||
code, pubBytes, err := helpers.PublicKeyMultibaseDecode(aux.PublicKeyMultibase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
|
||||
}
|
||||
decoder, ok := map[uint64]func(b []byte) (crypto.PublicKey, error){
|
||||
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
|
||||
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
|
||||
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
|
||||
}[code]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported publicKeyMultibase code: %d", code)
|
||||
}
|
||||
m.pubkey, err = decoder(pubBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
|
||||
}
|
||||
|
||||
m.controller = aux.Controller
|
||||
if !did.HasValidDIDSyntax(m.controller) {
|
||||
return errors.New("invalid controller")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MultiKey) ID() string {
|
||||
return m.id
|
||||
}
|
||||
|
||||
func (m MultiKey) Type() string {
|
||||
return Type
|
||||
}
|
||||
|
||||
func (m MultiKey) Controller() string {
|
||||
return m.controller
|
||||
}
|
||||
|
||||
func (m MultiKey) JsonLdContext() string {
|
||||
return JsonLdContext
|
||||
}
|
||||
|
||||
func (m MultiKey) Verify(data []byte, sig []byte) (bool, error) {
|
||||
if pub, ok := m.pubkey.(crypto.SigningPublicKey); ok {
|
||||
return pub.VerifyBytes(data, sig), nil
|
||||
}
|
||||
return false, errors.New("not a signing public key")
|
||||
}
|
||||
|
||||
func (m MultiKey) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool {
|
||||
return local.PublicKeyIsCompatible(m.pubkey)
|
||||
}
|
||||
|
||||
func (m MultiKey) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) {
|
||||
return local.KeyExchange(m.pubkey)
|
||||
}
|
||||
28
verifications/multikey/multikey_test.go
Normal file
28
verifications/multikey/multikey_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package multikey_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
_ "github.com/INFURA/go-did/methods/did-key"
|
||||
"github.com/INFURA/go-did/verifications/multikey"
|
||||
)
|
||||
|
||||
func TestJsonRoundTrip(t *testing.T) {
|
||||
data := `{
|
||||
"id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
||||
"type": "Multikey",
|
||||
"controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
||||
"publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
||||
}`
|
||||
|
||||
var mk multikey.MultiKey
|
||||
err := json.Unmarshal([]byte(data), &mk)
|
||||
require.NoError(t, err)
|
||||
|
||||
bytes, err := json.Marshal(mk)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, data, string(bytes))
|
||||
}
|
||||
@@ -25,12 +25,12 @@ type KeyAgreementKey2020 struct {
|
||||
controller string
|
||||
}
|
||||
|
||||
func NewKeyAgreementKey2020(id string, pubkey *x25519.PublicKey, controller did.DID) (*KeyAgreementKey2020, error) {
|
||||
func NewKeyAgreementKey2020(id string, pubkey *x25519.PublicKey, controller did.DID) *KeyAgreementKey2020 {
|
||||
return &KeyAgreementKey2020{
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
controller: controller.String(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (k KeyAgreementKey2020) MarshalJSON() ([]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user