1
0
mirror of https://github.com/henrydcase/nobs.git synced 2024-11-29 10:21:23 +00:00
nobs/dh/csidh/csidh.go

333 lines
7.2 KiB
Go

package csidh
import (
"io"
)
// 511-bit number representing prime field element GF(p)
type fp [numWords]uint64
// Represents projective point on elliptic curve E over GF(p)
type point struct {
x fp
z fp
}
// Curve coefficients
type coeff struct {
a fp
c fp
}
type fpRngGen struct {
// working buffer needed to avoid memory allocation
wbuf [64]byte
}
// Defines operations on public key
type PublicKey struct {
fpRngGen
// 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.
func (s *fpRngGen) randFp(v *fp, rng io.Reader) {
mask := uint64(1<<(pbits%limbBitSize)) - 1
for {
*v = fp{}
_, err := io.ReadFull(rng, s.wbuf[:])
if err != nil {
panic("Can't read random number")
}
for i := 0; i < len(s.wbuf); i++ {
j := i / limbByteSize
k := uint(i % 8)
v[j] |= uint64(s.wbuf[i]) << (8 * k)
}
v[len(v)-1] &= mask
if isLess(v, &p) {
return
}
}
}
// 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]}
xMul(p, p, a, &tmp)
if !p.z.isZero() {
// order does not divide p+1 -> ordinary curve
return true, false
}
mul512(order, order, primes[halfL])
if isLess(&fourSqrtP, order) {
// order > 4*sqrt(p) -> supersingular curve
return true, true
}
}
return false, false
}
// 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])
}
// calculate Q_i
xMul(&Q, p, a, &mulR)
xMul(p, p, a, &mulL)
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
var done = [2]bool{false, false}
var A = coeff{a: pub.a, c: one}
k[0][0] = 4
k[1][0] = 4
for i, v := range primes {
t := (prv.e[uint(i)>>1] << ((uint(i) % 2) * 4)) >> 4
if t > 0 {
e[0][i] = uint8(t)
e[1][i] = 0
mul512(&k[1], &k[1], v)
} else if t < 0 {
e[1][i] = uint8(-t)
e[0][i] = 0
mul512(&k[0], &k[0], v)
} else {
e[0][i] = 0
e[1][i] = 0
mul512(&k[0], &k[0], v)
mul512(&k[1], &k[1], v)
}
}
for {
var P point
var rhs fp
prv.randFp(&P.x, rng)
P.z = one
montEval(&rhs, &A.a, &P.x)
sign := rhs.isNonQuadRes()
if done[sign] {
continue
}
xMul(&P, &P, &A, &k[sign])
done[sign] = true
for i, v := range primes {
if e[sign][i] != 0 {
var cof = fp{1}
var K point
for j := i + 1; j < len(primes); j++ {
if e[sign][j] != 0 {
mul512(&cof, &cof, primes[j])
}
}
xMul(&K, &P, &A, &cof)
if !K.z.isZero() {
xIso(&P, &A, &K, v)
e[sign][i] = e[sign][i] - 1
if e[sign][i] == 0 {
mul512(&k[sign], &k[sign], primes[i])
}
}
}
done[sign] = done[sign] && (e[sign][i] == 0)
}
modExpRdc512(&A.c, &A.c, &pMin1)
mulRdc(&A.a, &A.a, &A.c)
A.c = one
if done[0] && done[1] {
break
}
}
pub.a = A.a
}
// PrivateKey operations
func (c *PrivateKey) Import(key []byte) bool {
if len(key) < len(c.e) {
return false
}
for i, v := range key {
c.e[i] = int8(v)
}
return true
}
func (c PrivateKey) Export(out []byte) bool {
if len(out) < len(c.e) {
return false
}
for i, v := range c.e {
out[i] = byte(v)
}
return true
}
func GeneratePrivateKey(key *PrivateKey, rng io.Reader) error {
for i := range key.e {
key.e[i] = 0
}
for i := 0; i < len(primes); {
_, err := io.ReadFull(rng, key.wbuf[:])
if err != nil {
return err
}
for j := range key.wbuf {
if int8(key.wbuf[j]) <= expMax && int8(key.wbuf[j]) >= -expMax {
key.e[i>>1] |= int8((key.wbuf[j] & 0xF) << uint((i%2)*4))
i = i + 1
if i == len(primes) {
break
}
}
}
}
return nil
}
// Public key operations
// Assumes key is in Montgomery domain.
func (c *PublicKey) Import(key []byte) bool {
if len(key) != numWords*limbByteSize {
return false
}
for i := 0; i < len(key); i++ {
j := i / limbByteSize
k := uint64(i % 8)
c.a[j] |= uint64(key[i]) << (8 * k)
}
return true
}
// Assumes key is exported as encoded in Montgomery domain.
func (c *PublicKey) Export(out []byte) bool {
if len(out) != numWords*limbByteSize {
return false
}
for i := 0; i < len(out); i++ {
j := i / limbByteSize
k := uint64(i % 8)
out[i] = byte(c.a[j] >> (8 * k))
}
return true
}
func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
for i := range pub.a {
pub.a[i] = 0
}
groupAction(pub, prv, rng)
}
// 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
}
// Check if pub represents a smooth Montgomery curve.
if pub.a.equal(&two) || pub.a.equal(&twoNeg) {
return false
}
// 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)
done, res := cofactorMul(&P, &coeff{A.x, A.z}, 0, len(primes), &fp{1})
if done {
return res
}
}
}
// 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 {
var pk PublicKey
if !Validate(pub, rng) {
return false
}
// Resulting shared secret is stored in the pk
copy(pk.a[:], pub.a[:])
groupAction(&pk, prv, rng)
pk.Export(out[:])
return true
}