@@ -7,7 +7,7 @@ import ( | |||
// 511-bit number representing prime field element GF(p) | |||
type fp [numWords]uint64 | |||
// Represents projective point on elliptic curve E over fp | |||
// Represents projective point on elliptic curve E over GF(p) | |||
type point struct { | |||
x fp | |||
z fp | |||
@@ -27,17 +27,20 @@ type fpRngGen struct { | |||
// Defines operations on public key | |||
type PublicKey struct { | |||
fpRngGen | |||
// Montgomery coefficient: represents y^2 = x^3 + Ax^2 + x | |||
// Montgomery coefficient A from GF(p) of the elliptic curve | |||
// y^2 = x^3 + Ax^2 + x. | |||
a fp | |||
} | |||
// Defines operations on private key | |||
type PrivateKey struct { | |||
fpRngGen | |||
// private key is a set of integers randomly | |||
// each sampled from a range [-5, 5]. | |||
e [PrivateKeySize]int8 | |||
} | |||
// randFp generates random element from Fp | |||
// randFp generates random element from Fp. | |||
func (s *fpRngGen) randFp(v *fp, rng io.Reader) { | |||
mask := uint64(1<<(pbits%limbBitSize)) - 1 | |||
for { | |||
@@ -60,23 +63,31 @@ func (s *fpRngGen) randFp(v *fp, rng io.Reader) { | |||
} | |||
} | |||
func cofactorMultiples(p *point, a *coeff, halfL, halfR int, order *fp) (bool, bool) { | |||
// cofactorMul helper implements batch cofactor multiplication as described | |||
// in the ia.cr/2018/383 (algo. 3). Returns tuple of two booleans, first indicates | |||
// if function has finished successfully. In case first return value is true, | |||
// second return value indicates if curve represented by coffactor 'a' is | |||
// supersingular. | |||
// Implemenation uses divide-and-conquer strategy and recursion in order to | |||
// speed up calculation of Q_i = [(p+1)/l_i] * P. | |||
// Implementation is not constant time, but it operates on public data only. | |||
func cofactorMul(p *point, a *coeff, halfL, halfR int, order *fp) (bool, bool) { | |||
var Q point | |||
var r1, d1, r2, d2 bool | |||
if (halfR - halfL) == 1 { | |||
// base case | |||
if !p.z.isZero() { | |||
var tmp = fp{primes[halfL]} | |||
xMul512(p, p, a, &tmp) | |||
xMul(p, p, a, &tmp) | |||
if !p.z.isZero() { | |||
// order does not divide p+1 | |||
return false, true | |||
// order does not divide p+1 -> ordinary curve | |||
return true, false | |||
} | |||
mul512(order, order, primes[halfL]) | |||
if sub512(&tmp, &fourSqrtP, order) == 1 { | |||
// order > 4*sqrt(p) -> supersingular | |||
if isLess(&fourSqrtP, order) { | |||
// order > 4*sqrt(p) -> supersingular curve | |||
return true, true | |||
} | |||
} | |||
@@ -86,21 +97,27 @@ func cofactorMultiples(p *point, a *coeff, halfL, halfR int, order *fp) (bool, b | |||
// perform another recursive step | |||
mid := halfL + ((halfR - halfL + 1) / 2) | |||
var mulL, mulR = fp{1}, fp{1} | |||
// compute u = primes_1 * ... * primes_m | |||
for i := halfL; i < mid; i++ { | |||
mul512(&mulR, &mulR, primes[i]) | |||
} | |||
// compute v = primes_m+1 * ... * primes_n | |||
for i := mid; i < halfR; i++ { | |||
mul512(&mulL, &mulL, primes[i]) | |||
} | |||
xMul512(&Q, p, a, &mulR) | |||
xMul512(p, p, a, &mulL) | |||
// calculate Q_i | |||
xMul(&Q, p, a, &mulR) | |||
xMul(p, p, a, &mulL) | |||
r1, d1 = cofactorMultiples(&Q, a, mid, halfR, order) | |||
r2, d2 = cofactorMultiples(p, a, halfL, mid, order) | |||
return r1 || r2, d1 || d2 | |||
d1, r1 = cofactorMul(&Q, a, mid, halfR, order) | |||
d2, r2 = cofactorMul(p, a, halfL, mid, order) | |||
return d1 || d2, r1 || r2 | |||
} | |||
// groupAction evaluates group action of prv.e on a Montgomery | |||
// curve represented by coefficient pub.A. | |||
// This is implementation of algorithm 2 from ia.cr/2018/383. | |||
func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) { | |||
var k [2]fp | |||
var e [2][primeCount]uint8 | |||
@@ -140,7 +157,7 @@ func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) { | |||
continue | |||
} | |||
xMul512(&P, &P, &A, &k[sign]) | |||
xMul(&P, &P, &A, &k[sign]) | |||
done[sign] = true | |||
for i, v := range primes { | |||
@@ -154,9 +171,9 @@ func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) { | |||
} | |||
} | |||
xMul512(&K, &P, &A, &cof) | |||
xMul(&K, &P, &A, &cof) | |||
if !K.z.isZero() { | |||
isom(&P, &A, &K, v) | |||
xIso(&P, &A, &K, v) | |||
e[sign][i] = e[sign][i] - 1 | |||
if e[sign][i] == 0 { | |||
mul512(&k[sign], &k[sign], primes[i]) | |||
@@ -225,7 +242,7 @@ func GeneratePrivateKey(key *PrivateKey, rng io.Reader) error { | |||
// Public key operations | |||
// Assumes key is in Montgomery domain | |||
// Assumes key is in Montgomery domain. | |||
func (c *PublicKey) Import(key []byte) bool { | |||
if len(key) != numWords*limbByteSize { | |||
return false | |||
@@ -238,7 +255,7 @@ func (c *PublicKey) Import(key []byte) bool { | |||
return true | |||
} | |||
// Assumes key is exported as encoded in Montgomery domain | |||
// Assumes key is exported as encoded in Montgomery domain. | |||
func (c *PublicKey) Export(out []byte) bool { | |||
if len(out) != numWords*limbByteSize { | |||
return false | |||
@@ -251,44 +268,45 @@ func (c *PublicKey) Export(out []byte) bool { | |||
return true | |||
} | |||
func (c *PublicKey) reset() { | |||
for i := range c.a { | |||
c.a[i] = 0 | |||
} | |||
} | |||
func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) { | |||
pub.reset() | |||
for i := range pub.a { | |||
pub.a[i] = 0 | |||
} | |||
groupAction(pub, prv, rng) | |||
} | |||
// Validate does public key validation. It returns true if | |||
// a 'pub' is a valid cSIDH public key, otherwise false. | |||
// Validate returns true if 'pub' is a valid cSIDH public key, | |||
// otherwise false. | |||
// More precisely, the function verifies that curve | |||
// y^2 = x^3 + pub.a * x^2 + x | |||
// is supersingular. | |||
func Validate(pub *PublicKey, rng io.Reader) bool { | |||
// Check if in range | |||
if !isLess(&pub.a, &p) { | |||
return false | |||
} | |||
// j-invariant for montgomery curves is something like | |||
// j = (256*(A^3-3)^3)/(A^2 - 4), so any |A| = 2 is invalid | |||
// Check if pub represents a smooth Montgomery curve. | |||
if pub.a.equal(&two) || pub.a.equal(&twoNeg) { | |||
return false | |||
} | |||
// P must have big enough order to prove supersingularity. The | |||
// probability that this loop will be repeated is negligible. | |||
// Check if pub represents a supersingular curve. | |||
for { | |||
var P point | |||
var A = point{pub.a, one} | |||
// Randomly chosen P must have big enough order to check | |||
// supersingularity. Probability of random P having big | |||
// enough order is very high, as proven by W.Castryck et | |||
// al. (ia.cr/2018/383, ch 5) | |||
pub.randFp(&P.x, rng) | |||
P.z = one | |||
xDbl(&P, &P, &A) | |||
xDbl(&P, &P, &A) | |||
res, done := cofactorMultiples(&P, &coeff{A.x, A.z}, 0, len(primes), &fp{1}) | |||
done, res := cofactorMul(&P, &coeff{A.x, A.z}, 0, len(primes), &fp{1}) | |||
if done { | |||
return res | |||
} | |||
@@ -297,6 +315,9 @@ func Validate(pub *PublicKey, rng io.Reader) bool { | |||
// DeriveSecret computes a cSIDH shared secret. If successful, returns true | |||
// and fills 'out' with shared secret. Function returns false in case 'pub' is invalid. | |||
// More precisely, shared secret is a Montgomery coefficient A of a secret | |||
// curve y^2 = x^3 + Ax^2 + x, computed by applying action of a prv.e | |||
// on a curve represented by pub.a. | |||
func DeriveSecret(out *[64]byte, pub *PublicKey, prv *PrivateKey, rng io.Reader) bool { | |||
if !Validate(pub, rng) { | |||
return false | |||
@@ -2,14 +2,12 @@ package csidh | |||
import ( | |||
"bytes" | |||
crand "crypto/rand" | |||
"encoding/hex" | |||
"encoding/json" | |||
"fmt" | |||
"os" | |||
"testing" | |||
crand "crypto/rand" | |||
"github.com/henrydcase/nobs/drbg" | |||
) | |||
@@ -30,6 +28,20 @@ var StatusValues = map[int]string{ | |||
InvalidPublicKey2: "invalid_public_key2", | |||
} | |||
func checkErr(t testing.TB, err error, msg string) { | |||
t.Helper() | |||
if err != nil { | |||
t.Error(msg) | |||
} | |||
} | |||
func Ok(t testing.TB, f bool, msg string) { | |||
t.Helper() | |||
if !f { | |||
t.Error(msg) | |||
} | |||
} | |||
type TestVector struct { | |||
ID int `json:"Id"` | |||
Pk1 string `json:"Pk1"` | |||
@@ -39,10 +51,6 @@ type TestVector struct { | |||
Status string `json:"status"` | |||
} | |||
type TestVectors struct { | |||
Vectors []TestVector `json:"Vectors"` | |||
} | |||
var rng *drbg.CtrDrbg | |||
func init() { | |||
@@ -56,6 +64,10 @@ func init() { | |||
} | |||
} | |||
type TestVectors struct { | |||
Vectors []TestVector `json:"Vectors"` | |||
} | |||
func TestCompare64(t *testing.T) { | |||
const s uint64 = 0xFFFFFFFFFFFFFFFF | |||
var val1 = fp{0, 2, 3, 4, 5, 6, 7, 8} | |||
@@ -82,20 +94,17 @@ func TestEphemeralKeyExchange(t *testing.T) { | |||
prv1.Import(prvBytes1) | |||
GeneratePublicKey(&pub1, &prv1, rng) | |||
GeneratePrivateKey(&prv2, rng) | |||
checkErr(t, GeneratePrivateKey(&prv2, rng), "PrivateKey generation failed") | |||
GeneratePublicKey(&pub2, &prv2, rng) | |||
if !DeriveSecret(&ss1, &pub1, &prv2, rng) { | |||
t.Errorf("Derivation failed\n") | |||
} | |||
if !DeriveSecret(&ss2, &pub2, &prv1, rng) { | |||
t.Errorf("Derivation failed\n") | |||
} | |||
Ok(t, | |||
DeriveSecret(&ss1, &pub1, &prv2, rng), | |||
"Derivation failed") | |||
Ok(t, | |||
DeriveSecret(&ss2, &pub2, &prv1, rng), | |||
"Derivation failed") | |||
if !bytes.Equal(ss1[:], ss2[:]) { | |||
fmt.Printf("%X\n", ss1) | |||
fmt.Printf("%X\n", ss2) | |||
t.Error("ss1 != ss2") | |||
} | |||
} | |||
@@ -104,7 +113,7 @@ func TestPrivateKeyExportImport(t *testing.T) { | |||
var buf [37]byte | |||
for i := 0; i < numIter; i++ { | |||
var prv1, prv2 PrivateKey | |||
GeneratePrivateKey(&prv1, rng) | |||
checkErr(t, GeneratePrivateKey(&prv1, rng), "PrivateKey generation failed") | |||
prv1.Export(buf[:]) | |||
prv2.Import(buf[:]) | |||
@@ -153,7 +162,7 @@ func TestPublicKeyExportImport(t *testing.T) { | |||
for i := 0; i < numIter; i++ { | |||
var prv PrivateKey | |||
var pub1, pub2 PublicKey | |||
GeneratePrivateKey(&prv, rng) | |||
checkErr(t, GeneratePrivateKey(&prv, rng), "PrivateKey generation failed") | |||
GeneratePublicKey(&pub1, &prv, rng) | |||
pub1.Export(buf[:]) | |||
@@ -165,10 +174,10 @@ func TestPublicKeyExportImport(t *testing.T) { | |||
} | |||
} | |||
// Test vectors generated by reference implementation | |||
// Test vectors generated by reference implementation. | |||
func TestKAT(t *testing.T) { | |||
var tests TestVectors | |||
var testVectorFile string | |||
var katFile string | |||
// Helper checks if e==true and reports an error if not. | |||
checkExpr := func(e bool, vec *TestVector, t *testing.T, msg string) { | |||
@@ -179,9 +188,9 @@ func TestKAT(t *testing.T) { | |||
} | |||
if hasADXandBMI2 { | |||
testVectorFile = "testdata/csidh_testvectors.dat" | |||
katFile = "testdata/csidh_testvectors.dat" | |||
} else { | |||
testVectorFile = "testdata/csidh_testvectors_small.dat" | |||
katFile = "testdata/csidh_testvectors_small.dat" | |||
} | |||
// checkSharedSecret implements nominal case - imports asymmetric keys for | |||
@@ -197,39 +206,24 @@ func TestKAT(t *testing.T) { | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
checkExpr( | |||
prv1.Import(prBuf[:]), | |||
vec, t, "PrivateKey wrong") | |||
checkExpr(prv1.Import(prBuf[:]), vec, t, "PrivateKey wrong") | |||
pkBuf, err := hex.DecodeString(vec.Pk1) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
checkExpr( | |||
pub1.Import(pkBuf[:]), | |||
vec, t, "PublicKey 1 wrong") | |||
checkExpr(pub1.Import(pkBuf[:]), vec, t, "PublicKey 1 wrong") | |||
pkBuf, err = hex.DecodeString(vec.Pk2) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
checkExpr( | |||
pub2.Import(pkBuf[:]), | |||
vec, t, "PublicKey 2 wrong") | |||
checkExpr( | |||
DeriveSecret(&ss, &pub2, &prv1, rng), | |||
vec, t, "Error when deriving key") | |||
checkExpr(pub2.Import(pkBuf[:]), vec, t, "PublicKey 2 wrong") | |||
checkExpr(DeriveSecret(&ss, &pub2, &prv1, rng), vec, t, "Error when deriving key") | |||
ssExp, err := hex.DecodeString(vec.Ss) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
checkExpr( | |||
bytes.Equal(ss[:], ssExp) == (status == Valid), | |||
vec, t, "Unexpected value of shared secret") | |||
checkExpr(bytes.Equal(ss[:], ssExp) == (status == Valid), vec, t, "Unexpected value of shared secret") | |||
} | |||
// checkPublicKey1 imports public and private key for one party A | |||
// and tries to generate public key for a private key. After that | |||
// it compares generated key to a key from test vector. Comparison | |||
@@ -254,7 +248,7 @@ func TestKAT(t *testing.T) { | |||
vec, t, "PrivateKey wrong") | |||
// Generate public key | |||
GeneratePrivateKey(&prv, rng) | |||
checkErr(t, GeneratePrivateKey(&prv, rng), "PrivateKey generation failed") | |||
pub.Export(pubBytesGot[:]) | |||
// pubBytesGot must be different than pubBytesExp | |||
@@ -262,27 +256,23 @@ func TestKAT(t *testing.T) { | |||
!bytes.Equal(pubBytesGot[:], pubBytesExp), | |||
vec, t, "Public key generated is the same as public key from the test vector") | |||
} | |||
// checkPublicKey2 the goal is to test key validation. Test tries to | |||
// import public key for B and ensure that import succeeds in case | |||
// status is "Valid" and fails otherwise. | |||
checkPublicKey2 := func(vec *TestVector, t *testing.T, status int) { | |||
var pub PublicKey | |||
pubBytesExp, err := hex.DecodeString(vec.Pk2) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
// Import validates an input, so it must fail | |||
pub.Import(pubBytesExp[:]) | |||
checkExpr( | |||
Validate(&pub, rng) == (status == Valid || status == ValidPublicKey2), | |||
vec, t, "PublicKey has been validated correctly") | |||
} | |||
// Load test data | |||
file, err := os.Open(testVectorFile) | |||
file, err := os.Open(katFile) | |||
if err != nil { | |||
t.Fatal(err.Error()) | |||
} | |||
@@ -290,9 +280,14 @@ func TestKAT(t *testing.T) { | |||
if err != nil { | |||
t.Fatal(err.Error()) | |||
} | |||
// Loop over all test cases | |||
for _, test := range tests.Vectors { | |||
for i := range tests.Vectors { | |||
if !hasADXandBMI2 && i >= numIter { | |||
// The algorithm is relatively slow, so on slow systems test | |||
// against smaller number of test vectors (otherwise CI may break) | |||
return | |||
} | |||
test := tests.Vectors[i] | |||
switch test.Status { | |||
case StatusValues[Valid]: | |||
checkSharedSecret(&test, t, Valid) | |||
@@ -314,23 +309,23 @@ func TestKAT(t *testing.T) { | |||
var prv1, prv2 PrivateKey | |||
var pub1, pub2 PublicKey | |||
// Private key generation | |||
// Private key generation. | |||
func BenchmarkGeneratePrivate(b *testing.B) { | |||
for n := 0; n < b.N; n++ { | |||
GeneratePrivateKey(&prv1, rng) | |||
_ = GeneratePrivateKey(&prv1, rng) | |||
} | |||
} | |||
// Public key generation from private (group action on empty key) | |||
// Public key generation from private (group action on empty key). | |||
func BenchmarkGenerateKeyPair(b *testing.B) { | |||
for n := 0; n < b.N; n++ { | |||
var pub PublicKey | |||
GeneratePrivateKey(&prv1, rng) | |||
_ = GeneratePrivateKey(&prv1, rng) | |||
GeneratePublicKey(&pub, &prv1, rng) | |||
} | |||
} | |||
// Benchmark validation on same key multiple times | |||
// Benchmark validation on same key multiple times. | |||
func BenchmarkValidate(b *testing.B) { | |||
prvBytes := []byte{0xaa, 0x54, 0xe4, 0xd4, 0xd0, 0xbd, 0xee, 0xcb, 0xf4, 0xd0, 0xc2, 0xbc, 0x52, 0x44, 0x11, 0xee, 0xe1, 0x14, 0xd2, 0x24, 0xe5, 0x0, 0xcc, 0xf5, 0xc0, 0xe1, 0x1e, 0xb3, 0x43, 0x52, 0x45, 0xbe, 0xfb, 0x54, 0xc0, 0x55, 0xb2} | |||
prv1.Import(prvBytes) | |||
@@ -343,7 +338,7 @@ func BenchmarkValidate(b *testing.B) { | |||
} | |||
} | |||
// Benchmark validation on random (most probably wrong) key | |||
// Benchmark validation on random (most probably wrong) key. | |||
func BenchmarkValidateRandom(b *testing.B) { | |||
var tmp [64]byte | |||
var pub PublicKey | |||
@@ -357,39 +352,40 @@ func BenchmarkValidateRandom(b *testing.B) { | |||
} | |||
} | |||
// Benchmark validation on different keys | |||
// Benchmark validation on different keys. | |||
func BenchmarkValidateGenerated(b *testing.B) { | |||
for n := 0; n < b.N; n++ { | |||
GeneratePrivateKey(&prv1, rng) | |||
_ = GeneratePrivateKey(&prv1, rng) | |||
GeneratePublicKey(&pub1, &prv1, rng) | |||
Validate(&pub1, rng) | |||
} | |||
} | |||
// Generate some keys and benchmark derive | |||
// Generate some keys and benchmark derive. | |||
func BenchmarkDerive(b *testing.B) { | |||
var ss [64]byte | |||
GeneratePrivateKey(&prv1, rng) | |||
_ = GeneratePrivateKey(&prv1, rng) | |||
GeneratePublicKey(&pub1, &prv1, rng) | |||
GeneratePrivateKey(&prv2, rng) | |||
_ = GeneratePrivateKey(&prv2, rng) | |||
GeneratePublicKey(&pub2, &prv2, rng) | |||
b.ResetTimer() | |||
for n := 0; n < b.N; n++ { | |||
DeriveSecret(&ss, &pub2, &prv1, rng) | |||
} | |||
} | |||
// Benchmarks both - key generation and derivation | |||
// Benchmarks both - key generation and derivation. | |||
func BenchmarkDeriveGenerated(b *testing.B) { | |||
var ss [64]byte | |||
for n := 0; n < b.N; n++ { | |||
GeneratePrivateKey(&prv1, rng) | |||
_ = GeneratePrivateKey(&prv1, rng) | |||
GeneratePublicKey(&pub1, &prv1, rng) | |||
GeneratePrivateKey(&prv2, rng) | |||
_ = GeneratePrivateKey(&prv2, rng) | |||
GeneratePublicKey(&pub2, &prv2, rng) | |||
DeriveSecret(&ss, &pub2, &prv1, rng) | |||
@@ -1,10 +1,10 @@ | |||
package csidh | |||
// Implements differential arithmetic in P^1 for montgomery | |||
// curves a mapping: x(P),x(Q),x(P-Q) -> x(P+Q) | |||
// PaQ = P + Q | |||
// xAdd implements differential arithmetic in P^1 for Montgomery | |||
// curves E(x): x^3 + A*x^2 + x by using x-coordinate only arithmetic. | |||
// x(PaQ) = x(P) + x(Q) by using x(P-Q) | |||
// This algorithms is correctly defined only for cases when | |||
// P!=inf, Q!=inf, P!=Q and P!=-Q | |||
// P!=inf, Q!=inf, P!=Q and P!=-Q. | |||
func xAdd(PaQ, P, Q, PdQ *point) { | |||
var t0, t1, t2, t3 fp | |||
addRdc(&t0, &P.x, &P.z) | |||
@@ -21,8 +21,10 @@ func xAdd(PaQ, P, Q, PdQ *point) { | |||
mulRdc(&PaQ.z, &PdQ.x, &t3) | |||
} | |||
// Q = 2*P on a montgomery curve E(x): x^3 + A*x^2 + x | |||
// It is correctly defined for all P != inf | |||
// xDbl implements point doubling on a Montgomery curve | |||
// E(x): x^3 + A*x^2 + x by using x-coordinate onlyh arithmetic. | |||
// x(Q) = [2]*x(P) | |||
// It is correctly defined for all P != inf. | |||
func xDbl(Q, P, A *point) { | |||
var t0, t1, t2 fp | |||
addRdc(&t0, &P.x, &P.z) | |||
@@ -40,8 +42,11 @@ func xDbl(Q, P, A *point) { | |||
mulRdc(&Q.z, &t0, &t2) | |||
} | |||
// PaP = 2*P; PaQ = P+Q | |||
// PaP can override P and PaQ can override Q | |||
// xDblAdd implements combined doubling of point P | |||
// and addition of points P and Q on a Montgomery curve | |||
// E(x): x^3 + A*x^2 + x by using x-coordinate onlyh arithmetic. | |||
// x(PaP) = x(2*P) | |||
// x(PaQ) = x(P+Q) | |||
func xDblAdd(PaP, PaQ, P, Q, PdQ *point, A24 *coeff) { | |||
var t0, t1, t2 fp | |||
@@ -67,7 +72,7 @@ func xDblAdd(PaP, PaQ, P, Q, PdQ *point, A24 *coeff) { | |||
mulRdc(&PaQ.x, &PaQ.x, &PdQ.z) | |||
} | |||
// Swap P1 with P2 in constant time. The 'choice' | |||
// cswappoint swaps P1 with P2 in constant time. The 'choice' | |||
// parameter must have a value of either 1 (results | |||
// in swap) or 0 (results in no-swap). | |||
func cswappoint(P1, P2 *point, choice uint8) { | |||
@@ -75,13 +80,11 @@ func cswappoint(P1, P2 *point, choice uint8) { | |||
cswap512(&P1.z, &P2.z, choice) | |||
} | |||
// A uniform Montgomery ladder. co is A coefficient of | |||
// x^3 + A*x^2 + x curve. k MUST be > 0 | |||
// xMul implements point multiplication with left-to-right Montgomery | |||
// adder. co is A coefficient of x^3 + A*x^2 + x curve. k must be > 0 | |||
// | |||
// kP = [k]P. xM=x(0 + k*P) | |||
// | |||
// non-constant time. | |||
func xMul512(kP, P *point, co *coeff, k *fp) { | |||
// Non-constant time! | |||
func xMul(kP, P *point, co *coeff, k *fp) { | |||
var A24 coeff | |||
var Q point | |||
var j uint | |||
@@ -107,16 +110,23 @@ func xMul512(kP, P *point, co *coeff, k *fp) { | |||
for i := j; i > 0; { | |||
i-- | |||
bit := uint8(k[i>>6] >> (i & 63) & 1) | |||
swap := prevBit ^ bit | |||
prevBit = bit | |||
cswappoint(&Q, &R, swap) | |||
cswappoint(&Q, &R, prevBit^bit) | |||
xDblAdd(&Q, &R, &Q, &R, P, &A24) | |||
prevBit = bit | |||
} | |||
cswappoint(&Q, &R, uint8(k[0]&1)) | |||
*kP = Q | |||
} | |||
func isom(img *point, co *coeff, kern *point, order uint64) { | |||
// xIso computes the isogeny with kernel point kern of a given order | |||
// kernOrder. Returns the new curve coefficient co and the image img. | |||
// | |||
// During computation function switches between Montgomery and twisted | |||
// Edwards curves in order to compute image curve parameters faster. | |||
// This technique is described by Meyer and Reith in ia.cr/2018/782. | |||
// | |||
// Non-constant time. | |||
func xIso(img *point, co *coeff, kern *point, kernOrder uint64) { | |||
var t0, t1, t2, S, D fp | |||
var Q, prod point | |||
var coEd coeff | |||
@@ -145,8 +155,8 @@ func isom(img *point, co *coeff, kern *point, order uint64) { | |||
xDbl(&M[1], kern, &point{x: co.a, z: co.c}) | |||
// TODO: Not constant time. | |||
for i := uint64(1); i < order>>1; i++ { | |||
// NOTE: Not constant time. | |||
for i := uint64(1); i < kernOrder>>1; i++ { | |||
if i >= 2 { | |||
xAdd(&M[i%3], &M[(i-1)%3], kern, &M[(i-2)%3]) | |||
} | |||
@@ -160,7 +170,6 @@ func isom(img *point, co *coeff, kern *point, order uint64) { | |||
mulRdc(&Q.x, &Q.x, &t2) | |||
subRdc(&t2, &t0, &t1) | |||
mulRdc(&Q.z, &Q.z, &t2) | |||
} | |||
mulRdc(&Q.x, &Q.x, &Q.x) | |||
@@ -168,9 +177,9 @@ func isom(img *point, co *coeff, kern *point, order uint64) { | |||
mulRdc(&img.x, &img.x, &Q.x) | |||
mulRdc(&img.z, &img.z, &Q.z) | |||
// coEd.a^order and coEd.c^order | |||
modExpRdc64(&coEd.a, &coEd.a, order) | |||
modExpRdc64(&coEd.c, &coEd.c, order) | |||
// coEd.a^kernOrder and coEd.c^kernOrder | |||
modExpRdc64(&coEd.a, &coEd.a, kernOrder) | |||
modExpRdc64(&coEd.c, &coEd.c, kernOrder) | |||
// prod^8 | |||
mulRdc(&prod.x, &prod.x, &prod.x) | |||
@@ -190,7 +199,7 @@ func isom(img *point, co *coeff, kern *point, order uint64) { | |||
addRdc(&co.a, &co.a, &co.a) | |||
} | |||
// evaluates x^3 + Ax^2 + x | |||
// montEval evaluates x^3 + Ax^2 + x. | |||
func montEval(res, A, x *fp) { | |||
var t fp | |||
@@ -5,7 +5,7 @@ import ( | |||
"testing" | |||
) | |||
// Actual test implementation | |||
// Actual test implementation. | |||
func TestXAdd(t *testing.T) { | |||
var P, Q, PdQ point | |||
var PaQ point | |||
@@ -180,14 +180,14 @@ func TestXMul(t *testing.T) { | |||
checkXMul := func() { | |||
var kP point | |||
xMul512(&kP, &P, &co, &k) | |||
xMul(&kP, &P, &co, &k) | |||
retKP := toNormX(&kP) | |||
if expKP.Cmp(&retKP) != 0 { | |||
t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16)) | |||
} | |||
// Check if first and second argument can overlap | |||
xMul512(&P, &P, &co, &k) | |||
xMul(&P, &P, &co, &k) | |||
retKP = toNormX(&P) | |||
if expKP.Cmp(&retKP) != 0 { | |||
t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16)) | |||
@@ -261,13 +261,13 @@ func TestMappointHardcoded3(t *testing.T) { | |||
var expP = point{ | |||
x: fp{0x91aba9b39f280495, 0xfbd8ea69d2990aeb, 0xb03e1b8ed7fe3dba, 0x3d30a41499f08998, 0xb15a42630de9c606, 0xa7dd487fef16f5c8, 0x8673948afed8e968, 0x57ecc8710004cd4d}, | |||
z: fp{0xce8819869a942526, 0xb98ca2ff79ef8969, 0xd49c9703743a1812, 0x21dbb090f9152e03, 0xbabdcac831b1adea, 0x8cee90762baa2ddd, 0xa0dd2ddcef809d96, 0x1de2a8887a32f19b}} | |||
isom(&P, &A, &K, k) | |||
if !ceqFp(&P.x, &expP.x) || !ceqFp(&P.z, &expP.z) { | |||
xIso(&P, &A, &K, k) | |||
if !eqFp(&P.x, &expP.x) || !eqFp(&P.z, &expP.z) { | |||
normP := toNormX(&P) | |||
normPExp := toNormX(&expP) | |||
t.Errorf("P != expP [\n %s != %s\n]", normP.Text(16), normPExp.Text(16)) | |||
} | |||
if !ceqFp(&A.a, &expA.a) || !ceqFp(&A.c, &expA.c) { | |||
if !eqFp(&A.a, &expA.a) || !eqFp(&A.c, &expA.c) { | |||
t.Errorf("A != expA %X %X", A.a[0], expA.a[0]) | |||
} | |||
} | |||
@@ -291,13 +291,13 @@ func TestMappointHardcoded5(t *testing.T) { | |||
x: fp{0x3b75fc94b2a6df2d, 0x96d53dc9b0e867a0, 0x22e87202421d274e, 0x30a361440697ee1a, 0x8b52ee078bdbddcd, 0x64425d500e6b934d, 0xf47d1f568f6df391, 0x5d9d3607431395ab}, | |||
z: fp{0x746e02dafa040976, 0xcd408f2cddbf3a8e, 0xf643354e0e13a93f, 0x7c39ed96ce9a5e29, 0xfcdf26f1a1a550ca, 0x2fc8aafc4ca0a559, 0x5d204a2b14cf19ba, 0xbd2c3406762f05d}} | |||
isom(&P, &A, &K, k) | |||
if !ceqFp(&P.x, &expP.x) || !ceqFp(&P.z, &expP.z) { | |||
xIso(&P, &A, &K, k) | |||
if !eqFp(&P.x, &expP.x) || !eqFp(&P.z, &expP.z) { | |||
normP := toNormX(&P) | |||
normPExp := toNormX(&expP) | |||
t.Errorf("P != expP [\n %s != %s\n]", normP.Text(16), normPExp.Text(16)) | |||
} | |||
if !ceqFp(&A.a, &expA.a) || !ceqFp(&A.c, &expA.c) { | |||
if !eqFp(&A.a, &expA.a) || !eqFp(&A.c, &expA.c) { | |||
t.Errorf("A != expA %X %X", A.a[0], expA.a[0]) | |||
} | |||
} | |||
@@ -317,7 +317,7 @@ func BenchmarkXMul(b *testing.B) { | |||
k = fp{0x7A36C930A83EFBD5, 0xD0E80041ED0DDF9F, 0x5AA17134F1B8F877, 0x975711EC94168E51, 0xB3CAD962BED4BAC5, 0x3026DFDD7E4F5687, 0xE67F91AB8EC9C3AF, 0x34671D3FD8C317E7} | |||
for n := 0; n < b.N; n++ { | |||
xMul512(&kP, &P, &co, &k) | |||
xMul(&kP, &P, &co, &k) | |||
} | |||
} | |||
@@ -366,6 +366,6 @@ func BenchmarkIsom(b *testing.B) { | |||
kern.z = toFp("1") | |||
for n := 0; n < b.N; n++ { | |||
isom(&P, &co, &kern, k) | |||
xIso(&P, &co, &kern, k) | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
// Package csidh implements cSIDH key exchange, isogeny-based scheme | |||
// resulting from the group action. Implementation uses only prime | |||
// field of a size 512-bits and uses Ed some performance improvements | |||
// by using twisted Edwards curves in the isogeny image curve | |||
// computations. This work has been described by M. Meyer and S. Reith | |||
// in the ia.cr/2018/782. Original cSIDH paper can be found in the | |||
// ia.cr/2018/383. | |||
// | |||
// It is experimental implementation, not meant to be secure. Have fun! | |||
// | |||
package csidh |
@@ -12,7 +12,7 @@ import ( | |||
// We declare variables not constants, in order to facilitate testing. | |||
var ( | |||
// Signals support for BMI2 (MULX) | |||
hasBMI2 = cpu.X86.HasBMI2 | |||
hasBMI2 = cpu.X86.HasBMI2 //nolint | |||
// Signals support for ADX and BMI2 | |||
hasADXandBMI2 = cpu.X86.HasBMI2 && cpu.X86.HasADX | |||
) | |||
@@ -20,7 +20,7 @@ var ( | |||
// Constant time select. | |||
// if pick == 0xFF..FF (out = in1) | |||
// if pick == 0 (out = in2) | |||
// else out is undefined | |||
// else out is undefined. | |||
func ctPick64(which uint64, in1, in2 uint64) uint64 { | |||
return (in1 & which) | (in2 & ^which) | |||
} | |||
@@ -41,7 +41,6 @@ func mulGeneric(r, x, y *fp) { | |||
var c, q uint64 | |||
for i := 0; i < numWords-1; i++ { | |||
q = ((x[i] * y[0]) + s[0]) * pNegInv[0] | |||
mul576(&t1, &p, q) | |||
mul576(&t2, y, x[i]) | |||
@@ -143,7 +142,7 @@ func addRdc(r, x, y *fp) { | |||
r[7] = ctPick64(w, r[7], t[7]) | |||
} | |||
// r = x - y | |||
// r = x - y. | |||
func sub512(r, x, y *fp) uint64 { | |||
var c uint64 | |||
r[0], c = bits.Sub64(x[0], y[0], 0) | |||
@@ -199,7 +198,7 @@ func modExpRdcCommon(r, b, e *fp, fpBitLen int) { | |||
precomp[0] = one // b ^ 0 | |||
precomp[1] = *b // b ^ 1 | |||
for i := 2; i < 16; i = i + 2 { | |||
// TODO: implement fast squering. Then interleaving fast squaring | |||
// OPTIMIZE: implement fast squering. Then interleaving fast squaring | |||
// with multiplication should improve performance. | |||
mulRdc(&precomp[i], &precomp[i/2], &precomp[i/2]) // sqr | |||
mulRdc(&precomp[i+1], &precomp[i], b) | |||
@@ -234,7 +233,6 @@ func modExpRdcCommon(r, b, e *fp, fpBitLen int) { | |||
r[5] = ctPick64(w, r[5], t[5]) | |||
r[6] = ctPick64(w, r[6], t[6]) | |||
r[7] = ctPick64(w, r[7], t[7]) | |||
} | |||
// modExpRdc does modular exponentation of 512-bit number. | |||
@@ -280,7 +278,7 @@ func (v *fp) isZero() bool { | |||
return ctIsNonZero64(r) == 0 | |||
} | |||
// equal checks if v is equal to in. Constant time | |||
// equal checks if v is equal to in. Constant time. | |||
func (v *fp) equal(in *fp) bool { | |||
var r uint64 | |||
for i := range v { | |||
@@ -4,6 +4,9 @@ package csidh | |||
import "math/bits" | |||
// mul576 implements schoolbook multiplication of | |||
// 64x512-bit integer. Returns result modulo 2^512. | |||
// r = m1*m2 | |||
func mul512(r, m1 *fp, m2 uint64) { | |||
var c, h, l uint64 | |||
@@ -33,10 +36,14 @@ func mul512(r, m1 *fp, m2 uint64) { | |||
r[6], c = bits.Add64(l, c, 0) | |||
c = h + c | |||
h, l = bits.Mul64(m2, m1[7]) | |||
_, l = bits.Mul64(m2, m1[7]) | |||
r[7], _ = bits.Add64(l, c, 0) | |||
} | |||
// mul576 implements schoolbook multiplication of | |||
// 64x512-bit integer. Returns 576-bit result of | |||
// multiplication. | |||
// r = m1*m2 | |||
func mul576(r *[9]uint64, m1 *fp, m2 uint64) { | |||
var c, h, l uint64 | |||
@@ -72,6 +79,9 @@ func mul576(r *[9]uint64, m1 *fp, m2 uint64) { | |||
r[8] += c | |||
} | |||
// cswap512 implements constant time swap operation. | |||
// If choice = 0, leave x,y unchanged. If choice = 1, set x,y = y,x. | |||
// If choice is neither 0 nor 1 then behaviour is undefined. | |||
func cswap512(x, y *fp, choice uint8) { | |||
var tmp uint64 | |||
mask64 := 0 - uint64(choice) | |||
@@ -83,10 +93,6 @@ func cswap512(x, y *fp, choice uint8) { | |||
} | |||
} | |||
func mul(res, x, y *fp) { | |||
mulGeneric(res, x, y) | |||
} | |||
// mulRdc performs montgomery multiplication r = x * y mod P. | |||
// Returned result r is already reduced and in Montgomery domain. | |||
func mulRdc(r, x, y *fp) { | |||
@@ -39,14 +39,13 @@ func testFp512Mul3Nominal(t *testing.T) { | |||
} | |||
// Check if mul512 produces result | |||
// z = x*y mod 2^512 | |||
// z = x*y mod 2^512. | |||
func TestFp512Mul3_Nominal(t *testing.T) { | |||
hasBMI2 = false | |||
testFp512Mul3Nominal(t) | |||
resetCPUFeatures() | |||
testFp512Mul3Nominal(t) | |||
} | |||
func TestAddRdcRandom(t *testing.T) { | |||
@@ -78,26 +77,26 @@ func TestAddRdcNominal(t *testing.T) { | |||
tmp := oneFp512 | |||
addRdc(&res, &tmp, &p) | |||
if !ceq512(&res, &tmp) { | |||
if !eqFp(&res, &tmp) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
tmp = zeroFp512 | |||
addRdc(&res, &p, &p) | |||
if !ceq512(&res, &p) { | |||
if !eqFp(&res, &p) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
tmp = fp{1, 1, 1, 1, 1, 1, 1, 1} | |||
addRdc(&res, &p, &tmp) | |||
if !ceq512(&res, &tmp) { | |||
if !eqFp(&res, &tmp) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
tmp = fp{1, 1, 1, 1, 1, 1, 1, 1} | |||
exp := fp{2, 2, 2, 2, 2, 2, 2, 2} | |||
addRdc(&res, &tmp, &tmp) | |||
if !ceq512(&res, &exp) { | |||
if !eqFp(&res, &exp) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
} | |||
@@ -168,19 +167,19 @@ func TestCswap(t *testing.T) { | |||
arg1cpy := arg1 | |||
cswap512(&arg1, &arg2, 0) | |||
if !ceq512(&arg1, &arg1cpy) { | |||
if !eqFp(&arg1, &arg1cpy) { | |||
t.Error("cswap swapped") | |||
} | |||
arg1cpy = arg1 | |||
cswap512(&arg1, &arg2, 1) | |||
if ceq512(&arg1, &arg1cpy) { | |||
if eqFp(&arg1, &arg1cpy) { | |||
t.Error("cswap didn't swapped") | |||
} | |||
arg1cpy = arg1 | |||
cswap512(&arg1, &arg2, 0xF2) | |||
if ceq512(&arg1, &arg1cpy) { | |||
if eqFp(&arg1, &arg1cpy) { | |||
t.Error("cswap didn't swapped") | |||
} | |||
} | |||
@@ -191,7 +190,7 @@ func TestSubRdc(t *testing.T) { | |||
// 1 - 1 mod P | |||
tmp := oneFp512 | |||
subRdc(&res, &tmp, &tmp) | |||
if !ceq512(&res, &zeroFp512) { | |||
if !eqFp(&res, &zeroFp512) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
zero(&res) | |||
@@ -201,7 +200,7 @@ func TestSubRdc(t *testing.T) { | |||
exp[0]-- | |||
subRdc(&res, &zeroFp512, &oneFp512) | |||
if !ceq512(&res, &exp) { | |||
if !eqFp(&res, &exp) { | |||
t.Errorf("Wrong value\n%X\n%X", res, exp) | |||
} | |||
zero(&res) | |||
@@ -210,13 +209,13 @@ func TestSubRdc(t *testing.T) { | |||
pMinusOne := p | |||
pMinusOne[0]-- | |||
subRdc(&res, &p, &pMinusOne) | |||
if !ceq512(&res, &oneFp512) { | |||
if !eqFp(&res, &oneFp512) { | |||
t.Errorf("Wrong value\n[%X != %X]", res, oneFp512) | |||
} | |||
zero(&res) | |||
subRdc(&res, &p, &oneFp512) | |||
if !ceq512(&res, &pMinusOne) { | |||
if !eqFp(&res, &pMinusOne) { | |||
t.Errorf("Wrong value\n[%X != %X]", res, pMinusOne) | |||
} | |||
} | |||
@@ -245,28 +244,28 @@ func testMulRdc(t *testing.T) { | |||
// 0*0 | |||
tmp := zeroFp512 | |||
mulRdc(&res, &tmp, &tmp) | |||
if !ceq512(&res, &tmp) { | |||
if !eqFp(&res, &tmp) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
// 1*m1 == m1 | |||
zero(&res) | |||
mulRdc(&res, &m1, &one) | |||
if !ceq512(&res, &m1) { | |||
if !eqFp(&res, &m1) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
// m1*m2 < p | |||
zero(&res) | |||
mulRdc(&res, &m1, &m2) | |||
if !ceq512(&res, &m1m2) { | |||
if !eqFp(&res, &m1m2) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
// m1*m1 > p | |||
zero(&res) | |||
mulRdc(&res, &m1, &m1) | |||
if !ceq512(&res, &m1m1) { | |||
if !eqFp(&res, &m1m1) { | |||
t.Errorf("Wrong value\n%X", res) | |||
} | |||
} | |||
@@ -300,13 +299,13 @@ func TestModExp(t *testing.T) { | |||
// Perform modexp with our implementation | |||
modExpRdc512(&resFp, &baseFp, &expFp) | |||
if !ceq512(&resFp, &resFpExp) { | |||
if !eqFp(&resFp, &resFpExp) { | |||
t.Errorf("Wrong value\n%X!=%X", resFp, intGetU64(&resExp)) | |||
} | |||
} | |||
} | |||
// Test uses Euler's Criterion | |||
// Test uses Euler's Criterion. | |||
func TestIsNonQuadRes(t *testing.T) { | |||
var n, nMont big.Int | |||
var pm1o2, rawP big.Int | |||
@@ -318,7 +317,7 @@ func TestIsNonQuadRes(t *testing.T) { | |||
rawP.SetString("0x65b48e8f740f89bffc8ab0d15e3e4c4ab42d083aedc88c425afbfcc69322c9cda7aac6c567f35507516730cc1f0b4f25c2721bf457aca8351b81b90533c6c87b", 0) | |||
// There is 641 quadratic residues in this range | |||
for i := uint64(1); i < 1000; i++ { | |||
for i := uint64(1); i < uint64(numIter); i++ { | |||
n.SetUint64(i) | |||
n.Exp(&n, &pm1o2, &rawP) | |||
// exp == 1 iff n is quadratic non-residue | |||
@@ -6,7 +6,6 @@ import ( | |||
mrand "math/rand" | |||
) | |||
// Commonly used variables | |||
var ( | |||
// Number of interations | |||
numIter = 10 | |||
@@ -16,6 +15,8 @@ var ( | |||
zeroFp512 = fp{} | |||
// One in fp | |||
oneFp512 = fp{1, 0, 0, 0, 0, 0, 0, 0} | |||
// file with KAT vectors | |||
katFile = "testdata/csidh_testvectors.dat" | |||
) | |||
// Converts dst to Montgomery if "toMont==true" or from Montgomery domain otherwise. | |||
@@ -41,14 +42,14 @@ func fp2S(v fp) string { | |||
return str | |||
} | |||
// zeroize fp | |||
// zeroize fp. | |||
func zero(v *fp) { | |||
for i := range *v { | |||
v[i] = 0 | |||
} | |||
} | |||
// returns random value in a range (0,p) | |||
// returns random value in a range (0,p). | |||
func randomFp() fp { | |||
var u fp | |||
for i := 0; i < 8; i++ { | |||
@@ -57,25 +58,8 @@ func randomFp() fp { | |||
return u | |||
} | |||
// x<y: <0 | |||
// x>y: >0 | |||
// x==y: 0 | |||
func cmp512(x, y *fp) int { | |||
if len(*x) == len(*y) { | |||
for i := len(*x) - 1; i >= 0; i-- { | |||
if x[i] < y[i] { | |||
return -1 | |||
} else if x[i] > y[i] { | |||
return 1 | |||
} | |||
} | |||
return 0 | |||
} | |||
return len(*x) - len(*y) | |||
} | |||
// return x==y for fp | |||
func ceqFp(l, r *fp) bool { | |||
// return x==y for fp. | |||
func eqFp(l, r *fp) bool { | |||
for idx := range l { | |||
if l[idx] != r[idx] { | |||
return false | |||
@@ -84,19 +68,14 @@ func ceqFp(l, r *fp) bool { | |||
return true | |||
} | |||
// return x==y for point | |||
// return x==y for point. | |||
func ceqpoint(l, r *point) bool { | |||
return ceqFp(&l.x, &r.x) && ceqFp(&l.z, &r.z) | |||
} | |||
// return x==y | |||
func ceq512(x, y *fp) bool { | |||
return cmp512(x, y) == 0 | |||
return eqFp(&l.x, &r.x) && eqFp(&l.z, &r.z) | |||
} | |||
// Converts src to big.Int. Function assumes that src is a slice of uint64 | |||
// values encoded in little-endian byte order. | |||
func intSetU64(dst *big.Int, src []uint64) *big.Int { | |||
func intSetU64(dst *big.Int, src []uint64) { | |||
var tmp big.Int | |||
dst.SetUint64(0) | |||
@@ -105,7 +84,6 @@ func intSetU64(dst *big.Int, src []uint64) *big.Int { | |||
tmp.Lsh(&tmp, uint(i*64)) | |||
dst.Add(dst, &tmp) | |||
} | |||
return dst | |||
} | |||
// Converts src to an array of uint64 values encoded in little-endian | |||
@@ -140,7 +118,7 @@ func toNormX(point *point) big.Int { | |||
return bigDnt | |||
} | |||
// Converts string to fp element in Montgomery domain of cSIDH-512 | |||
// Converts string to fp element in Montgomery domain of cSIDH-512. | |||
func toFp(num string) fp { | |||
var tmp big.Int | |||
var ok bool | |||