diff --git a/Makefile.in b/Makefile.in
index 38b69e35..4a2854b2 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -149,7 +149,7 @@ nettle_SOURCES = aes-decrypt-internal.c aes-decrypt-table.c \
 		 ocb.c ocb-aes128.c \
 		 pbkdf2.c pbkdf2-hmac-gosthash94.c pbkdf2-hmac-sha1.c \
 		 pbkdf2-hmac-sha256.c pbkdf2-hmac-sha384.c pbkdf2-hmac-sha512.c \
-		 poly1305-aes.c poly1305-internal.c poly1305-update.c \
+		 poly1305-aes.c poly1305.c poly1305-internal.c poly1305-update.c \
 		 realloc.c \
 		 ripemd160.c ripemd160-compress.c ripemd160-meta.c \
 		 salsa20-core-internal.c salsa20-crypt-internal.c \
diff --git a/chacha-poly1305.c b/chacha-poly1305.c
index 27f18a2c..f89c1612 100644
--- a/chacha-poly1305.c
+++ b/chacha-poly1305.c
@@ -94,7 +94,7 @@ chacha_poly1305_set_nonce (struct chacha_poly1305_ctx *ctx,
 #define COMPRESS(ctx, data) _nettle_poly1305_block(&(ctx)->poly1305, (data), 1)
 
 static void
-poly1305_update (struct chacha_poly1305_ctx *ctx,
+inner_chacha_poly1305_update (struct chacha_poly1305_ctx *ctx,
 		 size_t length, const uint8_t *data)
 {
   ctx->index = _nettle_poly1305_update (&(ctx)->poly1305,
@@ -116,8 +116,8 @@ void
 chacha_poly1305_update (struct chacha_poly1305_ctx *ctx,
 			size_t length, const uint8_t *data)
 {
-  assert (ctx->data_size == 0);  
-  poly1305_update (ctx, length, data);
+  assert (ctx->data_size == 0);
+  inner_chacha_poly1305_update (ctx, length, data);
   ctx->auth_size += length;
 }
 
@@ -133,10 +133,10 @@ chacha_poly1305_encrypt (struct chacha_poly1305_ctx *ctx,
   poly1305_pad (ctx);
 
   chacha_crypt32 (&ctx->chacha, length, dst, src);
-  poly1305_update (ctx, length, dst);
+  inner_chacha_poly1305_update (ctx, length, dst);
   ctx->data_size += length;
 }
-			 
+
 void
 chacha_poly1305_decrypt (struct chacha_poly1305_ctx *ctx,
 			 size_t length, uint8_t *dst, const uint8_t *src)
@@ -147,11 +147,11 @@ chacha_poly1305_decrypt (struct chacha_poly1305_ctx *ctx,
   assert (ctx->data_size % CHACHA_POLY1305_BLOCK_SIZE == 0);
   poly1305_pad (ctx);
 
-  poly1305_update (ctx, length, src);
+  inner_chacha_poly1305_update (ctx, length, src);
   chacha_crypt32 (&ctx->chacha, length, dst, src);
   ctx->data_size += length;
 }
-			 
+
 void
 chacha_poly1305_digest (struct chacha_poly1305_ctx *ctx,
 			uint8_t *digest)
diff --git a/nettle.texinfo b/nettle.texinfo
index 5729d396..848cdb41 100644
--- a/nettle.texinfo
+++ b/nettle.texinfo
@@ -4409,13 +4409,16 @@ Poly1305-@acronym{AES} is a message authentication code designed by D. J.
 Bernstein. It treats the message as a polynomial modulo the prime number
 @math{2^130 - 5}.
 
-The key, 256 bits, consists of two parts, where the first half is an
-@acronym{AES}-128 key, and the second half specifies the point where the
-polynomial is evaluated. Of the latter half, 22 bits are set to zero, to
+The underlying Poly1305 algorithm partitions the key in two parts
+@code{r} and @code{s}, where the first specifies the point where the
+polynomial is evaluated. Of the former half, 22 bits are set to zero, to
 enable high-performance implementation, leaving 106 bits for specifying
-an evaluation point @code{r}. For each message, one must also provide a
-128-bit nonce. The nonce is encrypted using the @acronym{AES} key, and
-that's the only thing @acronym{AES} is used for.
+an evaluation point @code{r}. While @code{r} may be reused, the combined
+pair should to be unique and unpredictable for each message. To
+accomplish this, Poly1305-AES creates a unique @code{s} for each message
+by encrypting a 128-bit nonce using AES. The Poly1305-AES key consists
+of two parts: The first half is an AES-128 key, and the second half is
+the key @code{r} passed to the underlying Poly1305.
 
 The message is split into 128-bit chunks (with final chunk possibly
 being shorter), each read as a little-endian integer. Each chunk has a
@@ -4466,6 +4469,34 @@ Extracts the digest, writing @code{POLY1305_AES_DIGEST_SIZE} octets to
 processing a new message.
 @end deftypefun
 
+The standalone Poly1305 algorithm is also available.
+
+@defvr Constant POLY1305_KEYART_R_SIZE
+@defvrx Constant POLY1305_KEYPART_S_SIZE
+The two parts @code{r} and @code{s} of the key, each 16 octets.
+@end defvr
+
+@defvr Constant POLY1305_DIGEST_SIZE
+Size of the digest or ``authenticator'', 16 octets.
+@end defvr
+
+@deftp {Context struct} {struct poly1305_mac_ctx}
+The poly1305 context struct.
+@end deftp
+
+@deftypefun void poly1305_set_key (struct poly1305_mac_ctx *@var{ctx}, const uint8_t *@var{r})
+Initialize the context struct.
+@end deftypefun
+
+@deftypefun void poly1305_update (struct poly1305_mac_ctx *@var{ctx}, size_t @var{length}, const uint8_t *@var{data})
+Process more data.
+@end deftypefun
+
+@deftypefun void poly1305_digest (struct poly1305_mac_ctx *@var{ctx}, const uint8_t *s, uint8_t *@var{digest})
+Extracts the digest, writing @code{POLY1305_DIGEST_SIZE} octets to
+@var{digest}. Also prepares the context for processing a new message.
+@end deftypefun
+
 
 @node Key derivation functions
 @section Key derivation Functions
diff --git a/poly1305.c b/poly1305.c
new file mode 100644
index 00000000..264aab0c
--- /dev/null
+++ b/poly1305.c
@@ -0,0 +1,72 @@
+/* poly1305.c
+
+   Copyright (C) 2013 Nikos Mavrogiannopoulos
+   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/.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include "poly1305.h"
+#include "poly1305-internal.h"
+#include "macros.h"
+
+void
+poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r)
+{
+  _nettle_poly1305_set_key(&ctx->pctx, r);
+  ctx->index = 0;
+}
+
+void
+poly1305_update(struct poly1305_mac_ctx* ctx, size_t length, const uint8_t *data)
+{
+  ctx->index = _nettle_poly1305_update(&ctx->pctx, ctx->block, ctx->index, length, data);
+}
+
+void
+poly1305_digest(struct poly1305_mac_ctx* ctx, const uint8_t *s, uint8_t* digest)
+{
+  union nettle_block16 buffer;
+
+  if (ctx->index > 0) {
+    ctx->block[ctx->index] = 1;
+    memset(ctx->block + ctx->index + 1, 0, POLY1305_BLOCK_SIZE - 1 - ctx->index);
+    _nettle_poly1305_block (&ctx->pctx, ctx->block, 0);
+  }
+
+  memcpy(&buffer.b, s, POLY1305_KEYPART_S_SIZE);
+  _nettle_poly1305_digest(&ctx->pctx, &buffer);
+  memcpy(digest, buffer.b, POLY1305_DIGEST_SIZE);
+}
+
diff --git a/poly1305.h b/poly1305.h
index bbe6c2f4..faede88d 100644
--- a/poly1305.h
+++ b/poly1305.h
@@ -42,6 +42,9 @@ extern "C" {
 #endif
 
 /* Name mangling */
+#define poly1305_set_key nettle_poly1305_set_key
+#define poly1305_update nettle_poly1305_update
+#define poly1305_digest nettle_poly1305_digest
 #define poly1305_aes_set_key nettle_poly1305_aes_set_key
 #define poly1305_aes_set_nonce nettle_poly1305_aes_set_nonce
 #define poly1305_aes_update nettle_poly1305_aes_update
@@ -70,6 +73,27 @@ struct poly1305_ctx {
   } h;
 };
 
+struct poly1305_mac_ctx {
+  struct poly1305_ctx pctx;
+  uint8_t block[POLY1305_BLOCK_SIZE];
+  unsigned index;
+};
+
+#define POLY1305_DIGEST_SIZE 16
+#define POLY1305_KEYPART_S_SIZE 16
+#define POLY1305_KEYPART_R_SIZE 16
+
+/* Also sets index to 0 */
+void
+poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r);
+
+void
+poly1305_update(struct poly1305_mac_ctx* ctx, size_t length, const uint8_t *data);
+
+void
+poly1305_digest(struct poly1305_mac_ctx* ctx, const uint8_t *s, uint8_t* digest);
+
+
 /* poly1305-aes */
 
 #define POLY1305_AES_KEY_SIZE 32
diff --git a/testsuite/poly1305-test.c b/testsuite/poly1305-test.c
index 50305164..2216a01d 100644
--- a/testsuite/poly1305-test.c
+++ b/testsuite/poly1305-test.c
@@ -218,6 +218,35 @@ test_random(void)
     }
 }
 
