go-sike/sike.go

748 lines
21 KiB
Go
Raw Normal View History

2019-03-29 22:13:53 +00:00
package sike
import (
"crypto/subtle"
"errors"
2019-05-06 11:42:26 +01:00
"github.com/henrydcase/nobs/hash/shake"
2019-03-29 22:13:53 +00:00
"io"
)
2019-05-06 11:42:26 +01:00
var cshakeG, cshakeH, cshakeF *shake.CShake
2019-03-29 22:13:53 +00:00
2019-05-06 11:42:26 +01:00
func init() {
// Constants used for cSHAKE customization
// Those values are different than in [SIKE] - they are encoded on 16bits. This is
// done in order for implementation to be compatible with [REF] and test vectors.
var G = []byte{0x00, 0x00}
var H = []byte{0x01, 0x00}
var F = []byte{0x02, 0x00}
cshakeG = shake.NewCShake256(nil, G)
cshakeH = shake.NewCShake256(nil, H)
cshakeF = shake.NewCShake256(nil, F)
}
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
2019-03-29 22:13:53 +00:00
}
// 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) < 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)
2019-05-06 11:42:26 +01:00
var i, sIdx int
var phi isogeny4
2019-03-29 22:13:53 +00:00
cparam := CalcCurveParamsEquiv4(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)
2019-05-06 11:42:26 +01:00
k := strat[sIdx]
sIdx++
2019-03-29 22:13:53 +00:00
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)
2019-05-06 11:42:26 +01:00
var i, sIdx int
var phi isogeny4
2019-03-29 22:13:53 +00:00
cparam := CalcCurveParamsEquiv4(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)
2019-05-06 11:42:26 +01:00
k := strat[sIdx]
sIdx++
2019-03-29 22:13:53 +00:00
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)
2019-05-06 11:42:26 +01:00
var i, sIdx int
var phi isogeny3
2019-03-29 22:13:53 +00:00
cparam := CalcCurveParamsEquiv3(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)
2019-05-06 11:42:26 +01:00
k := strat[sIdx]
sIdx++
2019-03-29 22:13:53 +00:00
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)
2019-05-06 11:42:26 +01:00
var i, sIdx int
var phi isogeny3
2019-03-29 22:13:53 +00:00
cparam := CalcCurveParamsEquiv3(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)
2019-05-06 11:42:26 +01:00
k := strat[sIdx]
sIdx++
2019-03-29 22:13:53 +00:00
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
2019-05-06 11:42:26 +01:00
func publicKeyGenA(pub *PublicKey, prv *PrivateKey) {
2019-03-29 22:13:53 +00:00
var xPA, xQA, xRA ProjectivePoint
var xPB, xQB, xRB, xR ProjectivePoint
var invZP, invZQ, invZR Fp2
var tmp ProjectiveCurveParameters
2019-05-06 11:42:26 +01:00
var phi isogeny4
2019-03-29 22:13:53 +00:00
// 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
tmp.C = pub.params.OneFp2
xR = ScalarMul3Pt(&tmp, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar)
// Reset params object and travers isogeny tree
tmp.C = pub.params.OneFp2
zeroize(&tmp.A)
traverseTreePublicKeyA(&tmp, &xR, &xPB, &xQB, &xRB, pub)
// Secret isogeny
phi.GenerateCurve(&xR)
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
2019-05-06 11:42:26 +01:00
func publicKeyGenB(pub *PublicKey, prv *PrivateKey) {
2019-03-29 22:13:53 +00:00
var xPB, xQB, xRB, xR ProjectivePoint
var xPA, xQA, xRA ProjectivePoint
var invZP, invZQ, invZR Fp2
var tmp ProjectiveCurveParameters
2019-05-06 11:42:26 +01:00
var phi isogeny3
2019-03-29 22:13:53 +00:00
// 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}
tmp.C = pub.params.OneFp2
xR = ScalarMul3Pt(&tmp, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar)
tmp.C = pub.params.OneFp2
zeroize(&tmp.A)
traverseTreePublicKeyB(&tmp, &xR, &xPA, &xQA, &xRA, pub)
phi.GenerateCurve(&xR)
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
2019-05-06 11:42:26 +01:00
func deriveSecretA(ss []byte, prv *PrivateKey, pub *PublicKey) {
2019-03-29 22:13:53 +00:00
var cparam ProjectiveCurveParameters
var xP, xQ, xQmP ProjectivePoint
var xR ProjectivePoint
2019-05-06 11:42:26 +01:00
var phi isogeny4
2019-03-29 22:13:53 +00:00
var jInv Fp2
// Recover curve coefficients
cparam.C = pub.params.OneFp2
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}
xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar)
// Traverse isogeny tree
traverseTreeSharedKeyA(&cparam, &xR, pub)
// Calculate j-invariant on isogeneus curve
c := phi.GenerateCurve(&xR)
RecoverCurveCoefficients4(&cparam, &c)
Jinvariant(&cparam, &jInv)
2019-05-06 11:42:26 +01:00
convFp2ToBytes(ss, &jInv)
2019-03-29 22:13:53 +00:00
}
// Establishing shared keys in in 3-torsion group
2019-05-06 11:42:26 +01:00
func deriveSecretB(ss []byte, prv *PrivateKey, pub *PublicKey) {
2019-03-29 22:13:53 +00:00
var xP, xQ, xQmP ProjectivePoint
var xR ProjectivePoint
var cparam ProjectiveCurveParameters
2019-05-06 11:42:26 +01:00
var phi isogeny3
2019-03-29 22:13:53 +00:00
var jInv Fp2
// Recover curve coefficients
cparam.C = pub.params.OneFp2
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}
xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar)
// Traverse isogeny tree
traverseTreeSharedKeyB(&cparam, &xR, pub)
// Calculate j-invariant on isogeneus curve
c := phi.GenerateCurve(&xR)
RecoverCurveCoefficients3(&cparam, &c)
Jinvariant(&cparam, &jInv)
2019-05-06 11:42:26 +01:00
convFp2ToBytes(ss, &jInv)
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
func generateCiphertext(ctext []byte, skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) error {
var n [40]byte // OZAPTF n can is max 320-bit (see 1.4 of [SIKE])
var j [126]byte
2019-03-29 22:13:53 +00:00
var ptextLen = len(ptext)
if pkB.keyVariant != KeyVariant_SIKE {
2019-05-06 11:42:26 +01:00
return errors.New("wrong key type")
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
err := DeriveSecret(j[:], skA, pkB)
2019-03-29 22:13:53 +00:00
if err != nil {
2019-05-06 11:42:26 +01:00
return err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
cshakeF.Reset()
cshakeF.Write(j[:2*Params.Bytelen])
cshakeF.Read(n[:ptextLen])
2019-03-29 22:13:53 +00:00
for i, _ := range ptext {
n[i] ^= ptext[i]
}
2019-05-06 11:42:26 +01:00
//ret := make([]byte, pkA.Size()+ptextLen)
pkA.Export(ctext)
copy(ctext[pkA.Size():], n[:ptextLen])
return nil
2019-03-29 22:13:53 +00:00
}
// 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")
}
2019-05-06 11:42:26 +01:00
2019-03-29 22:13:53 +00:00
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.
2019-05-06 11:42:26 +01:00
func (pub *PublicKey) Export(out []byte) {
2019-03-29 22:13:53 +00:00
ssSz := pub.params.SharedSecretSize
2019-05-06 11:42:26 +01:00
convFp2ToBytes(out[0:ssSz], &pub.affine_xP)
convFp2ToBytes(out[ssSz:2*ssSz], &pub.affine_xQ)
convFp2ToBytes(out[2*ssSz:3*ssSz], &pub.affine_xQmP)
2019-03-29 22:13:53 +00:00
}
// 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 {
2019-05-06 11:42:26 +01:00
// OZAPTF
2019-03-29 22:13:53 +00:00
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 == 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.
2019-05-06 11:42:26 +01:00
func (prv *PrivateKey) GeneratePublicKey(pub *PublicKey) {
2019-03-29 22:13:53 +00:00
if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
2019-05-06 11:42:26 +01:00
publicKeyGenA(pub, prv)
} else {
publicKeyGenB(pub, prv)
2019-03-29 22:13:53 +00:00
}
}
// 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.
2019-05-06 11:42:26 +01:00
func DeriveSecret(ss []byte, prv *PrivateKey, pub *PublicKey) error {
2019-03-29 22:13:53 +00:00
if (pub == nil) || (prv == nil) {
2019-05-06 11:42:26 +01:00
return errors.New("sidh: invalid arguments")
2019-03-29 22:13:53 +00:00
}
if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) {
2019-05-06 11:42:26 +01:00
return errors.New("sidh: public and private are incompatbile")
2019-03-29 22:13:53 +00:00
}
if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
2019-05-06 11:42:26 +01:00
deriveSecretA(ss, prv, pub)
2019-03-29 22:13:53 +00:00
} else {
2019-05-06 11:42:26 +01:00
deriveSecretB(ss, prv, pub)
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
return nil
2019-03-29 22:13:53 +00:00
}
// 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.
2019-05-06 11:42:26 +01:00
func encrypt(ctext []byte, rng io.Reader, pub *PublicKey, ptext []byte) error {
2019-03-29 22:13:53 +00:00
var ptextLen = len(ptext)
// c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
if ptextLen != (pub.params.KemSize + 8) {
2019-05-06 11:42:26 +01:00
return errors.New("Unsupported message length")
2019-03-29 22:13:53 +00:00
}
skA := NewPrivateKey(KeyVariant_SIDH_A)
2019-05-06 11:42:26 +01:00
pkA := NewPublicKey(KeyVariant_SIDH_A)
2019-03-29 22:13:53 +00:00
err := skA.Generate(rng)
if err != nil {
2019-05-06 11:42:26 +01:00
return err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
skA.GeneratePublicKey(pkA)
return generateCiphertext(ctext, skA, pkA, pub, ptext)
2019-03-29 22:13:53 +00:00
}
// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case
// decryption succeeds or error in case unexptected input was provided.
// Constant time
2019-05-06 11:42:26 +01:00
func decrypt(n []byte, prv *PrivateKey, ctext []byte) (int, error) {
2019-03-29 22:13:53 +00:00
var c1_len int
2019-05-06 11:42:26 +01:00
var j [2 * 63]byte // OZAPTF: 63
2019-03-29 22:13:53 +00:00
var pk_len = prv.params.PublicKeySize
if prv.keyVariant != KeyVariant_SIKE {
2019-05-06 11:42:26 +01:00
return 0, errors.New("wrong key type")
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
// ctext is a concatenation of (ciphertext = pubkey_A || c1)
2019-03-29 22:13:53 +00:00
// 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) + 8) {
2019-05-06 11:42:26 +01:00
return 0, errors.New("wrong size of cipher text")
2019-03-29 22:13:53 +00:00
}
c0 := NewPublicKey(KeyVariant_SIDH_A)
err := c0.Import(ctext[:pk_len])
if err != nil {
2019-05-06 11:42:26 +01:00
return 0, err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
err = DeriveSecret(j[:], prv, c0)
2019-03-29 22:13:53 +00:00
if err != nil {
2019-05-06 11:42:26 +01:00
return 0, err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
cshakeF.Reset()
cshakeF.Write(j[:2*Params.Bytelen])
cshakeF.Read(n[:c1_len])
2019-03-29 22:13:53 +00:00
for i, _ := range n[:c1_len] {
n[i] ^= ctext[pk_len+i]
}
2019-05-06 11:42:26 +01:00
return c1_len, nil
}
// KEM API
func (kem *KEM) Allocate(rng io.Reader) {
kem.allocated = true
kem.rng = rng
kem.msg = make([]byte, 24)
kem.secretBytes = make([]byte, 32)
}
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
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
func (kem *KEM) SharedSecretSize() int {
return Params.KemSize
}
const (
// See [SIKE], 1.4
MaxMsgLen = 40
MaxPublicKey = 378
)
2019-03-29 22:13:53 +00:00
// 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.
2019-05-06 11:42:26 +01:00
func (kem *KEM) Encapsulate(ciphertext, secret []byte, pub *PublicKey) error {
if !kem.allocated {
panic("KEM unallocated")
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
// OZAPTF: MaxBuf: 3*SharedSecretSize
var buf [3 * 126]byte
2019-03-29 22:13:53 +00:00
2019-05-06 11:42:26 +01:00
var skA = PrivateKey{
key: key{
params: &Params,
keyVariant: KeyVariant_SIDH_A},
Scalar: kem.secretBytes}
// Generate ephemeral value
_, err := io.ReadFull(kem.rng, kem.msg[:])
2019-03-29 22:13:53 +00:00
if err != nil {
2019-05-06 11:42:26 +01:00
return err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
pub.Export(buf[:])
cshakeG.Reset()
cshakeG.Write(kem.msg)
cshakeG.Write(buf[:])
cshakeG.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[:])
2019-03-29 22:13:53 +00:00
if err != nil {
2019-05-06 11:42:26 +01:00
return err
2019-03-29 22:13:53 +00:00
}
2019-05-06 11:42:26 +01:00
// K = H(msg||(c0||c1))
cshakeH.Reset()
cshakeH.Write(kem.msg)
cshakeH.Write(ciphertext)
cshakeH.Read(secret)
return nil
2019-03-29 22:13:53 +00:00
}
// 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.
2019-05-06 11:42:26 +01:00
func (kem *KEM) Decapsulate(secret []byte, prv *PrivateKey, pub *PublicKey, ctext []byte) error {
2019-03-29 22:13:53 +00:00
// Resulting shared secret
2019-05-06 11:42:26 +01:00
var c0 [3 * 126]byte
var r [32]byte // OZAPTF: to change
var keyBuf [3 * 126]byte
var m [40]byte // OZAPTF: to change
var skA = PrivateKey{
key: key{
params: &Params,
keyVariant: KeyVariant_SIDH_A},
Scalar: kem.secretBytes}
c1_len, err := decrypt(m[:], prv, ctext)
2019-03-29 22:13:53 +00:00
if err != nil {
2019-05-06 11:42:26 +01:00
return err
2019-03-29 22:13:53 +00:00
}
// r' = G(m'||pub)
2019-05-06 11:42:26 +01:00
//var key = make([]byte, pub.Size()+2*Params.MsgLen)
pub.Export(keyBuf[:])
cshakeG.Reset()
cshakeG.Write(m[:c1_len])
cshakeG.Write(keyBuf[:])
cshakeG.Read(r[:])
2019-03-29 22:13:53 +00:00
// Ensure bitlength is not bigger than 2^e2-1
r[len(r)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1
// Never fails
2019-05-06 11:42:26 +01:00
skA.Import(r[:])
2019-03-29 22:13:53 +00:00
// Never fails
2019-05-06 11:42:26 +01:00
pkA := NewPublicKey(KeyVariant_SIDH_A)
skA.GeneratePublicKey(pkA)
pkA.Export(c0[:])
// 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(c0[:pub.params.PublicKeySize], ctext[:pub.params.PublicKeySize])
cpick(mask, m[:c1_len], m[:c1_len], prv.S)
cshakeH.Reset()
cshakeH.Write(m[:c1_len])
cshakeH.Write(ctext)
cshakeH.Read(secret)
return nil
2019-03-29 22:13:53 +00:00
}