add a crypto base layer that abstract various keypair type, + test/bench suite
This commit is contained in:
30
crypto/ed25519/key.go
Normal file
30
crypto/ed25519/key.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = ed25519.PublicKeySize
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = ed25519.PrivateKeySize
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = ed25519.SignatureSize
|
||||
|
||||
MultibaseCode = uint64(0xed)
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return PublicKey{}, PrivateKey{}, err
|
||||
}
|
||||
return PublicKey{k: pub}, PrivateKey{k: priv}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
pemPubBlockType = "PUBLIC KEY"
|
||||
pemPrivBlockType = "PRIVATE KEY"
|
||||
)
|
||||
31
crypto/ed25519/key_test.go
Normal file
31
crypto/ed25519/key_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[PublicKey, PrivateKey]{
|
||||
Name: "ed25519",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
|
||||
PublicKeyFromX509DER: PublicKeyFromX509DER,
|
||||
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
|
||||
PrivateKeyFromBytes: PrivateKeyFromBytes,
|
||||
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
|
||||
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
|
||||
MultibaseCode: MultibaseCode,
|
||||
PublicKeySize: PublicKeySize,
|
||||
PrivateKeySize: PrivateKeySize,
|
||||
SignatureSize: SignatureSize,
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
}
|
||||
90
crypto/ed25519/private.go
Normal file
90
crypto/ed25519/private.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPrivateKey = &PrivateKey{}
|
||||
|
||||
type PrivateKey struct {
|
||||
k ed25519.PrivateKey
|
||||
}
|
||||
|
||||
// PrivateKeyFromBytes converts a serialized private key to a PrivateKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if the slice is not the right size.
|
||||
func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
|
||||
if len(b) != PrivateKeySize {
|
||||
return PrivateKey{}, fmt.Errorf("invalid ed25519 private key size")
|
||||
}
|
||||
// make a copy
|
||||
return PrivateKey{k: append([]byte{}, b...)}, nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
|
||||
func PrivateKeyFromPKCS8DER(bytes []byte) (PrivateKey, error) {
|
||||
priv, err := x509.ParsePKCS8PrivateKey(bytes)
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
return PrivateKey{k: priv.(ed25519.PrivateKey)}, nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
|
||||
func PrivateKeyFromPKCS8PEM(str string) (PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return PrivateKey{}, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPrivBlockType {
|
||||
return PrivateKey{}, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PrivateKeyFromPKCS8DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p PrivateKey) Equal(other crypto.PrivateKey) bool {
|
||||
if other, ok := other.(PrivateKey); ok {
|
||||
return p.k.Equal(other.k)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p PrivateKey) Public() crypto.PublicKey {
|
||||
return PublicKey{k: p.k.Public().(ed25519.PublicKey)}
|
||||
}
|
||||
|
||||
func (p PrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
return ed25519.Sign(p.k, message), nil
|
||||
}
|
||||
|
||||
func (p PrivateKey) ToBytes() []byte {
|
||||
// Copy the private key to a fixed size buffer that can get allocated on the
|
||||
// caller's stack after inlining.
|
||||
var buf [PrivateKeySize]byte
|
||||
return append(buf[:0], p.k...)
|
||||
}
|
||||
|
||||
func (p PrivateKey) ToPKCS8DER() []byte {
|
||||
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
|
||||
return res
|
||||
}
|
||||
|
||||
func (p PrivateKey) ToPKCS8PEM() string {
|
||||
der := p.ToPKCS8DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPrivBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
// Seed returns the private key seed corresponding to priv. It is provided for
|
||||
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
|
||||
// in this package.
|
||||
func (p PrivateKey) Seed() []byte {
|
||||
return p.k.Seed()
|
||||
}
|
||||
99
crypto/ed25519/public.go
Normal file
99
crypto/ed25519/public.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPublicKey = &PublicKey{}
|
||||
|
||||
type PublicKey struct {
|
||||
k ed25519.PublicKey
|
||||
}
|
||||
|
||||
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if the slice is not the right size.
|
||||
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
|
||||
if len(b) != PublicKeySize {
|
||||
return PublicKey{}, fmt.Errorf("invalid ed25519 public key size")
|
||||
}
|
||||
// make a copy
|
||||
return PublicKey{k: append([]byte{}, b...)}, nil
|
||||
}
|
||||
|
||||
// PublicKeyFromPublicKeyMultibase decodes the public key from its PublicKeyMultibase form
|
||||
func PublicKeyFromPublicKeyMultibase(multibase string) (PublicKey, error) {
|
||||
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
|
||||
if err != nil {
|
||||
return PublicKey{}, err
|
||||
}
|
||||
if code != MultibaseCode {
|
||||
return PublicKey{}, fmt.Errorf("invalid code")
|
||||
}
|
||||
if len(bytes) != PublicKeySize {
|
||||
return PublicKey{}, fmt.Errorf("invalid ed25519 public key size")
|
||||
}
|
||||
return PublicKeyFromBytes(bytes)
|
||||
}
|
||||
|
||||
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
|
||||
func PublicKeyFromX509DER(bytes []byte) (PublicKey, error) {
|
||||
pub, err := x509.ParsePKIXPublicKey(bytes)
|
||||
if err != nil {
|
||||
return PublicKey{}, err
|
||||
}
|
||||
return PublicKey{k: pub.(ed25519.PublicKey)}, nil
|
||||
}
|
||||
|
||||
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
|
||||
func PublicKeyFromX509PEM(str string) (PublicKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return PublicKey{}, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPubBlockType {
|
||||
return PublicKey{}, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PublicKeyFromX509DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p PublicKey) ToBytes() []byte {
|
||||
// Copy the private key to a fixed size buffer that can get allocated on the
|
||||
// caller's stack after inlining.
|
||||
var buf [PublicKeySize]byte
|
||||
return append(buf[:0], p.k...)
|
||||
}
|
||||
|
||||
func (p PublicKey) ToPublicKeyMultibase() string {
|
||||
return helpers.PublicKeyMultibaseEncode(MultibaseCode, p.k)
|
||||
}
|
||||
|
||||
func (p PublicKey) ToX509DER() []byte {
|
||||
res, _ := x509.MarshalPKIXPublicKey(p.k)
|
||||
return res
|
||||
}
|
||||
|
||||
func (p PublicKey) ToX509PEM() string {
|
||||
der := p.ToX509DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPubBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
func (p PublicKey) Equal(other crypto.PublicKey) bool {
|
||||
if other, ok := other.(PublicKey); ok {
|
||||
return p.k.Equal(other.k)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p PublicKey) Verify(message, signature []byte) bool {
|
||||
return ed25519.Verify(p.k, message, signature)
|
||||
}
|
||||
41
crypto/interface.go
Normal file
41
crypto/interface.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package crypto
|
||||
|
||||
type PublicKey interface {
|
||||
Equal(other PublicKey) bool
|
||||
|
||||
ToBytes() []byte
|
||||
ToPublicKeyMultibase() string
|
||||
ToX509DER() []byte
|
||||
ToX509PEM() string
|
||||
}
|
||||
|
||||
type PrivateKey interface {
|
||||
Equal(other PrivateKey) bool
|
||||
Public() PublicKey
|
||||
|
||||
ToBytes() []byte
|
||||
ToPKCS8DER() []byte
|
||||
ToPKCS8PEM() string
|
||||
}
|
||||
|
||||
type SigningPublicKey interface {
|
||||
PublicKey
|
||||
|
||||
Verify(message, signature []byte) bool
|
||||
}
|
||||
|
||||
type SigningPrivateKey interface {
|
||||
PrivateKey
|
||||
|
||||
Sign(message []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type KeyExchangePublicKey interface {
|
||||
PublicKey
|
||||
|
||||
// PrivateKeyIsCompatible checks that the given PrivateKey is compatible to perform key exchange.
|
||||
PrivateKeyIsCompatible(local PrivateKey) bool
|
||||
|
||||
// ECDH computes the shared key using the given PrivateKey.
|
||||
ECDH(local PrivateKey) ([]byte, error)
|
||||
}
|
||||
35
crypto/internal/multibase.go
Normal file
35
crypto/internal/multibase.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-varint"
|
||||
)
|
||||
|
||||
// PublicKeyMultibaseDecode is a helper for decoding multibase public keys.
|
||||
func PublicKeyMultibaseDecode(multibase string) (uint64, []byte, error) {
|
||||
baseCodec, bytes, err := mbase.Decode(multibase)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
// the specification enforces that encoding
|
||||
if baseCodec != mbase.Base58BTC {
|
||||
return 0, nil, fmt.Errorf("not Base58BTC encoded")
|
||||
}
|
||||
code, read, err := varint.FromUvarint(bytes)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if read != 2 {
|
||||
return 0, nil, fmt.Errorf("unexpected multibase")
|
||||
}
|
||||
return code, bytes[read:], nil
|
||||
}
|
||||
|
||||
// PublicKeyMultibaseEncode is a helper for encoding multibase public keys.
|
||||
func PublicKeyMultibaseEncode(code uint64, bytes []byte) string {
|
||||
// can only fail with an invalid encoding, but it's hardcoded
|
||||
res, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(code), bytes...))
|
||||
return res
|
||||
}
|
||||
394
crypto/internal/testSuite.go
Normal file
394
crypto/internal/testSuite.go
Normal file
@@ -0,0 +1,394 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/tabwriter"
|
||||
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-varint"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
)
|
||||
|
||||
type TestHarness[PubT crypto.PublicKey, PrivT crypto.PrivateKey] struct {
|
||||
Name string
|
||||
|
||||
GenerateKeyPair func() (PubT, PrivT, error)
|
||||
|
||||
PublicKeyFromBytes func(b []byte) (PubT, error)
|
||||
PublicKeyFromPublicKeyMultibase func(multibase string) (PubT, error)
|
||||
PublicKeyFromX509DER func(bytes []byte) (PubT, error)
|
||||
PublicKeyFromX509PEM func(str string) (PubT, error)
|
||||
|
||||
PrivateKeyFromBytes func(b []byte) (PrivT, error)
|
||||
PrivateKeyFromPKCS8DER func(bytes []byte) (PrivT, error)
|
||||
PrivateKeyFromPKCS8PEM func(str string) (PrivT, error)
|
||||
|
||||
MultibaseCode uint64
|
||||
|
||||
PublicKeySize int
|
||||
PrivateKeySize int
|
||||
SignatureSize int
|
||||
}
|
||||
|
||||
func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, harness TestHarness[PubT, PrivT]) {
|
||||
stats := struct {
|
||||
bytesPubSize int
|
||||
bytesPrivSize int
|
||||
|
||||
x509DerPubSize int
|
||||
pkcs8DerPrivSize int
|
||||
|
||||
x509PemPubSize int
|
||||
pkcs8PemPrivSize int
|
||||
}{}
|
||||
|
||||
t.Cleanup(func() {
|
||||
out := strings.Builder{}
|
||||
w := tabwriter.NewWriter(&out, 0, 0, 3, ' ', 0)
|
||||
|
||||
_, _ = fmt.Fprintln(w, "\tPublic key\tPrivate key")
|
||||
_, _ = fmt.Fprintf(w, "Bytes\t%v\t%v\n", stats.bytesPubSize, stats.bytesPrivSize)
|
||||
_, _ = fmt.Fprintf(w, "DER (pub:x509, priv:PKCS#8)\t%v\t%v\n", stats.x509DerPubSize, stats.pkcs8DerPrivSize)
|
||||
_, _ = fmt.Fprintf(w, "PEM (pub:x509, priv:PKCS#8)\t%v\t%v\n", stats.x509PemPubSize, stats.pkcs8PemPrivSize)
|
||||
_ = w.Flush()
|
||||
|
||||
t.Logf("Test result for %s:\n%s\n", harness.Name, out.String())
|
||||
})
|
||||
|
||||
t.Run("GenerateKeyPair", func(t *testing.T) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pub)
|
||||
require.NotNil(t, priv)
|
||||
require.True(t, pub.Equal(priv.Public()))
|
||||
})
|
||||
|
||||
t.Run("Equality", func(t *testing.T) {
|
||||
pub1, priv1, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
pub2, priv2, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, pub1.Equal(pub1))
|
||||
require.True(t, priv1.Equal(priv1))
|
||||
require.False(t, pub1.Equal(pub2))
|
||||
require.False(t, priv1.Equal(priv2))
|
||||
|
||||
pub1copy, err := harness.PublicKeyFromBytes(pub1.ToBytes())
|
||||
require.NoError(t, err)
|
||||
require.True(t, pub1.Equal(pub1copy))
|
||||
require.True(t, pub1copy.Equal(pub1))
|
||||
|
||||
priv1copy, err := harness.PrivateKeyFromBytes(priv1.ToBytes())
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv1.Equal(priv1copy))
|
||||
require.True(t, priv1copy.Equal(priv1))
|
||||
})
|
||||
|
||||
t.Run("BytesRoundTrip", func(t *testing.T) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
bytes := pub.ToBytes()
|
||||
stats.bytesPubSize = len(bytes)
|
||||
rtPub, err := harness.PublicKeyFromBytes(bytes)
|
||||
require.NoError(t, err)
|
||||
require.True(t, pub.Equal(rtPub))
|
||||
|
||||
bytes = priv.ToBytes()
|
||||
stats.bytesPrivSize = len(bytes)
|
||||
rtPriv, err := harness.PrivateKeyFromBytes(bytes)
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv.Equal(rtPriv))
|
||||
})
|
||||
|
||||
t.Run("MultibaseRoundTrip", func(t *testing.T) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
mb := pub.ToPublicKeyMultibase()
|
||||
rt, err := harness.PublicKeyFromPublicKeyMultibase(mb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pub, rt)
|
||||
|
||||
encoding, bytes, err := mbase.Decode(mb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mbase.Base58BTC, int32(encoding)) // according to the DID spec
|
||||
code, _, err := varint.FromUvarint(bytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, harness.MultibaseCode, code)
|
||||
})
|
||||
|
||||
t.Run("PublicKeyX509RoundTrip", func(t *testing.T) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
der := pub.ToX509DER()
|
||||
stats.x509DerPubSize = len(der)
|
||||
rt, err := harness.PublicKeyFromX509DER(der)
|
||||
require.NoError(t, err)
|
||||
require.True(t, pub.Equal(rt))
|
||||
|
||||
pem := pub.ToX509PEM()
|
||||
stats.x509PemPubSize = len(pem)
|
||||
rt, err = harness.PublicKeyFromX509PEM(pem)
|
||||
require.NoError(t, err)
|
||||
require.True(t, pub.Equal(rt))
|
||||
})
|
||||
|
||||
t.Run("PrivateKeyPKCS8RoundTrip", func(t *testing.T) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
der := priv.ToPKCS8DER()
|
||||
stats.pkcs8DerPrivSize = len(der)
|
||||
rt, err := harness.PrivateKeyFromPKCS8DER(der)
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv.Equal(rt))
|
||||
require.True(t, pub.Equal(rt.Public()))
|
||||
|
||||
pem := priv.ToPKCS8PEM()
|
||||
stats.pkcs8PemPrivSize = len(pem)
|
||||
rt, err = harness.PrivateKeyFromPKCS8PEM(pem)
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv.Equal(rt))
|
||||
require.True(t, pub.Equal(rt.Public()))
|
||||
})
|
||||
|
||||
t.Run("Signature", func(t *testing.T) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
spub, ok := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
|
||||
if !ok {
|
||||
t.Skip("Signature is not implemented")
|
||||
}
|
||||
spriv, ok := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
if !ok {
|
||||
t.Skip("Signature is not implemented")
|
||||
}
|
||||
|
||||
msg := []byte("message")
|
||||
|
||||
sig, err := spriv.Sign(msg)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, sig)
|
||||
require.Equal(t, harness.SignatureSize, len(sig))
|
||||
|
||||
valid := spub.Verify(msg, sig)
|
||||
require.True(t, valid)
|
||||
|
||||
valid = spub.Verify([]byte("wrong message"), sig)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("KeyExchange", func(t *testing.T) {
|
||||
pub1, priv1, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
pub2, priv2, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
kePub1, ok := (crypto.PublicKey(pub1)).(crypto.KeyExchangePublicKey)
|
||||
if !ok {
|
||||
t.Skip("Key exchange is not implemented")
|
||||
}
|
||||
kePub2 := (crypto.PublicKey(pub2)).(crypto.KeyExchangePublicKey)
|
||||
|
||||
// TODO: test with incompatible private keys
|
||||
require.True(t, kePub1.PrivateKeyIsCompatible(priv2))
|
||||
require.True(t, kePub2.PrivateKeyIsCompatible(priv1))
|
||||
|
||||
k1, err := kePub1.ECDH(priv2)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, k1)
|
||||
k2, err := kePub2.ECDH(priv1)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, k2)
|
||||
|
||||
require.Equal(t, k1, k2)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, harness TestHarness[PubT, PrivT]) {
|
||||
b.Run("GenerateKeyPair", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = harness.GenerateKeyPair()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Bytes", func(b *testing.B) {
|
||||
b.Run("PubToBytes", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = pub.ToBytes()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PubFromBytes", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := pub.ToBytes()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PublicKeyFromBytes(buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivToBytes", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = priv.ToBytes()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivFromBytes", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := priv.ToBytes()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PrivateKeyFromBytes(buf)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("DER", func(b *testing.B) {
|
||||
b.Run("PubToDER", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = pub.ToX509DER()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PubFromDER", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := pub.ToX509DER()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PublicKeyFromX509DER(buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivToDER", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = priv.ToPKCS8DER()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivFromDER", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := priv.ToPKCS8DER()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PrivateKeyFromPKCS8DER(buf)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("PEM", func(b *testing.B) {
|
||||
b.Run("PubToPEM", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = pub.ToX509PEM()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PubFromPEM", func(b *testing.B) {
|
||||
pub, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := pub.ToX509PEM()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PublicKeyFromX509PEM(buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivToPEM", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = priv.ToPKCS8PEM()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrivFromPEM", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
buf := priv.ToPKCS8PEM()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = harness.PrivateKeyFromPKCS8PEM(buf)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Signatures", func(b *testing.B) {
|
||||
if _, ok := (crypto.PublicKey(*new(PubT))).(crypto.SigningPublicKey); !ok {
|
||||
b.Skip("Signature is not implemented")
|
||||
}
|
||||
|
||||
b.Run("Sign", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
spriv.Sign([]byte("message"))
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
|
||||
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
sig, err := spriv.Sign([]byte("message"))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
spub.Verify([]byte("message"), sig)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: add key exchange benchmarks
|
||||
}
|
||||
30
crypto/p256/key.go
Normal file
30
crypto/p256/key.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO
|
||||
PublicKeySize = 33
|
||||
PrivateKeySize = 32
|
||||
SignatureSize = 123456
|
||||
|
||||
MultibaseCode = uint64(0x1200)
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub := priv.Public().(*ecdsa.PublicKey)
|
||||
return (*PublicKey)(pub), (*PrivateKey)(priv), nil
|
||||
}
|
||||
|
||||
const (
|
||||
pemPubBlockType = "PUBLIC KEY"
|
||||
pemPrivBlockType = "PRIVATE KEY"
|
||||
)
|
||||
31
crypto/p256/key_test.go
Normal file
31
crypto/p256/key_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
Name: "p256",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
|
||||
PublicKeyFromX509DER: PublicKeyFromX509DER,
|
||||
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
|
||||
PrivateKeyFromBytes: PrivateKeyFromBytes,
|
||||
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
|
||||
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
|
||||
MultibaseCode: MultibaseCode,
|
||||
PublicKeySize: PublicKeySize,
|
||||
PrivateKeySize: PrivateKeySize,
|
||||
SignatureSize: SignatureSize,
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
}
|
||||
94
crypto/p256/private.go
Normal file
94
crypto/p256/private.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPrivateKey = (*PrivateKey)(nil)
|
||||
|
||||
type PrivateKey ecdsa.PrivateKey
|
||||
|
||||
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if the slice is not the right size.
|
||||
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
||||
if len(b) != PrivateKeySize {
|
||||
return nil, fmt.Errorf("invalid P-256 private key size")
|
||||
}
|
||||
|
||||
res := &ecdsa.PrivateKey{
|
||||
D: new(big.Int).SetBytes(b),
|
||||
PublicKey: ecdsa.PublicKey{Curve: elliptic.P256()},
|
||||
}
|
||||
|
||||
// recompute the public key
|
||||
res.PublicKey.X, res.PublicKey.Y = res.PublicKey.Curve.ScalarBaseMult(b)
|
||||
|
||||
return (*PrivateKey)(res), nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
|
||||
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
|
||||
priv, err := x509.ParsePKCS8PrivateKey(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaPriv := priv.(*ecdsa.PrivateKey)
|
||||
return (*PrivateKey)(ecdsaPriv), nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
|
||||
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPrivBlockType {
|
||||
return nil, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PrivateKeyFromPKCS8DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
|
||||
if other, ok := other.(*PrivateKey); ok {
|
||||
return (*ecdsa.PrivateKey)(p).Equal((*ecdsa.PrivateKey)(other))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Public() crypto.PublicKey {
|
||||
ecdhPub := (*ecdsa.PrivateKey)(p).Public().(*ecdsa.PublicKey)
|
||||
return (*PublicKey)(ecdhPub)
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToBytes() []byte {
|
||||
// fixed size buffer that can get allocated on the caller's stack after inlining.
|
||||
var buf [PrivateKeySize]byte
|
||||
((*ecdsa.PrivateKey)(p)).D.FillBytes(buf[:])
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToPKCS8DER() []byte {
|
||||
res, _ := x509.MarshalPKCS8PrivateKey((*ecdsa.PrivateKey)(p))
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToPKCS8PEM() string {
|
||||
der := p.ToPKCS8DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPrivBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
return (*ecdsa.PrivateKey)(p).Sign(rand.Reader, message, nil)
|
||||
}
|
||||
99
crypto/p256/public.go
Normal file
99
crypto/p256/public.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package p256
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var _ crypto.SigningPublicKey = (*PublicKey)(nil)
|
||||
|
||||
type PublicKey ecdsa.PublicKey
|
||||
|
||||
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if the slice is not the right size.
|
||||
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
||||
if len(b) != PublicKeySize {
|
||||
return nil, fmt.Errorf("invalid P-256 public key size")
|
||||
}
|
||||
x, y := elliptic.UnmarshalCompressed(elliptic.P256(), b)
|
||||
if x == nil {
|
||||
return nil, fmt.Errorf("invalid P-256 public key")
|
||||
}
|
||||
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
|
||||
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
|
||||
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if code != MultibaseCode {
|
||||
return nil, fmt.Errorf("invalid code")
|
||||
}
|
||||
return PublicKeyFromBytes(bytes)
|
||||
}
|
||||
|
||||
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
|
||||
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
|
||||
pub, err := x509.ParsePKIXPublicKey(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaPub := pub.(*ecdsa.PublicKey)
|
||||
return (*PublicKey)(ecdsaPub), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
|
||||
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPubBlockType {
|
||||
return nil, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PublicKeyFromX509DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
|
||||
if other, ok := other.(*PublicKey); ok {
|
||||
return (*ecdsa.PublicKey)(p).Equal((*ecdsa.PublicKey)(other))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToBytes() []byte {
|
||||
ecdsaPub := (*ecdsa.PublicKey)(p)
|
||||
return elliptic.MarshalCompressed(elliptic.P256(), ecdsaPub.X, ecdsaPub.Y)
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToPublicKeyMultibase() string {
|
||||
ecdsaPub := (*ecdsa.PublicKey)(p)
|
||||
bytes := elliptic.MarshalCompressed(elliptic.P256(), ecdsaPub.X, ecdsaPub.Y)
|
||||
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToX509DER() []byte {
|
||||
res, _ := x509.MarshalPKIXPublicKey((*ecdsa.PublicKey)(p))
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToX509PEM() string {
|
||||
der := p.ToX509DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPubBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PublicKey) Verify(message, signature []byte) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
31
crypto/x25519/key.go
Normal file
31
crypto/x25519/key.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package x25519
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = 32
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = 32
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = 32
|
||||
|
||||
MultibaseCode = uint64(0xec)
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
|
||||
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub := priv.Public().(*ecdh.PublicKey)
|
||||
return (*PublicKey)(pub), (*PrivateKey)(priv), nil
|
||||
}
|
||||
|
||||
const (
|
||||
pemPubBlockType = "PUBLIC KEY"
|
||||
pemPrivBlockType = "PRIVATE KEY"
|
||||
)
|
||||
71
crypto/x25519/key_test.go
Normal file
71
crypto/x25519/key_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package x25519
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var harness = helpers.TestHarness[*PublicKey, *PrivateKey]{
|
||||
Name: "x25519",
|
||||
GenerateKeyPair: GenerateKeyPair,
|
||||
PublicKeyFromBytes: PublicKeyFromBytes,
|
||||
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
|
||||
PublicKeyFromX509DER: PublicKeyFromX509DER,
|
||||
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
|
||||
PrivateKeyFromBytes: PrivateKeyFromBytes,
|
||||
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
|
||||
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
|
||||
MultibaseCode: MultibaseCode,
|
||||
PublicKeySize: PublicKeySize,
|
||||
PrivateKeySize: PrivateKeySize,
|
||||
SignatureSize: SignatureSize,
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
helpers.TestSuite(t, harness)
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
helpers.BenchSuite(b, harness)
|
||||
}
|
||||
|
||||
func TestEd25519ToX25519(t *testing.T) {
|
||||
// Known pubkey ed25519 --> x25519
|
||||
for _, tc := range []struct {
|
||||
pubEdMultibase string
|
||||
pubXMultibase string
|
||||
}{
|
||||
{
|
||||
// From https://w3c-ccg.github.io/did-key-spec/#ed25519-with-x25519
|
||||
pubEdMultibase: "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
||||
pubXMultibase: "z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.pubEdMultibase, func(t *testing.T) {
|
||||
pubEd, err := ed25519.PublicKeyFromPublicKeyMultibase(tc.pubEdMultibase)
|
||||
require.NoError(t, err)
|
||||
pubX, err := PublicKeyFromEd25519(pubEd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.pubXMultibase, pubX.ToPublicKeyMultibase())
|
||||
})
|
||||
}
|
||||
|
||||
// Check that ed25519 --> x25519 match for pubkeys and privkeys
|
||||
t.Run("ed25519 --> x25519 priv+pub are matching", func(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
pubEd, privEd, err := ed25519.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
pubX, err := PublicKeyFromEd25519(pubEd)
|
||||
require.NoError(t, err)
|
||||
privX, err := PrivateKeyFromEd25519(privEd)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, pubX.Equal(privX.Public()))
|
||||
}
|
||||
})
|
||||
}
|
||||
97
crypto/x25519/private.go
Normal file
97
crypto/x25519/private.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package x25519
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
)
|
||||
|
||||
var _ crypto.PrivateKey = (*PrivateKey)(nil)
|
||||
|
||||
type PrivateKey ecdh.PrivateKey
|
||||
|
||||
// PrivateKeyFromBytes converts a serialized private key to a PrivateKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if len(privateKey) is not [PrivateKeySize].
|
||||
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
||||
// this already check the size of b
|
||||
priv, err := ecdh.X25519().NewPrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*PrivateKey)(priv), nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromEd25519 converts an ed25519 private key to a x25519 private key.
|
||||
// It errors if the slice is not the right size.
|
||||
//
|
||||
// This function is based on the algorithm described in https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm#name-curve25519
|
||||
func PrivateKeyFromEd25519(priv ed25519.PrivateKey) (*PrivateKey, error) {
|
||||
// get the 32-byte seed (first half of the private key)
|
||||
seed := priv.Seed()
|
||||
|
||||
h := sha512.Sum512(seed)
|
||||
|
||||
// clamp as per the X25519 spec
|
||||
h[0] &= 248
|
||||
h[31] &= 127
|
||||
h[31] |= 64
|
||||
|
||||
return PrivateKeyFromBytes(h[:32])
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
|
||||
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
|
||||
priv, err := x509.ParsePKCS8PrivateKey(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdhPriv := priv.(*ecdh.PrivateKey)
|
||||
return (*PrivateKey)(ecdhPriv), nil
|
||||
}
|
||||
|
||||
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
|
||||
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPrivBlockType {
|
||||
return nil, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PrivateKeyFromPKCS8DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
|
||||
if other, ok := other.(*PrivateKey); ok {
|
||||
return (*ecdh.PrivateKey)(p).Equal((*ecdh.PrivateKey)(other))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Public() crypto.PublicKey {
|
||||
ecdhPub := (*ecdh.PrivateKey)(p).Public().(*ecdh.PublicKey)
|
||||
return (*PublicKey)(ecdhPub)
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToBytes() []byte {
|
||||
return (*ecdh.PrivateKey)(p).Bytes()
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToPKCS8DER() []byte {
|
||||
res, _ := x509.MarshalPKCS8PrivateKey((*ecdh.PrivateKey)(p))
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *PrivateKey) ToPKCS8PEM() string {
|
||||
der := p.ToPKCS8DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPrivBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
168
crypto/x25519/public.go
Normal file
168
crypto/x25519/public.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package x25519
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var _ crypto.KeyExchangePublicKey = (*PublicKey)(nil)
|
||||
|
||||
type PublicKey ecdh.PublicKey
|
||||
|
||||
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
|
||||
// This compact serialization format is the raw key material, without metadata or structure.
|
||||
// It errors if the slice is not the right size.
|
||||
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
||||
pub, err := ecdh.X25519().NewPublicKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*PublicKey)(pub), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromEd25519 converts an ed25519 public key to a x25519 public key.
|
||||
// It errors if the slice is not the right size.
|
||||
//
|
||||
// This function is based on the algorithm described in https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm#name-curve25519
|
||||
func PublicKeyFromEd25519(pub ed25519.PublicKey) (*PublicKey, error) {
|
||||
// Conversion formula is u = (1 + y) / (1 - y) (mod p)
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm#name-ecdh-with-montgomery-coordi
|
||||
|
||||
pubBytes := pub.ToBytes()
|
||||
|
||||
// Clear the sign bit (MSB of last byte)
|
||||
// This is because ed25519 serialize as bytes with 255 bit for Y, and one bit for the sign.
|
||||
// We only want Y, and the sign is irrelevant for the conversion.
|
||||
pubBytes[ed25519.PublicKeySize-1] &= 0x7F
|
||||
|
||||
// ed25519 are little-endian, but big.Int expects big-endian
|
||||
// See https://www.rfc-editor.org/rfc/rfc8032
|
||||
y := new(big.Int).SetBytes(reverseBytes(pubBytes))
|
||||
one := big.NewInt(1)
|
||||
negOne := big.NewInt(-1)
|
||||
|
||||
if y.Cmp(one) == 0 || y.Cmp(negOne) == 0 {
|
||||
return nil, fmt.Errorf("x25519 undefined for this public key")
|
||||
}
|
||||
|
||||
// p = 2^255-19
|
||||
//
|
||||
// Equivalent to:
|
||||
// two := big.NewInt(2)
|
||||
// exp := big.NewInt(255)
|
||||
// p := new(big.Int).Exp(two, exp, nil)
|
||||
// p.Sub(p, big.NewInt(19))
|
||||
//
|
||||
p := new(big.Int).SetBytes([]byte{
|
||||
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed,
|
||||
})
|
||||
|
||||
onePlusY := new(big.Int).Add(one, y)
|
||||
oneMinusY := new(big.Int).Sub(one, y)
|
||||
oneMinusYInv := new(big.Int).ModInverse(oneMinusY, p)
|
||||
u := new(big.Int).Mul(onePlusY, oneMinusYInv)
|
||||
u.Mod(u, p)
|
||||
|
||||
// make sure we get 32 bytes, pad if necessary
|
||||
uBytes := u.Bytes()
|
||||
res := make([]byte, PublicKeySize)
|
||||
copy(res[PublicKeySize-len(uBytes):], uBytes)
|
||||
|
||||
// x25519 are little-endian, but big.Int gives us big-endian.
|
||||
// See https://www.ietf.org/rfc/rfc7748.txt
|
||||
return PublicKeyFromBytes(reverseBytes(res))
|
||||
}
|
||||
|
||||
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
|
||||
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
|
||||
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if code != MultibaseCode {
|
||||
return nil, fmt.Errorf("invalid code")
|
||||
}
|
||||
return PublicKeyFromBytes(bytes)
|
||||
}
|
||||
|
||||
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
|
||||
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
|
||||
pub, err := x509.ParsePKIXPublicKey(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdhPub := pub.(*ecdh.PublicKey)
|
||||
return (*PublicKey)(ecdhPub), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
|
||||
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
|
||||
block, _ := pem.Decode([]byte(str))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
if block.Type != pemPubBlockType {
|
||||
return nil, fmt.Errorf("incorrect PEM block type")
|
||||
}
|
||||
return PublicKeyFromX509DER(block.Bytes)
|
||||
}
|
||||
|
||||
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
|
||||
if other, ok := other.(*PublicKey); ok {
|
||||
return (*ecdh.PublicKey)(p).Equal((*ecdh.PublicKey)(other))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToBytes() []byte {
|
||||
return (*ecdh.PublicKey)(p).Bytes()
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToPublicKeyMultibase() string {
|
||||
return helpers.PublicKeyMultibaseEncode(MultibaseCode, (*ecdh.PublicKey)(p).Bytes())
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToX509DER() []byte {
|
||||
res, _ := x509.MarshalPKIXPublicKey((*ecdh.PublicKey)(p))
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToX509PEM() string {
|
||||
der := p.ToX509DER()
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: pemPubBlockType,
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PublicKey) PrivateKeyIsCompatible(local crypto.PrivateKey) bool {
|
||||
if _, ok := local.(*PrivateKey); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PublicKey) ECDH(local crypto.PrivateKey) ([]byte, error) {
|
||||
if local, ok := local.(*PrivateKey); ok {
|
||||
return (*ecdh.PrivateKey)(local).ECDH((*ecdh.PublicKey)(p))
|
||||
}
|
||||
return nil, fmt.Errorf("incompatible private key")
|
||||
}
|
||||
|
||||
func reverseBytes(b []byte) []byte {
|
||||
r := make([]byte, len(b))
|
||||
for i := 0; i < len(b); i++ {
|
||||
r[i] = b[len(b)-1-i]
|
||||
}
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user