github.com/cloudflare/circl@v1.5.0/dh/csidh/csidh.go (about)

     1  package csidh
     2  
     3  import (
     4  	"io"
     5  )
     6  
     7  // 511-bit number representing prime field element GF(p)
     8  type fp [numWords]uint64
     9  
    10  // Represents projective point on elliptic curve E over GF(p)
    11  type point struct {
    12  	x fp
    13  	z fp
    14  }
    15  
    16  // Curve coefficients
    17  type coeff struct {
    18  	a fp
    19  	c fp
    20  }
    21  
    22  type fpRngGen struct {
    23  	// working buffer needed to avoid memory allocation
    24  	wbuf [64]byte
    25  }
    26  
    27  // Defines operations on public key
    28  type PublicKey struct {
    29  	fpRngGen
    30  	// Montgomery coefficient A from GF(p) of the elliptic curve
    31  	// y^2 = x^3 + Ax^2 + x.
    32  	a fp
    33  }
    34  
    35  // Defines operations on private key
    36  type PrivateKey struct {
    37  	fpRngGen
    38  	// private key is a set of integers randomly
    39  	// each sampled from a range [-5, 5].
    40  	e [PrivateKeySize]int8
    41  }
    42  
    43  // randFp generates random element from Fp.
    44  func (s *fpRngGen) randFp(v *fp, rng io.Reader) {
    45  	mask := uint64(1<<(pbits%limbBitSize)) - 1
    46  	for {
    47  		*v = fp{}
    48  		_, err := io.ReadFull(rng, s.wbuf[:])
    49  		if err != nil {
    50  			panic("Can't read random number")
    51  		}
    52  
    53  		for i := 0; i < len(s.wbuf); i++ {
    54  			j := i / limbByteSize
    55  			k := uint(i % 8)
    56  			v[j] |= uint64(s.wbuf[i]) << (8 * k)
    57  		}
    58  
    59  		v[len(v)-1] &= mask
    60  		if isLess(v, &p) {
    61  			return
    62  		}
    63  	}
    64  }
    65  
    66  // cofactorMul helper implements batch cofactor multiplication as described
    67  // in the ia.cr/2018/383 (algo. 3). Returns tuple of two booleans, first indicates
    68  // if function has finished successfully. In case first return value is true,
    69  // second return value indicates if curve represented by cofactor 'a' is
    70  // supersingular.
    71  // Implementation uses divide-and-conquer strategy and recursion in order to
    72  // speed up calculation of Q_i = [(p+1)/l_i] * P.
    73  // Implementation is not constant time, but it operates on public data only.
    74  func cofactorMul(p *point, a *coeff, halfL, halfR int, order *fp) (bool, bool) {
    75  	var Q point
    76  	var r1, d1, r2, d2 bool
    77  	if (halfR - halfL) == 1 {
    78  		// base case
    79  		if !p.z.isZero() {
    80  			tmp := fp{primes[halfL]}
    81  			xMul(p, p, a, &tmp)
    82  
    83  			if !p.z.isZero() {
    84  				// order does not divide p+1 -> ordinary curve
    85  				return true, false
    86  			}
    87  
    88  			mul512(order, order, primes[halfL])
    89  			if isLess(&fourSqrtP, order) {
    90  				// order > 4*sqrt(p) -> supersingular curve
    91  				return true, true
    92  			}
    93  		}
    94  		return false, false
    95  	}
    96  
    97  	// perform another recursive step
    98  	mid := halfL + ((halfR - halfL + 1) / 2)
    99  	mulL, mulR := fp{1}, fp{1}
   100  	// compute u = primes_1 * ... * primes_m
   101  	for i := halfL; i < mid; i++ {
   102  		mul512(&mulR, &mulR, primes[i])
   103  	}
   104  	// compute v = primes_m+1 * ... * primes_n
   105  	for i := mid; i < halfR; i++ {
   106  		mul512(&mulL, &mulL, primes[i])
   107  	}
   108  
   109  	// calculate Q_i
   110  	xMul(&Q, p, a, &mulR)
   111  	xMul(p, p, a, &mulL)
   112  
   113  	d1, r1 = cofactorMul(&Q, a, mid, halfR, order)
   114  	d2, r2 = cofactorMul(p, a, halfL, mid, order)
   115  	return d1 || d2, r1 || r2
   116  }
   117  
   118  // groupAction evaluates group action of prv.e on a Montgomery
   119  // curve represented by coefficient pub.A.
   120  // This is implementation of algorithm 2 from ia.cr/2018/383.
   121  func groupAction(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
   122  	var k [2]fp
   123  	var e [2][primeCount]uint8
   124  	done := [2]bool{false, false}
   125  	A := coeff{a: pub.a, c: one}
   126  
   127  	k[0][0] = 4
   128  	k[1][0] = 4
   129  
   130  	for i, v := range primes {
   131  		t := (prv.e[uint(i)>>1] << ((uint(i) % 2) * 4)) >> 4
   132  		if t > 0 {
   133  			e[0][i] = uint8(t)
   134  			e[1][i] = 0
   135  			mul512(&k[1], &k[1], v)
   136  		} else if t < 0 {
   137  			e[1][i] = uint8(-t)
   138  			e[0][i] = 0
   139  			mul512(&k[0], &k[0], v)
   140  		} else {
   141  			e[0][i] = 0
   142  			e[1][i] = 0
   143  			mul512(&k[0], &k[0], v)
   144  			mul512(&k[1], &k[1], v)
   145  		}
   146  	}
   147  
   148  	for {
   149  		var P point
   150  		var rhs fp
   151  		prv.randFp(&P.x, rng)
   152  		P.z = one
   153  		montEval(&rhs, &A.a, &P.x)
   154  		sign := rhs.isNonQuadRes()
   155  
   156  		if done[sign] {
   157  			continue
   158  		}
   159  
   160  		xMul(&P, &P, &A, &k[sign])
   161  		done[sign] = true
   162  
   163  		for i, v := range primes {
   164  			if e[sign][i] != 0 {
   165  				cof := fp{1}
   166  				var K point
   167  
   168  				for j := i + 1; j < len(primes); j++ {
   169  					if e[sign][j] != 0 {
   170  						mul512(&cof, &cof, primes[j])
   171  					}
   172  				}
   173  
   174  				xMul(&K, &P, &A, &cof)
   175  				if !K.z.isZero() {
   176  					xIso(&P, &A, &K, v)
   177  					e[sign][i] = e[sign][i] - 1
   178  					if e[sign][i] == 0 {
   179  						mul512(&k[sign], &k[sign], primes[i])
   180  					}
   181  				}
   182  			}
   183  			done[sign] = done[sign] && (e[sign][i] == 0)
   184  		}
   185  
   186  		modExpRdc512(&A.c, &A.c, &pMin1)
   187  		mulRdc(&A.a, &A.a, &A.c)
   188  		A.c = one
   189  
   190  		if done[0] && done[1] {
   191  			break
   192  		}
   193  	}
   194  	pub.a = A.a
   195  }
   196  
   197  // PrivateKey operations
   198  
   199  func (c *PrivateKey) Import(key []byte) bool {
   200  	if len(key) < len(c.e) {
   201  		return false
   202  	}
   203  	for i, v := range key {
   204  		c.e[i] = int8(v)
   205  	}
   206  	return true
   207  }
   208  
   209  func (c PrivateKey) Export(out []byte) bool {
   210  	if len(out) < len(c.e) {
   211  		return false
   212  	}
   213  	for i, v := range c.e {
   214  		out[i] = byte(v)
   215  	}
   216  	return true
   217  }
   218  
   219  func GeneratePrivateKey(key *PrivateKey, rng io.Reader) error {
   220  	for i := range key.e {
   221  		key.e[i] = 0
   222  	}
   223  
   224  	for i := 0; i < len(primes); {
   225  		_, err := io.ReadFull(rng, key.wbuf[:])
   226  		if err != nil {
   227  			return err
   228  		}
   229  
   230  		for j := range key.wbuf {
   231  			if int8(key.wbuf[j]) <= expMax && int8(key.wbuf[j]) >= -expMax {
   232  				key.e[i>>1] |= int8((key.wbuf[j] & 0xF) << uint((i%2)*4))
   233  				i = i + 1
   234  				if i == len(primes) {
   235  					break
   236  				}
   237  			}
   238  		}
   239  	}
   240  	return nil
   241  }
   242  
   243  // Public key operations
   244  
   245  // reset removes key material from PublicKey.
   246  func (c *PublicKey) reset() {
   247  	for i := range c.a {
   248  		c.a[i] = 0
   249  	}
   250  }
   251  
   252  // Assumes key is in Montgomery domain.
   253  func (c *PublicKey) Import(key []byte) bool {
   254  	if len(key) != numWords*limbByteSize {
   255  		return false
   256  	}
   257  	for i := 0; i < len(key); i++ {
   258  		j := i / limbByteSize
   259  		k := uint64(i % 8)
   260  		c.a[j] |= uint64(key[i]) << (8 * k)
   261  	}
   262  	return true
   263  }
   264  
   265  // Assumes key is exported as encoded in Montgomery domain.
   266  func (c *PublicKey) Export(out []byte) bool {
   267  	if len(out) != numWords*limbByteSize {
   268  		return false
   269  	}
   270  	for i := 0; i < len(out); i++ {
   271  		j := i / limbByteSize
   272  		k := uint64(i % 8)
   273  		out[i] = byte(c.a[j] >> (8 * k))
   274  	}
   275  	return true
   276  }
   277  
   278  func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) {
   279  	pub.reset()
   280  	groupAction(pub, prv, rng)
   281  }
   282  
   283  // Validate returns true if 'pub' is a valid cSIDH public key,
   284  // otherwise false.
   285  // More precisely, the function verifies that curve
   286  //
   287  //	y^2 = x^3 + pub.a * x^2 + x
   288  //
   289  // is supersingular.
   290  func Validate(pub *PublicKey, rng io.Reader) bool {
   291  	// Check if in range
   292  	if !isLess(&pub.a, &p) {
   293  		return false
   294  	}
   295  
   296  	// Check if pub represents a smooth Montgomery curve.
   297  	if pub.a.equal(&two) || pub.a.equal(&twoNeg) {
   298  		return false
   299  	}
   300  
   301  	// Check if pub represents a supersingular curve.
   302  	for {
   303  		var P point
   304  		A := point{pub.a, one}
   305  
   306  		// Randomly chosen P must have big enough order to check
   307  		// supersingularity. Probability of random P having big
   308  		// enough order is very high, as proven by W.Castryck et
   309  		// al. (ia.cr/2018/383, ch 5)
   310  		pub.randFp(&P.x, rng)
   311  		P.z = one
   312  
   313  		xDbl(&P, &P, &A)
   314  		xDbl(&P, &P, &A)
   315  
   316  		done, res := cofactorMul(&P, &coeff{A.x, A.z}, 0, len(primes), &fp{1})
   317  		if done {
   318  			return res
   319  		}
   320  	}
   321  }
   322  
   323  // DeriveSecret computes a cSIDH shared secret. If successful, returns true
   324  // and fills 'out' with shared secret. Function returns false in case 'pub' is invalid.
   325  // More precisely, shared secret is a Montgomery coefficient A of a secret
   326  // curve y^2 = x^3 + Ax^2 + x, computed by applying action of a prv.e
   327  // on a curve represented by pub.a.
   328  func DeriveSecret(out *[64]byte, pub *PublicKey, prv *PrivateKey, rng io.Reader) bool {
   329  	if !Validate(pub, rng) {
   330  		return false
   331  	}
   332  	groupAction(pub, prv, rng)
   333  	pub.Export(out[:])
   334  	return true
   335  }