@@ -0,0 +1,20 @@ | |||
# Supersingular Isogeny Key Encapsulation | |||
Repository stores implementation of SIKE based on field p503 in Go. It is small and condese implementation. | |||
Implementation uses HMAC instead of cSHAKE. | |||
## Speed | |||
This version is highly not optimized, it doesn't use any assembly. | |||
``` | |||
> go test -run=. -bench=. | |||
goos: linux | |||
goarch: amd64 | |||
BenchmarkKeygen-4 1000000 1204 ns/op | |||
BenchmarkEncaps-4 20 54651908 ns/op | |||
BenchmarkDecaps-4 20 60516975 ns/op | |||
PASS | |||
ok _/home/hdc/repos/go-sike-p503 5.550s | |||
``` |
@@ -0,0 +1,416 @@ | |||
package sike | |||
// Helpers | |||
// uint128 representation | |||
type uint128 struct { | |||
H, L uint64 | |||
} | |||
func addc64(cin, a, b uint64) (ret, cout uint64) { | |||
ret = cin | |||
ret = ret + a | |||
if ret < a { | |||
cout = 1 | |||
} | |||
ret = ret + b | |||
if ret < b { | |||
cout = 1 | |||
} | |||
return | |||
} | |||
func subc64(bIn, a, b uint64) (ret, bOut uint64) { | |||
tmp := a - bIn | |||
if tmp > a { | |||
bOut = 1 | |||
} | |||
ret = tmp - b | |||
if ret > tmp { | |||
bOut = 1 | |||
} | |||
return | |||
} | |||
func mul64(a, b uint64) (res uint128) { | |||
var al, bl, ah, bh, albl, albh, ahbl, ahbh uint64 | |||
var res1, res2, res3 uint64 | |||
var carry, maskL, maskH, temp uint64 | |||
maskL = (^maskL) >> 32 | |||
maskH = ^maskL | |||
al = a & maskL | |||
ah = a >> 32 | |||
bl = b & maskL | |||
bh = b >> 32 | |||
albl = al * bl | |||
albh = al * bh | |||
ahbl = ah * bl | |||
ahbh = ah * bh | |||
res.L = albl & maskL | |||
res1 = albl >> 32 | |||
res2 = ahbl & maskL | |||
res3 = albh & maskL | |||
temp = res1 + res2 + res3 | |||
carry = temp >> 32 | |||
res.L ^= temp << 32 | |||
res1 = ahbl >> 32 | |||
res2 = albh >> 32 | |||
res3 = ahbh & maskL | |||
temp = res1 + res2 + res3 + carry | |||
res.H = temp & maskL | |||
carry = temp & maskH | |||
res.H ^= (ahbh & maskH) + carry | |||
return | |||
} | |||
// Fp implementation | |||
// Compute z = x + y (mod 2*p). | |||
func fpAddRdc(z, x, y *Fp) { | |||
var carry uint64 | |||
// z=x+y % p503 | |||
for i := 0; i < FP_WORDS; i++ { | |||
z[i], carry = addc64(carry, x[i], y[i]) | |||
} | |||
// z = z - p503x2 | |||
carry = 0 | |||
for i := 0; i < FP_WORDS; i++ { | |||
z[i], carry = subc64(carry, z[i], p503x2[i]) | |||
} | |||
// if z<0 add p503x2 back | |||
mask := uint64(0 - carry) | |||
carry = 0 | |||
for i := 0; i < FP_WORDS; i++ { | |||
z[i], carry = addc64(carry, z[i], p503x2[i]&mask) | |||
} | |||
} | |||
// Compute z = x - y (mod 2*p). | |||
func fpSubRdc(z, x, y *Fp) { | |||
var borrow uint64 | |||
// z = z - p503x2 | |||
for i := 0; i < FP_WORDS; i++ { | |||
z[i], borrow = subc64(borrow, x[i], y[i]) | |||
} | |||
// if z<0 add p503x2 back | |||
mask := uint64(0 - borrow) | |||
borrow = 0 | |||
for i := 0; i < FP_WORDS; i++ { | |||
z[i], borrow = addc64(borrow, z[i], p503x2[i]&mask) | |||
} | |||
} | |||
// Reduce a field element in [0, 2*p) to one in [0,p). | |||
func fpRdcP(x *Fp) { | |||
var borrow, mask uint64 | |||
for i := 0; i < FP_WORDS; i++ { | |||
x[i], borrow = subc64(borrow, x[i], p503[i]) | |||
} | |||
// Sets all bits if borrow = 1 | |||
mask = 0 - borrow | |||
borrow = 0 | |||
for i := 0; i < FP_WORDS; i++ { | |||
x[i], borrow = addc64(borrow, x[i], p503[i]&mask) | |||
} | |||
} | |||
// Implementation doesn't actually depend on a prime field. | |||
func fpSwapCond(x, y *Fp, mask uint8) { | |||
if mask != 0 { | |||
var tmp Fp | |||
copy(tmp[:], y[:]) | |||
copy(y[:], x[:]) | |||
copy(x[:], tmp[:]) | |||
} | |||
} | |||
// Compute z = x * y. | |||
func fpMul(z *FpX2, x, y *Fp) { | |||
var u, v, t uint64 | |||
var carry uint64 | |||
var uv uint128 | |||
for i := uint64(0); i < FP_WORDS; i++ { | |||
for j := uint64(0); j <= i; j++ { | |||
uv = mul64(x[j], y[i-j]) | |||
v, carry = addc64(0, uv.L, v) | |||
u, carry = addc64(carry, uv.H, u) | |||
t += carry | |||
} | |||
z[i] = v | |||
v = u | |||
u = t | |||
t = 0 | |||
} | |||
for i := FP_WORDS; i < (2*FP_WORDS)-1; i++ { | |||
for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { | |||
uv = mul64(x[j], y[i-j]) | |||
v, carry = addc64(0, uv.L, v) | |||
u, carry = addc64(carry, uv.H, u) | |||
t += carry | |||
} | |||
z[i] = v | |||
v = u | |||
u = t | |||
t = 0 | |||
} | |||
z[2*FP_WORDS-1] = v | |||
} | |||
// Perform Montgomery reduction: set z = x R^{-1} (mod 2*p) | |||
// with R=2^512. Destroys the input value. | |||
func fpMontRdc(z *Fp, x *FpX2) { | |||
var carry, t, u, v uint64 | |||
var uv uint128 | |||
var count int | |||
count = 3 // number of 0 digits in the least significat part of p503 + 1 | |||
for i := 0; i < FP_WORDS; i++ { | |||
for j := 0; j < i; j++ { | |||
if j < (i - count + 1) { | |||
uv = mul64(z[j], p503p1[i-j]) | |||
v, carry = addc64(0, uv.L, v) | |||
u, carry = addc64(carry, uv.H, u) | |||
t += carry | |||
} | |||
} | |||
v, carry = addc64(0, v, x[i]) | |||
u, carry = addc64(carry, u, 0) | |||
t += carry | |||
z[i] = v | |||
v = u | |||
u = t | |||
t = 0 | |||
} | |||
for i := FP_WORDS; i < 2*FP_WORDS-1; i++ { | |||
if count > 0 { | |||
count-- | |||
} | |||
for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { | |||
if j < (FP_WORDS - count) { | |||
uv = mul64(z[j], p503p1[i-j]) | |||
v, carry = addc64(0, uv.L, v) | |||
u, carry = addc64(carry, uv.H, u) | |||
t += carry | |||
} | |||
} | |||
v, carry = addc64(0, v, x[i]) | |||
u, carry = addc64(carry, u, 0) | |||
t += carry | |||
z[i-FP_WORDS] = v | |||
v = u | |||
u = t | |||
t = 0 | |||
} | |||
v, carry = addc64(0, v, x[2*FP_WORDS-1]) | |||
z[FP_WORDS-1] = v | |||
} | |||
// Compute z = x + y, without reducing mod p. | |||
func fp2Add(z, x, y *FpX2) { | |||
var carry uint64 | |||
for i := 0; i < 2*FP_WORDS; i++ { | |||
z[i], carry = addc64(carry, x[i], y[i]) | |||
} | |||
} | |||
// Compute z = x - y, without reducing mod p. | |||
func fp2Sub(z, x, y *FpX2) { | |||
var borrow, mask uint64 | |||
for i := 0; i < 2*FP_WORDS; i++ { | |||
z[i], borrow = subc64(borrow, x[i], y[i]) | |||
} | |||
// Sets all bits if borrow = 1 | |||
mask = 0 - borrow | |||
borrow = 0 | |||
for i := FP_WORDS; i < 2*FP_WORDS; i++ { | |||
z[i], borrow = addc64(borrow, z[i], p503[i-FP_WORDS]&mask) | |||
} | |||
} | |||
// Montgomery multiplication. Input values must be already | |||
// in Montgomery domain. | |||
func fpMulRdc(dest, lhs, rhs *Fp) { | |||
a := lhs // = a*R | |||
b := rhs // = b*R | |||
var ab FpX2 | |||
fpMul(&ab, a, b) // = a*b*R*R | |||
fpMontRdc(dest, &ab) // = a*b*R mod p | |||
} | |||
// Set dest = x^((p-3)/4). If x is square, this is 1/sqrt(x). | |||
// Uses variation of sliding-window algorithm from with window size | |||
// of 5 and least to most significant bit sliding (left-to-right) | |||
// See HAC 14.85 for general description. | |||
// | |||
// Allowed to overlap x with dest. | |||
// All values in Montgomery domains | |||
func p34(dest, x *Fp) { | |||
// Set dest = x^(2^k), for k >= 1, by repeated squarings. | |||
pow2k := func(dest, x *Fp, k uint8) { | |||
fpMulRdc(dest, x, x) | |||
for i := uint8(1); i < k; i++ { | |||
fpMulRdc(dest, dest, dest) | |||
} | |||
} | |||
// Sliding-window strategy computed with etc/scripts/sliding_window_strat_calc.py | |||
// | |||
// This performs sum(powStrategy) + 1 squarings and len(lookup) + len(mulStrategy) | |||
// multiplications. | |||
powStrategy := []uint8{1, 12, 5, 5, 2, 7, 11, 3, 8, 4, 11, 4, 7, 5, 6, 3, 7, 5, 7, 2, 12, 5, 6, 4, 6, 8, 6, 4, 7, 5, 5, 8, 5, 8, 5, 5, 8, 9, 3, 6, 2, 10, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3} | |||
mulStrategy := []uint8{0, 12, 11, 10, 0, 1, 8, 3, 7, 1, 8, 3, 6, 7, 14, 2, 14, 14, 9, 0, 13, 9, 15, 5, 12, 7, 13, 7, 15, 6, 7, 9, 0, 5, 7, 6, 8, 8, 3, 7, 0, 10, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3} | |||
// Precompute lookup table of odd multiples of x for window | |||
// size k=5. | |||
lookup := [16]Fp{} | |||
var xx Fp | |||
fpMulRdc(&xx, x, x) | |||
lookup[0] = *x | |||
for i := 1; i < 16; i++ { | |||
fpMulRdc(&lookup[i], &lookup[i-1], &xx) | |||
} | |||
// Now lookup = {x, x^3, x^5, ... } | |||
// so that lookup[i] = x^{2*i + 1} | |||
// so that lookup[k/2] = x^k, for odd k | |||
*dest = lookup[mulStrategy[0]] | |||
for i := uint8(1); i < uint8(len(powStrategy)); i++ { | |||
pow2k(dest, dest, powStrategy[i]) | |||
fpMulRdc(dest, dest, &lookup[mulStrategy[i]]) | |||
} | |||
} | |||
func add(dest, lhs, rhs *Fp2) { | |||
fpAddRdc(&dest.A, &lhs.A, &rhs.A) | |||
fpAddRdc(&dest.B, &lhs.B, &rhs.B) | |||
} | |||
func sub(dest, lhs, rhs *Fp2) { | |||
fpSubRdc(&dest.A, &lhs.A, &rhs.A) | |||
fpSubRdc(&dest.B, &lhs.B, &rhs.B) | |||
} | |||
func mul(dest, lhs, rhs *Fp2) { | |||
// Let (a,b,c,d) = (lhs.a,lhs.b,rhs.a,rhs.b). | |||
a := &lhs.A | |||
b := &lhs.B | |||
c := &rhs.A | |||
d := &rhs.B | |||
// We want to compute | |||
// | |||
// (a + bi)*(c + di) = (a*c - b*d) + (a*d + b*c)i | |||
// | |||
// Use Karatsuba's trick: note that | |||
// | |||
// (b - a)*(c - d) = (b*c + a*d) - a*c - b*d | |||
// | |||
// so (a*d + b*c) = (b-a)*(c-d) + a*c + b*d. | |||
var ac, bd FpX2 | |||
fpMul(&ac, a, c) // = a*c*R*R | |||
fpMul(&bd, b, d) // = b*d*R*R | |||
var b_minus_a, c_minus_d Fp | |||
fpSubRdc(&b_minus_a, b, a) // = (b-a)*R | |||
fpSubRdc(&c_minus_d, c, d) // = (c-d)*R | |||
var ad_plus_bc FpX2 | |||
fpMul(&ad_plus_bc, &b_minus_a, &c_minus_d) // = (b-a)*(c-d)*R*R | |||
fp2Add(&ad_plus_bc, &ad_plus_bc, &ac) // = ((b-a)*(c-d) + a*c)*R*R | |||
fp2Add(&ad_plus_bc, &ad_plus_bc, &bd) // = ((b-a)*(c-d) + a*c + b*d)*R*R | |||
fpMontRdc(&dest.B, &ad_plus_bc) // = (a*d + b*c)*R mod p | |||
var ac_minus_bd FpX2 | |||
fp2Sub(&ac_minus_bd, &ac, &bd) // = (a*c - b*d)*R*R | |||
fpMontRdc(&dest.A, &ac_minus_bd) // = (a*c - b*d)*R mod p | |||
} | |||
func inv(dest, x *Fp2) { | |||
var a2PlusB2 Fp | |||
var asq, bsq FpX2 | |||
var ac FpX2 | |||
var minusB Fp | |||
var minusBC FpX2 | |||
a := &x.A | |||
b := &x.B | |||
// We want to compute | |||
// | |||
// 1 1 (a - bi) (a - bi) | |||
// -------- = -------- -------- = ----------- | |||
// (a + bi) (a + bi) (a - bi) (a^2 + b^2) | |||
// | |||
// Letting c = 1/(a^2 + b^2), this is | |||
// | |||
// 1/(a+bi) = a*c - b*ci. | |||
fpMul(&asq, a, a) // = a*a*R*R | |||
fpMul(&bsq, b, b) // = b*b*R*R | |||
fp2Add(&asq, &asq, &bsq) // = (a^2 + b^2)*R*R | |||
fpMontRdc(&a2PlusB2, &asq) // = (a^2 + b^2)*R mod p | |||
// Now a2PlusB2 = a^2 + b^2 | |||
inv := a2PlusB2 | |||
fpMulRdc(&inv, &a2PlusB2, &a2PlusB2) | |||
p34(&inv, &inv) | |||
fpMulRdc(&inv, &inv, &inv) | |||
fpMulRdc(&inv, &inv, &a2PlusB2) | |||
fpMul(&ac, a, &inv) | |||
fpMontRdc(&dest.A, &ac) | |||
fpSubRdc(&minusB, &minusB, b) | |||
fpMul(&minusBC, &minusB, &inv) | |||
fpMontRdc(&dest.B, &minusBC) | |||
} | |||
func sqr(dest, x *Fp2) { | |||
var a2, aPlusB, aMinusB Fp | |||
var a2MinB2, ab2 FpX2 | |||
a := &x.A | |||
b := &x.B | |||
// (a + bi)*(a + bi) = (a^2 - b^2) + 2abi. | |||
fpAddRdc(&a2, a, a) // = a*R + a*R = 2*a*R | |||
fpAddRdc(&aPlusB, a, b) // = a*R + b*R = (a+b)*R | |||
fpSubRdc(&aMinusB, a, b) // = a*R - b*R = (a-b)*R | |||
fpMul(&a2MinB2, &aPlusB, &aMinusB) // = (a+b)*(a-b)*R*R = (a^2 - b^2)*R*R | |||
fpMul(&ab2, &a2, b) // = 2*a*b*R*R | |||
fpMontRdc(&dest.A, &a2MinB2) // = (a^2 - b^2)*R mod p | |||
fpMontRdc(&dest.B, &ab2) // = 2*a*b*R mod p | |||
} | |||
// In case choice == 1, performs following swap in constant time: | |||
// xPx <-> xQx | |||
// xPz <-> xQz | |||
// Otherwise returns xPx, xPz, xQx, xQz unchanged | |||
func condSwap(xPx, xPz, xQx, xQz *Fp2, choice uint8) { | |||
fpSwapCond(&xPx.A, &xQx.A, choice) | |||
fpSwapCond(&xPx.B, &xQx.B, choice) | |||
fpSwapCond(&xPz.A, &xQz.A, choice) | |||
fpSwapCond(&xPz.B, &xQz.B, choice) | |||
} |
@@ -0,0 +1,287 @@ | |||
package sike | |||
// I keep it bool in order to be able to apply logical NOT | |||
type KeyVariant uint | |||
// Representation of an element of the base field F_p. | |||
// | |||
// No particular meaning is assigned to the representation -- it could represent | |||
// an element in Montgomery form, or not. Tracking the meaning of the field | |||
// element is left to higher types. | |||
type Fp [FP_WORDS]uint64 | |||
// Represents an intermediate product of two elements of the base field F_p. | |||
type FpX2 [2 * FP_WORDS]uint64 | |||
// Represents an element of the extended field Fp^2 = Fp(x+i) | |||
type Fp2 struct { | |||
A Fp | |||
B Fp | |||
} | |||
type DomainParams struct { | |||
// P, Q and R=P-Q base points | |||
Affine_P, Affine_Q, Affine_R Fp2 | |||
// Size of a compuatation strategy for x-torsion group | |||
IsogenyStrategy []uint32 | |||
// Max size of secret key for x-torsion group | |||
SecretBitLen uint | |||
// Max size of secret key for x-torsion group | |||
SecretByteLen uint | |||
} | |||
type SidhParams struct { | |||
Id uint8 | |||
// Bytelen of P | |||
Bytelen int | |||
// The public key size, in bytes. | |||
PublicKeySize int | |||
// The shared secret size, in bytes. | |||
SharedSecretSize int | |||
// 2- and 3-torsion group parameter definitions | |||
A, B DomainParams | |||
// Precomputed identity element in the Fp2 in Montgomery domain | |||
OneFp2 Fp2 | |||
// Precomputed 1/2 in the Fp2 in Montgomery domain | |||
HalfFp2 Fp2 | |||
// Length of SIKE secret message. Must be one of {24,32,40}, | |||
// depending on size of prime field used (see [SIKE], 1.4 and 5.1) | |||
MsgLen int | |||
// Length of SIKE ephemeral KEM key (see [SIKE], 1.4 and 5.1) | |||
KemSize int | |||
} | |||
// Stores curve projective parameters equivalent to A/C. Meaning of the | |||
// values depends on the context. When working with isogenies over | |||
// subgroup that are powers of: | |||
// * three then (A:C) ~ (A+2C:A-2C) | |||
// * four then (A:C) ~ (A+2C: 4C) | |||
// See Appendix A of SIKE for more details | |||
type CurveCoefficientsEquiv struct { | |||
A Fp2 | |||
C Fp2 | |||
} | |||
// A point on the projective line P^1(F_{p^2}). | |||
// | |||
// This represents a point on the Kummer line of a Montgomery curve. The | |||
// curve is specified by a ProjectiveCurveParameters struct. | |||
type ProjectivePoint struct { | |||
X Fp2 | |||
Z Fp2 | |||
} | |||
// Base type for public and private key. Used mainly to carry domain | |||
// parameters. | |||
type key struct { | |||
// Domain parameters of the algorithm to be used with a key | |||
params *SidhParams | |||
// Flag indicates wether corresponds to 2-, 3-torsion group or SIKE | |||
keyVariant KeyVariant | |||
} | |||
// Defines operations on private key | |||
type PrivateKey struct { | |||
key | |||
// Secret key | |||
Scalar []byte | |||
// Used only by KEM | |||
S []byte | |||
} | |||
// Defines operations on public key | |||
type PublicKey struct { | |||
key | |||
affine_xP Fp2 | |||
affine_xQ Fp2 | |||
affine_xQmP Fp2 | |||
} | |||
// A point on the projective line P^1(F_{p^2}). | |||
// | |||
// This is used to work projectively with the curve coefficients. | |||
type ProjectiveCurveParameters struct { | |||
A Fp2 | |||
C Fp2 | |||
} | |||
const ( | |||
// First 2 bits identify SIDH variant third bit indicates | |||
// wether key is a SIKE variant (set) or SIDH (not set) | |||
// 001 - SIDH: corresponds to 2-torsion group | |||
KeyVariant_SIDH_A KeyVariant = 1 << 0 | |||
// 010 - SIDH: corresponds to 3-torsion group | |||
KeyVariant_SIDH_B = 1 << 1 | |||
// 110 - SIKE | |||
KeyVariant_SIKE = 1<<2 | KeyVariant_SIDH_B | |||
// Number of uint64 limbs used to store field element | |||
FP_WORDS = 8 | |||
) | |||
// Used internally by this package | |||
// ------------------------------- | |||
var p503 = Fp{ | |||
0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xABFFFFFFFFFFFFFF, | |||
0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E, | |||
} | |||
// 2*503 | |||
var p503x2 = Fp{ | |||
0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x57FFFFFFFFFFFFFF, | |||
0x2610B7B44423CF41, 0x3737ED90F6FCFB5E, 0xC08B8D7BB4EF49A0, 0x0080CDEA83023C3C, | |||
} | |||
// p503 + 1 | |||
var p503p1 = Fp{ | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xAC00000000000000, | |||
0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E, | |||
} | |||
// R^2=(2^512)^2 mod p | |||
var p503R2 = Fp{ | |||
0x5289A0CF641D011F, 0x9B88257189FED2B9, 0xA3B365D58DC8F17A, 0x5BC57AB6EFF168EC, | |||
0x9E51998BD84D4423, 0xBF8999CBAC3B5695, 0x46E9127BCE14CDB6, 0x003F6CFCE8B81771, | |||
} | |||
// p503 + 1 left-shifted by 8, assuming little endianness | |||
var p503p1s8 = Fp{ | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, | |||
0x085BDA2211E7A0AC, 0x9BF6C87B7E7DAF13, 0x45C6BDDA77A4D01B, 0x4066F541811E1E60, | |||
} | |||
// 1*R mod p | |||
var P503_OneFp2 = Fp2{ | |||
A: Fp{ | |||
0x00000000000003F9, 0x0000000000000000, 0x0000000000000000, 0xB400000000000000, | |||
0x63CB1A6EA6DED2B4, 0x51689D8D667EB37D, 0x8ACD77C71AB24142, 0x0026FBAEC60F5953}, | |||
} | |||
// 1/2 * R mod p | |||
var P503_HalfFp2 = Fp2{ | |||
A: Fp{ | |||
0x00000000000001FC, 0x0000000000000000, 0x0000000000000000, 0xB000000000000000, | |||
0x3B69BB2464785D2A, 0x36824A2AF0FE9896, 0xF5899F427A94F309, 0x0033B15203C83BB8}, | |||
} | |||
var Params SidhParams | |||
func init() { | |||
Params = SidhParams{ | |||
// SIDH public key byte size. | |||
PublicKeySize: 378, | |||
// SIDH shared secret byte size. | |||
SharedSecretSize: 126, | |||
A: DomainParams{ | |||
// The x-coordinate of PA | |||
Affine_P: Fp2{ | |||
A: Fp{ | |||
0xE7EF4AA786D855AF, 0xED5758F03EB34D3B, 0x09AE172535A86AA9, 0x237B9CC07D622723, | |||
0xE3A284CBA4E7932D, 0x27481D9176C5E63F, 0x6A323FF55C6E71BF, 0x002ECC31A6FB8773, | |||
}, | |||
B: Fp{ | |||
0x64D02E4E90A620B8, 0xDAB8128537D4B9F1, 0x4BADF77B8A228F98, 0x0F5DBDF9D1FB7D1B, | |||
0xBEC4DB288E1A0DCC, 0xE76A8665E80675DB, 0x6D6F252E12929463, 0x003188BD1463FACC, | |||
}, | |||
}, | |||
// The x-coordinate of QA | |||
Affine_Q: Fp2{ | |||
A: Fp{ | |||
0xB79D41025DE85D56, 0x0B867DA9DF169686, 0x740E5368021C827D, 0x20615D72157BF25C, | |||
0xFF1590013C9B9F5B, 0xC884DCADE8C16CEA, 0xEBD05E53BF724E01, 0x0032FEF8FDA5748C, | |||
}, | |||
B: Fp{ | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, | |||
}, | |||
}, | |||
// The x-coordinate of RA = PA-QA | |||
Affine_R: Fp2{ | |||
A: Fp{ | |||
0x12E2E849AA0A8006, 0x41CF47008635A1E8, 0x9CD720A70798AED7, 0x42A820B42FCF04CF, | |||
0x7BF9BAD32AAE88B1, 0xF619127A54090BBE, 0x1CB10D8F56408EAA, 0x001D6B54C3C0EDEB, | |||
}, | |||
B: Fp{ | |||
0x34DB54931CBAAC36, 0x420A18CB8DD5F0C4, 0x32008C1A48C0F44D, 0x3B3BA772B1CFD44D, | |||
0xA74B058FDAF13515, 0x095FC9CA7EEC17B4, 0x448E829D28F120F8, 0x00261EC3ED16A489, | |||
}, | |||
}, | |||
// Max size of secret key for 2-torsion group, corresponds to 2^e2 - 1 | |||
SecretBitLen: 250, | |||
// SecretBitLen in bytes. | |||
SecretByteLen: uint((250 + 7) / 8), | |||
// 2-torsion group computation strategy | |||
IsogenyStrategy: []uint32{ | |||
0x3D, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, | |||
0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, | |||
0x01, 0x01, 0x02, 0x01, 0x01, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, | |||
0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, | |||
0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x1D, 0x10, 0x08, 0x04, 0x02, 0x01, | |||
0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, | |||
0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x0D, 0x08, | |||
0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, | |||
0x05, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01}, | |||
}, | |||
B: DomainParams{ | |||
// The x-coordinate of PB | |||
Affine_P: Fp2{ | |||
A: Fp{ | |||
0x7EDE37F4FA0BC727, 0xF7F8EC5C8598941C, 0xD15519B516B5F5C8, 0xF6D5AC9B87A36282, | |||
0x7B19F105B30E952E, 0x13BD8B2025B4EBEE, 0x7B96D27F4EC579A2, 0x00140850CAB7E5DE, | |||
}, | |||
B: Fp{ | |||
0x7764909DAE7B7B2D, 0x578ABB16284911AB, 0x76E2BFD146A6BF4D, 0x4824044B23AA02F0, | |||
0x1105048912A321F3, 0xB8A2E482CF0F10C1, 0x42FF7D0BE2152085, 0x0018E599C5223352, | |||
}, | |||
}, | |||
// The x-coordinate of QB | |||
Affine_Q: Fp2{ | |||
A: Fp{ | |||
0x4256C520FB388820, 0x744FD7C3BAAF0A13, 0x4B6A2DDDB12CBCB8, 0xE46826E27F427DF8, | |||
0xFE4A663CD505A61B, 0xD6B3A1BAF025C695, 0x7C3BB62B8FCC00BD, 0x003AFDDE4A35746C, | |||
}, | |||
B: Fp{ | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, | |||
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, | |||
}, | |||
}, | |||
// The x-coordinate of RB = PB - QB | |||
Affine_R: Fp2{ | |||
A: Fp{ | |||
0x75601CD1E6C0DFCB, 0x1A9007239B58F93E, 0xC1F1BE80C62107AC, 0x7F513B898F29FF08, | |||
0xEA0BEDFF43E1F7B2, 0x2C6D94018CBAE6D0, 0x3A430D31BCD84672, 0x000D26892ECCFE83, | |||
}, | |||
B: Fp{ | |||
0x1119D62AEA3007A1, 0xE3702AA4E04BAE1B, 0x9AB96F7D59F990E7, 0xF58440E8B43319C0, | |||
0xAF8134BEE1489775, 0xE7F7774E905192AA, 0xF54AE09308E98039, 0x001EF7A041A86112, | |||
}, | |||
}, | |||
// Size of secret key for 3-torsion group, corresponds to log_2(3^e3) - 1. | |||
SecretBitLen: 252, | |||
// SecretBitLen in bytes. | |||
SecretByteLen: uint((252 + 7) / 8), | |||
// 3-torsion group computation strategy | |||
IsogenyStrategy: []uint32{ | |||
0x47, 0x26, 0x15, 0x0D, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, | |||
0x01, 0x01, 0x02, 0x01, 0x01, 0x05, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, | |||
0x01, 0x01, 0x01, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, | |||
0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x11, 0x09, 0x05, 0x03, 0x02, | |||
0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, | |||
0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, | |||
0x01, 0x02, 0x01, 0x01, 0x21, 0x11, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, | |||
0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, | |||
0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, | |||
0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, | |||
0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, | |||
0x01, 0x02, 0x01, 0x01}, | |||
}, | |||
OneFp2: P503_OneFp2, | |||
HalfFp2: P503_HalfFp2, | |||
MsgLen: 24, | |||
// SIKEp503 provides 128 bit of classical security ([SIKE], 5.1) | |||
KemSize: 16, | |||
// ceil(503+7/8) | |||
Bytelen: 63, | |||
} | |||
} |
@@ -0,0 +1,408 @@ | |||
package sike | |||
// Interface for working with isogenies. | |||
type isogeny interface { | |||
// Given a torsion point on a curve computes isogenous curve. | |||
// Returns curve coefficients (A:C), so that E_(A/C) = E_(A/C)/<P>, | |||
// where P is a provided projective point. Sets also isogeny constants | |||
// that are needed for isogeny evaluation. | |||
GenerateCurve(*ProjectivePoint) CurveCoefficientsEquiv | |||
// Evaluates isogeny at caller provided point. Requires isogeny curve constants | |||
// to be earlier computed by GenerateCurve. | |||
EvaluatePoint(*ProjectivePoint) ProjectivePoint | |||
} | |||
// Stores isogeny 3 curve constants | |||
type isogeny3 struct { | |||
K1 Fp2 | |||
K2 Fp2 | |||
} | |||
// Stores isogeny 4 curve constants | |||
type isogeny4 struct { | |||
isogeny3 | |||
K3 Fp2 | |||
} | |||
// Constructs isogeny3 objects | |||
func NewIsogeny3() isogeny { | |||
return &isogeny3{} | |||
} | |||
// Constructs isogeny4 objects | |||
func NewIsogeny4() isogeny { | |||
return &isogeny4{} | |||
} | |||
// Helper function for RightToLeftLadder(). Returns A+2C / 4. | |||
func calcAplus2Over4(cparams *ProjectiveCurveParameters) (ret Fp2) { | |||
var tmp Fp2 | |||
// 2C | |||
add(&tmp, &cparams.C, &cparams.C) | |||
// A+2C | |||
add(&ret, &cparams.A, &tmp) | |||
// 1/4C | |||
add(&tmp, &tmp, &tmp) | |||
inv(&tmp, &tmp) | |||
// A+2C/4C | |||
mul(&ret, &ret, &tmp) | |||
return | |||
} | |||
// Converts values in x.A and x.B to Montgomery domain | |||
// x.A = x.A * R mod p | |||
// x.B = x.B * R mod p | |||
// Performs v = v*R^2*R^(-1) mod p, for both x.A and x.B | |||
func toMontDomain(x *Fp2) { | |||
var aRR FpX2 | |||
// convert to montgomery domain | |||
fpMul(&aRR, &x.A, &p503R2) // = a*R*R | |||
fpMontRdc(&x.A, &aRR) // = a*R mod p | |||
fpMul(&aRR, &x.B, &p503R2) | |||
fpMontRdc(&x.B, &aRR) | |||
} | |||
// Converts values in x.A and x.B from Montgomery domain | |||
// a = x.A mod p | |||
// b = x.B mod p | |||
// | |||
// After returning from the call x is not modified. | |||
func fromMontDomain(x *Fp2, out *Fp2) { | |||
var aR FpX2 | |||
// convert from montgomery domain | |||
copy(aR[:], x.A[:]) | |||
fpMontRdc(&out.A, &aR) // = a mod p in [0, 2p) | |||
fpRdcP(&out.A) // = a mod p in [0, p) | |||
for i := range aR { | |||
aR[i] = 0 | |||
} | |||
copy(aR[:], x.B[:]) | |||
fpMontRdc(&out.B, &aR) | |||
fpRdcP(&out.B) | |||
} | |||
// Computes j-invariant for a curve y2=x3+A/Cx+x with A,C in F_(p^2). Result | |||
// is returned in 'j'. Implementation corresponds to Algorithm 9 from SIKE. | |||
func Jinvariant(cparams *ProjectiveCurveParameters, j *Fp2) { | |||
var t0, t1 Fp2 | |||
sqr(j, &cparams.A) // j = A^2 | |||
sqr(&t1, &cparams.C) // t1 = C^2 | |||
add(&t0, &t1, &t1) // t0 = t1 + t1 | |||
sub(&t0, j, &t0) // t0 = j - t0 | |||
sub(&t0, &t0, &t1) // t0 = t0 - t1 | |||
sub(j, &t0, &t1) // t0 = t0 - t1 | |||
sqr(&t1, &t1) // t1 = t1^2 | |||
mul(j, j, &t1) // j = j * t1 | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
sqr(&t1, &t0) // t1 = t0^2 | |||
mul(&t0, &t0, &t1) // t0 = t0 * t1 | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
inv(j, j) // j = 1/j | |||
mul(j, &t0, j) // j = t0 * j | |||
} | |||
// Given affine points x(P), x(Q) and x(Q-P) in a extension field F_{p^2}, function | |||
// recorvers projective coordinate A of a curve. This is Algorithm 10 from SIKE. | |||
func RecoverCoordinateA(curve *ProjectiveCurveParameters, xp, xq, xr *Fp2) { | |||
var t0, t1 Fp2 | |||
add(&t1, xp, xq) // t1 = Xp + Xq | |||
mul(&t0, xp, xq) // t0 = Xp * Xq | |||
mul(&curve.A, xr, &t1) // A = X(q-p) * t1 | |||
add(&curve.A, &curve.A, &t0) // A = A + t0 | |||
mul(&t0, &t0, xr) // t0 = t0 * X(q-p) | |||
sub(&curve.A, &curve.A, &Params.OneFp2) // A = A - 1 | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
add(&t1, &t1, xr) // t1 = t1 + X(q-p) | |||
add(&t0, &t0, &t0) // t0 = t0 + t0 | |||
sqr(&curve.A, &curve.A) // A = A^2 | |||
inv(&t0, &t0) // t0 = 1/t0 | |||
mul(&curve.A, &curve.A, &t0) // A = A * t0 | |||
sub(&curve.A, &curve.A, &t1) // A = A - t1 | |||
} | |||
// Computes equivalence (A:C) ~ (A+2C : A-2C) | |||
func CalcCurveParamsEquiv3(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { | |||
var coef CurveCoefficientsEquiv | |||
var c2 Fp2 | |||
add(&c2, &cparams.C, &cparams.C) | |||
// A24p = A+2*C | |||
add(&coef.A, &cparams.A, &c2) | |||
// A24m = A-2*C | |||
sub(&coef.C, &cparams.A, &c2) | |||
return coef | |||
} | |||
// Computes equivalence (A:C) ~ (A+2C : 4C) | |||
func CalcCurveParamsEquiv4(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { | |||
var coefEq CurveCoefficientsEquiv | |||
add(&coefEq.C, &cparams.C, &cparams.C) | |||
// A24p = A+2C | |||
add(&coefEq.A, &cparams.A, &coefEq.C) | |||
// C24 = 4*C | |||
add(&coefEq.C, &coefEq.C, &coefEq.C) | |||
return coefEq | |||
} | |||
// Recovers (A:C) curve parameters from projectively equivalent (A+2C:A-2C). | |||
func RecoverCurveCoefficients3(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { | |||
add(&cparams.A, &coefEq.A, &coefEq.C) | |||
// cparams.A = 2*(A+2C+A-2C) = 4A | |||
add(&cparams.A, &cparams.A, &cparams.A) | |||
// cparams.C = (A+2C-A+2C) = 4C | |||
sub(&cparams.C, &coefEq.A, &coefEq.C) | |||
return | |||
} | |||
// Recovers (A:C) curve parameters from projectively equivalent (A+2C:4C). | |||
func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { | |||
// cparams.C = (4C)*1/2=2C | |||
mul(&cparams.C, &coefEq.C, &Params.HalfFp2) | |||
// cparams.A = A+2C - 2C = A | |||
sub(&cparams.A, &coefEq.A, &cparams.C) | |||
// cparams.C = 2C * 1/2 = C | |||
mul(&cparams.C, &cparams.C, &Params.HalfFp2) | |||
return | |||
} | |||
// Combined coordinate doubling and differential addition. Takes projective points | |||
// P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. | |||
// Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE | |||
func xDbladd(P, Q, QmP *ProjectivePoint, a24 *Fp2) (dblP, PaQ ProjectivePoint) { | |||
var t0, t1, t2 Fp2 | |||
xQmP, zQmP := &QmP.X, &QmP.Z | |||
xPaQ, zPaQ := &PaQ.X, &PaQ.Z | |||
x2P, z2P := &dblP.X, &dblP.Z | |||
xP, zP := &P.X, &P.Z | |||
xQ, zQ := &Q.X, &Q.Z | |||
add(&t0, xP, zP) // t0 = Xp+Zp | |||
sub(&t1, xP, zP) // t1 = Xp-Zp | |||
sqr(x2P, &t0) // 2P.X = t0^2 | |||
sub(&t2, xQ, zQ) // t2 = Xq-Zq | |||
add(xPaQ, xQ, zQ) // Xp+q = Xq+Zq | |||
mul(&t0, &t0, &t2) // t0 = t0 * t2 | |||
mul(z2P, &t1, &t1) // 2P.Z = t1 * t1 | |||
mul(&t1, &t1, xPaQ) // t1 = t1 * Xp+q | |||
sub(&t2, x2P, z2P) // t2 = 2P.X - 2P.Z | |||
mul(x2P, x2P, z2P) // 2P.X = 2P.X * 2P.Z | |||
mul(xPaQ, a24, &t2) // Xp+q = A24 * t2 | |||
sub(zPaQ, &t0, &t1) // Zp+q = t0 - t1 | |||
add(z2P, xPaQ, z2P) // 2P.Z = Xp+q + 2P.Z | |||
add(xPaQ, &t0, &t1) // Xp+q = t0 + t1 | |||
mul(z2P, z2P, &t2) // 2P.Z = 2P.Z * t2 | |||
sqr(zPaQ, zPaQ) // Zp+q = Zp+q ^ 2 | |||
sqr(xPaQ, xPaQ) // Xp+q = Xp+q ^ 2 | |||
mul(zPaQ, xQmP, zPaQ) // Zp+q = Xq-p * Zp+q | |||
mul(xPaQ, zQmP, xPaQ) // Xp+q = Zq-p * Xp+q | |||
return | |||
} | |||
// Given the curve parameters, xP = x(P), computes xP = x([2^k]P) | |||
// Safe to overlap xP, x2P. | |||
func Pow2k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { | |||
var t0, t1 Fp2 | |||
x, z := &xP.X, &xP.Z | |||
for i := uint32(0); i < k; i++ { | |||
sub(&t0, x, z) // t0 = Xp - Zp | |||
add(&t1, x, z) // t1 = Xp + Zp | |||
sqr(&t0, &t0) // t0 = t0 ^ 2 | |||
sqr(&t1, &t1) // t1 = t1 ^ 2 | |||
mul(z, ¶ms.C, &t0) // Z2p = C24 * t0 | |||
mul(x, z, &t1) // X2p = Z2p * t1 | |||
sub(&t1, &t1, &t0) // t1 = t1 - t0 | |||
mul(&t0, ¶ms.A, &t1) // t0 = A24+ * t1 | |||
add(z, z, &t0) // Z2p = Z2p + t0 | |||
mul(z, z, &t1) // Zp = Z2p * t1 | |||
} | |||
} | |||
// Given the curve parameters, xP = x(P), and k >= 0, compute xP = x([3^k]P). | |||
// | |||
// Safe to overlap xP, xR. | |||
func Pow3k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { | |||
var t0, t1, t2, t3, t4, t5, t6 Fp2 | |||
x, z := &xP.X, &xP.Z | |||
for i := uint32(0); i < k; i++ { | |||
sub(&t0, x, z) // t0 = Xp - Zp | |||
sqr(&t2, &t0) // t2 = t0^2 | |||
add(&t1, x, z) // t1 = Xp + Zp | |||
sqr(&t3, &t1) // t3 = t1^2 | |||
add(&t4, &t1, &t0) // t4 = t1 + t0 | |||
sub(&t0, &t1, &t0) // t0 = t1 - t0 | |||
sqr(&t1, &t4) // t1 = t4^2 | |||
sub(&t1, &t1, &t3) // t1 = t1 - t3 | |||
sub(&t1, &t1, &t2) // t1 = t1 - t2 | |||
mul(&t5, &t3, ¶ms.A) // t5 = t3 * A24+ | |||
mul(&t3, &t3, &t5) // t3 = t5 * t3 | |||
mul(&t6, &t2, ¶ms.C) // t6 = t2 * A24- | |||
mul(&t2, &t2, &t6) // t2 = t2 * t6 | |||
sub(&t3, &t2, &t3) // t3 = t2 - t3 | |||
sub(&t2, &t5, &t6) // t2 = t5 - t6 | |||
mul(&t1, &t2, &t1) // t1 = t2 * t1 | |||
add(&t2, &t3, &t1) // t2 = t3 + t1 | |||
sqr(&t2, &t2) // t2 = t2^2 | |||
mul(x, &t2, &t4) // X3p = t2 * t4 | |||
sub(&t1, &t3, &t1) // t1 = t3 - t1 | |||
sqr(&t1, &t1) // t1 = t1^2 | |||
mul(z, &t1, &t0) // Z3p = t1 * t0 | |||
} | |||
} | |||
// Set (y1, y2, y3) = (1/x1, 1/x2, 1/x3). | |||
// | |||
// All xi, yi must be distinct. | |||
func Fp2Batch3Inv(x1, x2, x3, y1, y2, y3 *Fp2) { | |||
var x1x2, t Fp2 | |||
mul(&x1x2, x1, x2) // x1*x2 | |||
mul(&t, &x1x2, x3) // 1/(x1*x2*x3) | |||
inv(&t, &t) | |||
mul(y1, &t, x2) // 1/x1 | |||
mul(y1, y1, x3) | |||
mul(y2, &t, x1) // 1/x2 | |||
mul(y2, y2, x3) | |||
mul(y3, &t, &x1x2) // 1/x3 | |||
} | |||
// ScalarMul3Pt is a right-to-left point multiplication that given the | |||
// x-coordinate of P, Q and P-Q calculates the x-coordinate of R=Q+[scalar]P. | |||
// nbits must be smaller or equal to len(scalar). | |||
func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint, scalar []uint8) ProjectivePoint { | |||
var R0, R2, R1 ProjectivePoint | |||
aPlus2Over4 := calcAplus2Over4(cparams) | |||
R1 = *P | |||
R2 = *PmQ | |||
R0 = *Q | |||
// Iterate over the bits of the scalar, bottom to top | |||
prevBit := uint8(0) | |||
for i := uint(0); i < nbits; i++ { | |||
bit := (scalar[i>>3] >> (i & 7) & 1) | |||
swap := prevBit ^ bit | |||
prevBit = bit | |||
condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) | |||
R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) | |||
} | |||
condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) | |||
return R1 | |||
} | |||
// Given a three-torsion point p = x(PB) on the curve E_(A:C), construct the | |||
// three-isogeny phi : E_(A:C) -> E_(A:C)/<P_3> = E_(A':C'). | |||
// | |||
// Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C | |||
// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/<P3> | |||
// * isogeny phi with constants in F_p^2 | |||
func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { | |||
var t0, t1, t2, t3, t4 Fp2 | |||
var coefEq CurveCoefficientsEquiv | |||
var K1, K2 = &phi.K1, &phi.K2 | |||
sub(K1, &p.X, &p.Z) // K1 = XP3 - ZP3 | |||
sqr(&t0, K1) // t0 = K1^2 | |||
add(K2, &p.X, &p.Z) // K2 = XP3 + ZP3 | |||
sqr(&t1, K2) // t1 = K2^2 | |||
add(&t2, &t0, &t1) // t2 = t0 + t1 | |||
add(&t3, K1, K2) // t3 = K1 + K2 | |||
sqr(&t3, &t3) // t3 = t3^2 | |||
sub(&t3, &t3, &t2) // t3 = t3 - t2 | |||
add(&t2, &t1, &t3) // t2 = t1 + t3 | |||
add(&t3, &t3, &t0) // t3 = t3 + t0 | |||
add(&t4, &t3, &t0) // t4 = t3 + t0 | |||
add(&t4, &t4, &t4) // t4 = t4 + t4 | |||
add(&t4, &t1, &t4) // t4 = t1 + t4 | |||
mul(&coefEq.C, &t2, &t4) // A24m = t2 * t4 | |||
add(&t4, &t1, &t2) // t4 = t1 + t2 | |||
add(&t4, &t4, &t4) // t4 = t4 + t4 | |||
add(&t4, &t0, &t4) // t4 = t0 + t4 | |||
mul(&t4, &t3, &t4) // t4 = t3 * t4 | |||
sub(&t0, &t4, &coefEq.C) // t0 = t4 - A24m | |||
add(&coefEq.A, &coefEq.C, &t0) // A24p = A24m + t0 | |||
return coefEq | |||
} | |||
// Given a 3-isogeny phi and a point pB = x(PB), compute x(QB), the x-coordinate | |||
// of the image QB = phi(PB) of PB under phi : E_(A:C) -> E_(A':C'). | |||
// | |||
// The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve | |||
// parameters are returned by the GenerateCurve function used to construct phi. | |||
func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { | |||
var t0, t1, t2 Fp2 | |||
var q ProjectivePoint | |||
var K1, K2 = &phi.K1, &phi.K2 | |||
var px, pz = &p.X, &p.Z | |||
add(&t0, px, pz) // t0 = XQ + ZQ | |||
sub(&t1, px, pz) // t1 = XQ - ZQ | |||
mul(&t0, K1, &t0) // t2 = K1 * t0 | |||
mul(&t1, K2, &t1) // t1 = K2 * t1 | |||
add(&t2, &t0, &t1) // t2 = t0 + t1 | |||
sub(&t0, &t1, &t0) // t0 = t1 - t0 | |||
sqr(&t2, &t2) // t2 = t2 ^ 2 | |||
sqr(&t0, &t0) // t0 = t0 ^ 2 | |||
mul(&q.X, px, &t2) // XQ'= XQ * t2 | |||
mul(&q.Z, pz, &t0) // ZQ'= ZQ * t0 | |||
return q | |||
} | |||
// Given a four-torsion point p = x(PB) on the curve E_(A:C), construct the | |||
// four-isogeny phi : E_(A:C) -> E_(A:C)/<P_4> = E_(A':C'). | |||
// | |||
// Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C | |||
// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/<P4> | |||
// * isogeny phi with constants in F_p^2 | |||
func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { | |||
var coefEq CurveCoefficientsEquiv | |||
var xp4, zp4 = &p.X, &p.Z | |||
var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 | |||
sub(K2, xp4, zp4) | |||
add(K3, xp4, zp4) | |||
sqr(K1, zp4) | |||
add(K1, K1, K1) | |||
sqr(&coefEq.C, K1) | |||
add(K1, K1, K1) | |||
sqr(&coefEq.A, xp4) | |||
add(&coefEq.A, &coefEq.A, &coefEq.A) | |||
sqr(&coefEq.A, &coefEq.A) | |||
return coefEq | |||
} | |||
// Given a 4-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate | |||
// of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C'). | |||
// | |||
// Input: isogeny returned by GenerateCurve and point q=(Qx,Qz) from E0_A/C | |||
// Output: Corresponding point q from E1_A'/C', where E1 is 4-isogenous to E0 | |||
func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { | |||
var t0, t1 Fp2 | |||
var q = *p | |||
var xq, zq = &q.X, &q.Z | |||
var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 | |||
add(&t0, xq, zq) | |||
sub(&t1, xq, zq) | |||
mul(xq, &t0, K2) | |||
mul(zq, &t1, K3) | |||
mul(&t0, &t0, &t1) | |||
mul(&t0, &t0, K1) | |||
add(&t1, xq, zq) | |||
sub(zq, xq, zq) | |||
sqr(&t1, &t1) | |||
sqr(zq, zq) | |||
add(xq, &t0, &t1) | |||
sub(&t0, zq, &t0) | |||
mul(xq, xq, &t1) | |||
mul(zq, zq, &t0) | |||
return q | |||
} |
@@ -0,0 +1,695 @@ | |||
package sike | |||
import ( | |||
"crypto/hmac" | |||
"crypto/sha256" | |||
"crypto/subtle" | |||
"errors" | |||
"io" | |||
) | |||
// 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} | |||
// Generates HMAC-SHA256 sum | |||
func hashMac(out, in, S []byte) { | |||
h := hmac.New(sha256.New, in) | |||
h.Write(S) | |||
copy(out, h.Sum(nil)) | |||
} | |||
// 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) | |||
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, xR ProjectivePoint | |||
var invZP, invZQ, invZR Fp2 | |||
var tmp ProjectiveCurveParameters | |||
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 | |||
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) | |||
return | |||
} | |||
// Generate a public key in the 3-torsion group | |||
func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) { | |||
var xPB, xQB, xRB, xR ProjectivePoint | |||
var xPA, xQA, xRA ProjectivePoint | |||
var invZP, invZQ, invZR Fp2 | |||
var tmp ProjectiveCurveParameters | |||
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} | |||
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) | |||
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 cparam ProjectiveCurveParameters | |||
var xP, xQ, xQmP ProjectivePoint | |||
var xR ProjectivePoint | |||
var phi = NewIsogeny4() | |||
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) | |||
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 xR ProjectivePoint | |||
var cparam ProjectiveCurveParameters | |||
var phi = NewIsogeny3() | |||
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) | |||
convFp2ToBytes(sharedSecret, &jInv) | |||
return sharedSecret | |||
} | |||
func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) { | |||
var n [40]byte // n can is max 320-bit (see 1.4 of [SIKE]) | |||
var ptextLen = len(ptext) | |||
if pkB.keyVariant != KeyVariant_SIKE { | |||
return nil, errors.New("wrong key type") | |||
} | |||
j, err := DeriveSecret(skA, pkB) | |||
if err != nil { | |||
return nil, err | |||
} | |||
hashMac(n[:ptextLen], j, F) | |||
for i, _ := range ptext { | |||
n[i] ^= ptext[i] | |||
} | |||
ret := make([]byte, pkA.Size()+ptextLen) | |||
copy(ret, pkA.Export()) | |||
copy(ret[pkA.Size():], n[:ptextLen]) | |||
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 == 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() *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 formated 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 + 8) { | |||
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 n [40]byte // n can is max 320-bit (see 1.4 of [SIKE]) | |||
var c1_len int | |||
var 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) + 8) { | |||
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 | |||
} | |||
hashMac(n[:c1_len], j, F) | |||
for i, _ := range n[:c1_len] { | |||
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 formated input was provided. | |||
func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) { | |||
// Buffer for random, secret message | |||
var ptext = make([]byte, pub.params.MsgLen) | |||
// r = G(ptext||pub) | |||
var r = make([]byte, pub.params.A.SecretByteLen) | |||
// Resulting shared secret | |||
secret = make([]byte, pub.params.KemSize) | |||
// Generate ephemeral value | |||
_, err = io.ReadFull(rng, ptext) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
// must be big enough to store ptext+c0+c1 | |||
var hmac_key = make([]byte, pub.Size()+2*Params.MsgLen) | |||
copy(hmac_key, ptext) | |||
copy(hmac_key[len(ptext):], pub.Export()) | |||
hashMac(r, hmac_key[:len(ptext)+pub.Size()], G) | |||
// Ensure bitlength is not bigger then to 2^e2-1 | |||
r[len(r)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1 | |||
// (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)) | |||
copy(hmac_key, ptext) | |||
copy(hmac_key[len(ptext):], ctext) | |||
hashMac(secret, hmac_key[:len(ptext)+len(ctext)], H) | |||
return ctext, secret, 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 Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) { | |||
var r = make([]byte, pub.params.A.SecretByteLen) | |||
// Resulting shared secret | |||
var secret = make([]byte, pub.params.KemSize) | |||
var skA = NewPrivateKey(KeyVariant_SIDH_A) | |||
m, err := Decrypt(prv, ctext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// r' = G(m'||pub) | |||
var hmac_key = make([]byte, pub.Size()+2*Params.MsgLen) | |||
copy(hmac_key, m) | |||
copy(hmac_key[len(m):], pub.Export()) | |||
hashMac(r, hmac_key[:len(m)+pub.Size()], G) | |||
// Ensure bitlength is not bigger than 2^e2-1 | |||
r[len(r)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1 | |||
// Never fails | |||
skA.Import(r) | |||
// Never fails | |||
pkA := skA.GeneratePublicKey() | |||
c0 := pkA.Export() | |||
if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 { | |||
copy(hmac_key, m) | |||
} else { | |||
// 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 in case | |||
// | |||
// See more details in "On the security of supersingular isogeny cryptosystems" | |||
// (S. Galbraith, et al., 2016, ePrint #859). | |||
copy(hmac_key, prv.S) | |||
} | |||
copy(hmac_key[len(m):], ctext) | |||
hashMac(secret, hmac_key[:len(m)+len(ctext)], H) | |||
return secret, nil | |||
} |
@@ -0,0 +1,658 @@ | |||
package sike | |||
import ( | |||
"bufio" | |||
"bytes" | |||
"crypto/rand" | |||
"encoding/hex" | |||
"math/big" | |||
"strings" | |||
"testing" | |||
) | |||
var tdata = struct { | |||
name string | |||
PrB_sidh string | |||
PkB_sidh string | |||
PkB_sike string | |||
PrB_sike string | |||
PrA_sike string | |||
PkA_sike string | |||
}{ | |||
name: "P-503", | |||
PkB_sike: "68460C22466E95864CFEA7B5D9077E768FF4F9ED69AE56D7CF3F236FB06B31020EEE34B5B572CEA5DDF20B531966AA8F5F3ACC0C6D1CE04EEDC30FD1F1233E2D96FE60C6D638FC646EAF2E2246F1AEC96859CE874A1F029A78F9C978CD6B22114A0D5AB20101191FD923E80C76908B1498B9D0200065CCA09159A0C65A1E346CC6470314FE78388DAA89DD08EC67DBE63C1F606674ACC49EBF9FDBB2B898B3CE733113AA6F942DB401A76D629CE6EE6C0FDAF4CFB1A5E366DB66C17B3923A1B7FB26A3FF25B9018869C674D3DEF4AF269901D686FE4647F9D2CDB2CEB3AFA305B27C885F037ED167F595066C21E7DD467D8332B934A5102DA5F13332DFA356B82156A0BB2E7E91C6B85B7D1E381BC9E3F0FC4DB9C36016D9ECEC415D7E977E9AC29910D934BA2FE4EE49D3B387607A4E1AFABF495FB86A77194626589E802FF5167C7A25C542C1EAD25A6E0AA931D94F2F9AFD3DBDF222E651F729A90E77B20974905F1E65E041CE6C95AAB3E1F22D332E0A5DE9C5DB3D9C7A38", | |||
PrB_sike: "80FC55DA74DEFE3113487B80841E678AF9ED4E0599CF07353A4AB93971C090A0" + | |||
"A9402C9DC98AC6DC8F5FDE5E970AE22BA48A400EFC72851C", | |||
PrB_sidh: "A885A8B889520A6DBAD9FB33365E5B77FDED629440A16A533F259A510F63A822", | |||
PrA_sike: "B0AD510708F4ABCF3E0D97DC2F2FF112D9D2AAE49D97FFD1E4267F21C6E71C03", | |||
PkA_sike: "A6BADBA04518A924B20046B59AC197DCDF0EA48014C9E228C4994CCA432F360E" + | |||
"2D527AFB06CA7C96EE5CEE19BAD53BF9218A3961CAD7EC092BD8D9EBB22A3D51" + | |||
"33008895A3F1F6A023F91E0FE06A00A622FD6335DAC107F8EC4283DC2632F080" + | |||
"4E64B390DAD8A2572F1947C67FDF4F8787D140CE2C6B24E752DA9A195040EDFA" + | |||
"C27333FAE97DBDEB41DA9EEB2DB067AE7DA8C58C0EF57AEFC18A3D6BD0576FF2" + | |||
"F1CFCAEC50C958331BF631F3D2E769790C7B6DF282B74BBC02998AD10F291D47" + | |||
"C5A762FF84253D3B3278BDF20C8D4D4AA317BE401B884E26A1F02C7308AADB68" + | |||
"20EBDB0D339F5A63346F3B40CACED72F544DAF51566C6E807D0E6E1E38514342" + | |||
"432661DC9564DA07548570E256688CD9E8060D8775F95D501886D958588CACA0" + | |||
"9F2D2AE1913F996E76AF63E31A179A7A7D2A46EDA03B2BCCF9020A5AA15F9A28" + | |||
"9340B33F3AE7F97360D45F8AE1B9DD48779A57E8C45B50A02C00349CD1C58C55" + | |||
"1D68BC2A75EAFED944E8C599C288037181E997471352E24C952B", | |||
PkB_sidh: "244AF1F367C2C33912750A98497CC8214BC195BD52BD76513D32ACE4B75E31F0" + | |||
"281755C265F5565C74E3C04182B9C244071859C8588CC7F09547CEFF8F7705D2" + | |||
"60CE87D6BFF914EE7DBE4B9AF051CA420062EEBDF043AF58184495026949B068" + | |||
"98A47046BFAE8DF3B447746184AF550553BB5D266D6E1967ACA33CAC5F399F90" + | |||
"360D70867F2C71EF6F94FF915C7DA8BC9549FB7656E691DAEFC93CF56876E482" + | |||
"CA2F8BE2D6CDCC374C31AD8833CABE997CC92305F38497BEC4DFD1821B004FEC" + | |||
"E16448F9A24F965EFE409A8939EEA671633D9FFCF961283E59B8834BDF7EDDB3" + | |||
"05D6275B61DA6692325432A0BAA074FC7C1F51E76208AB193A57520D40A76334" + | |||
"EE5712BDC3E1EFB6103966F2329EDFF63082C4DFCDF6BE1C5A048630B81871B8" + | |||
"83B735748A8FD4E2D9530C272163AB18105B10015CA7456202FE1C9B92CEB167" + | |||
"5EAE1132E582C88E47ED87B363D45F05BEA714D5E9933D7AF4071CBB5D49008F" + | |||
"3E3DAD7DFF935EE509D5DE561842B678CCEB133D62E270E9AC3E", | |||
} | |||
/* ------------------------------------------------------------------------- | |||
Helpers | |||
-------------------------------------------------------------------------*/ | |||
// Fail if err !=nil. Display msg as an error message | |||
func checkErr(t testing.TB, err error, msg string) { | |||
if err != nil { | |||
t.Error(msg) | |||
} | |||
} | |||
// Utility used for running same test with all registered prime fields | |||
type MultiIdTestingFunc func(testing.TB) | |||
// Converts string to private key | |||
func convToPrv(s string, v KeyVariant) *PrivateKey { | |||
key := NewPrivateKey(v) | |||
hex, e := hex.DecodeString(s) | |||
if e != nil { | |||
panic("non-hex number provided") | |||
} | |||
e = key.Import(hex) | |||
if e != nil { | |||
panic("Can't import private key") | |||
} | |||
return key | |||
} | |||
// Converts string to public key | |||
func convToPub(s string, v KeyVariant) *PublicKey { | |||
key := NewPublicKey(v) | |||
hex, e := hex.DecodeString(s) | |||
if e != nil { | |||
panic("non-hex number provided") | |||
} | |||
e = key.Import(hex) | |||
if e != nil { | |||
panic("Can't import public key") | |||
} | |||
return key | |||
} | |||
/* ------------------------------------------------------------------------- | |||
Unit tests | |||
-------------------------------------------------------------------------*/ | |||
func TestKeygen(t *testing.T) { | |||
alicePrivate := convToPrv(tdata.PrA_sike, KeyVariant_SIDH_A) | |||
bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) | |||
expPubA := convToPub(tdata.PkA_sike, KeyVariant_SIDH_A) | |||
expPubB := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B) | |||
pubA := alicePrivate.GeneratePublicKey() | |||
pubB := bobPrivate.GeneratePublicKey() | |||
if !bytes.Equal(pubA.Export(), expPubA.Export()) { | |||
t.Fatalf("unexpected value of public key A") | |||
} | |||
if !bytes.Equal(pubB.Export(), expPubB.Export()) { | |||
t.Fatalf("unexpected value of public key B") | |||
} | |||
} | |||
func TestImportExport(t *testing.T) { | |||
var err error | |||
a := NewPublicKey(KeyVariant_SIDH_A) | |||
b := NewPublicKey(KeyVariant_SIDH_B) | |||
// Import keys | |||
a_hex, err := hex.DecodeString(tdata.PkA_sike) | |||
checkErr(t, err, "invalid hex-number provided") | |||
err = a.Import(a_hex) | |||
checkErr(t, err, "import failed") | |||
b_hex, err := hex.DecodeString(tdata.PkB_sike) | |||
checkErr(t, err, "invalid hex-number provided") | |||
err = b.Import(b_hex) | |||
checkErr(t, err, "import failed") | |||
// Export and check if same | |||
if !bytes.Equal(b.Export(), b_hex) || !bytes.Equal(a.Export(), a_hex) { | |||
t.Fatalf("export/import failed") | |||
} | |||
if (len(b.Export()) != b.Size()) || (len(a.Export()) != a.Size()) { | |||
t.Fatalf("wrong size of exported keys") | |||
} | |||
} | |||
func testPrivateKeyBelowMax(t testing.TB) { | |||
for variant, keySz := range map[KeyVariant]*DomainParams{ | |||
KeyVariant_SIDH_A: &Params.A, | |||
KeyVariant_SIDH_B: &Params.B} { | |||
func(v KeyVariant, dp *DomainParams) { | |||
var blen = int(dp.SecretByteLen) | |||
var prv = NewPrivateKey(v) | |||
// Calculate either (2^e2 - 1) or (2^s - 1); where s=ceil(log_2(3^e3))) | |||
maxSecertVal := big.NewInt(int64(dp.SecretBitLen)) | |||
maxSecertVal.Exp(big.NewInt(int64(2)), maxSecertVal, nil) | |||
maxSecertVal.Sub(maxSecertVal, big.NewInt(1)) | |||
// Do same test 1000 times | |||
for i := 0; i < 1000; i++ { | |||
err := prv.Generate(rand.Reader) | |||
checkErr(t, err, "Private key generation") | |||
// Convert to big-endian, as that's what expected by (*Int)SetBytes() | |||
secretBytes := prv.Export() | |||
for i := 0; i < int(blen/2); i++ { | |||
tmp := secretBytes[i] ^ secretBytes[blen-i-1] | |||
secretBytes[i] = tmp ^ secretBytes[i] | |||
secretBytes[blen-i-1] = tmp ^ secretBytes[blen-i-1] | |||
} | |||
prvBig := new(big.Int).SetBytes(secretBytes) | |||
// Check if generated key is bigger then acceptable | |||
if prvBig.Cmp(maxSecertVal) == 1 { | |||
t.Error("Generated private key is wrong") | |||
} | |||
} | |||
}(variant, keySz) | |||
} | |||
} | |||
func testKeyAgreement(t *testing.T, pkA, prA, pkB, prB string) { | |||
var e error | |||
// KeyPairs | |||
alicePublic := convToPub(pkA, KeyVariant_SIDH_A) | |||
bobPublic := convToPub(pkB, KeyVariant_SIDH_B) | |||
alicePrivate := convToPrv(prA, KeyVariant_SIDH_A) | |||
bobPrivate := convToPrv(prB, KeyVariant_SIDH_B) | |||
// Do actual test | |||
s1, e := DeriveSecret(bobPrivate, alicePublic) | |||
checkErr(t, e, "derivation s1") | |||
s2, e := DeriveSecret(alicePrivate, bobPublic) | |||
checkErr(t, e, "derivation s1") | |||
if !bytes.Equal(s1[:], s2[:]) { | |||
t.Fatalf("two shared keys: %d, %d do not match", s1, s2) | |||
} | |||
// Negative case | |||
dec, e := hex.DecodeString(tdata.PkA_sike) | |||
if e != nil { | |||
t.FailNow() | |||
} | |||
dec[0] = ^dec[0] | |||
e = alicePublic.Import(dec) | |||
if e != nil { | |||
t.FailNow() | |||
} | |||
s1, e = DeriveSecret(bobPrivate, alicePublic) | |||
checkErr(t, e, "derivation of s1 failed") | |||
s2, e = DeriveSecret(alicePrivate, bobPublic) | |||
checkErr(t, e, "derivation of s2 failed") | |||
if bytes.Equal(s1[:], s2[:]) { | |||
t.Fatalf("The two shared keys: %d, %d match", s1, s2) | |||
} | |||
} | |||
func TestDerivationRoundTrip(t *testing.T) { | |||
var err error | |||
prvA := NewPrivateKey(KeyVariant_SIDH_A) | |||
prvB := NewPrivateKey(KeyVariant_SIDH_B) | |||
// Generate private keys | |||
err = prvA.Generate(rand.Reader) | |||
checkErr(t, err, "key generation failed") | |||
err = prvB.Generate(rand.Reader) | |||
checkErr(t, err, "key generation failed") | |||
// Generate public keys | |||
pubA := prvA.GeneratePublicKey() | |||
pubB := prvB.GeneratePublicKey() | |||
// Derive shared secret | |||
s1, err := DeriveSecret(prvB, pubA) | |||
checkErr(t, err, "") | |||
s2, err := DeriveSecret(prvA, pubB) | |||
checkErr(t, err, "") | |||
if !bytes.Equal(s1[:], s2[:]) { | |||
t.Fatalf("Two shared keys: \n%X, \n%X do not match", s1, s2) | |||
} | |||
} | |||
// Encrypt, Decrypt, check if input/output plaintext is the same | |||
func testPKERoundTrip(t testing.TB, id uint8) { | |||
// Message to be encrypted | |||
var msg = make([]byte, Params.MsgLen) | |||
for i, _ := range msg { | |||
msg[i] = byte(i) | |||
} | |||
// Import keys | |||
pkB := NewPublicKey(KeyVariant_SIKE) | |||
skB := NewPrivateKey(KeyVariant_SIKE) | |||
pk_hex, err := hex.DecodeString(tdata.PkB_sike) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
sk_hex, err := hex.DecodeString(tdata.PrB_sike) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
if pkB.Import(pk_hex) != nil || skB.Import(sk_hex) != nil { | |||
t.Error("Import") | |||
} | |||
ct, err := Encrypt(rand.Reader, pkB, msg[:]) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
pt, err := Decrypt(skB, ct) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
if !bytes.Equal(pt[:], msg[:]) { | |||
t.Errorf("Decryption failed \n got : %X\n exp : %X", pt, msg) | |||
} | |||
} | |||
// Generate key and check if can encrypt | |||
func TestPKEKeyGeneration(t *testing.T) { | |||
// Message to be encrypted | |||
var msg = make([]byte, Params.MsgLen) | |||
var err error | |||
for i, _ := range msg { | |||
msg[i] = byte(i) | |||
} | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
err = sk.Generate(rand.Reader) | |||
checkErr(t, err, "PEK key generation") | |||
pk := sk.GeneratePublicKey() | |||
// Try to encrypt | |||
ct, err := Encrypt(rand.Reader, pk, msg[:]) | |||
checkErr(t, err, "PEK encryption") | |||
pt, err := Decrypt(sk, ct) | |||
checkErr(t, err, "PEK key decryption") | |||
if !bytes.Equal(pt[:], msg[:]) { | |||
t.Fatalf("Decryption failed \n got : %X\n exp : %X", pt, msg) | |||
} | |||
} | |||
func TestNegativePKE(t *testing.T) { | |||
var msg [40]byte | |||
var err error | |||
// Generate key | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
err = sk.Generate(rand.Reader) | |||
checkErr(t, err, "key generation") | |||
pk := sk.GeneratePublicKey() | |||
// bytelen(msg) - 1 | |||
ct, err := Encrypt(rand.Reader, pk, msg[:Params.KemSize+8-1]) | |||
if err == nil { | |||
t.Fatal("Error hasn't been returned") | |||
} | |||
if ct != nil { | |||
t.Fatal("Ciphertext must be nil") | |||
} | |||
// KemSize - 1 | |||
pt, err := Decrypt(sk, msg[:Params.KemSize+8-1]) | |||
if err == nil { | |||
t.Fatal("Error hasn't been returned") | |||
} | |||
if pt != nil { | |||
t.Fatal("Ciphertext must be nil") | |||
} | |||
} | |||
func testKEMRoundTrip(t *testing.T, pkB, skB []byte) { | |||
// Import keys | |||
pk := NewPublicKey(KeyVariant_SIKE) | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
if pk.Import(pkB) != nil || sk.Import(skB) != nil { | |||
t.Error("Import failed") | |||
} | |||
ct, ss_e, err := Encapsulate(rand.Reader, pk) | |||
if err != nil { | |||
t.Error("Encapsulate failed") | |||
} | |||
ss_d, err := Decapsulate(sk, pk, ct) | |||
if err != nil { | |||
t.Error("Decapsulate failed") | |||
} | |||
if !bytes.Equal(ss_e, ss_d) { | |||
t.Error("Shared secrets from decapsulation and encapsulation differ") | |||
} | |||
} | |||
func TestKEMRoundTrip(t *testing.T) { | |||
pk, err := hex.DecodeString(tdata.PkB_sike) | |||
checkErr(t, err, "public key B not a number") | |||
sk, err := hex.DecodeString(tdata.PrB_sike) | |||
checkErr(t, err, "private key B not a number") | |||
testKEMRoundTrip(t, pk, sk) | |||
} | |||
func TestKEMKeyGeneration(t *testing.T) { | |||
// Generate key | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
checkErr(t, sk.Generate(rand.Reader), "error: key generation") | |||
pk := sk.GeneratePublicKey() | |||
// calculated shared secret | |||
ct, ss_e, err := Encapsulate(rand.Reader, pk) | |||
checkErr(t, err, "encapsulation failed") | |||
ss_d, err := Decapsulate(sk, pk, ct) | |||
checkErr(t, err, "decapsulation failed") | |||
if !bytes.Equal(ss_e, ss_d) { | |||
t.Fatalf("KEM failed \n encapsulated: %X\n decapsulated: %X", ss_d, ss_e) | |||
} | |||
} | |||
func TestNegativeKEM(t *testing.T) { | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
checkErr(t, sk.Generate(rand.Reader), "error: key generation") | |||
pk := sk.GeneratePublicKey() | |||
ct, ss_e, err := Encapsulate(rand.Reader, pk) | |||
checkErr(t, err, "pre-requisite for a test failed") | |||
ct[0] = ct[0] - 1 | |||
ss_d, err := Decapsulate(sk, pk, ct) | |||
checkErr(t, err, "decapsulation returns error when invalid ciphertext provided") | |||
if bytes.Equal(ss_e, ss_d) { | |||
// no idea how this could ever happen, but it would be very bad | |||
t.Error("critical error") | |||
} | |||
// Try encapsulating with SIDH key | |||
pkSidh := NewPublicKey(KeyVariant_SIDH_B) | |||
prSidh := NewPrivateKey(KeyVariant_SIDH_B) | |||
_, _, err = Encapsulate(rand.Reader, pkSidh) | |||
if err == nil { | |||
t.Error("encapsulation accepts SIDH public key") | |||
} | |||
// Try decapsulating with SIDH key | |||
_, err = Decapsulate(prSidh, pk, ct) | |||
if err == nil { | |||
t.Error("decapsulation accepts SIDH private key key") | |||
} | |||
} | |||
// In case invalid ciphertext is provided, SIKE's decapsulation must | |||
// return same (but unpredictable) result for a given key. | |||
func TestNegativeKEMSameWrongResult(t *testing.T) { | |||
sk := NewPrivateKey(KeyVariant_SIKE) | |||
checkErr(t, sk.Generate(rand.Reader), "error: key generation") | |||
pk := sk.GeneratePublicKey() | |||
ct, encSs, err := Encapsulate(rand.Reader, pk) | |||
checkErr(t, err, "pre-requisite for a test failed") | |||
// make ciphertext wrong | |||
ct[0] = ct[0] - 1 | |||
decSs1, err := Decapsulate(sk, pk, ct) | |||
checkErr(t, err, "pre-requisite for a test failed") | |||
// second decapsulation must be done with same, but imported private key | |||
expSk := sk.Export() | |||
// creat new private key | |||
sk = NewPrivateKey(KeyVariant_SIKE) | |||
err = sk.Import(expSk) | |||
checkErr(t, err, "import failed") | |||
// try decapsulating again. ss2 must be same as ss1 and different than | |||
// original plaintext | |||
decSs2, err := Decapsulate(sk, pk, ct) | |||
checkErr(t, err, "pre-requisite for a test failed") | |||
if !bytes.Equal(decSs1, decSs2) { | |||
t.Error("decapsulation is insecure") | |||
} | |||
if bytes.Equal(encSs, decSs1) || bytes.Equal(encSs, decSs2) { | |||
// this test requires that decapsulation returns wrong result | |||
t.Errorf("test implementation error") | |||
} | |||
} | |||
func readAndCheckLine(r *bufio.Reader) []byte { | |||
// Read next line from buffer | |||
line, isPrefix, err := r.ReadLine() | |||
if err != nil || isPrefix { | |||
panic("Wrong format of input file") | |||
} | |||
// Function expects that line is in format "KEY = HEX_VALUE". Get | |||
// value, which should be a hex string | |||
hexst := strings.Split(string(line), "=")[1] | |||
hexst = strings.TrimSpace(hexst) | |||
// Convert value to byte string | |||
ret, err := hex.DecodeString(hexst) | |||
if err != nil { | |||
panic("Wrong format of input file") | |||
} | |||
return ret | |||
} | |||
func testKeygenSIKE(pk, sk []byte, id uint8) bool { | |||
// Import provided private key | |||
var prvKey = NewPrivateKey(KeyVariant_SIKE) | |||
if prvKey.Import(sk) != nil { | |||
panic("sike test: can't load KAT") | |||
} | |||
// Generate public key | |||
pubKey := prvKey.GeneratePublicKey() | |||
return bytes.Equal(pubKey.Export(), pk) | |||
} | |||
func testDecapsulation(pk, sk, ct, ssExpected []byte, id uint8) bool { | |||
var pubKey = NewPublicKey(KeyVariant_SIKE) | |||
var prvKey = NewPrivateKey(KeyVariant_SIKE) | |||
if pubKey.Import(pk) != nil || prvKey.Import(sk) != nil { | |||
panic("sike test: can't load KAT") | |||
} | |||
ssGot, err := Decapsulate(prvKey, pubKey, ct) | |||
if err != nil { | |||
panic("sike test: can't perform degcapsulation KAT") | |||
} | |||
if err != nil { | |||
return false | |||
} | |||
return bytes.Equal(ssGot, ssExpected) | |||
} | |||
func TestKeyAgreement(t *testing.T) { | |||
testKeyAgreement(t, tdata.PkA_sike, tdata.PrA_sike, tdata.PkB_sidh, tdata.PrB_sidh) | |||
} | |||
// Same values as in sike_test.cc | |||
func TestDecapsulation(t *testing.T) { | |||
var sk = [56]byte{ | |||
0xDB, 0xAF, 0x2C, 0x89, 0xCA, 0x5A, 0xD4, 0x9D, 0x4F, 0x13, | |||
0x40, 0xDF, 0x2D, 0xB1, 0x5F, 0x4C, 0x91, 0xA7, 0x1F, 0x0B, | |||
0x29, 0x15, 0x01, 0x59, 0xBC, 0x5F, 0x0B, 0x4A, 0x03, 0x27, | |||
0x6F, 0x18} | |||
var pk = []byte{ | |||
0x07, 0xAA, 0x51, 0x45, 0x3E, 0x1F, 0x53, 0x2A, 0x0A, 0x05, | |||
0x46, 0xF6, 0x54, 0x7F, 0x5D, 0x56, 0xD6, 0x76, 0xD3, 0xEA, | |||
0x4B, 0x6B, 0x01, 0x9B, 0x11, 0x72, 0x6F, 0x75, 0xEA, 0x34, | |||
0x3C, 0x28, 0x2C, 0x36, 0xFD, 0x77, 0xDA, 0xBE, 0xB6, 0x20, | |||
0x18, 0xC1, 0x93, 0x98, 0x18, 0x86, 0x30, 0x2F, 0x2E, 0xD2, | |||
0x00, 0x61, 0xFF, 0xAE, 0x78, 0xAE, 0xFB, 0x6F, 0x32, 0xAC, | |||
0x06, 0xBF, 0x35, 0xF6, 0xF7, 0x5B, 0x98, 0x26, 0x95, 0xC2, | |||
0xD8, 0xD6, 0x1C, 0x0E, 0x47, 0xDA, 0x76, 0xCE, 0xB5, 0xF1, | |||
0x19, 0xCC, 0x01, 0xE1, 0x17, 0xA9, 0x62, 0xF7, 0x82, 0x6C, | |||
0x25, 0x51, 0x25, 0xAE, 0xFE, 0xE3, 0xE2, 0xE1, 0x35, 0xAE, | |||
0x2E, 0x8F, 0x38, 0xE0, 0x7C, 0x74, 0x3C, 0x1D, 0x39, 0x91, | |||
0x1B, 0xC7, 0x9F, 0x8E, 0x33, 0x4E, 0x84, 0x19, 0xB8, 0xD9, | |||
0xC2, 0x71, 0x35, 0x02, 0x47, 0x3E, 0x79, 0xEF, 0x47, 0xE1, | |||
0xD8, 0x21, 0x96, 0x1F, 0x11, 0x59, 0x39, 0x34, 0x76, 0xEF, | |||
0x3E, 0xB7, 0x4E, 0xFB, 0x7C, 0x55, 0xA1, 0x85, 0xAA, 0xAB, | |||
0xAD, 0xF0, 0x09, 0xCB, 0xD1, 0xE3, 0x7C, 0x4F, 0x5D, 0x2D, | |||
0xE1, 0x13, 0xF0, 0x71, 0xD9, 0xE5, 0xF6, 0xAF, 0x7F, 0xC1, | |||
0x27, 0x95, 0x8D, 0x52, 0xD5, 0x96, 0x42, 0x38, 0x41, 0xF7, | |||
0x24, 0x3F, 0x3A, 0xB5, 0x7E, 0x11, 0xE4, 0xF9, 0x33, 0xEE, | |||
0x4D, 0xBE, 0x74, 0x48, 0xF9, 0x98, 0x04, 0x01, 0x16, 0xEB, | |||
0xA9, 0x0D, 0x61, 0xC6, 0xFD, 0x4C, 0xCF, 0x98, 0x84, 0x4A, | |||
0x94, 0xAC, 0x69, 0x2C, 0x02, 0x8B, 0xE3, 0xD1, 0x41, 0x0D, | |||
0xF2, 0x2D, 0x46, 0x1F, 0x57, 0x1C, 0x77, 0x86, 0x18, 0xE3, | |||
0x63, 0xDE, 0xF3, 0xE3, 0x02, 0x30, 0x54, 0x73, 0xAE, 0xC2, | |||
0x32, 0xA2, 0xCE, 0xEB, 0xCF, 0x81, 0x46, 0x54, 0x5C, 0xF4, | |||
0x5D, 0x2A, 0x03, 0x5D, 0x9C, 0xAE, 0xE0, 0x60, 0x03, 0x80, | |||
0x11, 0x30, 0xA5, 0xAA, 0xD1, 0x75, 0x67, 0xE0, 0x1C, 0x2B, | |||
0x6B, 0x5D, 0x83, 0xDE, 0x92, 0x9B, 0x0E, 0xD7, 0x11, 0x0F, | |||
0x00, 0xC4, 0x59, 0xE4, 0x81, 0x04, 0x3B, 0xEE, 0x5C, 0x04, | |||
0xD1, 0x0E, 0xD0, 0x67, 0xF5, 0xCC, 0xAA, 0x72, 0x73, 0xEA, | |||
0xC4, 0x76, 0x99, 0x3B, 0x4C, 0x90, 0x2F, 0xCB, 0xD8, 0x0A, | |||
0x5B, 0xEC, 0x0E, 0x0E, 0x1F, 0x59, 0xEA, 0x14, 0x8D, 0x34, | |||
0x53, 0x65, 0x4C, 0x1A, 0x59, 0xA8, 0x95, 0x66, 0x60, 0xBB, | |||
0xC4, 0xCC, 0x32, 0xA9, 0x8D, 0x2A, 0xAA, 0x14, 0x6F, 0x0F, | |||
0x81, 0x4D, 0x32, 0x02, 0xFD, 0x33, 0x58, 0x42, 0xCF, 0xF3, | |||
0x67, 0xD0, 0x9F, 0x0B, 0xB1, 0xCC, 0x18, 0xA5, 0xC4, 0x19, | |||
0xB6, 0x00, 0xED, 0xFA, 0x32, 0x1A, 0x5F, 0x67, 0xC8, 0xC3, | |||
0xEB, 0x0D, 0xB5, 0x9A, 0x36, 0x47, 0x82, 0x00, | |||
} | |||
var ct = []byte{ | |||
0xE6, 0xB7, 0xE5, 0x7B, 0xA9, 0x19, 0xD1, 0x2C, 0xB8, 0x5C, | |||
0x7B, 0x66, 0x74, 0xB0, 0x71, 0xA1, 0xFF, 0x71, 0x7F, 0x4B, | |||
0xB5, 0xA6, 0xAF, 0x48, 0x32, 0x52, 0xD5, 0x82, 0xEE, 0x8A, | |||
0xBB, 0x08, 0x1E, 0xF6, 0xAC, 0x91, 0xA2, 0xCB, 0x6B, 0x6A, | |||
0x09, 0x2B, 0xD9, 0xC6, 0x27, 0xD6, 0x3A, 0x6B, 0x8D, 0xFC, | |||
0xB8, 0x90, 0x8F, 0x72, 0xB3, 0xFA, 0x7D, 0x34, 0x7A, 0xC4, | |||
0x7E, 0xE3, 0x30, 0xC5, 0xA0, 0xFE, 0x3D, 0x43, 0x14, 0x4E, | |||
0x3A, 0x14, 0x76, 0x3E, 0xFB, 0xDF, 0xE3, 0xA8, 0xE3, 0x5E, | |||
0x38, 0xF2, 0xE0, 0x39, 0x67, 0x60, 0xFD, 0xFB, 0xB4, 0x19, | |||
0xCD, 0xE1, 0x93, 0xA2, 0x06, 0xCC, 0x65, 0xCD, 0x6E, 0xC8, | |||
0xB4, 0x5E, 0x41, 0x4B, 0x6C, 0xA5, 0xF4, 0xE4, 0x9D, 0x52, | |||
0x8C, 0x25, 0x60, 0xDD, 0x3D, 0xA9, 0x7F, 0xF2, 0x88, 0xC1, | |||
0x0C, 0xEE, 0x97, 0xE0, 0xE7, 0x3B, 0xB7, 0xD3, 0x6F, 0x28, | |||
0x79, 0x2F, 0x50, 0xB2, 0x4F, 0x74, 0x3A, 0x0C, 0x88, 0x27, | |||
0x98, 0x3A, 0x27, 0xD3, 0x26, 0x83, 0x59, 0x49, 0x81, 0x5B, | |||
0x0D, 0xA7, 0x0C, 0x4F, 0xEF, 0xFB, 0x1E, 0xAF, 0xE9, 0xD2, | |||
0x1C, 0x10, 0x25, 0xEC, 0x9E, 0xFA, 0x57, 0x36, 0xAA, 0x3F, | |||
0xC1, 0xA3, 0x2C, 0xE9, 0xB5, 0xC9, 0xED, 0x72, 0x51, 0x4C, | |||
0x02, 0xB4, 0x7B, 0xB3, 0xED, 0x9F, 0x45, 0x03, 0x34, 0xAC, | |||
0x9A, 0x9E, 0x62, 0x5F, 0x82, 0x7A, 0x77, 0x34, 0xF9, 0x21, | |||
0x94, 0xD2, 0x38, 0x3D, 0x05, 0xF0, 0x8A, 0x60, 0x1C, 0xB7, | |||
0x1D, 0xF5, 0xB7, 0x53, 0x77, 0xD3, 0x9D, 0x3D, 0x70, 0x6A, | |||
0xCB, 0x18, 0x20, 0x6B, 0x29, 0x17, 0x3A, 0x6D, 0xA1, 0xB2, | |||
0x64, 0xDB, 0x6C, 0xE6, 0x1A, 0x95, 0xA7, 0xF4, 0x1A, 0x78, | |||
0x1D, 0xA2, 0x40, 0x15, 0x41, 0x59, 0xDD, 0xEE, 0x23, 0x57, | |||
0xCE, 0x36, 0x0D, 0x55, 0xBD, 0xB8, 0xFD, 0x0F, 0x35, 0xBD, | |||
0x5B, 0x92, 0xD6, 0x1C, 0x84, 0x8C, 0x32, 0x64, 0xA6, 0x5C, | |||
0x45, 0x18, 0x07, 0x6B, 0xF9, 0xA9, 0x43, 0x9A, 0x83, 0xCD, | |||
0xB5, 0xB3, 0xD9, 0x17, 0x99, 0x2C, 0x2A, 0x8B, 0xE0, 0x8E, | |||
0xAF, 0xA6, 0x4C, 0x95, 0xBB, 0x70, 0x60, 0x1A, 0x3A, 0x97, | |||
0xAA, 0x2F, 0x3D, 0x22, 0x83, 0xB7, 0x4F, 0x59, 0xED, 0x3F, | |||
0x4E, 0xF4, 0x19, 0xC6, 0x25, 0x0B, 0x0A, 0x5E, 0x21, 0xB9, | |||
0x91, 0xB8, 0x19, 0x84, 0x48, 0x78, 0xCE, 0x27, 0xBF, 0x41, | |||
0x89, 0xF6, 0x30, 0xFD, 0x6B, 0xD9, 0xB8, 0x1D, 0x72, 0x8A, | |||
0x56, 0xCC, 0x2F, 0x82, 0xE4, 0x46, 0x4D, 0x75, 0xD8, 0x92, | |||
0xE6, 0x9C, 0xCC, 0xD2, 0xCD, 0x35, 0xE4, 0xFC, 0x2A, 0x85, | |||
0x6B, 0xA9, 0xB2, 0x27, 0xC9, 0xA1, 0xFF, 0xB3, 0x96, 0x3E, | |||
0x59, 0xF6, 0x4C, 0x66, 0x56, 0x2E, 0xF5, 0x1B, 0x97, 0x32, | |||
0xB0, 0x71, 0x5A, 0x9C, 0x50, 0x4B, 0x6F, 0xC4, 0xCA, 0x94, | |||
0x75, 0x37, 0x46, 0x10, 0x12, 0x2F, 0x4F, 0xA3, 0x82, 0xCD, | |||
0xBD, 0x7C, | |||
} | |||
var ss_exp = []byte{ | |||
0x74, 0x3D, 0x25, 0x36, 0x00, 0x24, 0x63, 0x1A, 0x39, 0x1A, | |||
0xB4, 0xAD, 0x01, 0x17, 0x78, 0xE9} | |||
var prvObj = NewPrivateKey(KeyVariant_SIKE) | |||
var pubObj = NewPublicKey(KeyVariant_SIKE) | |||
if pubObj.Import(pk) != nil || prvObj.Import(sk[:]) != nil { | |||
t.Error("Can't import one of the keys") | |||
} | |||
res, _ := Decapsulate(prvObj, pubObj, ct) | |||
if !bytes.Equal(ss_exp, res) { | |||
t.Error("Wrong decapsulation result") | |||
} | |||
} | |||
/* ------------------------------------------------------------------------- | |||
Benchmarking | |||
-------------------------------------------------------------------------*/ | |||
func BenchmarkKeygen(b *testing.B) { | |||
prv := NewPrivateKey(KeyVariant_SIKE) | |||
for n := 0; n < b.N; n++ { | |||
prv.Generate(rand.Reader) | |||
} | |||
} | |||
func BenchmarkEncaps(b *testing.B) { | |||
prv := NewPrivateKey(KeyVariant_SIKE) | |||
if prv.Generate(rand.Reader) != nil { | |||
b.FailNow() | |||
} | |||
pub := prv.GeneratePublicKey() | |||
for n := 0; n < b.N; n++ { | |||
Encapsulate(rand.Reader, pub) | |||
} | |||
} | |||
func BenchmarkDecaps(b *testing.B) { | |||
prvA := NewPrivateKey(KeyVariant_SIKE) | |||
prvB := NewPrivateKey(KeyVariant_SIKE) | |||
if prvA.Generate(rand.Reader) != nil || prvB.Generate(rand.Reader) != nil { | |||
b.FailNow() | |||
} | |||
pubA := prvA.GeneratePublicKey() | |||
pubB := prvB.GeneratePublicKey() | |||
ctext, _, err := Encapsulate(rand.Reader, pubA) | |||
if err != nil { | |||
b.FailNow() | |||
} | |||
for n := 0; n < b.N; n++ { | |||
Decapsulate(prvA, pubB, ctext) | |||
} | |||
} |