]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - sshecc.c
Clean up elliptic curve selection and naming.
[PuTTY.git] / sshecc.c
index 7d877bade133d12292fa6b960dc7f686332d7938..dd5b24a43b0fa1ffee7a3a190aabdada2d70bd84 100644 (file)
--- a/sshecc.c
+++ b/sshecc.c
@@ -1,7 +1,13 @@
 /*
  * Elliptic-curve crypto module for PuTTY
  * Implements the three required curves, no optional curves
+ *
  * NOTE: Only curves on prime field are handled by the maths functions
+ *       in Weierstrass form using Jacobian co-ordinates.
+ *
+ *       Montgomery form curves are supported for DH. (Curve25519)
+ *
+ *       Edwards form curves are supported for DSA. (Ed25519)
  */
 
 /*
  * That specification delegates details of public key formatting and a
  * lot of underlying mechanism to SEC 1:
  *   http://www.secg.org/sec1-v2.pdf
+ *
+ * Montgomery maths from:
+ * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13
+ *   http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf
+ *
+ * Edwards DSA:
+ *   http://ed25519.cr.yp.to/ed25519-20110926.pdf
  */
 
 #include <stdlib.h>
  * Elliptic curve definitions
  */
 
-static int initialise_curve(struct ec_curve *curve, int bits, unsigned char *p,
-                            unsigned char *a, unsigned char *b,
-                            unsigned char *n, unsigned char *Gx,
-                            unsigned char *Gy)
+static int initialise_wcurve(struct ec_curve *curve, int bits, unsigned char *p,
+                             unsigned char *a, unsigned char *b,
+                             unsigned char *n, unsigned char *Gx,
+                             unsigned char *Gy)
 {
     int length = bits / 8;
     if (bits % 8) ++length;
 
+    curve->type = EC_WEIERSTRASS;
+
     curve->fieldBits = bits;
     curve->p = bignum_from_bytes(p, length);
     if (!curve->p) goto error;
 
     /* Curve co-efficients */
-    curve->a = bignum_from_bytes(a, length);
-    if (!curve->a) goto error;
-    curve->b = bignum_from_bytes(b, length);
-    if (!curve->b) goto error;
+    curve->w.a = bignum_from_bytes(a, length);
+    if (!curve->w.a) goto error;
+    curve->w.b = bignum_from_bytes(b, length);
+    if (!curve->w.b) goto error;
 
     /* Group order and generator */
-    curve->n = bignum_from_bytes(n, length);
-    if (!curve->n) goto error;
-    curve->G.x = bignum_from_bytes(Gx, length);
-    if (!curve->G.x) goto error;
-    curve->G.y = bignum_from_bytes(Gy, length);
-    if (!curve->G.y) goto error;
-    curve->G.curve = curve;
-    curve->G.infinity = 0;
+    curve->w.n = bignum_from_bytes(n, length);
+    if (!curve->w.n) goto error;
+    curve->w.G.x = bignum_from_bytes(Gx, length);
+    if (!curve->w.G.x) goto error;
+    curve->w.G.y = bignum_from_bytes(Gy, length);
+    if (!curve->w.G.y) goto error;
+    curve->w.G.curve = curve;
+    curve->w.G.infinity = 0;
+
+    return 1;
+  error:
+    if (curve->p) freebn(curve->p);
+    if (curve->w.a) freebn(curve->w.a);
+    if (curve->w.b) freebn(curve->w.b);
+    if (curve->w.n) freebn(curve->w.n);
+    if (curve->w.G.x) freebn(curve->w.G.x);
+    return 0;
+}
+
+static int initialise_mcurve(struct ec_curve *curve, int bits, unsigned char *p,
+                             unsigned char *a, unsigned char *b,
+                             unsigned char *Gx)
+{
+    int length = bits / 8;
+    if (bits % 8) ++length;
+
+    curve->type = EC_MONTGOMERY;
+
+    curve->fieldBits = bits;
+    curve->p = bignum_from_bytes(p, length);
+    if (!curve->p) goto error;
+
+    /* Curve co-efficients */
+    curve->m.a = bignum_from_bytes(a, length);
+    if (!curve->m.a) goto error;
+    curve->m.b = bignum_from_bytes(b, length);
+    if (!curve->m.b) goto error;
+
+    /* Generator */
+    curve->m.G.x = bignum_from_bytes(Gx, length);
+    if (!curve->m.G.x) goto error;
+    curve->m.G.y = NULL;
+    curve->m.G.z = NULL;
+    curve->m.G.curve = curve;
+    curve->m.G.infinity = 0;
 
     return 1;
   error:
     if (curve->p) freebn(curve->p);
-    if (curve->a) freebn(curve->a);
-    if (curve->b) freebn(curve->b);
-    if (curve->n) freebn(curve->n);
-    if (curve->G.x) freebn(curve->G.x);
+    if (curve->m.a) freebn(curve->m.a);
+    if (curve->m.b) freebn(curve->m.b);
     return 0;
 }
 
-unsigned char nistp256_oid[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07};
-int nistp256_oid_len = 8;
-unsigned char nistp384_oid[] = {0x2b, 0x81, 0x04, 0x00, 0x22};
-int nistp384_oid_len = 5;
-unsigned char nistp521_oid[] = {0x2b, 0x81, 0x04, 0x00, 0x23};
-int nistp521_oid_len = 5;
+static int initialise_ecurve(struct ec_curve *curve, int bits, unsigned char *p,
+                             unsigned char *l, unsigned char *d,
+                             unsigned char *Bx, unsigned char *By)
+{
+    int length = bits / 8;
+    if (bits % 8) ++length;
+
+    curve->type = EC_EDWARDS;
+
+    curve->fieldBits = bits;
+    curve->p = bignum_from_bytes(p, length);
+    if (!curve->p) goto error;
+
+    /* Curve co-efficients */
+    curve->e.l = bignum_from_bytes(l, length);
+    if (!curve->e.l) goto error;
+    curve->e.d = bignum_from_bytes(d, length);
+    if (!curve->e.d) goto error;
+
+    /* Group order and generator */
+    curve->e.B.x = bignum_from_bytes(Bx, length);
+    if (!curve->e.B.x) goto error;
+    curve->e.B.y = bignum_from_bytes(By, length);
+    if (!curve->e.B.y) goto error;
+    curve->e.B.curve = curve;
+    curve->e.B.infinity = 0;
+
+    return 1;
+  error:
+    if (curve->p) freebn(curve->p);
+    if (curve->e.l) freebn(curve->e.l);
+    if (curve->e.d) freebn(curve->e.d);
+    if (curve->e.B.x) freebn(curve->e.B.x);
+    return 0;
+}
 
-struct ec_curve *ec_p256(void)
+static struct ec_curve *ec_p256(void)
 {
     static struct ec_curve curve = { 0 };
     static unsigned char initialised = 0;
@@ -113,10 +192,12 @@ struct ec_curve *ec_p256(void)
             0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5
         };
 
-        if (!initialise_curve(&curve, 256, p, a, b, n, Gx, Gy)) {
+        if (!initialise_wcurve(&curve, 256, p, a, b, n, Gx, Gy)) {
             return NULL;
         }
 
+        curve.name = "nistp256";
+
         /* Now initialised, no need to do it again */
         initialised = 1;
     }
@@ -124,7 +205,7 @@ struct ec_curve *ec_p256(void)
     return &curve;
 }
 
-struct ec_curve *ec_p384(void)
+static struct ec_curve *ec_p384(void)
 {
     static struct ec_curve curve = { 0 };
     static unsigned char initialised = 0;
@@ -180,10 +261,12 @@ struct ec_curve *ec_p384(void)
             0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f
         };
 
-        if (!initialise_curve(&curve, 384, p, a, b, n, Gx, Gy)) {
+        if (!initialise_wcurve(&curve, 384, p, a, b, n, Gx, Gy)) {
             return NULL;
         }
 
+        curve.name = "nistp384";
+
         /* Now initialised, no need to do it again */
         initialised = 1;
     }
@@ -191,7 +274,7 @@ struct ec_curve *ec_p384(void)
     return &curve;
 }
 
-struct ec_curve *ec_p521(void)
+static struct ec_curve *ec_p521(void)
 {
     static struct ec_curve curve = { 0 };
     static unsigned char initialised = 0;
@@ -265,10 +348,12 @@ struct ec_curve *ec_p521(void)
             0x66, 0x50
         };
 
-        if (!initialise_curve(&curve, 521, p, a, b, n, Gx, Gy)) {
+        if (!initialise_wcurve(&curve, 521, p, a, b, n, Gx, Gy)) {
             return NULL;
         }
 
+        curve.name = "nistp521";
+
         /* Now initialised, no need to do it again */
         initialised = 1;
     }
@@ -276,45 +361,104 @@ struct ec_curve *ec_p521(void)
     return &curve;
 }
 
-static struct ec_curve *ec_name_to_curve(char *name, int len) {
-    if (len == 8 && !memcmp(name, "nistp", 5)) {
-        name += 5;
-        if (!memcmp(name, "256", 3)) {
-            return ec_p256();
-        } else if (!memcmp(name, "384", 3)) {
-            return ec_p384();
-        } else if (!memcmp(name, "521", 3)) {
-            return ec_p521();
+static struct ec_curve *ec_curve25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        unsigned char p[] = {
+            0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed
+        };
+        unsigned char a[] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6d, 0x06
+        };
+        unsigned char b[] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+        };
+        unsigned char gx[32] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09
+        };
+
+        if (!initialise_mcurve(&curve, 256, p, a, b, gx)) {
+            return NULL;
         }
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
     }
 
-    return NULL;
+    return &curve;
 }
 
-static int ec_curve_to_name(const struct ec_curve *curve, unsigned char *name, int len) {
-    /* Return length of string */
-    if (name == NULL) return 8;
-
-    /* Not enough space for the name */
-    if (len < 8) return 0;
-
-    /* Put the name in the buffer */
-    switch (curve->fieldBits) {
-      case 256:
-        memcpy(name+5, "256", 3);
-        break;
-      case 384:
-        memcpy(name+5, "384", 3);
-        break;
-      case 521:
-        memcpy(name+5, "521", 3);
-        break;
-      default:
-        return 0;
+static struct ec_curve *ec_ed25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        unsigned char q[] = {
+            0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed
+        };
+        unsigned char l[32] = {
+            0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6,
+            0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed
+        };
+        unsigned char d[32] = {
+            0x52, 0x03, 0x6c, 0xee, 0x2b, 0x6f, 0xfe, 0x73,
+            0x8c, 0xc7, 0x40, 0x79, 0x77, 0x79, 0xe8, 0x98,
+            0x00, 0x70, 0x0a, 0x4d, 0x41, 0x41, 0xd8, 0xab,
+            0x75, 0xeb, 0x4d, 0xca, 0x13, 0x59, 0x78, 0xa3
+        };
+        unsigned char Bx[32] = {
+            0x21, 0x69, 0x36, 0xd3, 0xcd, 0x6e, 0x53, 0xfe,
+            0xc0, 0xa4, 0xe2, 0x31, 0xfd, 0xd6, 0xdc, 0x5c,
+            0x69, 0x2c, 0xc7, 0x60, 0x95, 0x25, 0xa7, 0xb2,
+            0xc9, 0x56, 0x2d, 0x60, 0x8f, 0x25, 0xd5, 0x1a
+        };
+        unsigned char By[32] = {
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x58
+        };
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        if (!initialise_ecurve(&curve, 256, q, l, d, Bx, By)) {
+            return NULL;
+        }
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
     }
 
-    memcpy(name, "nistp", 5);
-    return 8;
+    return &curve;
 }
 
 /* Return 1 if a is -3 % p, otherwise return 0
@@ -324,7 +468,11 @@ static int ec_aminus3(const struct ec_curve *curve)
     int ret;
     Bignum _p;
 
-    _p = bignum_add_long(curve->a, 3);
+    if (curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
+
+    _p = bignum_add_long(curve->w.a, 3);
     if (!_p) return 0;
 
     ret = !bignum_cmp(curve->p, _p);
@@ -445,12 +593,52 @@ static struct ec_point *ec_point_copy(const struct ec_point *a)
 
 static int ec_point_verify(const struct ec_point *a)
 {
-    if (a->infinity)
-    {
+    if (a->infinity) {
         return 1;
-    }
-    else
-    {
+    } else if (a->curve->type == EC_EDWARDS) {
+        /* Check y^2 - x^2 - 1 - d * x^2 * y^2 == 0 */
+        Bignum y2, x2, tmp, tmp2, tmp3;
+        int ret;
+
+        y2 = ecf_square(a->y, a->curve);
+        if (!y2) {
+            return 0;
+        }
+        x2 = ecf_square(a->x, a->curve);
+        if (!x2) {
+            freebn(y2);
+            return 0;
+        }
+        tmp = modmul(a->curve->e.d, x2, a->curve->p);
+        if (!tmp) {
+            freebn(x2);
+            freebn(y2);
+            return 0;
+        }
+        tmp2 = modmul(tmp, y2, a->curve->p);
+        freebn(tmp);
+        if (!tmp2) {
+            freebn(x2);
+            freebn(y2);
+            return 0;
+        }
+        tmp = modsub(y2, x2, a->curve->p);
+        freebn(y2);
+        freebn(x2);
+        if (!tmp) {
+            freebn(tmp2);
+            return 0;
+        }
+        tmp3 = modsub(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        if (!tmp3) {
+            return 0;
+        }
+        ret = !bignum_cmp(tmp3, One);
+        freebn(tmp3);
+        return ret;
+    } else if (a->curve->type == EC_WEIERSTRASS) {
         /* Verify y^2 = x^3 + ax + b */
         int ret = 0;
 
@@ -466,7 +654,7 @@ static int ec_point_verify(const struct ec_point *a)
         x3 = modpow(a->x, Three, a->curve->p);
         freebn(Three);
         if (!x3) goto error;
-        ax = modmul(a->curve->a, a->x, a->curve->p);
+        ax = modmul(a->curve->w.a, a->x, a->curve->p);
         if (!ax) goto error;
         x3ax = bigadd(x3, ax);
         if (!x3ax) goto error;
@@ -475,7 +663,7 @@ static int ec_point_verify(const struct ec_point *a)
         x3axm = bigmod(x3ax, a->curve->p);
         if (!x3axm) goto error;
         freebn(x3ax); x3ax = NULL;
-        x3axb = bigadd(x3axm, a->curve->b);
+        x3axb = bigadd(x3axm, a->curve->w.b);
         if (!x3axb) goto error;
         freebn(x3axm); x3axm = NULL;
         rhs = bigmod(x3axb, a->curve->p);
@@ -496,6 +684,8 @@ static int ec_point_verify(const struct ec_point *a)
         if (x3axb) freebn(x3axb);
         if (lhs) freebn(lhs);
         return 0;
+    } else {
+        return 0;
     }
 }
 
