Add SPAKE2 over Ed25519.

SPAKE2 is a password-authenticated key exchange. This implementation is
over the twisted Edwards curve Ed25519, and uses SHA-512 as the hash
primitive.

See https://tools.ietf.org/html/draft-irtf-cfrg-spake2-03

Change-Id: I2cd3c3ebdc3d55ac3aea3a9eb0d06275509597ac
Reviewed-on: https://boringssl-review.googlesource.com/7114
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/curve25519/CMakeLists.txt b/crypto/curve25519/CMakeLists.txt
index a2ef3bb..195b1e6 100644
--- a/crypto/curve25519/CMakeLists.txt
+++ b/crypto/curve25519/CMakeLists.txt
@@ -22,6 +22,7 @@
   OBJECT
 
   curve25519.c
+  spake25519.c
   x25519-x86_64.c
 
   ${CURVE25519_ARCH_SOURCES}
@@ -45,3 +46,12 @@
 
 target_link_libraries(x25519_test crypto)
 add_dependencies(all_tests x25519_test)
+
+add_executable(
+  spake25519_test
+
+  spake25519_test.cc
+)
+
+target_link_libraries(spake25519_test crypto)
+add_dependencies(all_tests spake25519_test)
diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c
index 61bbdce..272dfcf 100644
--- a/crypto/curve25519/curve25519.c
+++ b/crypto/curve25519/curve25519.c
@@ -30,13 +30,6 @@
 
 #include "internal.h"
 
-
-/* fe means field element. Here the field is \Z/(2^255-19). An element t,
- * entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
- * t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
- * context.  */
-typedef int32_t fe[10];
-
 static uint64_t load_3(const uint8_t *in) {
   uint64_t result;
   result = (uint64_t)in[0];
@@ -974,52 +967,7 @@
   fe_mul(out, t0, z);
 }
 
-/* ge means group element.
-
- * Here the group is the set of pairs (x,y) of field elements (see fe.h)
- * satisfying -x^2 + y^2 = 1 + d x^2y^2
- * where d = -121665/121666.
- *
- * Representations:
- *   ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
- *   ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
- *   ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
- *   ge_precomp (Duif): (y+x,y-x,2dxy) */
-
-typedef struct {
-  fe X;
-  fe Y;
-  fe Z;
-} ge_p2;
-
-typedef struct {
-  fe X;
-  fe Y;
-  fe Z;
-  fe T;
-} ge_p3;
-
-typedef struct {
-  fe X;
-  fe Y;
-  fe Z;
-  fe T;
-} ge_p1p1;
-
-typedef struct {
-  fe yplusx;
-  fe yminusx;
-  fe xy2d;
-} ge_precomp;
-
-typedef struct {
-  fe YplusX;
-  fe YminusX;
-  fe Z;
-  fe T2d;
-} ge_cached;
-
-static void ge_tobytes(uint8_t *s, const ge_p2 *h) {
+void x25519_ge_tobytes(uint8_t *s, const ge_p2 *h) {
   fe recip;
   fe x;
   fe y;
@@ -1049,7 +997,7 @@
 static const fe sqrtm1 = {-32595792, -7943725,  9377950,  3500415, 12389472,
                           -272473,   -25146209, -2005654, 326686,  11406482};
 
-static int ge_frombytes_vartime(ge_p3 *h, const uint8_t *s) {
+int x25519_ge_frombytes_vartime(ge_p3 *h, const uint8_t *s) {
   fe u;
   fe v;
   fe v3;
@@ -1105,6 +1053,13 @@
   fe_0(h->T);
 }
 
+static void ge_cached_0(ge_cached *h) {
+  fe_1(h->YplusX);
+  fe_1(h->YminusX);
+  fe_1(h->Z);
+  fe_0(h->T2d);
+}
+
 static void ge_precomp_0(ge_precomp *h) {
   fe_1(h->yplusx);
   fe_1(h->yminusx);
@@ -1122,7 +1077,7 @@
                       15978800,  -12551817, -6495438,  29715968, 9444199};
 
 /* r = p */
-static void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) {
+void x25519_ge_p3_to_cached(ge_cached *r, const ge_p3 *p) {
   fe_add(r->YplusX, p->Y, p->X);
   fe_sub(r->YminusX, p->Y, p->X);
   fe_copy(r->Z, p->Z);
@@ -1130,20 +1085,27 @@
 }
 
 /* r = p */
-static void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) {
+void x25519_ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) {
   fe_mul(r->X, p->X, p->T);
   fe_mul(r->Y, p->Y, p->Z);
   fe_mul(r->Z, p->Z, p->T);
 }
 
 /* r = p */
-static void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) {
+void x25519_ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) {
   fe_mul(r->X, p->X, p->T);
   fe_mul(r->Y, p->Y, p->Z);
   fe_mul(r->Z, p->Z, p->T);
   fe_mul(r->T, p->X, p->Y);
 }
 
+/* r = p */
+static void ge_p1p1_to_cached(ge_cached *r, const ge_p1p1 *p) {
+  ge_p3 t;
+  x25519_ge_p1p1_to_p3(&t, p);
+  x25519_ge_p3_to_cached(r, &t);
+}
+
 /* r = 2 * p */
 static void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) {
   fe t0;
@@ -1199,7 +1161,7 @@
 }
 
 /* r = p + q */
-static void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
+void x25519_ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
   fe t0;
 
   fe_add(r->X, p->Y, p->X);
@@ -1216,7 +1178,7 @@
 }
 
 /* r = p - q */
-static void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
+void x25519_ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
   fe t0;
 
   fe_add(r->X, p->Y, p->X);
@@ -1248,6 +1210,58 @@
   fe_cmov(t->xy2d, u->xy2d, b);
 }
 
+void x25519_ge_scalarmult_small_precomp(
+    ge_p3 *h, const uint8_t a[32], const uint8_t precomp_table[15 * 2 * 32]) {
+  /* precomp_table is first expanded into matching |ge_precomp|
+   * elements. */
+  ge_precomp multiples[15];
+
+  unsigned i;
+  for (i = 0; i < 15; i++) {
+    const uint8_t *bytes = &precomp_table[i*(2 * 32)];
+    fe x, y;
+    fe_frombytes(x, bytes);
+    fe_frombytes(y, bytes + 32);
+
+    ge_precomp *out = &multiples[i];
+    fe_add(out->yplusx, y, x);
+    fe_sub(out->yminusx, y, x);
+    fe_mul(out->xy2d, x, y);
+    fe_mul(out->xy2d, out->xy2d, d2);
+  }
+
+  /* See the comment above |k25519SmallPrecomp| about the structure of the
+   * precomputed elements. This loop does 64 additions and 64 doublings to
+   * calculate the result. */
+  ge_p3_0(h);
+
+  for (i = 63; i < 64; i--) {
+    unsigned j;
+    signed char index = 0;
+
+    for (j = 0; j < 4; j++) {
+      const uint8_t bit = 1 & (a[(8 * j) + (i / 8)] >> (i & 7));
+      index |= (bit << j);
+    }
+
+    ge_precomp e;
+    ge_precomp_0(&e);
+
+    for (j = 1; j < 16; j++) {
+      cmov(&e, &multiples[j-1], equal(index, j));
+    }
+
+    ge_cached cached;
+    ge_p1p1 r;
+    x25519_ge_p3_to_cached(&cached, h);
+    x25519_ge_add(&r, h, &cached);
+    x25519_ge_p1p1_to_p3(h, &r);
+
+    ge_madd(&r, h, &e);
+    x25519_ge_p1p1_to_p3(h, &r);
+  }
+}
+
 #if defined(OPENSSL_SMALL)
 
 /* This block of code replaces the standard base-point table with a much smaller
@@ -1341,55 +1355,8 @@
     0x45, 0xc9, 0x8b, 0x17, 0x79, 0xe7, 0xc7, 0x90, 0x99, 0x3a, 0x18, 0x25,
 };
 
-static void ge_scalarmult_base(ge_p3 *h, const uint8_t a[32]) {
-  /* k25519SmallPrecomp is first expanded into matching |ge_precomp|
-   * elements. */
-  ge_precomp multiples[15];
-
-  unsigned i;
-  for (i = 0; i < 15; i++) {
-    const uint8_t *bytes = &k25519SmallPrecomp[i*(2 * 32)];
-    fe x, y;
-    fe_frombytes(x, bytes);
-    fe_frombytes(y, bytes + 32);
-
-    ge_precomp *out = &multiples[i];
-    fe_add(out->yplusx, y, x);
-    fe_sub(out->yminusx, y, x);
-    fe_mul(out->xy2d, x, y);
-    fe_mul(out->xy2d, out->xy2d, d2);
-  }
-
-  /* See the comment above |k25519SmallPrecomp| about the structure of the
-   * precomputed elements. This loop does 64 additions and 64 doublings to
-   * calculate the result. */
-  ge_p3_0(h);
-
-  for (i = 63; i < 64; i--) {
-    unsigned j;
-    signed char index = 0;
-
-    for (j = 0; j < 4; j++) {
-      const uint8_t bit = 1 & (a[(8 * j) + (i / 8)] >> (i & 7));
-      index |= (bit << j);
-    }
-
-    ge_precomp e;
-    ge_precomp_0(&e);
-
-    for (j = 1; j < 16; j++) {
-      cmov(&e, &multiples[j-1], equal(index, j));
-    }
-
-    ge_cached cached;
-    ge_p1p1 r;
-    ge_p3_to_cached(&cached, h);
-    ge_add(&r, h, &cached);
-    ge_p1p1_to_p3(h, &r);
-
-    ge_madd(&r, h, &e);
-    ge_p1p1_to_p3(h, &r);
-  }
+void x25519_ge_scalarmult_base(ge_p3 *h, const uint8_t a[32]) {
+  x25519_ge_scalarmult_small_precomp(h, a, k25519SmallPrecomp);
 }
 
 #else
