Browse Source

csidh: cosmettic updates

pull/31/head
Henry Case 4 years ago
parent
commit
91945fde1f
9 changed files with 218 additions and 200 deletions
  1. +55
    -34
      dh/csidh/csidh.go
  2. +61
    -65
      dh/csidh/csidh_test.go
  3. +35
    -26
      dh/csidh/curve.go
  4. +11
    -11
      dh/csidh/curve_test.go
  5. +11
    -0
      dh/csidh/doc.go
  6. +5
    -7
      dh/csidh/fp511.go
  7. +11
    -5
      dh/csidh/fp511_generic.go
  8. +19
    -20
      dh/csidh/fp511_test.go
  9. +10
    -32
      dh/csidh/utils_test.go

+ 55
- 34
dh/csidh/csidh.go View File

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


+ 61
- 65
dh/csidh/csidh_test.go View File

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


+ 35
- 26
dh/csidh/curve.go View File

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



+ 11
- 11
dh/csidh/curve_test.go View File

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

+ 11
- 0
dh/csidh/doc.go 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

+ 5
- 7
dh/csidh/fp511.go View File

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


+ 11
- 5
dh/csidh/fp511_generic.go View File

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


+ 19
- 20
dh/csidh/fp511_test.go View File

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


+ 10
- 32
dh/csidh/utils_test.go View File

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


Loading…
Cancel
Save