diff --git a/ChangeLog b/ChangeLog
index e68181a97fc37ace9d51a77e1ce867e114d7f55e..08686f54dc3c15c8ed0fc71c46b622d052d94c86 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2014-10-21  Niels Möller  <nisse@lysator.liu.se>
 
+	* testsuite/ed25519-test.c: New test case. Optionally reads the
+	file pointed to by $ED25519_SIGN_INPUT.
+
 	* testsuite/testutils.c (tstring_hex): Rewrite, using Nettle's
 	base16 functions.
 	(decode_hex, decode_hex_length): Deleted functions.
diff --git a/testsuite/.test-rules.make b/testsuite/.test-rules.make
index b3587ca320e4db4faf717ef838032305b60a2c3f..5d793208561021c0161b70ec80ec4385c5d601b7 100644
--- a/testsuite/.test-rules.make
+++ b/testsuite/.test-rules.make
@@ -235,6 +235,9 @@ eddsa-sign-test$(EXEEXT): eddsa-sign-test.$(OBJEXT)
 eddsa-verify-test$(EXEEXT): eddsa-verify-test.$(OBJEXT)
 	$(LINK) eddsa-verify-test.$(OBJEXT) $(TEST_OBJS) -o eddsa-verify-test$(EXEEXT)
 
+ed25519-test$(EXEEXT): ed25519-test.$(OBJEXT)
+	$(LINK) ed25519-test.$(OBJEXT) $(TEST_OBJS) -o ed25519-test$(EXEEXT)
+
 sha1-huge-test$(EXEEXT): sha1-huge-test.$(OBJEXT)
 	$(LINK) sha1-huge-test.$(OBJEXT) $(TEST_OBJS) -o sha1-huge-test$(EXEEXT)
 
diff --git a/testsuite/Makefile.in b/testsuite/Makefile.in
index 87d97edd3a9fb01f003a6cb1886bd68e735cb398..96c0bc80aee9b24c239fbc1cb5c2714b6f1fd049 100644
--- a/testsuite/Makefile.in
+++ b/testsuite/Makefile.in
@@ -46,7 +46,7 @@ TS_HOGWEED_SOURCES = sexp-test.c sexp-format-test.c \
 		     ecdsa-sign-test.c ecdsa-verify-test.c \
 		     ecdsa-keygen-test.c ecdh-test.c \
 		     eddsa-compress-test.c eddsa-sign-test.c \
-		     eddsa-verify-test.c
+		     eddsa-verify-test.c ed25519-test.c
 
 TS_SOURCES = $(TS_NETTLE_SOURCES) $(TS_HOGWEED_SOURCES)
 CXX_SOURCES = cxx-test.cxx
diff --git a/testsuite/ed25519-test.c b/testsuite/ed25519-test.c
new file mode 100644
index 0000000000000000000000000000000000000000..be1313760c5500f1fb55e2bcea101a9f01d3b3ce
--- /dev/null
+++ b/testsuite/ed25519-test.c
@@ -0,0 +1,147 @@
+/* ed25519-test.c
+
+   Copyright (C) 2014 Niels Möller
+
+   This file is part of GNU Nettle.
+
+   GNU Nettle is free software: you can redistribute it and/or
+   modify it under the terms of either:
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at your
+       option) any later version.
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at your
+       option) any later version.
+
+   or both in parallel, as here.
+
+   GNU Nettle is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see http://www.gnu.org/licenses/.
+*/
+
+#include "testutils.h"
+
+#include <errno.h>
+
+#include "eddsa.h"
+
+#include "base16.h"
+
+static void
+decode_hex (size_t length, uint8_t *dst, const char *src)
+{
+  struct base16_decode_ctx ctx;
+  size_t out_size;
+  base16_decode_init (&ctx);
+  ASSERT (base16_decode_update (&ctx, &out_size, dst, 2*length, src));
+  ASSERT (out_size == length);
+  ASSERT (base16_decode_final (&ctx));
+}
+
+/* Processes a single line in the format of
+   http://ed25519.cr.yp.to/python/sign.input:
+
+     sk pk : pk : m : s m :
+
+   where sk (secret key) and pk (public key) are 32 bytes each, m is
+   variable size, and s is 64 bytes. All values hex encoded.
+*/
+static void
+test_one (const char *line)
+{
+  const char *p;
+  const char *mp;
+  uint8_t sk[ED25519_KEY_SIZE];
+  uint8_t pk[ED25519_KEY_SIZE];
+  uint8_t s[ED25519_SIGNATURE_SIZE];
+  uint8_t *msg;
+  size_t msg_size;
+  uint8_t s2[ED25519_SIGNATURE_SIZE];
+
+  struct ed25519_public_key pub;
+  struct ed25519_private_key priv;
+  decode_hex (ED25519_KEY_SIZE, sk, line);
+
+  p = strchr (line, ':');
+  ASSERT (p == line + 128);
+  p++;
+  decode_hex (ED25519_KEY_SIZE, pk, p);
+  p = strchr (p, ':');
+  ASSERT (p == line + 193);
+  mp = ++p;
+  p = strchr (p, ':');
+  ASSERT (p);
+  ASSERT ((p - mp) % 2 == 0);
+  msg_size = (p - mp) / 2;
+
+  decode_hex (ED25519_SIGNATURE_SIZE, s, p+1);
+
+  msg = xalloc (msg_size + 1);
+  msg[msg_size] = 'x';
+
+  decode_hex (msg_size, msg, mp);
+
+  ed25519_sha512_set_private_key (&priv, sk);
+  ASSERT (MEMEQ(ED25519_KEY_SIZE, priv.pub, pk));
+
+  ed25519_sha512_sign (&priv, msg_size, msg, s2);
+  ASSERT (MEMEQ (ED25519_SIGNATURE_SIZE, s, s2));
+  ASSERT (ed25519_sha512_set_public_key (&pub, pk));
+
+  ASSERT (ed25519_sha512_verify (&pub, msg_size, msg, s));
+
+  s2[ED25519_SIGNATURE_SIZE/3] ^= 0x40;
+  ASSERT (!ed25519_sha512_verify (&pub, msg_size, msg, s2));
+
+  memcpy (s2, s, ED25519_SIGNATURE_SIZE);
+  s2[2*ED25519_SIGNATURE_SIZE/3] ^= 0x40;
+  ASSERT (!ed25519_sha512_verify (&pub, msg_size, msg, s2));
+
+  ASSERT (!ed25519_sha512_verify (&pub, msg_size + 1, msg, s));
+
+  if (msg_size > 0)
+    {
+      msg[msg_size-1] ^= 0x20;
+      ASSERT (!ed25519_sha512_verify (&pub, msg_size, msg, s));
+    }
+  free (msg);
+}
+
+void
+test_main(void)
+{
+  const char *input = getenv ("ED25519_SIGN_INPUT");
+  if (input)
+    {
+      size_t buf_size;
+      char *buf;
+      FILE *f = fopen (input, "r");
+      if (!f)
+	die ("Opening input file '%s' failed: %s\n",
+	     input, strerror (errno));
+
+      for (buf = NULL; getline (&buf, &buf_size, f) >= 0; )
+	test_one (buf);
+
+      free (buf);
+      fclose (f);
+    }
+  else
+    {
+      /* First few lines only */
+      test_one ("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b:");
+      test_one ("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072:");
+      test_one ("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82:");
+      test_one ("0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b:");
+    }
+}