Skip to content
This repository has been archived by the owner on Jul 13, 2022. It is now read-only.

Additional Key Types #60

Merged
merged 3 commits into from
Jan 14, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
uses: actions/checkout@v2-beta

- name: Install GolangCI-Linter
run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s latest
run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.22.2

- name: Run linter
run: make lint
Expand Down
35 changes: 30 additions & 5 deletions cmd/chainbridge/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
"syscall"

"github.com/ChainSafe/ChainBridgeV2/crypto"
"github.com/ChainSafe/ChainBridgeV2/crypto/ed25519"
"github.com/ChainSafe/ChainBridgeV2/crypto/secp256k1"
"github.com/ChainSafe/ChainBridgeV2/crypto/sr25519"
"github.com/ChainSafe/ChainBridgeV2/keystore"

log "github.com/ChainSafe/log15"
Expand Down Expand Up @@ -42,8 +44,15 @@ func handleAccountsCmd(ctx *cli.Context) error {
if keygen := ctx.Bool(GenerateFlag.Name); keygen {
log.Info("Generating keypair...")

// check if --ed25519 or --sr25519 is set
keytype := crypto.Secp256k1Type
// TODO: add other key types
if flagtype := ctx.Bool(Sr25519Flag.Name); flagtype {
keytype = crypto.Sr25519Type
} else if flagtype := ctx.Bool(Ed25519Flag.Name); flagtype {
keytype = crypto.Ed25519Type
} else if flagtype := ctx.Bool(Secp256k1Flag.Name); flagtype {
keytype = crypto.Secp256k1Type
}

// check if --password is set
var password []byte = nil
Expand Down Expand Up @@ -159,15 +168,31 @@ func generateKeypair(keytype, datadir string, password []byte) (string, error) {
}

if keytype == "" {
log.Info("Using default key type", "type", keytype)
keytype = crypto.Secp256k1Type
}

var kp crypto.Keypair
var err error
// TODO: add more keytypes
kp, err = secp256k1.GenerateKeypair()
if err != nil {
return "", fmt.Errorf("could not generate secp256k1 keypair: %s", err)

if keytype == crypto.Sr25519Type {
// generate sr25519 keys
kp, err = sr25519.GenerateKeypair()
if err != nil {
return "", fmt.Errorf("could not generate sr25519 keypair: %s", err)
}
} else if keytype == crypto.Ed25519Type {
// generate ed25519 keys
kp, err = ed25519.GenerateKeypair()
if err != nil {
return "", fmt.Errorf("could not generate ed25519 keypair: %s", err)
}
} else if keytype == crypto.Secp256k1Type {
// generate secp256k1 keys
kp, err = secp256k1.GenerateKeypair()
if err != nil {
return "", fmt.Errorf("could not generate secp256k1 keypair: %s", err)
}
}

keystorepath, err := keystoreDir(datadir)
Expand Down
19 changes: 9 additions & 10 deletions cmd/chainbridge/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,15 @@ var (
Name: "list",
Usage: "List node keys",
}
// TODO: add more key types
// Ed25519Flag = cli.BoolFlag{
// Name: "ed25519",
// Usage: "Specify account type as ed25519",
// }
// Sr25519Flag = cli.BoolFlag{
// Name: "sr25519",
// Usage: "Specify account type as sr25519",
// }
Secp256k1 = cli.BoolFlag{
Ed25519Flag = cli.BoolFlag{
Name: "ed25519",
Usage: "Specify account type as ed25519",
}
Sr25519Flag = cli.BoolFlag{
Name: "sr25519",
Usage: "Specify account type as sr25519",
}
Secp256k1Flag = cli.BoolFlag{
Name: "secp256k1",
Usage: "Specify account type as secp256k1",
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/chainbridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ var accountFlags = []cli.Flag{
PasswordFlag,
ImportFlag,
ListFlag,
Secp256k1,
Ed25519Flag,
Sr25519Flag,
Secp256k1Flag,
}

var accountCommand = cli.Command{
Expand Down
178 changes: 178 additions & 0 deletions crypto/ed25519/ed25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package ed25519

import (
"crypto/ed25519"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"

"github.com/ChainSafe/ChainBridgeV2/crypto"
)

const SeedLength = 32
const PublicKeyLength = 32
const PrivateKeyLength = 64
const SignatureLength = 64

// Keypair is a ed25519 public-private keypair
type Keypair struct {
public *PublicKey
private *PrivateKey
}

type PrivateKey ed25519.PrivateKey
type PublicKey ed25519.PublicKey

// NewKeypair returns an Ed25519 keypair given a ed25519 private key
func NewKeypair(priv ed25519.PrivateKey) *Keypair {
pubkey := PublicKey(priv.Public().(ed25519.PublicKey))
privkey := PrivateKey(priv)
return &Keypair{
public: &pubkey,
private: &privkey,
}
}

// NewKeypairFromPrivate returns a ed25519 Keypair given a *ed25519.PrivateKey
func NewKeypairFromPrivate(priv *PrivateKey) (*Keypair, error) {
pub, err := priv.Public()
if err != nil {
return nil, err
}
return &Keypair{
public: pub.(*PublicKey),
private: priv,
}, nil
}

// NewKeypairFromSeed generates a new ed25519 keypair from a 32 byte seed
func NewKeypairFromSeed(seed []byte) (*Keypair, error) {
if len(seed) != SeedLength {
return nil, fmt.Errorf("cannot generate key from seed: seed is not 32 bytes long")
}
edpriv := ed25519.NewKeyFromSeed(seed)
return NewKeypair(edpriv), nil
}

// GenerateKeypair returns a new ed25519 keypair
func GenerateKeypair() (*Keypair, error) {
buf := make([]byte, SeedLength)
_, err := rand.Read(buf)
if err != nil {
return nil, err
}

priv := ed25519.NewKeyFromSeed(buf)

return NewKeypair(priv), nil
}

// NewPublicKey returns an ed25519 public key that consists of the input bytes
// Input length must be 32 bytes
func NewPublicKey(in []byte) (*PublicKey, error) {
if len(in) != PublicKeyLength {
return nil, fmt.Errorf("cannot create public key: input is not 32 bytes")
}

pub := PublicKey(ed25519.PublicKey(in))
return &pub, nil
}

// NewPrivateKey returns an ed25519 private key that consists of the input bytes
// Input length must be 64 bytes
func NewPrivateKey(in []byte) (*PrivateKey, error) {
if len(in) != PrivateKeyLength {
return nil, fmt.Errorf("cannot create private key: input is not %d bytes", PrivateKeyLength)
}

priv := PrivateKey(ed25519.PrivateKey(in))
return &priv, nil
}

// Verify returns true if the signature is valid for the given message and public key, false otherwise
func Verify(pub *PublicKey, msg, sig []byte) (bool, error) {
if len(sig) != SignatureLength {
return false, errors.New("invalid signature length")
}

return ed25519.Verify(ed25519.PublicKey(*pub), msg, sig), nil
}

// Sign uses the keypair to sign the message using the ed25519 signature algorithm
func (kp *Keypair) Sign(msg []byte) ([]byte, error) {
return ed25519.Sign(ed25519.PrivateKey(*kp.private), msg), nil
}

// Public returns the keypair's public key
func (kp *Keypair) Public() crypto.PublicKey {
return kp.public
}

// Private returns the keypair's private key
func (kp *Keypair) Private() crypto.PrivateKey {
return kp.private
}

// Sign uses the ed25519 signature algorithm to sign the message
func (k *PrivateKey) Sign(msg []byte) ([]byte, error) {
return ed25519.Sign(ed25519.PrivateKey(*k), msg), nil
}

// Public returns the public key corresponding to the ed25519 private key
func (k *PrivateKey) Public() (crypto.PublicKey, error) {
kp := NewKeypair(ed25519.PrivateKey(*k))
return kp.Public(), nil
}

// Encode returns the bytes underlying the ed25519 PrivateKey
func (k *PrivateKey) Encode() []byte {
return *k
}

// Decode turns the input bytes into a ed25519 PrivateKey
// the input must be 64 bytes, or the function will return an error
func (k *PrivateKey) Decode(in []byte) error {
priv, err := NewPrivateKey(in)
if err != nil {
return err
}
*k = *priv
return nil
}

// Verify checks that Ed25519PublicKey was used to create the signature for the message
func (k *PublicKey) Verify(msg, sig []byte) (bool, error) {
if len(sig) != SignatureLength {
return false, errors.New("invalid signature length")
}
return ed25519.Verify(ed25519.PublicKey(*k), msg, sig), nil
}

// Encode returns the encoding of the ed25519 PublicKey
func (k *PublicKey) Encode() []byte {
return []byte(ed25519.PublicKey(*k))
}

// Decode turns the input bytes into an ed25519 PublicKey
// the input must be 32 bytes, or the function will return and error
func (k *PublicKey) Decode(in []byte) error {
pub, err := NewPublicKey(in)
if err != nil {
return err
}
*k = *pub
return nil
}

// Address returns the ss58 address for this public key
func (k *PublicKey) Address() crypto.Address {
return crypto.PublicKeyToAddress(k)
}

// Hex returns the public key as a '0x' prefixed hex string
func (k *PublicKey) Hex() string {
enc := k.Encode()
h := hex.EncodeToString(enc)
return "0x" + h
}
74 changes: 74 additions & 0 deletions crypto/ed25519/ed25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ed25519

import (
"reflect"
"testing"

"crypto/ed25519"
)

func TestSignAndVerify(t *testing.T) {
kp, err := GenerateKeypair()
if err != nil {
t.Fatal(err)
}

msg := []byte("helloworld")
sig, _ := kp.Sign(msg)

ok, err := Verify(kp.Public().(*PublicKey), msg, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("Fail: did not verify ed25519 sig")
}
}

func TestPublicKeys(t *testing.T) {
kp, err := GenerateKeypair()
if err != nil {
t.Fatal(err)
}

kp2 := NewKeypair(ed25519.PrivateKey(*(kp.Private().(*PrivateKey))))
if !reflect.DeepEqual(kp.Public(), kp2.Public()) {
t.Fatal("Fail: pubkeys do not match")
}
}

func TestEncodeAndDecodePrivateKey(t *testing.T) {
kp, err := GenerateKeypair()
if err != nil {
t.Fatal(err)
}

enc := kp.Private().Encode()
res := new(PrivateKey)
err = res.Decode(enc)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(res, kp.Private()) {
t.Fatalf("Fail: got %x expected %x", res, kp.Private())
}
}

func TestEncodeAndDecodePublicKey(t *testing.T) {
kp, err := GenerateKeypair()
if err != nil {
t.Fatal(err)
}

enc := kp.Public().Encode()
res := new(PublicKey)
err = res.Decode(enc)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(res, kp.Public()) {
t.Fatalf("Fail: got %x expected %x", res, kp.Public())
}
}