@@ -3542,7 +3509,7 @@
  *
  * Preconditions:
  *   a[31] <= 127 */
-static void ge_scalarmult_base(ge_p3 *h, const uint8_t *a) {
+void x25519_ge_scalarmult_base(ge_p3 *h, const uint8_t *a) {
   signed char e[64];
   signed char carry;
   ge_p1p1 r;
@@ -3571,27 +3538,87 @@
   for (i = 1; i < 64; i += 2) {
     table_select(&t, i / 2, e[i]);
     ge_madd(&r, h, &t);
-    ge_p1p1_to_p3(h, &r);
+    x25519_ge_p1p1_to_p3(h, &r);
   }
 
   ge_p3_dbl(&r, h);
-  ge_p1p1_to_p2(&s, &r);
+  x25519_ge_p1p1_to_p2(&s, &r);
   ge_p2_dbl(&r, &s);
-  ge_p1p1_to_p2(&s, &r);
+  x25519_ge_p1p1_to_p2(&s, &r);
   ge_p2_dbl(&r, &s);
-  ge_p1p1_to_p2(&s, &r);
+  x25519_ge_p1p1_to_p2(&s, &r);
   ge_p2_dbl(&r, &s);
-  ge_p1p1_to_p3(h, &r);
+  x25519_ge_p1p1_to_p3(h, &r);
 
   for (i = 0; i < 64; i += 2) {
     table_select(&t, i / 2, e[i]);
     ge_madd(&r, h, &t);
-    ge_p1p1_to_p3(h, &r);
+    x25519_ge_p1p1_to_p3(h, &r);
   }
 }
 
 #endif
 
+static void cmov_cached(ge_cached *t, ge_cached *u, uint8_t b) {
+  fe_cmov(t->YplusX, u->YplusX, b);
+  fe_cmov(t->YminusX, u->YminusX, b);
+  fe_cmov(t->Z, u->Z, b);
+  fe_cmov(t->T2d, u->T2d, b);
+}
+
+/* r = scalar * A.
+ * where a = a[0]+256*a[1]+...+256^31 a[31]. */
+void x25519_ge_scalarmult(ge_p2 *r, const uint8_t *scalar, const ge_p3 *A) {
+  ge_p2 Ai_p2[8];
+  ge_cached Ai[16];
+  ge_p1p1 t;
+
+  ge_cached_0(&Ai[0]);
+  x25519_ge_p3_to_cached(&Ai[1], A);
+  ge_p3_to_p2(&Ai_p2[1], A);
+
+  unsigned i;
+  for (i = 2; i < 16; i += 2) {
+    ge_p2_dbl(&t, &Ai_p2[i / 2]);
+    ge_p1p1_to_cached(&Ai[i], &t);
+    if (i < 8) {
+      x25519_ge_p1p1_to_p2(&Ai_p2[i], &t);
+    }
+    x25519_ge_add(&t, A, &Ai[i]);
+    ge_p1p1_to_cached(&Ai[i + 1], &t);
+    if (i < 7) {
+      x25519_ge_p1p1_to_p2(&Ai_p2[i + 1], &t);
+    }
+  }
+
+  ge_p2_0(r);
+  ge_p3 u;
+
+  for (i = 0; i < 256; i += 4) {
+    ge_p2_dbl(&t, r);
+    x25519_ge_p1p1_to_p2(r, &t);
+    ge_p2_dbl(&t, r);
+    x25519_ge_p1p1_to_p2(r, &t);
+    ge_p2_dbl(&t, r);
+    x25519_ge_p1p1_to_p2(r, &t);
+    ge_p2_dbl(&t, r);
+    x25519_ge_p1p1_to_p3(&u, &t);
+
+    uint8_t index = scalar[31 - i/8];
+    index >>= 4 - (i & 4);
+    index &= 0xf;
+
+    unsigned j;
+    ge_cached selected;
+    for (j = 0; j < 16; j++) {
+      cmov_cached(&selected, &Ai[j], equal(j, index));
+    }
+
+    x25519_ge_add(&t, &u, &selected);
+    x25519_ge_p1p1_to_p2(r, &t);
+  }
+}
+
 static void slide(signed char *r, const uint8_t *a) {
   int i;
   int b;
@@ -3697,8 +3724,8 @@
  * where a = a[0]+256*a[1]+...+256^31 a[31].
  * and b = b[0]+256*b[1]+...+256^31 b[31].
  * B is the Ed25519 base point (x,4/5) with x positive. */
-void ge_double_scalarmult_vartime(ge_p2 *r, const uint8_t *a,
-                                  const ge_p3 *A, const uint8_t *b) {
+static void ge_double_scalarmult_vartime(ge_p2 *r, const uint8_t *a,
+                                         const ge_p3 *A, const uint8_t *b) {
   signed char aslide[256];
   signed char bslide[256];
   ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
@@ -3710,30 +3737,30 @@
   slide(aslide, a);
   slide(bslide, b);
 
-  ge_p3_to_cached(&Ai[0], A);
+  x25519_ge_p3_to_cached(&Ai[0], A);
   ge_p3_dbl(&t, A);
-  ge_p1p1_to_p3(&A2, &t);
-  ge_add(&t, &A2, &Ai[0]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[1], &u);
-  ge_add(&t, &A2, &Ai[1]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[2], &u);
-  ge_add(&t, &A2, &Ai[2]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[3], &u);
-  ge_add(&t, &A2, &Ai[3]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[4], &u);
-  ge_add(&t, &A2, &Ai[4]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[5], &u);
-  ge_add(&t, &A2, &Ai[5]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[6], &u);
-  ge_add(&t, &A2, &Ai[6]);
-  ge_p1p1_to_p3(&u, &t);
-  ge_p3_to_cached(&Ai[7], &u);
+  x25519_ge_p1p1_to_p3(&A2, &t);
+  x25519_ge_add(&t, &A2, &Ai[0]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[1], &u);
+  x25519_ge_add(&t, &A2, &Ai[1]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[2], &u);
+  x25519_ge_add(&t, &A2, &Ai[2]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[3], &u);
+  x25519_ge_add(&t, &A2, &Ai[3]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[4], &u);
+  x25519_ge_add(&t, &A2, &Ai[4]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[5], &u);
+  x25519_ge_add(&t, &A2, &Ai[5]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[6], &u);
+  x25519_ge_add(&t, &A2, &Ai[6]);
+  x25519_ge_p1p1_to_p3(&u, &t);
+  x25519_ge_p3_to_cached(&Ai[7], &u);
 
   ge_p2_0(r);
 
@@ -3747,22 +3774,22 @@
     ge_p2_dbl(&t, r);
 
     if (aslide[i] > 0) {
-      ge_p1p1_to_p3(&u, &t);
-      ge_add(&t, &u, &Ai[aslide[i] / 2]);
+      x25519_ge_p1p1_to_p3(&u, &t);
+      x25519_ge_add(&t, &u, &Ai[aslide[i] / 2]);
     } else if (aslide[i] < 0) {
-      ge_p1p1_to_p3(&u, &t);
-      ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
+      x25519_ge_p1p1_to_p3(&u, &t);
+      x25519_ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
     }
 
     if (bslide[i] > 0) {
-      ge_p1p1_to_p3(&u, &t);
+      x25519_ge_p1p1_to_p3(&u, &t);
       ge_madd(&t, &u, &Bi[bslide[i] / 2]);
     } else if (bslide[i] < 0) {
-      ge_p1p1_to_p3(&u, &t);
+      x25519_ge_p1p1_to_p3(&u, &t);
       ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
     }
 
-    ge_p1p1_to_p2(r, &t);
+    x25519_ge_p1p1_to_p2(r, &t);
   }
 }
 
@@ -3776,7 +3803,7 @@
  *   s[0]+256*s[1]+...+256^31*s[31] = s mod l
  *   where l = 2^252 + 27742317777372353535851937790883648493.
  *   Overwrites s in place. */
-static void sc_reduce(uint8_t *s) {
+void x25519_sc_reduce(uint8_t *s) {
   int64_t s0 = 2097151 & load_3(s);
   int64_t s1 = 2097151 & (load_4(s + 2) >> 5);
   int64_t s2 = 2097151 & (load_3(s + 5) >> 2);
@@ -4610,7 +4637,7 @@
   az[31] |= 64;
 
   ge_p3 A;
-  ge_scalarmult_base(&A, az);
+  x25519_ge_scalarmult_base(&A, az);
   ge_p3_tobytes(out_public_key, &A);
 
   memcpy(out_private_key, seed, 32);
@@ -4633,9 +4660,9 @@
   uint8_t nonce[SHA512_DIGEST_LENGTH];
   SHA512_Final(nonce, &hash_ctx);
 
-  sc_reduce(nonce);
+  x25519_sc_reduce(nonce);
   ge_p3 R;
-  ge_scalarmult_base(&R, nonce);
+  x25519_ge_scalarmult_base(&R, nonce);
   ge_p3_tobytes(out_sig, &R);
 
   SHA512_Init(&hash_ctx);
@@ -4645,7 +4672,7 @@
   uint8_t hram[SHA512_DIGEST_LENGTH];
   SHA512_Final(hram, &hash_ctx);
 
-  sc_reduce(hram);
+  x25519_sc_reduce(hram);
   sc_muladd(out_sig + 32, hram, az, nonce);
 
   return 1;
@@ -4655,7 +4682,7 @@
                    const uint8_t signature[64], const uint8_t public_key[32]) {
   ge_p3 A;
   if ((signature[63] & 224) != 0 ||
-      ge_frombytes_vartime(&A, public_key) != 0) {
+      x25519_ge_frombytes_vartime(&A, public_key) != 0) {
     return 0;
   }
 
@@ -4677,13 +4704,13 @@
   uint8_t h[SHA512_DIGEST_LENGTH];
   SHA512_Final(h, &hash_ctx);
 
-  sc_reduce(h);
+  x25519_sc_reduce(h);
 
   ge_p2 R;
   ge_double_scalarmult_vartime(&R, h, &A, scopy);
 
   uint8_t rcheck[32];
-  ge_tobytes(rcheck, &R);
+  x25519_ge_tobytes(rcheck, &R);
 
   return CRYPTO_memcmp(rcheck, rcopy, sizeof(rcheck)) == 0;
 }
@@ -4887,7 +4914,7 @@
   e[31] |= 64;
 
   ge_p3 A;
-  ge_scalarmult_base(&A, e);
+  x25519_ge_scalarmult_base(&A, e);
 
   /* We only need the u-coordinate of the curve25519 point. The map is
    * u=(y+1)/(1-y). Since y=Y/Z, this gives u=(Z+Y)/(Z-Y). */
diff --git a/crypto/curve25519/internal.h b/crypto/curve25519/internal.h
index 27994b7..ea206a3 100644
--- a/crypto/curve25519/internal.h
+++ b/crypto/curve25519/internal.h
@@ -37,6 +37,70 @@
                  const uint8_t point[32]);
 #endif
 
+/* fe means field element. Here the field is \Z/(2^255-19). An element t,
+ * entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
+ * t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
+ * context.  */
+typedef int32_t fe[10];
+
+/* ge means group element.
+
+ * Here the group is the set of pairs (x,y) of field elements (see fe.h)
+ * satisfying -x^2 + y^2 = 1 + d x^2y^2
+ * where d = -121665/121666.
+ *
+ * Representations:
+ *   ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
+ *   ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
+ *   ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
+ *   ge_precomp (Duif): (y+x,y-x,2dxy) */
+
+typedef struct {
+  fe X;
+  fe Y;
+  fe Z;
+} ge_p2;
+
+typedef struct {
+  fe X;
+  fe Y;
+  fe Z;
+  fe T;
+} ge_p3;
+
+typedef struct {
+  fe X;
+  fe Y;
+  fe Z;
+  fe T;
+} ge_p1p1;
+
+typedef struct {
+  fe yplusx;
+  fe yminusx;
+  fe xy2d;
+} ge_precomp;
+
+typedef struct {
+  fe YplusX;
+  fe YminusX;
+  fe Z;
+  fe T2d;
+} ge_cached;
+
+void x25519_ge_tobytes(uint8_t *s, const ge_p2 *h);
+int x25519_ge_frombytes_vartime(ge_p3 *h, const uint8_t *s);
+void x25519_ge_p3_to_cached(ge_cached *r, const ge_p3 *p);
+void x25519_ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p);
+void x25519_ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p);
+void x25519_ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
+void x25519_ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
+void x25519_ge_scalarmult_small_precomp(
+    ge_p3 *h, const uint8_t a[32], const uint8_t precomp_table[15 * 2 * 32]);
+void x25519_ge_scalarmult_base(ge_p3 *h, const uint8_t a[32]);
+void x25519_ge_scalarmult(ge_p2 *r, const uint8_t *scalar, const ge_p3 *A);
+void x25519_sc_reduce(uint8_t *s);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/crypto/curve25519/spake25519.c b/crypto/curve25519/spake25519.c
new file mode 100644
index 0000000..617418c
--- /dev/null
+++ b/crypto/curve25519/spake25519.c
@@ -0,0 +1,464 @@
+/* Copyright (c) 2016, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/curve25519.h>
+
+#include <string.h>
+
+#include <openssl/bytestring.h>
+#include <openssl/mem.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+#include "internal.h"
+
+
+/* The following precomputation tables are for the following
+ * points used in the SPAKE2 protocol.
+ *
+ * N:
+ *   x: 49918732221787544735331783592030787422991506689877079631459872391322455579424
+ *   y: 54629554431565467720832445949441049581317094546788069926228343916274969994000
+ *   encoded: 10e3df0ae37d8e7a99b5fe74b44672103dbddcbd06af680d71329a11693bc778
+ *
+ * M:
+ *   x: 31406539342727633121250288103050113562375374900226415211311216773867585644232
+ *   y: 21177308356423958466833845032658859666296341766942662650232962324899758529114
+ *   encoded: 5ada7e4bf6ddd9adb6626d32131c6b5c51a1e347a3478f53cfcf441b88eed12e
+ *
+ * These points and their precomputation tables are generated with the
+ * following Python code. For a description of the precomputation table,
+ * see curve25519.c in this directory.
+ *
+ * Exact copies of the source code are kept in bug 27296743.
+ *
+ * import hashlib
+ * import ed25519 as E  # http://ed25519.cr.yp.to/python/ed25519.py
+ *
+ * SEED_N = 'edwards25519 point generation seed (N)'
+ * SEED_M = 'edwards25519 point generation seed (M)'
+ *
+ * def genpoint(seed):
+ *     v = hashlib.sha256(seed).digest()
+ *     it = 1
+ *     while True:
+ *         try:
+ *             x,y = E.decodepoint(v)
+ *         except Exception, e:
+ *             print e
+ *             it += 1
+ *             v = hashlib.sha256(v).digest()
+ *             continue
+ *         print "Found in %d iterations:" % it
+ *         print "  x = %d" % x
+ *         print "  y = %d" % y
+ *         print " Encoded (hex)"
+ *         print E.encodepoint((x,y)).encode('hex')
+ *         return (x,y)
+ *
+ * def gentable(P):
+ *     t = []
+ *     for i in range(1,16):
+ *         k = (i >> 3 & 1) * (1 << 192) + \
+ *             (i >> 2 & 1) * (1 << 128) + \
+ *             (i >> 1 & 1) * (1 <<  64) + \
+ *             (i      & 1)
+ *         t.append(E.scalarmult(P, k))
+ *     return ''.join(E.encodeint(x) + E.encodeint(y) for (x,y) in t)
+ *
+ * def printtable(table, name):
+ *     print "static const uint8_t %s[15 * 2 * 32] = {" % name,
+ *     for i in range(15 * 2 * 32):
+ *         if i % 12 == 0:
+ *             print "\n   ",
+ *         print " 0x%02x," % ord(table[i]),
+ *     print "\n};"
+ *
+ * if __name__ == "__main__":
+ *     print "Searching for N"
+ *     N = genpoint(SEED_N)
+ *     print "Generating precomputation table for N"
+ *     Ntable = gentable(N)
+ *     printtable(Ntable, "kSpakeNSmallPrecomp")
+ *
+ *     print "Searching for M"
+ *     M = genpoint(SEED_M)
+ *     print "Generating precomputation table for M"
+ *     Mtable = gentable(M)
+ *     printtable(Mtable, "kSpakeMSmallPrecomp")
+ */
+static const uint8_t kSpakeNSmallPrecomp[15 * 2 * 32] = {
+    0x20, 0x1b, 0xc5, 0xb3, 0x43, 0x17, 0x71, 0x10, 0x44, 0x1e, 0x73, 0xb3,
+    0xae, 0x3f, 0xbf, 0x9f, 0xf5, 0x44, 0xc8, 0x13, 0x8f, 0xd1, 0x01, 0xc2,
+    0x8a, 0x1a, 0x6d, 0xea, 0x4d, 0x00, 0x5d, 0x6e, 0x10, 0xe3, 0xdf, 0x0a,
+    0xe3, 0x7d, 0x8e, 0x7a, 0x99, 0xb5, 0xfe, 0x74, 0xb4, 0x46, 0x72, 0x10,
+    0x3d, 0xbd, 0xdc, 0xbd, 0x06, 0xaf, 0x68, 0x0d, 0x71, 0x32, 0x9a, 0x11,
+    0x69, 0x3b, 0xc7, 0x78, 0x93, 0xf1, 0x57, 0x97, 0x6e, 0xf0, 0x6e, 0x45,
+    0x37, 0x4a, 0xf4, 0x0b, 0x18, 0x51, 0xf5, 0x4f, 0x67, 0x3c, 0xdc, 0xec,
+    0x84, 0xed, 0xd0, 0xeb, 0xca, 0xfb, 0xdb, 0xff, 0x7f, 0xeb, 0xa8, 0x23,
+    0x68, 0x87, 0x13, 0x64, 0x6a, 0x10, 0xf7, 0x45, 0xe0, 0x0f, 0x32, 0x21,
+    0x59, 0x7c, 0x0e, 0x50, 0xad, 0x56, 0xd7, 0x12, 0x69, 0x7b, 0x58, 0xf8,
+    0xb9, 0x3b, 0xa5, 0xbb, 0x4d, 0x1b, 0x87, 0x1c, 0x46, 0xa7, 0x17, 0x9d,
+    0x6d, 0x84, 0x45, 0xbe, 0x7f, 0x95, 0xd2, 0x34, 0xcd, 0x89, 0x95, 0xc0,
+    0xf0, 0xd3, 0xdf, 0x6e, 0x10, 0x4a, 0xe3, 0x7b, 0xce, 0x7f, 0x40, 0x27,
+    0xc7, 0x2b, 0xab, 0x66, 0x03, 0x59, 0xb4, 0x7b, 0xc7, 0xc7, 0xf0, 0x39,
+    0x9a, 0x33, 0x35, 0xbf, 0xcc, 0x2f, 0xf3, 0x2e, 0x68, 0x9d, 0x53, 0x5c,
+    0x88, 0x52, 0xe3, 0x77, 0x90, 0xa1, 0x27, 0x85, 0xc5, 0x74, 0x7f, 0x23,
+    0x0e, 0x93, 0x01, 0x3e, 0xe7, 0x2e, 0x2e, 0x95, 0xf3, 0x0d, 0xc2, 0x25,
+    0x25, 0x39, 0x39, 0x3d, 0x6e, 0x8e, 0x89, 0xbd, 0xe8, 0xbb, 0x67, 0x5e,
+    0x8c, 0x66, 0x8b, 0x63, 0x28, 0x1e, 0x4e, 0x74, 0x85, 0xa8, 0xaf, 0x0f,
+    0x12, 0x5d, 0xb6, 0x8a, 0x83, 0x1a, 0x77, 0x76, 0x5e, 0x62, 0x8a, 0xa7,
+    0x3c, 0xb8, 0x05, 0x57, 0x2b, 0xaf, 0x36, 0x2e, 0x10, 0x90, 0xb2, 0x39,
+    0xb4, 0x3e, 0x75, 0x6d, 0x3a, 0xa8, 0x31, 0x35, 0xc2, 0x1e, 0x8f, 0xc2,
+    0x79, 0x89, 0x35, 0x16, 0x26, 0xd1, 0xc7, 0x0b, 0x04, 0x1f, 0x1d, 0xf9,
+    0x9c, 0x05, 0xa6, 0x6b, 0xb5, 0x19, 0x5a, 0x24, 0x6d, 0x91, 0xc5, 0x31,
+    0xfd, 0xc5, 0xfa, 0xe7, 0xa6, 0xcb, 0x0e, 0x4b, 0x18, 0x0d, 0x94, 0xc7,
+    0xee, 0x1d, 0x46, 0x1f, 0x92, 0xb1, 0xb2, 0x4a, 0x2b, 0x43, 0x37, 0xfe,
+    0xc2, 0x15, 0x11, 0x89, 0xef, 0x59, 0x73, 0x3c, 0x06, 0x76, 0x78, 0xcb,
+    0xa6, 0x0d, 0x79, 0x5f, 0x28, 0x0b, 0x5b, 0x8c, 0x9e, 0xe4, 0xaa, 0x51,
+    0x9a, 0x42, 0x6f, 0x11, 0x50, 0x3d, 0x01, 0xd6, 0x21, 0xc0, 0x99, 0x5e,
+    0x1a, 0xe8, 0x81, 0x25, 0x80, 0xeb, 0xed, 0x5d, 0x37, 0x47, 0x30, 0x70,
+    0xa0, 0x4e, 0x0b, 0x43, 0x17, 0xbe, 0xb6, 0x47, 0xe7, 0x2a, 0x62, 0x9d,
+    0x5d, 0xa6, 0xc5, 0x33, 0x62, 0x9d, 0x56, 0x24, 0x9d, 0x1d, 0xb2, 0x13,
+    0xbc, 0x17, 0x66, 0x43, 0xd1, 0x68, 0xd5, 0x3b, 0x17, 0x69, 0x17, 0xa6,
+    0x06, 0x9e, 0x12, 0xb8, 0x7c, 0xd5, 0xaf, 0x3e, 0x21, 0x1b, 0x31, 0xeb,
+    0x0b, 0xa4, 0x98, 0x1c, 0xf2, 0x6a, 0x5e, 0x7c, 0x9b, 0x45, 0x8f, 0xb2,
+    0x12, 0x06, 0xd5, 0x8c, 0x1d, 0xb2, 0xa7, 0x57, 0x5f, 0x2f, 0x4f, 0xdb,
+    0x52, 0x99, 0x7c, 0x58, 0x01, 0x5f, 0xf2, 0xa5, 0xf6, 0x51, 0x86, 0x21,
+    0x2f, 0x5b, 0x8d, 0x6a, 0xae, 0x83, 0x34, 0x6d, 0x58, 0x4b, 0xef, 0xfe,
+    0xbf, 0x73, 0x5d, 0xdb, 0xc4, 0x97, 0x2a, 0x85, 0xf3, 0x6c, 0x46, 0x42,
+    0xb3, 0x90, 0xc1, 0x57, 0x97, 0x50, 0x35, 0xb1, 0x9d, 0xb7, 0xc7, 0x3c,
+    0x85, 0x6d, 0x6c, 0xfd, 0xce, 0xb0, 0xc9, 0xa2, 0x77, 0xee, 0xc3, 0x6b,
+    0x0c, 0x37, 0xfa, 0x30, 0x91, 0xd1, 0x2c, 0xb8, 0x5e, 0x7f, 0x81, 0x5f,
+    0x87, 0xfd, 0x18, 0x02, 0x5a, 0x30, 0x4e, 0x62, 0xbc, 0x65, 0xc6, 0xce,
+    0x1a, 0xcf, 0x2b, 0xaa, 0x56, 0x3e, 0x4d, 0xcf, 0xba, 0x62, 0x5f, 0x9a,
+    0xd0, 0x72, 0xff, 0xef, 0x28, 0xbd, 0xbe, 0xd8, 0x57, 0x3d, 0xf5, 0x57,
+    0x7d, 0xe9, 0x71, 0x31, 0xec, 0x98, 0x90, 0x94, 0xd9, 0x54, 0xbf, 0x84,
+    0x0b, 0xe3, 0x06, 0x47, 0x19, 0x9a, 0x13, 0x1d, 0xef, 0x9d, 0x13, 0xf3,
+    0xdb, 0xc3, 0x5c, 0x72, 0x9e, 0xed, 0x24, 0xaa, 0x64, 0xed, 0xe7, 0x0d,
+    0xa0, 0x7c, 0x73, 0xba, 0x9b, 0x86, 0xa7, 0x3b, 0x55, 0xab, 0x58, 0x30,
+    0xf1, 0x15, 0x81, 0x83, 0x2f, 0xf9, 0x62, 0x84, 0x98, 0x66, 0xf6, 0x55,
+    0x21, 0xd8, 0xf2, 0x25, 0x64, 0x71, 0x4b, 0x12, 0x76, 0x59, 0xc5, 0xaa,
+    0x93, 0x67, 0xc3, 0x86, 0x25, 0xab, 0x4e, 0x4b, 0xf6, 0xd8, 0x3f, 0x44,
+    0x2e, 0x11, 0xe0, 0xbd, 0x6a, 0xf2, 0x5d, 0xf5, 0xf9, 0x53, 0xea, 0xa4,
+    0xc8, 0xd9, 0x50, 0x33, 0x81, 0xd9, 0xa8, 0x2d, 0x91, 0x7d, 0x13, 0x2a,
+    0x11, 0xcf, 0xde, 0x3f, 0x0a, 0xd2, 0xbc, 0x33, 0xb2, 0x62, 0x53, 0xea,
+    0x77, 0x88, 0x43, 0x66, 0x27, 0x43, 0x85, 0xe9, 0x5f, 0x55, 0xf5, 0x2a,
+    0x8a, 0xac, 0xdf, 0xff, 0x9b, 0x4c, 0x96, 0x9c, 0xa5, 0x7a, 0xce, 0xd5,
+    0x79, 0x18, 0xf1, 0x0b, 0x58, 0x95, 0x7a, 0xe7, 0xd3, 0x74, 0x65, 0x0b,
+    0xa4, 0x64, 0x30, 0xe8, 0x5c, 0xfc, 0x55, 0x56, 0xee, 0x14, 0x14, 0xd3,
+    0x45, 0x3b, 0xf8, 0xde, 0x05, 0x3e, 0xb9, 0x3c, 0xd7, 0x6a, 0x52, 0x72,
+    0x5b, 0x39, 0x09, 0xbe, 0x82, 0x23, 0x10, 0x4a, 0xb7, 0xc3, 0xdc, 0x4c,
+    0x5d, 0xc9, 0xf1, 0x14, 0x83, 0xf9, 0x0b, 0x9b, 0xe9, 0x23, 0x84, 0x6a,
+    0xc4, 0x08, 0x3d, 0xda, 0x3d, 0x12, 0x95, 0x87, 0x18, 0xa4, 0x7d, 0x3f,
+    0x23, 0xde, 0xd4, 0x1e, 0xa8, 0x47, 0xc3, 0x71, 0xdb, 0xf5, 0x03, 0x6c,
+    0x57, 0xe7, 0xa4, 0x43, 0x82, 0x33, 0x7b, 0x62, 0x46, 0x7d, 0xf7, 0x10,
+    0x69, 0x18, 0x38, 0x27, 0x9a, 0x6f, 0x38, 0xac, 0xfa, 0x92, 0xc5, 0xae,
+    0x66, 0xa6, 0x73, 0x95, 0x15, 0x0e, 0x4c, 0x04, 0xb6, 0xfc, 0xf5, 0xc7,
+    0x21, 0x3a, 0x99, 0xdb, 0x0e, 0x36, 0xf0, 0x56, 0xbc, 0x75, 0xf9, 0x87,
+    0x9b, 0x11, 0x18, 0x92, 0x64, 0x1a, 0xe7, 0xc7, 0xab, 0x5a, 0xc7, 0x26,
+    0x7f, 0x13, 0x98, 0x42, 0x52, 0x43, 0xdb, 0xc8, 0x6d, 0x0b, 0xb7, 0x31,
+    0x93, 0x24, 0xd6, 0xe8, 0x24, 0x1f, 0x6f, 0x21, 0xa7, 0x8c, 0xeb, 0xdb,
+    0x83, 0xb8, 0x89, 0xe3, 0xc1, 0xd7, 0x69, 0x3b, 0x02, 0x6b, 0x54, 0x0f,
+    0x84, 0x2f, 0xb5, 0x5c, 0x17, 0x77, 0xbe, 0xe5, 0x61, 0x0d, 0xc5, 0xdf,
+    0x3b, 0xcf, 0x3e, 0x93, 0x4f, 0xf5, 0x89, 0xb9, 0x5a, 0xc5, 0x29, 0x31,
+    0xc0, 0xc2, 0xff, 0xe5, 0x3f, 0xa6, 0xac, 0x03, 0xca, 0xf5, 0xff, 0xe0,
+    0x36, 0xce, 0xf3, 0xe2, 0xb7, 0x9c, 0x02, 0xe9, 0x9e, 0xd2, 0xbc, 0x87,
+    0x2f, 0x3d, 0x9a, 0x1d, 0x8f, 0xc5, 0x72, 0xb8, 0xa2, 0x01, 0xd4, 0x68,
+    0xb1, 0x84, 0x16, 0x10, 0xf6, 0xf3, 0x52, 0x25, 0xd9, 0xdc, 0x4c, 0xdd,
+    0x0f, 0xd6, 0x4a, 0xcf, 0x60, 0x96, 0x7e, 0xcc, 0x42, 0x0f, 0x64, 0x9d,
+    0x72, 0x46, 0x04, 0x07, 0xf2, 0x5b, 0xf4, 0x07, 0xd1, 0xf4, 0x59, 0x71,
+};
+
+static const uint8_t kSpakeMSmallPrecomp[15 * 2 * 32] = {
+    0xc8, 0xa6, 0x63, 0xc5, 0x97, 0xf1, 0xee, 0x40, 0xab, 0x62, 0x42, 0xee,
+    0x25, 0x6f, 0x32, 0x6c, 0x75, 0x2c, 0xa7, 0xd3, 0xbd, 0x32, 0x3b, 0x1e,
+    0x11, 0x9c, 0xbd, 0x04, 0xa9, 0x78, 0x6f, 0x45, 0x5a, 0xda, 0x7e, 0x4b,
+    0xf6, 0xdd, 0xd9, 0xad, 0xb6, 0x62, 0x6d, 0x32, 0x13, 0x1c, 0x6b, 0x5c,
+    0x51, 0xa1, 0xe3, 0x47, 0xa3, 0x47, 0x8f, 0x53, 0xcf, 0xcf, 0x44, 0x1b,
+    0x88, 0xee, 0xd1, 0x2e, 0x03, 0x89, 0xaf, 0xc0, 0x61, 0x2d, 0x9e, 0x35,
+    0xeb, 0x0e, 0x03, 0xe0, 0xb7, 0xfb, 0xa5, 0xbc, 0x44, 0xbe, 0x0c, 0x89,
+    0x0a, 0x0f, 0xd6, 0x59, 0x47, 0x9e, 0xe6, 0x3d, 0x36, 0x9d, 0xff, 0x44,
+    0x5e, 0xac, 0xab, 0xe5, 0x3a, 0xd5, 0xb0, 0x35, 0x9f, 0x6d, 0x7f, 0xba,
+    0xc0, 0x85, 0x0e, 0xf4, 0x70, 0x3f, 0x13, 0x90, 0x4c, 0x50, 0x1a, 0xee,
+    0xc5, 0xeb, 0x69, 0xfe, 0x98, 0x42, 0x87, 0x1d, 0xce, 0x6c, 0x29, 0xaa,
+    0x2b, 0x31, 0xc2, 0x38, 0x7b, 0x6b, 0xee, 0x88, 0x0b, 0xba, 0xce, 0xa8,
+    0xca, 0x19, 0x60, 0x1b, 0x16, 0xf1, 0x25, 0x1e, 0xcf, 0x63, 0x66, 0x1e,
+    0xbb, 0x63, 0xeb, 0x7d, 0xca, 0xd2, 0xb4, 0x23, 0x5a, 0x01, 0x6f, 0x05,
+    0xd1, 0xdc, 0x41, 0x73, 0x75, 0xc0, 0xfd, 0x30, 0x91, 0x52, 0x68, 0x96,
+    0x45, 0xb3, 0x66, 0x01, 0x3b, 0x53, 0x89, 0x3c, 0x69, 0xbc, 0x6c, 0x69,
+    0xe3, 0x51, 0x8f, 0xe3, 0xd2, 0x84, 0xd5, 0x28, 0x66, 0xb5, 0xe6, 0x06,
+    0x09, 0xfe, 0x6d, 0xb0, 0x72, 0x16, 0xe0, 0x8a, 0xce, 0x61, 0x65, 0xa9,
+    0x21, 0x32, 0x48, 0xdc, 0x7a, 0x1d, 0xe1, 0x38, 0x7f, 0x8c, 0x75, 0x88,
+    0x3d, 0x08, 0xa9, 0x4a, 0x6f, 0x3d, 0x9f, 0x7f, 0x3f, 0xbd, 0x57, 0x6b,
+    0x19, 0xce, 0x3f, 0x4a, 0xc9, 0xd3, 0xf9, 0x6e, 0x72, 0x7b, 0x5b, 0x74,
+    0xea, 0xbe, 0x9c, 0x7a, 0x6d, 0x9c, 0x40, 0x49, 0xe6, 0xfb, 0x2a, 0x1a,
+    0x75, 0x70, 0xe5, 0x4e, 0xed, 0x74, 0xe0, 0x75, 0xac, 0xc0, 0xb1, 0x11,
+    0x3e, 0xf2, 0xaf, 0x88, 0x4d, 0x66, 0xb6, 0xf6, 0x15, 0x4f, 0x3c, 0x6c,
+    0x77, 0xae, 0x47, 0x51, 0x63, 0x9a, 0xfe, 0xe1, 0xb4, 0x1a, 0x12, 0xdf,
+    0xe9, 0x54, 0x8d, 0x3b, 0x30, 0x2a, 0x75, 0xe3, 0xe5, 0x29, 0xb1, 0x4c,
+    0xb0, 0x7c, 0x6d, 0xb5, 0xae, 0x85, 0xdb, 0x1e, 0x38, 0x55, 0x96, 0xa5,
+    0x5b, 0x9f, 0x15, 0x23, 0x28, 0x36, 0xb8, 0xa2, 0x41, 0xb4, 0xd7, 0x19,
+    0x91, 0x8d, 0x26, 0x3e, 0xca, 0x9c, 0x05, 0x7a, 0x2b, 0x60, 0x45, 0x86,
+    0x8b, 0xee, 0x64, 0x6f, 0x5c, 0x09, 0x4d, 0x4b, 0x5a, 0x7f, 0xb0, 0xc3,
+    0x26, 0x9d, 0x8b, 0xb8, 0x83, 0x69, 0xcf, 0x16, 0x72, 0x62, 0x3e, 0x5e,
+    0x53, 0x4f, 0x9c, 0x73, 0x76, 0xfc, 0x19, 0xef, 0xa0, 0x74, 0x3a, 0x11,
+    0x1e, 0xd0, 0x4d, 0xb7, 0x87, 0xa1, 0xd6, 0x87, 0x6c, 0x0e, 0x6c, 0x8c,
+    0xe9, 0xa0, 0x44, 0xc4, 0x72, 0x3e, 0x73, 0x17, 0x13, 0xd1, 0x4e, 0x3d,
+    0x8e, 0x1d, 0x5a, 0x8b, 0x75, 0xcb, 0x59, 0x2c, 0x47, 0x87, 0x15, 0x41,
+    0xfe, 0x08, 0xe9, 0xa6, 0x97, 0x17, 0x08, 0x26, 0x6a, 0xb5, 0xbb, 0x73,
+    0xaa, 0xb8, 0x5b, 0x65, 0x65, 0x5b, 0x30, 0x9e, 0x62, 0x59, 0x02, 0xf8,
+    0xb8, 0x0f, 0x32, 0x10, 0xc1, 0x36, 0x08, 0x52, 0x98, 0x4a, 0x1e, 0xf0,
+    0xab, 0x21, 0x5e, 0xde, 0x16, 0x0c, 0xda, 0x09, 0x99, 0x6b, 0x9e, 0xc0,
+    0x90, 0xa5, 0x5a, 0xcc, 0xb0, 0xb7, 0xbb, 0xd2, 0x8b, 0x5f, 0xd3, 0x3b,
+    0x3e, 0x8c, 0xa5, 0x71, 0x66, 0x06, 0xe3, 0x28, 0xd4, 0xf8, 0x3f, 0xe5,
+    0x27, 0xdf, 0xfe, 0x0f, 0x09, 0xb2, 0x8a, 0x09, 0x5a, 0x23, 0x61, 0x0d,
+    0x2d, 0xf5, 0x44, 0xf1, 0x5c, 0xf8, 0x82, 0x4e, 0xdc, 0x78, 0x7a, 0xab,
+    0xc3, 0x57, 0x91, 0xaf, 0x65, 0x6e, 0x71, 0xf1, 0x44, 0xbf, 0xed, 0x43,
+    0x50, 0xb4, 0x67, 0x48, 0xef, 0x5a, 0x10, 0x46, 0x81, 0xb4, 0x0c, 0xc8,
+    0x48, 0xed, 0x99, 0x7a, 0x45, 0xa5, 0x92, 0xc3, 0x69, 0xd6, 0xd7, 0x8a,
+    0x20, 0x1b, 0xeb, 0x8f, 0xb2, 0xff, 0xec, 0x6d, 0x76, 0x04, 0xf8, 0xc2,
+    0x58, 0x9b, 0xf2, 0x20, 0x53, 0xc4, 0x74, 0x91, 0x19, 0xdd, 0x2d, 0x12,
+    0x53, 0xc7, 0x6e, 0xd0, 0x02, 0x51, 0x3c, 0xa6, 0x7d, 0x80, 0x75, 0x6b,
+    0x1d, 0xdf, 0xf8, 0x6a, 0x52, 0xbb, 0x81, 0xf8, 0x30, 0x45, 0xef, 0x51,
+    0x85, 0x36, 0xbe, 0x8e, 0xcf, 0x0b, 0x9a, 0x46, 0xe8, 0x3f, 0x99, 0xfd,
+    0xf7, 0xd9, 0x3e, 0x84, 0xe5, 0xe3, 0x37, 0xcf, 0x98, 0x7f, 0xeb, 0x5e,
+    0x5a, 0x53, 0x77, 0x1c, 0x20, 0xdc, 0xf1, 0x20, 0x99, 0xec, 0x60, 0x40,
+    0x93, 0xef, 0x5c, 0x1c, 0x81, 0xe2, 0xa5, 0xad, 0x2a, 0xc2, 0xdb, 0x6b,
+    0xc1, 0x7e, 0x8f, 0xa9, 0x23, 0x5b, 0xd9, 0x0d, 0xfe, 0xa0, 0xac, 0x11,
+    0x28, 0xba, 0x8e, 0x92, 0x07, 0x2d, 0x07, 0x40, 0x83, 0x14, 0x4c, 0x35,
+    0x8d, 0xd0, 0x11, 0xff, 0x98, 0xdb, 0x00, 0x30, 0x6f, 0x65, 0xb6, 0xa0,
+    0x7f, 0x9c, 0x08, 0xb8, 0xce, 0xb3, 0xa8, 0x42, 0xd3, 0x84, 0x45, 0xe1,
+    0xe3, 0x8f, 0xa6, 0x89, 0x21, 0xd7, 0x74, 0x02, 0x4d, 0x64, 0xdf, 0x54,
+    0x15, 0x9e, 0xba, 0x12, 0x49, 0x09, 0x41, 0xf6, 0x10, 0x24, 0xa1, 0x84,
+    0x15, 0xfd, 0x68, 0x6a, 0x57, 0x66, 0xb3, 0x6d, 0x4c, 0xea, 0xbf, 0xbc,
+    0x60, 0x3f, 0x52, 0x1c, 0x44, 0x1b, 0xc0, 0x4a, 0x25, 0xe3, 0xd9, 0x4c,
+    0x9a, 0x74, 0xad, 0xfc, 0x9e, 0x8d, 0x0b, 0x18, 0x66, 0x24, 0xd1, 0x06,
+    0xac, 0x68, 0xc1, 0xae, 0x14, 0xce, 0xb1, 0xf3, 0x86, 0x9f, 0x87, 0x11,
+    0xd7, 0x9f, 0x30, 0x92, 0xdb, 0xec, 0x0b, 0x4a, 0xe8, 0xf6, 0x53, 0x36,
+    0x68, 0x12, 0x11, 0x5e, 0xe0, 0x34, 0xa4, 0xff, 0x00, 0x0a, 0x26, 0xb8,
+    0x62, 0x79, 0x9c, 0x0c, 0xd5, 0xe5, 0xf5, 0x1c, 0x1a, 0x16, 0x84, 0x4d,
+    0x8e, 0x5d, 0x31, 0x7e, 0xf7, 0xe2, 0xd3, 0xa1, 0x41, 0x90, 0x61, 0x5d,
+    0x04, 0xb2, 0x9a, 0x18, 0x9e, 0x54, 0xfb, 0xd1, 0x61, 0x95, 0x1b, 0x08,
+    0xca, 0x7c, 0x49, 0x44, 0x74, 0x1d, 0x2f, 0xca, 0xc4, 0x7a, 0xe1, 0x8b,
+    0x2f, 0xbb, 0x96, 0xee, 0x19, 0x8a, 0x5d, 0xfb, 0x3e, 0x82, 0xe7, 0x15,
+    0xdb, 0x29, 0x14, 0xee, 0xc9, 0x4d, 0x9a, 0xfb, 0x9f, 0x8a, 0xbb, 0x17,
+    0x37, 0x1b, 0x6e, 0x28, 0x6c, 0xf9, 0xff, 0xb5, 0xb5, 0x8b, 0x9d, 0x88,
+    0x20, 0x08, 0x10, 0xd7, 0xca, 0x58, 0xf6, 0xe1, 0x32, 0x91, 0x6f, 0x36,
+    0xc0, 0xad, 0xc1, 0x57, 0x5d, 0x76, 0x31, 0x43, 0xf3, 0xdd, 0xec, 0xf1,
+    0xa9, 0x79, 0xe9, 0xe9, 0x85, 0xd7, 0x91, 0xc7, 0x31, 0x62, 0x3c, 0xd2,
+    0x90, 0x2c, 0x9c, 0xa4, 0x56, 0x37, 0x7b, 0xbe, 0x40, 0x58, 0xc0, 0x81,
+    0x83, 0x22, 0xe8, 0x13, 0x79, 0x18, 0xdb, 0x3a, 0x1b, 0x31, 0x0d, 0x00,
+    0x6c, 0x22, 0x62, 0x75, 0x70, 0xd8, 0x96, 0x59, 0x99, 0x44, 0x79, 0x71,
+    0xa6, 0x76, 0x81, 0x28, 0xb2, 0x65, 0xe8, 0x47, 0x14, 0xc6, 0x39, 0x06,
+};
+
+enum spake2_state_t {
+  spake2_state_init = 0,
+  spake2_state_msg_generated,
+  spake2_state_key_generated,
+};
+
+struct spake2_ctx_st {
+  uint8_t private_key[32];
+  uint8_t my_msg[32];
+  uint8_t password_scalar[32];
+  uint8_t password_hash[SHA512_DIGEST_LENGTH];
+  uint8_t *my_name;
+  size_t my_name_len;
+  uint8_t *their_name;
+  size_t their_name_len;
+  enum spake2_role_t my_role;
+  enum spake2_state_t state;
+};
+
+SPAKE2_CTX *SPAKE2_CTX_new(enum spake2_role_t my_role,
+                           const uint8_t *my_name, size_t my_name_len,
+                           const uint8_t *their_name, size_t their_name_len) {
+  SPAKE2_CTX *ctx = OPENSSL_malloc(sizeof(SPAKE2_CTX));
+  if (ctx == NULL) {
+    return NULL;
+  }
+
+  memset(ctx, 0, sizeof(SPAKE2_CTX));
+  ctx->my_role = my_role;
+
+  CBS my_name_cbs, their_name_cbs;
+  CBS_init(&my_name_cbs, my_name, my_name_len);
+  CBS_init(&their_name_cbs, their_name, their_name_len);
+  if (!CBS_stow(&my_name_cbs, &ctx->my_name, &ctx->my_name_len) ||
+      !CBS_stow(&their_name_cbs, &ctx->their_name, &ctx->their_name_len)) {
+    SPAKE2_CTX_free(ctx);
+    return NULL;
+  }
+
+  return ctx;
+}
+
+void SPAKE2_CTX_free(SPAKE2_CTX *ctx) {
+  if (ctx == NULL) {
+    return;
+  }
+
+  OPENSSL_free(ctx->my_name);
+  OPENSSL_free(ctx->their_name);
+  OPENSSL_free(ctx);
+}
+
+/* left_shift_3 sets |n| to |n|*8, where |n| is represented in little-endian
+ * order. */
+static void left_shift_3(uint8_t n[32]) {
+  uint8_t carry = 0;
+  unsigned i;
+
+  for (i = 0; i < 32; i++) {
+    const uint8_t next_carry = n[i] >> 5;
+    n[i] = (n[i] << 3) | carry;
+    carry = next_carry;
+  }
+}
+
+int SPAKE2_generate_msg(SPAKE2_CTX *ctx, uint8_t *out, size_t *out_len,
+                         size_t max_out_len, const uint8_t *password,
+                         size_t password_len) {
+  if (ctx->state != spake2_state_init) {
+    return 0;
+  }
+
+  if (max_out_len < sizeof(ctx->my_msg)) {
+    return 0;
+  }
+
+  uint8_t private_tmp[64];
+  RAND_bytes(private_tmp, sizeof(private_tmp));
+  x25519_sc_reduce(private_tmp);
+  /* Multiply by the cofactor (eight) so that we'll clear it when operating on
+   * the peer's point later in the protocol. */
+  left_shift_3(private_tmp);
+  memcpy(ctx->private_key, private_tmp, sizeof(ctx->private_key));
+
+  ge_p3 P;
+  x25519_ge_scalarmult_base(&P, ctx->private_key);
+
+  /* mask = h(password) * <N or M>. */
+  uint8_t password_tmp[SHA512_DIGEST_LENGTH];
+  SHA512(password, password_len, password_tmp);
+  memcpy(ctx->password_hash, password_tmp, sizeof(ctx->password_hash));
+  x25519_sc_reduce(password_tmp);
+  memcpy(ctx->password_scalar, password_tmp, sizeof(ctx->password_scalar));
+
+  ge_p3 mask;
+  x25519_ge_scalarmult_small_precomp(&mask, ctx->password_scalar,
+                              ctx->my_role == spake2_role_alice
+                                  ? kSpakeMSmallPrecomp
+                                  : kSpakeNSmallPrecomp);
+
+  /* P* = P + mask. */
+  ge_cached mask_cached;
+  x25519_ge_p3_to_cached(&mask_cached, &mask);
+  ge_p1p1 Pstar;
+  x25519_ge_add(&Pstar, &P, &mask_cached);
+
+  /* Encode P* */
+  ge_p2 Pstar_proj;
+  x25519_ge_p1p1_to_p2(&Pstar_proj, &Pstar);
+  x25519_ge_tobytes(ctx->my_msg, &Pstar_proj);
+
+  memcpy(out, ctx->my_msg, sizeof(ctx->my_msg));
+  *out_len = sizeof(ctx->my_msg);
+  ctx->state = spake2_state_msg_generated;
+
+  return 1;
+}
+
+static void update_with_length_prefix(SHA512_CTX *sha, const uint8_t *data,
+                                      const size_t len) {
+  uint8_t len_le[8];
+  size_t l = len;
+  unsigned i;
+
+  for (i = 0; i < 8; i++) {
+    len_le[i] = l & 0xff;
+    l >>= 8;
+  }
+
+  SHA512_Update(sha, len_le, sizeof(len_le));
+  SHA512_Update(sha, data, len);
+}
+
+int SPAKE2_process_msg(SPAKE2_CTX *ctx, uint8_t *out_key, size_t *out_key_len,
+                       size_t max_out_key_len, const uint8_t *their_msg,
+                       size_t their_msg_len) {
+  if (ctx->state != spake2_state_msg_generated ||
+      their_msg_len != 32) {
+    return 0;
+  }
+
+  ge_p3 Qstar;
+  if (0 != x25519_ge_frombytes_vartime(&Qstar, their_msg)) {
+    /* Point received from peer was not on the curve. */
+    return 0;
+  }
+
+  /* Unmask peer's value. */
+  ge_p3 peers_mask;
+  x25519_ge_scalarmult_small_precomp(&peers_mask, ctx->password_scalar,
+                                    ctx->my_role == spake2_role_alice
+                                        ? kSpakeNSmallPrecomp
+                                        : kSpakeMSmallPrecomp);
+
+  ge_cached peers_mask_cached;
+  x25519_ge_p3_to_cached(&peers_mask_cached, &peers_mask);
+
+  ge_p1p1 Q_compl;
+  ge_p3 Q_ext;
+  x25519_ge_sub(&Q_compl, &Qstar, &peers_mask_cached);
+  x25519_ge_p1p1_to_p3(&Q_ext, &Q_compl);
+
+  ge_p2 dh_shared;
+  x25519_ge_scalarmult(&dh_shared, ctx->private_key, &Q_ext);
+
+  uint8_t dh_shared_encoded[32];
+  x25519_ge_tobytes(dh_shared_encoded, &dh_shared);
+
+  SHA512_CTX sha;
+  SHA512_Init(&sha);
+  if (ctx->my_role == spake2_role_alice) {
+    update_with_length_prefix(&sha, ctx->my_name, ctx->my_name_len);
+    update_with_length_prefix(&sha, ctx->their_name, ctx->their_name_len);
+    update_with_length_prefix(&sha, ctx->my_msg, sizeof(ctx->my_msg));
+    update_with_length_prefix(&sha, their_msg, 32);
+  } else {
+    update_with_length_prefix(&sha, ctx->their_name, ctx->their_name_len);
+    update_with_length_prefix(&sha, ctx->my_name, ctx->my_name_len);
+    update_with_length_prefix(&sha, their_msg, 32);
+    update_with_length_prefix(&sha, ctx->my_msg, sizeof(ctx->my_msg));
+  }
+  update_with_length_prefix(&sha, dh_shared_encoded, sizeof(dh_shared_encoded));
+  update_with_length_prefix(&sha, ctx->password_hash,
+                            sizeof(ctx->password_hash));
+
+  uint8_t key[SHA512_DIGEST_LENGTH];
+  SHA512_Final(key, &sha);
+
+  size_t to_copy = max_out_key_len;
+  if (to_copy > sizeof(key)) {
+    to_copy = sizeof(key);
+  }
+  memcpy(out_key, key, to_copy);
+  *out_key_len = to_copy;
+  ctx->state = spake2_state_key_generated;
+
+  return 1;
+}
diff --git a/crypto/curve25519/spake25519_test.cc b/crypto/curve25519/spake25519_test.cc
new file mode 100644
index 0000000..d97a860
--- /dev/null
+++ b/crypto/curve25519/spake25519_test.cc
@@ -0,0 +1,169 @@
+/* Copyright (c) 2016, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <string>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <openssl/curve25519.h>
+#include "../test/scoped_types.h"
+
+
+struct SPAKE2Run {
+  bool Run() {
+    ScopedSPAKE2_CTX alice(SPAKE2_CTX_new(
+        spake2_role_alice,
+        reinterpret_cast<const uint8_t *>(alice_names.first.data()),
+        alice_names.first.size(),
+        reinterpret_cast<const uint8_t *>(alice_names.second.data()),
+        alice_names.second.size()));
+    ScopedSPAKE2_CTX bob(SPAKE2_CTX_new(
+        spake2_role_bob,
+        reinterpret_cast<const uint8_t *>(bob_names.first.data()),
+        bob_names.first.size(),
+        reinterpret_cast<const uint8_t *>(bob_names.second.data()),
+        bob_names.second.size()));
+
+    if (!alice || !bob) {
+      return false;
+    }
+
+    uint8_t alice_msg[SPAKE2_MAX_MSG_SIZE];
+    uint8_t bob_msg[SPAKE2_MAX_MSG_SIZE];
+    size_t alice_msg_len, bob_msg_len;
+
+    if (!SPAKE2_generate_msg(
+            alice.get(), alice_msg, &alice_msg_len, sizeof(alice_msg),
+            reinterpret_cast<const uint8_t *>(alice_password.data()),
+            alice_password.size()) ||
+        !SPAKE2_generate_msg(
+            bob.get(), bob_msg, &bob_msg_len, sizeof(bob_msg),
+            reinterpret_cast<const uint8_t *>(bob_password.data()),
+            bob_password.size())) {
+      return false;
+    }
+
+    if (alice_corrupt_msg_bit >= 0 &&
+        static_cast<size_t>(alice_corrupt_msg_bit) < 8 * alice_msg_len) {
+      alice_msg[alice_corrupt_msg_bit/8] ^= 1 << (alice_corrupt_msg_bit & 7);
+    }
+
+    uint8_t alice_key[64], bob_key[64];
+    size_t alice_key_len, bob_key_len;
+
+    if (!SPAKE2_process_msg(alice.get(), alice_key, &alice_key_len,
+                            sizeof(alice_key), bob_msg, bob_msg_len) ||
+        !SPAKE2_process_msg(bob.get(), bob_key, &bob_key_len, sizeof(bob_key),
+                            alice_msg, alice_msg_len)) {
+      return false;
+    }
+
+    key_matches_ = (alice_key_len == bob_key_len &&
+                    memcmp(alice_key, bob_key, alice_key_len) == 0);
+
+    return true;
+  }
+
+  bool key_matches() const {
+    return key_matches_;
+  }
+
+  std::string alice_password = "password";
+  std::string bob_password = "password";
+  std::pair<std::string, std::string> alice_names = {"alice", "bob"};
+  std::pair<std::string, std::string> bob_names = {"bob", "alice"};
+  int alice_corrupt_msg_bit = -1;
+
+ private:
+  bool key_matches_ = false;
+};
+
+static bool TestSPAKE2() {
+  for (unsigned i = 0; i < 20; i++) {
+    SPAKE2Run spake2;
+    if (!spake2.Run()) {
+      fprintf(stderr, "TestSPAKE2: SPAKE2 failed.\n");
+      return false;
+    }
+
+    if (!spake2.key_matches()) {
+      fprintf(stderr, "Key didn't match for equal passwords.\n");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool TestWrongPassword() {
+  SPAKE2Run spake2;
+  spake2.bob_password = "wrong password";
+  if (!spake2.Run()) {
+    fprintf(stderr, "TestSPAKE2: SPAKE2 failed.\n");
+    return false;
+  }
+
+  if (spake2.key_matches()) {
+    fprintf(stderr, "Key matched for unequal passwords.\n");
+    return false;
+  }
+
+  return true;
+}
+
+static bool TestWrongNames() {
+  SPAKE2Run spake2;
+  spake2.alice_names.second = "charlie";
+  spake2.bob_names.second = "charlie";
+  if (!spake2.Run()) {
+    fprintf(stderr, "TestSPAKE2: SPAKE2 failed.\n");
+    return false;
+  }
+
+  if (spake2.key_matches()) {
+    fprintf(stderr, "Key matched for unequal names.\n");
+    return false;
+  }
+
+  return true;
+}
+
+static bool TestCorruptMessages() {
+  for (int i = 0; i < 8 * SPAKE2_MAX_MSG_SIZE; i++) {
+    SPAKE2Run spake2;
+    spake2.alice_corrupt_msg_bit = i;
+    if (spake2.Run() && spake2.key_matches()) {
+      fprintf(stderr, "Passed after corrupting Alice's message, bit %d\n", i);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/* TODO(agl): add tests with fixed vectors once SPAKE2 is nailed down. */
+
+int main(int argc, char **argv) {
+  if (!TestSPAKE2() ||
+      !TestWrongPassword() ||
+      !TestWrongNames() ||
+      !TestCorruptMessages()) {
+    return 1;
+  }
+
+  printf("PASS\n");
+  return 0;
+}
diff --git a/crypto/test/scoped_types.h b/crypto/test/scoped_types.h
index b5ae324..eef30e7 100644
--- a/crypto/test/scoped_types.h
+++ b/crypto/test/scoped_types.h
@@ -25,10 +25,11 @@
 #include <openssl/bio.h>
 #include <openssl/bn.h>
 #include <openssl/cmac.h>