@@ -506,74 +696,113 @@ static int ec_point_verify(const struct ec_point *a)
 /* Returns 1 on success and 0 on memory error */
 static int ecp_normalise(struct ec_point *a)
 {
-    Bignum Z2, Z2inv, Z3, Z3inv, tx, ty;
-
-    /* In Jacobian Coordinates the triple (X, Y, Z) represents
-       the affine point (X / Z^2, Y / Z^3) */
     if (!a) {
         /* No point */
         return 0;
     }
+
     if (a->infinity) {
         /* Point is at infinity - i.e. normalised */
         return 1;
-    } else if (!a->x || !a->y) {
-        /* No point defined */
-        return 0;
-    } else if (!a->z) {
-        /* Already normalised */
-        return 1;
     }
 
-    Z2 = ecf_square(a->z, a->curve);
-    if (!Z2) {
-        return 0;
-    }
-    Z2inv = modinv(Z2, a->curve->p);
-    if (!Z2inv) {
-        freebn(Z2);
-        return 0;
-    }
-    tx = modmul(a->x, Z2inv, a->curve->p);
-    freebn(Z2inv);
-    if (!tx) {
+    if (a->curve->type == EC_WEIERSTRASS) {
+        /* In Jacobian Coordinates the triple (X, Y, Z) represents
+           the affine point (X / Z^2, Y / Z^3) */
+
+        Bignum Z2, Z2inv, Z3, Z3inv, tx, ty;
+
+        if (!a->x || !a->y) {
+            /* No point defined */
+            return 0;
+        } else if (!a->z) {
+            /* Already normalised */
+            return 1;
+        }
+
+        Z2 = ecf_square(a->z, a->curve);
+        if (!Z2) {
+            return 0;
+        }
+        Z2inv = modinv(Z2, a->curve->p);
+        if (!Z2inv) {
+            freebn(Z2);
+            return 0;
+        }
+        tx = modmul(a->x, Z2inv, a->curve->p);
+        freebn(Z2inv);
+        if (!tx) {
+            freebn(Z2);
+            return 0;
+        }
+
+        Z3 = modmul(Z2, a->z, a->curve->p);
         freebn(Z2);
-        return 0;
-    }
+        if (!Z3) {
+            freebn(tx);
+            return 0;
+        }
+        Z3inv = modinv(Z3, a->curve->p);
+        freebn(Z3);
+        if (!Z3inv) {
+            freebn(tx);
+            return 0;
+        }
+        ty = modmul(a->y, Z3inv, a->curve->p);
+        freebn(Z3inv);
+        if (!ty) {
+            freebn(tx);
+            return 0;
+        }
 
-    Z3 = modmul(Z2, a->z, a->curve->p);
-    freebn(Z2);
-    if (!Z3) {
-        freebn(tx);
-        return 0;
-    }
-    Z3inv = modinv(Z3, a->curve->p);
-    freebn(Z3);
-    if (!Z3inv) {
-        freebn(tx);
-        return 0;
-    }
-    ty = modmul(a->y, Z3inv, a->curve->p);
-    freebn(Z3inv);
-    if (!ty) {
-        freebn(tx);
+        freebn(a->x);
+        a->x = tx;
+        freebn(a->y);
+        a->y = ty;
+        freebn(a->z);
+        a->z = NULL;
+        return 1;
+    } else if (a->curve->type == EC_MONTGOMERY) {
+        /* In Montgomery (X : Z) represents the x co-ord (X / Z, ?) */
+
+        Bignum tmp, tmp2;
+
+        if (!a->x) {
+            /* No point defined */
+            return 0;
+        } else if (!a->z) {
+            /* Already normalised */
+            return 1;
+        }
+
+        tmp = modinv(a->z, a->curve->p);
+        if (!tmp) {
+            return 0;
+        }
+        tmp2 = modmul(a->x, tmp, a->curve->p);
+        freebn(tmp);
+        if (!tmp2) {
+            return 0;
+        }
+
+        freebn(a->z);
+        a->z = NULL;
+        freebn(a->x);
+        a->x = tmp2;
+        return 1;
+    } else if (a->curve->type == EC_EDWARDS) {
+        /* Always normalised */
+        return 1;
+    } else {
         return 0;
     }
-
-    freebn(a->x);
-    a->x = tx;
-    freebn(a->y);
-    a->y = ty;
-    freebn(a->z);
-    a->z = NULL;
-    return 1;
 }
 
-static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
+static struct ec_point *ecp_doublew(const struct ec_point *a, const int aminus3)
 {
     Bignum S, M, outx, outy, outz;
 
-    if (a->infinity || bignum_cmp(a->y, Zero) == 0)
+    if (bignum_cmp(a->y, Zero) == 0)
     {
         /* Identity */
         return ec_point_new(a->curve, NULL, NULL, NULL, 1);
@@ -628,7 +857,7 @@ static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
         }
         XmZ2 = modsub(a->x, Z2, a->curve->p);
         freebn(Z2);
-        if (!XpZ2) {
+        if (!XmZ2) {
             freebn(S);
             freebn(XpZ2);
             return NULL;
@@ -653,7 +882,7 @@ static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
         Bignum _3X2, X2, aZ4;
 
         if (a->z == NULL) {
-            aZ4 = copybn(a->curve->a);
+            aZ4 = copybn(a->curve->w.a);
         } else {
             Bignum Z2, Z4;
 
@@ -668,7 +897,7 @@ static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
                 freebn(S);
                 return NULL;
             }
-            aZ4 = modmul(a->curve->a, Z4, a->curve->p);
+            aZ4 = modmul(a->curve->w.a, Z4, a->curve->p);
             freebn(Z4);
         }
         if (!aZ4) {
@@ -809,81 +1038,232 @@ static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
     return ec_point_new(a->curve, outx, outy, outz, 0);
 }
 
-static struct ec_point *ecp_add(const struct ec_point *a,
-                                const struct ec_point *b,
-                                const int aminus3)
+static struct ec_point *ecp_doublem(const struct ec_point *a)
 {
-    Bignum U1, U2, S1, S2, outx, outy, outz;
+    Bignum z, outx, outz, xpz, xmz;
 
-    /* Check if multiplying by infinity */
-    if (a->infinity) return ec_point_copy(b);
-    if (b->infinity) return ec_point_copy(a);
+    z = a->z;
+    if (!z) {
+        z = One;
+    }
 
-    /* U1 = X1*Z2^2 */
-    /* S1 = Y1*Z2^3 */
-    if (b->z) {
-        Bignum Z2, Z3;
+    /* 4xz = (x + z)^2 - (x - z)^2 */
+    {
+        Bignum tmp;
 
-        Z2 = ecf_square(b->z, a->curve);
-        if (!Z2) {
+        tmp = ecf_add(a->x, z, a->curve);
+        if (!tmp) {
             return NULL;
         }
-        U1 = modmul(a->x, Z2, a->curve->p);
-        if (!U1) {
-            freebn(Z2);
+        xpz = ecf_square(tmp, a->curve);
+        freebn(tmp);
+        if (!xpz) {
             return NULL;
         }
-        Z3 = modmul(Z2, b->z, a->curve->p);
-        freebn(Z2);
-        if (!Z3) {
-            freebn(U1);
+
+        tmp = modsub(a->x, z, a->curve->p);
+        if (!tmp) {
+            freebn(xpz);
             return NULL;
         }
-        S1 = modmul(a->y, Z3, a->curve->p);
-        freebn(Z3);
-        if (!S1) {
-            freebn(U1);
+        xmz = ecf_square(tmp, a->curve);
+        freebn(tmp);
+        if (!xmz) {
+            freebn(xpz);
             return NULL;
         }
-    } else {
-        U1 = copybn(a->x);
-        if (!U1) {
+    }
+
+    /* outx = (x + z)^2 * (x - z)^2 */
+    outx = modmul(xpz, xmz, a->curve->p);
+    if (!outx) {
+        freebn(xpz);
+        freebn(xmz);
+        return NULL;
+    }
+
+    /* outz = 4xz * ((x - z)^2 + ((A + 2) / 4)*4xz) */
+    {
+        Bignum _4xz, tmp, tmp2, tmp3;
+
+        tmp = bignum_from_long(2);
+        if (!tmp) {
+            freebn(xpz);
+            freebn(outx);
+            freebn(xmz);
             return NULL;
         }
-        S1 = copybn(a->y);
-        if (!S1) {
-            freebn(U1);
+        tmp2 = ecf_add(a->curve->m.a, tmp, a->curve);
+        freebn(tmp);
+        if (!tmp2) {
+            freebn(xpz);
+            freebn(outx);
+            freebn(xmz);
             return NULL;
         }
-    }
-
-    /* U2 = X2*Z1^2 */
-    /* S2 = Y2*Z1^3 */
-    if (a->z) {
-        Bignum Z2, Z3;
 
-        Z2 = ecf_square(a->z, b->curve);
-        if (!Z2) {
-            freebn(U1);
-            freebn(S1);
+        _4xz = modsub(xpz, xmz, a->curve->p);
+        freebn(xpz);
+        if (!_4xz) {
+            freebn(tmp2);
+            freebn(outx);
+            freebn(xmz);
             return NULL;
         }
-        U2 = modmul(b->x, Z2, b->curve->p);
-        if (!U2) {
-            freebn(U1);
-            freebn(S1);
-            freebn(Z2);
+        tmp = modmul(tmp2, _4xz, a->curve->p);
+        freebn(tmp2);
+        if (!tmp) {
+            freebn(_4xz);
+            freebn(outx);
+            freebn(xmz);
             return NULL;
         }
-        Z3 = modmul(Z2, a->z, b->curve->p);
-        freebn(Z2);
-        if (!Z3) {
-            freebn(U1);
-            freebn(S1);
-            freebn(U2);
+
+        tmp2 = bignum_from_long(4);
+        if (!tmp2) {
+            freebn(tmp);
+            freebn(_4xz);
+            freebn(outx);
+            freebn(xmz);
             return NULL;
         }
-        S2 = modmul(b->y, Z3, b->curve->p);
+        tmp3 = modinv(tmp2, a->curve->p);
+        freebn(tmp2);
+        if (!tmp3) {
+            freebn(tmp);
+            freebn(_4xz);
+            freebn(outx);
+            freebn(xmz);
+            return NULL;
+        }
+        tmp2 = modmul(tmp, tmp3, a->curve->p);
+        freebn(tmp);
+        freebn(tmp3);
+        if (!tmp2) {
+            freebn(_4xz);
+            freebn(outx);
+            freebn(xmz);
+            return NULL;
+        }
+
+        tmp = ecf_add(xmz, tmp2, a->curve);
+        freebn(xmz);
+        freebn(tmp2);
+        if (!tmp) {
+            freebn(_4xz);
+            freebn(outx);
+            return NULL;
+        }
+        outz = modmul(_4xz, tmp, a->curve->p);
+        freebn(_4xz);
+        freebn(tmp);
+        if (!outz) {
+            freebn(outx);
+            return NULL;
+        }
+    }
+
+    return ec_point_new(a->curve, outx, NULL, outz, 0);
+}
+
+/* Forward declaration for Edwards curve doubling */
+static struct ec_point *ecp_add(const struct ec_point *a,
+                                const struct ec_point *b,
+                                const int aminus3);
+
+static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
+{
+    if (a->infinity)
+    {
+        /* Identity */
+        return ec_point_new(a->curve, NULL, NULL, NULL, 1);
+    }
+
+    if (a->curve->type == EC_EDWARDS)
+    {
+        return ecp_add(a, a, aminus3);
+    }
+    else if (a->curve->type == EC_WEIERSTRASS)
+    {
+        return ecp_doublew(a, aminus3);
+    }
+    else
+    {
+        return ecp_doublem(a);
+    }
+}
+
+static struct ec_point *ecp_addw(const struct ec_point *a,
+                                 const struct ec_point *b,
+                                 const int aminus3)
+{
+    Bignum U1, U2, S1, S2, outx, outy, outz;
+
+    /* U1 = X1*Z2^2 */
+    /* S1 = Y1*Z2^3 */
+    if (b->z) {
+        Bignum Z2, Z3;
+
+        Z2 = ecf_square(b->z, a->curve);
+        if (!Z2) {
+            return NULL;
+        }
+        U1 = modmul(a->x, Z2, a->curve->p);
+        if (!U1) {
+            freebn(Z2);
+            return NULL;
+        }
+        Z3 = modmul(Z2, b->z, a->curve->p);
+        freebn(Z2);
+        if (!Z3) {
+            freebn(U1);
+            return NULL;
+        }
+        S1 = modmul(a->y, Z3, a->curve->p);
+        freebn(Z3);
+        if (!S1) {
+            freebn(U1);
+            return NULL;
+        }
+    } else {
+        U1 = copybn(a->x);
+        if (!U1) {
+            return NULL;
+        }
+        S1 = copybn(a->y);
+        if (!S1) {
+            freebn(U1);
+            return NULL;
+        }
+    }
+
+    /* U2 = X2*Z1^2 */
+    /* S2 = Y2*Z1^3 */
+    if (a->z) {
+        Bignum Z2, Z3;
+
+        Z2 = ecf_square(a->z, b->curve);
+        if (!Z2) {
+            freebn(U1);
+            freebn(S1);
+            return NULL;
+        }
+        U2 = modmul(b->x, Z2, b->curve->p);
+        if (!U2) {
+            freebn(U1);
+            freebn(S1);
+            freebn(Z2);
+            return NULL;
+        }
+        Z3 = modmul(Z2, a->z, b->curve->p);
+        freebn(Z2);
+        if (!Z3) {
+            freebn(U1);
+            freebn(S1);
+            freebn(U2);
+            return NULL;
+        }
+        S2 = modmul(b->y, Z3, b->curve->p);
         freebn(Z3);
         if (!S2) {
             freebn(U1);
@@ -1110,6 +1490,256 @@ static struct ec_point *ecp_add(const struct ec_point *a,
     return ec_point_new(a->curve, outx, outy, outz, 0);
 }
 
+static struct ec_point *ecp_addm(const struct ec_point *a,
+                                 const struct ec_point *b,
+                                 const struct ec_point *base)
+{
+    Bignum outx, outz, az, bz;
+
+    az = a->z;
+    if (!az) {
+        az = One;
+    }
+    bz = b->z;
+    if (!bz) {
+        bz = One;
+    }
+
+    /* a-b is maintained at 1 due to Montgomery ladder implementation */
+    /* Xa+b = Za-b * ((Xa - Za)*(Xb + Zb) + (Xa + Za)*(Xb - Zb))^2 */
+    /* Za+b = Xa-b * ((Xa - Za)*(Xb + Zb) - (Xa + Za)*(Xb - Zb))^2 */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        /* (Xa + Za) * (Xb - Zb) */
+        tmp = ecf_add(a->x, az, a->curve);
+        if (!tmp) {
+            return NULL;
+        }
+        tmp2 = modsub(b->x, bz, a->curve->p);
+        if (!tmp2) {
+            freebn(tmp);
+            return NULL;
+        }
+        tmp3 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        if (!tmp3) {
+            return NULL;
+        }
+
+        /* (Xa - Za) * (Xb + Zb) */
+        tmp = modsub(a->x, az, a->curve->p);
+        if (!tmp) {
+            freebn(tmp3);
+            return NULL;
+        }
+        tmp2 = ecf_add(b->x, bz, a->curve);
+        if (!tmp2) {
+            freebn(tmp);
+            freebn(tmp3);
+            return NULL;
+        }
+        tmp4 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        if (!tmp4) {
+            freebn(tmp3);
+            return NULL;
+        }
+
+        tmp = ecf_add(tmp3, tmp4, a->curve);
+        if (!tmp) {
+            freebn(tmp3);
+            freebn(tmp4);
+            return NULL;
+        }
+        outx = ecf_square(tmp, a->curve);
+        freebn(tmp);
+        if (!outx) {
+            freebn(tmp3);
+            freebn(tmp4);
+            return NULL;
+        }
+
+        tmp = modsub(tmp3, tmp4, a->curve->p);
+        freebn(tmp3);
+        freebn(tmp4);
+        if (!tmp) {
+            freebn(outx);
+            return NULL;
+        }
+        tmp2 = ecf_square(tmp, a->curve);
+        freebn(tmp);
+        if (!tmp2) {
+            freebn(outx);
+            return NULL;
+        }
+        outz = modmul(base->x, tmp2, a->curve->p);
+        freebn(tmp2);
+        if (!outz) {
+            freebn(outx);
+            return NULL;
+        }
+    }
+
+    return ec_point_new(a->curve, outx, NULL, outz, 0);
+}
+
+static struct ec_point *ecp_adde(const struct ec_point *a,
+                                 const struct ec_point *b)
+{
+    Bignum outx, outy, dmul;
+
+    /* outx = (a->x * b->y + b->x * a->y) /
+     *        (1 + a->curve->e.d * a->x * b->x * a->y * b->y) */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        tmp = modmul(a->x, b->y, a->curve->p);
+        if (!tmp)
+        {
+            return NULL;
+        }
+        tmp2 = modmul(b->x, a->y, a->curve->p);
+        if (!tmp2)
+        {
+            freebn(tmp);
+            return NULL;
+        }
+        tmp3 = ecf_add(tmp, tmp2, a->curve);
+        if (!tmp3)
+        {
+            freebn(tmp);
+            freebn(tmp2);
+            return NULL;
+        }
+
+        tmp4 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        if (!tmp4)
+        {
+            freebn(tmp3);
+            return NULL;
+        }
+        dmul = modmul(a->curve->e.d, tmp4, a->curve->p);
+        freebn(tmp4);
+        if (!dmul) {
+            freebn(tmp3);
+            return NULL;
+        }
+
+        tmp = ecf_add(One, dmul, a->curve);
+        if (!tmp)
+        {
+            freebn(tmp3);
+            freebn(dmul);
+            return NULL;
+        }
+        tmp2 = modinv(tmp, a->curve->p);
+        freebn(tmp);
+        if (!tmp2)
+        {
+            freebn(tmp3);
+            freebn(dmul);
+            return NULL;
+        }
+
+        outx = modmul(tmp3, tmp2, a->curve->p);
+        freebn(tmp3);
+        freebn(tmp2);
+        if (!outx)
+        {
+            freebn(dmul);
+            return NULL;
+        }
+    }
+
+    /* outy = (a->y * b->y + a->x * b->x) /
+     *        (1 - a->curve->e.d * a->x * b->x * a->y * b->y) */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        tmp = modsub(One, dmul, a->curve->p);
+        freebn(dmul);
+        if (!tmp)
+        {
+            freebn(outx);
+            return NULL;
+        }
+
+        tmp2 = modinv(tmp, a->curve->p);
+        freebn(tmp);
+        if (!tmp2)
+        {
+            freebn(outx);
+            return NULL;
+        }
+
+        tmp = modmul(a->y, b->y, a->curve->p);
+        if (!tmp)
+        {
+            freebn(tmp2);
+            freebn(outx);
+            return NULL;
+        }
+        tmp3 = modmul(a->x, b->x, a->curve->p);
+        if (!tmp3)
+        {
+            freebn(tmp);
+            freebn(tmp2);
+            freebn(outx);
+            return NULL;
+        }
+        tmp4 = ecf_add(tmp, tmp3, a->curve);
+        freebn(tmp);
+        freebn(tmp3);
+        if (!tmp4)
+        {
+            freebn(tmp2);
+            freebn(outx);
+            return NULL;
+        }
+
+        outy = modmul(tmp4, tmp2, a->curve->p);
+        freebn(tmp4);
+        freebn(tmp2);
+        if (!outy)
+        {
+            freebn(outx);
+            return NULL;
+        }
+    }
+
+    return ec_point_new(a->curve, outx, outy, NULL, 0);
+}
+
+static struct ec_point *ecp_add(const struct ec_point *a,
+                                const struct ec_point *b,
+                                const int aminus3)
+{
+    if (a->curve != b->curve) {
+        return NULL;
+    }
+
+    /* Check if multiplying by infinity */
+    if (a->infinity) return ec_point_copy(b);
+    if (b->infinity) return ec_point_copy(a);
+
+    if (a->curve->type == EC_EDWARDS)
+    {
+        return ecp_adde(a, b);
+    }
+
+    if (a->curve->type == EC_WEIERSTRASS)
+    {
+        return ecp_addw(a, b, aminus3);
+    }
+
+    return NULL;
+}
+
 static struct ec_point *ecp_mul_(const struct ec_point *a, const Bignum b, int aminus3)
 {
     struct ec_point *A, *ret;
@@ -1145,8 +1775,7 @@ static struct ec_point *ecp_mul_(const struct ec_point *a, const Bignum b, int a
     return ret;
 }
 
-/* Not static because it is used by sshecdsag.c to generate a new key */
-struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b)
+static struct ec_point *ecp_mulw(const struct ec_point *a, const Bignum b)
 {
     struct ec_point *ret = ecp_mul_(a, b, ec_aminus3(a->curve));
 
@@ -1158,13 +1787,117 @@ struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b)
     return ret;
 }
 
+static struct ec_point *ecp_mule(const struct ec_point *a, const Bignum b)
+{
+    int i;
+    struct ec_point *ret;
+
+    ret = ec_point_new(a->curve, NULL, NULL, NULL, 1);
+
+    for (i = bignum_bitcount(b); i >= 0 && ret; --i)
+    {
+        {
+            struct ec_point *tmp = ecp_double(ret, 0);
+            ec_point_free(ret);
+            ret = tmp;
+        }
+        if (ret && bignum_bit(b, i))
+        {
+            struct ec_point *tmp = ecp_add(ret, a, 0);
+            ec_point_free(ret);
+            ret = tmp;
+        }
+    }
+
+    return ret;
+}
+
+static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n)
+{
+    struct ec_point *P1, *P2;
+    int bits, i;
+
+    /* P1 <- P and P2 <- [2]P */
+    P2 = ecp_double(p, 0);
+    if (!P2) {
+        return NULL;
+    }
+    P1 = ec_point_copy(p);
+    if (!P1) {
+        ec_point_free(P2);
+        return NULL;
+    }
+
+    /* for i = bits âˆ’ 2 down to 0 */
+    bits = bignum_bitcount(n);
+    for (i = bits - 2; P1 != NULL && P2 != NULL && i >= 0; --i)
+    {
+        if (!bignum_bit(n, i))
+        {
+            /* P2 <- P1 + P2 */
+            struct ec_point *tmp = ecp_addm(P1, P2, p);
+            ec_point_free(P2);
+            P2 = tmp;
+
+            /* P1 <- [2]P1 */
+            tmp = ecp_double(P1, 0);
+            ec_point_free(P1);
+            P1 = tmp;
+        }
+        else
+        {
+            /* P1 <- P1 + P2 */
+            struct ec_point *tmp = ecp_addm(P1, P2, p);
+            ec_point_free(P1);
+            P1 = tmp;
+
+            /* P2 <- [2]P2 */
+            tmp = ecp_double(P2, 0);
+            ec_point_free(P2);
+            P2 = tmp;
+        }
+    }
+
+    if (!P2) {
+        if (P1) ec_point_free(P1);
+        P1 = NULL;
+    } else {
+        ec_point_free(P2);
+    }
+
+    if (!ecp_normalise(P1)) {
+        ec_point_free(P1);
+        return NULL;
+    }
+
+    return P1;
+}
+
+/* Not static because it is used by sshecdsag.c to generate a new key */
+struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b)
+{
+    if (a->curve->type == EC_WEIERSTRASS) {
+        return ecp_mulw(a, b);
+    } else if (a->curve->type == EC_EDWARDS) {
+        return ecp_mule(a, b);
+    } else {
+        return ecp_mulm(a, b);
+    }
+}
+
 static struct ec_point *ecp_summul(const Bignum a, const Bignum b,
                                    const struct ec_point *point)
 {
     struct ec_point *aG, *bP, *ret;
-    int aminus3 = ec_aminus3(point->curve);
+    int aminus3;
 
-    aG = ecp_mul_(&point->curve->G, a, aminus3);
+    if (point->curve->type != EC_WEIERSTRASS) {
+        return NULL;
+    }
+
+    aminus3 = ec_aminus3(point->curve);
+
+    aG = ecp_mul_(&point->curve->w.G, a, aminus3);
     if (!aG) return NULL;
     bP = ecp_mul_(point, b, aminus3);
     if (!bP) {
@@ -1184,6 +1917,192 @@ static struct ec_point *ecp_summul(const Bignum a, const Bignum b,
 
     return ret;
 }
+static Bignum *ecp_edx(const struct ec_curve *curve, const Bignum y)
+{
+    /* Get the x value on the given Edwards curve for a given y */
+    Bignum x, xx;
+
+    /* xx = (y^2 - 1) / (d * y^2 + 1) */
+    {
+        Bignum tmp, tmp2, tmp3;
+
+        tmp = ecf_square(y, curve);
+        if (!tmp) {
+            return NULL;
+        }
+        tmp2 = modmul(curve->e.d, tmp, curve->p);
+        if (!tmp2) {
+            freebn(tmp);
+            return NULL;
+        }
+        tmp3 = ecf_add(tmp2, One, curve);
+        freebn(tmp2);
+        if (!tmp3) {
+            freebn(tmp);
+            return NULL;
+        }
+        tmp2 = modinv(tmp3, curve->p);
+        freebn(tmp3);
+        if (!tmp2) {
+            freebn(tmp);
+            return NULL;
+        }
+
+        tmp3 = modsub(tmp, One, curve->p);
+        freebn(tmp);
+        if (!tmp3) {
+            freebn(tmp2);
+            return NULL;
+        }
+        xx = modmul(tmp3, tmp2, curve->p);
+        freebn(tmp3);
+        freebn(tmp2);
+        if (!xx) {
+            return NULL;
+        }
+    }
+
+    /* x = xx^((p + 3) / 8) */
+    {
+        Bignum tmp, tmp2;
+
+        tmp = bignum_add_long(curve->p, 3);
+        if (!tmp) {
+            freebn(xx);
+            return NULL;
+        }
+        tmp2 = bignum_rshift(tmp, 3);
+        freebn(tmp);
+        if (!tmp2) {
+            freebn(xx);
+            return NULL;
+        }
+        x = modpow(xx, tmp2, curve->p);
+        freebn(tmp2);
+        if (!x) {
+            freebn(xx);
+            return NULL;
+        }
+    }
+
+    /* if x^2 - xx != 0 then x = x*(2^((p - 1) / 4)) */
+    {
+        Bignum tmp, tmp2;
+
+        tmp = ecf_square(x, curve);
+        if (!tmp) {
+            freebn(x);
+            freebn(xx);
+            return NULL;
+        }
+        tmp2 = modsub(tmp, xx, curve->p);
+        freebn(tmp);
+        freebn(xx);
+        if (!tmp2) {
+            freebn(x);
+            return NULL;
+        }
+        if (bignum_cmp(tmp2, Zero)) {
+            Bignum tmp3;
+
+            freebn(tmp2);
+
+            tmp = modsub(curve->p, One, curve->p);
+            if (!tmp) {
+                freebn(x);
+                return NULL;
+            }
+            tmp2 = bignum_rshift(tmp, 2);
+            freebn(tmp);
+            if (!tmp2) {
+                freebn(x);
+                return NULL;
+            }
+            tmp = bignum_from_long(2);
+            if (!tmp) {
+                freebn(tmp2);
+                freebn(x);
+                return NULL;
+            }
+            tmp3 = modpow(tmp, tmp2, curve->p);
+            freebn(tmp);
+            freebn(tmp2);
+            if (!tmp3) {
+                freebn(x);
+                return NULL;
+            }
+
+            tmp = modmul(x, tmp3, curve->p);
+            freebn(x);
+            freebn(tmp3);
+            x = tmp;
+            if (!tmp) {
+                return NULL;
+            }
+        } else {
+            freebn(tmp2);
+        }
+    }
+
+    /* if x % 2 != 0 then x = p - x */
+    if (bignum_bit(x, 0)) {
+        Bignum tmp = modsub(curve->p, x, curve->p);
+        freebn(x);
+        x = tmp;
+        if (!tmp) {
+            return NULL;
+        }
+    }
+
+    return x;
+}
+
+/* ----------------------------------------------------------------------
+ * Public point from private
+ */
+
+struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve)
+{
+    if (curve->type == EC_WEIERSTRASS) {
+        return ecp_mul(&curve->w.G, privateKey);
+    } else if (curve->type == EC_EDWARDS) {
+        /* hash = H(sk) (where hash creates 2 * fieldBits)
+         * b = fieldBits
+         * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2
+         * publicKey = aB */
+        struct ec_point *ret;
+        unsigned char hash[512/8];
+        Bignum a;
+        int i, keylen;
+        SHA512_State s;
+        SHA512_Init(&s);
+
+        keylen = curve->fieldBits / 8;
+        for (i = 0; i < keylen; ++i) {
+            unsigned char b = bignum_byte(privateKey, i);
+            SHA512_Bytes(&s, &b, 1);
+        }
+        SHA512_Final(&s, hash);
+
+        /* The second part is simply turning the hash into a Bignum,
+         * however the 2^(b-2) bit *must* be set, and the bottom 3
+         * bits *must* not be */
+        hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */
+        hash[31] &= 0x7f; /* Unset above (b-2) */
+        hash[31] |= 0x40; /* Set 2^(b-2) */
+        /* Chop off the top part and convert to int */
+        a = bignum_from_bytes_le(hash, 32);
+        if (!a) {
+            return NULL;
+        }
+
+        ret = ecp_mul(&curve->e.B, a);
+        freebn(a);
+        return ret;
+    } else {
+        return NULL;
+    }
+}
 
 /* ----------------------------------------------------------------------
  * Basic sign and verify routines
@@ -1197,9 +2116,13 @@ static int _ecdsa_verify(const struct ec_point *publicKey,
     Bignum z;
     int valid = 0;
 
+    if (publicKey->curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
+
     /* Sanity checks */
-    if (bignum_cmp(r, Zero) == 0 || bignum_cmp(r, publicKey->curve->n) >= 0
-        || bignum_cmp(s, Zero) == 0 || bignum_cmp(s, publicKey->curve->n) >= 0)
+    if (bignum_cmp(r, Zero) == 0 || bignum_cmp(r, publicKey->curve->w.n) >= 0
+        || bignum_cmp(s, Zero) == 0 || bignum_cmp(s, publicKey->curve->w.n) >= 0)
     {
         return 0;
     }
@@ -1207,7 +2130,7 @@ static int _ecdsa_verify(const struct ec_point *publicKey,
     /* z = left most bitlen(curve->n) of data */
     z = bignum_from_bytes(data, dataLen);
     if (!z) return 0;
-    n_bits = bignum_bitcount(publicKey->curve->n);
+    n_bits = bignum_bitcount(publicKey->curve->w.n);
     z_bits = bignum_bitcount(z);
     if (z_bits > n_bits)
     {
@@ -1219,7 +2142,7 @@ static int _ecdsa_verify(const struct ec_point *publicKey,
 
     /* Ensure z in range of n */
     {
-        Bignum tmp = bigmod(z, publicKey->curve->n);
+        Bignum tmp = bigmod(z, publicKey->curve->w.n);
         freebn(z);
         z = tmp;
         if (!z) return 0;
@@ -1230,18 +2153,18 @@ static int _ecdsa_verify(const struct ec_point *publicKey,
         Bignum w, x, u1, u2;
         struct ec_point *tmp;
 
-        w = modinv(s, publicKey->curve->n);
+        w = modinv(s, publicKey->curve->w.n);
         if (!w) {
             freebn(z);
             return 0;
         }
-        u1 = modmul(z, w, publicKey->curve->n);
+        u1 = modmul(z, w, publicKey->curve->w.n);
         if (!u1) {
             freebn(z);
             freebn(w);
             return 0;
         }
-        u2 = modmul(r, w, publicKey->curve->n);
+        u2 = modmul(r, w, publicKey->curve->w.n);
         freebn(w);
         if (!u2) {
             freebn(z);
@@ -1257,7 +2180,7 @@ static int _ecdsa_verify(const struct ec_point *publicKey,
             return 0;
         }
 
-        x = bigmod(tmp->x, publicKey->curve->n);
+        x = bigmod(tmp->x, publicKey->curve->w.n);
         ec_point_free(tmp);
         if (!x) {
             freebn(z);
@@ -1285,10 +2208,14 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
     *r = NULL;
     *s = NULL;
 
+    if (curve->type != EC_WEIERSTRASS) {
+        return;
+    }
+
     /* z = left most bitlen(curve->n) of data */
     z = bignum_from_bytes(data, dataLen);
     if (!z) return;
-    n_bits = bignum_bitcount(curve->n);
+    n_bits = bignum_bitcount(curve->w.n);
     z_bits = bignum_bitcount(z);
     if (z_bits > n_bits)
     {
@@ -1302,11 +2229,11 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
     /* Generate k between 1 and curve->n, using the same deterministic
      * k generation system we use for conventional DSA. */
     SHA_Simple(data, dataLen, digest);
-    k = dss_gen_k("ECDSA deterministic k generator", curve->n, privateKey,
+    k = dss_gen_k("ECDSA deterministic k generator", curve->w.n, privateKey,
                   digest, sizeof(digest));
     if (!k) return;
 
-    kG = ecp_mul(&curve->G, k);
+    kG = ecp_mul(&curve->w.G, k);
     if (!kG) {
         freebn(z);
         freebn(k);
@@ -1314,7 +2241,7 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
     }
 
     /* r = kG.x mod n */
-    *r = bigmod(kG->x, curve->n);
+    *r = bigmod(kG->x, curve->w.n);
     ec_point_free(kG);
     if (!*r) {
         freebn(z);
@@ -1325,14 +2252,14 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
     /* s = (z + r * priv)/k mod n */
     {
         Bignum rPriv, zMod, first, firstMod, kInv;
-        rPriv = modmul(*r, privateKey, curve->n);
+        rPriv = modmul(*r, privateKey, curve->w.n);
         if (!rPriv) {
             freebn(*r);
             freebn(z);
             freebn(k);
             return;
         }
-        zMod = bigmod(z, curve->n);
+        zMod = bigmod(z, curve->w.n);
         freebn(z);
         if (!zMod) {
             freebn(rPriv);
@@ -1348,21 +2275,21 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
             freebn(k);
             return;
         }
-        firstMod = bigmod(first, curve->n);
+        firstMod = bigmod(first, curve->w.n);
         freebn(first);
         if (!firstMod) {
             freebn(*r);
             freebn(k);
             return;
         }
-        kInv = modinv(k, curve->n);
+        kInv = modinv(k, curve->w.n);
         freebn(k);
         if (!kInv) {
             freebn(firstMod);
             freebn(*r);
             return;
         }
-        *s = modmul(firstMod, kInv, curve->n);
+        *s = modmul(firstMod, kInv, curve->w.n);
         freebn(firstMod);
         freebn(kInv);
         if (!*s) {
@@ -1376,7 +2303,8 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
  * Misc functions
  */
 
-static void getstring(char **data, int *datalen, char **p, int *length)
+static void getstring(const char **data, int *datalen,
+                      const char **p, int *length)
 {
     *p = NULL;
     if (*datalen < 4)
@@ -1393,9 +2321,9 @@ static void getstring(char **data, int *datalen, char **p, int *length)
     *datalen -= *length;
 }
 
-static Bignum getmp(char **data, int *datalen)
+static Bignum getmp(const char **data, int *datalen)
 {
-    char *p;
+    const char *p;
     int length;
 
     getstring(data, datalen, &p, &length);
@@ -1406,8 +2334,71 @@ static Bignum getmp(char **data, int *datalen)
     return bignum_from_bytes((unsigned char *)p, length);
 }
 
-static int decodepoint(char *p, int length, struct ec_point *point)
+static Bignum getmp_le(const char **data, int *datalen)
+{
+    const char *p;
+    int length;
+
+    getstring(data, datalen, &p, &length);
+    if (!p)
+        return NULL;
+    return bignum_from_bytes_le((const unsigned char *)p, length);
+}
+
+static int decodepoint_ed(const char *p, int length, struct ec_point *point)
+{
+    /* Got some conversion to do, first read in the y co-ord */
+    int negative;
+
+    point->y = bignum_from_bytes_le((const unsigned char*)p, length);
+    if (!point->y) {
+        return 0;
+    }
+    if ((unsigned)bignum_bitcount(point->y) > point->curve->fieldBits) {
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+    /* Read x bit and then reset it */
+    negative = bignum_bit(point->y, point->curve->fieldBits - 1);
+    bignum_set_bit(point->y, point->curve->fieldBits - 1, 0);
+
+    /* Get the x from the y */
+    point->x = ecp_edx(point->curve, point->y);
+    if (!point->x) {
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+    if (negative) {
+        Bignum tmp = modsub(point->curve->p, point->x, point->curve->p);
+        freebn(point->x);
+        point->x = tmp;
+        if (!tmp) {
+            freebn(point->y);
+            point->y = NULL;
+            return 0;
+        }
+    }
+
+    /* Verify the point is on the curve */
+    if (!ec_point_verify(point)) {
+        freebn(point->x);
+        point->x = NULL;
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+
+    return 1;
+}
+
+static int decodepoint(const char *p, int length, struct ec_point *point)
 {
+    if (point->curve->type == EC_EDWARDS) {
+        return decodepoint_ed(p, length, point);
+    }
+
     if (length < 1 || p[0] != 0x04) /* Only support uncompressed point */
         return 0;
     /* Skip compression flag */
@@ -1421,10 +2412,10 @@ static int decodepoint(char *p, int length, struct ec_point *point)
         return 0;
     }
     length = length / 2;
-    point->x = bignum_from_bytes((unsigned char *)p, length);
+    point->x = bignum_from_bytes((const unsigned char *)p, length);
     if (!point->x) return 0;
     p += length;
-    point->y = bignum_from_bytes((unsigned char *)p, length);
+    point->y = bignum_from_bytes((const unsigned char *)p, length);
     if (!point->y) {
         freebn(point->x);
         point->x = NULL;
@@ -1434,16 +2425,19 @@ static int decodepoint(char *p, int length, struct ec_point *point)
 
     /* Verify the point is on the curve */
     if (!ec_point_verify(point)) {
-        ec_point_free(point);
+        freebn(point->x);
+        point->x = NULL;
+        freebn(point->y);
+        point->y = NULL;
         return 0;
     }
 
     return 1;
 }
 
-static int getmppoint(char **data, int *datalen, struct ec_point *point)
+static int getmppoint(const char **data, int *datalen, struct ec_point *point)
 {
-    char *p;
+    const char *p;
     int length;
 
     getstring(data, datalen, &p, &length);
@@ -1455,6 +2449,14 @@ static int getmppoint(char **data, int *datalen, struct ec_point *point)
  * Exposed ECDSA interface
  */
 
+struct ecsign_extra {
+    struct ec_curve *(*curve)(void);
+
+    /* These fields are used by the OpenSSH PEM format importer/exporter */
+    const unsigned char *oid;
+    int oidlen;
+};
+
 static void ecdsa_freekey(void *key)
 {
     struct ec_key *ec = (struct ec_key *) key;
@@ -1471,27 +2473,33 @@ static void ecdsa_freekey(void *key)
     sfree(ec);
 }
 
-static void *ecdsa_newkey(char *data, int len)
+static void *ecdsa_newkey(const struct ssh_signkey *self,
+                          const char *data, int len)
 {
-    char *p;
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)self->extra;
+    const char *p;
     int slen;
     struct ec_key *ec;
     struct ec_curve *curve;
 
     getstring(&data, &len, &p, &slen);
 
-    if (!p || slen < 11 || memcmp(p, "ecdsa-sha2-", 11)) {
+    if (!p) {
         return NULL;
     }
-    curve = ec_name_to_curve(p+11, slen-11);
-    if (!curve) return NULL;
+    curve = extra->curve();
+    assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS);
 
-    getstring(&data, &len, &p, &slen);
-
-    if (curve != ec_name_to_curve(p, slen)) return NULL;
+    /* Curve name is duplicated for Weierstrass form */
+    if (curve->type == EC_WEIERSTRASS) {
+        getstring(&data, &len, &p, &slen);
+        if (!match_ssh_id(slen, p, curve->name)) return NULL;
+    }
 
     ec = snew(struct ec_key);
 
+    ec->signalg = self;
     ec->publicKey.curve = curve;
     ec->publicKey.infinity = 0;
     ec->publicKey.x = NULL;
@@ -1523,17 +2531,17 @@ static char *ecdsa_fmtkey(void *key)
     if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve)
         return NULL;
 
-    pos = ec_curve_to_name(ec->publicKey.curve, NULL, 0);
-    if (pos == 0) return NULL;
-
     len = 4 + 2 + 1;                  /* 2 x "0x", punctuation, \0 */
-    len += pos; /* Curve name */
+    if (ec->publicKey.curve->name)
+        len += strlen(ec->publicKey.curve->name); /* Curve name */
     len += 4 * (bignum_bitcount(ec->publicKey.x) + 15) / 16;
     len += 4 * (bignum_bitcount(ec->publicKey.y) + 15) / 16;
     p = snewn(len, char);
 
-    pos = ec_curve_to_name(ec->publicKey.curve, (unsigned char*)p, pos);
-    pos += sprintf(p + pos, ",0x");
+    pos = 0;
+    if (ec->publicKey.curve->name)
+        pos += sprintf(p + pos, "%s,", ec->publicKey.curve->name);
+    pos += sprintf(p + pos, "0x");
     nibbles = (3 + bignum_bitcount(ec->publicKey.x)) / 4;
     if (nibbles < 1)
         nibbles = 1;
@@ -1556,37 +2564,72 @@ static char *ecdsa_fmtkey(void *key)
 static unsigned char *ecdsa_public_blob(void *key, int *len)
 {
     struct ec_key *ec = (struct ec_key *) key;
-    int pointlen, bloblen, namelen;
+    int pointlen, bloblen, fullnamelen, namelen;
     int i;
     unsigned char *blob, *p;
 
-    namelen = ec_curve_to_name(ec->publicKey.curve, NULL, 0);
-    if (namelen == 0) return NULL;
-
-    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
-
-    /*
-     * string "ecdsa-sha2-<name>", string "<name>", 0x04 point x, y.
-     */
-    bloblen = 4 + 11 + namelen + 4 + namelen + 4 + 1 + (pointlen * 2);
-    blob = snewn(bloblen, unsigned char);
-
-    p = blob;
-    PUT_32BIT(p, 11 + namelen);
-    p += 4;
-    memcpy(p, "ecdsa-sha2-", 11);
-    p += 11;
-    p += ec_curve_to_name(ec->publicKey.curve, p, namelen);
-    PUT_32BIT(p, namelen);
-    p += 4;
-    p += ec_curve_to_name(ec->publicKey.curve, p, namelen);
-    PUT_32BIT(p, (2 * pointlen) + 1);
-    p += 4;
-    *p++ = 0x04;
-    for (i = pointlen; i--;)
-        *p++ = bignum_byte(ec->publicKey.x, i);
-    for (i = pointlen; i--;)
-        *p++ = bignum_byte(ec->publicKey.y, i);
+    fullnamelen = strlen(ec->signalg->name);
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Edwards compressed form "ssh-ed25519" point y[:-1] + x[0:1] */
+
+        pointlen = ec->publicKey.curve->fieldBits / 8;
+
+        /* Can't handle this in our loop */
+        if (pointlen < 2) return NULL;
+
+        bloblen = 4 + fullnamelen + 4 + pointlen;
+        blob = snewn(bloblen, unsigned char);
+        if (!blob) return NULL;
+
+        p = blob;
+        PUT_32BIT(p, fullnamelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, fullnamelen);
+        p += fullnamelen;
+        PUT_32BIT(p, pointlen);
+        p += 4;
+
+        /* Unset last bit of y and set first bit of x in its place */
+        for (i = 0; i < pointlen - 1; ++i) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+        /* Unset last bit of y and set first bit of x in its place */
+        *p = bignum_byte(ec->publicKey.y, i) & 0x7f;
+        *p++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+    } else if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        assert(ec->publicKey.curve->name);
+        namelen = strlen(ec->publicKey.curve->name);
+
+        pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+
+        /*
+         * string "ecdsa-sha2-<name>", string "<name>", 0x04 point x, y.
+         */
+        bloblen = 4 + fullnamelen + 4 + namelen + 4 + 1 + (pointlen * 2);
+        blob = snewn(bloblen, unsigned char);
+
+        p = blob;
+        PUT_32BIT(p, fullnamelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, fullnamelen);
+        p += fullnamelen;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->publicKey.curve->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, (2 * pointlen) + 1);
+        p += 4;
+        *p++ = 0x04;
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+    } else {
+        return NULL;
+    }
 
     assert(p == blob + bloblen);
     *len = bloblen;
@@ -1603,7 +2646,13 @@ static unsigned char *ecdsa_private_blob(void *key, int *len)
 
     if (!ec->privateKey) return NULL;
 
-    keylen = (bignum_bitcount(ec->privateKey) + 8) / 8;
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Unsigned */
+        keylen = (bignum_bitcount(ec->privateKey) + 7) / 8;
+    } else {
+        /* Signed */
+        keylen = (bignum_bitcount(ec->privateKey) + 8) / 8;
+    }
 
     /*
      * mpint privateKey. Total 4 + keylen.
@@ -1611,37 +2660,115 @@ static unsigned char *ecdsa_private_blob(void *key, int *len)
     bloblen = 4 + keylen;
     blob = snewn(bloblen, unsigned char);
 
-    p = blob;
-    PUT_32BIT(p, keylen);
-    p += 4;
-    for (i = keylen; i--;)
-        *p++ = bignum_byte(ec->privateKey, i);
+    p = blob;
+    PUT_32BIT(p, keylen);
+    p += 4;
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Little endian */
+        for (i = 0; i < keylen; ++i)
+            *p++ = bignum_byte(ec->privateKey, i);
+    } else {
+        for (i = keylen; i--;)
+            *p++ = bignum_byte(ec->privateKey, i);
+    }
+
+    assert(p == blob + bloblen);
+    *len = bloblen;
+    return blob;
+}
+
+static void *ecdsa_createkey(const struct ssh_signkey *self,
+                             const unsigned char *pub_blob, int pub_len,
+                             const unsigned char *priv_blob, int priv_len)
+{
+    struct ec_key *ec;
+    struct ec_point *publicKey;
+    const char *pb = (const char *) priv_blob;
+
+    ec = (struct ec_key*)ecdsa_newkey(self, (const char *) pub_blob, pub_len);
+    if (!ec) {
+        return NULL;
+    }
+
+    if (ec->publicKey.curve->type != EC_WEIERSTRASS
+        && ec->publicKey.curve->type != EC_EDWARDS) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        ec->privateKey = getmp_le(&pb, &priv_len);
+    } else {
+        ec->privateKey = getmp(&pb, &priv_len);
+    }
+    if (!ec->privateKey) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    /* Check that private key generates public key */
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
 
-    assert(p == blob + bloblen);
-    *len = bloblen;
-    return blob;
+    if (!publicKey ||
+        bignum_cmp(publicKey->x, ec->publicKey.x) ||
+        bignum_cmp(publicKey->y, ec->publicKey.y))
+    {
+        ecdsa_freekey(ec);
+        ec = NULL;
+    }
+    ec_point_free(publicKey);
+
+    return ec;
 }
 
-static void *ecdsa_createkey(unsigned char *pub_blob, int pub_len,
-                             unsigned char *priv_blob, int priv_len)
+static void *ed25519_openssh_createkey(const struct ssh_signkey *self,
+                                       const unsigned char **blob, int *len)
 {
     struct ec_key *ec;
     struct ec_point *publicKey;
-    char *pb = (char *) priv_blob;
+    const char *p, *q;
+    int plen, qlen;
 
-    ec = (struct ec_key*)ecdsa_newkey((char *) pub_blob, pub_len);
-    if (!ec) {
+    getstring((const char**)blob, len, &p, &plen);
+    if (!p)
+    {
+        return NULL;
+    }
+
+    ec = snew(struct ec_key);
+    if (!ec)
+    {
+        return NULL;
+    }
+
+    ec->signalg = self;
+    ec->publicKey.curve = ec_ed25519();
+    ec->publicKey.infinity = 0;
+    ec->privateKey = NULL;
+    ec->publicKey.x = NULL;
+    ec->publicKey.z = NULL;
+    ec->publicKey.y = NULL;
+
+    if (!decodepoint_ed(p, plen, &ec->publicKey))
+    {
+        ecdsa_freekey(ec);
         return NULL;
     }
 
-    ec->privateKey = getmp(&pb, &priv_len);
+    getstring((const char**)blob, len, &q, &qlen);
+    if (!q)
+        return NULL;
+    if (qlen != 64)
+        return NULL;
+
+    ec->privateKey = bignum_from_bytes_le((const unsigned char *)q, 32);
     if (!ec->privateKey) {
         ecdsa_freekey(ec);
         return NULL;
     }
 
     /* Check that private key generates public key */
-    publicKey = ecp_mul(&ec->publicKey.curve->G, ec->privateKey);
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
 
     if (!publicKey ||
         bignum_cmp(publicKey->x, ec->publicKey.x) ||
@@ -1652,13 +2779,76 @@ static void *ecdsa_createkey(unsigned char *pub_blob, int pub_len,
     }
     ec_point_free(publicKey);
 
+    /* The OpenSSH format for ed25519 private keys also for some
+     * reason encodes an extra copy of the public key in the second
+     * half of the secret-key string. Check that that's present and
+     * correct as well, otherwise the key we think we've imported
+     * won't behave identically to the way OpenSSH would have treated
+     * it. */
+    if (plen != 32 || 0 != memcmp(q + 32, p, 32)) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
     return ec;
 }
 
-static void *ecdsa_openssh_createkey(unsigned char **blob, int *len)
+static int ed25519_openssh_fmtkey(void *key, unsigned char *blob, int len)
 {
-    char **b = (char **) blob;
-    char *p;
+    struct ec_key *ec = (struct ec_key *) key;
+
+    int pointlen;
+    int keylen;
+    int bloblen;
+    int i;
+
+    if (ec->publicKey.curve->type != EC_EDWARDS) {
+        return 0;
+    }
+
+    pointlen = (bignum_bitcount(ec->publicKey.y) + 7) / 8;
+    keylen = (bignum_bitcount(ec->privateKey) + 7) / 8;
+    bloblen = 4 + pointlen + 4 + keylen + pointlen;
+
+    if (bloblen > len)
+        return bloblen;
+
+    /* Encode the public point */
+    PUT_32BIT(blob, pointlen);
+    blob += 4;
+
+    for (i = 0; i < pointlen - 1; ++i) {
+         *blob++ = bignum_byte(ec->publicKey.y, i);
+    }
+    /* Unset last bit of y and set first bit of x in its place */
+    *blob = bignum_byte(ec->publicKey.y, i) & 0x7f;
+    *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+
+    PUT_32BIT(blob, keylen + pointlen);
+    blob += 4;
+    for (i = 0; i < keylen; ++i) {
+         *blob++ = bignum_byte(ec->privateKey, i);
+    }
+    /* Now encode an extra copy of the public point as the second half
+     * of the private key string, as the OpenSSH format for some
+     * reason requires */
+    for (i = 0; i < pointlen - 1; ++i) {
+         *blob++ = bignum_byte(ec->publicKey.y, i);
+    }
+    /* Unset last bit of y and set first bit of x in its place */
+    *blob = bignum_byte(ec->publicKey.y, i) & 0x7f;
+    *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+
+    return bloblen;
+}
+
+static void *ecdsa_openssh_createkey(const struct ssh_signkey *self,
+                                     const unsigned char **blob, int *len)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)self->extra;
+    const char **b = (const char **) blob;
+    const char *p;
     int slen;
     struct ec_key *ec;
     struct ec_curve *curve;
@@ -1669,11 +2859,12 @@ static void *ecdsa_openssh_createkey(unsigned char **blob, int *len)
     if (!p) {
         return NULL;
     }
-    curve = ec_name_to_curve(p, slen);
-    if (!curve) return NULL;
+    curve = extra->curve();
+    assert(curve->type == EC_WEIERSTRASS);
 
     ec = snew(struct ec_key);
 
+    ec->signalg = self;
     ec->publicKey.curve = curve;
     ec->publicKey.infinity = 0;
     ec->publicKey.x = NULL;
@@ -1701,7 +2892,7 @@ static void *ecdsa_openssh_createkey(unsigned char **blob, int *len)
     }
 
     /* Now check that the private key makes the public key */
-    publicKey = ecp_mul(&ec->publicKey.curve->G, ec->privateKey);
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
     if (!publicKey)
     {
         ecdsa_freekey(ec);
@@ -1714,6 +2905,7 @@ static void *ecdsa_openssh_createkey(unsigned char **blob, int *len)
         /* Private key doesn't make the public key on the given curve */
         ecdsa_freekey(ec);
         ec_point_free(publicKey);
+        return NULL;
     }
 
     ec_point_free(publicKey);
@@ -1725,17 +2917,22 @@ static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len)
 {
     struct ec_key *ec = (struct ec_key *) key;
 
-    int pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+    int pointlen;
+    int namelen;
+    int bloblen;
+    int i;
 
-    int namelen = ec_curve_to_name(ec->publicKey.curve, NULL, 0);
+    if (ec->publicKey.curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
 
-    int bloblen =
+    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+    namelen = strlen(ec->publicKey.curve->name);
+    bloblen =
         4 + namelen /* <LEN> nistpXXX */
         + 4 + 1 + (pointlen * 2) /* <LEN> 0x04 pX pY */
         + ssh2_bignum_length(ec->privateKey);
 
-    int i;
-
     if (bloblen > len)
         return bloblen;
 
@@ -1743,8 +2940,8 @@ static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len)
 
     PUT_32BIT(blob+bloblen, namelen);
     bloblen += 4;
-
-    bloblen += ec_curve_to_name(ec->publicKey.curve, blob+bloblen, namelen);
+    memcpy(blob+bloblen, ec->publicKey.curve->name, namelen);
+    bloblen += namelen;
 
     PUT_32BIT(blob+bloblen, 1 + (pointlen * 2));
     bloblen += 4;
@@ -1763,12 +2960,13 @@ static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len)
     return bloblen;
 }
 
-static int ecdsa_pubkey_bits(void *blob, int len)
+static int ecdsa_pubkey_bits(const struct ssh_signkey *self,
+                             const void *blob, int len)
 {
     struct ec_key *ec;
     int ret;
 
-    ec = (struct ec_key*)ecdsa_newkey((char *) blob, len);
+    ec = (struct ec_key*)ecdsa_newkey(self, (const char *) blob, len);
     if (!ec)
         return -1;
     ret = ec->publicKey.curve->fieldBits;
@@ -1777,117 +2975,170 @@ static int ecdsa_pubkey_bits(void *blob, int len)
     return ret;
 }
 
-static char *ecdsa_fingerprint(void *key)
-{
-    struct ec_key *ec = (struct ec_key *) key;
-    struct MD5Context md5c;
-    unsigned char digest[16], lenbuf[4];
-    char *ret;
-    unsigned char *name;
-    int pointlen, namelen, i, j;
-
-    namelen = ec_curve_to_name(ec->publicKey.curve, NULL, 0);
-    name = snewn(namelen, unsigned char);
-    ec_curve_to_name(ec->publicKey.curve, name, namelen);
-
-    MD5Init(&md5c);
-
-    PUT_32BIT(lenbuf, namelen + 11);
-    MD5Update(&md5c, lenbuf, 4);
-    MD5Update(&md5c, (const unsigned char *)"ecdsa-sha2-", 11);
-    MD5Update(&md5c, name, namelen);
-
-    PUT_32BIT(lenbuf, namelen);
-    MD5Update(&md5c, lenbuf, 4);
-    MD5Update(&md5c, name, namelen);
-
-    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
-    PUT_32BIT(lenbuf, 1 + (pointlen * 2));
-    MD5Update(&md5c, lenbuf, 4);
-    MD5Update(&md5c, (const unsigned char *)"\x04", 1);
-    for (i = pointlen; i--; ) {
-        unsigned char c = bignum_byte(ec->publicKey.x, i);
-        MD5Update(&md5c, &c, 1);
-    }
-    for (i = pointlen; i--; ) {
-        unsigned char c = bignum_byte(ec->publicKey.y, i);
-        MD5Update(&md5c, &c, 1);
-    }
-
-    MD5Final(digest, &md5c);
-
-    ret = snewn(11 + namelen + 1 + (16 * 3), char);
-
-    i = 11;
-    memcpy(ret, "ecdsa-sha2-", 11);
-    memcpy(ret+i, name, namelen);
-    i += namelen;
-    sfree(name);
-    ret[i++] = ' ';
-    for (j = 0; j < 16; j++)
-        i += sprintf(ret + i, "%s%02x", j ? ":" : "", digest[j]);
-
-    return ret;
-}
-
-static int ecdsa_verifysig(void *key, char *sig, int siglen,
-                           char *data, int datalen)
+static int ecdsa_verifysig(void *key, const char *sig, int siglen,
+                           const char *data, int datalen)
 {
     struct ec_key *ec = (struct ec_key *) key;
-    char *p;
+    const char *p;
     int slen;
-    unsigned char digest[512 / 8];
     int digestLen;
-    Bignum r, s;
     int ret;
 
     if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve)
         return 0;
 
-    /* Check the signature curve matches the key curve */
+    /* Check the signature starts with the algorithm name */
     getstring(&sig, &siglen, &p, &slen);
-    if (!p || slen < 11 || memcmp(p, "ecdsa-sha2-", 11)) {
+    if (!p) {
         return 0;
     }
-    if (ec->publicKey.curve != ec_name_to_curve(p+11, slen-11)) {
+    if (!match_ssh_id(slen, p, ec->signalg->name)) {
         return 0;
     }
 
     getstring(&sig, &siglen, &p, &slen);
-    r = getmp(&p, &slen);
-    if (!r) return 0;
-    s = getmp(&p, &slen);
-    if (!s) {
-        freebn(r);
-        return 0;
-    }
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        struct ec_point *r;
+        Bignum s, h;
 
-    /* Perform correct hash function depending on curve size */
-    if (ec->publicKey.curve->fieldBits <= 256) {
-        SHA256_Simple(data, datalen, digest);
-        digestLen = 256 / 8;
-    } else if (ec->publicKey.curve->fieldBits <= 384) {
-        SHA384_Simple(data, datalen, digest);
-        digestLen = 384 / 8;
-    } else {
-        SHA512_Simple(data, datalen, digest);
-        digestLen = 512 / 8;
-    }
+        /* Check that the signature is two times the length of a point */
+        if (slen != (ec->publicKey.curve->fieldBits / 8) * 2) {
+            return 0;
+        }
+
+        /* Check it's the 256 bit field so that SHA512 is the correct hash */
+        if (ec->publicKey.curve->fieldBits != 256) {
+            return 0;
+        }
+
+        /* Get the signature */
+        r = ec_point_new(ec->publicKey.curve, NULL, NULL, NULL, 0);
+        if (!r) {
+            return 0;
+        }
+        if (!decodepoint(p, ec->publicKey.curve->fieldBits / 8, r)) {
+            ec_point_free(r);
+            return 0;
+        }
+        s = bignum_from_bytes_le((unsigned char*)p + (ec->publicKey.curve->fieldBits / 8),
+                                 ec->publicKey.curve->fieldBits / 8);
+        if (!s) {
+            ec_point_free(r);
+            return 0;
+        }
+
+        /* Get the hash of the encoded value of R + encoded value of pk + message */
+        {
+            int i, pointlen;
+            unsigned char b;
+            unsigned char digest[512 / 8];
+            SHA512_State hs;
+            SHA512_Init(&hs);
+
+            /* Add encoded r (no need to encode it again, it was in the signature) */
+            SHA512_Bytes(&hs, p, ec->publicKey.curve->fieldBits / 8);
+
+            /* Encode pk and add it */
+            pointlen = ec->publicKey.curve->fieldBits / 8;
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(ec->publicKey.y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(ec->publicKey.y, i) & 0x7f;
+            b |= bignum_bit(ec->publicKey.x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Add the message itself */
+            SHA512_Bytes(&hs, data, datalen);
+
+            /* Get the hash */
+            SHA512_Final(&hs, digest);
+
+            /* Convert to Bignum */
+            h = bignum_from_bytes_le(digest, sizeof(digest));
+            if (!h) {
+                ec_point_free(r);
+                freebn(s);
+                return 0;
+            }
+        }
+
+        /* Verify sB == r + h*publicKey */
+        {
+            struct ec_point *lhs, *rhs, *tmp;
+
+            /* lhs = sB */
+            lhs = ecp_mul(&ec->publicKey.curve->e.B, s);
+            freebn(s);
+            if (!lhs) {
+                ec_point_free(r);
+                freebn(h);
+                return 0;
+            }
+
+            /* rhs = r + h*publicKey */
+            tmp = ecp_mul(&ec->publicKey, h);
+            freebn(h);
+            if (!tmp) {
+                ec_point_free(lhs);
+                ec_point_free(r);
+                return 0;
+            }
+            rhs = ecp_add(r, tmp, 0);
+            ec_point_free(r);
+            ec_point_free(tmp);
+            if (!rhs) {
+                ec_point_free(lhs);
+                return 0;
+            }
 
-    /* Verify the signature */
-    if (!_ecdsa_verify(&ec->publicKey, digest, digestLen, r, s)) {
-        ret = 0;
+            /* Check the point is the same */
+            ret = !bignum_cmp(lhs->x, rhs->x);
+            if (ret) {
+                ret = !bignum_cmp(lhs->y, rhs->y);
+                if (ret) {
+                    ret = 1;
+                }
+            }
+            ec_point_free(lhs);
+            ec_point_free(rhs);
+        }
     } else {
-        ret = 1;
-    }
+        Bignum r, s;
+        unsigned char digest[512 / 8];
+
+        r = getmp(&p, &slen);
+        if (!r) return 0;
+        s = getmp(&p, &slen);
+        if (!s) {
+            freebn(r);
+            return 0;
+        }
+
+        /* Perform correct hash function depending on curve size */
+        if (ec->publicKey.curve->fieldBits <= 256) {
+            SHA256_Simple(data, datalen, digest);
+            digestLen = 256 / 8;
+        } else if (ec->publicKey.curve->fieldBits <= 384) {
+            SHA384_Simple(data, datalen, digest);
+            digestLen = 384 / 8;
+        } else {
+            SHA512_Simple(data, datalen, digest);
+            digestLen = 512 / 8;
+        }
 
-    freebn(r);
-    freebn(s);
+        /* Verify the signature */
+        ret = _ecdsa_verify(&ec->publicKey, digest, digestLen, r, s);
+
+        freebn(r);
+        freebn(s);
+    }
 
     return ret;
 }
 
-static unsigned char *ecdsa_sign(void *key, char *data, int datalen,
+static unsigned char *ecdsa_sign(void *key, const char *data, int datalen,
                                  int *siglen)
 {
     struct ec_key *ec = (struct ec_key *) key;
@@ -1902,54 +3153,231 @@ static unsigned char *ecdsa_sign(void *key, char *data, int datalen,
         return NULL;
     }
 
-    /* Perform correct hash function depending on curve size */
-    if (ec->publicKey.curve->fieldBits <= 256) {
-        SHA256_Simple(data, datalen, digest);
-        digestLen = 256 / 8;
-    } else if (ec->publicKey.curve->fieldBits <= 384) {
-        SHA384_Simple(data, datalen, digest);
-        digestLen = 384 / 8;
-    } else {
-        SHA512_Simple(data, datalen, digest);
-        digestLen = 512 / 8;
-    }
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        struct ec_point *rp;
+        int pointlen = ec->publicKey.curve->fieldBits / 8;
 
-    /* Do the signature */
-    _ecdsa_sign(ec->privateKey, ec->publicKey.curve, digest, digestLen, &r, &s);
-    if (!r || !s) {
-        if (r) freebn(r);
-        if (s) freebn(s);
-        return NULL;
-    }
+        /* hash = H(sk) (where hash creates 2 * fieldBits)
+         * b = fieldBits
+         * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2
+         * r = H(h[b/8:b/4] + m)
+         * R = rB
+         * S = (r + H(encodepoint(R) + encodepoint(pk) + m) * a) % l */
+        {
+            unsigned char hash[512/8];
+            unsigned char b;
+            Bignum a;
+            SHA512_State hs;
+            SHA512_Init(&hs);
+
+            for (i = 0; i < pointlen; ++i) {
+                unsigned char b = (unsigned char)bignum_byte(ec->privateKey, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+
+            SHA512_Final(&hs, hash);
+
+            /* The second part is simply turning the hash into a
+             * Bignum, however the 2^(b-2) bit *must* be set, and the
+             * bottom 3 bits *must* not be */
+            hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */
+            hash[31] &= 0x7f; /* Unset above (b-2) */
+            hash[31] |= 0x40; /* Set 2^(b-2) */
+            /* Chop off the top part and convert to int */
+            a = bignum_from_bytes_le(hash, 32);
+            if (!a) {
+                return NULL;
+            }
+
+            SHA512_Init(&hs);
+            SHA512_Bytes(&hs,
+                         hash+(ec->publicKey.curve->fieldBits / 8),
+                         (ec->publicKey.curve->fieldBits / 4)
+                         - (ec->publicKey.curve->fieldBits / 8));
+            SHA512_Bytes(&hs, data, datalen);
+            SHA512_Final(&hs, hash);
+
+            r = bignum_from_bytes_le(hash, 512/8);
+            if (!r) {
+                freebn(a);
+                return NULL;
+            }
+            rp = ecp_mul(&ec->publicKey.curve->e.B, r);
+            if (!rp) {
+                freebn(r);
+                freebn(a);
+                return NULL;
+            }
 
-    rlen = (bignum_bitcount(r) + 8) / 8;
-    slen = (bignum_bitcount(s) + 8) / 8;
+            /* Now calculate s */
+            SHA512_Init(&hs);
+            /* Encode the point R */
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(rp->y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(rp->y, i) & 0x7f;
+            b |= bignum_bit(rp->x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Encode the point pk */
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(ec->publicKey.y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(ec->publicKey.y, i) & 0x7f;
+            b |= bignum_bit(ec->publicKey.x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Add the message */
+            SHA512_Bytes(&hs, data, datalen);
+            SHA512_Final(&hs, hash);
+
+            {
+                Bignum tmp, tmp2;
+
+                tmp = bignum_from_bytes_le(hash, 512/8);
+                if (!tmp) {
+                    ec_point_free(rp);
+                    freebn(r);
+                    freebn(a);
+                    return NULL;
+                }
+                tmp2 = modmul(tmp, a, ec->publicKey.curve->e.l);
+                freebn(a);
+                freebn(tmp);
+                if (!tmp2) {
+                    ec_point_free(rp);
+                    freebn(r);
+                    return NULL;
+                }
+                tmp = bigadd(r, tmp2);
+                freebn(r);
+                freebn(tmp2);
+                if (!tmp) {
+                    ec_point_free(rp);
+                    return NULL;
+                }
+                s = bigmod(tmp, ec->publicKey.curve->e.l);
+                freebn(tmp);
+                if (!s) {
+                    ec_point_free(rp);
+                    return NULL;
+                }
+            }
+        }
 
-    namelen = ec_curve_to_name(ec->publicKey.curve, NULL, 0);
+        /* Format the output */
+        namelen = strlen(ec->signalg->name);
+        *siglen = 4+namelen+4+((ec->publicKey.curve->fieldBits / 8)*2);
+        buf = snewn(*siglen, unsigned char);
+        p = buf;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, ((ec->publicKey.curve->fieldBits / 8)*2));
+        p += 4;
+
+        /* Encode the point */
+        pointlen = ec->publicKey.curve->fieldBits / 8;
+        for (i = 0; i < pointlen - 1; ++i) {
+            *p++ = bignum_byte(rp->y, i);
+        }
+        /* Unset last bit of y and set first bit of x in its place */
+        *p = bignum_byte(rp->y, i) & 0x7f;
+        *p++ |= bignum_bit(rp->x, 0) << 7;
+        ec_point_free(rp);
+
+        /* Encode the int */
+        for (i = 0; i < pointlen; ++i) {
+            *p++ = bignum_byte(s, i);
+        }
+        freebn(s);
+    } else {
+        /* Perform correct hash function depending on curve size */
+        if (ec->publicKey.curve->fieldBits <= 256) {
+            SHA256_Simple(data, datalen, digest);
+            digestLen = 256 / 8;
+        } else if (ec->publicKey.curve->fieldBits <= 384) {
+            SHA384_Simple(data, datalen, digest);
+            digestLen = 384 / 8;
+        } else {
+            SHA512_Simple(data, datalen, digest);
+            digestLen = 512 / 8;
+        }
 
-    /* Format the output */
-    *siglen = 8+11+namelen+rlen+slen+8;
-    buf = snewn(*siglen, unsigned char);
-    p = buf;
-    PUT_32BIT(p, 11+namelen);
-    p += 4;
-    memcpy(p, "ecdsa-sha2-", 11);
-    p += 11;
-    p += ec_curve_to_name(ec->publicKey.curve, p, namelen);
-    PUT_32BIT(p, rlen + slen + 8);
-    p += 4;
-    PUT_32BIT(p, rlen);
-    p += 4;
-    for (i = rlen; i--;)
-        *p++ = bignum_byte(r, i);
-    PUT_32BIT(p, slen);
-    p += 4;
-    for (i = slen; i--;)
-        *p++ = bignum_byte(s, i);
+        /* Do the signature */
+        _ecdsa_sign(ec->privateKey, ec->publicKey.curve, digest, digestLen, &r, &s);
+        if (!r || !s) {
+            if (r) freebn(r);
+            if (s) freebn(s);
+            return NULL;
+        }
+
+        rlen = (bignum_bitcount(r) + 8) / 8;
+        slen = (bignum_bitcount(s) + 8) / 8;
+
+        namelen = strlen(ec->signalg->name);
+
+        /* Format the output */
+        *siglen = 8+namelen+rlen+slen+8;
+        buf = snewn(*siglen, unsigned char);
+        p = buf;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, rlen + slen + 8);
+        p += 4;
+        PUT_32BIT(p, rlen);
+        p += 4;
+        for (i = rlen; i--;)
+            *p++ = bignum_byte(r, i);
+        PUT_32BIT(p, slen);
+        p += 4;
+        for (i = slen; i--;)
+            *p++ = bignum_byte(s, i);
+
+        freebn(r);
+        freebn(s);
+    }
 
     return buf;
 }
 
+const struct ecsign_extra sign_extra_ed25519 = {
+    ec_ed25519,
+    NULL, 0,
+};
+const struct ssh_signkey ssh_ecdsa_ed25519 = {
+    ecdsa_newkey,
+    ecdsa_freekey,
+    ecdsa_fmtkey,
+    ecdsa_public_blob,
+    ecdsa_private_blob,
+    ecdsa_createkey,
+    ed25519_openssh_createkey,
+    ed25519_openssh_fmtkey,
+    2 /* point, private exponent */,
+    ecdsa_pubkey_bits,
+    ecdsa_verifysig,
+    ecdsa_sign,
+    "ssh-ed25519",
+    "ssh-ed25519",
+    &sign_extra_ed25519,
+};
+
+/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
+static const unsigned char nistp256_oid[] = {
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+};
+const struct ecsign_extra sign_extra_nistp256 = {
+    ec_p256,
+    nistp256_oid, lenof(nistp256_oid),
+};
 const struct ssh_signkey ssh_ecdsa_nistp256 = {
     ecdsa_newkey,
     ecdsa_freekey,
@@ -1959,14 +3387,23 @@ const struct ssh_signkey ssh_ecdsa_nistp256 = {
     ecdsa_createkey,
     ecdsa_openssh_createkey,
     ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
     ecdsa_pubkey_bits,
-    ecdsa_fingerprint,
     ecdsa_verifysig,
     ecdsa_sign,
     "ecdsa-sha2-nistp256",
     "ecdsa-sha2-nistp256",
+    &sign_extra_nistp256,
 };
 
+/* OID: 1.3.132.0.34 (secp384r1) */
+static const unsigned char nistp384_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x22
+};
+const struct ecsign_extra sign_extra_nistp384 = {
+    ec_p384,
+    nistp384_oid, lenof(nistp384_oid),
+};
 const struct ssh_signkey ssh_ecdsa_nistp384 = {
     ecdsa_newkey,
     ecdsa_freekey,
@@ -1976,14 +3413,23 @@ const struct ssh_signkey ssh_ecdsa_nistp384 = {
     ecdsa_createkey,
     ecdsa_openssh_createkey,
     ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
     ecdsa_pubkey_bits,
-    ecdsa_fingerprint,
     ecdsa_verifysig,
     ecdsa_sign,
     "ecdsa-sha2-nistp384",
     "ecdsa-sha2-nistp384",
+    &sign_extra_nistp384,
 };
 
+/* OID: 1.3.132.0.35 (secp521r1) */
+static const unsigned char nistp521_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x23
+};
+const struct ecsign_extra sign_extra_nistp521 = {
+    ec_p521,
+    nistp521_oid, lenof(nistp521_oid),
+};
 const struct ssh_signkey ssh_ecdsa_nistp521 = {
     ecdsa_newkey,
     ecdsa_freekey,
@@ -1993,18 +3439,23 @@ const struct ssh_signkey ssh_ecdsa_nistp521 = {
     ecdsa_createkey,
     ecdsa_openssh_createkey,
     ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
     ecdsa_pubkey_bits,
-    ecdsa_fingerprint,
     ecdsa_verifysig,
     ecdsa_sign,
     "ecdsa-sha2-nistp521",
     "ecdsa-sha2-nistp521",
+    &sign_extra_nistp521,
 };
 
 /* ----------------------------------------------------------------------
  * Exposed ECDH interface
  */
 
+struct eckex_extra {
+    struct ec_curve *(*curve)(void);
+};
+
 static Bignum ecdh_calculate(const Bignum private,
                              const struct ec_point *public)
 {
@@ -2014,30 +3465,93 @@ static Bignum ecdh_calculate(const Bignum private,
     if (!p) return NULL;
     ret = p->x;
     p->x = NULL;
+
+    if (p->curve->type == EC_MONTGOMERY) {
+        /* Do conversion in network byte order */
+        int i;
+        int bytes = (bignum_bitcount(ret)+7) / 8;
+        unsigned char *byteorder = snewn(bytes, unsigned char);
+        if (!byteorder) {
+            ec_point_free(p);
+            freebn(ret);
+            return NULL;
+        }
+        for (i = 0; i < bytes; ++i) {
+            byteorder[i] = bignum_byte(ret, i);
+        }
+        freebn(ret);
+        ret = bignum_from_bytes(byteorder, bytes);
+        sfree(byteorder);
+    }
+
     ec_point_free(p);
     return ret;
 }
 
-void *ssh_ecdhkex_newkey(struct ec_curve *curve)
+void *ssh_ecdhkex_newkey(const struct ssh_kex *kex)
 {
-    struct ec_key *key = snew(struct ec_key);
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    struct ec_curve *curve;
+    struct ec_key *key;
     struct ec_point *publicKey;
-    key->publicKey.curve = curve;
-    key->privateKey = bignum_random_in_range(One, key->publicKey.curve->n);
-    if (!key->privateKey) {
-        sfree(key);
+
+    curve = extra->curve();
+
+    key = snew(struct ec_key);
+    if (!key) {
         return NULL;
     }
-    publicKey = ecp_mul(&key->publicKey.curve->G, key->privateKey);
-    if (!publicKey) {
-        freebn(key->privateKey);
-        sfree(key);
-        return NULL;
+
+    key->signalg = NULL;
+    key->publicKey.curve = curve;
+
+    if (curve->type == EC_MONTGOMERY) {
+        unsigned char bytes[32] = {0};
+        int i;
+
+        for (i = 0; i < sizeof(bytes); ++i)
+        {
+            bytes[i] = (unsigned char)random_byte();
+        }
+        bytes[0] &= 248;
+        bytes[31] &= 127;
+        bytes[31] |= 64;
+        key->privateKey = bignum_from_bytes(bytes, sizeof(bytes));
+        for (i = 0; i < sizeof(bytes); ++i)
+        {
+            ((volatile char*)bytes)[i] = 0;
+        }
+        if (!key->privateKey) {
+            sfree(key);
+            return NULL;
+        }
+        publicKey = ecp_mul(&key->publicKey.curve->m.G, key->privateKey);
+        if (!publicKey) {
+            freebn(key->privateKey);
+            sfree(key);
+            return NULL;
+        }
+        key->publicKey.x = publicKey->x;
+        key->publicKey.y = publicKey->y;
+        key->publicKey.z = NULL;
+        sfree(publicKey);
+    } else {
+        key->privateKey = bignum_random_in_range(One, key->publicKey.curve->w.n);
+        if (!key->privateKey) {
+            sfree(key);
+            return NULL;
+        }
+        publicKey = ecp_mul(&key->publicKey.curve->w.G, key->privateKey);
+        if (!publicKey) {
+            freebn(key->privateKey);
+            sfree(key);
+            return NULL;
+        }
+        key->publicKey.x = publicKey->x;
+        key->publicKey.y = publicKey->y;
+        key->publicKey.z = NULL;
+        sfree(publicKey);
     }
-    key->publicKey.x = publicKey->x;
-    key->publicKey.y = publicKey->y;
-    key->publicKey.z = NULL;
-    sfree(publicKey);
     return key;
 }
 
@@ -2046,17 +3560,34 @@ char *ssh_ecdhkex_getpublic(void *key, int *len)
     struct ec_key *ec = (struct ec_key*)key;
     char *point, *p;
     int i;
-    int pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+    int pointlen;
+
+    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
 
-    *len = 1 + pointlen * 2;
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        *len = 1 + pointlen * 2;
+    } else {
+        *len = pointlen;
+    }
     point = (char*)snewn(*len, char);
+    if (!point) {
+        return NULL;
+    }
 
     p = point;
-    *p++ = 0x04;
-    for (i = pointlen; i--;)
-        *p++ = bignum_byte(ec->publicKey.x, i);
-    for (i = pointlen; i--;)
-        *p++ = bignum_byte(ec->publicKey.y, i);
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        *p++ = 0x04;
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+    } else {
+        for (i = 0; i < pointlen; ++i) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+    }
 
     return point;
 }
@@ -2065,14 +3596,31 @@ Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen)
 {
     struct ec_key *ec = (struct ec_key*) key;
     struct ec_point remote;
+    Bignum ret;
 
-    remote.curve = ec->publicKey.curve;
-    remote.infinity = 0;
-    if (!decodepoint(remoteKey, remoteKeyLen, &remote)) {
-        return NULL;
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        remote.curve = ec->publicKey.curve;
+        remote.infinity = 0;
+        if (!decodepoint(remoteKey, remoteKeyLen, &remote)) {
+            return NULL;
+        }
+    } else {
+        /* Point length has to be the same length */
+        if (remoteKeyLen != (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8) {
+            return NULL;
+        }
+
+        remote.curve = ec->publicKey.curve;
+        remote.infinity = 0;
+        remote.x = bignum_from_bytes_le((unsigned char*)remoteKey, remoteKeyLen);
+        remote.y = NULL;
+        remote.z = NULL;
     }
 
-    return ecdh_calculate(ec->privateKey, &remote);
+    ret = ecdh_calculate(ec->privateKey, &remote);
+    if (remote.x) freebn(remote.x);
+    if (remote.y) freebn(remote.y);
+    return ret;
 }
 
 void ssh_ecdhkex_freekey(void *key)
@@ -2080,25 +3628,99 @@ void ssh_ecdhkex_freekey(void *key)
     ecdsa_freekey(key);
 }
 
+static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 };
+static const struct ssh_kex ssh_ec_kex_curve25519 = {
+    "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_curve25519,
+};
+
+const struct eckex_extra kex_extra_nistp256 = { ec_p256 };
 static const struct ssh_kex ssh_ec_kex_nistp256 = {
-    "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, NULL, NULL, 0, 0, &ssh_sha256
+    "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_nistp256,
 };
 
+const struct eckex_extra kex_extra_nistp384 = { ec_p384 };
 static const struct ssh_kex ssh_ec_kex_nistp384 = {
-    "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, NULL, NULL, 0, 0, &ssh_sha384
+    "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH,
+    &ssh_sha384, &kex_extra_nistp384,
 };
 
+const struct eckex_extra kex_extra_nistp521 = { ec_p521 };
 static const struct ssh_kex ssh_ec_kex_nistp521 = {
-    "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, NULL, NULL, 0, 0, &ssh_sha512
+    "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH,
+    &ssh_sha512, &kex_extra_nistp521,
 };
 
 static const struct ssh_kex *const ec_kex_list[] = {
+    &ssh_ec_kex_curve25519,
     &ssh_ec_kex_nistp256,
     &ssh_ec_kex_nistp384,
-    &ssh_ec_kex_nistp521
+    &ssh_ec_kex_nistp521,
 };
 
 const struct ssh_kexes ssh_ecdh_kex = {
     sizeof(ec_kex_list) / sizeof(*ec_kex_list),
     ec_kex_list
 };
+
+/* ----------------------------------------------------------------------
+ * Helper functions for finding key algorithms and returning auxiliary
+ * data.
+ */
+
+const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid,
+                                        const struct ec_curve **curve)
+{
+    static const struct ssh_signkey *algs_with_oid[] = {
+        &ssh_ecdsa_nistp256,
+        &ssh_ecdsa_nistp384,
+        &ssh_ecdsa_nistp521,
+    };
+    int i;
+
+    for (i = 0; i < lenof(algs_with_oid); i++) {
+        const struct ssh_signkey *alg = algs_with_oid[i];
+        const struct ecsign_extra *extra =
+            (const struct ecsign_extra *)alg->extra;
+        if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) {
+            *curve = extra->curve();
+            return alg;
+        }
+    }
+    return NULL;
+}
+
+const unsigned char *ec_alg_oid(const struct ssh_signkey *alg,
+                                int *oidlen)
+{
+    const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra;
+    *oidlen = extra->oidlen;
+    return extra->oid;
+}
+
+const int ec_nist_alg_and_curve_by_bits(int bits,
+                                        const struct ec_curve **curve,
+                                        const struct ssh_signkey **alg)
+{
+    switch (bits) {
+      case 256: *alg = &ssh_ecdsa_nistp256; break;
+      case 384: *alg = &ssh_ecdsa_nistp384; break;
+      case 521: *alg = &ssh_ecdsa_nistp521; break;
+      default: return FALSE;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return TRUE;
+}
+
+const int ec_ed_alg_and_curve_by_bits(int bits,
+                                      const struct ec_curve **curve,
+                                      const struct ssh_signkey **alg)
+{
+    switch (bits) {
+      case 256: *alg = &ssh_ecdsa_ed25519; break;
+      default: return FALSE;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return TRUE;
+}