Skip to content

Commit 4503b2e

Browse files
authored
Merge pull request #9 from INFURA/crypto
add a crypto base layer that abstract various keypair type, + test/bench suite
2 parents 6bd03b4 + 4bf944c commit 4503b2e

33 files changed

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

crypto/ed25519/public.go

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

crypto/interface.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package crypto
2+
3+
type PublicKey interface {
4+
// Equal returns true if other is the same PublicKey
5+
Equal(other PublicKey) bool
6+
7+
// ToBytes serializes the PublicKey into "raw bytes", without metadata or structure.
8+
// This format can make some assumptions and may not be what you expect.
9+
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
10+
ToBytes() []byte
11+
12+
// ToPublicKeyMultibase format the PublicKey into a string compatible with a PublicKeyMultibase field
13+
// in a DID Document.
14+
ToPublicKeyMultibase() string
15+
16+
// ToX509DER serializes the PublicKey into the X.509 DER (binary) format.
17+
ToX509DER() []byte
18+
19+
// ToX509PEM serializes the PublicKey into the X.509 PEM (string) format.
20+
ToX509PEM() string
21+
}
22+
23+
type PrivateKey interface {
24+
// Equal returns true if other is the same PrivateKey
25+
Equal(other PrivateKey) bool
26+
27+
// Public returns the matching PublicKey.
28+
Public() PublicKey
29+
30+
// ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure.
31+
// This format can make some assumptions and may not be what you expect.
32+
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
33+
ToBytes() []byte
34+
35+
// ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format.
36+
ToPKCS8DER() []byte
37+
38+
// ToPKCS8PEM serializes the PrivateKey into the PKCS#8 PEM (string) format.
39+
ToPKCS8PEM() string
40+
}
41+
42+
type SigningPublicKey interface {
43+
PublicKey
44+
45+
// VerifyBytes checks a signature in the "raw bytes" format.
46+
// This format can make some assumptions and may not be what you expect.
47+
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
48+
VerifyBytes(message, signature []byte) bool
49+
50+
// VerifyASN1 checks a signature in the ASN.1 format.
51+
VerifyASN1(message, signature []byte) bool
52+
}
53+
54+
type SigningPrivateKey interface {
55+
PrivateKey
56+
57+
// SignToBytes creates a signature in the "raw bytes" format.
58+
// This format can make some assumptions and may not be what you expect.
59+
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
60+
SignToBytes(message []byte) ([]byte, error)
61+
62+
// SignToASN1 creates a signature in the ASN.1 format.
63+
SignToASN1(message []byte) ([]byte, error)
64+
}
65+
66+
type KeyExchangePrivateKey interface {
67+
PrivateKey
68+
69+
// PublicKeyIsCompatible checks that the given PublicKey is compatible to perform key exchange.
70+
PublicKeyIsCompatible(remote PublicKey) bool
71+
72+
// KeyExchange computes the shared key using the given PublicKey.
73+
KeyExchange(remote PublicKey) ([]byte, error)
74+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"github.com/multiformats/go-varint"
88
)
99

10-
// MultibaseDecode is a helper for decoding multibase public keys.
11-
func MultibaseDecode(multibase string) (uint64, []byte, error) {
10+
// PublicKeyMultibaseDecode is a helper for decoding multibase public keys.
11+
func PublicKeyMultibaseDecode(multibase string) (uint64, []byte, error) {
1212
baseCodec, bytes, err := mbase.Decode(multibase)
1313
if err != nil {
1414
return 0, nil, err
@@ -27,8 +27,8 @@ func MultibaseDecode(multibase string) (uint64, []byte, error) {
2727
return code, bytes[read:], nil
2828
}
2929

30-
// MultibaseEncode is a helper for encoding multibase public keys.
31-
func MultibaseEncode(code uint64, bytes []byte) string {
30+
// PublicKeyMultibaseEncode is a helper for encoding multibase public keys.
31+
func PublicKeyMultibaseEncode(code uint64, bytes []byte) string {
3232
// can only fail with an invalid encoding, but it's hardcoded
3333
res, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(code), bytes...))
3434
return res

0 commit comments

Comments
 (0)