diff options
author | Pete Bentley <prb@google.com> | 2019-08-09 14:24:27 +0000 |
---|---|---|
committer | Pete Bentley <prb@google.com> | 2019-08-09 14:24:27 +0000 |
commit | a5c947b7c91bac52eeb5086507b67e52a59ef980 (patch) | |
tree | 3725c3e206175c177a448c50d41ad2c2589a07fa /src/ssl/test/runner/sike/sike.go | |
parent | 228bd6249d17f351ea66508b3ec3112ed1cbdf30 (diff) | |
download | boringssl-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.go | 683 |
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 +} |