Skip to content

Commit

Permalink
kem: add P-256 + Kyber768Draft00 hybrid
Browse files Browse the repository at this point in the history
  • Loading branch information
bwesterb committed Feb 13, 2023
1 parent 7a181da commit 7c53851
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 4 deletions.
211 changes: 211 additions & 0 deletions kem/hybrid/ckem.go
@@ -0,0 +1,211 @@
package hybrid

// TODO move over to crypto/ecdh once we can assume Go 1.20.

import (
"bytes"
"crypto/elliptic"
cryptoRand "crypto/rand"
"crypto/subtle"
"math/big"

"github.com/cloudflare/circl/kem"
"github.com/cloudflare/circl/xof"
)

type cPublicKey struct {
scheme *cScheme
x, y *big.Int
}
type cPrivateKey struct {
scheme *cScheme
key []byte
}
type cScheme struct {
curve elliptic.Curve
}

var p256Kem = &cScheme{elliptic.P256()}

func (sch *cScheme) scSize() int {
return (sch.curve.Params().N.BitLen() + 7) / 8
}

func (sch *cScheme) ptSize() int {
return (sch.curve.Params().BitSize + 7) / 8
}

func (sch *cScheme) Name() string {
return sch.curve.Params().Name
}

func (sch *cScheme) PublicKeySize() int {
return 2*sch.ptSize() + 1
}

func (sch *cScheme) PrivateKeySize() int {
return sch.scSize()
}

func (sch *cScheme) SeedSize() int {
return sch.PrivateKeySize()
}

func (sch *cScheme) SharedKeySize() int {
return sch.ptSize()
}

func (sch *cScheme) CiphertextSize() int {
return sch.PublicKeySize()
}

func (sch *cScheme) EncapsulationSeedSize() int {
return sch.SeedSize()
}

func (sk *cPrivateKey) Scheme() kem.Scheme { return sk.scheme }
func (pk *cPublicKey) Scheme() kem.Scheme { return pk.scheme }

func (sk *cPrivateKey) MarshalBinary() ([]byte, error) {
ret := make([]byte, len(sk.key))
copy(ret, sk.key)
return ret, nil
}

func (sk *cPrivateKey) Equal(other kem.PrivateKey) bool {
oth, ok := other.(*cPrivateKey)
if !ok {
return false
}
if oth.scheme != sk.scheme {
return false
}
return subtle.ConstantTimeCompare(oth.key, sk.key) == 1
}

func (sk *cPrivateKey) Public() kem.PublicKey {
x, y := sk.scheme.curve.ScalarBaseMult(sk.key)
return &cPublicKey{
sk.scheme,
x,
y,
}
}

func (pk *cPublicKey) Equal(other kem.PublicKey) bool {
oth, ok := other.(*cPublicKey)
if !ok {
return false
}
if oth.scheme != pk.scheme {
return false
}
return oth.x.Cmp(pk.x) == 0 && oth.y.Cmp(pk.y) == 0
}

func (pk *cPublicKey) MarshalBinary() ([]byte, error) {
return elliptic.Marshal(pk.scheme.curve, pk.x, pk.y), nil
}

func (sch *cScheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
seed := make([]byte, sch.SeedSize())
_, err := cryptoRand.Read(seed)
if err != nil {
return nil, nil, err
}
pk, sk := sch.DeriveKeyPair(seed)
return pk, sk, nil
}