+#include <openssl/curve25519.h>
 #include <openssl/dh.h>
+#include <openssl/ecdsa.h>
 #include <openssl/ec.h>
 #include <openssl/ec_key.h>
-#include <openssl/ecdsa.h>
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
 #include <openssl/mem.h>
@@ -112,6 +113,7 @@
 using ScopedPKCS8_PRIV_KEY_INFO = ScopedOpenSSLType<PKCS8_PRIV_KEY_INFO,
                                                     PKCS8_PRIV_KEY_INFO_free>;
 using ScopedPKCS12 = ScopedOpenSSLType<PKCS12, PKCS12_free>;
+using ScopedSPAKE2_CTX = ScopedOpenSSLType<SPAKE2_CTX, SPAKE2_CTX_free>;
 using ScopedRSA = ScopedOpenSSLType<RSA, RSA_free>;
 using ScopedX509 = ScopedOpenSSLType<X509, X509_free>;
 using ScopedX509_ALGOR = ScopedOpenSSLType<X509_ALGOR, X509_ALGOR_free>;
diff --git a/include/openssl/base.h b/include/openssl/base.h
index f1da61a..8507f37 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -240,6 +240,7 @@
 typedef struct sha256_state_st SHA256_CTX;
 typedef struct sha512_state_st SHA512_CTX;
 typedef struct sha_state_st SHA_CTX;
