1
0
mirror of https://github.com/henrydcase/nobs.git synced 2024-11-26 00:51:22 +00:00

csidh: cosmettic updates

This commit is contained in:
Henry Case 2020-05-13 23:48:43 +00:00
parent 7d891c7eb8
commit 91945fde1f
9 changed files with 218 additions and 200 deletions

View File

@ -7,7 +7,7 @@ import (
// 511-bit number representing prime field element GF(p) // 511-bit number representing prime field element GF(p)
type fp [numWords]uint64 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 { type point struct {
x fp x fp
z fp z fp
@ -27,17 +27,20 @@ type fpRngGen struct {
// Defines operations on public key // Defines operations on public key
type PublicKey struct { type PublicKey struct {
fpRngGen 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 a fp
} }
// Defines operations on private key // Defines operations on private key
type PrivateKey struct { type PrivateKey struct {
fpRngGen fpRngGen
// private key is a set of integers randomly
// each sampled from a range [-5, 5].
e [PrivateKeySize]int8 e [PrivateKeySize]int8
} }
// randFp generates random element from Fp // randFp generates random element from Fp.
func (s *fpRngGen) randFp(v *fp, rng io.Reader) { func (s *fpRngGen) randFp(v *fp, rng io.Reader) {
mask := uint64(1<<(pbits%limbBitSize)) - 1 mask := uint64(1<<(pbits%limbBitSize)) - 1
for { 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 Q point
var r1, d1, r2, d2 bool var r1, d1, r2, d2 bool
if (halfR - halfL) == 1 { if (halfR - halfL) == 1 {
// base case
if !p.z.isZero() { if !p.z.isZero() {
var tmp = fp{primes[halfL]} var tmp = fp{primes[halfL]}
xMul512(p, p, a, &tmp) xMul(p, p, a, &tmp)
if !p.z.isZero() { if !p.z.isZero() {
// order does not divide p+1 // order does not divide p+1 -> ordinary curve
return false, true return true, false
} }
mul512(order, order, primes[halfL]) mul512(order, order, primes[halfL])
if sub512(&tmp, &fourSqrtP, order) == 1 { if isLess(&fourSqrtP, order) {
// order > 4*sqrt(p) -> supersingular // order > 4*sqrt(p) -> supersingular curve
return true, true return true, true
} }
} }
@ -86,21 +97,27 @@ func cofactorMultiples(p *point, a *coeff, halfL, halfR int, order *fp) (bool, b
// perform another recursive step // perform another recursive step
mid := halfL + ((halfR - halfL + 1) / 2) mid := halfL + ((halfR - halfL + 1) / 2)
var mulL, mulR = fp{1}, fp{1} var mulL, mulR = fp{1}, fp{1}
// compute u = primes_1 * ... * primes_m
for i := halfL; i < mid; i++ { for i := halfL; i < mid; i++ {
mul512(&mulR, &mulR, primes[i]) mul512(&mulR, &mulR, primes[i])
} }
// compute v = primes_m+1 * ... * primes_n
for i := mid; i < halfR; i++ { for i := mid; i < halfR; i++ {
mul512(&mulL, &mulL, primes[i]) mul512(&mulL, &mulL, primes[i])
} }
xMul512(&Q, p, a, &mulR) // calculate Q_i
xMul512(p, p, a, &mulL) xMul(&Q, p, a, &mulR)
xMul(p, p, a, &mulL)
r1, d1 = cofactorMultiples(&Q, a, mid, halfR, order) d1, r1 = cofactorMul(&Q, a, mid, halfR, order)
r2, d2 = cofactorMultiples(p, a, halfL, mid, order) d2, r2 = cofactorMul(p, a, halfL, mid, order)
return r1 || r2, d1 || d2 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) { func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
var k [2]fp var k [2]fp
var e [2][primeCount]uint8 var e [2][primeCount]uint8
@ -140,7 +157,7 @@ func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
continue continue
} }
xMul512(&P, &P, &A, &k[sign]) xMul(&P, &P, &A, &k[sign])
done[sign] = true done[sign] = true
for i, v := range primes { 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() { if !K.z.isZero() {
isom(&P, &A, &K, v) xIso(&P, &A, &K, v)
e[sign][i] = e[sign][i] - 1 e[sign][i] = e[sign][i] - 1
if e[sign][i] == 0 { if e[sign][i] == 0 {
mul512(&k[sign], &k[sign], primes[i]) mul512(&k[sign], &k[sign], primes[i])
@ -225,7 +242,7 @@ func GeneratePrivateKey(key *PrivateKey, rng io.Reader) error {
// Public key operations // Public key operations
// Assumes key is in Montgomery domain // Assumes key is in Montgomery domain.
func (c *PublicKey) Import(key []byte) bool { func (c *PublicKey) Import(key []byte) bool {
if len(key) != numWords*limbByteSize { if len(key) != numWords*limbByteSize {
return false return false
@ -238,7 +255,7 @@ func (c *PublicKey) Import(key []byte) bool {
return true 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 { func (c *PublicKey) Export(out []byte) bool {
if len(out) != numWords*limbByteSize { if len(out) != numWords*limbByteSize {
return false return false
@ -251,44 +268,45 @@ func (c *PublicKey) Export(out []byte) bool {
return true return true
} }
func (c *PublicKey) reset() {
for i := range c.a {
c.a[i] = 0
}
}
func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) { func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
pub.reset() for i := range pub.a {
pub.a[i] = 0
}
groupAction(pub, prv, rng) groupAction(pub, prv, rng)
} }
// Validate does public key validation. It returns true if // Validate returns true if 'pub' is a valid cSIDH public key,
// a 'pub' is a valid cSIDH public key, otherwise false. // 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 { func Validate(pub *PublicKey, rng io.Reader) bool {
// Check if in range // Check if in range
if !isLess(&pub.a, &p) { if !isLess(&pub.a, &p) {
return false return false
} }
// j-invariant for montgomery curves is something like // Check if pub represents a smooth Montgomery curve.
// j = (256*(A^3-3)^3)/(A^2 - 4), so any |A| = 2 is invalid
if pub.a.equal(&two) || pub.a.equal(&twoNeg) { if pub.a.equal(&two) || pub.a.equal(&twoNeg) {
return false return false
} }
// P must have big enough order to prove supersingularity. The // Check if pub represents a supersingular curve.
// probability that this loop will be repeated is negligible.
for { for {
var P point var P point
var A = point{pub.a, one} 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) pub.randFp(&P.x, rng)
P.z = one P.z = one
xDbl(&P, &P, &A) xDbl(&P, &P, &A)
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 { if done {
return res return res
} }
@ -297,6 +315,9 @@ func Validate(pub *PublicKey, rng io.Reader) bool {
// DeriveSecret computes a cSIDH shared secret. If successful, returns true // DeriveSecret computes a cSIDH shared secret. If successful, returns true
// and fills 'out' with shared secret. Function returns false in case 'pub' is invalid. // 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 { func DeriveSecret(out *[64]byte, pub *PublicKey, prv *PrivateKey, rng io.Reader) bool {
if !Validate(pub, rng) { if !Validate(pub, rng) {
return false return false

View File

@ -2,14 +2,12 @@ package csidh
import ( import (
"bytes" "bytes"
crand "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"testing" "testing"
crand "crypto/rand"
"github.com/henrydcase/nobs/drbg" "github.com/henrydcase/nobs/drbg"
) )
@ -30,6 +28,20 @@ var StatusValues = map[int]string{
InvalidPublicKey2: "invalid_public_key2", 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 { type TestVector struct {
ID int `json:"Id"` ID int `json:"Id"`
Pk1 string `json:"Pk1"` Pk1 string `json:"Pk1"`
@ -39,10 +51,6 @@ type TestVector struct {
Status string `json:"status"` Status string `json:"status"`
} }
type TestVectors struct {
Vectors []TestVector `json:"Vectors"`
}
var rng *drbg.CtrDrbg var rng *drbg.CtrDrbg
func init() { func init() {
@ -56,6 +64,10 @@ func init() {
} }
} }
type TestVectors struct {
Vectors []TestVector `json:"Vectors"`
}
func TestCompare64(t *testing.T) { func TestCompare64(t *testing.T) {
const s uint64 = 0xFFFFFFFFFFFFFFFF const s uint64 = 0xFFFFFFFFFFFFFFFF
var val1 = fp{0, 2, 3, 4, 5, 6, 7, 8} var val1 = fp{0, 2, 3, 4, 5, 6, 7, 8}
@ -82,20 +94,17 @@ func TestEphemeralKeyExchange(t *testing.T) {
prv1.Import(prvBytes1) prv1.Import(prvBytes1)
GeneratePublicKey(&pub1, &prv1, rng) GeneratePublicKey(&pub1, &prv1, rng)
GeneratePrivateKey(&prv2, rng) checkErr(t, GeneratePrivateKey(&prv2, rng), "PrivateKey generation failed")
GeneratePublicKey(&pub2, &prv2, rng) GeneratePublicKey(&pub2, &prv2, rng)
if !DeriveSecret(&ss1, &pub1, &prv2, rng) { Ok(t,
t.Errorf("Derivation failed\n") DeriveSecret(&ss1, &pub1, &prv2, rng),
} "Derivation failed")
Ok(t,
if !DeriveSecret(&ss2, &pub2, &prv1, rng) { DeriveSecret(&ss2, &pub2, &prv1, rng),
t.Errorf("Derivation failed\n") "Derivation failed")
}
if !bytes.Equal(ss1[:], ss2[:]) { if !bytes.Equal(ss1[:], ss2[:]) {
fmt.Printf("%X\n", ss1)
fmt.Printf("%X\n", ss2)
t.Error("ss1 != ss2") t.Error("ss1 != ss2")
} }
} }
@ -104,7 +113,7 @@ func TestPrivateKeyExportImport(t *testing.T) {
var buf [37]byte var buf [37]byte
for i := 0; i < numIter; i++ { for i := 0; i < numIter; i++ {
var prv1, prv2 PrivateKey var prv1, prv2 PrivateKey
GeneratePrivateKey(&prv1, rng) checkErr(t, GeneratePrivateKey(&prv1, rng), "PrivateKey generation failed")
prv1.Export(buf[:]) prv1.Export(buf[:])
prv2.Import(buf[:]) prv2.Import(buf[:])
@ -153,7 +162,7 @@ func TestPublicKeyExportImport(t *testing.T) {
for i := 0; i < numIter; i++ { for i := 0; i < numIter; i++ {
var prv PrivateKey var prv PrivateKey
var pub1, pub2 PublicKey var pub1, pub2 PublicKey
GeneratePrivateKey(&prv, rng) checkErr(t, GeneratePrivateKey(&prv, rng), "PrivateKey generation failed")
GeneratePublicKey(&pub1, &prv, rng) GeneratePublicKey(&pub1, &prv, rng)
pub1.Export(buf[:]) 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) { func TestKAT(t *testing.T) {
var tests TestVectors var tests TestVectors
var testVectorFile string var katFile string
// Helper checks if e==true and reports an error if not. // Helper checks if e==true and reports an error if not.
checkExpr := func(e bool, vec *TestVector, t *testing.T, msg string) { checkExpr := func(e bool, vec *TestVector, t *testing.T, msg string) {
@ -179,9 +188,9 @@ func TestKAT(t *testing.T) {
} }
if hasADXandBMI2 { if hasADXandBMI2 {
testVectorFile = "testdata/csidh_testvectors.dat" katFile = "testdata/csidh_testvectors.dat"
} else { } else {
testVectorFile = "testdata/csidh_testvectors_small.dat" katFile = "testdata/csidh_testvectors_small.dat"
} }
// checkSharedSecret implements nominal case - imports asymmetric keys for // checkSharedSecret implements nominal case - imports asymmetric keys for
@ -197,39 +206,24 @@ func TestKAT(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkExpr( checkExpr(prv1.Import(prBuf[:]), vec, t, "PrivateKey wrong")
prv1.Import(prBuf[:]),
vec, t, "PrivateKey wrong")
pkBuf, err := hex.DecodeString(vec.Pk1) pkBuf, err := hex.DecodeString(vec.Pk1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkExpr( checkExpr(pub1.Import(pkBuf[:]), vec, t, "PublicKey 1 wrong")
pub1.Import(pkBuf[:]),
vec, t, "PublicKey 1 wrong")
pkBuf, err = hex.DecodeString(vec.Pk2) pkBuf, err = hex.DecodeString(vec.Pk2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkExpr( checkExpr(pub2.Import(pkBuf[:]), vec, t, "PublicKey 2 wrong")
pub2.Import(pkBuf[:]), checkExpr(DeriveSecret(&ss, &pub2, &prv1, rng), vec, t, "Error when deriving key")
vec, t, "PublicKey 2 wrong")
checkExpr(
DeriveSecret(&ss, &pub2, &prv1, rng),
vec, t, "Error when deriving key")
ssExp, err := hex.DecodeString(vec.Ss) ssExp, err := hex.DecodeString(vec.Ss)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkExpr( checkExpr(bytes.Equal(ss[:], ssExp) == (status == Valid), vec, t, "Unexpected value of shared secret")
bytes.Equal(ss[:], ssExp) == (status == Valid),
vec, t, "Unexpected value of shared secret")
} }
// checkPublicKey1 imports public and private key for one party A // checkPublicKey1 imports public and private key for one party A
// and tries to generate public key for a private key. After that // and tries to generate public key for a private key. After that
// it compares generated key to a key from test vector. Comparison // it compares generated key to a key from test vector. Comparison
@ -254,7 +248,7 @@ func TestKAT(t *testing.T) {
vec, t, "PrivateKey wrong") vec, t, "PrivateKey wrong")
// Generate public key // Generate public key
GeneratePrivateKey(&prv, rng) checkErr(t, GeneratePrivateKey(&prv, rng), "PrivateKey generation failed")
pub.Export(pubBytesGot[:]) pub.Export(pubBytesGot[:])
// pubBytesGot must be different than pubBytesExp // pubBytesGot must be different than pubBytesExp
@ -262,27 +256,23 @@ func TestKAT(t *testing.T) {
!bytes.Equal(pubBytesGot[:], pubBytesExp), !bytes.Equal(pubBytesGot[:], pubBytesExp),
vec, t, "Public key generated is the same as public key from the test vector") 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 // checkPublicKey2 the goal is to test key validation. Test tries to
// import public key for B and ensure that import succeeds in case // import public key for B and ensure that import succeeds in case
// status is "Valid" and fails otherwise. // status is "Valid" and fails otherwise.
checkPublicKey2 := func(vec *TestVector, t *testing.T, status int) { checkPublicKey2 := func(vec *TestVector, t *testing.T, status int) {
var pub PublicKey var pub PublicKey
pubBytesExp, err := hex.DecodeString(vec.Pk2) pubBytesExp, err := hex.DecodeString(vec.Pk2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Import validates an input, so it must fail // Import validates an input, so it must fail
pub.Import(pubBytesExp[:]) pub.Import(pubBytesExp[:])
checkExpr( checkExpr(
Validate(&pub, rng) == (status == Valid || status == ValidPublicKey2), Validate(&pub, rng) == (status == Valid || status == ValidPublicKey2),
vec, t, "PublicKey has been validated correctly") vec, t, "PublicKey has been validated correctly")
} }
// Load test data // Load test data
file, err := os.Open(testVectorFile) file, err := os.Open(katFile)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
@ -290,9 +280,14 @@ func TestKAT(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
// Loop over all test cases // 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 { switch test.Status {
case StatusValues[Valid]: case StatusValues[Valid]:
checkSharedSecret(&test, t, Valid) checkSharedSecret(&test, t, Valid)
@ -314,23 +309,23 @@ func TestKAT(t *testing.T) {
var prv1, prv2 PrivateKey var prv1, prv2 PrivateKey
var pub1, pub2 PublicKey var pub1, pub2 PublicKey
// Private key generation // Private key generation.
func BenchmarkGeneratePrivate(b *testing.B) { func BenchmarkGeneratePrivate(b *testing.B) {
for n := 0; n < b.N; n++ { 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) { func BenchmarkGenerateKeyPair(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
var pub PublicKey var pub PublicKey
GeneratePrivateKey(&prv1, rng) _ = GeneratePrivateKey(&prv1, rng)
GeneratePublicKey(&pub, &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) { 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} 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) 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) { func BenchmarkValidateRandom(b *testing.B) {
var tmp [64]byte var tmp [64]byte
var pub PublicKey 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) { func BenchmarkValidateGenerated(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
GeneratePrivateKey(&prv1, rng) _ = GeneratePrivateKey(&prv1, rng)
GeneratePublicKey(&pub1, &prv1, rng) GeneratePublicKey(&pub1, &prv1, rng)
Validate(&pub1, rng) Validate(&pub1, rng)
} }
} }
// Generate some keys and benchmark derive // Generate some keys and benchmark derive.
func BenchmarkDerive(b *testing.B) { func BenchmarkDerive(b *testing.B) {
var ss [64]byte var ss [64]byte
GeneratePrivateKey(&prv1, rng) _ = GeneratePrivateKey(&prv1, rng)
GeneratePublicKey(&pub1, &prv1, rng) GeneratePublicKey(&pub1, &prv1, rng)
GeneratePrivateKey(&prv2, rng) _ = GeneratePrivateKey(&prv2, rng)
GeneratePublicKey(&pub2, &prv2, rng) GeneratePublicKey(&pub2, &prv2, rng)
b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
DeriveSecret(&ss, &pub2, &prv1, rng) DeriveSecret(&ss, &pub2, &prv1, rng)
} }
} }
// Benchmarks both - key generation and derivation // Benchmarks both - key generation and derivation.
func BenchmarkDeriveGenerated(b *testing.B) { func BenchmarkDeriveGenerated(b *testing.B) {
var ss [64]byte var ss [64]byte
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
GeneratePrivateKey(&prv1, rng) _ = GeneratePrivateKey(&prv1, rng)
GeneratePublicKey(&pub1, &prv1, rng) GeneratePublicKey(&pub1, &prv1, rng)
GeneratePrivateKey(&prv2, rng) _ = GeneratePrivateKey(&prv2, rng)
GeneratePublicKey(&pub2, &prv2, rng) GeneratePublicKey(&pub2, &prv2, rng)
DeriveSecret(&ss, &pub2, &prv1, rng) DeriveSecret(&ss, &pub2, &prv1, rng)

View File

@ -1,10 +1,10 @@
package csidh package csidh
// Implements differential arithmetic in P^1 for montgomery // xAdd implements differential arithmetic in P^1 for Montgomery
// curves a mapping: x(P),x(Q),x(P-Q) -> x(P+Q) // curves E(x): x^3 + A*x^2 + x by using x-coordinate only arithmetic.
// PaQ = P + Q // x(PaQ) = x(P) + x(Q) by using x(P-Q)
// This algorithms is correctly defined only for cases when // 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) { func xAdd(PaQ, P, Q, PdQ *point) {
var t0, t1, t2, t3 fp var t0, t1, t2, t3 fp
addRdc(&t0, &P.x, &P.z) addRdc(&t0, &P.x, &P.z)
@ -21,8 +21,10 @@ func xAdd(PaQ, P, Q, PdQ *point) {
mulRdc(&PaQ.z, &PdQ.x, &t3) mulRdc(&PaQ.z, &PdQ.x, &t3)
} }
// Q = 2*P on a montgomery curve E(x): x^3 + A*x^2 + x // xDbl implements point doubling on a Montgomery curve
// It is correctly defined for all P != inf // 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) { func xDbl(Q, P, A *point) {
var t0, t1, t2 fp var t0, t1, t2 fp
addRdc(&t0, &P.x, &P.z) addRdc(&t0, &P.x, &P.z)
@ -40,8 +42,11 @@ func xDbl(Q, P, A *point) {
mulRdc(&Q.z, &t0, &t2) mulRdc(&Q.z, &t0, &t2)
} }
// PaP = 2*P; PaQ = P+Q // xDblAdd implements combined doubling of point P
// PaP can override P and PaQ can override Q // 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) { func xDblAdd(PaP, PaQ, P, Q, PdQ *point, A24 *coeff) {
var t0, t1, t2 fp 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) 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 // parameter must have a value of either 1 (results
// in swap) or 0 (results in no-swap). // in swap) or 0 (results in no-swap).
func cswappoint(P1, P2 *point, choice uint8) { func cswappoint(P1, P2 *point, choice uint8) {
@ -75,13 +80,11 @@ func cswappoint(P1, P2 *point, choice uint8) {
cswap512(&P1.z, &P2.z, choice) cswap512(&P1.z, &P2.z, choice)
} }
// A uniform Montgomery ladder. co is A coefficient of // xMul implements point multiplication with left-to-right Montgomery
// x^3 + A*x^2 + x curve. k MUST be > 0 // 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 xMul(kP, P *point, co *coeff, k *fp) {
// non-constant time.
func xMul512(kP, P *point, co *coeff, k *fp) {
var A24 coeff var A24 coeff
var Q point var Q point
var j uint var j uint
@ -107,16 +110,23 @@ func xMul512(kP, P *point, co *coeff, k *fp) {
for i := j; i > 0; { for i := j; i > 0; {
i-- i--
bit := uint8(k[i>>6] >> (i & 63) & 1) bit := uint8(k[i>>6] >> (i & 63) & 1)
swap := prevBit ^ bit cswappoint(&Q, &R, prevBit^bit)
prevBit = bit
cswappoint(&Q, &R, swap)
xDblAdd(&Q, &R, &Q, &R, P, &A24) xDblAdd(&Q, &R, &Q, &R, P, &A24)
prevBit = bit
} }
cswappoint(&Q, &R, uint8(k[0]&1)) cswappoint(&Q, &R, uint8(k[0]&1))
*kP = Q *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 t0, t1, t2, S, D fp
var Q, prod point var Q, prod point
var coEd coeff 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}) xDbl(&M[1], kern, &point{x: co.a, z: co.c})
// TODO: Not constant time. // NOTE: Not constant time.
for i := uint64(1); i < order>>1; i++ { for i := uint64(1); i < kernOrder>>1; i++ {
if i >= 2 { if i >= 2 {
xAdd(&M[i%3], &M[(i-1)%3], kern, &M[(i-2)%3]) 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) mulRdc(&Q.x, &Q.x, &t2)
subRdc(&t2, &t0, &t1) subRdc(&t2, &t0, &t1)
mulRdc(&Q.z, &Q.z, &t2) mulRdc(&Q.z, &Q.z, &t2)
} }
mulRdc(&Q.x, &Q.x, &Q.x) 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.x, &img.x, &Q.x)
mulRdc(&img.z, &img.z, &Q.z) mulRdc(&img.z, &img.z, &Q.z)
// coEd.a^order and coEd.c^order // coEd.a^kernOrder and coEd.c^kernOrder
modExpRdc64(&coEd.a, &coEd.a, order) modExpRdc64(&coEd.a, &coEd.a, kernOrder)
modExpRdc64(&coEd.c, &coEd.c, order) modExpRdc64(&coEd.c, &coEd.c, kernOrder)
// prod^8 // prod^8
mulRdc(&prod.x, &prod.x, &prod.x) 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) 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) { func montEval(res, A, x *fp) {
var t fp var t fp

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
// Actual test implementation // Actual test implementation.
func TestXAdd(t *testing.T) { func TestXAdd(t *testing.T) {
var P, Q, PdQ point var P, Q, PdQ point
var PaQ point var PaQ point
@ -180,14 +180,14 @@ func TestXMul(t *testing.T) {
checkXMul := func() { checkXMul := func() {
var kP point var kP point
xMul512(&kP, &P, &co, &k) xMul(&kP, &P, &co, &k)
retKP := toNormX(&kP) retKP := toNormX(&kP)
if expKP.Cmp(&retKP) != 0 { if expKP.Cmp(&retKP) != 0 {
t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16)) t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16))
} }
// Check if first and second argument can overlap // Check if first and second argument can overlap
xMul512(&P, &P, &co, &k) xMul(&P, &P, &co, &k)
retKP = toNormX(&P) retKP = toNormX(&P)
if expKP.Cmp(&retKP) != 0 { if expKP.Cmp(&retKP) != 0 {
t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16)) t.Errorf("\nExp: %s\nGot: %s", expKP.Text(16), retKP.Text(16))
@ -261,13 +261,13 @@ func TestMappointHardcoded3(t *testing.T) {
var expP = point{ var expP = point{
x: fp{0x91aba9b39f280495, 0xfbd8ea69d2990aeb, 0xb03e1b8ed7fe3dba, 0x3d30a41499f08998, 0xb15a42630de9c606, 0xa7dd487fef16f5c8, 0x8673948afed8e968, 0x57ecc8710004cd4d}, x: fp{0x91aba9b39f280495, 0xfbd8ea69d2990aeb, 0xb03e1b8ed7fe3dba, 0x3d30a41499f08998, 0xb15a42630de9c606, 0xa7dd487fef16f5c8, 0x8673948afed8e968, 0x57ecc8710004cd4d},
z: fp{0xce8819869a942526, 0xb98ca2ff79ef8969, 0xd49c9703743a1812, 0x21dbb090f9152e03, 0xbabdcac831b1adea, 0x8cee90762baa2ddd, 0xa0dd2ddcef809d96, 0x1de2a8887a32f19b}} z: fp{0xce8819869a942526, 0xb98ca2ff79ef8969, 0xd49c9703743a1812, 0x21dbb090f9152e03, 0xbabdcac831b1adea, 0x8cee90762baa2ddd, 0xa0dd2ddcef809d96, 0x1de2a8887a32f19b}}
isom(&P, &A, &K, k) xIso(&P, &A, &K, k)
if !ceqFp(&P.x, &expP.x) || !ceqFp(&P.z, &expP.z) { if !eqFp(&P.x, &expP.x) || !eqFp(&P.z, &expP.z) {
normP := toNormX(&P) normP := toNormX(&P)
normPExp := toNormX(&expP) normPExp := toNormX(&expP)
t.Errorf("P != expP [\n %s != %s\n]", normP.Text(16), normPExp.Text(16)) 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]) 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}, x: fp{0x3b75fc94b2a6df2d, 0x96d53dc9b0e867a0, 0x22e87202421d274e, 0x30a361440697ee1a, 0x8b52ee078bdbddcd, 0x64425d500e6b934d, 0xf47d1f568f6df391, 0x5d9d3607431395ab},
z: fp{0x746e02dafa040976, 0xcd408f2cddbf3a8e, 0xf643354e0e13a93f, 0x7c39ed96ce9a5e29, 0xfcdf26f1a1a550ca, 0x2fc8aafc4ca0a559, 0x5d204a2b14cf19ba, 0xbd2c3406762f05d}} z: fp{0x746e02dafa040976, 0xcd408f2cddbf3a8e, 0xf643354e0e13a93f, 0x7c39ed96ce9a5e29, 0xfcdf26f1a1a550ca, 0x2fc8aafc4ca0a559, 0x5d204a2b14cf19ba, 0xbd2c3406762f05d}}
isom(&P, &A, &K, k) xIso(&P, &A, &K, k)
if !ceqFp(&P.x, &expP.x) || !ceqFp(&P.z, &expP.z) { if !eqFp(&P.x, &expP.x) || !eqFp(&P.z, &expP.z) {
normP := toNormX(&P) normP := toNormX(&P)
normPExp := toNormX(&expP) normPExp := toNormX(&expP)
t.Errorf("P != expP [\n %s != %s\n]", normP.Text(16), normPExp.Text(16)) 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]) 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} k = fp{0x7A36C930A83EFBD5, 0xD0E80041ED0DDF9F, 0x5AA17134F1B8F877, 0x975711EC94168E51, 0xB3CAD962BED4BAC5, 0x3026DFDD7E4F5687, 0xE67F91AB8EC9C3AF, 0x34671D3FD8C317E7}
for n := 0; n < b.N; n++ { 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") kern.z = toFp("1")
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
isom(&P, &co, &kern, k) xIso(&P, &co, &kern, k)
} }
} }

11
dh/csidh/doc.go Normal file
View File

@ -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

View File

@ -12,7 +12,7 @@ import (
// We declare variables not constants, in order to facilitate testing. // We declare variables not constants, in order to facilitate testing.
var ( var (
// Signals support for BMI2 (MULX) // Signals support for BMI2 (MULX)
hasBMI2 = cpu.X86.HasBMI2 hasBMI2 = cpu.X86.HasBMI2 //nolint
// Signals support for ADX and BMI2 // Signals support for ADX and BMI2
hasADXandBMI2 = cpu.X86.HasBMI2 && cpu.X86.HasADX hasADXandBMI2 = cpu.X86.HasBMI2 && cpu.X86.HasADX
) )
@ -20,7 +20,7 @@ var (
// Constant time select. // Constant time select.
// if pick == 0xFF..FF (out = in1) // if pick == 0xFF..FF (out = in1)
// if pick == 0 (out = in2) // if pick == 0 (out = in2)
// else out is undefined // else out is undefined.
func ctPick64(which uint64, in1, in2 uint64) uint64 { func ctPick64(which uint64, in1, in2 uint64) uint64 {
return (in1 & which) | (in2 & ^which) return (in1 & which) | (in2 & ^which)
} }
@ -41,7 +41,6 @@ func mulGeneric(r, x, y *fp) {
var c, q uint64 var c, q uint64
for i := 0; i < numWords-1; i++ { for i := 0; i < numWords-1; i++ {
q = ((x[i] * y[0]) + s[0]) * pNegInv[0] q = ((x[i] * y[0]) + s[0]) * pNegInv[0]
mul576(&t1, &p, q) mul576(&t1, &p, q)
mul576(&t2, y, x[i]) mul576(&t2, y, x[i])
@ -143,7 +142,7 @@ func addRdc(r, x, y *fp) {
r[7] = ctPick64(w, r[7], t[7]) r[7] = ctPick64(w, r[7], t[7])
} }
// r = x - y // r = x - y.
func sub512(r, x, y *fp) uint64 { func sub512(r, x, y *fp) uint64 {
var c uint64 var c uint64
r[0], c = bits.Sub64(x[0], y[0], 0) 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[0] = one // b ^ 0
precomp[1] = *b // b ^ 1 precomp[1] = *b // b ^ 1
for i := 2; i < 16; i = i + 2 { 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. // with multiplication should improve performance.
mulRdc(&precomp[i], &precomp[i/2], &precomp[i/2]) // sqr mulRdc(&precomp[i], &precomp[i/2], &precomp[i/2]) // sqr
mulRdc(&precomp[i+1], &precomp[i], b) 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[5] = ctPick64(w, r[5], t[5])
r[6] = ctPick64(w, r[6], t[6]) r[6] = ctPick64(w, r[6], t[6])
r[7] = ctPick64(w, r[7], t[7]) r[7] = ctPick64(w, r[7], t[7])
} }
// modExpRdc does modular exponentation of 512-bit number. // modExpRdc does modular exponentation of 512-bit number.
@ -280,7 +278,7 @@ func (v *fp) isZero() bool {
return ctIsNonZero64(r) == 0 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 { func (v *fp) equal(in *fp) bool {
var r uint64 var r uint64
for i := range v { for i := range v {

View File

@ -4,6 +4,9 @@ package csidh
import "math/bits" 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) { func mul512(r, m1 *fp, m2 uint64) {
var c, h, l 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) r[6], c = bits.Add64(l, c, 0)
c = h + c c = h + c
h, l = bits.Mul64(m2, m1[7]) _, l = bits.Mul64(m2, m1[7])
r[7], _ = bits.Add64(l, c, 0) 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) { func mul576(r *[9]uint64, m1 *fp, m2 uint64) {
var c, h, l uint64 var c, h, l uint64
@ -72,6 +79,9 @@ func mul576(r *[9]uint64, m1 *fp, m2 uint64) {
r[8] += c 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) { func cswap512(x, y *fp, choice uint8) {
var tmp uint64 var tmp uint64
mask64 := 0 - uint64(choice) 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. // mulRdc performs montgomery multiplication r = x * y mod P.
// Returned result r is already reduced and in Montgomery domain. // Returned result r is already reduced and in Montgomery domain.
func mulRdc(r, x, y *fp) { func mulRdc(r, x, y *fp) {

View File

@ -39,14 +39,13 @@ func testFp512Mul3Nominal(t *testing.T) {
} }
// Check if mul512 produces result // Check if mul512 produces result
// z = x*y mod 2^512 // z = x*y mod 2^512.
func TestFp512Mul3_Nominal(t *testing.T) { func TestFp512Mul3_Nominal(t *testing.T) {
hasBMI2 = false hasBMI2 = false
testFp512Mul3Nominal(t) testFp512Mul3Nominal(t)
resetCPUFeatures() resetCPUFeatures()
testFp512Mul3Nominal(t) testFp512Mul3Nominal(t)
} }
func TestAddRdcRandom(t *testing.T) { func TestAddRdcRandom(t *testing.T) {
@ -78,26 +77,26 @@ func TestAddRdcNominal(t *testing.T) {
tmp := oneFp512 tmp := oneFp512
addRdc(&res, &tmp, &p) addRdc(&res, &tmp, &p)
if !ceq512(&res, &tmp) { if !eqFp(&res, &tmp) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
tmp = zeroFp512 tmp = zeroFp512
addRdc(&res, &p, &p) addRdc(&res, &p, &p)
if !ceq512(&res, &p) { if !eqFp(&res, &p) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
tmp = fp{1, 1, 1, 1, 1, 1, 1, 1} tmp = fp{1, 1, 1, 1, 1, 1, 1, 1}
addRdc(&res, &p, &tmp) addRdc(&res, &p, &tmp)
if !ceq512(&res, &tmp) { if !eqFp(&res, &tmp) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
tmp = fp{1, 1, 1, 1, 1, 1, 1, 1} tmp = fp{1, 1, 1, 1, 1, 1, 1, 1}
exp := fp{2, 2, 2, 2, 2, 2, 2, 2} exp := fp{2, 2, 2, 2, 2, 2, 2, 2}
addRdc(&res, &tmp, &tmp) addRdc(&res, &tmp, &tmp)
if !ceq512(&res, &exp) { if !eqFp(&res, &exp) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
} }
@ -168,19 +167,19 @@ func TestCswap(t *testing.T) {
arg1cpy := arg1 arg1cpy := arg1
cswap512(&arg1, &arg2, 0) cswap512(&arg1, &arg2, 0)
if !ceq512(&arg1, &arg1cpy) { if !eqFp(&arg1, &arg1cpy) {
t.Error("cswap swapped") t.Error("cswap swapped")
} }
arg1cpy = arg1 arg1cpy = arg1
cswap512(&arg1, &arg2, 1) cswap512(&arg1, &arg2, 1)
if ceq512(&arg1, &arg1cpy) { if eqFp(&arg1, &arg1cpy) {
t.Error("cswap didn't swapped") t.Error("cswap didn't swapped")
} }
arg1cpy = arg1 arg1cpy = arg1
cswap512(&arg1, &arg2, 0xF2) cswap512(&arg1, &arg2, 0xF2)
if ceq512(&arg1, &arg1cpy) { if eqFp(&arg1, &arg1cpy) {
t.Error("cswap didn't swapped") t.Error("cswap didn't swapped")
} }
} }
@ -191,7 +190,7 @@ func TestSubRdc(t *testing.T) {
// 1 - 1 mod P // 1 - 1 mod P
tmp := oneFp512 tmp := oneFp512
subRdc(&res, &tmp, &tmp) subRdc(&res, &tmp, &tmp)
if !ceq512(&res, &zeroFp512) { if !eqFp(&res, &zeroFp512) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
zero(&res) zero(&res)
@ -201,7 +200,7 @@ func TestSubRdc(t *testing.T) {
exp[0]-- exp[0]--
subRdc(&res, &zeroFp512, &oneFp512) subRdc(&res, &zeroFp512, &oneFp512)
if !ceq512(&res, &exp) { if !eqFp(&res, &exp) {
t.Errorf("Wrong value\n%X\n%X", res, exp) t.Errorf("Wrong value\n%X\n%X", res, exp)
} }
zero(&res) zero(&res)
@ -210,13 +209,13 @@ func TestSubRdc(t *testing.T) {
pMinusOne := p pMinusOne := p
pMinusOne[0]-- pMinusOne[0]--
subRdc(&res, &p, &pMinusOne) subRdc(&res, &p, &pMinusOne)
if !ceq512(&res, &oneFp512) { if !eqFp(&res, &oneFp512) {
t.Errorf("Wrong value\n[%X != %X]", res, oneFp512) t.Errorf("Wrong value\n[%X != %X]", res, oneFp512)
} }
zero(&res) zero(&res)
subRdc(&res, &p, &oneFp512) subRdc(&res, &p, &oneFp512)
if !ceq512(&res, &pMinusOne) { if !eqFp(&res, &pMinusOne) {
t.Errorf("Wrong value\n[%X != %X]", res, pMinusOne) t.Errorf("Wrong value\n[%X != %X]", res, pMinusOne)
} }
} }
@ -245,28 +244,28 @@ func testMulRdc(t *testing.T) {
// 0*0 // 0*0
tmp := zeroFp512 tmp := zeroFp512
mulRdc(&res, &tmp, &tmp) mulRdc(&res, &tmp, &tmp)
if !ceq512(&res, &tmp) { if !eqFp(&res, &tmp) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
// 1*m1 == m1 // 1*m1 == m1
zero(&res) zero(&res)
mulRdc(&res, &m1, &one) mulRdc(&res, &m1, &one)
if !ceq512(&res, &m1) { if !eqFp(&res, &m1) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
// m1*m2 < p // m1*m2 < p
zero(&res) zero(&res)
mulRdc(&res, &m1, &m2) mulRdc(&res, &m1, &m2)
if !ceq512(&res, &m1m2) { if !eqFp(&res, &m1m2) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
// m1*m1 > p // m1*m1 > p
zero(&res) zero(&res)
mulRdc(&res, &m1, &m1) mulRdc(&res, &m1, &m1)
if !ceq512(&res, &m1m1) { if !eqFp(&res, &m1m1) {
t.Errorf("Wrong value\n%X", res) t.Errorf("Wrong value\n%X", res)
} }
} }
@ -300,13 +299,13 @@ func TestModExp(t *testing.T) {
// Perform modexp with our implementation // Perform modexp with our implementation
modExpRdc512(&resFp, &baseFp, &expFp) modExpRdc512(&resFp, &baseFp, &expFp)
if !ceq512(&resFp, &resFpExp) { if !eqFp(&resFp, &resFpExp) {
t.Errorf("Wrong value\n%X!=%X", resFp, intGetU64(&resExp)) 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) { func TestIsNonQuadRes(t *testing.T) {
var n, nMont big.Int var n, nMont big.Int
var pm1o2, rawP big.Int var pm1o2, rawP big.Int
@ -318,7 +317,7 @@ func TestIsNonQuadRes(t *testing.T) {
rawP.SetString("0x65b48e8f740f89bffc8ab0d15e3e4c4ab42d083aedc88c425afbfcc69322c9cda7aac6c567f35507516730cc1f0b4f25c2721bf457aca8351b81b90533c6c87b", 0) rawP.SetString("0x65b48e8f740f89bffc8ab0d15e3e4c4ab42d083aedc88c425afbfcc69322c9cda7aac6c567f35507516730cc1f0b4f25c2721bf457aca8351b81b90533c6c87b", 0)
// There is 641 quadratic residues in this range // 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.SetUint64(i)
n.Exp(&n, &pm1o2, &rawP) n.Exp(&n, &pm1o2, &rawP)
// exp == 1 iff n is quadratic non-residue // exp == 1 iff n is quadratic non-residue

View File

@ -6,7 +6,6 @@ import (
mrand "math/rand" mrand "math/rand"
) )
// Commonly used variables
var ( var (
// Number of interations // Number of interations
numIter = 10 numIter = 10
@ -16,6 +15,8 @@ var (
zeroFp512 = fp{} zeroFp512 = fp{}
// One in fp // One in fp
oneFp512 = fp{1, 0, 0, 0, 0, 0, 0, 0} 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. // Converts dst to Montgomery if "toMont==true" or from Montgomery domain otherwise.
@ -41,14 +42,14 @@ func fp2S(v fp) string {
return str return str
} }
// zeroize fp // zeroize fp.
func zero(v *fp) { func zero(v *fp) {
for i := range *v { for i := range *v {
v[i] = 0 v[i] = 0
} }
} }
// returns random value in a range (0,p) // returns random value in a range (0,p).
func randomFp() fp { func randomFp() fp {
var u fp var u fp
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
@ -57,25 +58,8 @@ func randomFp() fp {
return u return u
} }
// x<y: <0 // return x==y for fp.
// x>y: >0 func eqFp(l, r *fp) bool {
// 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 {
for idx := range l { for idx := range l {
if l[idx] != r[idx] { if l[idx] != r[idx] {
return false return false
@ -84,19 +68,14 @@ func ceqFp(l, r *fp) bool {
return true return true
} }
// return x==y for point // return x==y for point.
func ceqpoint(l, r *point) bool { func ceqpoint(l, r *point) bool {
return ceqFp(&l.x, &r.x) && ceqFp(&l.z, &r.z) return eqFp(&l.x, &r.x) && eqFp(&l.z, &r.z)
}
// return x==y
func ceq512(x, y *fp) bool {
return cmp512(x, y) == 0
} }
// Converts src to big.Int. Function assumes that src is a slice of uint64 // Converts src to big.Int. Function assumes that src is a slice of uint64
// values encoded in little-endian byte order. // 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 var tmp big.Int
dst.SetUint64(0) dst.SetUint64(0)
@ -105,7 +84,6 @@ func intSetU64(dst *big.Int, src []uint64) *big.Int {
tmp.Lsh(&tmp, uint(i*64)) tmp.Lsh(&tmp, uint(i*64))
dst.Add(dst, &tmp) dst.Add(dst, &tmp)
} }
return dst
} }
// Converts src to an array of uint64 values encoded in little-endian // Converts src to an array of uint64 values encoded in little-endian
@ -140,7 +118,7 @@ func toNormX(point *point) big.Int {
return bigDnt 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 { func toFp(num string) fp {
var tmp big.Int var tmp big.Int
var ok bool var ok bool