Skip to content

Commit

Permalink
Add dilithium + EdDSA algos
Browse files Browse the repository at this point in the history
  • Loading branch information
wussler committed Aug 12, 2022
1 parent 28ec33a commit 9de57de
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 3 deletions.
84 changes: 84 additions & 0 deletions openpgp/dilithium_eddsa/dilithium_eddsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Package dilithium_eddsa implements hybrid Dilithium + EdDSA encryption, suitable for OpenPGP, experimental.
package dilithium_eddsa

import (
"crypto/subtle"
"errors"
"io"

"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
dilithium "github.com/kudelskisecurity/crystals-go/crystals-dilithium"
"golang.org/x/crypto/sha3"
)

type PublicKey struct {
AlgId uint8
Curve ecc.EdDSACurve
Dilithium *dilithium.Dilithium
PublicPoint, PublicDilithium []byte
}

type PrivateKey struct {
PublicKey
SecretEC []byte
SecretDilithium []byte
}

func GenerateKey(rand io.Reader, algId uint8, c ecc.EdDSACurve, d *dilithium.Dilithium) (priv *PrivateKey, err error) {
priv = new(PrivateKey)

priv.PublicKey.AlgId = algId
priv.PublicKey.Curve = c
priv.PublicKey.Dilithium = d

priv.PublicKey.PublicPoint, priv.SecretEC, err = c.GenerateEdDSA(rand)
if err != nil {
return nil, err
}

dilithiumSeed := make([]byte, dilithium.SEEDBYTES)
_, err = rand.Read(dilithiumSeed)
if err != nil {
return nil, err
}

priv.PublicKey.PublicDilithium, priv.SecretDilithium = priv.PublicKey.Dilithium.KeyGen(dilithiumSeed)
return
}

func Sign(priv *PrivateKey, message []byte) (dSig, ecSig []byte, err error) {
ecSig, err = priv.PublicKey.Curve.Sign(priv.PublicKey.PublicPoint, priv.SecretEC, message)
if err != nil {
return nil, nil, err
}

dSig = priv.PublicKey.Dilithium.Sign(priv.SecretDilithium, message)
if dSig == nil {
return nil, nil, errors.New("dilithium_eddsa: unable to sign with dilithium")
}

return
}

func Verify(pub *PublicKey, message, dSig, ecSig []byte) bool {
return pub.Curve.Verify(pub.PublicPoint, message, ecSig) && pub.Dilithium.Verify(pub.PublicDilithium, message, dSig)
}

func Validate(priv *PrivateKey) (err error) {
var tr [dilithium.SEEDBYTES]byte

if err = priv.PublicKey.Curve.Validate(priv.PublicKey.PublicPoint, priv.SecretEC); err != nil {
return err
}

state := sha3.NewShake256()

state.Write(priv.PublicKey.PublicDilithium)
state.Read(tr[:])
kSk := priv.PublicKey.Dilithium.UnpackSK(priv.SecretDilithium)
if subtle.ConstantTimeCompare(kSk.Tr[:], tr[:]) == 0 {
return errors.New("dilithium_eddsa: invalid public key")
}

return
}
87 changes: 87 additions & 0 deletions openpgp/dilithium_eddsa/dilithium_eddsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Package dilithium_eddsa_test tests the implementation of hybrid Dilithium + EdDSA encryption, suitable for OpenPGP, experimental.
package dilithium_eddsa_test

import (
"crypto/rand"
"io"
"testing"

"github.com/ProtonMail/go-crypto/openpgp/dilithium_eddsa"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)

func TestSignVerify(t *testing.T) {
asymmAlgos := map[string] packet.PublicKeyAlgorithm {
"Dilithium2_Ed25519": packet.PubKeyAlgoDilithium2Ed25519,
"Dilithium5_Ed448": packet.PubKeyAlgoDilithium5Ed448,
}

for asymmName, asymmAlgo := range asymmAlgos {
t.Run(asymmName, func(t *testing.T) {
key := testGenerateKeyAlgo(t, asymmAlgo)
testSignVerifyAlgo(t, key)
testvalidateAlgo(t, asymmAlgo)
})
}
}

func testvalidateAlgo(t *testing.T, algId packet.PublicKeyAlgorithm) {
key := testGenerateKeyAlgo(t, algId)
if err := dilithium_eddsa.Validate(key); err != nil {
t.Fatalf("valid key marked as invalid: %s", err)
}

key.PublicDilithium[5] ^= 1
if err := dilithium_eddsa.Validate(key); err == nil {
t.Fatalf("failed to detect invalid key")
}

// Generate fresh key
key = testGenerateKeyAlgo(t, algId)
if err := dilithium_eddsa.Validate(key); err != nil {
t.Fatalf("valid key marked as invalid: %s", err)
}

key.PublicPoint[5] ^= 1
if err := dilithium_eddsa.Validate(key); err == nil {
t.Fatalf("failed to detect invalid key")
}
}