+typedef struct spake2_ctx_st SPAKE2_CTX;
 typedef struct srtp_protection_profile_st SRTP_PROTECTION_PROFILE;
 typedef struct ssl_cipher_st SSL_CIPHER;
 typedef struct ssl_ctx_st SSL_CTX;
diff --git a/include/openssl/curve25519.h b/include/openssl/curve25519.h
index c202575..fd6a0da 100644
--- a/include/openssl/curve25519.h
+++ b/include/openssl/curve25519.h
@@ -84,6 +84,88 @@
                                   const uint8_t public_key[32]);
 
 
+/* SPAKE2.
+ *
+ * SPAKE2 is a password-authenticated key-exchange. It allows two parties,
+ * who share a low-entropy secret (i.e. password), to agree on a shared key.
+ * An attacker can only make one guess of the password per execution of the
+ * protocol.
+ *
+ * See https://tools.ietf.org/html/draft-irtf-cfrg-spake2-02. */
+
+/* spake2_role_t enumerates the different “roles” in SPAKE2. The protocol
+ * requires that the symmetry of the two parties be broken so one participant
+ * must be “Alice” and the other be “Bob”. */
+enum spake2_role_t {
+  spake2_role_alice,
+  spake2_role_bob,
+};
+
+/* SPAKE2_CTX_new creates a new |SPAKE2_CTX| (which can only be used for a
+ * single execution of the protocol). SPAKE2 requires the symmetry of the two
+ * parties to be broken which is indicated via |my_role| – each party must pass
+ * a different value for this argument.
+ *
+ * The |my_name| and |their_name| arguments allow optional, opaque names to be
+ * bound into the protocol. For example MAC addresses, hostnames, usernames
+ * etc. These values are not exposed and can avoid context-confusion attacks
+ * when a password is shared between several devices. */
+OPENSSL_EXPORT SPAKE2_CTX *SPAKE2_CTX_new(
+    enum spake2_role_t my_role,
+    const uint8_t *my_name, size_t my_name_len,
+    const uint8_t *their_name, size_t their_name_len);
+
+/* SPAKE2_CTX_free frees |ctx| and all the resources that it has allocated. */
+OPENSSL_EXPORT void SPAKE2_CTX_free(SPAKE2_CTX *ctx);
+
+/* SPAKE2_MAX_MSG_SIZE is the maximum size of a SPAKE2 message. */
+#define SPAKE2_MAX_MSG_SIZE 32
+
+/* SPAKE2_generate_msg generates a SPAKE2 message given |password|, writes
+ * it to |out| and sets |*out_len| to the number of bytes written.
+ *
+ * At most |max_out_len| bytes are written to |out| and, in order to ensure
+ * success, |max_out_len| should be at least |SPAKE2_MAX_MSG_SIZE| bytes.
+ *
+ * This function can only be called once for a given |SPAKE2_CTX|.
+ *
+ * It returns one on success and zero on error. */
+OPENSSL_EXPORT int SPAKE2_generate_msg(SPAKE2_CTX *ctx, uint8_t *out,
+                                       size_t *out_len, size_t max_out_len,
+                                       const uint8_t *password,
+                                       size_t password_len);
+
+/* SPAKE2_MAX_KEY_SIZE is the maximum amount of key material that SPAKE2 will
+ * produce. */
+#define SPAKE2_MAX_KEY_SIZE 64
+
+/* SPAKE2_process_msg completes the SPAKE2 exchange given the peer's message in
+ * |their_msg|, writes at most |max_out_key_len| bytes to |out_key| and sets
+ * |*out_key_len| to the number of bytes written.
+ *
+ * The resulting keying material is suitable for:
+ *   a) Using directly in a key-confirmation step: i.e. each side could
+ *      transmit a hash of their role, a channel-binding value and the key
+ *      material to prove to the other side that they know the shared key.
+ *   b) Using as input keying material to HKDF to generate a variety of subkeys
+ *      for encryption etc.
+ *
+ * If |max_out_key_key| is smaller than the amount of key material generated
+ * then the key is silently truncated. If you want to ensure that no truncation
+ * occurs then |max_out_key| should be at least |SPAKE2_MAX_KEY_SIZE|.
+ *
+ * You must call |SPAKE2_generate_msg| on a given |SPAKE2_CTX| before calling
+ * this function. On successful return, |ctx| is complete and calling
+ * |SPAKE2_CTX_free| is the only acceptable operation on it.
+ *
+ * Returns one on success or zero on error. */
+OPENSSL_EXPORT int SPAKE2_process_msg(SPAKE2_CTX *ctx, uint8_t *out_key,
+                                      size_t *out_key_len,
+                                      size_t max_out_key_len,
+                                      const uint8_t *their_msg,
+                                      size_t their_msg_len);
+
+
 #if defined(__cplusplus)
 }  /* extern C */
 #endif
