Skip to content

Commit 5be4ab8

Browse files
committed
add a crypto base layer that abstract various keypair type, + test/bench suite
1 parent 6bd03b4 commit 5be4ab8

File tree

15 files changed

+1341
-0
lines changed

15 files changed

+1341
-0
lines changed

crypto/ed25519/key.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ed25519
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/rand"
6+
)
7+
8+
const (
9+
// PublicKeySize is the size, in bytes, of public keys as used in this package.
10+
PublicKeySize = ed25519.PublicKeySize
11+
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
12+
PrivateKeySize = ed25519.PrivateKeySize
13+
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
14+
SignatureSize = ed25519.SignatureSize
15+
16+
MultibaseCode = uint64(0xed)
17+
)
18+
19+
func GenerateKeyPair() (PublicKey, PrivateKey, error) {
20+
pub, priv, err := ed25519.GenerateKey(rand.Reader)
21+
if err != nil {
22+
return PublicKey{}, PrivateKey{}, err
23+
}
24+
return PublicKey{k: pub}, PrivateKey{k: priv}, nil
25+
}
26+
27+
const (
28+
pemPubBlockType = "PUBLIC KEY"
29+
pemPrivBlockType = "PRIVATE KEY"
30+
)

crypto/ed25519/key_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ed25519
2+
3+
import (
4+
"testing"
5+
6+
"github.com/INFURA/go-did/crypto/internal"
7+
)
8+
9+
var harness = helpers.TestHarness[PublicKey, PrivateKey]{
10+
Name: "ed25519",
11+
GenerateKeyPair: GenerateKeyPair,
12+
PublicKeyFromBytes: PublicKeyFromBytes,
13+
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
14+
PublicKeyFromX509DER: PublicKeyFromX509DER,
15+
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
16+
PrivateKeyFromBytes: PrivateKeyFromBytes,
17+
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
18+
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
19+
MultibaseCode: MultibaseCode,
20+
PublicKeySize: PublicKeySize,
21+
PrivateKeySize: PrivateKeySize,
22+
SignatureSize: SignatureSize,
23+
}
24+
25+
func TestSuite(t *testing.T) {
26+
helpers.TestSuite(t, harness)
27+
}
28+
29+
func BenchmarkSuite(b *testing.B) {
30+
helpers.BenchSuite(b, harness)
31+
}

crypto/ed25519/private.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package ed25519
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"fmt"
8+
9+
"github.com/INFURA/go-did/crypto"
10+
)
11+
12+
var _ crypto.SigningPrivateKey = &PrivateKey{}
13+
14+
type PrivateKey struct {
15+
k ed25519.PrivateKey
16+
}
17+
18+
// PrivateKeyFromBytes converts a serialized private key to a PrivateKey.
19+
// This compact serialization format is the raw key material, without metadata or structure.
20+
// It errors if the slice is not the right size.
21+
func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
22+
if len(b) != PrivateKeySize {
23+
return PrivateKey{}, fmt.Errorf("invalid ed25519 private key size")
24+
}
25+
// make a copy
26+
return PrivateKey{k: append([]byte{}, b...)}, nil
27+
}
28+
29+
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
30+
func PrivateKeyFromPKCS8DER(bytes []byte) (PrivateKey, error) {
31+
priv, err := x509.ParsePKCS8PrivateKey(bytes)
32+
if err != nil {
33+
return PrivateKey{}, err
34+
}
35+
return PrivateKey{k: priv.(ed25519.PrivateKey)}, nil
36+
}
37+
38+
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
39+
func PrivateKeyFromPKCS8PEM(str string) (PrivateKey, error) {
40+
block, _ := pem.Decode([]byte(str))
41+
if block == nil {
42+
return PrivateKey{}, fmt.Errorf("failed to decode PEM block")
43+
}
44+
if block.Type != pemPrivBlockType {
45+
return PrivateKey{}, fmt.Errorf("incorrect PEM block type")
46+
}
47+
return PrivateKeyFromPKCS8DER(block.Bytes)
48+
}
49+
50+
func (p PrivateKey) Equal(other crypto.PrivateKey) bool {
51+
if other, ok := other.(PrivateKey); ok {
52+
return p.k.Equal(other.k)
53+
}
54+
return false
55+
}
56+
57+
func (p PrivateKey) Public() crypto.PublicKey {
58+
return PublicKey{k: p.k.Public().(ed25519.PublicKey)}
59+
}
60+
61+
func (p PrivateKey) Sign(message []byte) ([]byte, error) {
62+
return ed25519.Sign(p.k, message), nil
63+
}
64+
65+
func (p PrivateKey) ToBytes() []byte {
66+
// Copy the private key to a fixed size buffer that can get allocated on the
67+
// caller's stack after inlining.
68+
var buf [PrivateKeySize]byte
69+
return append(buf[:0], p.k...)
70+
}
71+
72+
func (p PrivateKey) ToPKCS8DER() []byte {
73+
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
74+
return res
75+
}
76+
77+
func (p PrivateKey) ToPKCS8PEM() string {
78+
der := p.ToPKCS8DER()
79+
return string(pem.EncodeToMemory(&pem.Block{
80+
Type: pemPrivBlockType,
81+
Bytes: der,
82+
}))
83+
}
84+
85+
// Seed returns the private key seed corresponding to priv. It is provided for
86+
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
87+
// in this package.
88+
func (p PrivateKey) Seed() []byte {
89+
return p.k.Seed()
90+
}