+static void
+test_poly1305_mac(const struct tstring *r,
+                  const struct tstring *s,
+                  const struct tstring *msg,
+                  const struct tstring *ref)
+{
+  struct poly1305_mac_ctx ctx;
+  uint8_t tag[POLY1305_DIGEST_SIZE];
+
+  ASSERT (r->length == POLY1305_KEYPART_R_SIZE);
+  ASSERT (s->length == POLY1305_KEYPART_S_SIZE);
+  ASSERT (ref->length == POLY1305_DIGEST_SIZE);
+
+  poly1305_set_key(&ctx, r->data);
+  poly1305_update(&ctx, msg->length, msg->data);
+
+  poly1305_digest(&ctx, s->data, tag);
+
+  if (!MEMEQ(ref->length, ref->data, tag))
+  {
+    printf ("poly1305 failed\n");
+    printf ("msg: "); print_hex (msg->length, msg->data);
+    printf ("length: %u\n", (unsigned) msg->length);
+    printf ("tag: "); print_hex (ref->length, tag);
+    printf ("ref: "); print_hex (ref->length, ref->data);
+    abort ();
+  }
+}
+
 void
 test_main(void)
 {
@@ -252,4 +281,142 @@ test_main(void)
 
   test_fixed();
   test_random();
+
+  /* From RFC 8439 */
+  test_poly1305_mac
+    (SHEX("85d6be7857556d337f4452fe42d506a8"),
+     SHEX("0103808afb0db2fd4abff6af4149f51b"),
+     SHEX("43727970746f6772617068696320466f72756d2052657365617263682047726f7570"),
+     SHEX("a8061dc1305136c6c22b8baf0c0127a9"));
+
+  test_poly1305_mac
+    (SHEX("00000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"
+          "00000000000000000000000000000000"
+          "00000000000000000000000000000000"
+          "00000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"));
+
+  test_poly1305_mac
+    (SHEX("00000000000000000000000000000000"),
+     SHEX("36e5f6b5c5e06070f0efca96227a863e"),
+     SHEX("416e79207375626d697373696f6e2074"
+          "6f20746865204945544620696e74656e"
+          "6465642062792074686520436f6e7472"
+          "696275746f7220666f72207075626c69"
+          "636174696f6e20617320616c6c206f72"
+          "2070617274206f6620616e2049455446"
+          "20496e7465726e65742d447261667420"
+          "6f722052464320616e6420616e792073"
+          "746174656d656e74206d616465207769"
+          "7468696e2074686520636f6e74657874"
+          "206f6620616e20494554462061637469"
+          "7669747920697320636f6e7369646572"
+          "656420616e20224945544620436f6e74"
+          "7269627574696f6e222e205375636820"
+          "73746174656d656e747320696e636c75"
+          "6465206f72616c2073746174656d656e"
+          "747320696e2049455446207365737369"
+          "6f6e732c2061732077656c6c20617320"
+          "7772697474656e20616e6420656c6563"
+          "74726f6e696320636f6d6d756e696361"
+          "74696f6e73206d61646520617420616e"
+          "792074696d65206f7220706c6163652c"
+          "20776869636820617265206164647265"
+          "7373656420746f"),
+     SHEX("36e5f6b5c5e06070f0efca96227a863e"));
+
+  test_poly1305_mac
+    (SHEX("36e5f6b5c5e06070f0efca96227a863e"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("416e79207375626d697373696f6e2074"
+          "6f20746865204945544620696e74656e"
+          "6465642062792074686520436f6e7472"
+          "696275746f7220666f72207075626c69"
+          "636174696f6e20617320616c6c206f72"
+          "2070617274206f6620616e2049455446"
+          "20496e7465726e65742d447261667420"
+          "6f722052464320616e6420616e792073"
+          "746174656d656e74206d616465207769"
+          "7468696e2074686520636f6e74657874"
+          "206f6620616e20494554462061637469"
+          "7669747920697320636f6e7369646572"
+          "656420616e20224945544620436f6e74"
+          "7269627574696f6e222e205375636820"
+          "73746174656d656e747320696e636c75"
+          "6465206f72616c2073746174656d656e"
+          "747320696e2049455446207365737369"
+          "6f6e732c2061732077656c6c20617320"
+          "7772697474656e20616e6420656c6563"
+          "74726f6e696320636f6d6d756e696361"
+          "74696f6e73206d61646520617420616e"
+          "792074696d65206f7220706c6163652c"
+          "20776869636820617265206164647265"
+          "7373656420746f"),
+     SHEX("f3477e7cd95417af89a6b8794c310cf0"));
+
+  test_poly1305_mac
+    (SHEX("1c9240a5eb55d38af333888604f6b5f0"),
+     SHEX("473917c1402b80099dca5cbc207075c0"),
+     SHEX("2754776173206272696c6c69672c2061"
+          "6e642074686520736c6974687920746f"
+          "7665730a446964206779726520616e64"
+          "2067696d626c6520696e207468652077"
+          "6162653a0a416c6c206d696d73792077"
+          "6572652074686520626f726f676f7665"
+          "732c0a416e6420746865206d6f6d6520"
+          "7261746873206f757467726162652e"),
+     SHEX("4541669a7eaaee61e708dc7cbcc5eb62"));
+
+  test_poly1305_mac
+    (SHEX("02000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+     SHEX("03000000000000000000000000000000"));
+
+  test_poly1305_mac
+    (SHEX("02000000000000000000000000000000"),
+     SHEX("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+     SHEX("02000000000000000000000000000000"),
+     SHEX("03000000000000000000000000000000"));
+
+  test_poly1305_mac
+    (SHEX("01000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+          "F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+          "11000000000000000000000000000000"),
+     SHEX("05000000000000000000000000000000"));
+
+  test_poly1305_mac
+    (SHEX("01000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+          "FBFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE"
+          "01010101010101010101010101010101"),
+     SHEX("00000000000000000000000000000000"));
+
+  test_poly1305_mac
+    (SHEX("02000000000000000000000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+     SHEX("FAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
+
+  test_poly1305_mac
+    (SHEX("01000000000000000400000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("E33594D7505E43B90000000000000000"
+          "3394D7505E4379CD0100000000000000"
+          "00000000000000000000000000000000"
+          "01000000000000000000000000000000"),
+     SHEX("14000000000000005500000000000000"));
+
+  test_poly1305_mac
+    (SHEX("01000000000000000400000000000000"),
+     SHEX("00000000000000000000000000000000"),
+     SHEX("E33594D7505E43B90000000000000000"
+          "3394D7505E4379CD0100000000000000"
+          "00000000000000000000000000000000"),
+     SHEX("13000000000000000000000000000000"));
 }
