1 // Written in the D programming language.
2 
3 module wrapper.sodium.crypto_aead_chacha20poly1305;
4 
5 import wrapper.sodium.core; // assure sodium got initialized
6 
7 public
8 import  deimos.sodium.crypto_aead_chacha20poly1305 : crypto_aead_chacha20poly1305_ietf_KEYBYTES,
9                                                      crypto_aead_chacha20poly1305_ietf_keybytes,
10                                                      crypto_aead_chacha20poly1305_ietf_NSECBYTES,
11                                                      crypto_aead_chacha20poly1305_ietf_nsecbytes,
12                                                      crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
13                                                      crypto_aead_chacha20poly1305_ietf_npubbytes,
14                                                      crypto_aead_chacha20poly1305_ietf_ABYTES,
15                                                      crypto_aead_chacha20poly1305_ietf_abytes,
16                                                      crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX,
17                                                      crypto_aead_chacha20poly1305_ietf_messagebytes_max,
18 /*                                                   crypto_aead_chacha20poly1305_ietf_encrypt,
19                                                      crypto_aead_chacha20poly1305_ietf_decrypt,
20                                                      crypto_aead_chacha20poly1305_ietf_encrypt_detached,
21                                                      crypto_aead_chacha20poly1305_ietf_decrypt_detached, */
22                                                      crypto_aead_chacha20poly1305_ietf_keygen,
23                                                      crypto_aead_chacha20poly1305_KEYBYTES,
24                                                      crypto_aead_chacha20poly1305_keybytes,
25                                                      crypto_aead_chacha20poly1305_NSECBYTES,
26                                                      crypto_aead_chacha20poly1305_nsecbytes,
27                                                      crypto_aead_chacha20poly1305_NPUBBYTES,
28                                                      crypto_aead_chacha20poly1305_npubbytes,
29                                                      crypto_aead_chacha20poly1305_ABYTES,
30                                                      crypto_aead_chacha20poly1305_abytes,
31                                                      crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX,
32                                                      crypto_aead_chacha20poly1305_messagebytes_max,
33 /*                                                   crypto_aead_chacha20poly1305_encrypt,
34                                                      crypto_aead_chacha20poly1305_decrypt,
35                                                      crypto_aead_chacha20poly1305_encrypt_detached,
36                                                      crypto_aead_chacha20poly1305_decrypt_detached, */
37                                                      crypto_aead_chacha20poly1305_keygen;
38 
39 
40 import std.exception : assertThrown, assertNotThrown;
41 import nogc.exception: enforce;
42 
43 // overloading some functions between module deimos.sodium.crypto_aead_chacha20poly1305 and this module
44 
45 alias crypto_aead_chacha20poly1305_ietf_encrypt = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_ietf_encrypt;
46 
47 /**
48  * The function crypto_aead_chacha20poly1305_ietf_encrypt()
49  * encrypts a message `m` using a secret key `k` (crypto_aead_chacha20poly1305_ietf_KEYBYTES bytes)
50  * and a public nonce `npub` (crypto_aead_chacha20poly1305_ietf_NPUBBYTES bytes).
51  * The encrypted message, as well as a tag authenticating both the confidential message m and ad.length bytes of non-confidential data `ad`,
52  * are put into `c`.
53  * ad can also be an empty array if no additional data are required.
54  * At most m.length + crypto_aead_chacha20poly1305_ietf_ABYTES bytes are put into `c`.
55  * The function always returns true.
56  * The public nonce npub should never ever be reused with the same key. The recommended way to generate it is to use
57  * randombytes_buf() for the first message, and then to increment it for each subsequent message using the same key.
58  */
59 bool crypto_aead_chacha20poly1305_ietf_encrypt(scope ubyte[] c,
60                                                const scope ubyte[] m,
61                                                const scope ubyte[] ad,
62                                                const ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] npub,
63                                                const ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
64 {
65 //  enforce(m.length, "Error invoking crypto_aead_chacha20poly1305_ietf_encrypt: m is null"); // TODO check if m.ptr==null would be okay
66   enforce(m.length <=  ulong.max - crypto_aead_chacha20poly1305_ietf_ABYTES);
67   const  c_expect_len = m.length + crypto_aead_chacha20poly1305_ietf_ABYTES;
68 //  enforce(c.length == c_expect_len, "Expected c.length: ", c.length, " to be equal to m.length + crypto_aead_chacha20poly1305_ietf_ABYTES: ", c_expect_len);
69   enforce(c.length == c_expect_len, "Expected c.length is not equal to m.length + crypto_aead_chacha20poly1305_ietf_ABYTES");
70   ulong clen_p;
71   bool result = crypto_aead_chacha20poly1305_ietf_encrypt(c.ptr, &clen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
72   if (result)
73     assert(clen_p ==  c_expect_len); // okay to be removed in release code
74   return  result;
75 }
76 
77 alias crypto_aead_chacha20poly1305_ietf_decrypt = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_ietf_decrypt;
78 
79 /**
80  * The function crypto_aead_chacha20poly1305_ietf_decrypt()
81   verifies that the ciphertext `c` (as produced by crypto_aead_chacha20poly1305_ietf_encrypt()),
82  * includes a valid tag using a secret key `k`, a public nonce `npub`, and additional data `ad`. c.length is the ciphertext length
83  * in bytes with the authenticator, so it has to be at least crypto_aead_chacha20poly1305_ietf_ABYTES.
84  *
85  * ad can be an empty array if no additional data are required.
86  * The function returns false if the verification fails.
87  * If the verification succeeds, the function returns true, puts the decrypted message into `m` and stores its actual number of bytes into `mlen_p`.
88  * At most c.length - crypto_aead_chacha20poly1305_ietf_ABYTES bytes will be put into m.
89  */
90 bool crypto_aead_chacha20poly1305_ietf_decrypt(scope ubyte[] m,
91                                                const scope ubyte[] c,
92                                                const scope ubyte[] ad,
93                                                const ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] npub,
94                                                const ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
95 {
96 //  enforce(c.length >= crypto_aead_chacha20poly1305_ietf_ABYTES, "Expected c.length: ", c.length, " to be greater_equal to crypto_aead_chacha20poly1305_ietf_ABYTES: ", crypto_aead_chacha20poly1305_ietf_ABYTES);
97   enforce(c.length >= crypto_aead_chacha20poly1305_ietf_ABYTES, "Expected c.length is not greater_equal to crypto_aead_chacha20poly1305_ietf_ABYTES");
98   const  m_expect_len = c.length - crypto_aead_chacha20poly1305_ietf_ABYTES;
99 //  enforce(m.length == m_expect_len, "Expected m.length: ", m.length, " to be equal to c.length - crypto_aead_chacha20poly1305_ietf_ABYTES: ", m_expect_len);
100   enforce(m.length == m_expect_len, "Expected m.length is not equal to c.length - crypto_aead_chacha20poly1305_ietf_ABYTES");
101   ulong mlen_p;
102   bool result = crypto_aead_chacha20poly1305_ietf_decrypt(m.ptr, &mlen_p, null, c.ptr, c.length, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
103   if (result)
104     assert(mlen_p ==  m_expect_len); // okay to be removed in release code
105   return  result;
106 }
107 
108 alias crypto_aead_chacha20poly1305_ietf_encrypt_detached = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_ietf_encrypt_detached;
109 
110 bool  crypto_aead_chacha20poly1305_ietf_encrypt_detached(scope ubyte[] c,
111                                                          out ubyte[crypto_aead_chacha20poly1305_ietf_ABYTES] mac,
112                                                          const scope ubyte[] m,
113                                                          const scope ubyte[] ad,
114                                                          const ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] npub,
115                                                          const ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
116 {
117 //  enforce(m.length, "Error invoking crypto_aead_chacha20poly1305_ietf_encrypt_detached: m is null"); // TODO check if m.ptr==null would be okay
118 //  enforce(c.length == m.length, "Expected c.length: ", c.length, " to be equal to m.length: ", m.length);
119   enforce(c.length == m.length, "Expected c.length is not equal to m.length");
120   ulong maclen_p;
121   bool result = crypto_aead_chacha20poly1305_ietf_encrypt_detached(c.ptr, mac.ptr, &maclen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
122   assert(maclen_p == crypto_aead_chacha20poly1305_ietf_ABYTES); // okay to be removed in release code
123   return  result;
124 }
125 
126 alias crypto_aead_chacha20poly1305_ietf_decrypt_detached = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_ietf_decrypt_detached;
127 
128 bool  crypto_aead_chacha20poly1305_ietf_decrypt_detached(scope ubyte[] m,
129                                                          const scope ubyte[] c,
130                                                          const ubyte[crypto_aead_chacha20poly1305_ietf_ABYTES] mac,
131                                                          const scope ubyte[] ad,
132                                                          const ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] npub,
133                                                          const ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
134 {
135 //  enforce(c.length, "Error invoking crypto_aead_chacha20poly1305_ietf_decrypt_detached: c is null"); // TODO check if c.ptr==null would be okay
136 //  enforce(m.length == c.length, "Expected m.length: ", m.length, " to be equal to c.length: ", c.length);
137   enforce(m.length == c.length, "Expected m.length is not equal to c.length");
138   return  crypto_aead_chacha20poly1305_ietf_decrypt_detached(m.ptr, null, c.ptr, c.length, mac.ptr, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
139 }
140 
141 /* -- Original ChaCha20-Poly1305 construction with a 64-bit nonce and a 64-bit internal counter -- */
142 
143 alias crypto_aead_chacha20poly1305_encrypt = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_encrypt;
144 
145 bool  crypto_aead_chacha20poly1305_encrypt(scope ubyte[] c,
146                                            const scope ubyte[] m,
147                                            const scope ubyte[] ad,
148                                            const ubyte[crypto_aead_chacha20poly1305_NPUBBYTES] npub,
149                                            const ubyte[crypto_aead_chacha20poly1305_KEYBYTES] k) @nogc @trusted
150 {
151 //  enforce(m.length, "Error invoking crypto_aead_chacha20poly1305_encrypt: m is null"); // TODO check if m.ptr==null would be okay
152   const  c_expect_len = m.length + crypto_aead_chacha20poly1305_ABYTES;
153 //  enforce(c.length == c_expect_len, "Expected c.length: ", c.length, " to be equal to m.length + crypto_aead_chacha20poly1305_ABYTES: ", c_expect_len);
154   enforce(c.length == c_expect_len, "Expected c.length is not equal to m.length + crypto_aead_chacha20poly1305_ABYTES");
155   ulong clen_p;
156   bool result = crypto_aead_chacha20poly1305_encrypt(c.ptr, &clen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
157   if (result)
158     assert(clen_p ==  c_expect_len); // okay to be removed in release code
159   return  result;
160 }
161 
162 alias crypto_aead_chacha20poly1305_decrypt = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_decrypt;
163 
164 bool  crypto_aead_chacha20poly1305_decrypt(scope ubyte[] m,
165                                            const scope ubyte[] c,
166                                            const scope ubyte[] ad,
167                                            const ubyte[crypto_aead_chacha20poly1305_NPUBBYTES] npub,
168                                            const ubyte[crypto_aead_chacha20poly1305_KEYBYTES] k) @nogc @trusted
169 {
170 //  enforce(c.length >= crypto_aead_chacha20poly1305_ABYTES, "Expected c.length: ", c.length, " to be greater_equal to crypto_aead_chacha20poly1305_ABYTES: ", crypto_aead_chacha20poly1305_ABYTES);
171   enforce(c.length >= crypto_aead_chacha20poly1305_ABYTES, "Expected c.length is not greater_equal to crypto_aead_chacha20poly1305_ABYTES");
172   const  m_expect_len = c.length - crypto_aead_chacha20poly1305_ABYTES;
173 //  enforce(m.length == m_expect_len, "Expected m.length: ", m.length, " to be equal to c.length - crypto_aead_chacha20poly1305_ABYTES: ", m_expect_len);
174   enforce(m.length == m_expect_len, "Expected m.length is not equal to c.length - crypto_aead_chacha20poly1305_ABYTES");
175   ulong mlen_p;
176   bool result = crypto_aead_chacha20poly1305_decrypt(m.ptr, &mlen_p, null, c.ptr, c.length, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
177   if (result)
178     assert(mlen_p ==  m_expect_len); // okay to be removed in release code
179   return  result;
180 }
181 
182 alias crypto_aead_chacha20poly1305_encrypt_detached = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_encrypt_detached;
183 
184 bool  crypto_aead_chacha20poly1305_encrypt_detached(scope ubyte[] c,
185                                                     out ubyte[crypto_aead_chacha20poly1305_ABYTES] mac,
186                                                     const scope ubyte[] m,
187                                                     const scope ubyte[] ad,
188                                                     const ubyte[crypto_aead_chacha20poly1305_NPUBBYTES] npub,
189                                                     const ubyte[crypto_aead_chacha20poly1305_KEYBYTES] k) @nogc @trusted
190 {
191 //  enforce(m.length, "Error invoking crypto_aead_chacha20poly1305_encrypt_detached: m is null"); // TODO check if m.ptr==null would be okay
192 //  enforce(c.length == m.length, "Expected c.length: ", c.length, " to be equal to m.length: ", m.length);
193   enforce(c.length == m.length, "Expected c.length is not equal to m.length");
194   ulong maclen_p;
195   bool result = crypto_aead_chacha20poly1305_encrypt_detached(c.ptr, mac.ptr, &maclen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
196   assert(maclen_p == crypto_aead_chacha20poly1305_ABYTES); // okay to be removed in release code
197   return  result;
198 }
199 
200 alias crypto_aead_chacha20poly1305_decrypt_detached = deimos.sodium.crypto_aead_chacha20poly1305.crypto_aead_chacha20poly1305_decrypt_detached;
201 
202 bool  crypto_aead_chacha20poly1305_decrypt_detached(scope ubyte[] m,
203                                                     const scope ubyte[] c,
204                                                     const ubyte[crypto_aead_chacha20poly1305_ABYTES] mac,
205                                                     const scope ubyte[] ad,
206                                                     const ubyte[crypto_aead_chacha20poly1305_NPUBBYTES] npub,
207                                                     const ubyte[crypto_aead_chacha20poly1305_KEYBYTES] k) @nogc @trusted
208 {
209 //  enforce(c.length, "Error invoking crypto_aead_chacha20poly1305_decrypt_detached: c is null"); // TODO check if c.ptr==null would be okay
210 //  enforce(m.length == c.length, "Expected m.length: ", m.length, " to be equal to c.length: ", c.length);
211   enforce(m.length == c.length, "Expected m.length is not equal to c.length");
212   return  crypto_aead_chacha20poly1305_decrypt_detached(m.ptr, null, c.ptr, c.length, mac.ptr, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
213 }
214 
215 
216 version(unittest)
217 {
218   import wrapper.sodium.randombytes : randombytes;
219   // share a key and nonce in the following unittests
220   ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]  nonce = void;
221   ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES]   key   = void;
222 
223   ubyte[crypto_aead_chacha20poly1305_NPUBBYTES]  nonce2 = void;
224   ubyte[crypto_aead_chacha20poly1305_KEYBYTES]   key2   = void;
225 
226   static this() {
227     randombytes(nonce);
228     randombytes(key);
229     randombytes(nonce2);
230     randombytes(key2);
231   }
232 }
233 
234 
235 @system
236 unittest
237 {
238   import std..string : representation;
239   import std.stdio : writeln;
240   debug writeln("unittest block 1 from sodium.crypto_aead_chacha20poly1305.d");
241 
242   auto message         = representation("test");
243   enum message_len = 4;
244   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
245     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
246   ubyte[message_len + crypto_aead_chacha20poly1305_ietf_ABYTES]  ciphertext;
247   ulong   ciphertext_len;
248 
249   crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext.ptr, &ciphertext_len, message.ptr, message.length,
250     additional_data.ptr, additional_data.length, null, nonce.ptr, key.ptr);
251 
252   ubyte[message_len] decrypted;
253   ulong decrypted_len;
254   assert(ciphertext_len==ciphertext.length);
255   assert(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted.ptr, &decrypted_len, null, ciphertext.ptr, ciphertext_len,
256       additional_data.ptr, additional_data.length, nonce.ptr, key.ptr) == 0);
257   assert(decrypted == message); //writeln("Decrypted message (aead_chacha20poly1305): ", cast(string)decrypted);
258   assert(decrypted_len == decrypted.length);
259 
260   // test null for &ciphertext_len / decrypted_len
261   crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext.ptr, null, message.ptr, message.length,
262     additional_data.ptr, additional_data.length, null, nonce.ptr, key.ptr);
263   crypto_aead_chacha20poly1305_ietf_decrypt(decrypted.ptr, null, null, ciphertext.ptr, ciphertext_len,
264     additional_data.ptr, additional_data.length, nonce.ptr, key.ptr);
265 
266   ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES] k;
267   crypto_aead_chacha20poly1305_ietf_keygen(k);
268   ubyte[crypto_aead_chacha20poly1305_KEYBYTES] k2;
269   crypto_aead_chacha20poly1305_keygen(k2);
270 
271 }
272 
273 @safe
274 unittest
275 {
276   import std..string : representation;
277   import std.stdio : writeln, writefln;
278   import wrapper.sodium.utils : sodium_increment;
279   debug writeln("unittest block 2 from sodium.crypto_aead_chacha20poly1305.d");
280 
281   assert(crypto_aead_chacha20poly1305_ietf_keybytes()         == crypto_aead_chacha20poly1305_ietf_KEYBYTES);
282   assert(crypto_aead_chacha20poly1305_ietf_nsecbytes()        == crypto_aead_chacha20poly1305_ietf_NSECBYTES);
283   assert(crypto_aead_chacha20poly1305_ietf_npubbytes()        == crypto_aead_chacha20poly1305_ietf_NPUBBYTES);
284   assert(crypto_aead_chacha20poly1305_ietf_abytes()           == crypto_aead_chacha20poly1305_ietf_ABYTES);
285   assert(crypto_aead_chacha20poly1305_ietf_messagebytes_max() == crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX); // see travis Build #74
286 //  debug writeln("crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX:   ", crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX);
287 //  debug writeln("crypto_aead_chacha20poly1305_ietf_messagebytes_max(): ", crypto_aead_chacha20poly1305_ietf_messagebytes_max());
288 
289   auto message         = representation("test");
290   enum message_len = 4;
291   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
292     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
293   ubyte[message_len + crypto_aead_chacha20poly1305_ietf_ABYTES]  ciphertext1;
294   sodium_increment(nonce);
295 
296   assertThrown   (crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext1[0..$-1],              message, additional_data, nonce, key));
297   assertNotThrown(crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext1[0..$-message.length], null,    additional_data, nonce, key));
298   assertNotThrown(crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext1,                      message, null,            nonce, key));
299 
300   assert(crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext1, message, additional_data, nonce, key));
301 
302   ubyte[message_len] decrypted;
303   assertThrown   (crypto_aead_chacha20poly1305_ietf_decrypt(decrypted,         ciphertext1[0..crypto_aead_chacha20poly1305_ietf_ABYTES-1], additional_data, nonce, key));
304   assertThrown   (crypto_aead_chacha20poly1305_ietf_decrypt(decrypted[0..$-1], ciphertext1,                                                additional_data, nonce, key));
305   assertNotThrown(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted,         ciphertext1,                                                null,            nonce, key));
306 
307   assert(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, ciphertext1, additional_data, nonce, key));
308   assert(decrypted == message);
309 //
310   ubyte[message_len] ciphertext2;
311   ubyte[crypto_aead_chacha20poly1305_ietf_ABYTES] mac;
312   sodium_increment(nonce);
313 
314   assertThrown   (crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext2[0..$-1],              mac, message, additional_data, nonce, key));
315   assertNotThrown(crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext2[0..$-message.length], mac, null,    additional_data, nonce, key));
316   assertNotThrown(crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext2,                      mac, message, null,            nonce, key));
317 
318   assert(crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext2, mac, message, additional_data, nonce, key));
319 
320   assertThrown   (crypto_aead_chacha20poly1305_ietf_decrypt_detached(decrypted[0..$-1],              ciphertext2, mac, additional_data, nonce, key));
321   assertNotThrown(crypto_aead_chacha20poly1305_ietf_decrypt_detached(decrypted[0..$-message.length], null,       mac, additional_data, nonce, key));
322   assertNotThrown(crypto_aead_chacha20poly1305_ietf_decrypt_detached(decrypted,                      ciphertext2, mac, null,            nonce, key));
323 
324   assert(crypto_aead_chacha20poly1305_ietf_decrypt_detached(decrypted, ciphertext2, mac, additional_data, nonce, key));
325   assert(decrypted == message);
326 
327 }
328 
329 @safe
330 unittest
331 {
332   import std..string : representation;
333   import std.stdio : writeln, writefln;
334   import wrapper.sodium.utils : sodium_increment;
335   debug writeln("unittest block 3 from sodium.crypto_aead_chacha20poly1305.d");
336 
337   assert(crypto_aead_chacha20poly1305_keybytes()         == crypto_aead_chacha20poly1305_KEYBYTES);
338   assert(crypto_aead_chacha20poly1305_nsecbytes()        == crypto_aead_chacha20poly1305_NSECBYTES);
339   assert(crypto_aead_chacha20poly1305_npubbytes()        == crypto_aead_chacha20poly1305_NPUBBYTES);
340   assert(crypto_aead_chacha20poly1305_abytes()           == crypto_aead_chacha20poly1305_ABYTES);
341   assert(crypto_aead_chacha20poly1305_messagebytes_max() == crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX);
342 
343   auto message         = representation("test");
344   enum message_len = 4;
345   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
346     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
347   ubyte[message_len + crypto_aead_chacha20poly1305_ABYTES] ciphertext1;
348   sodium_increment(nonce2);
349 
350   assertThrown   (crypto_aead_chacha20poly1305_encrypt(ciphertext1[0..$-1],              message, additional_data, nonce2, key2));
351   assertNotThrown(crypto_aead_chacha20poly1305_encrypt(ciphertext1[0..$-message.length], null,    additional_data, nonce2, key2));
352   assertNotThrown(crypto_aead_chacha20poly1305_encrypt(ciphertext1,                      message, null,            nonce2, key2));
353 
354   assert(crypto_aead_chacha20poly1305_encrypt(ciphertext1, message, additional_data, nonce2, key2));
355 
356   ubyte[message_len] decrypted;
357   assertThrown   (crypto_aead_chacha20poly1305_decrypt(decrypted,         ciphertext1[0..crypto_aead_chacha20poly1305_ABYTES-1], additional_data, nonce2, key2));
358   assertThrown   (crypto_aead_chacha20poly1305_decrypt(decrypted[0..$-1], ciphertext1,                                           additional_data, nonce2, key2));
359   assertNotThrown(crypto_aead_chacha20poly1305_decrypt(decrypted,         ciphertext1,                                           null,            nonce2, key2));
360 
361   assert(crypto_aead_chacha20poly1305_decrypt(decrypted, ciphertext1, additional_data, nonce2, key2));
362   assert(decrypted == message);
363 //
364   ubyte[message_len]  ciphertext2;
365   ubyte[crypto_aead_chacha20poly1305_ABYTES] mac;
366   sodium_increment(nonce2);
367 
368   assertThrown   (crypto_aead_chacha20poly1305_encrypt_detached(ciphertext2[0..$-1],              mac, message, additional_data, nonce2, key2));
369   assertNotThrown(crypto_aead_chacha20poly1305_encrypt_detached(ciphertext2[0..$-message.length], mac, null,    additional_data, nonce2, key2));
370   assertNotThrown(crypto_aead_chacha20poly1305_encrypt_detached(ciphertext2,                      mac, message, null,            nonce2, key2));
371 
372   assert(crypto_aead_chacha20poly1305_encrypt_detached(ciphertext2, mac, message, additional_data, nonce2, key2));
373 
374   assertThrown   (crypto_aead_chacha20poly1305_decrypt_detached(decrypted[0..$-1],              ciphertext2, mac, additional_data, nonce2, key2));
375   assertNotThrown(crypto_aead_chacha20poly1305_decrypt_detached(decrypted[0..$-message.length], null,       mac, additional_data, nonce2, key2));
376   assertNotThrown(crypto_aead_chacha20poly1305_decrypt_detached(decrypted,                      ciphertext2, mac, null,            nonce2, key2));
377 
378   assert(crypto_aead_chacha20poly1305_decrypt_detached(decrypted, ciphertext2, mac, additional_data, nonce2, key2));
379   assert(decrypted == message);
380 }
381 
382 
383 
384 @nogc @safe
385 unittest
386 {
387   // usage is not cryptographically safe here; it's purpose is to test @nogc @safe
388   import std..string : representation;
389   ubyte[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]  n1 = nonce;
390   ubyte[crypto_aead_chacha20poly1305_ietf_KEYBYTES]   k1;
391   crypto_aead_chacha20poly1305_ietf_keygen(k1);
392   ubyte[4] message  = [116, 101, 115, 116]; //representation("test");
393   ubyte[4] decrypted;
394   enum  m_len  = 4UL;
395   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
396     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
397   ubyte[m_len+crypto_aead_chacha20poly1305_ietf_ABYTES] ciphertext1;
398   ubyte[m_len]                                          ciphertext2;
399   ubyte[      crypto_aead_chacha20poly1305_ietf_ABYTES] mac1;
400 
401   assert(crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext1,   message, additional_data, n1, k1));
402   assert(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, ciphertext1, additional_data, n1, k1));
403   assert(decrypted == message);
404   decrypted = decrypted.init;
405   assert(crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext2, mac1,   message, additional_data, n1, k1));
406   assert(crypto_aead_chacha20poly1305_ietf_decrypt_detached(decrypted, ciphertext2, mac1, additional_data, n1, k1));
407   assert(decrypted == message);
408   ubyte[crypto_aead_chacha20poly1305_NPUBBYTES]  n2 = nonce[0..crypto_aead_chacha20poly1305_NPUBBYTES];
409   ubyte[crypto_aead_chacha20poly1305_KEYBYTES]   k2;
410   crypto_aead_chacha20poly1305_keygen(k2);
411   ubyte[m_len+crypto_aead_chacha20poly1305_ABYTES] ciphertext3;
412   ubyte[m_len]                                     ciphertext4;
413   ubyte[      crypto_aead_chacha20poly1305_ABYTES] mac2;
414   decrypted = decrypted.init;
415   assert(crypto_aead_chacha20poly1305_encrypt(ciphertext3,   message, additional_data, n2, k2));
416   assert(crypto_aead_chacha20poly1305_decrypt(decrypted, ciphertext3, additional_data, n2, k2));
417   assert(decrypted == message);
418   decrypted = decrypted.init;
419   assert(crypto_aead_chacha20poly1305_encrypt_detached(ciphertext4, mac2,   message, additional_data, n2, k2));
420   assert(crypto_aead_chacha20poly1305_decrypt_detached(decrypted, ciphertext4, mac2, additional_data, n2, k2));
421   assert(decrypted == message);
422 }