Skip to content

Commit 7c53851

Browse files
committed
kem: add P-256 + Kyber768Draft00 hybrid
1 parent 7a181da commit 7c53851

File tree

4 files changed

+226
-4
lines changed

4 files changed

+226
-4
lines changed

kem/hybrid/ckem.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package hybrid
2+
3+
// TODO move over to crypto/ecdh once we can assume Go 1.20.
4+
5+
import (
6+
"bytes"
7+
"crypto/elliptic"
8+
cryptoRand "crypto/rand"
9+
"crypto/subtle"
10+
"math/big"
11+
12+
"github.com/cloudflare/circl/kem"
13+
"github.com/cloudflare/circl/xof"
14+
)
15+
16+
type cPublicKey struct {
17+
scheme *cScheme
18+
x, y *big.Int
19+
}
20+
type cPrivateKey struct {
21+
scheme *cScheme
22+
key []byte
23+
}
24+
type cScheme struct {
25+
curve elliptic.Curve
26+
}
27+
28+
var p256Kem = &cScheme{elliptic.P256()}
29+
30+
func (sch *cScheme) scSize() int {
31+
return (sch.curve.Params().N.BitLen() + 7) / 8
32+
}
33+
34+
func (sch *cScheme) ptSize() int {
35+
return (sch.curve.Params().BitSize + 7) / 8
36+
}
37+
38+
func (sch *cScheme) Name() string {
39+
return sch.curve.Params().Name
40+
}
41+
42+
func (sch *cScheme) PublicKeySize() int {
43+
return 2*sch.ptSize() + 1
44+
}
45+
46+
func (sch *cScheme) PrivateKeySize() int {
47+
return sch.scSize()
48+
}
49+
50+
func (sch *cScheme) SeedSize() int {
51+
return sch.PrivateKeySize()
52+
}
53+
54+
func (sch *cScheme) SharedKeySize() int {
55+
return sch.ptSize()
56+
}
57+
58+
func (sch *cScheme) CiphertextSize() int {
59+
return sch.PublicKeySize()
60+
}
61+
62+
func (sch *cScheme) EncapsulationSeedSize() int {
63+
return sch.SeedSize()
64+
}
65+
66+
func (sk *cPrivateKey) Scheme() kem.Scheme { return sk.scheme }
67+
func (pk *cPublicKey) Scheme() kem.Scheme { return pk.scheme }
68+
69+
func (sk *cPrivateKey) MarshalBinary() ([]byte, error) {
70+
ret := make([]byte, len(sk.key))
71+
copy(ret, sk.key)
72+
return ret, nil
73+
}
74+
75+
func (sk *cPrivateKey) Equal(other kem.PrivateKey) bool {
76+
oth, ok := other.(*cPrivateKey)
77+
if !ok {
78+
return false
79+
}
80+
if oth.scheme != sk.scheme {
81+
return false
82+
}
83+
return subtle.ConstantTimeCompare(oth.key, sk.key) == 1
84+
}
85+
86+
func (sk *cPrivateKey) Public() kem.PublicKey {
87+
x, y := sk.scheme.curve.ScalarBaseMult(sk.key)
88+
return &cPublicKey{
89+
sk.scheme,
90+
x,
91+
y,
92+
}
93+
}
94+
95+
func (pk *cPublicKey) Equal(other kem.PublicKey) bool {
96+
oth, ok := other.(*cPublicKey)
97+
if !ok {
98+
return false
99+
}
100+
if oth.scheme != pk.scheme {
101+
return false
102+
}
103+
return oth.x.Cmp(pk.x) == 0 && oth.y.Cmp(pk.y) == 0
104+
}
105+
106+
func (pk *cPublicKey) MarshalBinary() ([]byte, error) {
107+
return elliptic.Marshal(pk.scheme.curve, pk.x, pk.y), nil
108+
}
109+
110+
func (sch *cScheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
111+
seed := make([]byte, sch.SeedSize())
112+
_, err := cryptoRand.Read(seed)
113+
if err != nil {
114+
return nil, nil, err
115+
}
116+
pk, sk := sch.DeriveKeyPair(seed)
117+
return pk, sk, nil
118+
}
119+
120+
func (sch *cScheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {
121+
if len(seed) != sch.SeedSize() {
122+
panic(kem.ErrSeedSize)
123+
}
124+
h := xof.SHAKE256.New()
125+
_, _ = h.Write(seed)
126+
buf := make([]byte, sch.PrivateKeySize())
127+
_, _ = h.Read(buf)
128+
rnd := bytes.NewReader(buf)
129+
key, x, y, err := elliptic.GenerateKey(sch.curve, rnd)
130+
if err != nil {
131+
panic(err)
132+
}
133+
134+
sk := cPrivateKey{scheme: sch, key: key}
135+
pk := cPublicKey{scheme: sch, x: x, y: y}
136+
137+
return &pk, &sk
138+
}
139+
140+
func (sch *cScheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
141+
seed := make([]byte, sch.EncapsulationSeedSize())
142+
_, err = cryptoRand.Read(seed)
143+
if err != nil {
144+
return
145+
}
146+
return sch.EncapsulateDeterministically(pk, seed)
147+
}
148+
149+
func (pk *cPublicKey) X(sk *cPrivateKey) []byte {
150+
if pk.scheme != sk.scheme {
151+
panic(kem.ErrTypeMismatch)
152+
}
153+
154+
sharedKey := make([]byte, pk.scheme.SharedKeySize())
155+
xShared, _ := pk.scheme.curve.ScalarMult(pk.x, pk.y, sk.key)
156+
xShared.FillBytes(sharedKey)
157+
return sharedKey
158+
}
159+
160+
func (sch *cScheme) EncapsulateDeterministically(
161+
pk kem.PublicKey, seed []byte,
162+
) (ct, ss []byte, err error) {
163+
if len(seed) != sch.EncapsulationSeedSize() {
164+
return nil, nil, kem.ErrSeedSize
165+
}
166+
pub, ok := pk.(*cPublicKey)
167+
if !ok || pub.scheme != sch {
168+
return nil, nil, kem.ErrTypeMismatch
169+
}
170+
171+
pk2, sk2 := sch.DeriveKeyPair(seed)
172+
ss = pub.X(sk2.(*cPrivateKey))
173+
ct, _ = pk2.MarshalBinary()
174+
return
175+
}
176+
177+
func (sch *cScheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
178+
if len(ct) != sch.CiphertextSize() {
179+
return nil, kem.ErrCiphertextSize
180+
}
181+
182+
priv, ok := sk.(*cPrivateKey)
183+
if !ok || priv.scheme != sch {
184+
return nil, kem.ErrTypeMismatch
185+
}
186+
187+
pk, err := sch.UnmarshalBinaryPublicKey(ct)
188+
if err != nil {
189+
return nil, err
190+
}
191+
192+
ss := pk.(*cPublicKey).X(priv)
193+
return ss, nil
194+
}
195+
196+
func (sch *cScheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) {
197+
if len(buf) != sch.PublicKeySize() {
198+
return nil, kem.ErrPubKeySize
199+
}
200+
x, y := elliptic.Unmarshal(sch.curve, buf)
201+
return &cPublicKey{sch, x, y}, nil
202+
}
203+
204+
func (sch *cScheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) {
205+
if len(buf) != sch.PrivateKeySize() {
206+
return nil, kem.ErrPrivKeySize
207+
}
208+
ret := cPrivateKey{sch, make([]byte, sch.PrivateKeySize())}
209+
copy(ret.key, buf)
210+
return &ret, nil
211+
}

kem/hybrid/hybrid.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,27 @@ import (
4242

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

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

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

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

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

57+
// Returns the hybrid KEM of Kyber768Draft00 and P-256.
58+
func P256Kyber768Draft00() kem.Scheme { return p256Kyber768Draft00 }
59+
60+
var p256Kyber768Draft00 kem.Scheme = &scheme{
61+
"P256Kyber768Draft00",
62+
p256Kem,
63+
kyber768.Scheme(),
64+
}
65+
5766
var kyber512X kem.Scheme = &scheme{
5867
"Kyber512-X25519",
5968
x25519Kem,

kem/schemes/schemes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var allSchemes = [...]kem.Scheme{
4242
hybrid.Kyber768X25519(),
4343
hybrid.Kyber768X448(),
4444
hybrid.Kyber1024X448(),
45+
hybrid.P256Kyber768Draft00(),
4546
}
4647

4748
var allSchemeNames map[string]kem.Scheme

kem/schemes/schemes_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,5 @@ func Example_schemes() {
159159
// Kyber768-X25519
160160
// Kyber768-X448
161161
// Kyber1024-X448
162+
// P256Kyber768Draft00
162163
}

0 commit comments

Comments
 (0)