Repository: bitmaelum/bitmaelum-suite
Pull request #77 (2020-11-18 10:47:50)

Moved key functionality to dedicated keytype structures


Patch:
diff --git a/cmd/bm-client/cmd/auth_create.go b/cmd/bm-client/cmd/auth_create.go
index d06cca0..c3ec917 100644
--- a/cmd/bm-client/cmd/auth_create.go
+++ b/cmd/bm-client/cmd/auth_create.go
@@ -37,7 +37,7 @@ var authCreateCmd = &cobra.Command{
 	Long: `By authorizing another users public key, you can allow it to send messages for you. This way, you can let other people or tools send messages without exposing your own private key.
 `,
 	Run: func(cmd *cobra.Command, args []string) {
-		targetKey, err := bmcrypto.NewPubKey(*authPublicKey)
+		targetKey, err := bmcrypto.PublicKeyFromString(*authPublicKey)
 		if err != nil {
 			logrus.Fatal("Incorrect public key to sign")
 			os.Exit(1)
diff --git a/cmd/bm-client/cmd/create_account.go b/cmd/bm-client/cmd/create_account.go
index 74e707e..88ac6dd 100644
--- a/cmd/bm-client/cmd/create_account.go
+++ b/cmd/bm-client/cmd/create_account.go
@@ -23,6 +23,7 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-client/handlers"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
@@ -35,7 +36,11 @@ This assumes you have a BitMaelum invitation token for the specific server.`,
 	Run: func(cmd *cobra.Command, args []string) {
 		v := vault.OpenVault()
 
-		handlers.CreateAccount(v, *addr, *name, *token, bmcrypto.KeyType(*keytype))
+		kt, err := bmcrypto.FindKeyType(*keytype)
+		if err != nil {
+			logrus.Fatal("incorrect key type")
+		}
+		handlers.CreateAccount(v, *addr, *name, *token, kt)
 	},
 }
 
diff --git a/cmd/bm-client/cmd/create_organisation.go b/cmd/bm-client/cmd/create_organisation.go
index 9407ae8..82e53da 100644
--- a/cmd/bm-client/cmd/create_organisation.go
+++ b/cmd/bm-client/cmd/create_organisation.go
@@ -23,6 +23,7 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-client/handlers"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
@@ -35,7 +36,11 @@ This assumes you have a BitMaelum invitation token for the specific server.`,
 	Run: func(cmd *cobra.Command, args []string) {
 		v := vault.OpenVault()
 
-		handlers.CreateOrganisation(v, *orgAddr, *orgFullName, *orgValidations, bmcrypto.KeyType(*orgKeytype))
+		kt, err := bmcrypto.FindKeyType(*orgKeytype)
+		if err != nil {
+			logrus.Fatal("incorrect key type")
+		}
+		handlers.CreateOrganisation(v, *orgAddr, *orgFullName, *orgValidations, kt)
 	},
 }
 
diff --git a/cmd/bm-client/handlers/create_account.go b/cmd/bm-client/handlers/create_account.go
index 0b6e12e..399dd55 100644
--- a/cmd/bm-client/handlers/create_account.go
+++ b/cmd/bm-client/handlers/create_account.go
@@ -107,7 +107,7 @@ func checkAccountInVault(vault *vault.Vault, addr address.Address) *vault.Accoun
 }
 
 // CreateAccount creates a new account locally in the vault, stores it on the mail server and pushes the public key to the resolver
-func CreateAccount(v *vault.Vault, bmAddr, name, token string, keyType bmcrypto.KeyType) {
+func CreateAccount(v *vault.Vault, bmAddr, name, token string, kt bmcrypto.KeyType) {
 	var mnemonicToShow string
 
 	fmt.Println("")
@@ -120,7 +120,7 @@ func CreateAccount(v *vault.Vault, bmAddr, name, token string, keyType bmcrypto.
 	if info == nil {
 		fmt.Printf("* Generating your secret key to send and read mail: ")
 
-		mnemonic, privKey, pubKey, err := internal.GenerateKeypairWithMnemonic(keyType)
+		mnemonic, privKey, pubKey, err := internal.GenerateKeypairWithMnemonic(kt)
 
 		if err != nil {
 			fmt.Print(err)
diff --git a/cmd/bm-client/handlers/create_organisation.go b/cmd/bm-client/handlers/create_organisation.go
index 4bfcf80..b703b49 100644
--- a/cmd/bm-client/handlers/create_organisation.go
+++ b/cmd/bm-client/handlers/create_organisation.go
@@ -34,7 +34,7 @@ import (
 )
 
 // CreateOrganisation creates a new organisation locally in the vault and pushes the public key to the resolver
-func CreateOrganisation(v *vault.Vault, orgAddr, fullName string, orgValidations []string, keyType bmcrypto.KeyType) {
+func CreateOrganisation(v *vault.Vault, orgAddr, fullName string, orgValidations []string, kt bmcrypto.KeyType) {
 	fmt.Printf("* Verifying if organisation name is valid: ")
 	orgHash := hash.New(orgAddr)
 
@@ -76,7 +76,7 @@ func CreateOrganisation(v *vault.Vault, orgAddr, fullName string, orgValidations
 			err     error
 		)
 
-		mnemonic, privKey, pubKey, err = internal.GenerateKeypairWithMnemonic(keyType)
+		mnemonic, privKey, pubKey, err = internal.GenerateKeypairWithMnemonic(kt)
 
 		if err != nil {
 			fmt.Print(err)
diff --git a/cmd/bm-client/handlers/fetch_messages.go b/cmd/bm-client/handlers/fetch_messages.go
index 6bbaafc..a0ee33c 100644
--- a/cmd/bm-client/handlers/fetch_messages.go
+++ b/cmd/bm-client/handlers/fetch_messages.go
@@ -26,10 +26,10 @@ import (
 	"time"
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-client/internal/container"
-	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/internal/api"
 	"github.com/bitmaelum/bitmaelum-suite/internal/message"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 	"github.com/c2h5oh/datasize"
 	"github.com/olekukonko/tablewriter"
 )
@@ -95,12 +95,12 @@ func displayBox(client *api.API, account *vault.AccountInfo, box string, table *
 	}
 
 	for _, msg := range mb.Messages {
-		key, err := message.Decrypt(account.PrivKey, msg.Header.Catalog.TransactionID, msg.Header.Catalog.EncryptedKey)
+		key, err := bmcrypto.Decrypt(account.PrivKey, msg.Header.Catalog.TransactionID, msg.Header.Catalog.EncryptedKey)
 		if err != nil {
 			continue
 		}
 		catalog := &message.Catalog{}
-		err = internal.CatalogDecrypt(key, msg.Catalog, catalog)
+		err = bmcrypto.CatalogDecrypt(key, msg.Catalog, catalog)
 		if err != nil {
 			continue
 		}
diff --git a/cmd/bm-client/handlers/read_message.go b/cmd/bm-client/handlers/read_message.go
index adcfa16..0ae21ee 100644
--- a/cmd/bm-client/handlers/read_message.go
+++ b/cmd/bm-client/handlers/read_message.go
@@ -24,11 +24,11 @@ import (
 	"fmt"
 	"io/ioutil"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/internal/api"
 	"github.com/bitmaelum/bitmaelum-suite/internal/message"
 	"github.com/bitmaelum/bitmaelum-suite/internal/resolver"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 	"github.com/c2h5oh/datasize"
 	"github.com/sirupsen/logrus"
 )
@@ -46,7 +46,7 @@ func ReadMessage(info *vault.AccountInfo, routingInfo *resolver.RoutingInfo, box
 		logrus.Fatal(err)
 	}
 
-	key, err := message.Decrypt(info.PrivKey, msg.Header.Catalog.TransactionID, msg.Header.Catalog.EncryptedKey)
+	key, err := bmcrypto.Decrypt(info.PrivKey, msg.Header.Catalog.TransactionID, msg.Header.Catalog.EncryptedKey)
 	if err != nil {
 		logrus.Fatal(err)
 	}
@@ -58,7 +58,7 @@ func ReadMessage(info *vault.AccountInfo, routingInfo *resolver.RoutingInfo, box
 
 	// Decrypt the catalog
 	catalog := &message.Catalog{}
-	err = internal.CatalogDecrypt(key, msg.Catalog, catalog)
+	err = bmcrypto.CatalogDecrypt(key, msg.Catalog, catalog)
 	if err != nil {
 		logrus.Fatal(err)
 	}
@@ -84,7 +84,7 @@ func ReadMessage(info *vault.AccountInfo, routingInfo *resolver.RoutingInfo, box
 		}
 		bb := bytes.NewBuffer(data)
 
-		r, err := internal.GetAesDecryptorReader(b.IV, b.Key, bb)
+		r, err := bmcrypto.GetAesDecryptorReader(b.IV, b.Key, bb)
 		if err != nil {
 			panic(err)
 		}
diff --git a/cmd/bm-config/cmd/generate_key.go b/cmd/bm-config/cmd/generate_key.go
index cd580de..e75bdb5 100644
--- a/cmd/bm-config/cmd/generate_key.go
+++ b/cmd/bm-config/cmd/generate_key.go
@@ -33,7 +33,11 @@ var generateKeyCmd = &cobra.Command{
 	Short: "Generates a new key",
 	Long:  `This command generates a new key`,
 	Run: func(cmd *cobra.Command, args []string) {
-		privKey, pubKey, err := bmcrypto.GenerateKeyPair(bmcrypto.KeyType(*keyType))
+		kt, err := bmcrypto.FindKeyType(*keyType)
+		if err != nil {
+			logrus.Fatal(err)
+		}
+		privKey, pubKey, err := bmcrypto.GenerateKeyPair(kt)
 		if err != nil {
 			logrus.Fatal(err)
 		}
diff --git a/cmd/bm-config/internal/letsencrypt/letsencrypt.go b/cmd/bm-config/internal/letsencrypt/letsencrypt.go
index f618653..9adf74d 100644
--- a/cmd/bm-config/internal/letsencrypt/letsencrypt.go
+++ b/cmd/bm-config/internal/letsencrypt/letsencrypt.go
@@ -322,7 +322,7 @@ func getAccountFromAcmeDir(dir string) (crypto.Signer, *acme.Account, error) {
 	if err != nil {
 		return nil, nil, err
 	}
-	privKey, err := bmcrypto.NewPrivKey(data)
+	privKey, err := bmcrypto.PrivateKeyFromString(data)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/cmd/bm-send/main.go b/cmd/bm-send/main.go
index e01eb9b..1ce8002 100644
--- a/cmd/bm-send/main.go
+++ b/cmd/bm-send/main.go
@@ -141,7 +141,7 @@ func parseFlags() {
 	}
 
 	// Check key
-	privKey, err = bmcrypto.NewPrivKey(opts.PrivateKey)
+	privKey, err = bmcrypto.PrivateKeyFromString(opts.PrivateKey)
 	if err != nil {
 		_, _ = fmt.Fprintln(os.Stderr, "Incorrect private key specified.")
 		os.Exit(1)
diff --git a/internal/api/jwt.go b/internal/api/jwt.go
index 44bcc33..86ba070 100644
--- a/internal/api/jwt.go
+++ b/internal/api/jwt.go
@@ -54,22 +54,7 @@ func GenerateJWTToken(addr hash.Hash, key bmcrypto.PrivKey) (string, error) {
 		Subject:   addr.String(),
 	}
 
-	var signMethod jwt.SigningMethod
-	switch key.Type {
-	case bmcrypto.KeyTypeRSA:
-		signMethod = jwt.SigningMethodRS256
-	case bmcrypto.KeyTypeRSAV1:
-		signMethod = jwt.SigningMethodRS256
-	case bmcrypto.KeyTypeECDSA:
-		signMethod = jwt.SigningMethodES384
-	case bmcrypto.KeyTypeED25519:
-		sm := &SigningMethodEdDSA{}
-		signMethod = sm
-		var edDSASigningMethod SigningMethodEdDSA
-		jwt.RegisterSigningMethod(edDSASigningMethod.Alg(), func() jwt.SigningMethod { return &edDSASigningMethod })
-	}
-
-	token := jwt.NewWithClaims(signMethod, claims)
+	token := jwt.NewWithClaims(key.Type.JWTSignMethod(), claims)
 
 	return token.SignedString(key.K)
 }
@@ -78,9 +63,6 @@ func GenerateJWTToken(addr hash.Hash, key bmcrypto.PrivKey) (string, error) {
 func ValidateJWTToken(tokenString string, addr hash.Hash, key bmcrypto.PubKey) (*jwt.Token, error) {
 	logrus.Tracef("validating JWT token: %s %s %s", tokenString, addr.String(), key.S)
 
-	var edDSASigningMethod SigningMethodEdDSA
-	jwt.RegisterSigningMethod(edDSASigningMethod.Alg(), func() jwt.SigningMethod { return &edDSASigningMethod })
-
 	// Just return the key from the token
 	kf := func(token *jwt.Token) (interface{}, error) {
 		return key.K, nil
@@ -93,33 +75,8 @@ func ValidateJWTToken(tokenString string, addr hash.Hash, key bmcrypto.PubKey) (
 		return nil, err
 	}
 
-	// Make sure the token actually uses the correct signing method
-	switch key.Type {
-	case bmcrypto.KeyTypeRSA:
-		_, ok := token.Method.(*jwt.SigningMethodRSA)
-		if !ok {
-			logrus.Tracef("auth: jwt: " + invalidSigningMethod)
-			return nil, errors.New(invalidSigningMethod)
-		}
-	case bmcrypto.KeyTypeRSAV1:
-		_, ok := token.Method.(*jwt.SigningMethodRSA)
-		if !ok {
-			logrus.Tracef("auth: jwt: " + invalidSigningMethod)
-			return nil, errors.New(invalidSigningMethod)
-		}
-	case bmcrypto.KeyTypeECDSA:
-		_, ok := token.Method.(*jwt.SigningMethodECDSA)
-		if !ok {
-			logrus.Tracef("auth: jwt: " + invalidSigningMethod)
-			return nil, errors.New(invalidSigningMethod)
-		}
-	case bmcrypto.KeyTypeED25519:
-		_, ok := token.Method.(*SigningMethodEdDSA)
-		if !ok {
-			logrus.Tracef("auth: jwt: " + invalidSigningMethod)
-			return nil, errors.New(invalidSigningMethod)
-		}
-	default:
+	// Make sure the signature method of the JWT matches our public key
+	if !key.Type.JWTHasValidSignMethod(token) {
 		logrus.Tracef("auth: jwt: " + invalidSigningMethod)
 		return nil, errors.New(invalidSigningMethod)
 	}
@@ -148,3 +105,8 @@ func ValidateJWTToken(tokenString string, addr hash.Hash, key bmcrypto.PubKey) (
 	logrus.Trace("auth: jwt: token is valid")
 	return token, nil
 }
+
+func init() {
+	var edDSASigningMethod bmcrypto.SigningMethodEdDSA
+	jwt.RegisterSigningMethod(edDSASigningMethod.Alg(), func() jwt.SigningMethod { return &edDSASigningMethod })
+}
diff --git a/internal/config/routing.go b/internal/config/routing.go
index a8400e6..d884104 100644
--- a/internal/config/routing.go
+++ b/internal/config/routing.go
@@ -95,7 +95,12 @@ func GenerateRoutingFromMnemonic(mnemonic string) (*RoutingConfig, error) {
 
 // GenerateRouting generates a new routing structure
 func GenerateRouting() (string, *RoutingConfig, error) {
-	mnemonic, privKey, pubKey, err := internal.GenerateKeypairWithMnemonic(bmcrypto.KeyTypeED25519)
+	kt, err := bmcrypto.FindKeyType("ed25519")
+	if err != nil {
+		return "", nil, err
+	}
+
+	mnemonic, privKey, pubKey, err := internal.GenerateKeypairWithMnemonic(kt)
 	if err != nil {
 		return "", nil, err
 	}
diff --git a/internal/message/catalog.go b/internal/message/catalog.go
index 6e44aa4..2e0ddda 100644
--- a/internal/message/catalog.go
+++ b/internal/message/catalog.go
@@ -28,7 +28,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/proofofwork"
@@ -151,13 +150,13 @@ func (c *Catalog) AddBlock(entry Block) error {
 	}
 
 	// Generate key iv for this block
-	iv, key, err := internal.GenerateIvAndKey()
+	iv, key, err := bmcrypto.GenerateIvAndKey()
 	if err != nil {
 		return err
 	}
 
 	// Wrap reader with encryption reader
-	reader, err = internal.GetAesEncryptorReader(iv, key, reader)
+	reader, err = bmcrypto.GetAesEncryptorReader(iv, key, reader)
 	if err != nil {
 		return err
 	}
@@ -205,13 +204,13 @@ func (c *Catalog) AddAttachment(entry Attachment) error {
 	}
 
 	// Generate Key and IV that we will use for encryption
-	iv, key, err := internal.GenerateIvAndKey()
+	iv, key, err := bmcrypto.GenerateIvAndKey()
 	if err != nil {
 		return err
 	}
 
 	// Wrap our reader with the encryption reader
-	reader, err = internal.GetAesEncryptorReader(iv, key, reader)
+	reader, err = bmcrypto.GetAesEncryptorReader(iv, key, reader)
 	if err != nil {
 		return err
 	}
diff --git a/internal/message/encrypt.go b/internal/message/encrypt.go
deleted file mode 100644
index 05fdd0a..0000000
--- a/internal/message/encrypt.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2020 BitMaelum Authors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of
-// this software and associated documentation files (the "Software"), to deal in
-// the Software without restriction, including without limitation the rights to
-// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-// the Software, and to permit persons to whom the Software is furnished to do so,
-// subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-package message
-
-import (
-	"crypto/rand"
-	"crypto/rsa"
-	"errors"
-
-	"github.com/bitmaelum/bitmaelum-suite/internal"
-	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
-)
-
-// Encrypt a message with the given key
-func Encrypt(pubKey bmcrypto.PubKey, message []byte) ([]byte, string, string, error) {
-	if !pubKey.CanEncrypt() && !pubKey.CanKeyExchange() {
-		return nil, "", "", errors.New("this key type is not usable for encryption")
-	}
-
-	switch pubKey.Type {
-	case bmcrypto.KeyTypeRSA, bmcrypto.KeyTypeRSAV1:
-		encryptedMessage, err := encryptRsa(pubKey.K.(*rsa.PublicKey), message)
-		return encryptedMessage, "", "rsa+aes256gcm", err
-		// TODO: Implement KeyTypeECDSA
-	case bmcrypto.KeyTypeED25519:
-		return encryptED25519(pubKey, message)
-	}
-
-	return nil, "", "", errors.New("encryption not implemented for" + pubKey.Type.String())
-}
-
-// Decrypt a message with the given key
-func Decrypt(key bmcrypto.PrivKey, txID string, message []byte) ([]byte, error) {
-	if !key.CanEncrypt() && !key.CanKeyExchange() {
-		return nil, errors.New("this key type is not usable for encryption")
-	}
-
-	switch key.Type {
-	case bmcrypto.KeyTypeRSA, bmcrypto.KeyTypeRSAV1:
-		return decryptRsa(key.K.(*rsa.PrivateKey), message)
-	// TODO: Implement KeyTypeECDSA
-	case bmcrypto.KeyTypeED25519:
-		return decryptED25519(key, txID, message)
-
-	}
-
-	return nil, errors.New("encryption not implemented for" + key.Type.String())
-}
-
-func encryptRsa(key *rsa.PublicKey, message []byte) ([]byte, error) {
-	return rsa.EncryptPKCS1v15(rand.Reader, key, message)
-}
-
-func decryptRsa(key *rsa.PrivateKey, message []byte) ([]byte, error) {
-	return rsa.DecryptPKCS1v15(rand.Reader, key, message)
-}
-
-func encryptED25519(pubKey bmcrypto.PubKey, message []byte) ([]byte, string, string, error) {
-	secret, txID, err := bmcrypto.DualKeyExchange(pubKey)
-	if err != nil {
-		return nil, "", "", err
-	}
-
-	encryptedMessage, err := internal.MessageEncrypt(secret, message)
-	return encryptedMessage, txID.ToHex(), "ed25519+aes256gcm", err
-}
-
-func decryptED25519(privKey bmcrypto.PrivKey, txIDString string, message []byte) ([]byte, error) {
-	txID, err := bmcrypto.TxIDFromString(txIDString)
-	if err != nil {
-		return nil, err
-	}
-
-	secret, ok, err := bmcrypto.DualKeyGetSecret(privKey, *txID)
-	if !ok || err != nil {
-		return nil, err
-	}
-
-	return internal.MessageDecrypt(secret, message)
-}
diff --git a/internal/message/envelope.go b/internal/message/envelope.go
index 0c62987..1532643 100644
--- a/internal/message/envelope.go
+++ b/internal/message/envelope.go
@@ -24,7 +24,6 @@ import (
 	"errors"
 	"io"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 )
@@ -89,7 +88,7 @@ func NewEnvelope() (*Envelope, error) {
 	}
 
 	// Always create a catalog key for later use
-	envelope.catalogKey, err = internal.CreateCatalogKey()
+	envelope.catalogKey, err = bmcrypto.CreateCatalogKey()
 	if err != nil {
 		return nil, err
 	}
@@ -139,7 +138,7 @@ func (e *Envelope) CloseAndEncrypt(senderPrivKey *bmcrypto.PrivKey, recipientPub
 	}
 
 	// Encrypt the catalog
-	e.EncryptedCatalog, err = internal.JSONEncrypt(e.catalogKey, e.catalog)
+	e.EncryptedCatalog, err = bmcrypto.JSONEncrypt(e.catalogKey, e.catalog)
 	if err != nil {
 		return err
 	}
@@ -153,7 +152,7 @@ func (e *Envelope) CloseAndEncrypt(senderPrivKey *bmcrypto.PrivKey, recipientPub
 
 	// Set catalog information in the header
 	e.Header.Catalog.Size = uint64(len(e.EncryptedCatalog))
-	ek, tx, cr, err := Encrypt(*recipientPubKey, e.catalogKey)
+	ek, tx, cr, err := bmcrypto.Encrypt(*recipientPubKey, e.catalogKey)
 	if err != nil {
 		return err
 	}
diff --git a/internal/mnemonic.go b/internal/mnemonic.go
index 489fbe3..dc918f4 100644
--- a/internal/mnemonic.go
+++ b/internal/mnemonic.go
@@ -20,50 +20,15 @@
 package internal
 
 import (
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/ed25519"
-	"crypto/sha256"
-	"errors"
-	"io"
+	"bytes"
 	"strings"
 
 	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
-	deterministicRsaKeygen "github.com/cloudflare/gokey/rsa"
 	"github.com/tyler-smith/go-bip39"
-	"golang.org/x/crypto/hkdf"
 )
 
-//Taken from https://github.com/cloudflare/gokey/blob/6bb7290160583cf1fd7cdcb5726093a00dd23c25/csprng.go#L18
-type devZero struct{}
-
-func (dz devZero) Read(p []byte) (n int, err error) {
-	for i := range p {
-		p[i] = 0
-	}
-	return len(p), nil
-}
-
-// GenerateRSAKeypairFromMnemonic generates a keypair based on the given mnemonic
-func GenerateRSAKeypairFromMnemonic(mnemonic string, keyType bmcrypto.KeyType) (*bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	e, err := bip39.MnemonicToByteArray(mnemonic, true)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var bits int
-	if keyType == bmcrypto.KeyTypeRSA {
-		bits = bmcrypto.RsaBits[0]
-	}
-	if keyType == bmcrypto.KeyTypeRSAV1 {
-		bits = bmcrypto.RsaBits[1]
-	}
-
-	return genRSAKey(e, bits)
-}
-
-// GenerateRSAKeypairWithMnemonic generates a mnemonic, and a RSA keypair that can be generated through the same mnemonic again.
-func GenerateRSAKeypairWithMnemonic(version int) (string, *bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
+// GenerateKeypairWithMnemonic generates a mnemonic, and a RSA keypair that can be generated through the same mnemonic again.
+func GenerateKeypairWithMnemonic(kt bmcrypto.KeyType) (string, *bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
 	// Generate large enough random string
 	e, err := bip39.NewEntropy(192)
 	if err != nil {
@@ -76,119 +41,27 @@ func GenerateRSAKeypairWithMnemonic(version int) (string, *bmcrypto.PrivKey, *bm
 		return "", nil, nil, err
 	}
 
-	privKey, pubKey, err := genRSAKey(e, bmcrypto.RsaBits[version])
+	privKey, pubKey, err := kt.GenerateKeyPair(bytes.NewReader(e))
 	if err != nil {
 		return "", nil, nil, err
 	}
 
-	return "rsa " + mnemonic, privKey, pubKey, nil
+	return kt.String() + " " + mnemonic, privKey, pubKey, nil
 }
 
 // GenerateKeypairFromMnemonic generates a keypair based on the given mnemonic
 func GenerateKeypairFromMnemonic(mnemonic string) (*bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	// @TODO: We should be able to use different kind of keys and such
-
-	// Check if it's a RSA mnemonic
 	words := strings.SplitN(mnemonic, " ", 2)
-	if strings.ToLower(words[0]) == "rsa" {
-		return GenerateRSAKeypairFromMnemonic(strings.Join(words[1:], " "), bmcrypto.KeyTypeRSA)
-	}
 
-	e, err := bip39.MnemonicToByteArray(mnemonic, true)
+	kt, err := bmcrypto.FindKeyType(words[0])
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return genKey(e)
-}
-
-// GenerateKeypairWithMnemonic generates a mnemonic, and a keypair that can be generated through the same mnemonic again.
-func GenerateKeypairWithMnemonic(kt bmcrypto.KeyType) (string, *bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	switch kt {
-	case bmcrypto.KeyTypeRSA:
-		return GenerateRSAKeypairWithMnemonic(0)
-	case bmcrypto.KeyTypeRSAV1:
-		return GenerateRSAKeypairWithMnemonic(1)
-	case bmcrypto.KeyTypeED25519:
-		return GenerateED25519KeypairWithMnemonic()
-	default:
-		return "", nil, nil, errors.New("key type not supported")
-	}
-}
-
-// GenerateED25519KeypairWithMnemonic generates a mnemonic, and a ED25519 keypair that can be generated through the same mnemonic again.
-func GenerateED25519KeypairWithMnemonic() (string, *bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	// Generate large enough random string
-	e, err := bip39.NewEntropy(192)
-	if err != nil {
-		return "", nil, nil, err
-	}
-
-	// Generate Mnemonic words
-	mnemonic, err := bip39.NewMnemonic(e)
-	if err != nil {
-		return "", nil, nil, err
-	}
-
-	privKey, pubKey, err := genKey(e)
-	if err != nil {
-		return "", nil, nil, err
-	}
-
-	return mnemonic, privKey, pubKey, nil
-}
-
-func genRSAKey(e []byte, bits int) (*bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	// Stretch 192 bits to 256 bits
-	rd := hkdf.New(sha256.New, e, []byte{}, []byte{})
-	expbuf := make([]byte, 32)
-	_, err := io.ReadFull(rd, expbuf)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Generate keypair
-	// Taken from https://github.com/cloudflare/gokey/blob/6bb7290160583cf1fd7cdcb5726093a00dd23c25/csprng.go#L56
-	block, _ := aes.NewCipher(expbuf[:32])
-	stream := cipher.NewCTR(block, make([]byte, 16))
-
-	randReader := cipher.StreamReader{S: stream, R: devZero{}}
-	privRSAKey, err := deterministicRsaKeygen.GenerateKey(randReader, bits)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	privKey, err := bmcrypto.NewPrivKeyFromInterface(privRSAKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	pubKey, err := bmcrypto.NewPubKeyFromInterface(privRSAKey.Public())
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return privKey, pubKey, nil
-}
-
-func genKey(e []byte) (*bmcrypto.PrivKey, *bmcrypto.PubKey, error) {
-	// Stretch 192 bits to 256 bits
-	rd := hkdf.New(sha256.New, e, []byte{}, []byte{})
-	expbuf := make([]byte, 32)
-	_, err := io.ReadFull(rd, expbuf)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Generate keypair
-	r := ed25519.NewKeyFromSeed(expbuf[:32])
-	privKey, err := bmcrypto.NewPrivKeyFromInterface(r)
-	if err != nil {
-		return nil, nil, err
-	}
-	pubKey, err := bmcrypto.NewPubKeyFromInterface(r.Public())
+	e, err := bip39.MnemonicToByteArray(words[1], true)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return privKey, pubKey, nil
+	return kt.GenerateKeyPair(bytes.NewReader(e))
 }
diff --git a/internal/resolver/sqlite.go b/internal/resolver/sqlite.go
index 1ca1f9d..f1d4159 100644
--- a/internal/resolver/sqlite.go
+++ b/internal/resolver/sqlite.go
@@ -105,7 +105,7 @@ func (r *sqliteRepo) ResolveAddress(addr hash.Hash) (*AddressInfo, error) {
 		return nil, err
 	}
 
-	pk, err := bmcrypto.NewPubKey(p)
+	pk, err := bmcrypto.PublicKeyFromString(p)
 	if err != nil {
 		return nil, err
 	}
@@ -130,7 +130,7 @@ func (r *sqliteRepo) ResolveRouting(routingID string) (*RoutingInfo, error) {
 		return nil, err
 	}
 
-	pk, err := bmcrypto.NewPubKey(p)
+	pk, err := bmcrypto.PublicKeyFromString(p)
 	if err != nil {
 		return nil, err
 	}
@@ -154,7 +154,7 @@ func (r *sqliteRepo) ResolveOrganisation(orgHash hash.Hash) (*OrganisationInfo,
 		return nil, err
 	}
 
-	pk, err := bmcrypto.NewPubKey(p)
+	pk, err := bmcrypto.PublicKeyFromString(p)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/aes_256_gcm.go b/pkg/bmcrypto/aes_256_gcm.go
similarity index 99%
rename from internal/aes_256_gcm.go
rename to pkg/bmcrypto/aes_256_gcm.go
index 204bc94..743fe21 100644
--- a/internal/aes_256_gcm.go
+++ b/pkg/bmcrypto/aes_256_gcm.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package internal
+package bmcrypto
 
 import (
 	"crypto/aes"
diff --git a/pkg/bmcrypto/sign.go b/pkg/bmcrypto/bmcrypto.go
similarity index 50%
rename from pkg/bmcrypto/sign.go
rename to pkg/bmcrypto/bmcrypto.go
index 77d33c2..d917ba5 100644
--- a/pkg/bmcrypto/sign.go
+++ b/pkg/bmcrypto/bmcrypto.go
@@ -20,13 +20,9 @@
 package bmcrypto
 
 import (
-	"crypto"
-	"crypto/ecdsa"
-	"crypto/ed25519"
 	"crypto/rand"
-	"crypto/rsa"
-	"encoding/asn1"
 	"errors"
+	"fmt"
 	"io"
 	"math/big"
 )
@@ -38,55 +34,59 @@ type ecdsaSignature struct {
 	R, S *big.Int
 }
 
+// FindKeyType finds the keytype based on the given string
+func FindKeyType(typ string) (KeyType, error) {
+	for _, kt := range KeyTypes {
+		if kt.String() == typ {
+			return kt, nil
+		}
+	}
+
+	return nil, fmt.Errorf("unknown key type specified '%s'", typ)
+}
+
 // Sign a message based on the given key.
 func Sign(key PrivKey, message []byte) ([]byte, error) {
-	switch key.Type {
-	case KeyTypeRSA, KeyTypeRSAV1:
-		h := crypto.SHA256.New()
-		h.Write(message)
-		hash := h.Sum(nil)
-
-		return rsa.SignPKCS1v15(randReader, key.K.(*rsa.PrivateKey), crypto.SHA256, hash[:])
-	case KeyTypeECDSA:
-		r, s, err := ecdsa.Sign(randReader, key.K.(*ecdsa.PrivateKey), message)
-		if err != nil {
-			return nil, err
-		}
+	return key.Type.Sign(randReader, key, message)
+}
 
-		sig := ecdsaSignature{
-			R: r,
-			S: s,
-		}
+// Verify if hash compares against the signature of the message
+func Verify(key PubKey, message []byte, sig []byte) (bool, error) {
+	return key.Type.Verify(key, message, sig)
+}
 
-		return asn1.Marshal(sig)
-	case KeyTypeED25519:
-		return ed25519.Sign(key.K.(ed25519.PrivateKey), message), nil
+// GenerateKeyPair generates a private/public keypair based on the given type
+func GenerateKeyPair(kt KeyType) (*PrivKey, *PubKey, error) {
+	if kt == nil {
+		return nil, nil, errors.New("need a valid keytype")
 	}
 
-	return nil, errors.New("unknown key type for signing")
+	return kt.GenerateKeyPair(randReader)
 }
 
-// Verify if hash compares against the signature of the message
-func Verify(key PubKey, message []byte, sig []byte) (bool, error) {
-	switch key.Type {
-	case KeyTypeRSA:
-		h := crypto.SHA256.New()
-		h.Write(message)
-		hash := h.Sum(nil)
-
-		err := rsa.VerifyPKCS1v15(key.K.(*rsa.PublicKey), crypto.SHA256, hash[:], sig)
-		return err == nil, err
-	case KeyTypeECDSA:
-		ecdsaSig := ecdsaSignature{}
-		_, err := asn1.Unmarshal(sig, &ecdsaSig)
-		if err != nil {
-			return false, err
-		}
+// KeyExchange exchange a message given the Private and other's Public Key
+func KeyExchange(privK PrivKey, pubK PubKey) ([]byte, error) {
+	if !privK.Type.CanKeyExchange() {
+		return nil, errors.New("key type cannot be used for key exchange")
+	}
+
+	return privK.Type.KeyExchange(privK, pubK)
+}
+
+// Encrypt a message with the given key
+func Encrypt(pubKey PubKey, message []byte) ([]byte, string, string, error) {
+	if !pubKey.Type.CanEncrypt() && !pubKey.Type.CanKeyExchange() {
+		return nil, "", "", errors.New("this key type is not usable for encryption")
+	}
+
+	return pubKey.Type.Encrypt(pubKey, message)
+}
 
-		return ecdsa.Verify(key.K.(*ecdsa.PublicKey), message, ecdsaSig.R, ecdsaSig.S), nil
-	case KeyTypeED25519:
-		return ed25519.Verify(key.K.(ed25519.PublicKey), message, sig), nil
+// Decrypt a message with the given key
+func Decrypt(key PrivKey, txID string, message []byte) ([]byte, error) {
+	if !key.Type.CanEncrypt() && !key.Type.CanKeyExchange() {
+		return nil, errors.New("this key type is not usable for encryption")
 	}
 
-	return false, errors.New("unknown key type for signing")
+	return key.Type.Decrypt(key, txID, message)
 }
diff --git a/pkg/bmcrypto/dual_keyexchange.go b/pkg/bmcrypto/dual_keyexchange.go
index 442deee..5eaba86 100644
--- a/pkg/bmcrypto/dual_keyexchange.go
+++ b/pkg/bmcrypto/dual_keyexchange.go
@@ -65,36 +65,18 @@ func TxIDFromString(s string) (*TransactionID, error) {
 // for alice and bob to communicate through a non-deterministic DH.
 // It returns the (shared) secret, a transaction ID that needs to be send over to the other user.
 func DualKeyExchange(pub PubKey) ([]byte, *TransactionID, error) {
-	if pub.Type != KeyTypeED25519 {
-		return nil, nil, errors.New("can only use ed25519 keys in dual key exchange")
+	if !pub.Type.CanDualKeyExchange() {
+		return nil, nil, errors.New("this type cannot be used for dual key exchange")
 	}
 
-	r := ed25519.NewKeyFromSeed(generateRandomScalar())
-	R := r.Public()
-
-	// Step 1: D = rA
-	D, err := curve25519.X25519(
-		EdPrivToX25519(r.Seed()),
-		EdPubToX25519(pub.K.(ed25519.PublicKey)),
-	)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	f := hs(D)      // Step 2: f = Hs(D)
-	P := f.Public() // Step 3-5: convert F into private Key (F=fG)
-
-	return D, &TransactionID{
-		P: P.(ed25519.PublicKey)[:32],
-		R: R.(ed25519.PublicKey)[:32],
-	}, nil
+	return pub.Type.DualKeyExchange(pub)
 }
 
 // DualKeyGetSecret verifies if the transaction ID matches our private key. If so, it will return the
 // secret that has been exchanged
 func DualKeyGetSecret(priv PrivKey, txID TransactionID) ([]byte, bool, error) {
-	if priv.Type != KeyTypeED25519 {
-		return nil, false, errors.New("can only use ed25519 keys in dual key exchange")
+	if !priv.Type.CanDualKeyExchange() {
+		return nil, false, errors.New("this type cannot be used for dual key exchange")
 	}
 
 	// Step 1-3: D' = aR
@@ -117,25 +99,22 @@ func DualKeyGetSecret(priv PrivKey, txID TransactionID) ([]byte, bool, error) {
 	return nil, false, nil
 }
 
-func generateRandomScalar() []byte {
+func generateRandomScalar() ([]byte, error) {
 	// get 256bits/32bytes of random data
 	buf := make([]byte, 32)
 	_, err := io.ReadFull(randReader, buf)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 
 	var buf32 [32]byte
 	copy(buf32[:], buf[:32])
 
 	scReduce32(&buf32)
-	return buf32[:]
+	return buf32[:], nil
 }
 
 func hs(b []byte) ed25519.PrivateKey {
-	if len(b) != 32 {
-		panic("incorrect hash len")
-	}
 	h := sha3.NewLegacyKeccak256()
 	h.Write(b)
 
diff --git a/pkg/bmcrypto/generate.go b/pkg/bmcrypto/generate.go
deleted file mode 100644
index 765f739..0000000
--- a/pkg/bmcrypto/generate.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2020 BitMaelum Authors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of
-// this software and associated documentation files (the "Software"), to deal in
-// the Software without restriction, including without limitation the rights to
-// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-// the Software, and to permit persons to whom the Software is furnished to do so,
-// subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-package bmcrypto
-
-import (
-	"crypto/ecdsa"
-	"crypto/ed25519"
-	"crypto/elliptic"
-	"crypto/rsa"
-	"errors"
-)
-
-var (
-	curveFunc = elliptic.P384 // Curve used for ECDSA
-	// RsaBits is an array with the given RSA versions and their corresponding bit sizes
-	RsaBits = []int{2048, 4096} // RSA key sizes
-)
-
-// GenerateKeyPair generates a private/public keypair based on the given type
-func GenerateKeyPair(kt KeyType) (*PrivKey, *PubKey, error) {
-	switch kt {
-	case KeyTypeRSA:
-		return generateKeyPairRSA(0)
-	case KeyTypeRSAV1:
-		return generateKeyPairRSA(1)
-	case KeyTypeECDSA:
-		return generateKeyPairECDSA()
-	case KeyTypeED25519:
-		return generateKeyPairED25519()
-	}
-
-	return nil, nil, errors.New("incorrect key type specified")
-}
-
-func generateKeyPairRSA(version int) (*PrivKey, *PubKey, error) {
-	privRSAKey, err := rsa.GenerateKey(randReader, RsaBits[version])
-	if err != nil {
-		return nil, nil, err
-	}
-
-	privKey, err := NewPrivKeyFromInterface(privRSAKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	pubKey, err := NewPubKeyFromInterface(privKey.K.(*rsa.PrivateKey).Public())
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Set the correct version of the key, since rsa can have multiple versions
-	switch version {
-	case 0:
-		privKey.Type = KeyTypeRSA
-		pubKey.Type = KeyTypeRSA
-	case 1:
-		privKey.Type = KeyTypeRSAV1
-		pubKey.Type = KeyTypeRSAV1
-	}
-
-	return privKey, pubKey, nil
-}
-
-func generateKeyPairECDSA() (*PrivKey, *PubKey, error) {
-	privECDSAKey, err := ecdsa.GenerateKey(curveFunc(), randReader)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	privKey, err := NewPrivKeyFromInterface(privECDSAKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	pubKey, err := NewPubKeyFromInterface(privKey.K.(*ecdsa.PrivateKey).Public())
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return privKey, pubKey, nil
-}
-
-func generateKeyPairED25519() (*PrivKey, *PubKey, error) {
-	pubKey, privKey, err := ed25519.GenerateKey(randReader)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	priv, err := NewPrivKeyFromInterface(privKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	pub, err := NewPubKeyFromInterface(pubKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	return priv, pub, nil
-}
diff --git a/internal/api/jwt_ed25519_sign.go b/pkg/bmcrypto/jwt_ed25519_sign.go
similarity index 99%
rename from internal/api/jwt_ed25519_sign.go
rename to pkg/bmcrypto/jwt_ed25519_sign.go
index 7c57443..c3d521b 100644
--- a/internal/api/jwt_ed25519_sign.go
+++ b/pkg/bmcrypto/jwt_ed25519_sign.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package api
+package bmcrypto
 
 import (
 	"crypto/ed25519"
diff --git a/pkg/bmcrypto/key_ecdsa.go b/pkg/bmcrypto/key_ecdsa.go
new file mode 100644
index 0000000..5347229
--- /dev/null
+++ b/pkg/bmcrypto/key_ecdsa.go
@@ -0,0 +1,191 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package bmcrypto
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/x509"
+	"encoding/asn1"
+	"errors"
+	"io"
+
+	"github.com/vtolstov/jwt-go"
+)
+
+// KeyEcdsa is a keytype for elliptic curve
+type KeyEcdsa struct {
+	Curve elliptic.Curve
+}
+
+// NewEcdsaKey creates a new keytype based on the given curve
+func NewEcdsaKey(curve elliptic.Curve) KeyType {
+	return &KeyEcdsa{
+		Curve: curve,
+	}
+}
+
+// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
+func (k *KeyEcdsa) CanEncrypt() bool {
+	return false
+}
+
+// CanKeyExchange returns true if the key(type) is able to be used for key exchange
+func (k *KeyEcdsa) CanKeyExchange() bool {
+	return true
+}
+
+// CanDualKeyExchange returns true if the key(type) is able to be used for a dual key exchange
+func (k *KeyEcdsa) CanDualKeyExchange() bool {
+	return false
+}
+
+// String returns a string representation of the key type ("rsa", "ecdsa", "ed25519" etc)
+func (k *KeyEcdsa) String() string {
+	return "ecdsa"
+}
+
+// ParsePrivateKeyData will parse a string representation of a key and returns the given key
+func (k *KeyEcdsa) ParsePrivateKeyData(buf []byte) (interface{}, error) {
+	return x509.ParsePKCS8PrivateKey(buf)
+	// return x509.ParseECPrivateKey(buf)
+}
+
+// ParsePrivateKeyInterface will parse a interface and returns the key representation
+func (k *KeyEcdsa) ParsePrivateKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case *ecdsa.PrivateKey:
+		return x509.MarshalPKCS8PrivateKey(key)
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// GenerateKeyPair will generate a new keypair for this keytype. io.Reader can be deterministic if needed
+func (k *KeyEcdsa) GenerateKeyPair(r io.Reader) (*PrivKey, *PubKey, error) {
+	pk, err := ecdsa.GenerateKey(k.Curve, r)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	privKey, err := PrivateKeyFromInterface(k, pk)
+	if err != nil {
+		return nil, nil, err
+	}
+	pubKey, err := PublicKeyFromInterface(k, pk.Public())
+	if err != nil {
+		return nil, nil, err
+	}
+	return privKey, pubKey, nil
+}
+
+// JWTSignMethod will return the signing method used for this keytype
+func (k *KeyEcdsa) JWTSignMethod() jwt.SigningMethod {
+	return jwt.SigningMethodES384
+}
+
+// JWTHasValidSignMethod will return true when this keytype has been used for signing the token
+func (k *KeyEcdsa) JWTHasValidSignMethod(token *jwt.Token) bool {
+	_, ok := token.Method.(*jwt.SigningMethodECDSA)
+	return ok
+}
+
+// Verify will verify the signature with the public key
+func (k *KeyEcdsa) Verify(key PubKey, message []byte, sig []byte) (bool, error) {
+	ecdsaSig := ecdsaSignature{}
+	_, err := asn1.Unmarshal(sig, &ecdsaSig)
+	if err != nil {
+		return false, err
+	}
+
+	return ecdsa.Verify(key.K.(*ecdsa.PublicKey), message, ecdsaSig.R, ecdsaSig.S), nil
+}
+
+// Sign will sign the given bytes with the private key
+func (k *KeyEcdsa) Sign(_ io.Reader, key PrivKey, message []byte) ([]byte, error) {
+	r, s, err := ecdsa.Sign(randReader, key.K.(*ecdsa.PrivateKey), message)
+	if err != nil {
+		return nil, err
+	}
+
+	sig := ecdsaSignature{
+		R: r,
+		S: s,
+	}
+
+	return asn1.Marshal(sig)
+}
+
+// Encrypt will encrypt the given bytes with the public key. Will return the ciphertext, a transaction ID (if needed), the crypto used and an error
+func (k *KeyEcdsa) Encrypt(key PubKey, message []byte) ([]byte, string, string, error) {
+	secret, txID, err := DualKeyExchange(key)
+	if err != nil {
+		return nil, "", "", err
+	}
+
+	encryptedMessage, err := MessageEncrypt(secret, message)
+
+	return encryptedMessage, txID.ToHex(), "ecdsa+aes", err
+}
+
+// Decrypt will decrypt the given bytes with the private key
+func (k *KeyEcdsa) Decrypt(key PrivKey, txID string, message []byte) ([]byte, error) {
+	tx, err := TxIDFromString(txID)
+	if err != nil {
+		return nil, err
+	}
+
+	secret, ok, err := DualKeyGetSecret(key, *tx)
+	if !ok || err != nil {
+		return nil, err
+	}
+
+	return MessageDecrypt(secret, message)
+}
+
+// ParsePublicKeyData will parse a interface and returns the key representation
+func (k *KeyEcdsa) ParsePublicKeyData(buf []byte) (interface{}, error) {
+	return x509.ParsePKIXPublicKey(buf)
+}
+
+// ParsePublicKeyInterface will parse a interface and returns the key representation
+func (k *KeyEcdsa) ParsePublicKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case *ecdsa.PublicKey:
+		return x509.MarshalPKIXPublicKey(key)
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// KeyExchange allows for a key exchange (if possible in the keytype)
+func (k *KeyEcdsa) KeyExchange(privK PrivKey, pubK PubKey) ([]byte, error) {
+	ke, _ := pubK.K.(*ecdsa.PublicKey).Curve.ScalarMult(
+		pubK.K.(*ecdsa.PublicKey).X,
+		pubK.K.(*ecdsa.PublicKey).Y,
+		privK.K.(*ecdsa.PrivateKey).D.Bytes(),
+	)
+	return ke.Bytes(), nil
+}
+
+// DualKeyExchange allows for a ECIES key exchange
+func (k *KeyEcdsa) DualKeyExchange(pub PubKey) ([]byte, *TransactionID, error) {
+	return nil, nil, errors.New("not supported yet")
+}
diff --git a/pkg/bmcrypto/key_ed25519.go b/pkg/bmcrypto/key_ed25519.go
new file mode 100644
index 0000000..38c4f51
--- /dev/null
+++ b/pkg/bmcrypto/key_ed25519.go
@@ -0,0 +1,258 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package bmcrypto
+
+import (
+	"crypto/ed25519"
+	"crypto/sha256"
+	"crypto/sha512"
+	"crypto/x509"
+	"errors"
+	"io"
+	"math/big"
+
+	"github.com/vtolstov/jwt-go"
+	"golang.org/x/crypto/curve25519"
+	"golang.org/x/crypto/hkdf"
+)
+
+// KeyEd25519 is the ed25519 keytype
+type KeyEd25519 struct {
+}
+
+// NewEd25519Key will return the keytype of ed25519. There is only a single curve here.
+func NewEd25519Key() KeyType {
+	return &KeyEd25519{}
+}
+
+// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
+func (k *KeyEd25519) CanEncrypt() bool {
+	return false
+}
+
+// CanKeyExchange returns true if the key(type) is able to be used for key exchange
+func (k *KeyEd25519) CanKeyExchange() bool {
+	return true
+}
+
+// CanDualKeyExchange returns true if the key(type) is able to be used for a dual key exchange
+func (k *KeyEd25519) CanDualKeyExchange() bool {
+	return true
+}
+
+// String returns a string representation of the key type ("rsa", "ecdsa", "ed25519" etc)
+func (k *KeyEd25519) String() string {
+	return "ed25519"
+}
+
+// ParsePrivateKeyData will parse a string representation of a key and returns the given key
+func (k *KeyEd25519) ParsePrivateKeyData(buf []byte) (interface{}, error) {
+	return x509.ParsePKCS8PrivateKey(buf)
+}
+
+// ParsePrivateKeyInterface will parse a interface and returns the key representation
+func (k *KeyEd25519) ParsePrivateKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case ed25519.PrivateKey:
+		return x509.MarshalPKCS8PrivateKey(key)
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// GenerateKeyPair will generate a new keypair for this keytype. io.Reader can be deterministic if needed
+func (k *KeyEd25519) GenerateKeyPair(r io.Reader) (*PrivKey, *PubKey, error) {
+	// Read 192 bits
+	randBuf := make([]byte, 24)
+	_, err := io.ReadFull(r, randBuf)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Stretch 192 bits to 256 bits
+	rd := hkdf.New(sha256.New, randBuf[:], []byte{}, []byte{})
+	expBuf := make([]byte, 32)
+	_, err = io.ReadFull(rd, expBuf)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Generate keypair
+	pk := ed25519.NewKeyFromSeed(expBuf[:32])
+	privKey, err := PrivateKeyFromInterface(k, pk)
+	if err != nil {
+		return nil, nil, err
+	}
+	pubKey, err := PublicKeyFromInterface(k, pk.Public())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return privKey, pubKey, nil
+}
+
+// JWTSignMethod will return the signing method used for this keytype
+func (k *KeyEd25519) JWTSignMethod() jwt.SigningMethod {
+	return &SigningMethodEdDSA{}
+}
+
+// JWTHasValidSignMethod will return true when this keytype has been used for signing the token
+func (k *KeyEd25519) JWTHasValidSignMethod(token *jwt.Token) bool {
+	_, ok := token.Method.(*SigningMethodEdDSA)
+	return ok
+}
+
+// Verify will verify the signature with the public key
+func (k *KeyEd25519) Verify(key PubKey, message []byte, sig []byte) (bool, error) {
+	return ed25519.Verify(key.K.(ed25519.PublicKey), message, sig), nil
+}
+
+// Sign will sign the given bytes with the private key
+func (k *KeyEd25519) Sign(reader io.Reader, key PrivKey, message []byte) ([]byte, error) {
+	return ed25519.Sign(key.K.(ed25519.PrivateKey), message), nil
+}
+
+// Encrypt will encrypt the given bytes with the public key. Will return the ciphertext, a transaction ID (if needed), the crypto used and an error
+func (k *KeyEd25519) Encrypt(key PubKey, message []byte) ([]byte, string, string, error) {
+	secret, txID, err := DualKeyExchange(key)
+	if err != nil {
+		return nil, "", "", err
+	}
+
+	encryptedMessage, err := MessageEncrypt(secret, message)
+
+	return encryptedMessage, txID.ToHex(), "ed25519+aes", err
+}
+
+// Decrypt will decrypt the given bytes with the private key
+func (k *KeyEd25519) Decrypt(key PrivKey, txID string, message []byte) ([]byte, error) {
+	tx, err := TxIDFromString(txID)
+	if err != nil {
+		return nil, err
+	}
+
+	secret, ok, err := DualKeyGetSecret(key, *tx)
+	if !ok || err != nil {
+		return nil, err
+	}
+
+	return MessageDecrypt(secret, message)
+}
+
+// ParsePublicKeyData will parse a interface and returns the key representation
+func (k *KeyEd25519) ParsePublicKeyData(buf []byte) (interface{}, error) {
+	return x509.ParsePKIXPublicKey(buf)
+}
+
+// ParsePublicKeyInterface will parse a interface and returns the key representation
+func (k *KeyEd25519) ParsePublicKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case ed25519.PublicKey:
+		return x509.MarshalPKIXPublicKey(key)
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// KeyExchange allows for a key exchange (if possible in the keytype)
+func (k *KeyEd25519) KeyExchange(privK PrivKey, pubK PubKey) ([]byte, error) {
+	x25519priv := EdPrivToX25519(privK.K.(ed25519.PrivateKey))
+	x25519pub := EdPubToX25519(pubK.K.(ed25519.PublicKey))
+
+	return curve25519.X25519(x25519priv, x25519pub)
+}
+
+// EdPrivToX25519 converts a ed25519 PrivateKey to a X25519 Private Key
+func EdPrivToX25519(privateKey ed25519.PrivateKey) []byte {
+	h := sha512.New()
+	_, _ = h.Write(privateKey[:32])
+	digest := h.Sum(nil)
+	h.Reset()
+
+	/* From https://cr.yp.to/ecdh.html (I don't think this is really needed in this case)
+	 * more info here: https://www.reddit.com/r/crypto/comments/66b3dp/how_do_is_a_curve25519_key_pair_generated/
+	 */
+	digest[0] &= 248
+	digest[31] &= 127
+	digest[31] |= 64
+
+	return digest[:32]
+}
+
+var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
+
+// EdPubToX25519 converts a ed25519 Public Key to a X25519 Public Key
+func EdPubToX25519(pk ed25519.PublicKey) []byte {
+	// ed25519.PublicKey is a little endian representation of the y-coordinate,
+	// with the most significant bit set based on the sign of the x-coordinate.
+	bigEndianY := make([]byte, ed25519.PublicKeySize)
+	for i, b := range pk {
+		bigEndianY[ed25519.PublicKeySize-i-1] = b
+	}
+	bigEndianY[0] &= 127
+
+	/* The Montgomery u-coordinate is derived through the bilinear map
+	 *
+	 *     u = (1 + y) / (1 - y)
+	 *
+	 * See https://blog.filippo.io/using-ed25519-keys-for-encryption.
+	 */
+	y := new(big.Int).SetBytes(bigEndianY)
+	denom := big.NewInt(1)
+	denom.ModInverse(denom.Sub(denom, y), curve25519P)
+	u := y.Mul(y.Add(y, big.NewInt(1)), denom)
+	u.Mod(u, curve25519P)
+
+	out := make([]byte, curve25519.PointSize)
+	uBytes := u.Bytes()
+	for i, b := range uBytes {
+		out[len(uBytes)-i-1] = b
+	}
+
+	return out
+}
+
+// DualKeyExchange allows for a ECIES key exchange
+func (k *KeyEd25519) DualKeyExchange(pub PubKey) ([]byte, *TransactionID, error) {
+	rs, err := generateRandomScalar()
+	if err != nil {
+		return nil, nil, errors.New("error while getting a random scalar")
+	}
+
+	r := ed25519.NewKeyFromSeed(rs)
+	R := r.Public()
+
+	// Step 1: D = rA
+	D, err := curve25519.X25519(
+		EdPrivToX25519(r.Seed()),
+		EdPubToX25519(pub.K.(ed25519.PublicKey)),
+	)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	f := hs(D)      // Step 2: f = Hs(D)
+	P := f.Public() // Step 3-5: convert F into private Key (F=fG)
+
+	return D, &TransactionID{
+		P: P.(ed25519.PublicKey)[:32],
+		R: R.(ed25519.PublicKey)[:32],
+	}, nil
+}
diff --git a/pkg/bmcrypto/key_rsa.go b/pkg/bmcrypto/key_rsa.go
new file mode 100644
index 0000000..f8fa104
--- /dev/null
+++ b/pkg/bmcrypto/key_rsa.go
@@ -0,0 +1,224 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package bmcrypto
+
+import (
+	"crypto"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/x509"
+	"errors"
+	"fmt"
+	"io"
+
+	deterministicRsaKeygen "github.com/cloudflare/gokey/rsa"
+	"github.com/vtolstov/jwt-go"
+	"golang.org/x/crypto/hkdf"
+)
+
+// Taken from https://github.com/cloudflare/gokey/blob/6bb7290160583cf1fd7cdcb5726093a00dd23c25/csprng.go#L18
+type devZero struct{}
+
+func (dz devZero) Read(p []byte) (n int, err error) {
+	for i := range p {
+		p[i] = 0
+	}
+	return len(p), nil
+}
+
+// KeyRsa is the RSA keytype
+type KeyRsa struct {
+	Type    string
+	BitSize int
+}
+
+// NewRsaKey will return a keytype based on the number of bits
+func NewRsaKey(bits int) KeyType {
+	k := &KeyRsa{}
+
+	k.BitSize = bits
+	k.Type = fmt.Sprintf("rsa%d", bits)
+
+	if bits == 2048 {
+		// rsa2048 is actually plain "rsa"
+		k.Type = "rsa"
+	}
+
+	return k
+}
+
+// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
+func (k *KeyRsa) CanEncrypt() bool {
+	return true
+}
+
+// CanKeyExchange returns true if the key(type) is able to be used for key exchange
+func (k *KeyRsa) CanKeyExchange() bool {
+	return false
+}
+
+// CanDualKeyExchange returns true if the key(type) is able to be used for a dual key exchange
+func (k *KeyRsa) CanDualKeyExchange() bool {
+	return false
+}
+
+// String returns a string representation of the key type ("rsa", "ecdsa", "ed25519" etc)
+func (k *KeyRsa) String() string {
+	return k.Type
+}
+
+// ParsePrivateKeyData will parse a string representation of a key and returns the given key
+func (k *KeyRsa) ParsePrivateKeyData(buf []byte) (interface{}, error) {
+	pk, err := x509.ParsePKCS1PrivateKey(buf)
+	if err == nil {
+		return pk, nil
+	}
+
+	return x509.ParsePKCS8PrivateKey(buf)
+}
+
+// ParsePrivateKeyInterface will parse a interface and returns the key representation
+func (k *KeyRsa) ParsePrivateKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case *rsa.PrivateKey:
+		if key.Size()*8 == k.BitSize {
+			return x509.MarshalPKCS8PrivateKey(key)
+		}
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// GenerateKeyPair will generate a new keypair for this keytype. io.Reader can be deterministic if needed
+func (k *KeyRsa) GenerateKeyPair(r io.Reader) (*PrivKey, *PubKey, error) {
+	// @TODO: we should check this:
+	// We have a reader that outputs 24bytes (192 bits), and stretches this to 256 bits.
+	// Then, create a new cipher stream, but how long is this stream?? Is it enough to create
+	// deterministic RSA keys?
+
+	// Read 192 bits
+	randBuf := make([]byte, 24)
+	_, err := io.ReadFull(r, randBuf)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Stretch 192 bits to 256 bits
+	rd := hkdf.New(sha256.New, randBuf, []byte{}, []byte{})
+	expBuf := make([]byte, 32)
+	_, err = io.ReadFull(rd, expBuf)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Generate keypair
+	// Taken from https://github.com/cloudflare/gokey/blob/6bb7290160583cf1fd7cdcb5726093a00dd23c25/csprng.go#L56
+	block, _ := aes.NewCipher(expBuf[:32])
+	stream := cipher.NewCTR(block, make([]byte, 16))
+
+	randReader := cipher.StreamReader{S: stream, R: devZero{}}
+	privRSAKey, err := deterministicRsaKeygen.GenerateKey(randReader, k.BitSize)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	privKey, err := PrivateKeyFromInterface(k, privRSAKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	pubKey, err := PublicKeyFromInterface(k, privRSAKey.Public())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return privKey, pubKey, nil
+}
+
+// JWTSignMethod will return the signing method used for this keytype
+func (k *KeyRsa) JWTSignMethod() jwt.SigningMethod {
+	return jwt.SigningMethodRS256
+}
+
+// JWTHasValidSignMethod will return true when this keytype has been used for signing the token
+func (k *KeyRsa) JWTHasValidSignMethod(token *jwt.Token) bool {
+	_, ok := token.Method.(*jwt.SigningMethodRSA)
+	return ok
+}
+
+// Verify will verify the signature with the public key
+func (k *KeyRsa) Verify(key PubKey, message []byte, sig []byte) (bool, error) {
+	h := crypto.SHA256.New()
+	h.Write(message)
+	hash := h.Sum(nil)
+
+	err := rsa.VerifyPKCS1v15(key.K.(*rsa.PublicKey), crypto.SHA256, hash[:], sig)
+	return err == nil, err
+}
+
+// Sign will sign the given bytes with the private key
+func (k *KeyRsa) Sign(_ io.Reader, key PrivKey, message []byte) ([]byte, error) {
+	h := crypto.SHA256.New()
+	h.Write(message)
+	hash := h.Sum(nil)
+
+	return rsa.SignPKCS1v15(randReader, key.K.(*rsa.PrivateKey), crypto.SHA256, hash[:])
+}
+
+// Encrypt will encrypt the given bytes with the public key. Will return the ciphertext, a transaction ID (if needed), the crypto used and an error
+func (k *KeyRsa) Encrypt(key PubKey, message []byte) ([]byte, string, string, error) {
+	data, err := rsa.EncryptPKCS1v15(rand.Reader, key.K.(*rsa.PublicKey), message)
+
+	return data, "", "rsa", err
+}
+
+// Decrypt will decrypt the given bytes with the private key
+func (k *KeyRsa) Decrypt(key PrivKey, _ string, message []byte) ([]byte, error) {
+	return rsa.DecryptPKCS1v15(rand.Reader, key.K.(*rsa.PrivateKey), message)
+}
+
+// ParsePublicKeyInterface will parse a interface and returns the key representation
+func (k *KeyRsa) ParsePublicKeyInterface(key interface{}) ([]byte, error) {
+	switch key := key.(type) {
+	case *rsa.PublicKey:
+		if key.Size()*8 == k.BitSize {
+			return x509.MarshalPKIXPublicKey(key)
+		}
+	}
+
+	return nil, errors.New("incorrect key")
+}
+
+// ParsePublicKeyData will parse a interface and returns the key representation
+func (k *KeyRsa) ParsePublicKeyData(buf []byte) (interface{}, error) {
+	return x509.ParsePKIXPublicKey(buf)
+}
+
+// KeyExchange allows for a key exchange (if possible in the keytype)
+func (k *KeyRsa) KeyExchange(_ PrivKey, _ PubKey) ([]byte, error) {
+	return nil, errors.New("cannot exchange with RSA")
+}
+
+// DualKeyExchange allows for a ECIES key exchange
+func (k *KeyRsa) DualKeyExchange(_ PubKey) ([]byte, *TransactionID, error) {
+	return nil, nil, errors.New("cannot dual exchange with RSA")
+}
diff --git a/pkg/bmcrypto/keyexchange.go b/pkg/bmcrypto/keyexchange.go
deleted file mode 100644
index dde11f6..0000000
--- a/pkg/bmcrypto/keyexchange.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2020 BitMaelum Authors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of
-// this software and associated documentation files (the "Software"), to deal in
-// the Software without restriction, including without limitation the rights to
-// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-// the Software, and to permit persons to whom the Software is furnished to do so,
-// subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-package bmcrypto
-
-import (
-	"crypto/ecdsa"
-	"crypto/ed25519"
-	"crypto/sha512"
-	"errors"
-	"math/big"
-
-	"golang.org/x/crypto/curve25519"
-)
-
-// KeyExchange exchange a message given the Private and other's Public Key
-func KeyExchange(privK PrivKey, pubK PubKey) ([]byte, error) {
-	switch privK.Type {
-	case KeyTypeECDSA:
-		k, _ := pubK.K.(*ecdsa.PublicKey).Curve.ScalarMult(pubK.K.(*ecdsa.PublicKey).X, pubK.K.(*ecdsa.PublicKey).Y, privK.K.(*ecdsa.PrivateKey).D.Bytes())
-
-		return k.Bytes(), nil
-
-	case KeyTypeED25519:
-		x25519priv := EdPrivToX25519(privK.K.(ed25519.PrivateKey))
-		x25519pub := EdPubToX25519(pubK.K.(ed25519.PublicKey))
-		return curve25519.X25519(x25519priv, x25519pub)
-	}
-
-	return nil, errors.New("unknown key type for key exchange")
-}
-
-// EdPrivToX25519 converts a ed25519 PrivateKey to a X25519 Private Key
-func EdPrivToX25519(privateKey ed25519.PrivateKey) []byte {
-	h := sha512.New()
-	_, _ = h.Write(privateKey[:32])
-	digest := h.Sum(nil)
-	h.Reset()
-
-	/* From https://cr.yp.to/ecdh.html (I don't think this is really needed in this case)
-	 * more info here: https://www.reddit.com/r/crypto/comments/66b3dp/how_do_is_a_curve25519_key_pair_generated/
-	 */
-	digest[0] &= 248
-	digest[31] &= 127
-	digest[31] |= 64
-
-	return digest[:32]
-}
-
-var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
-
-// EdPubToX25519 converts a ed25519 Public Key to a X25519 Public Key
-func EdPubToX25519(pk ed25519.PublicKey) []byte {
-	// ed25519.PublicKey is a little endian representation of the y-coordinate,
-	// with the most significant bit set based on the sign of the x-coordinate.
-	bigEndianY := make([]byte, ed25519.PublicKeySize)
-	for i, b := range pk {
-		bigEndianY[ed25519.PublicKeySize-i-1] = b
-	}
-	bigEndianY[0] &= 127
-
-	/* The Montgomery u-coordinate is derived through the bilinear map
-	 *
-	 *     u = (1 + y) / (1 - y)
-	 *
-	 * See https://blog.filippo.io/using-ed25519-keys-for-encryption.
-	 */
-	y := new(big.Int).SetBytes(bigEndianY)
-	denom := big.NewInt(1)
-	denom.ModInverse(denom.Sub(denom, y), curve25519P)
-	u := y.Mul(y.Add(y, big.NewInt(1)), denom)
-	u.Mod(u, curve25519P)
-
-	out := make([]byte, curve25519.PointSize)
-	uBytes := u.Bytes()
-	for i, b := range uBytes {
-		out[len(uBytes)-i-1] = b
-	}
-
-	return out
-}
diff --git a/pkg/bmcrypto/keys.go b/pkg/bmcrypto/keys.go
deleted file mode 100644
index f64c1c1..0000000
--- a/pkg/bmcrypto/keys.go
+++ /dev/null
@@ -1,315 +0,0 @@
-// Copyright (c) 2020 BitMaelum Authors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of
-// this software and associated documentation files (the "Software"), to deal in
-// the Software without restriction, including without limitation the rights to
-// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-// the Software, and to permit persons to whom the Software is furnished to do so,
-// subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-package bmcrypto
-
-import (
-	"crypto/ecdsa"
-	"crypto/ed25519"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/x509"
-	"encoding/base64"
-	"encoding/hex"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"strings"
-)
-
-// KeyType defines a type for a given key in a certain configuration (like: rsa 2048 or rsa 4096 etc)
-type KeyType string
-
-// String returns the textual representation of the keytype
-func (kt KeyType) String() string {
-	return string(kt)
-}
-
-// Keytype constants
-const (
-	KeyTypeRSA     KeyType = "rsa"
-	KeyTypeECDSA   KeyType = "ecdsa"
-	KeyTypeED25519 KeyType = "ed25519"
-	KeyTypeRSAV1   KeyType = "rsav1"
-)
-
-// PrivKey is a structure containing a private key in multiple formats
-type PrivKey struct {
-	Type KeyType     // Type of the private key
-	S    string      // String representation <type> <PEM key>
-	B    []byte      // Byte representation of string
-	K    interface{} // Key interface{}
-}
-
-// PubKey is a structure containing a public key in multiple formats
-type PubKey struct {
-	Type        KeyType     // Type of the the private key
-	S           string      // String representation <type> <PEM key> <description>
-	B           []byte      // Byte representation of string
-	K           interface{} // Key interface{}
-	Description string      // Optional description
-}
-
-// MarshalJSON marshals a key into bytes
-func (pk *PubKey) MarshalJSON() ([]byte, error) {
-	return json.Marshal(pk.String())
-}
-
-// String converts a key to "<type> <key> <description>"
-func (pk *PubKey) String() string {
-	return strings.TrimSpace(pk.Type.String() + " " + pk.S + " " + pk.Description)
-}
-
-// Fingerprint return the fingerprint of the key
-func (pk *PubKey) Fingerprint() string {
-	binKey, _ := base64.StdEncoding.DecodeString(pk.S)
-	b := sha256.Sum256(binKey)
-	return hex.EncodeToString(b[:])
-}
-
-// UnmarshalJSON unmarshals bytes into a key
-func (pk *PubKey) UnmarshalJSON(b []byte) error {
-	var s string
-
-	err := json.Unmarshal(b, &s)
-	if err != nil {
-		return err
-	}
-
-	pkCopy, err := NewPubKey(s)
-	if err != nil {
-		return err
-	}
-
-	*pk = *pkCopy
-	return err
-}
-
-// MarshalJSON marshals a key into bytes
-func (pk *PrivKey) MarshalJSON() ([]byte, error) {
-	return json.Marshal(pk.String())
-}
-
-// Strings returns the key in a textual representation
-func (pk *PrivKey) String() string {
-	return fmt.Sprintf("%s %s", pk.Type, pk.S)
-}
-
-// UnmarshalJSON unmarshals bytes into a key
-func (pk *PrivKey) UnmarshalJSON(b []byte) error {
-	var s string
-
-	err := json.Unmarshal(b, &s)
-	if err != nil {
-		return err
-	}
-
-	pkCopy, err := NewPrivKey(s)
-	if err != nil {
-		return err
-	}
-
-	*pk = *pkCopy
-	return err
-}
-
-// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
-func (pk *PubKey) CanEncrypt() bool {
-	return pk.Type == KeyTypeRSA || pk.Type == KeyTypeRSAV1
-}
-
-// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
-func (pk *PrivKey) CanEncrypt() bool {
-	return pk.Type == KeyTypeRSA || pk.Type == KeyTypeRSAV1
-}
-
-// CanKeyExchange returns true if the key(type) is able to be used for key exchange
-func (pk *PrivKey) CanKeyExchange() bool {
-	return pk.Type == KeyTypeED25519 || pk.Type == KeyTypeECDSA
-}
-
-// CanKeyExchange returns true if the key(type) is able to be used for key exchange
-func (pk *PubKey) CanKeyExchange() bool {
-	return pk.Type == KeyTypeED25519 || pk.Type == KeyTypeECDSA
-}
-
-// NewPubKey creates a new public key based on the string data "<type> <key> <description>"
-func NewPubKey(data string) (*PubKey, error) {
-	pk := &PubKey{}
-
-	if !strings.Contains(data, " ") {
-		return nil, errors.New("incorrect key format")
-	}
-
-	// <type> <data> <description>
-	parts := strings.SplitN(data, " ", 3)
-
-	// Check and set type
-	switch KeyType(parts[0]) {
-	case KeyTypeRSA:
-		pk.Type = KeyTypeRSA
-	case KeyTypeRSAV1:
-		pk.Type = KeyTypeRSAV1
-	case KeyTypeECDSA:
-		pk.Type = KeyTypeECDSA
-	case KeyTypeED25519:
-		pk.Type = KeyTypeED25519
-	default:
-		return nil, errors.New("incorrect key type")
-	}
-
-	// Set values
-	pk.S = strings.TrimSpace(parts[1])
-	pk.B = []byte(pk.S)
-	if len(parts) == 3 {
-		pk.Description = parts[2]
-	}
-
-	// Decode base64 before we parse to key
-	buf := make([]byte, base64.StdEncoding.DecodedLen(len(pk.B)))
-	n, err := base64.StdEncoding.Decode(buf, pk.B)
-	if err != nil {
-		return nil, errors.New("incorrect key data")
-	}
-
-	// Decode (base64-decoded) key
-	pk.K, err = x509.ParsePKIXPublicKey(buf[:n])
-	if err != nil {
-		return nil, errors.New("incorrect key data")
-	}
-
-	return pk, nil
-}
-
-// NewPrivKey creates a new private key based on the string data "<type> <key>"
-func NewPrivKey(data string) (*PrivKey, error) {
-	pk := &PrivKey{}
-
-	if !strings.Contains(data, " ") {
-		return nil, errors.New("incorrect key format")
-	}
-
-	// <type> <data> <description>
-	parts := strings.SplitN(data, " ", 2)
-
-	// Check and set type
-	switch KeyType(parts[0]) {
-	case KeyTypeRSA:
-		pk.Type = KeyTypeRSA
-	case KeyTypeRSAV1:
-		pk.Type = KeyTypeRSAV1
-	case KeyTypeECDSA:
-		pk.Type = KeyTypeECDSA
-	case KeyTypeED25519:
-		pk.Type = KeyTypeED25519
-	default:
-		return nil, errors.New("incorrect key type")
-	}
-
-	// Set values
-	pk.S = strings.TrimSpace(parts[1])
-	pk.B = []byte(pk.S)
-
-	// Decode base64 before we parse to key
-	buf := make([]byte, base64.StdEncoding.DecodedLen(len(pk.B)))
-	n, err := base64.StdEncoding.Decode(buf, pk.B)
-	if err != nil {
-		return nil, errors.New("incorrect key data")
-	}
-
-	// Parse key, see which parses actually works..
-	if pk.Type == KeyTypeECDSA {
-		pk.K, err = x509.ParseECPrivateKey(buf[:n])
-	} else {
-		pk.K, err = x509.ParsePKCS1PrivateKey(buf[:n])
-	}
-	if err != nil {
-		pk.K, err = x509.ParsePKCS8PrivateKey(buf[:n])
-	}
-
-	if err != nil {
-		return nil, errors.New("incorrect key data")
-	}
-
-	return pk, nil
-}
-
-// NewPrivKeyFromInterface creates a new key based on an interface{} (like rsa.PrivateKey)
-func NewPrivKeyFromInterface(key interface{}) (*PrivKey, error) {
-	var kt KeyType
-	switch v := key.(type) {
-	case *rsa.PrivateKey:
-		switch v.Size() {
-		case RsaBits[0]:
-			kt = KeyTypeRSA
-		case RsaBits[1]:
-			kt = KeyTypeRSAV1
-		}
-	case *ecdsa.PrivateKey:
-		kt = KeyTypeECDSA
-	case ed25519.PrivateKey:
-		kt = KeyTypeED25519
-	default:
-		return nil, errors.New("unknown key type")
-	}
-
-	pk := &PrivKey{
-		Type: kt,
-		K:    key,
-	}
-
-	buf, err := x509.MarshalPKCS8PrivateKey(key)
-	if err != nil {
-		return nil, errors.New("incorrect key")
-	}
-	pk.S = base64.StdEncoding.EncodeToString(buf)
-	pk.B = []byte(pk.S)
-
-	return pk, nil
-}
-
-// NewPubKeyFromInterface creates a new key based on an interface{} (like rsa.PublicKey)
-func NewPubKeyFromInterface(key interface{}) (*PubKey, error) {
-	var kt KeyType
-
-	switch key.(type) {
-	case *rsa.PublicKey:
-		kt = KeyTypeRSA
-	case *ecdsa.PublicKey:
-		kt = KeyTypeECDSA
-	case ed25519.PublicKey:
-		kt = KeyTypeED25519
-	default:
-		return nil, errors.New("unknown key type")
-	}
-
-	pk := &PubKey{
-		Type: kt,
-		K:    key,
-	}
-
-	buf, err := x509.MarshalPKIXPublicKey(key)
-	if err != nil {
-		return nil, errors.New("incorrect key")
-	}
-	pk.S = base64.StdEncoding.EncodeToString(buf)
-	pk.B = []byte(pk.S)
-
-	return pk, nil
-}
diff --git a/pkg/bmcrypto/keytype.go b/pkg/bmcrypto/keytype.go
new file mode 100644
index 0000000..2b0f1b8
--- /dev/null
+++ b/pkg/bmcrypto/keytype.go
@@ -0,0 +1,296 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package bmcrypto
+
+import (
+	"crypto/elliptic"
+	"crypto/sha256"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/vtolstov/jwt-go"
+)
+
+// KeyType is an interface that each key type should implement.
+type KeyType interface {
+	// CanEncrypt returns true if the key(type) is able to be used for encryption/decryption
+	CanEncrypt() bool
+	// CanKeyExchange returns true if the key(type) is able to be used for key exchange
+	CanKeyExchange() bool
+	// CanDualKeyExchange returns true if the key(type) is able to be used for a dual key exchange
+	CanDualKeyExchange() bool
+
+	// String returns a string representation of the key type ("rsa", "ecdsa", "ed25519" etc)
+	String() string
+
+	// ParsePrivateKeyData will parse a string representation of a key and returns the given key
+	ParsePrivateKeyData([]byte) (interface{}, error)
+	// ParsePrivateKeyInterface will parse a interface and returns the key representation
+	ParsePrivateKeyInterface(interface{}) ([]byte, error)
+	// ParsePublicKeyData will parse a interface and returns the key representation
+	ParsePublicKeyData([]byte) (interface{}, error)
+	// ParsePublicKeyInterface will parse a interface and returns the key representation
+	ParsePublicKeyInterface(interface{}) ([]byte, error)
+
+	// GenerateKeyPair will generate a new keypair for this keytype. io.Reader can be deterministic if needed
+	GenerateKeyPair(io.Reader) (*PrivKey, *PubKey, error)
+
+	// JWTSignMethod will return the signing method used for this keytype
+	JWTSignMethod() jwt.SigningMethod
+	// JWTHasValidSignMethod will return true when this keytype has been used for signing the token
+	JWTHasValidSignMethod(*jwt.Token) bool
+
+	// Encrypt will encrypt the given bytes with the public key. Will return the ciphertext, a transaction ID (if needed), the crypto used and an error
+	Encrypt(PubKey, []byte) ([]byte, string, string, error)
+	// Decrypt will decrypt the given bytes with the private key
+	Decrypt(PrivKey, string, []byte) ([]byte, error)
+
+	// Sign will sign the given bytes with the private key
+	Sign(io.Reader, PrivKey, []byte) ([]byte, error)
+	// Verify will verify the signature with the public key
+	Verify(PubKey, []byte, []byte) (bool, error)
+
+	// KeyExchange allows for a key exchange (if possible in the keytype)
+	KeyExchange(privK PrivKey, pubK PubKey) ([]byte, error)
+	// DualKeyExchange allows for a ECIES key exchange
+	DualKeyExchange(_ PubKey) ([]byte, *TransactionID, error)
+}
+
+var (
+	errIncorrectKeyFormat = errors.New("incorrect key format")
+	errUnsupportedKeyType = errors.New("unsupported key type")
+)
+
+// KeyTypes is a list of all keytypes available
+var KeyTypes = []KeyType{
+	NewRsaKey(2048),              // RSA 2048 bits
+	NewRsaKey(4096),              // RSA 4096 bits
+	NewEd25519Key(),              // ED25519
+	NewEcdsaKey(elliptic.P384()), // Ecdsa (P384)
+}
+
+// PrivKey is a structure containing a private key in multiple formats
+type PrivKey struct {
+	Type KeyType     // structure of the key
+	S    string      // String representation <type> <PEM key>
+	B    []byte      // Byte representation of string
+	K    interface{} // Key interface{}
+}
+
+// Strings returns the key in a textual representation
+func (pk *PrivKey) String() string {
+	return fmt.Sprintf("%s %s", pk.Type.String(), pk.S)
+}
+
+// MarshalJSON marshals a key into bytes
+func (pk *PrivKey) MarshalJSON() ([]byte, error) {
+	return json.Marshal(pk.String())
+}
+
+// UnmarshalJSON unmarshals bytes into a key
+func (pk *PrivKey) UnmarshalJSON(b []byte) error {
+	var s string
+
+	err := json.Unmarshal(b, &s)
+	if err != nil {
+		return err
+	}
+
+	pkCopy, err := PrivateKeyFromString(s)
+	if err != nil {
+		return err
+	}
+
+	*pk = *pkCopy
+	return err
+}
+
+// PrivateKeyFromString creates a new private key based on the string data "<type> <key>"
+func PrivateKeyFromString(data string) (*PrivKey, error) {
+	if !strings.Contains(data, " ") {
+		return nil, errIncorrectKeyFormat
+	}
+
+	// <type> <data>
+	parts := strings.SplitN(data, " ", 2)
+	if len(parts) != 2 {
+		return nil, errIncorrectKeyFormat
+	}
+
+	pk := &PrivKey{}
+
+	// Find the correct key type
+	var err error
+	pk.Type, err = FindKeyType(parts[0])
+	if err != nil {
+		return nil, errUnsupportedKeyType
+	}
+
+	// Set values
+	pk.S = strings.TrimSpace(parts[1])
+	pk.B = []byte(pk.S)
+
+	// Decode base64 before we parse to key
+	buf := make([]byte, base64.StdEncoding.DecodedLen(len(pk.B)))
+	n, err := base64.StdEncoding.Decode(buf, pk.B)
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	// Parse key
+	pk.K, err = pk.Type.ParsePrivateKeyData(buf[:n])
+
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	return pk, nil
+}
+
+// PrivateKeyFromInterface creates a new key based on an interface{} (like rsa.PrivateKey)
+func PrivateKeyFromInterface(kt KeyType, key interface{}) (*PrivKey, error) {
+	buf, err := kt.ParsePrivateKeyInterface(key)
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	s := base64.StdEncoding.EncodeToString(buf)
+
+	return &PrivKey{
+		Type: kt,
+		S:    s,
+		B:    []byte(s),
+		K:    key,
+	}, nil
+}
+
+// PubKey is a structure containing a public key in multiple formats
+type PubKey struct {
+	Type        KeyType     // Type of the the private key
+	S           string      // String representation <type> <PEM key> <description>
+	B           []byte      // Byte representation of string
+	K           interface{} // Key interface{}
+	Description string      // Optional description
+}
+
+// String converts a key to "<type> <key> <description>"
+func (pk *PubKey) String() string {
+	s := fmt.Sprintf("%s %s %s", pk.Type.String(), pk.S, pk.Description)
+
+	return strings.TrimSpace(s)
+}
+
+// Fingerprint return the fingerprint of the key
+func (pk *PubKey) Fingerprint() string {
+	binKey, _ := base64.StdEncoding.DecodeString(pk.S)
+	b := sha256.Sum256(binKey)
+	return hex.EncodeToString(b[:])
+}
+
+// MarshalJSON marshals a key into bytes
+func (pk *PubKey) MarshalJSON() ([]byte, error) {
+	return json.Marshal(pk.String())
+}
+
+// UnmarshalJSON unmarshals bytes into a key
+func (pk *PubKey) UnmarshalJSON(b []byte) error {
+	var s string
+
+	err := json.Unmarshal(b, &s)
+	if err != nil {
+		return err
+	}
+
+	pkCopy, err := PublicKeyFromString(s)
+	if err != nil {
+		return err
+	}
+
+	*pk = *pkCopy
+	return err
+}
+
+// PublicKeyFromString creates a new public key based on the string data "<type> <key> <description>"
+func PublicKeyFromString(data string) (*PubKey, error) {
+	if !strings.Contains(data, " ") {
+		return nil, errIncorrectKeyFormat
+	}
+
+	// <type> <data>
+	parts := strings.SplitN(data, " ", 3)
+	if len(parts) == 2 {
+		parts = append(parts, "")
+	}
+	if len(parts) != 3 {
+		return nil, errIncorrectKeyFormat
+	}
+
+	pk := &PubKey{}
+
+	// Find the correct key type
+	var err error
+	pk.Type, err = FindKeyType(parts[0])
+	if err != nil {
+		return nil, errUnsupportedKeyType
+	}
+
+	// Set values
+	pk.S = strings.TrimSpace(parts[1])
+	pk.B = []byte(pk.S)
+	pk.Description = parts[2]
+
+	// Decode base64 before we parse to key
+	buf := make([]byte, base64.StdEncoding.DecodedLen(len(pk.B)))
+	n, err := base64.StdEncoding.Decode(buf, pk.B)
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	// Parse key
+	pk.K, err = pk.Type.ParsePublicKeyData(buf[:n])
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	return pk, nil
+}
+
+// PublicKeyFromInterface creates a new key based on an interface{} (like rsa.PublicKey)
+func PublicKeyFromInterface(kt KeyType, key interface{}) (*PubKey, error) {
+	buf, err := kt.ParsePublicKeyInterface(key)
+	if err != nil {
+		return nil, errIncorrectKeyFormat
+	}
+
+	s := base64.StdEncoding.EncodeToString(buf)
+
+	return &PubKey{
+		Type:        kt,
+		S:           s,
+		B:           []byte(s),
+		K:           key,
+		Description: "",
+	}, nil
+}
diff --git a/pkg/hash/hash.go b/pkg/hash/hash.go
index 0860aac..d5a4542 100644
--- a/pkg/hash/hash.go
+++ b/pkg/hash/hash.go
@@ -43,9 +43,6 @@ type Hash string
 
 // New generates a regular hash. Assumes you know what you are hashing
 func New(s string) Hash {
-	/*if len(s) == 64 {
-		panic("seems like we are hashing a hash...")
-	}*/
 	sum := sha256.Sum256([]byte(s))
 
 	return Hash(hex.EncodeToString(sum[:]))
diff --git a/tools/jwt-validate/main.go b/tools/jwt-validate/main.go
index 835a46a..e0e1a2d 100644
--- a/tools/jwt-validate/main.go
+++ b/tools/jwt-validate/main.go
@@ -39,7 +39,7 @@ func main() {
 		panic(err)
 	}
 
-	key, err := bmcrypto.NewPubKey(os.Args[2])
+	key, err := bmcrypto.PublicKeyFromString(os.Args[2])
 	if err != nil {
 		panic(err)
 	}
diff --git a/tools/readmail/main.go b/tools/readmail/main.go
index 0aa64e9..3d83ab1 100644
--- a/tools/readmail/main.go
+++ b/tools/readmail/main.go
@@ -30,6 +30,7 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/internal/message"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 )
 
 type options struct {
@@ -86,13 +87,13 @@ func main() {
 		panic(err)
 	}
 
-	decryptedKey, err := message.Decrypt(info.PrivKey, header.Catalog.TransactionID, header.Catalog.EncryptedKey)
+	decryptedKey, err := bmcrypto.Decrypt(info.PrivKey, header.Catalog.TransactionID, header.Catalog.EncryptedKey)
 	if err != nil {
 		panic(err)
 	}
 
 	catalog := &message.Catalog{}
-	err = internal.CatalogDecrypt(decryptedKey, data, catalog)
+	err = bmcrypto.CatalogDecrypt(decryptedKey, data, catalog)
 	if err != nil {
 		panic(err)
 	}
@@ -107,7 +108,7 @@ func main() {
 			panic(err)
 		}
 
-		r, err := internal.GetAesDecryptorReader(block.IV, block.Key, f)
+		r, err := bmcrypto.GetAesDecryptorReader(block.IV, block.Key, f)
 		if err != nil {
 			_ = f.Close()
 			panic(err)


Test patch:
diff --git a/internal/api/jwt_test.go b/internal/api/jwt_test.go
index 6eb4a15..031eb92 100644
--- a/internal/api/jwt_test.go
+++ b/internal/api/jwt_test.go
@@ -42,7 +42,7 @@ func TestGenerateJWTToken(t *testing.T) {
 	}
 
 	data, _ := ioutil.ReadFile("../../testdata/privkey.rsa")
-	privKey, err := bmcrypto.NewPrivKey(string(data))
+	privKey, err := bmcrypto.PrivateKeyFromString(string(data))
 	assert.Nil(t, err)
 
 	haddr := hash.New("test!")
@@ -54,7 +54,7 @@ func TestGenerateJWTToken(t *testing.T) {
 
 func TestValidateJwtTokenExpiry(t *testing.T) {
 	data, _ := ioutil.ReadFile("../../testdata/pubkey.rsa")
-	pubKey, _ := bmcrypto.NewPubKey(string(data))
+	pubKey, _ := bmcrypto.PublicKeyFromString(string(data))
 	haddr := hash.New("test!")
 
 	// The current time block in our mock token is 12:34:30 - 12:36:00 (1577882040 - 1577882130)
@@ -101,7 +101,7 @@ func TestValidateJWTToken(t *testing.T) {
 	}
 
 	data, _ := ioutil.ReadFile("../../testdata/pubkey.rsa")
-	pubKey, _ := bmcrypto.NewPubKey(string(data))
+	pubKey, _ := bmcrypto.PublicKeyFromString(string(data))
 
 	haddr := hash.New("test!")
 
@@ -117,11 +117,14 @@ func TestValidateJWTToken(t *testing.T) {
 }
 
 func TestED25519(t *testing.T) {
-	priv, pub, err := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeED25519)
+	kt, err := bmcrypto.FindKeyType("ed25519")
+	assert.NoError(t, err)
+	priv, pub, err := bmcrypto.GenerateKeyPair(kt)
 	assert.NoError(t, err)
 
 	tokenStr, err := GenerateJWTToken(hash.New("test!"), *priv)
 	assert.NoError(t, err)
+	assert.NotEmpty(t, tokenStr)
 
 	token, err := ValidateJWTToken(tokenStr, hash.New("test!"), *pub)
 	assert.NoError(t, err)
@@ -130,7 +133,9 @@ func TestED25519(t *testing.T) {
 }
 
 func TestECDSA(t *testing.T) {
-	priv, pub, err := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeECDSA)
+	kt, err := bmcrypto.FindKeyType("ecdsa")
+	assert.NoError(t, err)
+	priv, pub, err := bmcrypto.GenerateKeyPair(kt)
 	assert.NoError(t, err)
 
 	tokenStr, err := GenerateJWTToken(hash.New("test!"), *priv)
@@ -140,5 +145,9 @@ func TestECDSA(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "ES384", token.Method.Alg())
 	assert.Equal(t, "1882b91b7f49d479cf1ec2f1ecee30d0e5392e963a2109015b7149bf712ad1b6", token.Claims.(*jwt.StandardClaims).Subject)
+}
 
+func init() {
+	var edDSASigningMethod bmcrypto.SigningMethodEdDSA
+	jwt.RegisterSigningMethod(edDSASigningMethod.Alg(), func() jwt.SigningMethod { return &edDSASigningMethod })
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 95d6f13..df4b6c0 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -150,7 +150,7 @@ func TestLoadClientConfig(t *testing.T) {
 }
 
 func TestGenerateRoutingFromMnemonic(t *testing.T) {
-	r, err := GenerateRoutingFromMnemonic("cluster puppy wash ceiling skate search great angry drift rose undo fragile boring fence stumble shuffle cable praise")
+	r, err := GenerateRoutingFromMnemonic("ed25519 cluster puppy wash ceiling skate search great angry drift rose undo fragile boring fence stumble shuffle cable praise")
 	assert.NoError(t, err)
 
 	assert.Equal(t, "f5f1dc4eff7237ac0e061a9e8982b7b913fc479138189cc8d6ba5131dee1bde9", r.RoutingID)
diff --git a/internal/message/compose_test.go b/internal/message/compose_test.go
new file mode 100644
index 0000000..c5c6ccc
--- /dev/null
+++ b/internal/message/compose_test.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package message
+
+import (
+	"testing"
+
+	testing2 "github.com/bitmaelum/bitmaelum-suite/internal/testing"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCompose(t *testing.T) {
+	privKey, _, _ := testing2.ReadTestKey("../../testdata/key-ed25519-1.json")
+	_, pubKey, _ := testing2.ReadTestKey("../../testdata/key-ed25519-2.json")
+
+	sender, _ := address.NewAddress("from!")
+	recipient, _ := address.NewAddress("too!")
+
+	addressing := NewAddressing(
+		*sender,
+		privKey,
+		"12345678",
+		*recipient,
+		pubKey,
+	)
+
+	env, err := Compose(addressing, "foobar", []string{"default,foobar"}, nil)
+	assert.NoError(t, err)
+	assert.Equal(t, "358ba60da66597e9945c9d26dd6703a53c550e962a09fd50f811e7650d7c8389", env.Header.From.Addr.String())
+	assert.Len(t, env.BlockReaders, 1)
+	assert.Len(t, env.AttachmentReaders, 0)
+}
diff --git a/internal/mnemonic_test.go b/internal/mnemonic_test.go
index 73a3adc..e350031 100644
--- a/internal/mnemonic_test.go
+++ b/internal/mnemonic_test.go
@@ -27,7 +27,9 @@ import (
 )
 
 func TestGenerateED25519KeypairFromMnemonic(t *testing.T) {
-	s, priv1, pub1, err := GenerateKeypairWithMnemonic(bmcrypto.KeyTypeED25519)
+	kt, err := bmcrypto.FindKeyType("ed25519")
+	assert.NoError(t, err)
+	s, priv1, pub1, err := GenerateKeypairWithMnemonic(kt)
 	assert.NoError(t, err)
 
 	priv2, pub2, err := GenerateKeypairFromMnemonic(s)
@@ -38,7 +40,9 @@ func TestGenerateED25519KeypairFromMnemonic(t *testing.T) {
 }
 
 func TestGenerateRSAKeypairFromMnemonic(t *testing.T) {
-	s, priv1, pub1, err := GenerateKeypairWithMnemonic(bmcrypto.KeyTypeRSA)
+	kt, err := bmcrypto.FindKeyType("rsa")
+	assert.NoError(t, err)
+	s, priv1, pub1, err := GenerateKeypairWithMnemonic(kt)
 	assert.NoError(t, err)
 
 	priv2, pub2, err := GenerateKeypairFromMnemonic(s)
diff --git a/internal/signature/invite_test.go b/internal/signature/invite_test.go
index e5bf914..d6a03d0 100644
--- a/internal/signature/invite_test.go
+++ b/internal/signature/invite_test.go
@@ -31,9 +31,9 @@ import (
 func TestSignature(t *testing.T) {
 	addr := hash.New("john@acme!")
 
-	privKey, err := bmcrypto.NewPrivKey("ed25519 MC4CAQAwBQYDK2VwBCIEILq+V/CUlMdbmoQC1odEgOEmtMBQu0UpIICxJbQM1vhd")
+	privKey, err := bmcrypto.PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEILq+V/CUlMdbmoQC1odEgOEmtMBQu0UpIICxJbQM1vhd")
 	assert.NoError(t, err)
-	pubKey, err := bmcrypto.NewPubKey("ed25519 MCowBQYDK2VwAyEARdZSwluYtMWTGI6Rvl0Bhu40RBDn6D88wyzFL1IR3DU=")
+	pubKey, err := bmcrypto.PublicKeyFromString("ed25519 MCowBQYDK2VwAyEARdZSwluYtMWTGI6Rvl0Bhu40RBDn6D88wyzFL1IR3DU=")
 	assert.NoError(t, err)
 
 	// Assume this is the current time during tests
@@ -70,7 +70,9 @@ func TestSignature(t *testing.T) {
 	assert.False(t, ok)
 
 	// Check token with different public key
-	_, pubKey2, _ := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeRSA)
+	kt, err := bmcrypto.FindKeyType("rsa")
+	assert.NoError(t, err)
+	_, pubKey2, _ := bmcrypto.GenerateKeyPair(kt)
 	it, err = NewInviteToken(addr, "12345678", time.Date(2010, 01, 04, 12, 34, 56, 0, time.UTC), *privKey)
 	assert.NoError(t, err)
 	ok = it.Verify("12345678", *pubKey2)
diff --git a/pkg/address/address_test.go b/pkg/address/address_test.go
index e68f493..bab2154 100644
--- a/pkg/address/address_test.go
+++ b/pkg/address/address_test.go
@@ -136,18 +136,13 @@ func TestJSON(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "\"john@foobar!\"", string(b))
 
-	c := Address{}
+	c := &Address{}
 	err = json.Unmarshal(b, &c)
 	assert.NoError(t, err)
 	assert.Equal(t, "john@foobar!", c.String())
 
-	b = []byte{12, 34}
-	c = Address{}
-	err = json.Unmarshal(b, &c)
-	assert.Error(t, err)
-
-	b = []byte("this$#@@@$-is-not-a-valid-address")
-	c = Address{}
+	b = []byte("\"{{{{{{\"")
+	c = &Address{}
 	err = json.Unmarshal(b, &c)
 	assert.Error(t, err)
 }
diff --git a/internal/aes_256_gcm_test.go b/pkg/bmcrypto/aes_256_gcm_test.go
similarity index 99%
rename from internal/aes_256_gcm_test.go
rename to pkg/bmcrypto/aes_256_gcm_test.go
index 2e597fd..9862285 100644
--- a/internal/aes_256_gcm_test.go
+++ b/pkg/bmcrypto/aes_256_gcm_test.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package internal
+package bmcrypto
 
 import (
 	"bytes"
diff --git a/pkg/bmcrypto/bmcrypto_test.go b/pkg/bmcrypto/bmcrypto_test.go
new file mode 100644
index 0000000..5fd8754
--- /dev/null
+++ b/pkg/bmcrypto/bmcrypto_test.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package bmcrypto
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFindKeyType(t *testing.T) {
+	kt, err := FindKeyType("rsa")
+	assert.NoError(t, err)
+	assert.Equal(t, "rsa", kt.String())
+
+	kt, err = FindKeyType("does-not-exist")
+	assert.Error(t, err)
+	assert.Nil(t, kt)
+}
+
+func TestGenerateKeyPair(t *testing.T) {
+	_, _, err := GenerateKeyPair(nil)
+	assert.Error(t, err)
+}
+
+func TestKeyExchangeError(t *testing.T) {
+	privKey, err := PrivateKeyFromString("rsa MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTi87QrrSXLiILJ8/o8t48rS4XwtizoYbIfjV2K16ps9EmrNJvaQKW4MX5k6yBEocQTlxWq5YlYBM8V2NC8+W5TKzGT+MQuANZPiUNuZM/b3EgYFAciVAiudhqgEdT5vgt6BzKkymux3D0SJX1blH8X9tMyQnJoIPiT1xnML5P4gSG6SG9sSHNgrHrZwLjUr0yon9YGoDrCnYfvEn90ko+MK6k1tdf2XdBuPSvbRnjc3l5/uKaN+0SHaTCJA2e96qHq+mdiXUHcBkL5HxnAuKE/9BoEOWH6di7L8ndJF8zrIVhmflhB0cqZSPTwdYZXGdK4VkV4pMfDahDmYv6XKLpAgMBAAECggEAR57Ryj0bzwNDa1tzPH7dVtWbAVhqXYaWR1LTbsqIJhRG/z0LkcSPp905qaGhiaFoMNEW2hEFqGm6mXdMl+JTKEUZSZrKWWKzX4d2rArkG1nzhu6UsNScWOVqq8P6YiGUbJZlCQCB4DaNu2bHvmw3PaaGbJyzv5ukiv4rXpRWGlzhb3Iw//FGmbDAbQPnYrGLNXv0WDFlhME1gIZvOSytg6QDeCN3xYIJcmTJDCRuPemRN/MqCMSQ3qhqYLlqB+8vH902ozGeX8eO5ZiBJqc6oEjlKKwU45ER52QsXVmjsNCxf7L3IgN5CXNeuh/LKmFrOFWxjXHfOXqF7gwM91t2cQKBgQDp5YjADjGm88UyUicTYu9Ta2o2gxfMiyPb73IhfPJ/ijqlUSCUN9Ji/6Vyrd5/OjqLENWKUsjrMMFFhqOmLem27yuRNAGUQeoO4fibf2oNSNMxvsEFfwlVJAq4ixPJDNyOne2vWEwb9u7w4YK+ssr3cOZL+mG2xk/5XcK36g5S3wKBgQDniZEcqkGzTiQFjgf+7qHtCwaDNiNzvQCPL0ZsQfiur4NQrL/osGIZ4jHV5eU/k/LuHcrZx5x+UAQqVISScFkkew2VvgfKaxdie8zhJSh8ic5RBGZg/B3gw5xHWIyJSxUp8PztuH2Raj0ECh+lzDtwYpPKGyQLDxwdeB1/Pq7LNwKBgG+juHb7D2YBuqD/J1mQgm0Nux+TyNs/mnkSvCYRzmlj4AQiSeuVDV1lamHnbWjKsUDJYzNnujDQD6AQ2LGr/n7rf58J9KsAHyjFYPVPhp4aoXt/8f+emCTEVD2rXGE9O1TzOozUF1fNsFTXPqGpE0mx4KppMxSbaXa78wH3vKh/AoGBAJf4645tEgKm323l88mYyB/WhMfK2So2fA9/cDHOe3PtL7vcJ3qLi1iB50QGSZqZeXJhi6u2ITmnO5StNPcJVvli61/GA0cRU6AIskl1IkXcDdePk8NEuDe3LPSHYncbGSEWVG2UEpdHrBTisDMbAkiZ63dUqSu5FzMgi/vhIMmxAoGBAOHgQ7xMwPOw7mpczSdT8gUqNwGFWKIVMo0YJFW20WntJgr/aFY70XoW4ENIHJyZFEl2GSuHiI6S8d2yLJdbSmjlj06wgwCsEk2Bc8e81BSVHisH8e2kSApyjMOxjgfUZoWcEHrWJeIcZykowiPrF39HDMbOtwv94n96+v90T4Nq")
+	assert.NoError(t, err)
+
+	pubKey, err := PublicKeyFromString("rsa MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA04vO0K60ly4iCyfP6PLePK0uF8LYs6GGyH41diteqbPRJqzSb2kCluDF+ZOsgRKHEE5cVquWJWATPFdjQvPluUysxk/jELgDWT4lDbmTP29xIGBQHIlQIrnYaoBHU+b4LegcypMprsdw9EiV9W5R/F/bTMkJyaCD4k9cZzC+T+IEhukhvbEhzYKx62cC41K9MqJ/WBqA6wp2H7xJ/dJKPjCupNbXX9l3Qbj0r20Z43N5ef7imjftEh2kwiQNnveqh6vpnYl1B3AZC+R8ZwLihP/QaBDlh+nYuy/J3SRfM6yFYZn5YQdHKmUj08HWGVxnSuFZFeKTHw2oQ5mL+lyi6QIDAQAB")
+	assert.NoError(t, err)
+
+	_, err = KeyExchange(*privKey, *pubKey)
+	assert.Error(t, err)
+}
diff --git a/pkg/bmcrypto/dual_keyexchange_test.go b/pkg/bmcrypto/dual_keyexchange_test.go
index 971597c..bcdd29a 100644
--- a/pkg/bmcrypto/dual_keyexchange_test.go
+++ b/pkg/bmcrypto/dual_keyexchange_test.go
@@ -47,9 +47,11 @@ func TestTransactionId(t *testing.T) {
 }
 
 func TestWrongKeyType(t *testing.T) {
-	privKey, pubKey, _ := generateKeyPairRSA(0)
+	kt, err := FindKeyType("rsa")
+	assert.NoError(t, err)
+	privKey, pubKey, _ := kt.GenerateKeyPair(randReader)
 
-	_, _, err := DualKeyExchange(*pubKey)
+	_, _, err = DualKeyExchange(*pubKey)
 	assert.Error(t, err)
 
 	_, _, err = DualKeyGetSecret(*privKey, TransactionID{})
@@ -58,8 +60,8 @@ func TestWrongKeyType(t *testing.T) {
 
 func TestDualKeyExchange(t *testing.T) {
 
-	privKey, _ := NewPrivKey("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
-	pubKey, _ := NewPubKey("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
+	privKey, _ := PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
+	pubKey, _ := PublicKeyFromString("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
 
 	D, txID, err := DualKeyExchange(*pubKey)
 	assert.NoError(t, err)
@@ -69,7 +71,7 @@ func TestDualKeyExchange(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, D, Dprime)
 
-	privKey2, _ := NewPrivKey("ed25519 MC4CAQAwBQYDK2VwBCIEII6nA1nsVQu1Pid+CoH6yxw9Z2yOU9++S30awQvIW3m/")
+	privKey2, _ := PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEII6nA1nsVQu1Pid+CoH6yxw9Z2yOU9++S30awQvIW3m/")
 	Dprime, ok, err = DualKeyGetSecret(*privKey2, *txID)
 	assert.NoError(t, err)
 	assert.False(t, ok)
@@ -77,8 +79,8 @@ func TestDualKeyExchange(t *testing.T) {
 }
 
 func BenchmarkDualKeyExchange(b *testing.B) {
-	privKey, _ := NewPrivKey("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
-	pubKey, _ := NewPubKey("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
+	privKey, _ := PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
+	pubKey, _ := PublicKeyFromString("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
 
 	for i := 0; i < b.N; i++ {
 		D, txID, err := DualKeyExchange(*pubKey)
diff --git a/internal/message/encrypt_test.go b/pkg/bmcrypto/encrypt_test.go
similarity index 65%
rename from internal/message/encrypt_test.go
rename to pkg/bmcrypto/encrypt_test.go
index fc857de..3dec64a 100644
--- a/internal/message/encrypt_test.go
+++ b/pkg/bmcrypto/encrypt_test.go
@@ -17,26 +17,26 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package message
+package bmcrypto
 
 import (
 	"io/ioutil"
 	"testing"
 
-	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestEncrypt(t *testing.T) {
 	// RSA Encryption
 	data, _ := ioutil.ReadFile("../../testdata/pubkey.rsa")
-	pubKey, _ := bmcrypto.NewPubKey(string(data))
+	pubKey, _ := PublicKeyFromString(string(data))
 
 	data, _ = ioutil.ReadFile("../../testdata/privkey.rsa")
-	privKey, _ := bmcrypto.NewPrivKey(string(data))
+	privKey, _ := PrivateKeyFromString(string(data))
 
-	cipher, _, _, err := Encrypt(*pubKey, []byte("foobar"))
+	cipher, _, c, err := Encrypt(*pubKey, []byte("foobar"))
 	assert.Nil(t, err)
+	assert.Equal(t, "rsa", c)
 	assert.NotEqual(t, []byte("foobar"), cipher)
 
 	plaintext, err := Decrypt(*privKey, "", cipher)
@@ -44,27 +44,14 @@ func TestEncrypt(t *testing.T) {
 	assert.Equal(t, []byte("foobar"), plaintext)
 
 	// ED25519 Dual Key-Exchange + Encryption
-	priv25519Key, _ := bmcrypto.NewPrivKey("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
-	pub25519Key, _ := bmcrypto.NewPubKey("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
-	cipher, txID, _, err := Encrypt(*pub25519Key, []byte("foobar"))
+	priv25519Key, _ := PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEIBJsN8lECIdeMHEOZhrdDNEZl5BuULetZsbbdsZBjZ8a")
+	pub25519Key, _ := PublicKeyFromString("ed25519 MCowBQYDK2VwAyEAblFzZuzz1vItSqdHbr/3DZMYvdoy17ALrjq3BM7kyKE=")
+	cipher, txID, c, err := pub25519Key.Type.Encrypt(*pub25519Key, []byte("foobar"))
 	assert.Nil(t, err)
+	assert.Equal(t, "ed25519+aes", c)
 	assert.NotEqual(t, []byte("foobar"), cipher)
 
-	plaintext, err = Decrypt(*priv25519Key, txID, cipher)
+	plaintext, err = pub25519Key.Type.Decrypt(*priv25519Key, txID, cipher)
 	assert.Nil(t, err)
 	assert.Equal(t, []byte("foobar"), plaintext)
 }
-
-func TestErrors(t *testing.T) {
-	priv, pub, err := bmcrypto.GenerateKeyPair(bmcrypto.KeyTypeECDSA)
-	assert.NoError(t, err)
-
-	cipher, txID, _, err := Encrypt(*pub, []byte("foobar"))
-	assert.Error(t, err)
-	assert.Nil(t, cipher)
-	assert.Equal(t, "", txID)
-
-	_, err = Decrypt(*priv, "", []byte("foobar"))
-	assert.Error(t, err)
-
-}
diff --git a/pkg/bmcrypto/generate_test.go b/pkg/bmcrypto/generate_test.go
index 029607a..ee16fc4 100644
--- a/pkg/bmcrypto/generate_test.go
+++ b/pkg/bmcrypto/generate_test.go
@@ -33,11 +33,13 @@ var (
 )
 
 func TestGenerate(t *testing.T) {
-	privKey, pubKey, err := GenerateKeyPair(KeyTypeRSA)
+	kt, err := FindKeyType("rsa")
+	assert.NoError(t, err)
+	privKey, pubKey, err := GenerateKeyPair(kt)
 	assert.Nil(t, err)
 	assert.IsType(t, (*rsa.PrivateKey)(nil), privKey.K)
 	assert.IsType(t, (*rsa.PublicKey)(nil), pubKey.K)
-	assert.Equal(t, pubKey.K.(*rsa.PublicKey).Size()*8, RsaBits[0])
+	assert.Equal(t, pubKey.K.(*rsa.PublicKey).Size()*8, 2048)
 
 	// Check if we can verify with this key
 	sig, err := Sign(*privKey, message)
@@ -46,7 +48,9 @@ func TestGenerate(t *testing.T) {
 	assert.Nil(t, err)
 	assert.True(t, b)
 
-	privKey, pubKey, err = GenerateKeyPair(KeyTypeECDSA)
+	kt, err = FindKeyType("ecdsa")
+	assert.NoError(t, err)
+	privKey, pubKey, err = GenerateKeyPair(kt)
 	assert.Nil(t, err)
 	assert.IsType(t, (*ecdsa.PrivateKey)(nil), privKey.K)
 	assert.IsType(t, (*ecdsa.PublicKey)(nil), pubKey.K)
@@ -58,7 +62,9 @@ func TestGenerate(t *testing.T) {
 	assert.Nil(t, err)
 	assert.True(t, b)
 
-	privKey, pubKey, err = GenerateKeyPair(KeyTypeED25519)
+	kt, err = FindKeyType("ed25519")
+	assert.NoError(t, err)
+	privKey, pubKey, err = GenerateKeyPair(kt)
 	assert.Nil(t, err)
 	assert.IsType(t, (ed25519.PrivateKey)(nil), privKey.K)
 	assert.IsType(t, (ed25519.PublicKey)(nil), pubKey.K)
@@ -71,15 +77,17 @@ func TestGenerate(t *testing.T) {
 	assert.True(t, b)
 
 	// Unknown key
-	_, _, err = GenerateKeyPair("foobar")
-	assert.EqualError(t, err, "incorrect key type specified")
+	_, _, err = GenerateKeyPair(nil)
+	assert.Error(t, err)
 }
 
-func TestRSAV1(t *testing.T) {
-	privKey, pubKey, err := GenerateKeyPair(KeyTypeRSAV1)
+func TestRSA4096(t *testing.T) {
+	kt, err := FindKeyType("rsa4096")
+	assert.NoError(t, err)
+
+	privKey, pubKey, err := GenerateKeyPair(kt)
 	assert.Nil(t, err)
 	assert.IsType(t, (*rsa.PrivateKey)(nil), privKey.K)
 	assert.IsType(t, (*rsa.PublicKey)(nil), pubKey.K)
-	assert.Equal(t, pubKey.K.(*rsa.PublicKey).Size()*8, RsaBits[1])
-
+	assert.Equal(t, pubKey.K.(*rsa.PublicKey).Size()*8, 4096)
 }
diff --git a/internal/api/jwt_ed25519_sign_test.go b/pkg/bmcrypto/jwt_ed25519_sign_test.go
similarity index 87%
rename from internal/api/jwt_ed25519_sign_test.go
rename to pkg/bmcrypto/jwt_ed25519_sign_test.go
index ae25d5f..d870be8 100644
--- a/internal/api/jwt_ed25519_sign_test.go
+++ b/pkg/bmcrypto/jwt_ed25519_sign_test.go
@@ -17,13 +17,12 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package api
+package bmcrypto
 
 import (
 	"crypto/ed25519"
 	"testing"
 
-	testing2 "github.com/bitmaelum/bitmaelum-suite/internal/testing"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -35,7 +34,8 @@ func TestSigningMethodEdDSAAlg(t *testing.T) {
 func TestSigningMethodEdDSASign(t *testing.T) {
 	m := &SigningMethodEdDSA{}
 
-	privKey, pubKey, _ := testing2.ReadTestKey("../../testdata/key-ed25519-1.json")
+	privKey, _ := PrivateKeyFromString("ed25519 MC4CAQAwBQYDK2VwBCIEILq+V/CUlMdbmoQC1odEgOEmtMBQu0UpIICxJbQM1vhd")
+	pubKey, _ := PublicKeyFromString("ed25519 MCowBQYDK2VwAyEARdZSwluYtMWTGI6Rvl0Bhu40RBDn6D88wyzFL1IR3DU=")
 
 	s, err := m.Sign("foobar", privKey.K.(ed25519.PrivateKey))
 	assert.NoError(t, err)
diff --git a/pkg/bmcrypto/keyexchange_test.go b/pkg/bmcrypto/keyexchange_test.go
index 650b8c7..f4dc356 100644
--- a/pkg/bmcrypto/keyexchange_test.go
+++ b/pkg/bmcrypto/keyexchange_test.go
@@ -54,37 +54,36 @@ var (
 
 func TestKeyExchange(t *testing.T) {
 	// Incorrect key type
-	privAliceK, _ := NewPrivKey(TestKeyExchangeSet1[0])
-	pubBobK, _ := NewPubKey(TestKeyExchangeSet1[1])
+	privAliceK, _ := PrivateKeyFromString(TestKeyExchangeSet1[0])
+	pubBobK, _ := PublicKeyFromString(TestKeyExchangeSet1[1])
 	_, err := KeyExchange(*privAliceK, *pubBobK)
 	assert.Error(t, err)
 
 	// ecdsa key exchange (ecdh) alice->bob
-	privAliceK, _ = NewPrivKey(TestKeyExchangeSet4[0])
-	pubBobK, _ = NewPubKey(TestKeyExchangeSet5[1])
+	privAliceK, _ = PrivateKeyFromString(TestKeyExchangeSet4[0])
+	pubBobK, _ = PublicKeyFromString(TestKeyExchangeSet5[1])
 	k, err := KeyExchange(*privAliceK, *pubBobK)
 	assert.NoError(t, err)
 	assert.Equal(t, ecdsaSharedSecret, k)
 
 	// ecdsa key exchange (ecdh) bob->alice
-	privBobK, _ := NewPrivKey(TestKeyExchangeSet5[0])
-	pubAliceK, _ := NewPubKey(TestKeyExchangeSet4[1])
+	privBobK, _ := PrivateKeyFromString(TestKeyExchangeSet5[0])
+	pubAliceK, _ := PublicKeyFromString(TestKeyExchangeSet4[1])
 	k, err = KeyExchange(*privBobK, *pubAliceK)
 	assert.NoError(t, err)
 	assert.Equal(t, ecdsaSharedSecret, k)
 
 	// ed25519 key exchange (ecdh on curve 25519) alice->bob
-	privAliceK, _ = NewPrivKey(TestKeyExchangeSet2[0])
-	pubBobK, _ = NewPubKey(TestKeyExchangeSet3[1])
+	privAliceK, _ = PrivateKeyFromString(TestKeyExchangeSet2[0])
+	pubBobK, _ = PublicKeyFromString(TestKeyExchangeSet3[1])
 	k, err = KeyExchange(*privAliceK, *pubBobK)
 	assert.NoError(t, err)
 	assert.Equal(t, ed25519SharedSecret, k)
 
 	// ed25519 key exchange (ecdh on curve 25519) bob->alice
-	privBobK, _ = NewPrivKey(TestKeyExchangeSet3[0])
-	pubAliceK, _ = NewPubKey(TestKeyExchangeSet2[1])
+	privBobK, _ = PrivateKeyFromString(TestKeyExchangeSet3[0])
+	pubAliceK, _ = PublicKeyFromString(TestKeyExchangeSet2[1])
 	k, err = KeyExchange(*privBobK, *pubAliceK)
 	assert.NoError(t, err)
 	assert.Equal(t, ed25519SharedSecret, k)
-
 }
diff --git a/pkg/bmcrypto/keys_test.go b/pkg/bmcrypto/keytype_test.go
similarity index 63%
rename from pkg/bmcrypto/keys_test.go
rename to pkg/bmcrypto/keytype_test.go
index 342121f..a0cdcdd 100644
--- a/pkg/bmcrypto/keys_test.go
+++ b/pkg/bmcrypto/keytype_test.go
@@ -31,7 +31,8 @@ import (
 )
 
 var TestKeySet1 = []string{
-	"rsa MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB",
+	"rsa MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAySB+eJGwb1Wu8KERTBLxKfMaZ7VRP92Q0LmJbqF5X2NrLyO+WkcYCcTqqoeW9unfoEiT4dS0I2YRLNcXztTaPs9xcoAWuXxPHwMKdjFKWQubttUoAAU08HpCkceO2y29NsrgOfKgUUc+D8FRXtyAe+GUg6wpWhxtT1BmjIgT4LIJKTKJ7cUdLxDQuKmT0uj0B+LQ4DTo4SvE7aV+Lb4wD2kwB0CHckHsbVC1jFPPcSLEtaSAjssaHe6v2c0oS2VdvuHq2pMa2lnuR/+wXeWn+zf0Rft0rcZNbIDpRt+DQH5g6VhA1Ww802mB/XYGaZcljzR5ArCDX8flvHP04ZcVWQIDAQAB",
+	// "rsa MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB",
 	"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB",
 	"fooobar",
 	"rsa foo",
@@ -44,7 +45,8 @@ var TestKeySet3 = []string{
 	"ecdsa MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE58d01mIg3iWGqBHY7N6ch4L4LWya8Es2luWC08Wjn994nLgIOUp+cdMUfDYBe/x1aPE/yghGe3rrF4jW8uxVWy40BZK4NIu5yjMgSw0WBGTxOmZsVaA/xaOzvZSMTXxM",
 }
 var TestKeySet4 = []string{
-	"ecdsa MIGkAgEBBDBLD4tDPxb/Xw2SzOsDEwl42LinqQmlWmcusiQJSnHn2VJsHzTuBoj7zE0dGhBS/ESgBwYFK4EEACKhZANiAATnx3TWYiDeJYaoEdjs3pyHgvgtbJrwSzaW5YLTxaOf33icuAg5Sn5x0xR8NgF7/HVo8T/KCEZ7eusXiNby7FVbLjQFkrg0i7nKMyBLDRYEZPE6ZmxVoD/Fo7O9lIxNfEw=",
+	"ecdsa MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDt8LNhN1AHVrBuBqKrryGgUS0EdRdjRuYGjONDYI96Vy8IcGsz4HQrX0biOzfE5iuhZANiAAT67cjQyt3qJUktq0dJy/KZ/15NhPpqBlG7NCwVyeyqcU2IlpE0bM+58BOkBpHCYq7zxEfXurDYIuCMKNKExJXXUeCczPBNHg9pWVVCPTHkypb69VURfYgQSo/58vSXCjU=",
+	"ecdsa MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+u3I0Mrd6iVJLatHScvymf9eTYT6agZRuzQsFcnsqnFNiJaRNGzPufATpAaRwmKu88RH17qw2CLgjCjShMSV11HgnMzwTR4PaVlVQj0x5MqW+vVVEX2IEEqP+fL0lwo1",
 }
 var TestKeySet5 = []string{
 	"ed25519 MC4CAQAwBQYDK2VwBCIEIMdUuZk8GXMMRnrbZ90JDNIrz5h7ac2whZiqpveDSgZ7",
@@ -52,34 +54,34 @@ var TestKeySet5 = []string{
 
 func TestRSAPubKey(t *testing.T) {
 	// Correct
-	pk, err := NewPubKey(TestKeySet1[0])
+	pk, err := PublicKeyFromString(TestKeySet1[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "rsa", pk.Type.String())
-	assert.Equal(t, "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB", pk.S)
+	assert.Equal(t, "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAySB+eJGwb1Wu8KERTBLxKfMaZ7VRP92Q0LmJbqF5X2NrLyO+WkcYCcTqqoeW9unfoEiT4dS0I2YRLNcXztTaPs9xcoAWuXxPHwMKdjFKWQubttUoAAU08HpCkceO2y29NsrgOfKgUUc+D8FRXtyAe+GUg6wpWhxtT1BmjIgT4LIJKTKJ7cUdLxDQuKmT0uj0B+LQ4DTo4SvE7aV+Lb4wD2kwB0CHckHsbVC1jFPPcSLEtaSAjssaHe6v2c0oS2VdvuHq2pMa2lnuR/+wXeWn+zf0Rft0rcZNbIDpRt+DQH5g6VhA1Ww802mB/XYGaZcljzR5ArCDX8flvHP04ZcVWQIDAQAB", pk.S)
 	assert.Equal(t, 65537, pk.K.(*rsa.PublicKey).E)
 	assert.Equal(t, "", pk.Description)
 
 	// Without type
-	_, err = NewPubKey(TestKeySet1[1])
+	_, err = PublicKeyFromString(TestKeySet1[1])
 	assert.EqualError(t, err, "incorrect key format")
 
 	// Incorrect too
-	_, err = NewPubKey(TestKeySet1[2])
+	_, err = PublicKeyFromString(TestKeySet1[2])
 	assert.EqualError(t, err, "incorrect key format")
 
 	// right type, wrong data
-	_, err = NewPubKey(TestKeySet1[3])
-	assert.EqualError(t, err, "incorrect key data")
+	_, err = PublicKeyFromString(TestKeySet1[3])
+	assert.EqualError(t, err, "incorrect key format")
 
 	// wrong type, right data
-	_, err = NewPubKey(TestKeySet1[4])
-	assert.EqualError(t, err, "incorrect key type")
+	_, err = PublicKeyFromString(TestKeySet1[4])
+	assert.EqualError(t, err, "unsupported key type")
 
-	pk, _ = NewPubKey(TestKeySet1[0])
-	npk, err := NewPubKeyFromInterface(pk.K)
+	pk, _ = PublicKeyFromString(TestKeySet1[0])
+	npk, err := PublicKeyFromInterface(pk.Type, pk.K)
 	assert.NoError(t, err)
 	assert.Equal(t, "rsa", npk.Type.String())
-	assert.Equal(t, "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB", npk.S)
+	assert.Equal(t, "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAySB+eJGwb1Wu8KERTBLxKfMaZ7VRP92Q0LmJbqF5X2NrLyO+WkcYCcTqqoeW9unfoEiT4dS0I2YRLNcXztTaPs9xcoAWuXxPHwMKdjFKWQubttUoAAU08HpCkceO2y29NsrgOfKgUUc+D8FRXtyAe+GUg6wpWhxtT1BmjIgT4LIJKTKJ7cUdLxDQuKmT0uj0B+LQ4DTo4SvE7aV+Lb4wD2kwB0CHckHsbVC1jFPPcSLEtaSAjssaHe6v2c0oS2VdvuHq2pMa2lnuR/+wXeWn+zf0Rft0rcZNbIDpRt+DQH5g6VhA1Ww802mB/XYGaZcljzR5ArCDX8flvHP04ZcVWQIDAQAB", npk.S)
 	assert.Equal(t, pk.K, npk.K.(*rsa.PublicKey))
 	assert.Equal(t, "", npk.Description)
 }
@@ -87,14 +89,14 @@ func TestRSAPubKey(t *testing.T) {
 func TestED25519PubKey(t *testing.T) {
 	key := ed25519.PublicKey{0x39, 0x3e, 0x4a, 0xf5, 0x5a, 0x91, 0x16, 0x32, 0x76, 0x43, 0x44, 0x56, 0x5a, 0xa4, 0xfc, 0xf8, 0xe9, 0xdd, 0x29, 0xa, 0xe6, 0xcd, 0x70, 0xae, 0x31, 0x34, 0xa7, 0x85, 0xfb, 0xd5, 0xa7, 0xb}
 	// Correct ED25519
-	pk, err := NewPubKey(TestKeySet2[0])
+	pk, err := PublicKeyFromString(TestKeySet2[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "ed25519", pk.Type.String())
 	assert.Equal(t, "MCowBQYDK2VwAyEAOT5K9VqRFjJ2Q0RWWqT8+OndKQrmzXCuMTSnhfvVpws=", pk.S)
 	assert.Equal(t, key, pk.K.(ed25519.PublicKey))
 	assert.Equal(t, "my foo bar", pk.Description)
 
-	pk, err = NewPubKeyFromInterface(key)
+	pk, err = PublicKeyFromInterface(pk.Type, key)
 	assert.NoError(t, err)
 	assert.Equal(t, "ed25519", pk.Type.String())
 	assert.Equal(t, "MCowBQYDK2VwAyEAOT5K9VqRFjJ2Q0RWWqT8+OndKQrmzXCuMTSnhfvVpws=", pk.S)
@@ -108,7 +110,7 @@ func TestECDSAPub(t *testing.T) {
 	y := new(big.Int)
 	y.SetString("16152110512098221797044896547068689859301830683829073190715770376238845227548146305554820081396880025274135358569548", 10)
 
-	pk, err := NewPubKey(TestKeySet3[0])
+	pk, err := PublicKeyFromString(TestKeySet3[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "ecdsa", pk.Type.String())
 	assert.Equal(t, "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE58d01mIg3iWGqBHY7N6ch4L4LWya8Es2luWC08Wjn994nLgIOUp+cdMUfDYBe/x1aPE/yghGe3rrF4jW8uxVWy40BZK4NIu5yjMgSw0WBGTxOmZsVaA/xaOzvZSMTXxM", pk.S)
@@ -117,19 +119,19 @@ func TestECDSAPub(t *testing.T) {
 	assert.Equal(t, "P-384", pk.K.(*ecdsa.PublicKey).Curve.Params().Name)
 	assert.Equal(t, "", pk.Description)
 
-	// Check private key data
-	_, err = NewPubKey(TestKeySet4[0])
-	assert.EqualError(t, err, "incorrect key data")
+	// Check public key data, but it's a private key
+	_, err = PublicKeyFromString(TestKeySet4[0])
+	assert.EqualError(t, err, "incorrect key format")
 }
 
 func TestECDSAPriv(t *testing.T) {
 	d := new(big.Int)
-	d.SetString("11552901970705313238876759535655836311969175439875617508331015348976563282371780402868289145078300182831985971100740", 10)
+	d.SetString("36622354286273408401742860803717975834272203860876773101980455299082624468593645020123023501111501921279897088878123", 10)
 
-	pk, err := NewPrivKey(TestKeySet4[0])
+	pk, err := PrivateKeyFromString(TestKeySet4[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "ecdsa", pk.Type.String())
-	assert.Equal(t, "MIGkAgEBBDBLD4tDPxb/Xw2SzOsDEwl42LinqQmlWmcusiQJSnHn2VJsHzTuBoj7zE0dGhBS/ESgBwYFK4EEACKhZANiAATnx3TWYiDeJYaoEdjs3pyHgvgtbJrwSzaW5YLTxaOf33icuAg5Sn5x0xR8NgF7/HVo8T/KCEZ7eusXiNby7FVbLjQFkrg0i7nKMyBLDRYEZPE6ZmxVoD/Fo7O9lIxNfEw=", pk.S)
+	assert.Equal(t, "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDt8LNhN1AHVrBuBqKrryGgUS0EdRdjRuYGjONDYI96Vy8IcGsz4HQrX0biOzfE5iuhZANiAAT67cjQyt3qJUktq0dJy/KZ/15NhPpqBlG7NCwVyeyqcU2IlpE0bM+58BOkBpHCYq7zxEfXurDYIuCMKNKExJXXUeCczPBNHg9pWVVCPTHkypb69VURfYgQSo/58vSXCjU=", pk.S)
 	assert.Equal(t, d, pk.K.(*ecdsa.PrivateKey).D)
 	assert.Equal(t, "P-384", pk.K.(*ecdsa.PrivateKey).Curve.Params().Name)
 }
@@ -137,13 +139,13 @@ func TestECDSAPriv(t *testing.T) {
 func TestED25519Priv(t *testing.T) {
 	b := []byte{0xc7, 0x54, 0xb9, 0x99, 0x3c, 0x19, 0x73, 0xc, 0x46, 0x7a, 0xdb, 0x67, 0xdd, 0x9, 0xc, 0xd2, 0x2b, 0xcf, 0x98, 0x7b, 0x69, 0xcd, 0xb0, 0x85, 0x98, 0xaa, 0xa6, 0xf7, 0x83, 0x4a, 0x6, 0x7b}
 
-	pk, err := NewPrivKey(TestKeySet5[0])
+	pk, err := PrivateKeyFromString(TestKeySet5[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "ed25519", pk.Type.String())
 	assert.Equal(t, "MC4CAQAwBQYDK2VwBCIEIMdUuZk8GXMMRnrbZ90JDNIrz5h7ac2whZiqpveDSgZ7", pk.S)
 	assert.Equal(t, b, pk.K.(ed25519.PrivateKey).Seed())
 
-	npk, err := NewPrivKeyFromInterface(pk.K)
+	npk, err := PrivateKeyFromInterface(pk.Type, pk.K)
 	assert.NoError(t, err)
 	assert.Equal(t, "ed25519", pk.Type.String())
 	assert.Equal(t, "MC4CAQAwBQYDK2VwBCIEIMdUuZk8GXMMRnrbZ90JDNIrz5h7ac2whZiqpveDSgZ7", pk.S)
@@ -151,17 +153,17 @@ func TestED25519Priv(t *testing.T) {
 }
 
 func TestIncorrectKeys(t *testing.T) {
-	pk, err := NewPrivKey("foo 12314")
+	pk, err := PrivateKeyFromString("foo 12314")
 	assert.Error(t, err)
 	assert.Nil(t, pk)
 
-	pk, err = NewPrivKey("foo12314")
+	pk, err = PrivateKeyFromString("foo12314")
 	assert.Error(t, err)
 	assert.Nil(t, pk)
 }
 
 func TestPrivateKeyJSON(t *testing.T) {
-	pk, err := NewPrivKey(TestKeySet5[0])
+	pk, err := PrivateKeyFromString(TestKeySet5[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "ed25519", pk.Type.String())
 
@@ -181,13 +183,13 @@ func TestPrivateKeyJSON(t *testing.T) {
 
 func TestPubKeyJSON(t *testing.T) {
 	// Pub keys
-	pk, err := NewPubKey(TestKeySet1[0])
+	pk, err := PublicKeyFromString(TestKeySet1[0])
 	assert.NoError(t, err)
 	assert.Equal(t, "rsa", pk.Type.String())
 
 	data, err := pk.MarshalJSON()
 	assert.NoError(t, err)
-	assert.Equal(t, "\"rsa MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB\"", string(data))
+	assert.Equal(t, "\"rsa MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAySB+eJGwb1Wu8KERTBLxKfMaZ7VRP92Q0LmJbqF5X2NrLyO+WkcYCcTqqoeW9unfoEiT4dS0I2YRLNcXztTaPs9xcoAWuXxPHwMKdjFKWQubttUoAAU08HpCkceO2y29NsrgOfKgUUc+D8FRXtyAe+GUg6wpWhxtT1BmjIgT4LIJKTKJ7cUdLxDQuKmT0uj0B+LQ4DTo4SvE7aV+Lb4wD2kwB0CHckHsbVC1jFPPcSLEtaSAjssaHe6v2c0oS2VdvuHq2pMa2lnuR/+wXeWn+zf0Rft0rcZNbIDpRt+DQH5g6VhA1Ww802mB/XYGaZcljzR5ArCDX8flvHP04ZcVWQIDAQAB\"", string(data))
 
 	err = pk.UnmarshalJSON([]byte("fasfdsadfA"))
 	assert.IsType(t, &json.SyntaxError{}, err)
@@ -200,9 +202,9 @@ func TestPubKeyJSON(t *testing.T) {
 }
 
 func TestFingerprint(t *testing.T) {
-	pk, _ := NewPubKey(TestKeySet1[0])
-	assert.Equal(t, "54fe2065620a04c17b022aa0f07461a862919e7c832d79aa211e6cebd784620b", pk.Fingerprint())
+	pk, _ := PublicKeyFromString(TestKeySet1[0])
+	assert.Equal(t, "4d6dc360edab1276404d3ece66868d6b390288b5e4bad42c75b350b792127046", pk.Fingerprint())
 
-	pk, _ = NewPubKey(TestKeySet2[0])
+	pk, _ = PublicKeyFromString(TestKeySet2[0])
 	assert.Equal(t, "3bde10ad2dc6508163a8a4944c46684102ed777e57f73514c75e6e62448a3d85", pk.Fingerprint())
 }
diff --git a/pkg/bmcrypto/sign_test.go b/pkg/bmcrypto/sign_test.go
index 2eb2488..df1b544 100644
--- a/pkg/bmcrypto/sign_test.go
+++ b/pkg/bmcrypto/sign_test.go
@@ -45,11 +45,11 @@ func TestSignRSA(t *testing.T) {
 
 	data, err := ioutil.ReadFile("../../testdata/privkey.rsa")
 	assert.NoError(t, err)
-	privKey, err := NewPrivKey(string(data))
+	privKey, err := PrivateKeyFromString(string(data))
 	assert.NoError(t, err)
 	data, err = ioutil.ReadFile("../../testdata/pubkey.rsa")
 	assert.NoError(t, err)
-	pubKey, err := NewPubKey(string(data))
+	pubKey, err := PublicKeyFromString(string(data))
 	assert.NoError(t, err)
 
 	sig, err := Sign(*privKey, signMessage)
@@ -70,11 +70,11 @@ func TestSignECDSA(t *testing.T) {
 
 	data, err := ioutil.ReadFile("../../testdata/privkey.ecdsa")
 	assert.NoError(t, err)
-	privKey, err := NewPrivKey(string(data))
+	privKey, err := PrivateKeyFromString(string(data))
 	assert.NoError(t, err)
 	data, err = ioutil.ReadFile("../../testdata/pubkey.ecdsa")
 	assert.NoError(t, err)
-	pubKey, err := NewPubKey(string(data))
+	pubKey, err := PublicKeyFromString(string(data))
 	assert.NoError(t, err)
 
 	sig, err := Sign(*privKey, signMessage)
@@ -95,11 +95,11 @@ func TestSignED25519(t *testing.T) {
 
 	data, err := ioutil.ReadFile("../../testdata/privkey.ed25519")
 	assert.NoError(t, err)
-	privKey, err := NewPrivKey(string(data))
+	privKey, err := PrivateKeyFromString(string(data))
 	assert.NoError(t, err)
 	data, err = ioutil.ReadFile("../../testdata/pubkey.ed25519")
 	assert.NoError(t, err)
-	pubKey, err := NewPubKey(string(data))
+	pubKey, err := PublicKeyFromString(string(data))
 	assert.NoError(t, err)
 
 	sig, err := Sign(*privKey, signMessage)
@@ -118,21 +118,11 @@ func TestSignED25519(t *testing.T) {
 func TestSignErr(t *testing.T) {
 	data, err := ioutil.ReadFile("../../testdata/privkey.ed25519")
 	assert.NoError(t, err)
-	privKey, err := NewPrivKey(string(data))
+	_, err = PrivateKeyFromString(string(data))
 	assert.NoError(t, err)
 
 	data, err = ioutil.ReadFile("../../testdata/pubkey.ed25519")
 	assert.NoError(t, err)
-	pubKey, err := NewPubKey(string(data))
+	_, err = PublicKeyFromString(string(data))
 	assert.NoError(t, err)
-
-	privKey.Type = "fooooobar-notexist"
-	sig, err := Sign(*privKey, []byte("message"))
-	assert.Errorf(t, err, "unknown key type for signing")
-	assert.Nil(t, sig)
-
-	pubKey.Type = "foooobar-notexist"
-	ok, err := Verify(*pubKey, []byte{}, []byte{})
-	assert.Errorf(t, err, "unknown key type for signing")
-	assert.False(t, ok)
 }
diff --git a/pkg/hash/hash_test.go b/pkg/hash/hash_test.go
index e254788..60d14c6 100644
--- a/pkg/hash/hash_test.go
+++ b/pkg/hash/hash_test.go
@@ -73,3 +73,11 @@ func TestVerify(t *testing.T) {
 		"00000000000c1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 	))
 }
+
+func TestEmpty(t *testing.T) {
+	h := New("")
+	assert.True(t, h.IsEmpty())
+
+	h = New("foo")
+	assert.False(t, h.IsEmpty())
+}