diff --git a/doc/crypt.tex b/doc/crypt.tex
index fc879fa9a..be7c6dca8 100644
--- a/doc/crypt.tex
+++ b/doc/crypt.tex
@@ -1305,14 +1305,28 @@ \chapter{Stream Ciphers}
Note: You shouldn't use the keystream interface as a PRNG, as it doesn't allow to re-seed the internal state.
-\mysection{ChaCha}
+\mysection{ChaCha and XChaCha20}
\textit{ChaCha} is currently the most modern stream cipher included in LibTomCrypt, so use this one unless you
have a reason for using some of the older algorithms.
+\vspace{1mm}
+
+\textit{XChaCha20} is a variant of \textit{ChaCha20} designed to accept only a 256-bit key and a
+longer 192-bit nonce (24 bytes), which dramatically reduces the risk of nonce collisions when
+generating nonces randomly. Initialization is the only difference between \textit{XChaCha20}
+and \textit{ChaCha20}. Even the \textit{chacha\_state} is the same. Thereafter,
+chacha\_crypt(), chacha\_keystream(), and chacha\_done() are used unaltered.
+chacha\_ivctr32() and chacha\_ivctr64() are NOT used with xchacha20\_setup().
+\vspace{1mm}
For more information about ChaCha see \url{https://en.wikipedia.org/wiki/ChaCha_(cipher)}.
+\vspace{1mm}
-Supported key size: 16 or 32 bytes (128 or 256 bits).
+For more information about XChaCha20 see
+\url{https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha}.
+\vspace{1mm}
+
+Supported key size: 16 or 32 bytes (128 or 256 bits) for ChaCha, 32 bytes only for XChaCha20.
You can initialize ChaCha with 96bit \textit{nonce} + 32bit \textit{counter}:
\begin{verbatim}
@@ -1328,9 +1342,17 @@ \chapter{Stream Ciphers}
err = chacha_ivctr64(&st, nonce, 8, initial_64bit_ctr);
\end{verbatim}
+To initialize \textit{XChaCha20} for the recommended 20 rounds with a 256-bit
+key (32 bytes) and a 192-bit nonce (24 bytes), use:
+\begin{verbatim}
+chacha_state st;
+err = xchacha20_setup(&st, key, key_len, nonce, nonce_len, rounds);
+\end{verbatim}
+
The \textit{chacha\_setup} takes the number of rounds as a parameter -- choose 20 if you are not sure.
As always never ever use the same key + nonce pair more than once.
+Both \textit{ChaCha} and \textit{XChaCha20} use the following functions.
For the actual encryption or decryption you have to call:
\begin{verbatim}
err = chacha_crypt(&st, in_buffer, in_len, out_buffer);
@@ -1354,6 +1376,39 @@ \chapter{Stream Ciphers}
err = chacha_memory(key, keylen, iv, ivlen, datain, datalen, rounds, dataout);
\end{verbatim}
+To encrypt plaintext (or decrypt ciphertext) using XChaCha20 for data already in
+memory with a single function call, the following function may be used.
+\begin{verbatim}
+err = xchacha20_memory(key, keylen, rounds, nonce, nonce_len, datain, datalen, dataout);
+\end{verbatim}
+
+For both \textit{ChaCha} and \textit{XChaCha20} rounds must be an even number
+and if set to 0 the default number of rounds, 20, will be used.
+\vspace{1mm}
+
+\subsection{HChaCha20}
+
+\textit{HChaCha20} is the key derivation function underlying \textit{XChaCha20}. It applies the
+ChaCha20 block function (without the final addition step) to a 256-bit key and a 128-bit input,
+producing a 256-bit derived key. It is also useful as a standalone KDF, for example in NaCl-style
+\textit{crypto\_box} constructions.
+
+\index{xchacha20\_hchacha20()}
+\begin{verbatim}
+int xchacha20_hchacha20(unsigned char *out, unsigned long outlen,
+ const unsigned char *key, unsigned long keylen,
+ const unsigned char *in, unsigned long inlen,
+ int rounds);
+\end{verbatim}
+This derives a 32-byte subkey from a 32-byte \textit{key} and a 16-byte \textit{in} using
+\textit{rounds} ChaCha20 rounds (0 = default 20). The output is stored in \textit{out}
+(\textit{outlen} must be 32, \textit{keylen} must be 32, \textit{inlen} must be 16).
+\vspace{1mm}
+
+If you define \textit{LTC\_XCHACHA20} to include \textit{XChaCha20} and \textit{HChaCha20} in a
+minimal \textit{libtomcrypt} library build, you must also define \textit{LTC\_CHACHA}.
+\vspace{1mm}
+
\mysection{Salsa20 and XSalsa20}
\textit{Salsa20} was Daniel Bernstein's submission to the EU eSTREAM
@@ -2462,6 +2517,9 @@ \subsection{Example Usage}
This authenticated encryption is based on the ChaCha20 stream cipher and Poly1305 authenticator.
It is defined by \url{https://tools.ietf.org/html/rfc8439}.
+When \textit{LTC\_XCHACHA20} is enabled, this mode also supports 24-byte IVs for
+\textit{XChaCha20--Poly1305} as defined by
+\url{https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha}.
IMPORTANT NOTICE: The Chacha20--Poly1305 implementation of LibTomCrypt is compliant to \textbf{RFC8439}, which differs slightly
to what OpenSSH defined as \textbf{chacha20-poly1305@openssh.com}. The OpenSSH compatibility mode can be enabled
@@ -2489,8 +2547,10 @@ \subsection{Initialization Vector}
const unsigned char *iv,
unsigned long ivlen);
\end{verbatim}
-This adds the initialization vector from \textit{iv} of length \textit{ivlen} octects (valid lengths: 8 or 12) to
-the ChaCha20--Poly1305 state \textit{st}.
+This adds the initialization vector from \textit{iv} of length \textit{ivlen} octects (valid lengths: 8, 12,
+or 24 when \textit{LTC\_XCHACHA20} is enabled) to the ChaCha20--Poly1305 state \textit{st}.
+When \textit{ivlen} is 24, this operates as \textit{XChaCha20--Poly1305}, internally deriving
+a subkey via HChaCha20 before proceeding.
\index{chacha20poly1305\_setiv\_rfc7905()}
\begin{verbatim}
diff --git a/libtomcrypt_VS2008.vcproj b/libtomcrypt_VS2008.vcproj
index 987f3a33e..88056e78e 100644
--- a/libtomcrypt_VS2008.vcproj
+++ b/libtomcrypt_VS2008.vcproj
@@ -2910,6 +2910,18 @@
RelativePath="src\stream\chacha\chacha_test.c"
>
+
+
+
+
+
+
chacha.input[4], orig_key + 0);
+ STORE32L(st->chacha.input[5], orig_key + 4);
+ STORE32L(st->chacha.input[6], orig_key + 8);
+ STORE32L(st->chacha.input[7], orig_key + 12);
+ STORE32L(st->chacha.input[8], orig_key + 16);
+ STORE32L(st->chacha.input[9], orig_key + 20);
+ STORE32L(st->chacha.input[10], orig_key + 24);
+ STORE32L(st->chacha.input[11], orig_key + 28);
+
+ /* derive subkey via HChaCha20 and set up state with counter=0 */
+ if ((err = xchacha20_setup(&st->chacha, orig_key, 32, iv, 24, 20)) != CRYPT_OK) {
+ zeromem(orig_key, sizeof(orig_key));
+ return err;
+ }
+
+ /* copy state to temporary for Poly1305 key derivation (counter=0) */
+ for (i = 0; i < 16; i++) tmp_st.input[i] = st->chacha.input[i];
+ tmp_st.input[12] = 0;
+ tmp_st.rounds = st->chacha.rounds;
+ tmp_st.ksleft = 0;
+ tmp_st.ivlen = 12; /* use IETF counter mode for keystream generation */
+
+ /* generate Poly1305 key from block 0 */
+ if ((err = chacha_keystream(&tmp_st, polykey, 32)) != CRYPT_OK) {
+ zeromem(orig_key, sizeof(orig_key));
+ return err;
+ }
+
+ /* set main state counter to 1 for encryption */
+ st->chacha.input[12] = 1;
+ st->chacha.ksleft = 0;
+
+ /* initialize Poly1305 */
+ if ((err = poly1305_init(&st->poly, polykey, 32)) != CRYPT_OK) {
+ zeromem(orig_key, sizeof(orig_key));
+ return err;
+ }
+ st->ctlen = 0;
+ st->aadlen = 0;
+ st->aadflg = 1;
+
+ zeromem(orig_key, sizeof(orig_key));
+ return CRYPT_OK;
+ }
+#endif
/* set IV for chacha20 */
if (ivlen == 12) {
diff --git a/src/encauth/chachapoly/chacha20poly1305_test.c b/src/encauth/chachapoly/chacha20poly1305_test.c
index f6f9c130b..902985c30 100644
--- a/src/encauth/chachapoly/chacha20poly1305_test.c
+++ b/src/encauth/chachapoly/chacha20poly1305_test.c
@@ -152,6 +152,71 @@ int chacha20poly1305_test(void)
}
}
+#ifdef LTC_XCHACHA20
+ /* XChaCha20-Poly1305 AEAD test (draft-irtf-cfrg-xchacha, Appendix A.3.1) */
+ {
+ unsigned char xk[] = { 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f };
+ unsigned char xi24[] = { 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,
+ 0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57 };
+ unsigned char xaad[] = { 0x50,0x51,0x52,0x53,0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7 };
+ unsigned char xpt[] = { 0x4c,0x61,0x64,0x69,0x65,0x73,0x20,0x61,0x6e,0x64,0x20,0x47,0x65,0x6e,0x74,0x6c,
+ 0x65,0x6d,0x65,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x63,0x6c,0x61,0x73,
+ 0x73,0x20,0x6f,0x66,0x20,0x27,0x39,0x39,0x3a,0x20,0x49,0x66,0x20,0x49,0x20,0x63,
+ 0x6f,0x75,0x6c,0x64,0x20,0x6f,0x66,0x66,0x65,0x72,0x20,0x79,0x6f,0x75,0x20,0x6f,
+ 0x6e,0x6c,0x79,0x20,0x6f,0x6e,0x65,0x20,0x74,0x69,0x70,0x20,0x66,0x6f,0x72,0x20,
+ 0x74,0x68,0x65,0x20,0x66,0x75,0x74,0x75,0x72,0x65,0x2c,0x20,0x73,0x75,0x6e,0x73,
+ 0x63,0x72,0x65,0x65,0x6e,0x20,0x77,0x6f,0x75,0x6c,0x64,0x20,0x62,0x65,0x20,0x69,
+ 0x74,0x2e };
+ unsigned char xenc[] = { 0xbd,0x6d,0x17,0x9d,0x3e,0x83,0xd4,0x3b,0x95,0x76,0x57,0x94,0x93,0xc0,0xe9,0x39,
+ 0x57,0x2a,0x17,0x00,0x25,0x2b,0xfa,0xcc,0xbe,0xd2,0x90,0x2c,0x21,0x39,0x6c,0xbb,
+ 0x73,0x1c,0x7f,0x1b,0x0b,0x4a,0xa6,0x44,0x0b,0xf3,0xa8,0x2f,0x4e,0xda,0x7e,0x39,
+ 0xae,0x64,0xc6,0x70,0x8c,0x54,0xc2,0x16,0xcb,0x96,0xb7,0x2e,0x12,0x13,0xb4,0x52,
+ 0x2f,0x8c,0x9b,0xa4,0x0d,0xb5,0xd9,0x45,0xb1,0x1b,0x69,0xb9,0x82,0xc1,0xbb,0x9e,
+ 0x3f,0x3f,0xac,0x2b,0xc3,0x69,0x48,0x8f,0x76,0xb2,0x38,0x35,0x65,0xd3,0xff,0xf9,
+ 0x21,0xf9,0x66,0x4c,0x97,0x63,0x7d,0xa9,0x76,0x88,0x12,0xf6,0x15,0xc6,0x8b,0x13,
+ 0xb5,0x2e };
+ unsigned char xtag[] = { 0xc0,0x87,0x59,0x24,0xc1,0xc7,0x98,0x79,0x47,0xde,0xaf,0xd8,0x78,0x0a,0xcf,0x49 };
+ unsigned long xptlen = sizeof(xpt);
+
+ /* encrypt with incremental API */
+ if ((err = chacha20poly1305_init(&st1, xk, sizeof(xk))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_setiv(&st1, xi24, sizeof(xi24))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_add_aad(&st1, xaad, sizeof(xaad))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_encrypt(&st1, xpt, xptlen, ct)) != CRYPT_OK) return err;
+ len = sizeof(emac);
+ if ((err = chacha20poly1305_done(&st1, emac, &len)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(ct, xptlen, xenc, sizeof(xenc), "XCHACHA20POLY1305-ENC-CT", 1) != 0) return CRYPT_FAIL_TESTVECTOR;
+ if (ltc_compare_testvector(emac, len, xtag, sizeof(xtag), "XCHACHA20POLY1305-ENC-TAG", 2) != 0) return CRYPT_FAIL_TESTVECTOR;
+
+ /* decrypt with incremental API */
+ if ((err = chacha20poly1305_init(&st2, xk, sizeof(xk))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_setiv(&st2, xi24, sizeof(xi24))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_add_aad(&st2, xaad, sizeof(xaad))) != CRYPT_OK) return err;
+ if ((err = chacha20poly1305_decrypt(&st2, ct, xptlen, pt)) != CRYPT_OK) return err;
+ len = sizeof(dmac);
+ if ((err = chacha20poly1305_done(&st2, dmac, &len)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(pt, xptlen, xpt, xptlen, "XCHACHA20POLY1305-DEC-PT", 3) != 0) return CRYPT_FAIL_TESTVECTOR;
+ if (ltc_compare_testvector(dmac, len, xtag, sizeof(xtag), "XCHACHA20POLY1305-DEC-TAG", 4) != 0) return CRYPT_FAIL_TESTVECTOR;
+
+ /* chacha20poly1305_memory - encrypt with 24-byte IV */
+ len = sizeof(emac);
+ if ((err = chacha20poly1305_memory(xk, sizeof(xk), xi24, sizeof(xi24), xaad, sizeof(xaad), xpt,
+ xptlen, ct, emac, &len, CHACHA20POLY1305_ENCRYPT)) != CRYPT_OK) return err;
+ if (ltc_compare_testvector(ct, xptlen, xenc, sizeof(xenc), "XCHACHA20POLY1305-MEM-CT", 1) != 0) return CRYPT_FAIL_TESTVECTOR;
+ if (ltc_compare_testvector(emac, len, xtag, sizeof(xtag), "XCHACHA20POLY1305-MEM-TAG", 2) != 0) return CRYPT_FAIL_TESTVECTOR;
+
+ /* chacha20poly1305_memory - decrypt with 24-byte IV */
+ len = sizeof(dmac);
+ XMEMCPY(dmac, xtag, sizeof(xtag));
+ if ((err = chacha20poly1305_memory(xk, sizeof(xk), xi24, sizeof(xi24), xaad, sizeof(xaad),
+ ct, xptlen, pt, dmac, &len, CHACHA20POLY1305_DECRYPT)) != CRYPT_OK) return err;
+ if (ltc_compare_testvector(pt, xptlen, xpt, xptlen, "XCHACHA20POLY1305-MEM-PT", 3) != 0) return CRYPT_FAIL_TESTVECTOR;
+ }
+#endif
+
return CRYPT_OK;
#endif
}
diff --git a/src/headers/tomcrypt_cipher.h b/src/headers/tomcrypt_cipher.h
index b370fedb0..3712f0dd3 100644
--- a/src/headers/tomcrypt_cipher.h
+++ b/src/headers/tomcrypt_cipher.h
@@ -1056,6 +1056,22 @@ int chacha_memory(const unsigned char *key, unsigned long keylen, unsigned l
#endif /* LTC_CHACHA */
+#ifdef LTC_XCHACHA20
+
+int xchacha20_hchacha20(unsigned char *out, unsigned long outlen,
+ const unsigned char *key, unsigned long keylen,
+ const unsigned char *in, unsigned long inlen,
+ int rounds);
+int xchacha20_setup(chacha_state *st, const unsigned char *key, unsigned long keylen,
+ const unsigned char *nonce, unsigned long noncelen,
+ int rounds);
+int xchacha20_test(void);
+int xchacha20_memory(const unsigned char *key, unsigned long keylen, unsigned long rounds,
+ const unsigned char *nonce, unsigned long noncelen,
+ const unsigned char *datain, unsigned long datalen, unsigned char *dataout);
+
+#endif /* LTC_XCHACHA20 */
+
#ifdef LTC_SALSA20
typedef struct {
diff --git a/src/headers/tomcrypt_custom.h b/src/headers/tomcrypt_custom.h
index ed1440082..99cc54868 100644
--- a/src/headers/tomcrypt_custom.h
+++ b/src/headers/tomcrypt_custom.h
@@ -218,6 +218,7 @@
/* stream ciphers */
#define LTC_CHACHA
+#define LTC_XCHACHA20
#define LTC_SALSA20
#define LTC_XSALSA20
#define LTC_SOSEMANUK
@@ -705,6 +706,10 @@
#error LTC_CHACHA20_PRNG requires LTC_CHACHA
#endif
+#if defined(LTC_XCHACHA20) && !defined(LTC_CHACHA)
+ #error LTC_XCHACHA20 requires LTC_CHACHA
+#endif
+
#if defined(LTC_XSALSA20) && !defined(LTC_SALSA20)
#error LTC_XSALSA20 requires LTC_SALSA20
#endif
diff --git a/src/misc/crypt/crypt.c b/src/misc/crypt/crypt.c
index ccef4a0c7..4dd7dd083 100644
--- a/src/misc/crypt/crypt.c
+++ b/src/misc/crypt/crypt.c
@@ -134,6 +134,9 @@ const char *crypt_build_settings =
#if defined(LTC_CHACHA)
" ChaCha\n"
#endif
+#if defined(LTC_XCHACHA20)
+ " XChaCha20\n"
+#endif
#if defined(LTC_SALSA20)
" Salsa20\n"
#endif
diff --git a/src/stream/chacha/xchacha20_memory.c b/src/stream/chacha/xchacha20_memory.c
new file mode 100644
index 000000000..3d4ccd18b
--- /dev/null
+++ b/src/stream/chacha/xchacha20_memory.c
@@ -0,0 +1,34 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+#ifdef LTC_XCHACHA20
+
+/**
+ Encrypt (or decrypt) bytes of ciphertext (or plaintext) with XChaCha20
+ @param key The key
+ @param keylen The key length
+ @param rounds The number of rounds
+ @param nonce The nonce
+ @param noncelen The nonce length, must be 24 (octets)
+ @param datain The plaintext (or ciphertext)
+ @param datalen The length of the input and output (octets)
+ @param dataout [out] The ciphertext (or plaintext)
+ @return CRYPT_OK if successful
+*/
+int xchacha20_memory(const unsigned char *key, unsigned long keylen, unsigned long rounds,
+ const unsigned char *nonce, unsigned long noncelen,
+ const unsigned char *datain, unsigned long datalen, unsigned char *dataout)
+{
+ chacha_state st;
+ int err;
+
+ err = xchacha20_setup(&st, key, keylen, nonce, noncelen, rounds);
+ if (err == CRYPT_OK) {
+ err = chacha_crypt(&st, datain, datalen, dataout);
+ }
+ chacha_done(&st);
+ return err;
+}
+
+#endif /* LTC_XCHACHA20 */
diff --git a/src/stream/chacha/xchacha20_setup.c b/src/stream/chacha/xchacha20_setup.c
new file mode 100644
index 000000000..c8d84329c
--- /dev/null
+++ b/src/stream/chacha/xchacha20_setup.c
@@ -0,0 +1,144 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/* The implementation is based on:
+ draft-irtf-cfrg-xchacha, "XChaCha: eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305"
+ RFC 8439, "ChaCha20 and Poly1305 for IETF Protocols"
+*/
+
+#ifdef LTC_XCHACHA20
+
+#define QUARTERROUND(a,b,c,d) \
+ x[a] += x[b]; x[d] = ROL(x[d] ^ x[a], 16); \
+ x[c] += x[d]; x[b] = ROL(x[b] ^ x[c], 12); \
+ x[a] += x[b]; x[d] = ROL(x[d] ^ x[a], 8); \
+ x[c] += x[d]; x[b] = ROL(x[b] ^ x[c], 7);
+
+/* ChaCha20 double-round without the final addition */
+static void s_hchacha20(ulong32 *x, int rounds)
+{
+ int i;
+
+ for (i = rounds; i > 0; i -= 2) {
+ /* columnround */
+ QUARTERROUND( 0, 4, 8,12)
+ QUARTERROUND( 1, 5, 9,13)
+ QUARTERROUND( 2, 6,10,14)
+ QUARTERROUND( 3, 7,11,15)
+ /* diagonalround */
+ QUARTERROUND( 0, 5,10,15)
+ QUARTERROUND( 1, 6,11,12)
+ QUARTERROUND( 2, 7, 8,13)
+ QUARTERROUND( 3, 4, 9,14)
+ }
+}
+
+#undef QUARTERROUND
+
+/**
+ HChaCha20: derive a 256-bit subkey from a 256-bit key and 128-bit input.
+ This is the ChaCha20 core (double-rounds) without the final addition step,
+ extracting output from state positions {0,1,2,3,12,13,14,15}.
+ @param out [out] The derived 32-byte subkey
+ @param outlen The length of the output buffer, must be 32 (octets)
+ @param key The secret key
+ @param keylen The length of the secret key, must be 32 (octets)
+ @param in The 16-byte input (nonce or constant)
+ @param inlen The length of the input, must be 16 (octets)
+ @param rounds Number of rounds (must be evenly divisible by 2, default is 20)
+ @return CRYPT_OK if successful
+*/
+int xchacha20_hchacha20(unsigned char *out, unsigned long outlen,
+ const unsigned char *key, unsigned long keylen,
+ const unsigned char *in, unsigned long inlen,
+ int rounds)
+{
+ const char * const constants = "expand 32-byte k";
+ ulong32 x[16];
+
+ LTC_ARGCHK(out != NULL);
+ LTC_ARGCHK(outlen == 32);
+ LTC_ARGCHK(key != NULL);
+ LTC_ARGCHK(keylen == 32);
+ LTC_ARGCHK(in != NULL);
+ LTC_ARGCHK(inlen == 16);
+ if (rounds == 0) rounds = 20;
+ LTC_ARGCHK(rounds % 2 == 0);
+
+ LOAD32L(x[ 0], constants + 0);
+ LOAD32L(x[ 1], constants + 4);
+ LOAD32L(x[ 2], constants + 8);
+ LOAD32L(x[ 3], constants + 12);
+ LOAD32L(x[ 4], key + 0);
+ LOAD32L(x[ 5], key + 4);
+ LOAD32L(x[ 6], key + 8);
+ LOAD32L(x[ 7], key + 12);
+ LOAD32L(x[ 8], key + 16);
+ LOAD32L(x[ 9], key + 20);
+ LOAD32L(x[10], key + 24);
+ LOAD32L(x[11], key + 28);
+ LOAD32L(x[12], in + 0);
+ LOAD32L(x[13], in + 4);
+ LOAD32L(x[14], in + 8);
+ LOAD32L(x[15], in + 12);
+
+ s_hchacha20(x, rounds);
+
+ STORE32L(x[ 0], out + 0);
+ STORE32L(x[ 1], out + 4);
+ STORE32L(x[ 2], out + 8);
+ STORE32L(x[ 3], out + 12);
+ STORE32L(x[12], out + 16);
+ STORE32L(x[13], out + 20);
+ STORE32L(x[14], out + 24);
+ STORE32L(x[15], out + 28);
+
+ zeromem(x, sizeof(x));
+ return CRYPT_OK;
+}
+
+/**
+ Initialize an XChaCha20 context (HChaCha20 subkey derivation + ChaCha20 IETF setup)
+ @param st [out] The destination of the ChaCha20 state
+ @param key The secret key
+ @param keylen The length of the secret key, must be 32 (octets)
+ @param nonce The nonce
+ @param noncelen The length of the nonce, must be 24 (octets)
+ @param rounds Number of rounds (must be evenly divisible by 2, default is 20)
+ @return CRYPT_OK if successful
+*/
+int xchacha20_setup(chacha_state *st, const unsigned char *key, unsigned long keylen,
+ const unsigned char *nonce, unsigned long noncelen,
+ int rounds)
+{
+ unsigned char subkey[32];
+ unsigned char iv[12];
+ int err;
+
+ LTC_ARGCHK(st != NULL);
+ LTC_ARGCHK(nonce != NULL);
+ LTC_ARGCHK(noncelen == 24);
+
+ /* HChaCha20: derive subkey from key and first 16 bytes of nonce */
+ if ((err = xchacha20_hchacha20(subkey, 32, key, keylen, nonce, 16, rounds)) != CRYPT_OK) goto cleanup;
+
+ /* set up ChaCha20 with the derived subkey */
+ if ((err = chacha_setup(st, subkey, 32, rounds)) != CRYPT_OK) goto cleanup;
+
+ /* build 12-byte IETF IV: 4 zero bytes + last 8 bytes of original nonce */
+ XMEMSET(iv, 0, 4);
+ XMEMCPY(iv + 4, nonce + 16, 8);
+ if ((err = chacha_ivctr32(st, iv, 12, 0)) != CRYPT_OK) goto cleanup;
+
+ /* mark as XChaCha20 */
+ st->ivlen = 24;
+
+cleanup:
+ zeromem(subkey, sizeof(subkey));
+ zeromem(iv, sizeof(iv));
+
+ return err;
+}
+
+#endif /* LTC_XCHACHA20 */
diff --git a/src/stream/chacha/xchacha20_test.c b/src/stream/chacha/xchacha20_test.c
new file mode 100644
index 000000000..4a280afdd
--- /dev/null
+++ b/src/stream/chacha/xchacha20_test.c
@@ -0,0 +1,119 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+#ifdef LTC_XCHACHA20
+
+int xchacha20_test(void)
+{
+#ifndef LTC_TEST
+ return CRYPT_NOP;
+#else
+ /* TV1: HChaCha20 known-answer test (draft-irtf-cfrg-xchacha, Section 2.2.1) */
+ {
+ const unsigned char key[] = {
+ 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+ 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f
+ };
+ const unsigned char in[] = {
+ 0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x00,0x31,0x41,0x59,0x27
+ };
+ const unsigned char expected_subkey[] = {
+ 0x82,0x41,0x3b,0x42,0x27,0xb2,0x7b,0xfe,0xd3,0x0e,0x42,0x50,0x8a,0x87,0x7d,0x73,
+ 0xa0,0xf9,0xe4,0xd5,0x8a,0x74,0xa8,0x53,0xc1,0x2e,0xc4,0x13,0x26,0xd3,0xec,0xdc
+ };
+ unsigned char subkey[32];
+ int err;
+
+ if ((err = xchacha20_hchacha20(subkey, 32, key, 32, in, 16, 20)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(subkey, 32, expected_subkey, 32, "XCHACHA20-TV1 (HChaCha20)", 1)) return CRYPT_FAIL_TESTVECTOR;
+ }
+
+ /* TV2: XChaCha20 encryption test (draft-irtf-cfrg-xchacha, Appendix A.3.2) */
+ {
+ const unsigned char key[] = {
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f
+ };
+ const unsigned char nonce[] = {
+ 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
+ 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x58
+ };
+ /* "The dhole (pronounced "dole") is also known as the Asiatic wild dog, ..." */
+ const unsigned char plaintext[] = {
+ 0x54,0x68,0x65,0x20,0x64,0x68,0x6f,0x6c,0x65,0x20,0x28,0x70,0x72,0x6f,0x6e,0x6f,
+ 0x75,0x6e,0x63,0x65,0x64,0x20,0x22,0x64,0x6f,0x6c,0x65,0x22,0x29,0x20,0x69,0x73,
+ 0x20,0x61,0x6c,0x73,0x6f,0x20,0x6b,0x6e,0x6f,0x77,0x6e,0x20,0x61,0x73,0x20,0x74,
+ 0x68,0x65,0x20,0x41,0x73,0x69,0x61,0x74,0x69,0x63,0x20,0x77,0x69,0x6c,0x64,0x20,
+ 0x64,0x6f,0x67,0x2c,0x20,0x72,0x65,0x64,0x20,0x64,0x6f,0x67,0x2c,0x20,0x61,0x6e,
+ 0x64,0x20,0x77,0x68,0x69,0x73,0x74,0x6c,0x69,0x6e,0x67,0x20,0x64,0x6f,0x67,0x2e,
+ 0x20,0x49,0x74,0x20,0x69,0x73,0x20,0x61,0x62,0x6f,0x75,0x74,0x20,0x74,0x68,0x65,
+ 0x20,0x73,0x69,0x7a,0x65,0x20,0x6f,0x66,0x20,0x61,0x20,0x47,0x65,0x72,0x6d,0x61,
+ 0x6e,0x20,0x73,0x68,0x65,0x70,0x68,0x65,0x72,0x64,0x20,0x62,0x75,0x74,0x20,0x6c,
+ 0x6f,0x6f,0x6b,0x73,0x20,0x6d,0x6f,0x72,0x65,0x20,0x6c,0x69,0x6b,0x65,0x20,0x61,
+ 0x20,0x6c,0x6f,0x6e,0x67,0x2d,0x6c,0x65,0x67,0x67,0x65,0x64,0x20,0x66,0x6f,0x78,
+ 0x2e,0x20,0x54,0x68,0x69,0x73,0x20,0x68,0x69,0x67,0x68,0x6c,0x79,0x20,0x65,0x6c,
+ 0x75,0x73,0x69,0x76,0x65,0x20,0x61,0x6e,0x64,0x20,0x73,0x6b,0x69,0x6c,0x6c,0x65,
+ 0x64,0x20,0x6a,0x75,0x6d,0x70,0x65,0x72,0x20,0x69,0x73,0x20,0x63,0x6c,0x61,0x73,
+ 0x73,0x69,0x66,0x69,0x65,0x64,0x20,0x77,0x69,0x74,0x68,0x20,0x77,0x6f,0x6c,0x76,
+ 0x65,0x73,0x2c,0x20,0x63,0x6f,0x79,0x6f,0x74,0x65,0x73,0x2c,0x20,0x6a,0x61,0x63,
+ 0x6b,0x61,0x6c,0x73,0x2c,0x20,0x61,0x6e,0x64,0x20,0x66,0x6f,0x78,0x65,0x73,0x20,
+ 0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x74,0x61,0x78,0x6f,0x6e,0x6f,0x6d,0x69,0x63,
+ 0x20,0x66,0x61,0x6d,0x69,0x6c,0x79,0x20,0x43,0x61,0x6e,0x69,0x64,0x61,0x65,0x2e
+ };
+ const unsigned char expected_ct[] = {
+ 0x45,0x59,0xab,0xba,0x4e,0x48,0xc1,0x61,0x02,0xe8,0xbb,0x2c,0x05,0xe6,0x94,0x7f,
+ 0x50,0xa7,0x86,0xde,0x16,0x2f,0x9b,0x0b,0x7e,0x59,0x2a,0x9b,0x53,0xd0,0xd4,0xe9,
+ 0x8d,0x8d,0x64,0x10,0xd5,0x40,0xa1,0xa6,0x37,0x5b,0x26,0xd8,0x0d,0xac,0xe4,0xfa,
+ 0xb5,0x23,0x84,0xc7,0x31,0xac,0xbf,0x16,0xa5,0x92,0x3c,0x0c,0x48,0xd3,0x57,0x5d,
+ 0x4d,0x0d,0x2c,0x67,0x3b,0x66,0x6f,0xaa,0x73,0x10,0x61,0x27,0x77,0x01,0x09,0x3a,
+ 0x6b,0xf7,0xa1,0x58,0xa8,0x86,0x42,0x92,0xa4,0x1c,0x48,0xe3,0xa9,0xb4,0xc0,0xda,
+ 0xec,0xe0,0xf8,0xd9,0x8d,0x0d,0x7e,0x05,0xb3,0x7a,0x30,0x7b,0xbb,0x66,0x33,0x31,
+ 0x64,0xec,0x9e,0x1b,0x24,0xea,0x0d,0x6c,0x3f,0xfd,0xdc,0xec,0x4f,0x68,0xe7,0x44,
+ 0x30,0x56,0x19,0x3a,0x03,0xc8,0x10,0xe1,0x13,0x44,0xca,0x06,0xd8,0xed,0x8a,0x2b,
+ 0xfb,0x1e,0x8d,0x48,0xcf,0xa6,0xbc,0x0e,0xb4,0xe2,0x46,0x4b,0x74,0x81,0x42,0x40,
+ 0x7c,0x9f,0x43,0x1a,0xee,0x76,0x99,0x60,0xe1,0x5b,0xa8,0xb9,0x68,0x90,0x46,0x6e,
+ 0xf2,0x45,0x75,0x99,0x85,0x23,0x85,0xc6,0x61,0xf7,0x52,0xce,0x20,0xf9,0xda,0x0c,
+ 0x09,0xab,0x6b,0x19,0xdf,0x74,0xe7,0x6a,0x95,0x96,0x74,0x46,0xf8,0xd0,0xfd,0x41,
+ 0x5e,0x7b,0xee,0x2a,0x12,0xa1,0x14,0xc2,0x0e,0xb5,0x29,0x2a,0xe7,0xa3,0x49,0xae,
+ 0x57,0x78,0x20,0xd5,0x52,0x0a,0x1f,0x3f,0xb6,0x2a,0x17,0xce,0x6a,0x7e,0x68,0xfa,
+ 0x7c,0x79,0x11,0x1d,0x88,0x60,0x92,0x0b,0xc0,0x48,0xef,0x43,0xfe,0x84,0x48,0x6c,
+ 0xcb,0x87,0xc2,0x5f,0x0a,0xe0,0x45,0xf0,0xcc,0xe1,0xe7,0x98,0x9a,0x9a,0xa2,0x20,
+ 0xa2,0x8b,0xdd,0x48,0x27,0xe7,0x51,0xa2,0x4a,0x6d,0x5c,0x62,0xd7,0x90,0xa6,0x63,
+ 0x93,0xb9,0x31,0x11,0xc1,0xa5,0x5d,0xd7,0x42,0x1a,0x10,0x18,0x49,0x74,0xc7,0xc5
+ };
+ unsigned long ptlen = sizeof(plaintext);
+ unsigned char ciphertext[304];
+ unsigned char msg2[304];
+ chacha_state st;
+ int err;
+
+ /* encrypt with incremental API */
+ if ((err = xchacha20_setup(&st, key, 32, nonce, 24, 20)) != CRYPT_OK) return err;
+ if ((err = chacha_crypt(&st, plaintext, ptlen, ciphertext)) != CRYPT_OK) return err;
+ if ((err = chacha_done(&st)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(ciphertext, ptlen, expected_ct, ptlen, "XCHACHA20-TV2", 1)) return CRYPT_FAIL_TESTVECTOR;
+
+ /* decrypt and verify round-trip */
+ if ((err = xchacha20_setup(&st, key, 32, nonce, 24, 20)) != CRYPT_OK) return err;
+ if ((err = chacha_crypt(&st, ciphertext, ptlen, msg2)) != CRYPT_OK) return err;
+ if ((err = chacha_done(&st)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(msg2, ptlen, plaintext, ptlen, "XCHACHA20-TV3 (roundtrip)", 1))
+ return CRYPT_FAIL_TESTVECTOR;
+
+ /* round-trip with xchacha20_memory */
+ if ((err = xchacha20_memory(key, 32, 20, nonce, 24, plaintext, ptlen, ciphertext)) != CRYPT_OK) return err;
+ if ((err = xchacha20_memory(key, 32, 20, nonce, 24, ciphertext, ptlen, msg2)) != CRYPT_OK) return err;
+
+ if (ltc_compare_testvector(msg2, ptlen, plaintext, ptlen, "XCHACHA20-TV4 (memory roundtrip)", 1)) return CRYPT_FAIL_TESTVECTOR;
+ }
+
+ return CRYPT_OK;
+
+#endif
+}
+
+#endif /* LTC_XCHACHA20 */
diff --git a/tests/cipher_hash_test.c b/tests/cipher_hash_test.c
index 92e082307..102bc3d96 100644
--- a/tests/cipher_hash_test.c
+++ b/tests/cipher_hash_test.c
@@ -32,6 +32,9 @@ int cipher_hash_test(void)
#ifdef LTC_CHACHA
DO(chacha_test());
#endif
+#ifdef LTC_XCHACHA20
+ DO(xchacha20_test());
+#endif
#ifdef LTC_SALSA20
DO(salsa20_test());
#endif