func testGenerateKeyAlgo(t *testing.T, algId packet.PublicKeyAlgorithm) *dilithium_eddsa.PrivateKey {
curveObj, err := packet.GetEdDSACurveFromAlgID(algId)
if err != nil {
t.Errorf("error getting curve: %s", err)
}

kyberObj, err := packet.GetDilithiumFromAlgID(algId)
if err != nil {
t.Errorf("error getting dilithium: %s", err)
}

priv, err := dilithium_eddsa.GenerateKey(rand.Reader, uint8(algId), curveObj, kyberObj)
if err != nil {
t.Fatal(err)
}

return priv
}


func testSignVerifyAlgo(t *testing.T, priv *dilithium_eddsa.PrivateKey) {
digest := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, digest[:])
if err != nil {
t.Fatal(err)
}

dSig, ecSig, err := dilithium_eddsa.Sign(priv, digest)
if err != nil {
t.Errorf("error encrypting: %s", err)
}

result := dilithium_eddsa.Verify(&priv.PublicKey, digest, dSig, ecSig)
if !result {
t.Error("unable to verify message")
}
}
2 changes: 1 addition & 1 deletion openpgp/kyber_ecdh/kyber_ecdh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func testGenerateKeyAlgo(t *testing.T, algId packet.PublicKeyAlgorithm) *kyber_e

kyberObj, err := packet.GetKyberFromAlgID(algId)
if err != nil {
t.Errorf("error getting kyber_ecdh: %s", err)
t.Errorf("error getting kyber: %s", err)
}

priv, err := kyber_ecdh.GenerateKey(rand.Reader, uint8(algId), curveObj, kyberObj)
Expand Down
10 changes: 9 additions & 1 deletion openpgp/packet/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,15 @@ const (
PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3

// Experimental PQC composite algorithms
// Experimental PQC DSA algorithms
PubKeyAlgoDilithium2Ed25519 = 25
PubKeyAlgoDilithium5Ed448 = 26
PubKeyAlgoDilithium3p384 = 27
PubKeyAlgoDilithium5p521 = 28
PubKeyAlgoDilithium3Brainpool384 = 29
PubKeyAlgoDilithium5Brainpool512 = 30

// Experimental PQC KEM algorithms
PubKeyAlgoKyber512X25519 = 32
PubKeyAlgoKyber1024X448 = 33
PubKeyAlgoKyber768P384 = 34
Expand Down
27 changes: 26 additions & 1 deletion openpgp/packet/public_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
goerrors "errors"
"fmt"
"github.com/ProtonMail/go-crypto/brainpool"
dilithium "github.com/kudelskisecurity/crystals-go/crystals-dilithium"
"hash"
"io"
"math/big"
Expand Down Expand Up @@ -888,7 +889,7 @@ func GetKyberFromAlgID(algId PublicKeyAlgorithm) (*libkyber.Kyber, error) {
case PubKeyAlgoKyber1024X448, PubKeyAlgoKyber1024P521, PubKeyAlgoKyber1024Brainpool512:
return libkyber.NewKyber1024(), nil
default:
return nil, goerrors.New("packet: unsupported kyber_ecdh public key algorithm")
return nil, goerrors.New("packet: unsupported Kyber public key algorithm")
}
}

Expand All @@ -909,4 +910,28 @@ func GetECDHCurveFromAlgID(algId PublicKeyAlgorithm) (ecc.ECDHCurve, error) {
default:
return nil, goerrors.New("packet: unsupported ECDH public key algorithm")
}
}

func GetDilithiumFromAlgID(algId PublicKeyAlgorithm) (*dilithium.Dilithium, error) {
switch algId {
case PubKeyAlgoDilithium2Ed25519:
return dilithium.NewDilithium2(), nil
case PubKeyAlgoDilithium3p384, PubKeyAlgoDilithium3Brainpool384:
return dilithium.NewDilithium3(), nil
case PubKeyAlgoDilithium5Ed448, PubKeyAlgoDilithium5p521, PubKeyAlgoDilithium5Brainpool512:
return dilithium.NewDilithium5(), nil
default:
return nil, goerrors.New("packet: unsupported Dilithium public key algorithm")
}
}

func GetEdDSACurveFromAlgID(algId PublicKeyAlgorithm) (ecc.EdDSACurve, error) {
switch algId {
case PubKeyAlgoDilithium2Ed25519:
return ecc.NewEd25519(), nil
case PubKeyAlgoDilithium5Ed448:
return ecc.NewEd448(), nil
default:
return nil, goerrors.New("packet: unsupported EdDSA public key algorithm")
}
}

0 comments on commit 9de57de

Please sign in to comment.