blob: dcd6cfc469f16509b1bfe5b20a4b06452a460e31 [file] [log] [blame]
Pete Bentley0c61efe2019-08-13 09:32:23 +01001// Copyright (c) 2019, Cloudflare Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15package sike
16
17import (
18 "crypto/sha256"
19 "crypto/subtle"
20 "errors"
21 "io"
22)
23
24// Zeroize Fp2
25func zeroize(fp *Fp2) {
26 // Zeroizing in 2 separated loops tells compiler to
27 // use fast runtime.memclr()
28 for i := range fp.A {
29 fp.A[i] = 0
30 }
31 for i := range fp.B {
32 fp.B[i] = 0
33 }
34}
35
36// Convert the input to wire format.
37//
38// The output byte slice must be at least 2*bytelen(p) bytes long.
39func convFp2ToBytes(output []byte, fp2 *Fp2) {
40 if len(output) < 2*Params.Bytelen {
41 panic("output byte slice too short")
42 }
43 var a Fp2
44 fromMontDomain(fp2, &a)
45
46 // convert to bytes in little endian form
47 for i := 0; i < Params.Bytelen; i++ {
48 // set i = j*8 + k
49 tmp := i / 8
50 k := uint64(i % 8)
51 output[i] = byte(a.A[tmp] >> (8 * k))
52 output[i+Params.Bytelen] = byte(a.B[tmp] >> (8 * k))
53 }
54}
55
56// Read 2*bytelen(p) bytes into the given ExtensionFieldElement.
57//
58// It is an error to call this function if the input byte slice is less than 2*bytelen(p) bytes long.
59func convBytesToFp2(fp2 *Fp2, input []byte) {
60 if len(input) < 2*Params.Bytelen {
61 panic("input byte slice too short")
62 }
63
64 for i := 0; i < Params.Bytelen; i++ {
65 j := i / 8
66 k := uint64(i % 8)
67 fp2.A[j] |= uint64(input[i]) << (8 * k)
68 fp2.B[j] |= uint64(input[i+Params.Bytelen]) << (8 * k)
69 }
70 toMontDomain(fp2)
71}
72
73// -----------------------------------------------------------------------------
74// Functions for traversing isogeny trees acoording to strategy. Key type 'A' is
75//
76
77// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
78// for public key generation.
79func traverseTreePublicKeyA(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
80 var points = make([]ProjectivePoint, 0, 8)
81 var indices = make([]int, 0, 8)
82 var i, sidx int
83
84 cparam := CalcCurveParamsEquiv4(curve)
85 phi := NewIsogeny4()
86 strat := pub.params.A.IsogenyStrategy
87 stratSz := len(strat)
88
89 for j := 1; j <= stratSz; j++ {
90 for i <= stratSz-j {
91 points = append(points, *xR)
92 indices = append(indices, i)
93
94 k := strat[sidx]
95 sidx++
96 Pow2k(xR, &cparam, 2*k)
97 i += int(k)
98 }
99
100 cparam = phi.GenerateCurve(xR)
101 for k := 0; k < len(points); k++ {
102 points[k] = phi.EvaluatePoint(&points[k])
103 }
104
105 *phiP = phi.EvaluatePoint(phiP)
106 *phiQ = phi.EvaluatePoint(phiQ)
107 *phiR = phi.EvaluatePoint(phiR)
108
109 // pop xR from points
110 *xR, points = points[len(points)-1], points[:len(points)-1]
111 i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
112 }
113}
114
115// Traverses isogeny tree in order to compute xR needed
116// for public key generation.
117func traverseTreeSharedKeyA(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
118 var points = make([]ProjectivePoint, 0, 8)
119 var indices = make([]int, 0, 8)
120 var i, sidx int
121
122 cparam := CalcCurveParamsEquiv4(curve)
123 phi := NewIsogeny4()
124 strat := pub.params.A.IsogenyStrategy
125 stratSz := len(strat)
126
127 for j := 1; j <= stratSz; j++ {
128 for i <= stratSz-j {
129 points = append(points, *xR)
130 indices = append(indices, i)
131
132 k := strat[sidx]
133 sidx++
134 Pow2k(xR, &cparam, 2*k)
135 i += int(k)
136 }
137
138 cparam = phi.GenerateCurve(xR)
139 for k := 0; k < len(points); k++ {
140 points[k] = phi.EvaluatePoint(&points[k])
141 }
142
143 // pop xR from points
144 *xR, points = points[len(points)-1], points[:len(points)-1]
145 i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
146 }
147}
148
149// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
150// for public key generation.
151func traverseTreePublicKeyB(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
152 var points = make([]ProjectivePoint, 0, 8)
153 var indices = make([]int, 0, 8)
154 var i, sidx int
155
156 cparam := CalcCurveParamsEquiv3(curve)
157 phi := NewIsogeny3()
158 strat := pub.params.B.IsogenyStrategy
159 stratSz := len(strat)
160
161 for j := 1; j <= stratSz; j++ {
162 for i <= stratSz-j {
163 points = append(points, *xR)
164 indices = append(indices, i)
165
166 k := strat[sidx]
167 sidx++
168 Pow3k(xR, &cparam, k)
169 i += int(k)
170 }
171
172 cparam = phi.GenerateCurve(xR)
173 for k := 0; k < len(points); k++ {
174 points[k] = phi.EvaluatePoint(&points[k])
175 }
176
177 *phiP = phi.EvaluatePoint(phiP)
178 *phiQ = phi.EvaluatePoint(phiQ)
179 *phiR = phi.EvaluatePoint(phiR)
180
181 // pop xR from points
182 *xR, points = points[len(points)-1], points[:len(points)-1]
183 i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
184 }
185}
186
187// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
188// for public key generation.
189func traverseTreeSharedKeyB(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
190 var points = make([]ProjectivePoint, 0, 8)
191 var indices = make([]int, 0, 8)
192 var i, sidx int
193
194 cparam := CalcCurveParamsEquiv3(curve)
195 phi := NewIsogeny3()
196 strat := pub.params.B.IsogenyStrategy
197 stratSz := len(strat)
198
199 for j := 1; j <= stratSz; j++ {
200 for i <= stratSz-j {
201 points = append(points, *xR)
202 indices = append(indices, i)
203
204 k := strat[sidx]
205 sidx++
206 Pow3k(xR, &cparam, k)
207 i += int(k)
208 }
209
210 cparam = phi.GenerateCurve(xR)
211 for k := 0; k < len(points); k++ {
212 points[k] = phi.EvaluatePoint(&points[k])
213 }
214
215 // pop xR from points
216 *xR, points = points[len(points)-1], points[:len(points)-1]
217 i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
218 }
219}
220
221// Generate a public key in the 2-torsion group
222func publicKeyGenA(prv *PrivateKey) (pub *PublicKey) {
223 var xPA, xQA, xRA ProjectivePoint
224 var xPB, xQB, xRB, xK ProjectivePoint
225 var invZP, invZQ, invZR Fp2
226
227 pub = NewPublicKey(KeyVariant_SIDH_A)
228 var phi = NewIsogeny4()
229
230 // Load points for A
231 xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
232 xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
233 xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
234
235 // Load points for B
236 xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
237 xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
238 xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
239
240 // Find isogeny kernel
241 xK = ScalarMul3Pt(&pub.params.InitCurve, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar)
242 traverseTreePublicKeyA(&pub.params.InitCurve, &xK, &xPB, &xQB, &xRB, pub)
243
244 // Secret isogeny
245 phi.GenerateCurve(&xK)
246 xPA = phi.EvaluatePoint(&xPB)
247 xQA = phi.EvaluatePoint(&xQB)
248 xRA = phi.EvaluatePoint(&xRB)
249 Fp2Batch3Inv(&xPA.Z, &xQA.Z, &xRA.Z, &invZP, &invZQ, &invZR)
250
251 mul(&pub.affine_xP, &xPA.X, &invZP)
252 mul(&pub.affine_xQ, &xQA.X, &invZQ)
253 mul(&pub.affine_xQmP, &xRA.X, &invZR)
254 return
255}
256
257// Generate a public key in the 3-torsion group
258func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) {
259 var xPB, xQB, xRB, xK ProjectivePoint
260 var xPA, xQA, xRA ProjectivePoint
261 var invZP, invZQ, invZR Fp2
262
263 pub = NewPublicKey(prv.keyVariant)
264 var phi = NewIsogeny3()
265
266 // Load points for B
267 xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
268 xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
269 xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
270
271 // Load points for A
272 xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
273 xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
274 xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
275
276 xK = ScalarMul3Pt(&pub.params.InitCurve, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar)
277 traverseTreePublicKeyB(&pub.params.InitCurve, &xK, &xPA, &xQA, &xRA, pub)
278
279 phi.GenerateCurve(&xK)
280 xPB = phi.EvaluatePoint(&xPA)
281 xQB = phi.EvaluatePoint(&xQA)
282 xRB = phi.EvaluatePoint(&xRA)
283 Fp2Batch3Inv(&xPB.Z, &xQB.Z, &xRB.Z, &invZP, &invZQ, &invZR)
284
285 mul(&pub.affine_xP, &xPB.X, &invZP)
286 mul(&pub.affine_xQ, &xQB.X, &invZQ)
287 mul(&pub.affine_xQmP, &xRB.X, &invZR)
288 return
289}
290
291// -----------------------------------------------------------------------------
292// Key agreement functions
293//
294
295// Establishing shared keys in in 2-torsion group
296func deriveSecretA(prv *PrivateKey, pub *PublicKey) []byte {
297 var sharedSecret = make([]byte, pub.params.SharedSecretSize)
298 var xP, xQ, xQmP ProjectivePoint
299 var xK ProjectivePoint
300 var cparam ProjectiveCurveParameters
301 var phi = NewIsogeny4()
302 var jInv Fp2
303
304 // Recover curve coefficients
305 RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
306 // C=1
307 cparam.C = Params.OneFp2
308
309 // Find kernel of the morphism
310 xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
311 xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
312 xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
313 xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar)
314
315 // Traverse isogeny tree
316 traverseTreeSharedKeyA(&cparam, &xK, pub)
317
318 // Calculate j-invariant on isogeneus curve
319 c := phi.GenerateCurve(&xK)
320 RecoverCurveCoefficients4(&cparam, &c)
321 Jinvariant(&cparam, &jInv)
322 convFp2ToBytes(sharedSecret, &jInv)
323 return sharedSecret
324}
325
326// Establishing shared keys in in 3-torsion group
327func deriveSecretB(prv *PrivateKey, pub *PublicKey) []byte {
328 var sharedSecret = make([]byte, pub.params.SharedSecretSize)
329 var xP, xQ, xQmP ProjectivePoint
330 var xK ProjectivePoint
331 var cparam ProjectiveCurveParameters
332 var phi = NewIsogeny3()
333 var jInv Fp2
334
335 // Recover curve A coefficient
336 RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
337 // C=1
338 cparam.C = Params.OneFp2
339
340 // Find kernel of the morphism
341 xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
342 xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
343 xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
344 xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar)
345
346 // Traverse isogeny tree
347 traverseTreeSharedKeyB(&cparam, &xK, pub)
348
349 // Calculate j-invariant on isogeneus curve
350 c := phi.GenerateCurve(&xK)
351 RecoverCurveCoefficients3(&cparam, &c)
352 Jinvariant(&cparam, &jInv)
353 convFp2ToBytes(sharedSecret, &jInv)
354 return sharedSecret
355}
356
357func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) {
358 if pkB.keyVariant != KeyVariant_SIKE {
359 return nil, errors.New("wrong key type")
360 }
361
362 j, err := DeriveSecret(skA, pkB)
363 if err != nil {
364 return nil, err
365 }
366
367 if len(ptext) != pkA.params.KemSize {
368 panic("Implementation error")
369 }
370
371 digest := sha256.Sum256(j)
372 // Uses truncated digest (first 16-bytes)
373 for i, _ := range ptext {
374 digest[i] ^= ptext[i]
375 }
376
377 ret := make([]byte, pkA.Size()+len(ptext))
378 copy(ret, pkA.Export())
379 copy(ret[pkA.Size():], digest[:pkA.params.KemSize])
380 return ret, nil
381}
382
383// NewPrivateKey initializes private key.
384// Usage of this function guarantees that the object is correctly initialized.
385func NewPrivateKey(v KeyVariant) *PrivateKey {
386 prv := &PrivateKey{key: key{params: &Params, keyVariant: v}}
387 if (v & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
388 prv.Scalar = make([]byte, prv.params.A.SecretByteLen)
389 } else {
390 prv.Scalar = make([]byte, prv.params.B.SecretByteLen)
391 }
392 if v == KeyVariant_SIKE {
393 prv.S = make([]byte, prv.params.MsgLen)
394 }
395 return prv
396}
397
398// NewPublicKey initializes public key.
399// Usage of this function guarantees that the object is correctly initialized.
400func NewPublicKey(v KeyVariant) *PublicKey {
401 return &PublicKey{key: key{params: &Params, keyVariant: v}}
402}
403
404// Import clears content of the public key currently stored in the structure
405// and imports key stored in the byte string. Returns error in case byte string
406// size is wrong. Doesn't perform any validation.
407func (pub *PublicKey) Import(input []byte) error {
408 if len(input) != pub.Size() {
409 return errors.New("sidh: input to short")
410 }
411 ssSz := pub.params.SharedSecretSize
412 convBytesToFp2(&pub.affine_xP, input[0:ssSz])
413 convBytesToFp2(&pub.affine_xQ, input[ssSz:2*ssSz])
414 convBytesToFp2(&pub.affine_xQmP, input[2*ssSz:3*ssSz])
415 return nil
416}
417
418// Exports currently stored key. In case structure hasn't been filled with key data
419// returned byte string is filled with zeros.
420func (pub *PublicKey) Export() []byte {
421 output := make([]byte, pub.params.PublicKeySize)
422 ssSz := pub.params.SharedSecretSize
423 convFp2ToBytes(output[0:ssSz], &pub.affine_xP)
424 convFp2ToBytes(output[ssSz:2*ssSz], &pub.affine_xQ)
425 convFp2ToBytes(output[2*ssSz:3*ssSz], &pub.affine_xQmP)
426 return output
427}
428
429// Size returns size of the public key in bytes
430func (pub *PublicKey) Size() int {
431 return pub.params.PublicKeySize
432}
433
434// Exports currently stored key. In case structure hasn't been filled with key data
435// returned byte string is filled with zeros.
436func (prv *PrivateKey) Export() []byte {
437 ret := make([]byte, len(prv.Scalar)+len(prv.S))
438 copy(ret, prv.S)
439 copy(ret[len(prv.S):], prv.Scalar)
440 return ret
441}
442
443// Size returns size of the private key in bytes
444func (prv *PrivateKey) Size() int {
445 tmp := len(prv.Scalar)
446 if prv.keyVariant == KeyVariant_SIKE {
447 tmp += int(prv.params.MsgLen)
448 }
449 return tmp
450}
451
452// Import clears content of the private key currently stored in the structure
453// and imports key from octet string. In case of SIKE, the random value 'S'
454// must be prepended to the value of actual private key (see SIKE spec for details).
455// Function doesn't import public key value to PrivateKey object.
456func (prv *PrivateKey) Import(input []byte) error {
457 if len(input) != prv.Size() {
458 return errors.New("sidh: input to short")
459 }
460 copy(prv.S, input[:len(prv.S)])
461 copy(prv.Scalar, input[len(prv.S):])
462 return nil
463}
464
465// Generates random private key for SIDH or SIKE. Generated value is
466// formed as little-endian integer from key-space <2^(e2-1)..2^e2 - 1>
467// for KeyVariant_A or <2^(s-1)..2^s - 1>, where s = floor(log_2(3^e3)),
468// for KeyVariant_B.
469//
470// Returns error in case user provided RNG fails.
471func (prv *PrivateKey) Generate(rand io.Reader) error {
472 var err error
473 var dp *DomainParams
474
475 if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
476 dp = &prv.params.A
477 } else {
478 dp = &prv.params.B
479 }
480
481 if prv.keyVariant == KeyVariant_SIKE {
482 _, err = io.ReadFull(rand, prv.S)
483 }
484
485 // Private key generation takes advantage of the fact that keyspace for secret
486 // key is (0, 2^x - 1), for some possitivite value of 'x' (see SIKE, 1.3.8).
487 // It means that all bytes in the secret key, but the last one, can take any
488 // value between <0x00,0xFF>. Similarily for the last byte, but generation
489 // needs to chop off some bits, to make sure generated value is an element of
490 // a key-space.
491 _, err = io.ReadFull(rand, prv.Scalar)
492 if err != nil {
493 return err
494 }
495 prv.Scalar[len(prv.Scalar)-1] &= (1 << (dp.SecretBitLen % 8)) - 1
496 // Make sure scalar is SecretBitLen long. SIKE spec says that key
497 // space starts from 0, but I'm not confortable with having low
498 // value scalars used for private keys. It is still secrure as per
499 // table 5.1 in [SIKE].
500 prv.Scalar[len(prv.Scalar)-1] |= 1 << ((dp.SecretBitLen % 8) - 1)
501 return err
502}
503
504// Generates public key.
505//
506// Constant time.
507func (prv *PrivateKey) GeneratePublicKey() *PublicKey {
508 if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
509 return publicKeyGenA(prv)
510 }
511 return publicKeyGenB(prv)
512}
513
514// Computes a shared secret which is a j-invariant. Function requires that pub has
515// different KeyVariant than prv. Length of returned output is 2*ceil(log_2 P)/8),
516// where P is a prime defining finite field.
517//
518// It's important to notice that each keypair must not be used more than once
519// to calculate shared secret.
520//
521// Function may return error. This happens only in case provided input is invalid.
522// Constant time for properly initialized private and public key.
523func DeriveSecret(prv *PrivateKey, pub *PublicKey) ([]byte, error) {
524
525 if (pub == nil) || (prv == nil) {
526 return nil, errors.New("sidh: invalid arguments")
527 }
528
529 if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) {
530 return nil, errors.New("sidh: public and private are incompatbile")
531 }
532
533 if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
534 return deriveSecretA(prv, pub), nil
535 } else {
536 return deriveSecretB(prv, pub), nil
537 }
538}
539
540// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG
541// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails
542// or wrongly formatted input was provided.
543func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) {
544 var ptextLen = len(ptext)
545 // c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
546 if ptextLen != pub.params.KemSize {
547 return nil, errors.New("Unsupported message length")
548 }
549
550 skA := NewPrivateKey(KeyVariant_SIDH_A)
551 err := skA.Generate(rng)
552 if err != nil {
553 return nil, err
554 }
555
556 pkA := skA.GeneratePublicKey()
557 return encrypt(skA, pkA, pub, ptext)
558}
559
560// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case
561// decryption succeeds or error in case unexptected input was provided.
562// Constant time
563func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) {
564 var c1_len int
565 n := make([]byte, prv.params.KemSize)
566 pk_len := prv.params.PublicKeySize
567
568 if prv.keyVariant != KeyVariant_SIKE {
569 return nil, errors.New("wrong key type")
570 }
571
572 // ctext is a concatenation of (pubkey_A || c1=ciphertext)
573 // it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
574 c1_len = len(ctext) - pk_len
575 if c1_len != int(prv.params.KemSize) {
576 return nil, errors.New("wrong size of cipher text")
577 }
578
579 c0 := NewPublicKey(KeyVariant_SIDH_A)
580 err := c0.Import(ctext[:pk_len])
581 if err != nil {
582 return nil, err
583 }
584 j, err := DeriveSecret(prv, c0)
585 if err != nil {
586 return nil, err
587 }
588
589 digest := sha256.Sum256(j)
590 copy(n, digest[:])
591
592 for i, _ := range n {
593 n[i] ^= ctext[pk_len+i]
594 }
595 return n[:c1_len], nil
596}
597
598// Encapsulation receives the public key and generates SIKE ciphertext and shared secret.
599// The generated ciphertext is used for authentication.
600// The rng must be cryptographically secure PRNG.
601// Error is returned in case PRNG fails or wrongly formatted input was provided.
602func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) {
603 // Buffer for random, secret message
604 ptext := make([]byte, pub.params.MsgLen)
605 // SHA256 hash context object
606 d := sha256.New()
607
608 // Generate ephemeral value
609 _, err = io.ReadFull(rng, ptext)
610 if err != nil {
611 return nil, nil, err
612 }
613
614 // Implementation uses first 28-bytes of secret
615 d.Write(ptext)
616 d.Write(pub.Export())
617 digest := d.Sum(nil)
618 // r = G(ptext||pub)
619 r := digest[:pub.params.A.SecretByteLen]
620
621 // (c0 || c1) = Enc(pkA, ptext; r)
622 skA := NewPrivateKey(KeyVariant_SIDH_A)
623 err = skA.Import(r)
624 if err != nil {
625 return nil, nil, err
626 }
627
628 pkA := skA.GeneratePublicKey()
629 ctext, err = encrypt(skA, pkA, pub, ptext)
630 if err != nil {
631 return nil, nil, err
632 }
633
634 // K = H(ptext||(c0||c1))
635 d.Reset()
636 d.Write(ptext)
637 d.Write(ctext)
638 digest = d.Sum(digest[:0])
639 return ctext, digest[:pub.params.KemSize], nil
640}
641
642// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared
643// secret if plaintext verifies correctly, otherwise function outputs random value.
644// Decapsulation may fail in case input is wrongly formatted.
645// Constant time for properly initialized input.
646func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) {
647 var skA = NewPrivateKey(KeyVariant_SIDH_A)
648 // SHA256 hash context object
649 d := sha256.New()
650
651 m, err := Decrypt(prv, ctext)
652 if err != nil {
653 return nil, err
654 }
655
656 // r' = G(m'||pub)
657 d.Write(m)
658 d.Write(pub.Export())
659 digest := d.Sum(nil)
660 // Never fails
661 skA.Import(digest[:pub.params.A.SecretByteLen])
662
663 // Never fails
664 pkA := skA.GeneratePublicKey()
665 c0 := pkA.Export()
666
667 d.Reset()
668 if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 {
669 d.Write(m)
670 } else {
671 // S is chosen at random when generating a key and is unknown to the other party. It
672 // may seem weird, but it's correct. It is important that S is unpredictable
673 // to other party. Without this check, it is possible to recover a secret, by
674 // providing series of invalid ciphertexts. It is also important that in case
675 //
676 // See more details in "On the security of supersingular isogeny cryptosystems"
677 // (S. Galbraith, et al., 2016, ePrint #859).
678 d.Write(prv.S)
679 }
680 d.Write(ctext)
681 digest = d.Sum(digest[:0])
682 return digest[:pub.params.KemSize], nil
683}