@@ -8,8 +8,10 @@ import (
8
8
"crypto/cipher"
9
9
"crypto/ecdh"
10
10
"crypto/hkdf"
11
+ "crypto/mlkem"
11
12
"crypto/rand"
12
13
"crypto/sha256"
14
+ "crypto/sha3"
13
15
"encoding/binary"
14
16
"errors"
15
17
"hash"
@@ -131,6 +133,135 @@ func (dh *dhkemRecipient) Decap(encPubEph []byte) ([]byte, error) {
131
133
return dh .extractAndExpand (dhVal , kemContext )
132
134
}
133
135
136
+ type qsf struct {
137
+ id uint16
138
+ label string
139
+ }
140
+
141
+ func (q * qsf ) ID () uint16 {
142
+ return q .id
143
+ }
144
+
145
+ func (q * qsf ) sharedSecret (ssPQ , ssT , ctT , ekT []byte ) []byte {
146
+ h := sha3 .New256 ()
147
+ h .Write (ssPQ )
148
+ h .Write (ssT )
149
+ h .Write (ctT )
150
+ h .Write (ekT )
151
+ h .Write ([]byte (q .label ))
152
+ return h .Sum (nil )
153
+ }
154
+
155
+ type qsfSender struct {
156
+ qsf
157
+ t * ecdh.PublicKey
158
+ pq * mlkem.EncapsulationKey768
159
+ }
160
+
161
+ // QSFSender returns a KEMSender implementing QSF-P256-MLKEM768-SHAKE256-SHA3256
162
+ // or QSF-X25519-MLKEM768-SHA3256-SHAKE256 (aka X-Wing) from draft-ietf-hpke-pq
163
+ // and draft-irtf-cfrg-concrete-hybrid-kems-00.
164
+ func QSFSender (t * ecdh.PublicKey , pq * mlkem.EncapsulationKey768 ) (KEMSender , error ) {
165
+ switch t .Curve () {
166
+ case ecdh .P256 ():
167
+ return & qsfSender {
168
+ t : t , pq : pq ,
169
+ qsf : qsf {
170
+ id : 0x0050 ,
171
+ label : "QSF-P256-MLKEM768-SHAKE256-SHA3256" ,
172
+ },
173
+ }, nil
174
+ case ecdh .X25519 ():
175
+ return & qsfSender {
176
+ t : t , pq : pq ,
177
+ qsf : qsf {
178
+ id : 0x647a ,
179
+ label : /**/ `\./` +
180
+ /* */ `/^\` ,
181
+ },
182
+ }, nil
183
+ default :
184
+ return nil , errors .New ("unsupported curve" )
185
+ }
186
+ }
187
+
188
+ var testingOnlyEncapsulate func () (ss , ct []byte )
189
+
190
+ func (s * qsfSender ) Encap () (sharedSecret []byte , encapPub []byte , err error ) {
191
+ skE , err := s .t .Curve ().GenerateKey (rand .Reader )
192
+ if err != nil {
193
+ return nil , nil , err
194
+ }
195
+ if testingOnlyGenerateKey != nil {
196
+ skE = testingOnlyGenerateKey ()
197
+ }
198
+ ssT , err := skE .ECDH (s .t )
199
+ if err != nil {
200
+ return nil , nil , err
201
+ }
202
+ ctT := skE .PublicKey ().Bytes ()
203
+
204
+ ssPQ , ctPQ := s .pq .Encapsulate ()
205
+ if testingOnlyEncapsulate != nil {
206
+ ssPQ , ctPQ = testingOnlyEncapsulate ()
207
+ }
208
+
209
+ ss := s .sharedSecret (ssPQ , ssT , ctT , s .t .Bytes ())
210
+ ct := append (ctPQ , ctT ... )
211
+ return ss , ct , nil
212
+ }
213
+
214
+ type qsfRecipient struct {
215
+ qsf
216
+ t * ecdh.PrivateKey
217
+ pq * mlkem.DecapsulationKey768
218
+ }
219
+
220
+ // QSFRecipient returns a KEMRecipient implementing QSF-P256-MLKEM768-SHAKE256-SHA3256
221
+ // or QSF-MLKEM768-X25519-SHA3256-SHAKE256 (aka X-Wing) from draft-ietf-hpke-pq
222
+ // and draft-irtf-cfrg-concrete-hybrid-kems-00.
223
+ func QSFRecipient (t * ecdh.PrivateKey , pq * mlkem.DecapsulationKey768 ) (KEMRecipient , error ) {
224
+ switch t .Curve () {
225
+ case ecdh .P256 ():
226
+ return & qsfRecipient {
227
+ t : t , pq : pq ,
228
+ qsf : qsf {
229
+ id : 0x0050 ,
230
+ label : "QSF-P256-MLKEM768-SHAKE256-SHA3256" ,
231
+ },
232
+ }, nil
233
+ case ecdh .X25519 ():
234
+ return & qsfRecipient {
235
+ t : t , pq : pq ,
236
+ qsf : qsf {
237
+ id : 0x647a ,
238
+ label : /**/ `\./` +
239
+ /* */ `/^\` ,
240
+ },
241
+ }, nil
242
+ default :
243
+ return nil , errors .New ("unsupported curve" )
244
+ }
245
+ }
246
+
247
+ func (r * qsfRecipient ) Decap (enc []byte ) ([]byte , error ) {
248
+ ctPQ , ctT := enc [:mlkem .CiphertextSize768 ], enc [mlkem .CiphertextSize768 :]
249
+ ssPQ , err := r .pq .Decapsulate (ctPQ )
250
+ if err != nil {
251
+ return nil , err
252
+ }
253
+ pub , err := r .t .Curve ().NewPublicKey (ctT )
254
+ if err != nil {
255
+ return nil , err
256
+ }
257
+ ssT , err := r .t .ECDH (pub )
258
+ if err != nil {
259
+ return nil , err
260
+ }
261
+ ss := r .sharedSecret (ssPQ , ssT , ctT , r .t .PublicKey ().Bytes ())
262
+ return ss , nil
263
+ }
264
+
134
265
type KDF interface {
135
266
LabeledExtract (sid , salt []byte , label string , inputKey []byte ) ([]byte , error )
136
267
LabeledExpand (suiteID , randomKey []byte , label string , info []byte , length uint16 ) ([]byte , error )
0 commit comments