summaryrefslogtreecommitdiff
path: root/src/ssl/test/runner/sike/sike.go
diff options
context:
space:
mode:
authorPete Bentley <prb@google.com>2019-08-09 14:24:27 +0000
committerPete Bentley <prb@google.com>2019-08-09 14:24:27 +0000
commita5c947b7c91bac52eeb5086507b67e52a59ef980 (patch)
tree3725c3e206175c177a448c50d41ad2c2589a07fa /src/ssl/test/runner/sike/sike.go
parent228bd6249d17f351ea66508b3ec3112ed1cbdf30 (diff)
downloadboringssl-a5c947b7c91bac52eeb5086507b67e52a59ef980.tar.gz
Revert "Revert "external/boringssl: Sync to 81080a729af568f7b5fde92b9170cc17065027c9.""
This reverts commit 228bd6249d17f351ea66508b3ec3112ed1cbdf30. Reason for revert: All fixes submitted for modules affected by the ENGINE_free API change. Change-Id: I30fafafa13ec0a6390f4a9211fbf3122a8b4865f
Diffstat (limited to 'src/ssl/test/runner/sike/sike.go')
-rw-r--r--src/ssl/test/runner/sike/sike.go683
1 files changed, 683 insertions, 0 deletions
diff --git a/src/ssl/test/runner/sike/sike.go b/src/ssl/test/runner/sike/sike.go
new file mode 100644
index 00000000..dcd6cfc4
--- /dev/null
+++ b/src/ssl/test/runner/sike/sike.go
@@ -0,0 +1,683 @@
+// Copyright (c) 2019, Cloudflare Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package sike
+
+import (
+ "crypto/sha256"
+ "crypto/subtle"
+ "errors"
+ "io"
+)
+
+// Zeroize Fp2
+func zeroize(fp *Fp2) {
+ // Zeroizing in 2 separated loops tells compiler to
+ // use fast runtime.memclr()
+ for i := range fp.A {
+ fp.A[i] = 0
+ }
+ for i := range fp.B {
+ fp.B[i] = 0
+ }
+}
+
+// Convert the input to wire format.
+//
+// The output byte slice must be at least 2*bytelen(p) bytes long.
+func convFp2ToBytes(output []byte, fp2 *Fp2) {
+ if len(output) < 2*Params.Bytelen {
+ panic("output byte slice too short")
+ }
+ var a Fp2
+ fromMontDomain(fp2, &a)
+
+ // convert to bytes in little endian form
+ for i := 0; i < Params.Bytelen; i++ {
+ // set i = j*8 + k
+ tmp := i / 8
+ k := uint64(i % 8)
+ output[i] = byte(a.A[tmp] >> (8 * k))
+ output[i+Params.Bytelen] = byte(a.B[tmp] >> (8 * k))
+ }
+}
+
+// Read 2*bytelen(p) bytes into the given ExtensionFieldElement.
+//
+// It is an error to call this function if the input byte slice is less than 2*bytelen(p) bytes long.
+func convBytesToFp2(fp2 *Fp2, input []byte) {
+ if len(input) < 2*Params.Bytelen {
+ panic("input byte slice too short")
+ }
+
+ for i := 0; i < Params.Bytelen; i++ {
+ j := i / 8
+ k := uint64(i % 8)
+ fp2.A[j] |= uint64(input[i]) << (8 * k)
+ fp2.B[j] |= uint64(input[i+Params.Bytelen]) << (8 * k)
+ }
+ toMontDomain(fp2)
+}
+
+// -----------------------------------------------------------------------------
+// Functions for traversing isogeny trees acoording to strategy. Key type 'A' is
+//
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreePublicKeyA(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
+ var points = make([]ProjectivePoint, 0, 8)
+ var indices = make([]int, 0, 8)
+ var i, sidx int
+
+ cparam := CalcCurveParamsEquiv4(curve)
+ phi := NewIsogeny4()
+ strat := pub.params.A.IsogenyStrategy
+ stratSz := len(strat)
+
+ for j := 1; j <= stratSz; j++ {
+ for i <= stratSz-j {
+ points = append(points, *xR)
+ indices = append(indices, i)
+
+ k := strat[sidx]
+ sidx++
+ Pow2k(xR, &cparam, 2*k)
+ i += int(k)
+ }
+
+ cparam = phi.GenerateCurve(xR)
+ for k := 0; k < len(points); k++ {
+ points[k] = phi.EvaluatePoint(&points[k])
+ }
+
+ *phiP = phi.EvaluatePoint(phiP)
+ *phiQ = phi.EvaluatePoint(phiQ)
+ *phiR = phi.EvaluatePoint(phiR)
+
+ // pop xR from points
+ *xR, points = points[len(points)-1], points[:len(points)-1]
+ i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+ }
+}
+
+// Traverses isogeny tree in order to compute xR needed
+// for public key generation.
+func traverseTreeSharedKeyA(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
+ var points = make([]ProjectivePoint, 0, 8)
+ var indices = make([]int, 0, 8)
+ var i, sidx int
+
+ cparam := CalcCurveParamsEquiv4(curve)
+ phi := NewIsogeny4()
+ strat := pub.params.A.IsogenyStrategy
+ stratSz := len(strat)
+
+ for j := 1; j <= stratSz; j++ {
+ for i <= stratSz-j {
+ points = append(points, *xR)
+ indices = append(indices, i)
+
+ k := strat[sidx]
+ sidx++
+ Pow2k(xR, &cparam, 2*k)
+ i += int(k)
+ }
+
+ cparam = phi.GenerateCurve(xR)
+ for k := 0; k < len(points); k++ {
+ points[k] = phi.EvaluatePoint(&points[k])
+ }
+
+ // pop xR from points
+ *xR, points = points[len(points)-1], points[:len(points)-1]
+ i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+ }
+}
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreePublicKeyB(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
+ var points = make([]ProjectivePoint, 0, 8)
+ var indices = make([]int, 0, 8)
+ var i, sidx int
+
+ cparam := CalcCurveParamsEquiv3(curve)
+ phi := NewIsogeny3()
+ strat := pub.params.B.IsogenyStrategy
+ stratSz := len(strat)
+
+ for j := 1; j <= stratSz; j++ {
+ for i <= stratSz-j {
+ points = append(points, *xR)
+ indices = append(indices, i)
+
+ k := strat[sidx]
+ sidx++
+ Pow3k(xR, &cparam, k)
+ i += int(k)
+ }
+
+ cparam = phi.GenerateCurve(xR)
+ for k := 0; k < len(points); k++ {
+ points[k] = phi.EvaluatePoint(&points[k])
+ }
+
+ *phiP = phi.EvaluatePoint(phiP)
+ *phiQ = phi.EvaluatePoint(phiQ)
+ *phiR = phi.EvaluatePoint(phiR)
+
+ // pop xR from points
+ *xR, points = points[len(points)-1], points[:len(points)-1]
+ i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+ }
+}
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreeSharedKeyB(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
+ var points = make([]ProjectivePoint, 0, 8)
+ var indices = make([]int, 0, 8)
+ var i, sidx int
+
+ cparam := CalcCurveParamsEquiv3(curve)
+ phi := NewIsogeny3()
+ strat := pub.params.B.IsogenyStrategy
+ stratSz := len(strat)
+
+ for j := 1; j <= stratSz; j++ {
+ for i <= stratSz-j {
+ points = append(points, *xR)
+ indices = append(indices, i)
+
+ k := strat[sidx]
+ sidx++
+ Pow3k(xR, &cparam, k)
+ i += int(k)
+ }
+
+ cparam = phi.GenerateCurve(xR)
+ for k := 0; k < len(points); k++ {
+ points[k] = phi.EvaluatePoint(&points[k])
+ }
+
+ // pop xR from points
+ *xR, points = points[len(points)-1], points[:len(points)-1]
+ i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+ }
+}
+
+// Generate a public key in the 2-torsion group
+func publicKeyGenA(prv *PrivateKey) (pub *PublicKey) {
+ var xPA, xQA, xRA ProjectivePoint
+ var xPB, xQB, xRB, xK ProjectivePoint
+ var invZP, invZQ, invZR Fp2
+
+ pub = NewPublicKey(KeyVariant_SIDH_A)
+ var phi = NewIsogeny4()
+
+ // Load points for A
+ xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
+ xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
+ xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
+
+ // Load points for B
+ xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
+ xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
+ xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
+
+ // Find isogeny kernel
+ xK = ScalarMul3Pt(&pub.params.InitCurve, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar)
+ traverseTreePublicKeyA(&pub.params.InitCurve, &xK, &xPB, &xQB, &xRB, pub)
+
+ // Secret isogeny
+ phi.GenerateCurve(&xK)
+ xPA = phi.EvaluatePoint(&xPB)
+ xQA = phi.EvaluatePoint(&xQB)
+ xRA = phi.EvaluatePoint(&xRB)
+ Fp2Batch3Inv(&xPA.Z, &xQA.Z, &xRA.Z, &invZP, &invZQ, &invZR)
+
+ mul(&pub.affine_xP, &xPA.X, &invZP)
+ mul(&pub.affine_xQ, &xQA.X, &invZQ)
+ mul(&pub.affine_xQmP, &xRA.X, &invZR)
+ return
+}
+
+// Generate a public key in the 3-torsion group
+func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) {
+ var xPB, xQB, xRB, xK ProjectivePoint
+ var xPA, xQA, xRA ProjectivePoint
+ var invZP, invZQ, invZR Fp2
+
+ pub = NewPublicKey(prv.keyVariant)
+ var phi = NewIsogeny3()
+
+ // Load points for B
+ xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
+ xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
+ xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
+
+ // Load points for A
+ xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
+ xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
+ xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
+
+ xK = ScalarMul3Pt(&pub.params.InitCurve, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar)
+ traverseTreePublicKeyB(&pub.params.InitCurve, &xK, &xPA, &xQA, &xRA, pub)
+
+ phi.GenerateCurve(&xK)
+ xPB = phi.EvaluatePoint(&xPA)
+ xQB = phi.EvaluatePoint(&xQA)
+ xRB = phi.EvaluatePoint(&xRA)
+ Fp2Batch3Inv(&xPB.Z, &xQB.Z, &xRB.Z, &invZP, &invZQ, &invZR)
+
+ mul(&pub.affine_xP, &xPB.X, &invZP)
+ mul(&pub.affine_xQ, &xQB.X, &invZQ)
+ mul(&pub.affine_xQmP, &xRB.X, &invZR)
+ return
+}
+
+// -----------------------------------------------------------------------------
+// Key agreement functions
+//
+
+// Establishing shared keys in in 2-torsion group
+func deriveSecretA(prv *PrivateKey, pub *PublicKey) []byte {
+ var sharedSecret = make([]byte, pub.params.SharedSecretSize)
+ var xP, xQ, xQmP ProjectivePoint
+ var xK ProjectivePoint
+ var cparam ProjectiveCurveParameters
+ var phi = NewIsogeny4()
+ var jInv Fp2
+
+ // Recover curve coefficients
+ RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
+ // C=1
+ cparam.C = Params.OneFp2
+
+ // Find kernel of the morphism
+ xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
+ xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
+ xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
+ xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar)
+
+ // Traverse isogeny tree
+ traverseTreeSharedKeyA(&cparam, &xK, pub)
+
+ // Calculate j-invariant on isogeneus curve
+ c := phi.GenerateCurve(&xK)
+ RecoverCurveCoefficients4(&cparam, &c)
+ Jinvariant(&cparam, &jInv)
+ convFp2ToBytes(sharedSecret, &jInv)
+ return sharedSecret
+}
+
+// Establishing shared keys in in 3-torsion group
+func deriveSecretB(prv *PrivateKey, pub *PublicKey) []byte {
+ var sharedSecret = make([]byte, pub.params.SharedSecretSize)
+ var xP, xQ, xQmP ProjectivePoint
+ var xK ProjectivePoint
+ var cparam ProjectiveCurveParameters
+ var phi = NewIsogeny3()
+ var jInv Fp2
+
+ // Recover curve A coefficient
+ RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
+ // C=1
+ cparam.C = Params.OneFp2
+
+ // Find kernel of the morphism
+ xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
+ xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
+ xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
+ xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar)
+
+ // Traverse isogeny tree
+ traverseTreeSharedKeyB(&cparam, &xK, pub)
+
+ // Calculate j-invariant on isogeneus curve
+ c := phi.GenerateCurve(&xK)
+ RecoverCurveCoefficients3(&cparam, &c)
+ Jinvariant(&cparam, &jInv)
+ convFp2ToBytes(sharedSecret, &jInv)
+ return sharedSecret
+}
+
+func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) {
+ if pkB.keyVariant != KeyVariant_SIKE {
+ return nil, errors.New("wrong key type")
+ }
+
+ j, err := DeriveSecret(skA, pkB)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(ptext) != pkA.params.KemSize {
+ panic("Implementation error")
+ }
+
+ digest := sha256.Sum256(j)
+ // Uses truncated digest (first 16-bytes)
+ for i, _ := range ptext {
+ digest[i] ^= ptext[i]
+ }
+
+ ret := make([]byte, pkA.Size()+len(ptext))
+ copy(ret, pkA.Export())
+ copy(ret[pkA.Size():], digest[:pkA.params.KemSize])
+ return ret, nil
+}
+
+// NewPrivateKey initializes private key.
+// Usage of this function guarantees that the object is correctly initialized.
+func NewPrivateKey(v KeyVariant) *PrivateKey {
+ prv := &PrivateKey{key: key{params: &Params, keyVariant: v}}
+ if (v & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+ prv.Scalar = make([]byte, prv.params.A.SecretByteLen)
+ } else {
+ prv.Scalar = make([]byte, prv.params.B.SecretByteLen)
+ }
+ if v == KeyVariant_SIKE {
+ prv.S = make([]byte, prv.params.MsgLen)
+ }
+ return prv
+}
+
+// NewPublicKey initializes public key.
+// Usage of this function guarantees that the object is correctly initialized.
+func NewPublicKey(v KeyVariant) *PublicKey {
+ return &PublicKey{key: key{params: &Params, keyVariant: v}}
+}
+
+// Import clears content of the public key currently stored in the structure
+// and imports key stored in the byte string. Returns error in case byte string
+// size is wrong. Doesn't perform any validation.
+func (pub *PublicKey) Import(input []byte) error {
+ if len(input) != pub.Size() {
+ return errors.New("sidh: input to short")
+ }
+ ssSz := pub.params.SharedSecretSize
+ convBytesToFp2(&pub.affine_xP, input[0:ssSz])
+ convBytesToFp2(&pub.affine_xQ, input[ssSz:2*ssSz])
+ convBytesToFp2(&pub.affine_xQmP, input[2*ssSz:3*ssSz])
+ return nil
+}
+
+// Exports currently stored key. In case structure hasn't been filled with key data
+// returned byte string is filled with zeros.
+func (pub *PublicKey) Export() []byte {
+ output := make([]byte, pub.params.PublicKeySize)
+ ssSz := pub.params.SharedSecretSize
+ convFp2ToBytes(output[0:ssSz], &pub.affine_xP)
+ convFp2ToBytes(output[ssSz:2*ssSz], &pub.affine_xQ)
+ convFp2ToBytes(output[2*ssSz:3*ssSz], &pub.affine_xQmP)
+ return output
+}
+
+// Size returns size of the public key in bytes
+func (pub *PublicKey) Size() int {
+ return pub.params.PublicKeySize
+}
+
+// Exports currently stored key. In case structure hasn't been filled with key data
+// returned byte string is filled with zeros.
+func (prv *PrivateKey) Export() []byte {
+ ret := make([]byte, len(prv.Scalar)+len(prv.S))
+ copy(ret, prv.S)
+ copy(ret[len(prv.S):], prv.Scalar)
+ return ret
+}
+
+// Size returns size of the private key in bytes
+func (prv *PrivateKey) Size() int {
+ tmp := len(prv.Scalar)
+ if prv.keyVariant == KeyVariant_SIKE {
+ tmp += int(prv.params.MsgLen)
+ }
+ return tmp
+}
+
+// Import clears content of the private key currently stored in the structure
+// and imports key from octet string. In case of SIKE, the random value 'S'
+// must be prepended to the value of actual private key (see SIKE spec for details).
+// Function doesn't import public key value to PrivateKey object.
+func (prv *PrivateKey) Import(input []byte) error {
+ if len(input) != prv.Size() {
+ return errors.New("sidh: input to short")
+ }
+ copy(prv.S, input[:len(prv.S)])
+ copy(prv.Scalar, input[len(prv.S):])
+ return nil
+}
+
+// Generates random private key for SIDH or SIKE. Generated value is
+// formed as little-endian integer from key-space <2^(e2-1)..2^e2 - 1>
+// for KeyVariant_A or <2^(s-1)..2^s - 1>, where s = floor(log_2(3^e3)),
+// for KeyVariant_B.
+//
+// Returns error in case user provided RNG fails.
+func (prv *PrivateKey) Generate(rand io.Reader) error {
+ var err error
+ var dp *DomainParams
+
+ if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+ dp = &prv.params.A
+ } else {
+ dp = &prv.params.B
+ }
+
+ if prv.keyVariant == KeyVariant_SIKE {
+ _, err = io.ReadFull(rand, prv.S)
+ }
+
+ // Private key generation takes advantage of the fact that keyspace for secret
+ // key is (0, 2^x - 1), for some possitivite value of 'x' (see SIKE, 1.3.8).
+ // It means that all bytes in the secret key, but the last one, can take any
+ // value between <0x00,0xFF>. Similarily for the last byte, but generation
+ // needs to chop off some bits, to make sure generated value is an element of
+ // a key-space.
+ _, err = io.ReadFull(rand, prv.Scalar)
+ if err != nil {
+ return err
+ }
+ prv.Scalar[len(prv.Scalar)-1] &= (1 << (dp.SecretBitLen % 8)) - 1
+ // Make sure scalar is SecretBitLen long. SIKE spec says that key
+ // space starts from 0, but I'm not confortable with having low
+ // value scalars used for private keys. It is still secrure as per
+ // table 5.1 in [SIKE].
+ prv.Scalar[len(prv.Scalar)-1] |= 1 << ((dp.SecretBitLen % 8) - 1)
+ return err
+}
+
+// Generates public key.
+//
+// Constant time.
+func (prv *PrivateKey) GeneratePublicKey() *PublicKey {
+ if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+ return publicKeyGenA(prv)
+ }
+ return publicKeyGenB(prv)
+}
+
+// Computes a shared secret which is a j-invariant. Function requires that pub has
+// different KeyVariant than prv. Length of returned output is 2*ceil(log_2 P)/8),
+// where P is a prime defining finite field.
+//
+// It's important to notice that each keypair must not be used more than once
+// to calculate shared secret.
+//
+// Function may return error. This happens only in case provided input is invalid.
+// Constant time for properly initialized private and public key.
+func DeriveSecret(prv *PrivateKey, pub *PublicKey) ([]byte, error) {
+
+ if (pub == nil) || (prv == nil) {
+ return nil, errors.New("sidh: invalid arguments")
+ }
+
+ if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) {
+ return nil, errors.New("sidh: public and private are incompatbile")
+ }
+
+ if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+ return deriveSecretA(prv, pub), nil
+ } else {
+ return deriveSecretB(prv, pub), nil
+ }
+}
+
+// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG
+// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails
+// or wrongly formatted input was provided.
+func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) {
+ var ptextLen = len(ptext)
+ // c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
+ if ptextLen != pub.params.KemSize {
+ return nil, errors.New("Unsupported message length")
+ }
+
+ skA := NewPrivateKey(KeyVariant_SIDH_A)
+ err := skA.Generate(rng)
+ if err != nil {
+ return nil, err
+ }
+
+ pkA := skA.GeneratePublicKey()
+ return encrypt(skA, pkA, pub, ptext)
+}
+
+// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case
+// decryption succeeds or error in case unexptected input was provided.
+// Constant time
+func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) {
+ var c1_len int
+ n := make([]byte, prv.params.KemSize)
+ pk_len := prv.params.PublicKeySize
+
+ if prv.keyVariant != KeyVariant_SIKE {
+ return nil, errors.New("wrong key type")
+ }
+
+ // ctext is a concatenation of (pubkey_A || c1=ciphertext)
+ // it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
+ c1_len = len(ctext) - pk_len
+ if c1_len != int(prv.params.KemSize) {
+ return nil, errors.New("wrong size of cipher text")
+ }
+
+ c0 := NewPublicKey(KeyVariant_SIDH_A)
+ err := c0.Import(ctext[:pk_len])
+ if err != nil {
+ return nil, err
+ }
+ j, err := DeriveSecret(prv, c0)
+ if err != nil {
+ return nil, err
+ }
+
+ digest := sha256.Sum256(j)
+ copy(n, digest[:])
+
+ for i, _ := range n {
+ n[i] ^= ctext[pk_len+i]
+ }
+ return n[:c1_len], nil
+}
+
+// Encapsulation receives the public key and generates SIKE ciphertext and shared secret.
+// The generated ciphertext is used for authentication.
+// The rng must be cryptographically secure PRNG.
+// Error is returned in case PRNG fails or wrongly formatted input was provided.
+func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) {
+ // Buffer for random, secret message
+ ptext := make([]byte, pub.params.MsgLen)
+ // SHA256 hash context object
+ d := sha256.New()
+
+ // Generate ephemeral value
+ _, err = io.ReadFull(rng, ptext)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Implementation uses first 28-bytes of secret
+ d.Write(ptext)
+ d.Write(pub.Export())
+ digest := d.Sum(nil)
+ // r = G(ptext||pub)
+ r := digest[:pub.params.A.SecretByteLen]
+
+ // (c0 || c1) = Enc(pkA, ptext; r)
+ skA := NewPrivateKey(KeyVariant_SIDH_A)
+ err = skA.Import(r)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pkA := skA.GeneratePublicKey()
+ ctext, err = encrypt(skA, pkA, pub, ptext)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // K = H(ptext||(c0||c1))
+ d.Reset()
+ d.Write(ptext)
+ d.Write(ctext)
+ digest = d.Sum(digest[:0])
+ return ctext, digest[:pub.params.KemSize], nil
+}
+
+// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared
+// secret if plaintext verifies correctly, otherwise function outputs random value.
+// Decapsulation may fail in case input is wrongly formatted.
+// Constant time for properly initialized input.
+func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) {
+ var skA = NewPrivateKey(KeyVariant_SIDH_A)
+ // SHA256 hash context object
+ d := sha256.New()
+
+ m, err := Decrypt(prv, ctext)
+ if err != nil {
+ return nil, err
+ }
+
+ // r' = G(m'||pub)
+ d.Write(m)
+ d.Write(pub.Export())
+ digest := d.Sum(nil)
+ // Never fails
+ skA.Import(digest[:pub.params.A.SecretByteLen])
+
+ // Never fails
+ pkA := skA.GeneratePublicKey()
+ c0 := pkA.Export()
+
+ d.Reset()
+ if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 {
+ d.Write(m)
+ } else {
+ // S is chosen at random when generating a key and is unknown to the other party. It
+ // may seem weird, but it's correct. It is important that S is unpredictable
+ // to other party. Without this check, it is possible to recover a secret, by
+ // providing series of invalid ciphertexts. It is also important that in case
+ //
+ // See more details in "On the security of supersingular isogeny cryptosystems"
+ // (S. Galbraith, et al., 2016, ePrint #859).
+ d.Write(prv.S)
+ }
+ d.Write(ctext)
+ digest = d.Sum(digest[:0])
+ return digest[:pub.params.KemSize], nil
+}