|
- package sike
-
- import (
- "crypto/subtle"
- "errors"
- "github.com/henrydcase/nobs/hash/shake"
- "io"
- )
-
- const (
- // n can is max 256-bit (see 1.4 of [SIKE])
- MaxMsgLen = 32
- MaxSharedSecretSize = 188
- MaxSecretByteLenA = 47
- )
-
- func cpick(pick int, out, in1, in2 []byte) {
- var which = byte((int8(pick << 7)) >> 7)
- for i, _ := range out {
- out[i] = (in1[i] & which) | (in2[i] & ^which)
- }
- }
-
- type KEM struct {
- allocated bool
- rng io.Reader
- msg []byte
- secretBytes []byte
- }
-
- // Zeroize Fp2
- func zeroize(fp *Fp2) {
- // Zeroizing in 2 seperated 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) < Params.SharedSecretSize {
- 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) < Params.SharedSecretSize {
- 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
- var phi isogeny4
-
- cparam := ToEquiv4(curve)
- 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
- var phi isogeny4
-
- cparam := ToEquiv4(curve)
- 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
- var phi isogeny3
-
- cparam := ToEquiv3(curve)
- 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
- var phi isogeny3
-
- cparam := ToEquiv3(curve)
- 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(pub *PublicKey, prv *PrivateKey) {
- var xPA, xQA, xRA ProjectivePoint
- var xPB, xQB, xRB, xK ProjectivePoint
- var invZP, invZQ, invZR Fp2
- var phi isogeny4
-
- // 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)
- }
-
- // Generate a public key in the 3-torsion group
- func publicKeyGenB(pub *PublicKey, prv *PrivateKey) {
- var xPB, xQB, xRB, xK ProjectivePoint
- var xPA, xQA, xRA ProjectivePoint
- var invZP, invZQ, invZR Fp2
- var phi isogeny3
-
- // 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}
-
- 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)
- }
-
- // -----------------------------------------------------------------------------
- // Key agreement functions
- //
-
- // Establishing shared keys in in 2-torsion group
- func deriveSecretA(ss []byte, prv *PrivateKey, pub *PublicKey) {
- var xP, xQ, xQmP ProjectivePoint
- var xK ProjectivePoint
- var phi isogeny4
- var jInv Fp2
-
- // Recover curve coefficients
- cparam := pub.params.InitCurve
- RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
-
- // 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)
- FromEquiv4(&cparam, &c)
- Jinvariant(&cparam, &jInv)
- convFp2ToBytes(ss, &jInv)
- }
-
- // Establishing shared keys in in 3-torsion group
- func deriveSecretB(ss []byte, prv *PrivateKey, pub *PublicKey) {
- var xP, xQ, xQmP ProjectivePoint
- var xK ProjectivePoint
- var phi isogeny3
- var jInv Fp2
-
- // Recover curve coefficients
- cparam := pub.params.InitCurve
- RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
-
- // 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)
- FromEquiv3(&cparam, &c)
- Jinvariant(&cparam, &jInv)
- convFp2ToBytes(ss, &jInv)
- }
-
- func generateCiphertext(ctext []byte, skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) error {
- var n [MaxMsgLen]byte
- var j [MaxSharedSecretSize]byte
- var ptextLen = len(ptext)
-
- if pkB.keyVariant != KeyVariant_SIKE {
- return errors.New("wrong key type")
- }
-
- err := DeriveSecret(j[:], skA, pkB)
- if err != nil {
- return err
- }
-
- f := shake.NewShake256()
- f.Write(j[:Params.SharedSecretSize])
- f.Read(n[:ptextLen])
- for i, _ := range ptext {
- n[i] ^= ptext[i]
- }
-
- pkA.Export(ctext)
- copy(ctext[pkA.Size():], n[:ptextLen])
- return 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(out []byte) {
- ssSz := pub.params.SharedSecretSize
- convFp2ToBytes(out[0:ssSz], &pub.affine_xP)
- convFp2ToBytes(out[ssSz:2*ssSz], &pub.affine_xQ)
- convFp2ToBytes(out[2*ssSz:3*ssSz], &pub.affine_xQmP)
- }
-
- // 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(out []byte) {
- copy(out, prv.S)
- copy(out[len(prv.S):], prv.Scalar)
- }
-
- // 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 == nil {
- _, 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(pub *PublicKey) {
- if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
- publicKeyGenA(pub, prv)
- } else {
- publicKeyGenB(pub, 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(ss []byte, prv *PrivateKey, pub *PublicKey) error {
- if (pub == nil) || (prv == nil) {
- return errors.New("sidh: invalid arguments")
- }
-
- if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) {
- return errors.New("sidh: public and private are incompatbile")
- }
-
- if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
- deriveSecretA(ss, prv, pub)
- } else {
- deriveSecretB(ss, prv, pub)
- }
- return 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 formated input was provided.
- func encrypt(ctext []byte, rng io.Reader, pub *PublicKey, ptext []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 errors.New("Unsupported message length")
- }
-
- skA := NewPrivateKey(KeyVariant_SIDH_A)
- pkA := NewPublicKey(KeyVariant_SIDH_A)
- err := skA.Generate(rng)
- if err != nil {
- return err
- }
-
- skA.GeneratePublicKey(pkA)
- return generateCiphertext(ctext, 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(n []byte, prv *PrivateKey, ctext []byte) (int, error) {
- var c1_len int
- var j [MaxSharedSecretSize]byte
- var pk_len = prv.params.PublicKeySize
-
- if prv.keyVariant != KeyVariant_SIKE {
- return 0, errors.New("wrong key type")
- }
-
- // ctext is a concatenation of (ciphertext = pubkey_A || c1)
- // it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
- c1_len = len(ctext) - pk_len
- if c1_len != prv.params.KemSize {
- return 0, errors.New("wrong size of cipher text")
- }
-
- c0 := NewPublicKey(KeyVariant_SIDH_A)
- err := c0.Import(ctext[:pk_len])
- if err != nil {
- return 0, err
- }
- err = DeriveSecret(j[:], prv, c0)
- if err != nil {
- return 0, err
- }
-
- s := shake.NewShake256()
- s.Write(j[:Params.SharedSecretSize])
- s.Read(n[:c1_len])
- for i, _ := range n[:c1_len] {
- n[i] ^= ctext[pk_len+i]
- }
- return c1_len, nil
- }
-
- // KEM API
- func (kem *KEM) Allocate(rng io.Reader) {
- kem.allocated = true
- kem.rng = rng
- kem.msg = make([]byte, Params.MsgLen)
- kem.secretBytes = make([]byte, Params.A.SecretByteLen)
- }
-
- func (kem *KEM) Reset() {
- for i, _ := range kem.msg {
- kem.msg[i] = 0
- }
-
- for i, _ := range kem.secretBytes {
- kem.secretBytes[i] = 0
- }
- }
-
- func (kem *KEM) CiphertextSize() int {
- return Params.CiphertextSize
- }
-
- func (kem *KEM) SharedSecretSize() int {
- return Params.KemSize
- }
-
- // 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 formated input was provided.
- func (kem *KEM) Encapsulate(ciphertext, secret []byte, pub *PublicKey) error {
- var buf [3 * MaxSharedSecretSize]byte
- var skA = PrivateKey{
- key: key{
- params: &Params,
- keyVariant: KeyVariant_SIDH_A},
- Scalar: kem.secretBytes}
-
- if !kem.allocated {
- panic("KEM unallocated")
- }
-
- // Generate ephemeral value
- _, err := io.ReadFull(kem.rng, kem.msg[:])
- if err != nil {
- return err
- }
-
- pub.Export(buf[:])
- g := shake.NewShake256()
- g.Write(kem.msg)
- g.Write(buf[:3*Params.SharedSecretSize])
- g.Read(skA.Scalar)
-
- // Ensure bitlength is not bigger then to 2^e2-1
- skA.Scalar[len(skA.Scalar)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1
- pkA := NewPublicKey(KeyVariant_SIDH_A)
- skA.GeneratePublicKey(pkA)
- err = generateCiphertext(ciphertext, &skA, pkA, pub, kem.msg[:])
- if err != nil {
- return err
- }
-
- // K = H(msg||(c0||c1))
- h := shake.NewShake256()
- h.Write(kem.msg)
- h.Write(ciphertext)
- h.Read(secret)
- return 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 formated.
- // Constant time for properly initialized input.
- func (kem *KEM) Decapsulate(secret []byte, prv *PrivateKey, pub *PublicKey, ctext []byte) error {
- var m [MaxMsgLen]byte
- var r [MaxSecretByteLenA]byte
- var pkBytes [3 * MaxSharedSecretSize]byte
- var skA = PrivateKey{
- key: key{
- params: &Params,
- keyVariant: KeyVariant_SIDH_A},
- Scalar: kem.secretBytes}
- var pkA = NewPublicKey(KeyVariant_SIDH_A)
-
- c1_len, err := decrypt(m[:], prv, ctext)
- if err != nil {
- return err
- }
-
- // r' = G(m'||pub)
- //var key = make([]byte, pub.Size()+2*Params.MsgLen)
- pub.Export(pkBytes[:])
- g := shake.NewShake256()
- g.Write(m[:c1_len])
- g.Write(pkBytes[:3*pub.params.SharedSecretSize])
- g.Read(r[:pub.params.A.SecretByteLen])
- // Ensure bitlength is not bigger than 2^e2-1
- r[pub.params.A.SecretByteLen-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1
-
- // Never fails
- skA.Import(r[:pub.params.A.SecretByteLen])
- skA.GeneratePublicKey(pkA)
- pkA.Export(pkBytes[:])
-
- // S is chosen at random when generating a key and unknown to 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 isn case
- //
- // See more details in "On the security of supersingular isogeny cryptosystems"
- // (S. Galbraith, et al., 2016, ePrint #859).
- mask := subtle.ConstantTimeCompare(pkBytes[:pub.params.PublicKeySize], ctext[:pub.params.PublicKeySize])
- cpick(mask, m[:c1_len], m[:c1_len], prv.S)
- h := shake.NewShake256()
- h.Write(m[:c1_len])
- h.Write(ctext)
- h.Read(secret)
- return nil
- }
|