diff --git a/tool/speed.cc b/tool/speed.cc
index db7c5fa..a4edfe7 100644
--- a/tool/speed.cc
+++ b/tool/speed.cc
@@ -467,6 +467,53 @@
   return true;
 }
 
+static bool SpeedSPAKE2(const std::string &selected) {
+  if (!selected.empty() && selected.find("SPAKE2") == std::string::npos) {
+    return true;
+  }
+
+  TimeResults results;
+
+  static const uint8_t kAliceName[] = {'A'};
+  static const uint8_t kBobName[] = {'B'};
+  static const uint8_t kPassword[] = "password";
+  ScopedSPAKE2_CTX alice(SPAKE2_CTX_new(spake2_role_alice, kAliceName,
+                                        sizeof(kAliceName), kBobName,
+                                        sizeof(kBobName)));
+  uint8_t alice_msg[SPAKE2_MAX_MSG_SIZE];
+  size_t alice_msg_len;
+
+  if (!SPAKE2_generate_msg(alice.get(), alice_msg, &alice_msg_len,
+                           sizeof(alice_msg),
+                           kPassword, sizeof(kPassword))) {
+    fprintf(stderr, "SPAKE2_generate_msg failed.\n");
+    return false;
+  }
+
+  if (!TimeFunction(&results, [alice_msg, alice_msg_len]() -> bool {
+        ScopedSPAKE2_CTX bob(SPAKE2_CTX_new(spake2_role_bob, kBobName,
+                                            sizeof(kBobName), kAliceName,
+                                            sizeof(kAliceName)));
+        uint8_t bob_msg[SPAKE2_MAX_MSG_SIZE], bob_key[64];
+        size_t bob_msg_len, bob_key_len;
+        if (!SPAKE2_generate_msg(bob.get(), bob_msg, &bob_msg_len,
+                                 sizeof(bob_msg), kPassword,
+                                 sizeof(kPassword)) ||
+            !SPAKE2_process_msg(bob.get(), bob_key, &bob_key_len,
+                                sizeof(bob_key), alice_msg, alice_msg_len)) {
+          return false;
+        }
+
+        return true;
+      })) {
+    fprintf(stderr, "SPAKE2 failed.\n");
+  }
+
+  results.Print("SPAKE2 over Ed25519");
+
+  return true;
+}
+
 bool Speed(const std::vector<std::string> &args) {
   std::string selected;
   if (args.size() > 1) {
@@ -546,7 +593,8 @@
       !SpeedRandom(selected) ||
       !SpeedECDH(selected) ||
       !SpeedECDSA(selected) ||
-      !Speed25519(selected)) {
+      !Speed25519(selected) ||
+      !SpeedSPAKE2(selected)) {
     return false;
   }
 
diff --git a/util/all_tests.json b/util/all_tests.json
index bacee7a..3614591 100644
--- a/util/all_tests.json
+++ b/util/all_tests.json
@@ -34,6 +34,7 @@
 	["crypto/constant_time_test"],
 	["crypto/curve25519/ed25519_test", "crypto/curve25519/ed25519_tests.txt"],
 	["crypto/curve25519/x25519_test"],
+	["crypto/curve25519/spake25519_test"],
 	["crypto/dh/dh_test"],
 	["crypto/digest/digest_test"],
 	["crypto/dsa/dsa_test"],