crypto/ed25519/public.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package ed25519
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"fmt"
8+
9+
"github.com/INFURA/go-did/crypto"
10+
"github.com/INFURA/go-did/crypto/internal"
11+
)
12+
13+
var _ crypto.SigningPublicKey = &PublicKey{}
14+
15+
type PublicKey struct {
16+
k ed25519.PublicKey
17+
}
18+
19+
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
20+
// This compact serialization format is the raw key material, without metadata or structure.
21+
// It errors if the slice is not the right size.
22+
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
23+
if len(b) != PublicKeySize {
24+
return PublicKey{}, fmt.Errorf("invalid ed25519 public key size")
25+
}
26+
// make a copy
27+
return PublicKey{k: append([]byte{}, b...)}, nil
28+
}
29+
30+
// PublicKeyFromPublicKeyMultibase decodes the public key from its PublicKeyMultibase form
31+
func PublicKeyFromPublicKeyMultibase(multibase string) (PublicKey, error) {
32+
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
33+
if err != nil {
34+
return PublicKey{}, err
35+
}
36+
if code != MultibaseCode {
37+
return PublicKey{}, fmt.Errorf("invalid code")
38+
}
39+
if len(bytes) != PublicKeySize {
40+
return PublicKey{}, fmt.Errorf("invalid ed25519 public key size")
41+
}
42+
return PublicKeyFromBytes(bytes)
43+
}
44+
45+
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
46+
func PublicKeyFromX509DER(bytes []byte) (PublicKey, error) {
47+
pub, err := x509.ParsePKIXPublicKey(bytes)
48+
if err != nil {
49+
return PublicKey{}, err
50+
}
51+
return PublicKey{k: pub.(ed25519.PublicKey)}, nil
52+
}
53+
54+
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
55+
func PublicKeyFromX509PEM(str string) (PublicKey, error) {
56+
block, _ := pem.Decode([]byte(str))
57+
if block == nil {
58+
return PublicKey{}, fmt.Errorf("failed to decode PEM block")
59+
}
60+
if block.Type != pemPubBlockType {
61+
return PublicKey{}, fmt.Errorf("incorrect PEM block type")
62+
}
63+
return PublicKeyFromX509DER(block.Bytes)
64+
}
65+
66+
func (p PublicKey) ToBytes() []byte {
67+
// Copy the private key to a fixed size buffer that can get allocated on the
68+
// caller's stack after inlining.
69+
var buf [PublicKeySize]byte
70+
return append(buf[:0], p.k...)
71+
}
72+
73+
func (p PublicKey) ToPublicKeyMultibase() string {
74+
return helpers.PublicKeyMultibaseEncode(MultibaseCode, p.k)
75+
}
76+
77+
func (p PublicKey) ToX509DER() []byte {
78+
res, _ := x509.MarshalPKIXPublicKey(p.k)
79+
return res
80+
}
81+
82+
func (p PublicKey) ToX509PEM() string {
83+
der := p.ToX509DER()
84+
return string(pem.EncodeToMemory(&pem.Block{
85+
Type: pemPubBlockType,
86+
Bytes: der,
87+
}))
88+
}
89+
90+
func (p PublicKey) Equal(other crypto.PublicKey) bool {
91+
if other, ok := other.(PublicKey); ok {
92+
return p.k.Equal(other.k)
93+
}
94+
return false
95+
}
96+
97+
func (p PublicKey) Verify(message, signature []byte) bool {
98+
return ed25519.Verify(p.k, message, signature)
99+
}

crypto/interface.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package crypto
2+
3+
type PublicKey interface {
4+
Equal(other PublicKey) bool
5+
6+
ToBytes() []byte
7+
ToPublicKeyMultibase() string
8+
ToX509DER() []byte
9+
ToX509PEM() string
10+
}
11+
12+
type PrivateKey interface {
13+
Equal(other PrivateKey) bool
14+
Public() PublicKey
15+
16+
ToBytes() []byte
17+
ToPKCS8DER() []byte
18+
ToPKCS8PEM() string
19+
}
20+
21+
type SigningPublicKey interface {
22+
PublicKey
23+
24+
Verify(message, signature []byte) bool
25+
}
26+
27+
type SigningPrivateKey interface {
28+
PrivateKey
29+
30+
Sign(message []byte) ([]byte, error)
31+
}
32+
33+
type KeyExchangePublicKey interface {
34+
PublicKey
35+
36+
// PrivateKeyIsCompatible checks that the given PrivateKey is compatible to perform key exchange.
37+
PrivateKeyIsCompatible(local PrivateKey) bool
38+
39+
// ECDH computes the shared key using the given PrivateKey.
40+
ECDH(local PrivateKey) ([]byte, error)
41+
}

crypto/internal/multibase.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package helpers
2+
3+
import (
4+
"fmt"
5+
6+
mbase "github.com/multiformats/go-multibase"
7+
"github.com/multiformats/go-varint"
8+
)
9+
10+
// PublicKeyMultibaseDecode is a helper for decoding multibase public keys.
11+
func PublicKeyMultibaseDecode(multibase string) (uint64, []byte, error) {
12+
baseCodec, bytes, err := mbase.Decode(multibase)
13+
if err != nil {
14+
return 0, nil, err
15+
}
16+
// the specification enforces that encoding
17+
if baseCodec != mbase.Base58BTC {
18+
return 0, nil, fmt.Errorf("not Base58BTC encoded")
19+
}
20+
code, read, err := varint.FromUvarint(bytes)
21+
if err != nil {
22+
return 0, nil, err
23+
}
24+
if read != 2 {
25+
return 0, nil, fmt.Errorf("unexpected multibase")
26+
}
27+
return code, bytes[read:], nil
28+
}
29+
30+
// PublicKeyMultibaseEncode is a helper for encoding multibase public keys.
31+
func PublicKeyMultibaseEncode(code uint64, bytes []byte) string {
32+
// can only fail with an invalid encoding, but it's hardcoded
33+
res, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(code), bytes...))
34+
return res
35+
}

0 commit comments

Comments
 (0)