From 4bfdc4bf2e6158f4021be44201c969d4d1b3d71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 18 Jun 2025 19:04:17 +0200 Subject: [PATCH] "bytes", PKCS8/X509 x DER/PEM serialization for every keys --- .../ed25519/VerificationKey2020_test.go | 2 +- verifications/ed25519/key.go | 82 +++++++++++ verifications/ed25519/key_test.go | 47 +++++++ verifications/p256/key.go | 128 ++++++++++++++++-- verifications/p256/key_test.go | 33 ++++- verifications/x25519/key.go | 83 ++++++++++++ verifications/x25519/key_test.go | 39 ++++++ 7 files changed, 400 insertions(+), 14 deletions(-) diff --git a/verifications/ed25519/VerificationKey2020_test.go b/verifications/ed25519/VerificationKey2020_test.go index 05b3108..8c499a4 100644 --- a/verifications/ed25519/VerificationKey2020_test.go +++ b/verifications/ed25519/VerificationKey2020_test.go @@ -29,7 +29,7 @@ func TestJsonRoundTrip(t *testing.T) { require.JSONEq(t, data, string(bytes)) } -func TestSignature(t *testing.T) { +func TestVerify(t *testing.T) { // test vector from https://datatracker.ietf.org/doc/html/rfc8032#section-7.1 pkHex := "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025" diff --git a/verifications/ed25519/key.go b/verifications/ed25519/key.go index d03a4a1..445f662 100644 --- a/verifications/ed25519/key.go +++ b/verifications/ed25519/key.go @@ -3,6 +3,8 @@ package ed25519 import ( "crypto/ed25519" "crypto/rand" + "crypto/x509" + "encoding/pem" "fmt" "github.com/INFURA/go-did/verifications/internal" @@ -27,6 +29,7 @@ func GenerateKeyPair() (PublicKey, PrivateKey, error) { } // 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 { @@ -37,6 +40,7 @@ func PublicKeyFromBytes(b []byte) (PublicKey, error) { } // PublicKeyToBytes converts a public key to a byte slice. +// This compact serialization format is the raw key material, without metadata or structure. func PublicKeyToBytes(pub PublicKey) []byte { // Copy the private key to a fixed size buffer that can get allocated on the // caller's stack after inlining. @@ -64,7 +68,46 @@ func PublicKeyToMultibase(pub PublicKey) string { return helpers.MultibaseEncode(MultibaseCode, pub) } +// 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 + } + return pub.(PublicKey), nil +} + +// PublicKeyToX509DER encodes the public key into the X.509 DER (binary) format. +func PublicKeyToX509DER(pub PublicKey) []byte { + res, _ := x509.MarshalPKIXPublicKey(pub) + return res +} + +const pemPubBlockType = "PUBLIC KEY" + +// 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) +} + +// PublicKeyToX509PEM encodes the public key into the X.509 PEM (binary) format. +func PublicKeyToX509PEM(pub PublicKey) string { + der := PublicKeyToX509DER(pub) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPubBlockType, + Bytes: der, + })) +} + // 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 { @@ -75,6 +118,7 @@ func PrivateKeyFromBytes(b []byte) (PrivateKey, error) { } // PrivateKeyToBytes converts a private key to a byte slice. +// This compact serialization format is the raw key material, without metadata or structure. func PrivateKeyToBytes(priv PrivateKey) []byte { // Copy the private key to a fixed size buffer that can get allocated on the // caller's stack after inlining. @@ -82,6 +126,44 @@ func PrivateKeyToBytes(priv PrivateKey) []byte { return append(buf[:0], priv...) } +// 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 + } + return priv.(PrivateKey), nil +} + +// PrivateKeyToPKCS8DER encodes the private key into the PKCS#8 DER (binary) format. +func PrivateKeyToPKCS8DER(priv PrivateKey) []byte { + res, _ := x509.MarshalPKCS8PrivateKey(priv) + return res +} + +const pemPrivBlockType = "PRIVATE KEY" + +// 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) +} + +// PrivateKeyToPKCS8PEM encodes the private key into the PKCS#8 PEM (binary) format. +func PrivateKeyToPKCS8PEM(priv PrivateKey) string { + der := PrivateKeyToPKCS8DER(priv) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPrivBlockType, + Bytes: der, + })) +} + // Sign signs the message with privateKey and returns a signature. // It will panic if len(privateKey) is not [PrivateKeySize]. func Sign(privateKey PrivateKey, message []byte) []byte { diff --git a/verifications/ed25519/key_test.go b/verifications/ed25519/key_test.go index ecdc45f..df90a83 100644 --- a/verifications/ed25519/key_test.go +++ b/verifications/ed25519/key_test.go @@ -1,6 +1,7 @@ package ed25519_test import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -21,11 +22,13 @@ func TestBytesRoundTrip(t *testing.T) { require.NoError(t, err) bytes := ed25519.PublicKeyToBytes(pub) + fmt.Println("pub", len(bytes)) rtPub, err := ed25519.PublicKeyFromBytes(bytes) require.NoError(t, err) require.True(t, pub.Equal(rtPub)) bytes = ed25519.PrivateKeyToBytes(priv) + fmt.Println("priv", len(bytes)) rtPriv, err := ed25519.PrivateKeyFromBytes(bytes) require.NoError(t, err) require.True(t, priv.Equal(rtPriv)) @@ -40,3 +43,47 @@ func TestMultibaseRoundTrip(t *testing.T) { require.NoError(t, err) require.Equal(t, pub, rt) } + +func TestPublicKeyX509RoundTrip(t *testing.T) { + pub, _, err := ed25519.GenerateKeyPair() + require.NoError(t, err) + + der := ed25519.PublicKeyToX509DER(pub) + fmt.Println("der", len(der)) + rt, err := ed25519.PublicKeyFromX509DER(der) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) + + pem := ed25519.PublicKeyToX509PEM(pub) + fmt.Println("pem", len(pem)) + rt, err = ed25519.PublicKeyFromX509PEM(pem) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) +} + +func TestPrivateKeyPKCS8RoundTrip(t *testing.T) { + pub, priv, err := ed25519.GenerateKeyPair() + require.NoError(t, err) + + der := ed25519.PrivateKeyToPKCS8DER(priv) + fmt.Println("der", len(der)) + rt, err := ed25519.PrivateKeyFromPKCS8DER(der) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) + + pem := ed25519.PrivateKeyToPKCS8PEM(priv) + fmt.Println("pem", len(pem)) + rt, err = ed25519.PrivateKeyFromPKCS8PEM(pem) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) +} + +// func TestSignature(t *testing.T) { +// pub, priv, err := ed25519.GenerateKeyPair() +// require.NoError(t, err) +// +// sig := ed25519.Sign(priv, []byte("message")) +// +// } diff --git a/verifications/p256/key.go b/verifications/p256/key.go index e19caf9..922bf01 100644 --- a/verifications/p256/key.go +++ b/verifications/p256/key.go @@ -5,7 +5,9 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" + "encoding/pem" "fmt" + "math/big" helpers "github.com/INFURA/go-did/verifications/internal" ) @@ -15,8 +17,8 @@ type PrivateKey = *ecdsa.PrivateKey const ( // TODO - PublicKeySize = 123456 - PrivateKeySize = 123456 + PublicKeySize = 33 + PrivateKeySize = 32 SignatureSize = 123456 MultibaseCode = uint64(0x1200) @@ -31,6 +33,7 @@ func GenerateKeyPair() (PublicKey, PrivateKey, error) { } // 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 { @@ -41,17 +44,25 @@ func PublicKeyFromBytes(b []byte) (PublicKey, error) { return nil, fmt.Errorf("invalid P-256 public key") } return &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}, nil + + // if len(b) != PublicKeySize { + // return nil, fmt.Errorf("invalid P-256 public key size") + // } + // x := new(big.Int).SetBytes(b[:PublicKeySize/2]) + // y := new(big.Int).SetBytes(b[PublicKeySize/2:]) + // return &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}, nil } // PublicKeyToBytes converts a public key to a byte slice. -func PublicKeyToBytes(pub PublicKey) (res []byte, err error) { - defer func() { - if rerr := recover(); rerr != nil { - err = fmt.Errorf("recovered panic: %s", rerr) - res = nil - } - }() - return x509.MarshalPKIXPublicKey(pub) +// This compact serialization format is the raw key material, without metadata or structure. +func PublicKeyToBytes(pub PublicKey) []byte { + return elliptic.MarshalCompressed(elliptic.P256(), pub.X, pub.Y) + + // // fixed size buffer that can get allocated on the caller's stack after inlining. + // var buf [PublicKeySize]byte + // pub.X.FillBytes(buf[:PublicKeySize/2]) + // pub.Y.FillBytes(buf[PublicKeySize/2:]) + // return buf[:] } // PublicKeyFromMultibase decodes the public key from its Multibase form @@ -72,13 +83,106 @@ func PublicKeyToMultibase(pub PublicKey) string { return helpers.MultibaseEncode(MultibaseCode, 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 + } + return pub.(PublicKey), nil +} + +// PublicKeyToX509DER encodes the public key into the X.509 DER (binary) format. +func PublicKeyToX509DER(pub PublicKey) []byte { + res, _ := x509.MarshalPKIXPublicKey(pub) + return res +} + +const pemPubBlockType = "PUBLIC KEY" + +// 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) +} + +// PublicKeyToX509PEM encodes the public key into the X.509 PEM (binary) format. +func PublicKeyToX509PEM(pub PublicKey) string { + der := PublicKeyToX509DER(pub) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPubBlockType, + Bytes: der, + })) +} + // 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") } - // TODO - return nil, nil + 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 res, nil +} + +// PrivateKeyToBytes converts a private key to a byte slice. +// This compact serialization format is the raw key material, without metadata or structure. +func PrivateKeyToBytes(priv PrivateKey) []byte { + // fixed size buffer that can get allocated on the caller's stack after inlining. + var buf [PrivateKeySize]byte + priv.D.FillBytes(buf[:]) + return buf[:] +} + +// 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 + } + return priv.(PrivateKey), nil +} + +// PrivateKeyToPKCS8DER encodes the private key into the PKCS#8 DER (binary) format. +func PrivateKeyToPKCS8DER(priv PrivateKey) []byte { + res, _ := x509.MarshalPKCS8PrivateKey(priv) + return res +} + +const pemPrivBlockType = "PRIVATE KEY" + +// 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) +} + +// PrivateKeyToPKCS8PEM encodes the private key into the PKCS#8 PEM (binary) format. +func PrivateKeyToPKCS8PEM(priv PrivateKey) string { + der := PrivateKeyToPKCS8DER(priv) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPrivBlockType, + Bytes: der, + })) } diff --git a/verifications/p256/key_test.go b/verifications/p256/key_test.go index 46e8dc0..a074a0a 100644 --- a/verifications/p256/key_test.go +++ b/verifications/p256/key_test.go @@ -5,7 +5,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/INFURA/go-did/verifications/ed25519" "github.com/INFURA/go-did/verifications/p256" ) @@ -32,6 +31,38 @@ func TestBytesRoundTrip(t *testing.T) { require.True(t, priv.Equal(rtPriv)) } +func TestPublicKeyX509RoundTrip(t *testing.T) { + pub, _, err := p256.GenerateKeyPair() + require.NoError(t, err) + + der := p256.PublicKeyToX509DER(pub) + rt, err := p256.PublicKeyFromX509DER(der) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) + + pem := p256.PublicKeyToX509PEM(pub) + rt, err = p256.PublicKeyFromX509PEM(pem) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) +} + +func TestPrivateKeyPKCS8RoundTrip(t *testing.T) { + pub, priv, err := p256.GenerateKeyPair() + require.NoError(t, err) + + der := p256.PrivateKeyToPKCS8DER(priv) + rt, err := p256.PrivateKeyFromPKCS8DER(der) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) + + pem := p256.PrivateKeyToPKCS8PEM(priv) + rt, err = p256.PrivateKeyFromPKCS8PEM(pem) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) +} + func TestMultibaseRoundTrip(t *testing.T) { pub, _, err := p256.GenerateKeyPair() require.NoError(t, err) diff --git a/verifications/x25519/key.go b/verifications/x25519/key.go index 05ebe31..f737c84 100644 --- a/verifications/x25519/key.go +++ b/verifications/x25519/key.go @@ -4,6 +4,8 @@ import ( "crypto/ecdh" "crypto/rand" "crypto/sha512" + "crypto/x509" + "encoding/pem" "fmt" "math/big" @@ -34,12 +36,14 @@ func GenerateKeyPair() (PublicKey, PrivateKey, error) { } // 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) { return ecdh.X25519().NewPublicKey(b) } // PublicKeyToBytes converts a public key to a byte slice. +// This compact serialization format is the raw key material, without metadata or structure. func PublicKeyToBytes(pub PublicKey) []byte { return pub.Bytes() } @@ -121,13 +125,54 @@ func PublicKeyToMultibase(pub PublicKey) string { return helpers.MultibaseEncode(MultibaseCode, pub.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 + } + return pub.(PublicKey), nil +} + +// PublicKeyToX509DER encodes the public key into the X.509 DER (binary) format. +func PublicKeyToX509DER(pub PublicKey) []byte { + res, _ := x509.MarshalPKIXPublicKey(pub) + return res +} + +const pemPubBlockType = "PUBLIC KEY" + +// 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) +} + +// PublicKeyToX509PEM encodes the public key into the X.509 PEM (binary) format. +func PublicKeyToX509PEM(pub PublicKey) string { + der := PublicKeyToX509DER(pub) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPubBlockType, + Bytes: der, + })) +} + // 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 return ecdh.X25519().NewPrivateKey(b) } // PrivateKeyToBytes converts a private key to a byte slice. +// This compact serialization format is the raw key material, without metadata or structure. func PrivateKeyToBytes(priv PrivateKey) []byte { return priv.Bytes() } @@ -154,6 +199,44 @@ func PrivateKeyFromEd25519(priv ed25519.PrivateKey) (PrivateKey, error) { return ecdh.X25519().NewPrivateKey(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 + } + return priv.(PrivateKey), nil +} + +// PrivateKeyToPKCS8DER encodes the private key into the PKCS#8 DER (binary) format. +func PrivateKeyToPKCS8DER(priv PrivateKey) []byte { + res, _ := x509.MarshalPKCS8PrivateKey(priv) + return res +} + +const pemPrivBlockType = "PRIVATE KEY" + +// 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) +} + +// PrivateKeyToPKCS8PEM encodes the private key into the PKCS#8 PEM (binary) format. +func PrivateKeyToPKCS8PEM(priv PrivateKey) string { + der := PrivateKeyToPKCS8DER(priv) + return string(pem.EncodeToMemory(&pem.Block{ + Type: pemPrivBlockType, + Bytes: der, + })) +} + func reverseBytes(b []byte) []byte { r := make([]byte, len(b)) for i := 0; i < len(b); i++ { diff --git a/verifications/x25519/key_test.go b/verifications/x25519/key_test.go index 1df61a4..d420014 100644 --- a/verifications/x25519/key_test.go +++ b/verifications/x25519/key_test.go @@ -2,6 +2,7 @@ package x25519_test import ( "crypto/ecdh" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -25,11 +26,13 @@ func TestBytesRoundTrip(t *testing.T) { require.NoError(t, err) bytes := x25519.PublicKeyToBytes(pub) + fmt.Println("pub", len(bytes)) rtPub, err := x25519.PublicKeyFromBytes(bytes) require.NoError(t, err) require.True(t, pub.Equal(rtPub)) bytes = x25519.PrivateKeyToBytes(priv) + fmt.Println("priv", len(bytes)) rtPriv, err := x25519.PrivateKeyFromBytes(bytes) require.NoError(t, err) require.True(t, priv.Equal(rtPriv)) @@ -45,6 +48,42 @@ func TestMultibaseRoundTrip(t *testing.T) { require.Equal(t, pub, rt) } +func TestPublicKeyX509RoundTrip(t *testing.T) { + pub, _, err := ed25519.GenerateKeyPair() + require.NoError(t, err) + + der := ed25519.PublicKeyToX509DER(pub) + fmt.Println("der", len(der)) + rt, err := ed25519.PublicKeyFromX509DER(der) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) + + pem := ed25519.PublicKeyToX509PEM(pub) + fmt.Println("pem", len(pem)) + rt, err = ed25519.PublicKeyFromX509PEM(pem) + require.NoError(t, err) + require.True(t, pub.Equal(rt)) +} + +func TestPrivateKeyPKCS8RoundTrip(t *testing.T) { + pub, priv, err := ed25519.GenerateKeyPair() + require.NoError(t, err) + + der := ed25519.PrivateKeyToPKCS8DER(priv) + fmt.Println("der", len(der)) + rt, err := ed25519.PrivateKeyFromPKCS8DER(der) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) + + pem := ed25519.PrivateKeyToPKCS8PEM(priv) + fmt.Println("pem", len(pem)) + rt, err = ed25519.PrivateKeyFromPKCS8PEM(pem) + require.NoError(t, err) + require.True(t, priv.Equal(rt)) + require.True(t, pub.Equal(rt.Public())) +} + func TestEd25519ToX25519(t *testing.T) { // Known pubkey ed25519 --> x25519 for _, tc := range []struct {