Skip to content

Commit eaec71f

Browse files
chris-woodarmfazh
authored andcommitted
Add X25519Kyber768Draft00 experimental HPKE KEM
This change also adds the ability to produce test vectors for the draft specification: https://datatracker.ietf.org/doc/draft-westerbaan-cfrg-hpke-xyber768d00/ This change also updates the known answer test vectors from RFC9180.
1 parent f0db288 commit eaec71f

13 files changed

+563
-130
lines changed

hpke/aead.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import (
77

88
type encdecContext struct {
99
// Serialized parameters
10-
suite Suite
11-
exporterSecret []byte
12-
key []byte
13-
baseNonce []byte
14-
sequenceNumber []byte
10+
suite Suite
11+
sharedSecret []byte
12+
secret []byte
13+
keyScheduleContext []byte
14+
exporterSecret []byte
15+
key []byte
16+
baseNonce []byte
17+
sequenceNumber []byte
1518

1619
// Operational parameters
1720
cipher.AEAD

hpke/aead_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ func setupAeadTest() (*sealContext, *openContext, error) {
4444

4545
sealer := &sealContext{
4646
&encdecContext{
47-
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
47+
suite, nil, nil, nil, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
4848
},
4949
}
5050
opener := &openContext{
5151
&encdecContext{
52-
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
52+
suite, nil, nil, nil, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
5353
},
5454
}
5555
return sealer, opener, nil

hpke/algs.go

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/cloudflare/circl/dh/x448"
1616
"github.com/cloudflare/circl/ecc/p384"
1717
"github.com/cloudflare/circl/kem"
18+
"github.com/cloudflare/circl/kem/kyber/kyber768"
1819
"golang.org/x/crypto/chacha20poly1305"
1920
"golang.org/x/crypto/hkdf"
2021
)
@@ -35,6 +36,9 @@ const (
3536
// KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and
3637
// HKDF with SHA-512.
3738
KEM_X448_HKDF_SHA512 KEM = 0x21
39+
// KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256)
40+
// and Kyber768Draft00
41+
KEM_X25519_KYBER768_DRAFT00 KEM = 0x30
3842
)
3943

4044
// IsValid returns true if the KEM identifier is supported by the HPKE package.
@@ -44,7 +48,8 @@ func (k KEM) IsValid() bool {
4448
KEM_P384_HKDF_SHA384,
4549
KEM_P521_HKDF_SHA512,
4650
KEM_X25519_HKDF_SHA256,
47-
KEM_X448_HKDF_SHA512:
51+
KEM_X448_HKDF_SHA512,
52+
KEM_X25519_KYBER768_DRAFT00:
4853
return true
4954
default:
5055
return false
@@ -65,32 +70,8 @@ func (k KEM) Scheme() kem.AuthScheme {
6570
return dhkemx25519hkdfsha256
6671
case KEM_X448_HKDF_SHA512:
6772
return dhkemx448hkdfsha512
68-
default:
69-
panic(ErrInvalidKEM)
70-
}
71-
}
72-
73-
func (k KEM) validatePublicKey(pk kem.PublicKey) bool {
74-
switch k {
75-
case KEM_P256_HKDF_SHA256, KEM_P384_HKDF_SHA384, KEM_P521_HKDF_SHA512:
76-
pub, ok := pk.(*shortKEMPubKey)
77-
return ok && k == pub.scheme.id && pub.Validate()
78-
case KEM_X25519_HKDF_SHA256, KEM_X448_HKDF_SHA512:
79-
pub, ok := pk.(*xKEMPubKey)
80-
return ok && k == pub.scheme.id && pub.Validate()
81-
default:
82-
panic(ErrInvalidKEM)
83-
}
84-
}
85-
86-
func (k KEM) validatePrivateKey(sk kem.PrivateKey) bool {
87-
switch k {
88-
case KEM_P256_HKDF_SHA256, KEM_P384_HKDF_SHA384, KEM_P521_HKDF_SHA512:
89-
priv, ok := sk.(*shortKEMPrivKey)
90-
return ok && k == priv.scheme.id && priv.Validate()
91-
case KEM_X25519_HKDF_SHA256, KEM_X448_HKDF_SHA512:
92-
priv, ok := sk.(*xKEMPrivKey)
93-
return ok && k == priv.scheme.id && priv.Validate()
73+
case KEM_X25519_KYBER768_DRAFT00:
74+
return hybridkemX25519Kyber768
9475
default:
9576
panic(ErrInvalidKEM)
9677
}
@@ -243,36 +224,43 @@ func (a AEAD) CipherLen(mLen uint) uint {
243224
var (
244225
dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM
245226
dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM
227+
hybridkemX25519Kyber768 hybridKEM
246228
)
247229

248230
func init() {
249231
dhkemp256hkdfsha256.Curve = elliptic.P256()
250-
dhkemp256hkdfsha256.kemBase.id = KEM_P256_HKDF_SHA256
251-
dhkemp256hkdfsha256.kemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
252-
dhkemp256hkdfsha256.kemBase.Hash = crypto.SHA256
253-
dhkemp256hkdfsha256.kemBase.dhKEM = dhkemp256hkdfsha256
232+
dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256
233+
dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
234+
dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256
235+
dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256
254236

255237
dhkemp384hkdfsha384.Curve = p384.P384()
256-
dhkemp384hkdfsha384.kemBase.id = KEM_P384_HKDF_SHA384
257-
dhkemp384hkdfsha384.kemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
258-
dhkemp384hkdfsha384.kemBase.Hash = crypto.SHA384
259-
dhkemp384hkdfsha384.kemBase.dhKEM = dhkemp384hkdfsha384
238+
dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384
239+
dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
240+
dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384
241+
dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384
260242

261243
dhkemp521hkdfsha512.Curve = elliptic.P521()
262-
dhkemp521hkdfsha512.kemBase.id = KEM_P521_HKDF_SHA512
263-
dhkemp521hkdfsha512.kemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
264-
dhkemp521hkdfsha512.kemBase.Hash = crypto.SHA512
265-
dhkemp521hkdfsha512.kemBase.dhKEM = dhkemp521hkdfsha512
244+
dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512
245+
dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
246+
dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512
247+
dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512
266248

267249
dhkemx25519hkdfsha256.size = x25519.Size
268-
dhkemx25519hkdfsha256.kemBase.id = KEM_X25519_HKDF_SHA256
269-
dhkemx25519hkdfsha256.kemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
270-
dhkemx25519hkdfsha256.kemBase.Hash = crypto.SHA256
271-
dhkemx25519hkdfsha256.kemBase.dhKEM = dhkemx25519hkdfsha256
250+
dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256
251+
dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
252+
dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256
253+
dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256
272254

273255
dhkemx448hkdfsha512.size = x448.Size
274-
dhkemx448hkdfsha512.kemBase.id = KEM_X448_HKDF_SHA512
275-
dhkemx448hkdfsha512.kemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
276-
dhkemx448hkdfsha512.kemBase.Hash = crypto.SHA512
277-
dhkemx448hkdfsha512.kemBase.dhKEM = dhkemx448hkdfsha512
256+
dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512
257+
dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
258+
dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512
259+
dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512
260+
261+
hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00
262+
hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256"
263+
hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256
264+
hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256
265+
hybridkemX25519Kyber768.kemB = kyber768.Scheme()
278266
}

hpke/hpke.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ type Sender struct {
105105

106106
// NewSender creates a Sender with knowledge of the receiver's public-key.
107107
func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) {
108-
if !suite.kemID.validatePublicKey(pkR) {
109-
return nil, ErrInvalidKEMPublicKey
110-
}
111-
112108
return &Sender{
113109
state: state{Suite: suite, info: info},
114110
pkR: pkR,
@@ -127,10 +123,6 @@ func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) {
127123
func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) (
128124
enc []byte, seal Sealer, err error,
129125
) {
130-
if !s.kemID.validatePrivateKey(skS) {
131-
return nil, nil, ErrInvalidKEMPrivateKey
132-
}
133-
134126
s.modeID = modeAuth
135127
s.state.skS = skS
136128
return s.allSetup(rnd)
@@ -152,10 +144,6 @@ func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) (
152144
func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) (
153145
enc []byte, seal Sealer, err error,
154146
) {
155-
if !s.kemID.validatePrivateKey(skS) {
156-
return nil, nil, ErrInvalidKEMPrivateKey
157-
}
158-
159147
s.modeID = modeAuthPSK
160148
s.state.skS = skS
161149
s.state.psk = psk
@@ -174,10 +162,6 @@ type Receiver struct {
174162
func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) (
175163
*Receiver, error,
176164
) {
177-
if !suite.kemID.validatePrivateKey(skR) {
178-
return nil, ErrInvalidKEMPrivateKey
179-
}
180-
181165
return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil
182166
}
183167

@@ -192,10 +176,6 @@ func (r *Receiver) Setup(enc []byte) (Opener, error) {
192176
// SetupAuth generates a new HPKE context used for Auth Mode encryption.
193177
// SetupAuth takes an encapsulated key and a public key, and returns an Opener.
194178
func (r *Receiver) SetupAuth(enc []byte, pkS kem.PublicKey) (Opener, error) {
195-
if !r.kemID.validatePublicKey(pkS) {
196-
return nil, ErrInvalidKEMPublicKey
197-
}
198-
199179
r.modeID = modeAuth
200180
r.enc = enc
201181
r.state.pkS = pkS
@@ -219,10 +199,6 @@ func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) {
219199
func (r *Receiver) SetupAuthPSK(
220200
enc, psk, pskID []byte, pkS kem.PublicKey,
221201
) (Opener, error) {
222-
if !r.kemID.validatePublicKey(pkS) {
223-
return nil, ErrInvalidKEMPublicKey
224-
}
225-
226202
r.modeID = modeAuthPSK
227203
r.enc = enc
228204
r.state.psk = psk

hpke/hybrid-x25119-kyber768-test-vectors.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)