diff --git a/crypto/_testsuite/testsuite.go b/crypto/_testsuite/testsuite.go index a2ae9ae..8b857fb 100644 --- a/crypto/_testsuite/testsuite.go +++ b/crypto/_testsuite/testsuite.go @@ -78,8 +78,14 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har }) t.Run("Equality", func(t *testing.T) { + if !pubImplements[PubT, crypto.PublicKeyToBytes]() { + t.Skip("Public key does not implement crypto.PublicKeyToBytes") + } + pub1, priv1, err := harness.GenerateKeyPair() require.NoError(t, err) + pub1Tb := (crypto.PublicKey(pub1)).(crypto.PublicKeyToBytes) + priv1Tb := (crypto.PrivateKey(priv1)).(crypto.PrivateKeyToBytes) pub2, priv2, err := harness.GenerateKeyPair() require.NoError(t, err) @@ -88,29 +94,35 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har require.False(t, pub1.Equal(pub2)) require.False(t, priv1.Equal(priv2)) - pub1copy, err := harness.PublicKeyFromBytes(pub1.ToBytes()) + pub1copy, err := harness.PublicKeyFromBytes(pub1Tb.ToBytes()) require.NoError(t, err) require.True(t, pub1.Equal(pub1copy)) require.True(t, pub1copy.Equal(pub1)) - priv1copy, err := harness.PrivateKeyFromBytes(priv1.ToBytes()) + priv1copy, err := harness.PrivateKeyFromBytes(priv1Tb.ToBytes()) require.NoError(t, err) require.True(t, priv1.Equal(priv1copy)) require.True(t, priv1copy.Equal(priv1)) }) t.Run("BytesRoundTrip", func(t *testing.T) { + if !pubImplements[PubT, crypto.PublicKeyToBytes]() { + t.Skip("Public key does not implement crypto.PublicKeyToBytes") + } + pub, priv, err := harness.GenerateKeyPair() require.NoError(t, err) + pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes) + privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes) - bytes := pub.ToBytes() + bytes := pubTb.ToBytes() stats.bytesPubSize = len(bytes) rtPub, err := harness.PublicKeyFromBytes(bytes) require.NoError(t, err) require.True(t, pub.Equal(rtPub)) require.Equal(t, harness.PublicKeyBytesSize, len(bytes)) - bytes = priv.ToBytes() + bytes = privTb.ToBytes() stats.bytesPrivSize = len(bytes) rtPriv, err := harness.PrivateKeyFromBytes(bytes) require.NoError(t, err) @@ -175,11 +187,11 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har pub, priv, err := harness.GenerateKeyPair() require.NoError(t, err) - spub, ok := (crypto.PublicKey(pub)).(crypto.SigningPublicKey) + spub, ok := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) if !ok { t.Skip("Signature is not implemented") } - spriv, ok := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey) + spriv, ok := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) if !ok { t.Skip("Signature is not implemented") } @@ -234,11 +246,11 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har pub3, _, err := harness.GenerateKeyPair() require.NoError(t, err) - kePriv1, ok := crypto.PrivateKey(priv1).(crypto.KeyExchangePrivateKey) + kePriv1, ok := crypto.PrivateKey(priv1).(crypto.PrivateKeyKeyExchange) if !ok { t.Skip("Key exchange is not implemented") } - kePriv2 := crypto.PrivateKey(priv2).(crypto.KeyExchangePrivateKey) + kePriv2 := crypto.PrivateKey(priv2).(crypto.PrivateKeyKeyExchange) // TODO: test with incompatible public keys require.True(t, kePriv1.PublicKeyIsCompatible(pub2)) @@ -271,20 +283,26 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Bytes", func(b *testing.B) { + if !pubImplements[PubT, crypto.PublicKeyToBytes]() { + b.Skip("Public key does not implement crypto.PublicKeyToBytes") + } + b.Run("PubToBytes", func(b *testing.B) { pub, _, err := harness.GenerateKeyPair() require.NoError(b, err) + pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _ = pub.ToBytes() + _ = pubTb.ToBytes() } }) b.Run("PubFromBytes", func(b *testing.B) { pub, _, err := harness.GenerateKeyPair() require.NoError(b, err) - buf := pub.ToBytes() + pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes) + buf := pubTb.ToBytes() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -295,17 +313,19 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha b.Run("PrivToBytes", func(b *testing.B) { _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) + privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _ = priv.ToBytes() + _ = privTb.ToBytes() } }) b.Run("PrivFromBytes", func(b *testing.B) { _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - buf := priv.ToBytes() + privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes) + buf := privTb.ToBytes() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -403,7 +423,7 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Signatures", func(b *testing.B) { - if _, ok := (crypto.PublicKey(*new(PubT))).(crypto.SigningPublicKey); !ok { + if !pubImplements[PubT, crypto.PublicKeySigning]() { b.Skip("Signature is not implemented") } @@ -411,7 +431,7 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) b.ResetTimer() b.ReportAllocs() @@ -425,8 +445,8 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha pub, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey) - spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey) + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) sig, err := spriv.SignToBytes([]byte("message")) require.NoError(b, err) @@ -442,7 +462,7 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) b.ResetTimer() b.ReportAllocs() @@ -456,8 +476,8 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha pub, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey) - spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey) + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) sig, err := spriv.SignToASN1([]byte("message")) require.NoError(b, err) @@ -471,14 +491,14 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Key exchange", func(b *testing.B) { - if _, ok := (crypto.PrivateKey(*new(PrivT))).(crypto.KeyExchangePrivateKey); !ok { + if !privImplements[PrivT, crypto.PrivateKeyKeyExchange]() { b.Skip("Key echange is not implemented") } b.Run("KeyExchange", func(b *testing.B) { _, priv1, err := harness.GenerateKeyPair() require.NoError(b, err) - kePriv1 := (crypto.PrivateKey(priv1)).(crypto.KeyExchangePrivateKey) + kePriv1 := (crypto.PrivateKey(priv1)).(crypto.PrivateKeyKeyExchange) pub2, _, err := harness.GenerateKeyPair() require.NoError(b, err) @@ -491,3 +511,13 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) }) } + +func privImplements[PrivT crypto.PrivateKey, wanted crypto.PrivateKey]() bool { + _, ok := crypto.PrivateKey(*new(PrivT)).(wanted) + return ok +} + +func pubImplements[PubT crypto.PublicKey, wanted crypto.PublicKey]() bool { + _, ok := crypto.PublicKey(*new(PubT)).(wanted) + return ok +} diff --git a/crypto/ed25519/private.go b/crypto/ed25519/private.go index 7bd2aed..83f071a 100644 --- a/crypto/ed25519/private.go +++ b/crypto/ed25519/private.go @@ -11,7 +11,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.SigningPrivateKey = &PrivateKey{} +var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeyToBytes = &PrivateKey{} type PrivateKey struct { k ed25519.PrivateKey diff --git a/crypto/ed25519/public.go b/crypto/ed25519/public.go index e0f24bc..53065ee 100644 --- a/crypto/ed25519/public.go +++ b/crypto/ed25519/public.go @@ -13,7 +13,8 @@ import ( "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.SigningPublicKey = &PublicKey{} +var _ crypto.PublicKeySigning = PublicKey{} +var _ crypto.PublicKeyToBytes = PublicKey{} type PublicKey struct { k ed25519.PublicKey diff --git a/crypto/interface.go b/crypto/interface.go index 58f19c5..1bd00f4 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -1,14 +1,11 @@ package crypto +// Public Key + type PublicKey interface { // Equal returns true if other is the same PublicKey Equal(other PublicKey) bool - // ToBytes serializes the PublicKey into "raw bytes", without metadata or structure. - // This format can make some assumptions and may not be what you expect. - // Ideally, this format is defined by the same specification as the underlying crypto scheme. - ToBytes() []byte - // ToPublicKeyMultibase format the PublicKey into a string compatible with a PublicKeyMultibase field // in a DID Document. ToPublicKeyMultibase() string @@ -20,26 +17,16 @@ type PublicKey interface { ToX509PEM() string } -type PrivateKey interface { - // Equal returns true if other is the same PrivateKey - Equal(other PrivateKey) bool +type PublicKeyToBytes interface { + PublicKey - // Public returns the matching PublicKey. - Public() PublicKey - - // ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure. + // ToBytes serializes the PublicKey into "raw bytes", without metadata or structure. // This format can make some assumptions and may not be what you expect. // Ideally, this format is defined by the same specification as the underlying crypto scheme. ToBytes() []byte - - // ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format. - ToPKCS8DER() []byte - - // ToPKCS8PEM serializes the PrivateKey into the PKCS#8 PEM (string) format. - ToPKCS8PEM() string } -type SigningPublicKey interface { +type PublicKeySigning interface { PublicKey // VerifyBytes checks a signature in the "raw bytes" format. @@ -51,7 +38,32 @@ type SigningPublicKey interface { VerifyASN1(message, signature []byte) bool } -type SigningPrivateKey interface { +// Private Key + +type PrivateKey interface { + // Equal returns true if other is the same PrivateKey + Equal(other PrivateKey) bool + + // Public returns the matching PublicKey. + Public() PublicKey + + // ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format. + ToPKCS8DER() []byte + + // ToPKCS8PEM serializes the PrivateKey into the PKCS#8 PEM (string) format. + ToPKCS8PEM() string +} + +type PrivateKeyToBytes interface { + PrivateKey + + // ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure. + // This format can make some assumptions and may not be what you expect. + // Ideally, this format is defined by the same specification as the underlying crypto scheme. + ToBytes() []byte +} + +type PrivateKeySigning interface { PrivateKey // SignToBytes creates a signature in the "raw bytes" format. @@ -63,7 +75,7 @@ type SigningPrivateKey interface { SignToASN1(message []byte) ([]byte, error) } -type KeyExchangePrivateKey interface { +type PrivateKeyKeyExchange interface { PrivateKey // PublicKeyIsCompatible checks that the given PublicKey is compatible to perform key exchange. diff --git a/crypto/p256/private.go b/crypto/p256/private.go index ce3ffc7..6ca8f0f 100644 --- a/crypto/p256/private.go +++ b/crypto/p256/private.go @@ -13,8 +13,9 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.SigningPrivateKey = (*PrivateKey)(nil) -var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil) +var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeyToBytes = &PrivateKey{} +var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} type PrivateKey struct { k *ecdsa.PrivateKey @@ -96,7 +97,7 @@ func (p *PrivateKey) ToPKCS8PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA256, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA256, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/p256/public.go b/crypto/p256/public.go index 8e20e17..5c75ba9 100644 --- a/crypto/p256/public.go +++ b/crypto/p256/public.go @@ -13,7 +13,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.SigningPublicKey = &PublicKey{} +var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { k *ecdsa.PublicKey @@ -124,7 +125,7 @@ func (p *PublicKey) ToX509PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA256, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA256, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/p384/private.go b/crypto/p384/private.go index a258446..ef4fabe 100644 --- a/crypto/p384/private.go +++ b/crypto/p384/private.go @@ -13,8 +13,9 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.SigningPrivateKey = (*PrivateKey)(nil) -var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil) +var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeyToBytes = &PrivateKey{} +var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} type PrivateKey struct { k *ecdsa.PrivateKey @@ -96,7 +97,7 @@ func (p *PrivateKey) ToPKCS8PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA384, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA384, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/p384/public.go b/crypto/p384/public.go index c9e984a..c3f405d 100644 --- a/crypto/p384/public.go +++ b/crypto/p384/public.go @@ -13,7 +13,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.SigningPublicKey = &PublicKey{} +var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { k *ecdsa.PublicKey @@ -124,7 +125,7 @@ func (p *PublicKey) ToX509PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA384, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA384, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/p521/private.go b/crypto/p521/private.go index 253f850..a33353e 100644 --- a/crypto/p521/private.go +++ b/crypto/p521/private.go @@ -13,8 +13,9 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.SigningPrivateKey = (*PrivateKey)(nil) -var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil) +var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeyToBytes = &PrivateKey{} +var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} type PrivateKey struct { k *ecdsa.PrivateKey @@ -96,7 +97,7 @@ func (p *PrivateKey) ToPKCS8PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA512, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA512, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/p521/public.go b/crypto/p521/public.go index ac48c1c..33d09e1 100644 --- a/crypto/p521/public.go +++ b/crypto/p521/public.go @@ -13,7 +13,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.SigningPublicKey = &PublicKey{} +var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { k *ecdsa.PublicKey @@ -124,7 +125,7 @@ func (p *PublicKey) ToX509PEM() string { } /* - Note: signatures for the crypto.SigningPrivateKey interface assumes SHA512, + Note: signatures for the crypto.PrivateKeySigning interface assumes SHA512, which should be correct almost always. If there is a need to use a different hash function, we can add separate functions that have that flexibility. */ diff --git a/crypto/x25519/private.go b/crypto/x25519/private.go index 34c063b..63b692f 100644 --- a/crypto/x25519/private.go +++ b/crypto/x25519/private.go @@ -11,7 +11,7 @@ import ( "github.com/INFURA/go-did/crypto/ed25519" ) -var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil) +var _ crypto.PrivateKeyKeyExchange = (*PrivateKey)(nil) type PrivateKey struct { k *ecdh.PrivateKey diff --git a/interfaces.go b/interfaces.go index 60eb450..6ac8da4 100644 --- a/interfaces.go +++ b/interfaces.go @@ -111,8 +111,8 @@ type VerificationMethodKeyAgreement interface { VerificationMethod // PrivateKeyIsCompatible checks that the given PrivateKey is compatible with this method. - PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool + PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool // KeyExchange computes the shared key using the given PrivateKey. - KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) + KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) } diff --git a/utilities.go b/utilities.go index 0835e24..020ace1 100644 --- a/utilities.go +++ b/utilities.go @@ -21,7 +21,7 @@ func TryAllVerify(methods []VerificationMethodSignature, data []byte, sig []byte // FindMatchingKeyAgreement tries to find a matching key agreement method for the given private key type. // It returns the shared key as well as the selected method. // If no matching method is found, it returns an error. -func FindMatchingKeyAgreement(methods []VerificationMethodKeyAgreement, priv crypto.KeyExchangePrivateKey) ([]byte, VerificationMethodKeyAgreement, error) { +func FindMatchingKeyAgreement(methods []VerificationMethodKeyAgreement, priv crypto.PrivateKeyKeyExchange) ([]byte, VerificationMethodKeyAgreement, error) { for _, method := range methods { if method.PrivateKeyIsCompatible(priv) { key, err := method.KeyExchange(priv) diff --git a/verifications/jsonwebkey/JsonWebKey2020.go b/verifications/jsonwebkey/JsonWebKey2020.go index 87b3a93..9ca4700 100644 --- a/verifications/jsonwebkey/JsonWebKey2020.go +++ b/verifications/jsonwebkey/JsonWebKey2020.go @@ -9,7 +9,9 @@ import ( "github.com/INFURA/go-did/crypto/jwk" ) -// Specification: https://www.w3.org/TR/vc-jws-2020/ +// Specification: +// - https://www.w3.org/TR/vc-jws-2020/ +// - https://w3c-ccg.github.io/lds-jws2020/ const ( JsonLdContext = "https://w3id.org/security/suites/jws-2020/v1" @@ -92,16 +94,16 @@ func (j JsonWebKey2020) JsonLdContext() string { } func (j JsonWebKey2020) Verify(data []byte, sig []byte) (bool, error) { - if pub, ok := j.pubkey.(crypto.SigningPublicKey); ok { + if pub, ok := j.pubkey.(crypto.PublicKeySigning); ok { return pub.VerifyBytes(data, sig), nil } return false, errors.New("not a signing public key") } -func (j JsonWebKey2020) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool { +func (j JsonWebKey2020) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool { return local.PublicKeyIsCompatible(j.pubkey) } -func (j JsonWebKey2020) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) { +func (j JsonWebKey2020) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) { return local.KeyExchange(j.pubkey) } diff --git a/verifications/multikey/multikey.go b/verifications/multikey/multikey.go index eb9d1cb..a72992b 100644 --- a/verifications/multikey/multikey.go +++ b/verifications/multikey/multikey.go @@ -96,16 +96,16 @@ func (m MultiKey) JsonLdContext() string { } func (m MultiKey) Verify(data []byte, sig []byte) (bool, error) { - if pub, ok := m.pubkey.(crypto.SigningPublicKey); ok { + if pub, ok := m.pubkey.(crypto.PublicKeySigning); ok { return pub.VerifyBytes(data, sig), nil } return false, errors.New("not a signing public key") } -func (m MultiKey) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool { +func (m MultiKey) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool { return local.PublicKeyIsCompatible(m.pubkey) } -func (m MultiKey) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) { +func (m MultiKey) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) { return local.KeyExchange(m.pubkey) }