func (sch *cScheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {
if len(seed) != sch.SeedSize() {
panic(kem.ErrSeedSize)
}
h := xof.SHAKE256.New()
_, _ = h.Write(seed)
buf := make([]byte, sch.PrivateKeySize())
_, _ = h.Read(buf)
rnd := bytes.NewReader(buf)
key, x, y, err := elliptic.GenerateKey(sch.curve, rnd)
if err != nil {
panic(err)
}

sk := cPrivateKey{scheme: sch, key: key}
pk := cPublicKey{scheme: sch, x: x, y: y}

return &pk, &sk
}

func (sch *cScheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
seed := make([]byte, sch.EncapsulationSeedSize())
_, err = cryptoRand.Read(seed)
if err != nil {
return
}
return sch.EncapsulateDeterministically(pk, seed)
}

func (pk *cPublicKey) X(sk *cPrivateKey) []byte {
if pk.scheme != sk.scheme {
panic(kem.ErrTypeMismatch)
}

sharedKey := make([]byte, pk.scheme.SharedKeySize())
xShared, _ := pk.scheme.curve.ScalarMult(pk.x, pk.y, sk.key)
xShared.FillBytes(sharedKey)
return sharedKey
}

func (sch *cScheme) EncapsulateDeterministically(
pk kem.PublicKey, seed []byte,
) (ct, ss []byte, err error) {
if len(seed) != sch.EncapsulationSeedSize() {
return nil, nil, kem.ErrSeedSize
}
pub, ok := pk.(*cPublicKey)
if !ok || pub.scheme != sch {
return nil, nil, kem.ErrTypeMismatch
}

pk2, sk2 := sch.DeriveKeyPair(seed)
ss = pub.X(sk2.(*cPrivateKey))
ct, _ = pk2.MarshalBinary()
return
}

func (sch *cScheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
if len(ct) != sch.CiphertextSize() {
return nil, kem.ErrCiphertextSize
}

priv, ok := sk.(*cPrivateKey)
if !ok || priv.scheme != sch {
return nil, kem.ErrTypeMismatch
}

pk, err := sch.UnmarshalBinaryPublicKey(ct)
if err != nil {
return nil, err
}

ss := pk.(*cPublicKey).X(priv)
return ss, nil
}

func (sch *cScheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) {
if len(buf) != sch.PublicKeySize() {
return nil, kem.ErrPubKeySize
}
x, y := elliptic.Unmarshal(sch.curve, buf)
return &cPublicKey{sch, x, y}, nil
}

func (sch *cScheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) {
if len(buf) != sch.PrivateKeySize() {
return nil, kem.ErrPrivKeySize
}
ret := cPrivateKey{sch, make([]byte, sch.PrivateKeySize())}
copy(ret.key, buf)
return &ret, nil
}
17 changes: 13 additions & 4 deletions kem/hybrid/hybrid.go
Expand Up @@ -42,18 +42,27 @@ import (

var ErrUninitialized = errors.New("public or private key not initialized")

// Returns the hybrid KEM of Kyber512 and X25519.
// Returns the hybrid KEM of Kyber512Draft00 and X25519.
func Kyber512X25519() kem.Scheme { return kyber512X }

// Returns the hybrid KEM of Kyber768 and X25519.
// Returns the hybrid KEM of Kyber768Draft00 and X25519.
func Kyber768X25519() kem.Scheme { return kyber768X }

// Returns the hybrid KEM of Kyber768 and X448.
// Returns the hybrid KEM of Kyber768Draft00 and X448.
func Kyber768X448() kem.Scheme { return kyber768X4 }

// Returns the hybrid KEM of Kyber1024 and X448.
// Returns the hybrid KEM of Kyber1024Draft00 and X448.
func Kyber1024X448() kem.Scheme { return kyber1024X }

// Returns the hybrid KEM of Kyber768Draft00 and P-256.
func P256Kyber768Draft00() kem.Scheme { return p256Kyber768Draft00 }

var p256Kyber768Draft00 kem.Scheme = &scheme{
"P256Kyber768Draft00",
p256Kem,
kyber768.Scheme(),
}

var kyber512X kem.Scheme = &scheme{
"Kyber512-X25519",
x25519Kem,
Expand Down
1 change: 1 addition & 0 deletions kem/schemes/schemes.go
Expand Up @@ -42,6 +42,7 @@ var allSchemes = [...]kem.Scheme{
hybrid.Kyber768X25519(),
hybrid.Kyber768X448(),
hybrid.Kyber1024X448(),
hybrid.P256Kyber768Draft00(),
}

var allSchemeNames map[string]kem.Scheme
Expand Down
1 change: 1 addition & 0 deletions kem/schemes/schemes_test.go
Expand Up @@ -159,4 +159,5 @@ func Example_schemes() {
// Kyber768-X25519
// Kyber768-X448
// Kyber1024-X448
// P256Kyber768Draft00
}

0 comments on commit 7c53851

Please sign in to comment.