-
Notifications
You must be signed in to change notification settings - Fork 151
/
Copy pathxwing.go
310 lines (265 loc) · 7.04 KB
/
xwing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// Package xwing implements the X-Wing PQ/T hybrid KEM
//
// https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem
//
// Implements the final version (-05).
package xwing
import (
cryptoRand "crypto/rand"
"errors"
"io"
"github.com/cloudflare/circl/dh/x25519"
"github.com/cloudflare/circl/internal/sha3"
"github.com/cloudflare/circl/kem"
"github.com/cloudflare/circl/kem/mlkem/mlkem768"
)
// An X-Wing private key.
type PrivateKey struct {
seed [32]byte
m mlkem768.PrivateKey
x x25519.Key
xpk x25519.Key
}
// An X-Wing public key.
type PublicKey struct {
m mlkem768.PublicKey
x x25519.Key
}
const (
// Size of a seed of a keypair
SeedSize = 32
// Size of an X-Wing public key
PublicKeySize = 1216
// Size of an X-Wing private key
PrivateKeySize = 32
// Size of the seed passed to EncapsulateTo
EncapsulationSeedSize = 64
// Size of the established shared key
SharedKeySize = 32
// Size of an X-Wing ciphertext.
CiphertextSize = 1120
)
func combiner(
out []byte,
ssm *[mlkem768.SharedKeySize]byte,
ssx *x25519.Key,
ctx *x25519.Key,
pkx *x25519.Key,
) {
h := sha3.New256()
_, _ = h.Write(ssm[:])
_, _ = h.Write(ssx[:])
_, _ = h.Write(ctx[:])
_, _ = h.Write(pkx[:])
// \./
// /^\
_, _ = h.Write([]byte(`\.//^\`))
_, _ = h.Read(out[:])
}
// Packs sk to buf.
//
// Panics if buf is not of size PrivateKeySize
func (sk *PrivateKey) Pack(buf []byte) {
if len(buf) != PrivateKeySize {
panic(kem.ErrPrivKeySize)
}
copy(buf, sk.seed[:])
}
// Packs pk to buf.
//
// Panics if buf is not of size PublicKeySize.
func (pk *PublicKey) Pack(buf []byte) {
if len(buf) != PublicKeySize {
panic(kem.ErrPubKeySize)
}
pk.m.Pack(buf[:mlkem768.PublicKeySize])
copy(buf[mlkem768.PublicKeySize:], pk.x[:])
}
// DeriveKeyPair derives a public/private keypair deterministically
// from the given seed.
//
// Panics if seed is not of length SeedSize.
func DeriveKeyPair(seed []byte) (*PrivateKey, *PublicKey) {
var (
sk PrivateKey
pk PublicKey
)
deriveKeyPair(seed, &sk, &pk)
return &sk, &pk
}
func deriveKeyPair(seed []byte, sk *PrivateKey, pk *PublicKey) {
if len(seed) != SeedSize {
panic(kem.ErrSeedSize)
}
var seedm [mlkem768.KeySeedSize]byte
copy(sk.seed[:], seed)
h := sha3.NewShake256()
_, _ = h.Write(seed)
_, _ = h.Read(seedm[:])
_, _ = h.Read(sk.x[:])
pkm, skm := mlkem768.NewKeyFromSeed(seedm[:])
sk.m = *skm
pk.m = *pkm
x25519.KeyGen(&pk.x, &sk.x)
sk.xpk = pk.x
}
// DeriveKeyPairPacked derives a keypair like DeriveKeyPair, and
// returns them packed.
func DeriveKeyPairPacked(seed []byte) ([]byte, []byte) {
sk, pk := DeriveKeyPair(seed)
var (
ppk [PublicKeySize]byte
psk [PrivateKeySize]byte
)
pk.Pack(ppk[:])
sk.Pack(psk[:])
return psk[:], ppk[:]
}
// GenerateKeyPair generates public and private keys using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKeyPair(rand io.Reader) (*PrivateKey, *PublicKey, error) {
var seed [SeedSize]byte
if rand == nil {
rand = cryptoRand.Reader
}
_, err := io.ReadFull(rand, seed[:])
if err != nil {
return nil, nil, err
}
sk, pk := DeriveKeyPair(seed[:])
return sk, pk, nil
}
// GenerateKeyPairPacked generates a keypair like GenerateKeyPair, and
// returns them packed.
func GenerateKeyPairPacked(rand io.Reader) ([]byte, []byte, error) {
sk, pk, err := GenerateKeyPair(rand)
if err != nil {
return nil, nil, err
}
var (
ppk [PublicKeySize]byte
psk [PrivateKeySize]byte
)
pk.Pack(ppk[:])
sk.Pack(psk[:])
return psk[:], ppk[:], nil
}
// Encapsulate generates a shared key and ciphertext that contains it
// for the public key pk using randomness from seed.
//
// seed may be nil, in which case crypto/rand.Reader is used.
//
// Warning: note that the order of the returned ss and ct matches the
// X-Wing standard, which is the reverse of the Circl KEM API.
//
// Returns ErrPubKey if ML-KEM encapsulation key check fails.
//
// Panics if pk is not of size PublicKeySize, or randomness could not
// be read from crypto/rand.Reader.
func Encapsulate(pk, seed []byte) (ss, ct []byte, err error) {
var pub PublicKey
if err := pub.Unpack(pk); err != nil {
return nil, nil, err
}
ct = make([]byte, CiphertextSize)
ss = make([]byte, SharedKeySize)
pub.EncapsulateTo(ct, ss, seed)
return ss, ct, nil
}
// Decapsulate computes the shared key which is encapsulated in ct
// for the private key sk.
//
// Panics if sk or ct are not of length PrivateKeySize and CiphertextSize
// respectively.
func Decapsulate(ct, sk []byte) (ss []byte) {
var priv PrivateKey
priv.Unpack(sk)
ss = make([]byte, SharedKeySize)
priv.DecapsulateTo(ss, ct)
return ss
}
// Raised when passing a byte slice of the wrong size for the shared
// secret to the EncapsulateTo or DecapsulateTo functions.
var ErrSharedKeySize = errors.New("wrong size for shared key")
// EncapsulateTo generates a shared key and ciphertext that contains it
// for the public key using randomness from seed and writes the shared key
// to ss and ciphertext to ct.
//
// Panics if ss, ct or seed are not of length SharedKeySize, CiphertextSize
// and EncapsulationSeedSize respectively.
//
// seed may be nil, in which case crypto/rand.Reader is used to generate one.
func (pk *PublicKey) EncapsulateTo(ct, ss, seed []byte) {
if seed == nil {
seed = make([]byte, EncapsulationSeedSize)
if _, err := cryptoRand.Read(seed[:]); err != nil {
panic(err)
}
} else {
if len(seed) != EncapsulationSeedSize {
panic(kem.ErrSeedSize)
}
}
if len(ct) != CiphertextSize {
panic(kem.ErrCiphertextSize)
}
if len(ss) != SharedKeySize {
panic(ErrSharedKeySize)
}
var (
seedm [32]byte
ekx x25519.Key
ctx x25519.Key
ssx x25519.Key
ssm [mlkem768.SharedKeySize]byte
)
copy(seedm[:], seed[:32])
copy(ekx[:], seed[32:])
x25519.KeyGen(&ctx, &ekx)
x25519.Shared(&ssx, &ekx, &pk.x)
pk.m.EncapsulateTo(ct[:mlkem768.CiphertextSize], ssm[:], seedm[:])
combiner(ss, &ssm, &ssx, &ctx, &pk.x)
copy(ct[mlkem768.CiphertextSize:], ctx[:])
}
// DecapsulateTo computes the shared key which is encapsulated in ct
// for the private key.
//
// Panics if ct or ss are not of length CiphertextSize and SharedKeySize
// respectively.
func (sk *PrivateKey) DecapsulateTo(ss, ct []byte) {
if len(ct) != CiphertextSize {
panic(kem.ErrCiphertextSize)
}
if len(ss) != SharedKeySize {
panic(ErrSharedKeySize)
}
ctm := ct[:mlkem768.CiphertextSize]
var (
ssm [mlkem768.SharedKeySize]byte
ssx x25519.Key
ctx x25519.Key
)
copy(ctx[:], ct[mlkem768.CiphertextSize:])
sk.m.DecapsulateTo(ssm[:], ctm)
x25519.Shared(&ssx, &sk.x, &ctx)
combiner(ss, &ssm, &ssx, &ctx, &sk.xpk)
}
// Unpacks pk from buf.
//
// Panics if buf is not of size PublicKeySize.
//
// Returns ErrPubKey if pk fails the ML-KEM encapsulation key check.
func (pk *PublicKey) Unpack(buf []byte) error {
if len(buf) != PublicKeySize {
panic(kem.ErrPubKeySize)
}
copy(pk.x[:], buf[mlkem768.PublicKeySize:])
return pk.m.Unpack(buf[:mlkem768.PublicKeySize])
}
// Unpacks sk from buf.
//
// Panics if buf is not of size PrivateKeySize.
func (sk *PrivateKey) Unpack(buf []byte) {
var pk PublicKey
deriveKeyPair(buf, sk, &pk)
}