Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ed25519 support #29

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions bccsp/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const (
// ECDSAReRand ECDSA key re-randomization
ECDSAReRand = "ECDSA_RERAND"

// ED25519 Algorithm
ED25519 = "ED25519"

// RSA at the default security level.
// Each BCCSP may or may not support default security level. If not supported than
// an error will be returned.
Expand Down Expand Up @@ -90,6 +93,21 @@ func (opts *ECDSAKeyGenOpts) Ephemeral() bool {
return opts.Temporary
}

type ED25519KeyGenOpts struct {
Temporary bool
}

// Algorithm returns the key generation algorithm identifier (to be used).
func (opts *ED25519KeyGenOpts) Algorithm() string {
return ED25519
}

// Ephemeral returns true if the key to generate has to be ephemeral,
// false otherwise.
func (opts *ED25519KeyGenOpts) Ephemeral() bool {
return opts.Temporary
}

// ECDSAPKIXPublicKeyImportOpts contains options for ECDSA public key importation in PKIX format
type ECDSAPKIXPublicKeyImportOpts struct {
Temporary bool
Expand Down Expand Up @@ -139,6 +157,38 @@ func (opts *ECDSAGoPublicKeyImportOpts) Ephemeral() bool {
return opts.Temporary
}

// ED25519PrivateKeyImportOpts contains options for ED25519 key importation from ed25519.PublicKey
type ED25519PrivateKeyImportOpts struct {
Temporary bool
}

// Algorithm returns the key importation algorithm identifier (to be used).
func (opts *ED25519PrivateKeyImportOpts) Algorithm() string {
return ED25519
}

// Ephemeral returns true if the key to generate has to be ephemeral,
// false otherwise.
func (opts *ED25519PrivateKeyImportOpts) Ephemeral() bool {
return opts.Temporary
}

// ED25519GoPublicKeyImportOpts contains options for ED25519 key importation from ed25519.PublicKey
type ED25519GoPublicKeyImportOpts struct {
Temporary bool
}

// Algorithm returns the key importation algorithm identifier (to be used).
func (opts *ED25519GoPublicKeyImportOpts) Algorithm() string {
return ED25519
}

// Ephemeral returns true if the key to generate has to be ephemeral,
// false otherwise.
func (opts *ED25519GoPublicKeyImportOpts) Ephemeral() bool {
return opts.Temporary
}

// ECDSAReRandKeyOpts contains options for ECDSA key re-randomization.
type ECDSAReRandKeyOpts struct {
Temporary bool
Expand Down
15 changes: 11 additions & 4 deletions bccsp/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,16 @@ func (s *bccspCryptoSigner) Public() crypto.PublicKey {
return s.pk
}

// Sign signs digest with the private key, possibly using entropy from rand.
// Sign signs digest or the full message with the private key,
// possibly using entropy from rand.
// For an (EC)DSA key, it should be a DER-serialised, ASN.1 signature
// structure.
// structure.For an ED25519 key, it should be a signature compatible
// to the with the RFC 8032.
//
// If (EC)DSA signature, the hash must be passed as the "digestOrMsg"
// parameter. Golang requires the full message to sign with the
// built-in ED25519 library. Therefore, the full message must be
// passed as the "digestOrMsg" parameter.
//
// Hash implements the SignerOpts interface and, in most cases, one can
// simply pass in the hash function used as opts. Sign may also attempt
Expand All @@ -73,6 +80,6 @@ func (s *bccspCryptoSigner) Public() crypto.PublicKey {
// Note that when a signature of a hash of a larger message is needed,
// the caller is responsible for hashing the larger message and passing
// the hash (as digest) and the hash function (as opts) to Sign.
func (s *bccspCryptoSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
return s.csp.Sign(s.key, digest, opts)
func (s *bccspCryptoSigner) Sign(rand io.Reader, digestOrMsg []byte, opts crypto.SignerOpts) ([]byte, error) {
return s.csp.Sign(s.key, digestOrMsg, opts)
}
40 changes: 40 additions & 0 deletions bccsp/sw/ed25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package sw

import (
"crypto/ed25519"

"github.com/hyperledger/fabric-lib-go/bccsp"
)

func signED25519(k *ed25519.PrivateKey, msg []byte, opts bccsp.SignerOpts) ([]byte, error) {
signature := ed25519.Sign(*k, msg)
return signature, nil
}

func verifyED25519(k *ed25519.PublicKey, signature, msg []byte, opts bccsp.SignerOpts) (bool, error) {
return ed25519.Verify(*k, msg, signature), nil
}

type ed25519Signer struct{}

func (s *ed25519Signer) Sign(k bccsp.Key, msg []byte, opts bccsp.SignerOpts) ([]byte, error) {
return signED25519(k.(*ed25519PrivateKey).privKey, msg, opts)
}

type ed25519PrivateKeyVerifier struct{}

func (v *ed25519PrivateKeyVerifier) Verify(k bccsp.Key, signature, msg []byte, opts bccsp.SignerOpts) (bool, error) {
castedKey, _ := (k.(*ed25519PrivateKey).privKey.Public()).(ed25519.PublicKey)
return verifyED25519(&castedKey, signature, msg, opts)
}

type ed25519PublicKeyKeyVerifier struct{}

func (v *ed25519PublicKeyKeyVerifier) Verify(k bccsp.Key, signature, msg []byte, opts bccsp.SignerOpts) (bool, error) {
return verifyED25519(k.(*ed25519PublicKey).pubKey, signature, msg, opts)
}
138 changes: 138 additions & 0 deletions bccsp/sw/ed25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package sw

import (
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"testing"

"github.com/stretchr/testify/require"
)

func TestVerifyED25519(t *testing.T) {
t.Parallel()

// Generate a key
_, lowLevelKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)

msg := []byte("hello world")
sigma, err := signED25519(&lowLevelKey, msg, nil)
require.NoError(t, err)

castedKey, _ := lowLevelKey.Public().(ed25519.PublicKey)
valid, err := verifyED25519(&castedKey, sigma, msg, nil)
require.NoError(t, err)
require.True(t, valid)
}

func TestEd25519SignerSign(t *testing.T) {
t.Parallel()

signer := &ed25519Signer{}
verifierPrivateKey := &ed25519PrivateKeyVerifier{}
verifierPublicKey := &ed25519PublicKeyKeyVerifier{}

// Generate a key
_, lowLevelKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
k := &ed25519PrivateKey{&lowLevelKey}
pk, err := k.PublicKey()
require.NoError(t, err)

// Sign
msg := []byte("Hello World")
sigma, err := signer.Sign(k, msg, nil)
require.NoError(t, err)
require.NotNil(t, sigma)

// Verify
castedKey, _ := lowLevelKey.Public().(ed25519.PublicKey)
valid, err := verifyED25519(&castedKey, sigma, msg, nil)
require.NoError(t, err)
require.True(t, valid)

valid, err = verifierPrivateKey.Verify(k, sigma, msg, nil)
require.NoError(t, err)
require.True(t, valid)

valid, err = verifierPublicKey.Verify(pk, sigma, msg, nil)
require.NoError(t, err)
require.True(t, valid)
}

func TestEd25519PrivateKey(t *testing.T) {
t.Parallel()

_, lowLevelKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
k := &ed25519PrivateKey{&lowLevelKey}

require.False(t, k.Symmetric())
require.True(t, k.Private())

_, err = k.Bytes()
require.Error(t, err)
require.Contains(t, err.Error(), "Not supported.")

k.privKey = nil
ski := k.SKI()
require.Nil(t, ski)

k.privKey = &lowLevelKey
ski = k.SKI()
raw := k.privKey.Public().(ed25519.PublicKey)
hash := sha256.New()
hash.Write(raw)
ski2 := hash.Sum(nil)
require.Equal(t, ski2, ski, "SKI is not computed in the right way.")

pk, err := k.PublicKey()
require.NoError(t, err)
require.NotNil(t, pk)
ed25519PK, ok := pk.(*ed25519PublicKey)
require.True(t, ok)
castedKey, _ := lowLevelKey.Public().(ed25519.PublicKey)
require.Equal(t, &castedKey, ed25519PK.pubKey)
}

func TestEd25519PublicKey(t *testing.T) {
t.Parallel()

_, lowLevelKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
castedKey, _ := lowLevelKey.Public().(ed25519.PublicKey)
k := &ed25519PublicKey{&castedKey}

require.False(t, k.Symmetric())
require.False(t, k.Private())

k.pubKey = nil
ski := k.SKI()
require.Nil(t, ski)

k.pubKey = &castedKey
ski = k.SKI()
raw := *(k.pubKey)
hash := sha256.New()
hash.Write(raw)
ski2 := hash.Sum(nil)
require.Equal(t, ski, ski2, "SKI is not computed in the right way.")

pk, err := k.PublicKey()
require.NoError(t, err)
require.Equal(t, k, pk)

bytes, err := k.Bytes()
require.NoError(t, err)
bytes2, err := x509.MarshalPKIXPublicKey(*k.pubKey)
require.NoError(t, err)
require.Equal(t, bytes2, bytes, "bytes are not computed in the right way.")
}
109 changes: 109 additions & 0 deletions bccsp/sw/ed25519key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package sw

import (
"crypto/ed25519"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"

"github.com/hyperledger/fabric-lib-go/bccsp"
)

type ed25519PrivateKey struct {
privKey *ed25519.PrivateKey
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ed25519PrivateKey) Bytes() ([]byte, error) {
return nil, errors.New("Not supported.")
}

// SKI returns the subject key identifier of this key.
func (k *ed25519PrivateKey) SKI() []byte {
if k.privKey == nil {
return nil
}

// Marshall the public key
raw := k.privKey.Public().(ed25519.PublicKey)

// Hash it
hash := sha256.New()
hash.Write(raw)
return hash.Sum(nil)
}

// Symmetric returns true if this key is a symmetric key,
// false if this key is asymmetric
func (k *ed25519PrivateKey) Symmetric() bool {
return false
}

// Private returns true if this key is a private key,
// false otherwise.
func (k *ed25519PrivateKey) Private() bool {
return true
}

// PublicKey returns the corresponding public key part of an asymmetric public/private key pair.
// This method returns an error in symmetric key schemes.
func (k *ed25519PrivateKey) PublicKey() (bccsp.Key, error) {
castedKey, ok := k.privKey.Public().(ed25519.PublicKey)
if !ok {
return nil, errors.New("Error casting ed25519 public key")
}
return &ed25519PublicKey{&castedKey}, nil
}

type ed25519PublicKey struct {
pubKey *ed25519.PublicKey
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ed25519PublicKey) Bytes() (raw []byte, err error) {
raw, err = x509.MarshalPKIXPublicKey(*k.pubKey)
if err != nil {
return nil, fmt.Errorf("Failed marshalling key [%s]", err)
}
return
}

// SKI returns the subject key identifier of this key.
func (k *ed25519PublicKey) SKI() []byte {
if k.pubKey == nil {
return nil
}

raw := *(k.pubKey)

// Hash it
hash := sha256.New()
hash.Write(raw)
return hash.Sum(nil)
}

// Symmetric returns true if this key is a symmetric key,
// false if this key is asymmetric
func (k *ed25519PublicKey) Symmetric() bool {
return false
}

// Private returns true if this key is a private key,
// false otherwise.
func (k *ed25519PublicKey) Private() bool {
return false
}

// PublicKey returns the corresponding public key part of an asymmetric public/private key pair.
// This method returns an error in symmetric key schemes.
func (k *ed25519PublicKey) PublicKey() (bccsp.Key, error) {
return k, nil
}
Loading
Loading