Hi,
I'm trying to implement chacha20-poly1305 as used by SSH (https://datatracker.ietf.org/doc/html/draft-ietf-sshm-chacha20-poly1305-04), which differs from RFC 8439's ChaCha-Poly1035 as implemented by Nettle.
While Nettle internally has the necessary building blocks, it lacks a public API to directly use poly1305. I think this is fairly straightforward to add one though. Please see below for my initial attempt. If you think this is the right approach, I'd be happy to work on a proper patch including tests and documentation.
#define POLY1305_KEY_SIZE 32 #define POLY1305_DIGEST_SIZE 16
struct poly1305_mac_ctx { struct poly1305_ctx pctx; union nettle_block16 s; uint8_t block[POLY1305_BLOCK_SIZE]; unsigned index; };
void poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *key) { _nettle_poly1305_set_key(&ctx->pctx, key); memcpy(ctx->s.b, key + 16, 16); 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); }
/* After calling this function, context must be re-keyed before calling poly1305_update/poly1305_digest again. */ void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest) { 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); } _nettle_poly1305_digest(&ctx->pctx, &ctx->s); memcpy(digest, ctx->s.b, POLY1305_DIGEST_SIZE); }
Regards, Tim
Tim Kosse tim.kosse@filezilla-project.org writes:
I'm trying to implement chacha20-poly1305 as used by SSH (https://datatracker.ietf.org/doc/html/draft-ietf-sshm-chacha20-poly1305-04), which differs from RFC 8439's ChaCha-Poly1035 as implemented by Nettle.
I agree Nettle should support some way of doing this, thanks for looking into that. Not sure what API should look like, though. It's rather different from other mac:s, since one can't set a key once and authenticate multiple messages.
One option could be to instead promote some of the low-level functions.
If we promote _nettle_poly1305_set_key, _nettle_poly1305_update, and _nettle_poly1305_digest, then what's missing is a utility for the digest-time padding of a partial block. We would need something like either
poly1305_digest (struct poly1305_ctx *ctx, union nettle_block16 *s, uint8_t *buffer, unsigned index);
or current _nettle_poly1305_digest and something like
poly1305_pad (struct poly1305_ctx *ctx, uint8_t *buffer, unsigned index);
I think it's desirable that the new poly1305 functions are defined in such a way that current poly1305_aes and chacha_poly1305 api:s could build on top of it (even if that might be unfeasible at the moment since refactoring the corresponding context structs might break the abi).
Another option is to add an implementation of the specific openssh construction, if we don't foresee any need for additional variants building on the poly1305 primitive.
Some more detailed comments below:
struct poly1305_mac_ctx { struct poly1305_ctx pctx; union nettle_block16 s; uint8_t block[POLY1305_BLOCK_SIZE]; unsigned index; };
Having s in this struct implies some impedance mismatch if we try to use it in the implementation of poly1305_aes (there, s is aes_k(nonce), and the nonce is kept in the context struct to support autoincrement). I think I'd prefer passing s as an argument at digest time.
/* After calling this function, context must be re-keyed before calling poly1305_update/poly1305_digest again. */ void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest)
The requirement for rekeying after each use stands out, if we try to otherwise stick to nettle's conventions for MAC functions.
Regards, /Niels
On Thu, Aug 13, 2026 at 10:05 AM Niels Möller nisse@lysator.liu.se wrote:
Tim Kosse tim.kosse@filezilla-project.org writes:
I'm trying to implement chacha20-poly1305 as used by SSH (https://datatracker.ietf.org/doc/html/draft-ietf-sshm-chacha20-poly1305-04), which differs from RFC 8439's ChaCha-Poly1035 as implemented by Nettle.
I agree Nettle should support some way of doing this, thanks for looking into that. Not sure what API should look like, though. It's rather different from other mac:s, since one can't set a key once and authenticate multiple messages.
One option could be to instead promote some of the low-level functions.
If we promote _nettle_poly1305_set_key, _nettle_poly1305_update, and _nettle_poly1305_digest, then what's missing is a utility for the digest-time padding of a partial block. We would need something like either
poly1305_digest (struct poly1305_ctx *ctx, union nettle_block16 *s, uint8_t *buffer, unsigned index);
or current _nettle_poly1305_digest and something like
poly1305_pad (struct poly1305_ctx *ctx, uint8_t *buffer, unsigned index);
Why not fold the padding into digest, your first variant. The pad is always immediately followed by the digest, so a separate poly1305_pad mostly adds a way to get it wrong, and digest with index == 0 is just the current _nettle_poly1305_digest, which is what an RFC8439-style caller wants after aligning to a block boundary.
Also, should digest return the tag in *s, like the internal function, or take separate (length, digest) outputs like nettle's other MACs? I have no strong opinion though.
I think it's desirable that the new poly1305 functions are defined in such a way that current poly1305_aes and chacha_poly1305 api:s could build on top of it (even if that might be unfeasible at the moment since refactoring the corresponding context structs might break the abi).
Another option is to add an implementation of the specific openssh construction, if we don't foresee any need for additional variants building on the poly1305 primitive.
Some more detailed comments below:
struct poly1305_mac_ctx { struct poly1305_ctx pctx; union nettle_block16 s; uint8_t block[POLY1305_BLOCK_SIZE]; unsigned index; };
Having s in this struct implies some impedance mismatch if we try to use it in the implementation of poly1305_aes (there, s is aes_k(nonce), and the nonce is kept in the context struct to support autoincrement). I think I'd prefer passing s as an argument at digest time.
/* After calling this function, context must be re-keyed before calling poly1305_update/poly1305_digest again. */ void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest)
The requirement for rekeying after each use stands out, if we try to otherwise stick to nettle's conventions for MAC functions.
Regards, /Niels
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance. _______________________________________________ nettle-bugs mailing list -- nettle-bugs@lists.lysator.liu.se To unsubscribe send an email to nettle-bugs-leave@lists.lysator.liu.se
On 2026-08-13 Thu 10:05 AM, Niels Möller wrote:
I think it's desirable that the new poly1305 functions are defined in such a way that current poly1305_aes and chacha_poly1305 api:s could build on top of it
struct poly1305_mac_ctx { struct poly1305_ctx pctx; union nettle_block16 s; uint8_t block[POLY1305_BLOCK_SIZE]; unsigned index; };
Having s in this struct implies some impedance mismatch if we try to use it in the implementation of poly1305_aes (there, s is aes_k(nonce), and the nonce is kept in the context struct to support autoincrement). I think I'd prefer passing s as an argument at digest time.
That makes sense, that way the caller can change s for each invocation.
I'd also change poly1306_set_key in that case though to only accept the first half of the key and change the define:
/* There are two key halves r and s */ #define POLY1305_HALFKEY_SIZE 16
void poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r);
void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest, const uint8_t *s);
If we promote [...] _nettle_poly1305_update, and _nettle_poly1305_digest, then what's missing is a utility for the digest-time padding of a partial block.
I would not promote the current functions. The caller shouldn't have to worry about handling partials blocks and the correct padding of the last block, the update/digest function itself should take care of it, just like with other mac types in Nettle.
Another option is to add an implementation of the specific openssh construction, if we don't foresee any need for additional variants building on the poly1305 primitive.
I think the API requirements for the SSH-specific construction are a bit more complex. In any case I would want to use wrappers around _nettle_poly1305_update and _nettle_poly1305_digest to make the code easier to understand.
Regards, Tim
Tim Kosse tim.kosse@filezilla-project.org writes:
On 2026-08-13 Thu 10:05 AM, Niels Möller wrote:
I think it's desirable that the new poly1305 functions are defined in such a way that current poly1305_aes and chacha_poly1305 api:s could build on top of it
struct poly1305_mac_ctx { struct poly1305_ctx pctx; union nettle_block16 s; uint8_t block[POLY1305_BLOCK_SIZE]; unsigned index; };
Having s in this struct implies some impedance mismatch if we try to use it in the implementation of poly1305_aes (there, s is aes_k(nonce), and the nonce is kept in the context struct to support autoincrement). I think I'd prefer passing s as an argument at digest time.
That makes sense, that way the caller can change s for each invocation.
I'd also change poly1306_set_key in that case though to only accept the first half of the key and change the define:
Do you plan to make an updated patch? In addition to the code, also needs tests and docs. I'm starting to think about a 4.1 release, and if any more features should get in.
/* There are two key halves r and s */ #define POLY1305_HALFKEY_SIZE 16
I wonder if we can come up with a better name. I think it may help with some names for the two parts, first half is the point where the polynomial is evaluated, and the second part is a kind of blinding.
void poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r);
void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest, const uint8_t *s);
I think I'd order the arguments with s (part of the key) before the output digest argument.
Regards, /Niels
On 2026-10-29 Thu 8:11 PM, Niels Möller wrote:
Do you plan to make an updated patch? In addition to the code, also needs tests and docs. I'm starting to think about a 4.1 release, and if any more features should get in.
Thank you for reminding me.
Please see the attached diff.
/* There are two key halves r and s */ #define POLY1305_HALFKEY_SIZE 16
I wonder if we can come up with a better name. I think it may help with some names for the two parts, first half is the point where the polynomial is evaluated, and the second part is a kind of blinding.
For now I've changed it to two defines POLY1305_KEYPART_R_SIZE and POLY1305_KEYPART_S_SIZE.
I've updated the documentation explaining how Poly1305-AES relates to Poly1305, I hope it's clear enough.
void poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r);
void poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest, const uint8_t *s);
I think I'd order the arguments with s (part of the key) before the output digest argument.
Done.
Regards, Tim
Tim Kosse tim.kosse@filezilla-project.org writes:
Thanks. It seems naming is one of the more difficult parts here. Some comments below.
--- a/nettle.texinfo +++ b/nettle.texinfo +The standalone Poly1305 algorithm is also available.
I wonder if we should either discourage direct use, or make some recommendations on how to use it without shooting oneself in the foot? E.g., say that if one can arrange to have a unique but predictable nonce for each message, like a sequence number, one can generate at least S, and preferably both R and S, by encrypting that nonce using a cipher with a secret key? (This could go somewhere else, e.g., in the intro paragraphs, I'm not saying it must be at this place).
+@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.
...new message with the same R (and to process several messages in this way, each call to _digest *must* use a new secret S).
--- /dev/null +++ b/poly1305.c +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);
I think this should reset ctx->index = 0 at the end?
--- 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;
+};
If we use "poly1305_mac" for the context, I think it would make sense to use the same prefix for constants and functions.
+#define POLY1305_DIGEST_SIZE 16
So this would be POLY1305_MAC_DIGEST_SIZE. (Alternatively, stick to POLY1305_DIGEST_SIZE, and think of POLY1305_AES_DIGEST_SIZE as a redundant alias, kept only for backwards caompatibility.
+#define POLY1305_KEYPART_S_SIZE 16 +#define POLY1305_KEYPART_R_SIZE 16
Maybe strike KEYPART, and name as POLY1305_MAC_R_SIZE and POLY1305_MAC_S_SIZE ?
+/* Also sets index to 0 */ +void +poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *r);
The comment looks a bit out of place. I think _mac_ should be in the name. Could also consider if _set_r is clearer (since it doesn't set all of the key).
+void +poly1305_update(struct poly1305_mac_ctx* ctx, size_t length, const uint8_t *data);
poly1305_mac_update (and then there's no need to rename the static function in chacha-poly1305.c).
+void +poly1305_digest(struct poly1305_mac_ctx* ctx, const uint8_t *s, uint8_t* digest);
poly1305_mac_digest, or possibly something also referencing the role of s (but I have no great idea. Maybe poly1305_mac_s_digest?).
To follow nettle conventions for _digest functions, it would mak sense if it is possible to start processing a new message after _digest. Which would then reuse the same r, and it would be essential to have a fresh secret s for the next _digest call. Is that how you intend it to work? A test case for this usage would also be nice.
--- 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 ();
- }
+}
For testing, you could think about defining a larger test context that includes the s value, and write the glue to test it using test_mac (defined in testutils.c). That way we get somewhat better test coverage for providing the message incrementally to the _update function. Alternatively, add some variants of with two calls to _update for different splitting of the message; that may be simpler.
Regards, /Niels
On 2026-11-02 Mon 9:09 PM, Niels Möller wrote:
Thanks. It seems naming is one of the more difficult parts here.
If we use "poly1305_mac" for the context, I think it would make sense to use the same prefix for constants and functions.
I've updated the patch to now consistently use the poly1305_mac_ prefix.
+The standalone Poly1305 algorithm is also available.
I wonder if we should either discourage direct use, or make some recommendations on how to use it without shooting oneself in the foot? E.g., say that if one can arrange to have a unique but predictable nonce for each message, like a sequence number, one can generate at least S, and preferably both R and S, by encrypting that nonce using a cipher with a secret key? (This could go somewhere else, e.g., in the intro paragraphs, I'm not saying it must be at this place).
I've added a word of caution. The introductory paragraph already explains how Poly1305-AES achieves uniqueness and unpredictability for R and S.
+@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.
...new message with the same R (and to process several messages in this way, each call to _digest *must* use a new secret S).
Added.
--- /dev/null +void +poly1305_digest(struct poly1305_mac_ctx* ctx, const uint8_t *s, uint8_t* digest);
poly1305_mac_digest, or possibly something also referencing the role of s (but I have no great idea. Maybe poly1305_mac_s_digest?).
To follow nettle conventions for _digest functions, it would mak sense if it is possible to start processing a new message after _digest. Which would then reuse the same r, and it would be essential to have a fresh secret s for the next _digest call. Is that how you intend it to work?
Yes, that's how it should work.
A test case for this usage would also be nice.
For testing, you could think about defining a larger test context that includes the s value, and write the glue to test it using test_mac (defined in testutils.c). That way we get somewhat better test coverage for providing the message incrementally to the _update function. Alternatively, add some variants of with two calls to _update for different splitting of the message; that may be simpler.
I didn't see an easy way to use test_mac, so I've pretty much copied its approach to test context reuse and byte-by-byte update.
Regards, Tim
nettle-bugs@lists.lysator.liu.se