1 /*
2  * Boost Software License - Version 1.0
3  * https://www.boost.org/LICENSE_1_0.txt
4 */
5 module aegis.aegis128;
6 
7 /// AEGIS C bindings
8 import c.aegisc; // @system
9 import core.stdc.string : memset;
10 
11 /**
12  * AEGIS-128L encryption/decryption state.
13  *
14  * Manages the lifecycle of an `aegis128l_state` object, ensuring proper initialization
15  * and cleanup in a `@nogc` and `@trusted` context.
16  *
17  * Examples:
18  * ---
19  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
20  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
21  * ubyte[] ad = [0x41, 0x42, 0x43]; // Associated data
22  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
23  * ubyte[128] ciphertext;
24  * ubyte[16] mac;
25  * size_t written;
26  *
27  * auto state = Aegis128LState(key, nonce, ad);
28  * state.encryptUpdate(ciphertext[], &written, message);
29  * state.encryptDetachedFinal(ciphertext[written .. $], &written, mac[], 16);
30  * ---
31  */
32 struct Aegis128LState
33 {
34     private aegis128l_state state; // Underlying C state
35     private bool initialized; // Tracks initialization status
36 
37     /**
38      * Initializes the AEGIS-128L state with the provided key, nonce, and associated data.
39      *
40      * Params:
41      *   key = The encryption key (must be `aegis128l_KEYBYTES` long).
42      *   nonce = The nonce (must be `aegis128l_NPUBBYTES` long).
43      *   ad = The associated data (optional, can be empty).
44      *
45      * Throws:
46      *   AssertError if key or nonce lengths are invalid.
47      */
48     @nogc @trusted
49     this(const(ubyte)[] key, const(ubyte)[] nonce, const(ubyte)[] ad = null)
50     {
51         assert(key.length == aegis128l_KEYBYTES, "Key length must be aegis128l_KEYBYTES");
52         assert(nonce.length == aegis128l_NPUBBYTES, "Nonce length must be aegis128l_NPUBBYTES");
53 
54         memset(&state, 0, aegis128l_state.sizeof);
55         aegis128l_state_init(&state, ad.ptr, ad.length, nonce.ptr, key.ptr);
56         initialized = true;
57     }
58 
59     /**
60      * Destructor to clean up the state.
61      */
62     @nogc @trusted ~this()
63     {
64         if (initialized)
65         {
66             memset(&state, 0, aegis128l_state.sizeof);
67             initialized = false;
68         }
69     }
70 
71     /**
72      * Disabled copy constructor to prevent state duplication.
73      */
74     @disable this(this);
75 
76     /**
77      * Encrypts a message chunk, producing ciphertext.
78      *
79      * Params:
80      *   ciphertext = Buffer to store the ciphertext.
81      *   written = Pointer to store the number of bytes written.
82      *   message = The plaintext message to encrypt.
83      *
84      * Returns:
85      *   0 on success, or a negative error code on failure.
86      */
87     @nogc @trusted
88     int encryptUpdate(ubyte[] ciphertext, size_t* written, const(ubyte)[] message)
89     {
90         assert(initialized, "State not initialized");
91         assert(written !is null, "Written pointer cannot be null");
92         return aegis128l_state_encrypt_update(&state, ciphertext.ptr, ciphertext.length,
93             written, message.ptr, message.length);
94     }
95 
96     /**
97      * Finalizes encryption in detached mode, producing the final ciphertext and MAC.
98      *
99      * Params:
100      *   ciphertext = Buffer for remaining ciphertext.
101      *   written = Pointer to store the number of bytes written.
102      *   mac = Buffer for the MAC (must be `aegis128l_ABYTES_MIN` to `aegis128l_ABYTES_MAX`).
103      *   maclen = Length of the MAC.
104      *
105      * Returns:
106      *   0 on success, or a negative error code on failure.
107      */
108     @nogc @trusted
109     int encryptDetachedFinal(ubyte[] ciphertext, size_t* written, ubyte[] mac, size_t maclen)
110     {
111         assert(initialized, "State not initialized");
112         assert(written !is null, "Written pointer cannot be null");
113         assert(maclen >= aegis128l_ABYTES_MIN && maclen <= aegis128l_ABYTES_MAX, "Invalid MAC length");
114         return aegis128l_state_encrypt_detached_final(&state, ciphertext.ptr, ciphertext.length,
115             written, mac.ptr, maclen);
116     }
117 
118     /**
119      * Decrypts a ciphertext chunk, producing plaintext.
120      *
121      * Params:
122      *   plaintext = Buffer to store the plaintext.
123      *   written = Pointer to store the number of bytes written.
124      *   ciphertext = The ciphertext to decrypt.
125      *
126      * Returns:
127      *   0 on success, or a negative error code on failure.
128      */
129     @nogc @trusted
130     int decryptDetachedUpdate(ubyte[] plaintext, size_t* written, const(ubyte)[] ciphertext)
131     {
132         assert(initialized, "State not initialized");
133         assert(written !is null, "Written pointer cannot be null");
134         return aegis128l_state_decrypt_detached_update(&state, plaintext.ptr, plaintext.length,
135             written, ciphertext.ptr, ciphertext.length);
136     }
137 
138     /**
139      * Finalizes decryption in detached mode, verifying the MAC and producing final plaintext.
140      *
141      * Params:
142      *   plaintext = Buffer for remaining plaintext.
143      *   written = Pointer to store the number of bytes written.
144      *   mac = The MAC to verify (must be `aegis128l_ABYTES_MIN` to `aegis128l_ABYTES_MAX`).
145      *   maclen = Length of the MAC.
146      *
147      * Returns:
148      *   0 on success, -1 if MAC verification fails, or another negative error code on failure.
149      */
150     @nogc @trusted
151     int decryptDetachedFinal(ubyte[] plaintext, size_t* written, const(ubyte)[] mac, size_t maclen)
152     {
153         assert(initialized, "State not initialized");
154         assert(written !is null, "Written pointer cannot be null");
155         assert(maclen >= aegis128l_ABYTES_MIN && maclen <= aegis128l_ABYTES_MAX, "Invalid MAC length");
156         return aegis128l_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
157             written, mac.ptr, maclen);
158     }
159 }
160 
161 /**
162  * AEGIS-128L MAC state.
163  *
164  * Manages the lifecycle of an `aegis128l_mac_state` object in a `@nogc` and `@trusted` context.
165  *
166  * Examples:
167  * ---
168  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
169  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
170  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
171  * ubyte[16] mac;
172  *
173  * auto macState = Aegis128LMACState(key, nonce);
174  * macState.update(message);
175  * macState.finalize(mac[], 16);
176  * ---
177  */
178 struct Aegis128LMACState
179 {
180     private aegis128l_mac_state state; // Underlying C MAC state
181     private bool initialized; // Tracks initialization status
182 
183     /**
184      * Initializes the AEGIS-128L MAC state with the provided key and nonce.
185      *
186      * Params:
187      *   key = The key (must be `aegis128l_KEYBYTES` long).
188      *   nonce = The nonce (must be `aegis128l_NPUBBYTES` long).
189      *
190      * Throws:
191      *   AssertError if key or nonce lengths are invalid.
192      */
193     @nogc @trusted
194     this(const(ubyte)[] key, const(ubyte)[] nonce)
195     {
196         assert(key.length == aegis128l_KEYBYTES, "Key length must be aegis128l_KEYBYTES");
197         assert(nonce.length == aegis128l_NPUBBYTES, "Nonce length must be aegis128l_NPUBBYTES");
198 
199         memset(&state, 0, aegis128l_mac_state.sizeof);
200         aegis128l_mac_init(&state, key.ptr, nonce.ptr);
201         initialized = true;
202     }
203 
204     /**
205      * Destructor to clean up the MAC state.
206      */
207     @nogc @trusted ~this()
208     {
209         if (initialized)
210         {
211             aegis128l_mac_reset(&state);
212             memset(&state, 0, aegis128l_mac_state.sizeof);
213             initialized = false;
214         }
215     }
216 
217     /**
218      * Disabled copy constructor to prevent state duplication.
219      */
220     @disable this(this);
221 
222     /**
223      * Updates the MAC state with a message chunk.
224      *
225      * Params:
226      *   message = The message to process.
227      *
228      * Returns:
229      *   0 on success, or a negative error code on failure.
230      */
231     @nogc @trusted
232     int update(const(ubyte)[] message)
233     {
234         assert(initialized, "MAC state not initialized");
235         return aegis128l_mac_update(&state, message.ptr, message.length);
236     }
237 
238     /**
239      * Finalizes the MAC computation, producing the MAC.
240      *
241      * Params:
242      *   mac = Buffer to store the MAC (must be `aegis128l_ABYTES_MIN` to `aegis128l_ABYTES_MAX`).
243      *   maclen = Length of the MAC.
244      *
245      * Returns:
246      *   0 on success, or a negative error code on failure.
247      */
248     @nogc @trusted
249     int finalize(ubyte[] mac, size_t maclen)
250     {
251         assert(initialized, "MAC state not initialized");
252         assert(maclen >= aegis128l_ABYTES_MIN && maclen <= aegis128l_ABYTES_MAX, "Invalid MAC length");
253         return aegis128l_mac_final(&state, mac.ptr, maclen);
254     }
255 
256     /**
257      * Verifies a MAC against the computed MAC.
258      *
259      * Params:
260      *   mac = The MAC to verify (must be `aegis128l_ABYTES_MIN` to `aegis128l_ABYTES_MAX`).
261      *   maclen = Length of the MAC.
262      *
263      * Returns:
264      *   0 if valid, -1 if verification fails, or another negative error code on failure.
265      */
266     @nogc @trusted
267     int verify(const(ubyte)[] mac, size_t maclen)
268     {
269         assert(initialized, "MAC state not initialized");
270         assert(maclen >= aegis128l_ABYTES_MIN && maclen <= aegis128l_ABYTES_MAX, "Invalid MAC length");
271         return aegis128l_mac_verify(&state, mac.ptr, maclen);
272     }
273 
274     /**
275      * Resets the MAC state for reuse with the same key and nonce.
276      */
277     @nogc @trusted
278     void reset()
279     {
280         assert(initialized, "MAC state not initialized");
281         aegis128l_mac_reset(&state);
282     }
283 }
284 
285 /**
286  * AEGIS-128x2 encryption/decryption state.
287  *
288  * Manages the lifecycle of an `aegis128x2_state` object in a `@nogc` and `@trusted` context.
289  *
290  * Examples:
291  * ---
292  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
293  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
294  * ubyte[] ad = [0x41, 0x42, 0x43]; // Associated data
295  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
296  * ubyte[128] ciphertext;
297  * ubyte[16] mac;
298  * size_t written;
299  *
300  * auto state = Aegis128x2State(key, nonce, ad);
301  * state.encryptUpdate(ciphertext[], &written, message);
302  * state.encryptDetachedFinal(ciphertext[written .. $], &written, mac[], 16);
303  * ---
304  */
305 struct Aegis128x2State
306 {
307     private aegis128x2_state state; // Underlying C state
308     private bool initialized; // Tracks initialization status
309 
310     /**
311      * Initializes the AEGIS-128x2 state with the provided key, nonce, and associated data.
312      *
313      * Params:
314      *   key = The encryption key (must be `aegis128x2_KEYBYTES` long).
315      *   nonce = The nonce (must be `aegis128x2_NPUBBYTES` long).
316      *   ad = The associated data (optional, can be empty).
317      *
318      * Throws:
319      *   AssertError if key or nonce lengths are invalid.
320      */
321     @nogc @trusted
322     this(const(ubyte)[] key, const(ubyte)[] nonce, const(ubyte)[] ad = null)
323     {
324         assert(key.length == aegis128x2_KEYBYTES, "Key length must be aegis128x2_KEYBYTES");
325         assert(nonce.length == aegis128x2_NPUBBYTES, "Nonce length must be aegis128x2_NPUBBYTES");
326 
327         memset(&state, 0, aegis128x2_state.sizeof);
328         aegis128x2_state_init(&state, ad.ptr, ad.length, nonce.ptr, key.ptr);
329         initialized = true;
330     }
331 
332     /**
333      * Destructor to clean up the state.
334      */
335     @nogc @trusted ~this()
336     {
337         if (initialized)
338         {
339             memset(&state, 0, aegis128x2_state.sizeof);
340             initialized = false;
341         }
342     }
343 
344     /**
345      * Disabled copy constructor to prevent state duplication.
346      */
347     @disable this(this);
348 
349     /**
350      * Encrypts a message chunk, producing ciphertext.
351      *
352      * Params:
353      *   ciphertext = Buffer to store the ciphertext.
354      *   written = Pointer to store the number of bytes written.
355      *   message = The plaintext message to encrypt.
356      *
357      * Returns:
358      *   0 on success, or a negative error code on failure.
359      */
360     @nogc @trusted
361     int encryptUpdate(ubyte[] ciphertext, size_t* written, const(ubyte)[] message)
362     {
363         assert(initialized, "State not initialized");
364         assert(written !is null, "Written pointer cannot be null");
365         return aegis128x2_state_encrypt_update(&state, ciphertext.ptr, ciphertext.length,
366             written, message.ptr, message.length);
367     }
368 
369     /**
370      * Finalizes encryption in detached mode, producing the final ciphertext and MAC.
371      *
372      * Params:
373      *   ciphertext = Buffer for remaining ciphertext.
374      *   written = Pointer to store the number of bytes written.
375      *   mac = Buffer for the MAC (must be `aegis128x2_ABYTES_MIN` to `aegis128x2_ABYTES_MAX`).
376      *   maclen = Length of the MAC.
377      *
378      * Returns:
379      *   0 on success, or a negative error code on failure.
380      */
381     @nogc @trusted
382     int encryptDetachedFinal(ubyte[] ciphertext, size_t* written, ubyte[] mac, size_t maclen)
383     {
384         assert(initialized, "State not initialized");
385         assert(written !is null, "Written pointer cannot be null");
386         assert(maclen >= aegis128x2_ABYTES_MIN && maclen <= aegis128x2_ABYTES_MAX, "Invalid MAC length");
387         return aegis128x2_state_encrypt_detached_final(&state, ciphertext.ptr, ciphertext.length,
388             written, mac.ptr, maclen);
389     }
390 
391     /**
392      * Decrypts a ciphertext chunk, producing plaintext.
393      *
394      * Params:
395      *   plaintext = Buffer to store the plaintext.
396      *   written = Pointer to store the number of bytes written.
397      *   ciphertext = The ciphertext to decrypt.
398      *
399      * Returns:
400      *   0 on success, or a negative error code on failure.
401      */
402     @nogc @trusted
403     int decryptDetachedUpdate(ubyte[] plaintext, size_t* written, const(ubyte)[] ciphertext)
404     {
405         assert(initialized, "State not initialized");
406         assert(written !is null, "Written pointer cannot be null");
407         return aegis128x2_state_decrypt_detached_update(&state, plaintext.ptr, plaintext.length,
408             written, ciphertext.ptr, ciphertext.length);
409     }
410 
411     /**
412      * Finalizes decryption in detached mode, verifying the MAC and producing final plaintext.
413      *
414      * Params:
415      *   plaintext = Buffer for remaining plaintext.
416      *   written = Pointer to store the number of bytes written.
417      *   mac = The MAC to verify (must be `aegis128x2_ABYTES_MIN` to `aegis128x2_ABYTES_MAX`).
418      *   maclen = Length of the MAC.
419      *
420      * Returns:
421      *   0 on success, -1 if MAC verification fails, or another negative error code on failure.
422      */
423     @nogc @trusted
424     int decryptDetachedFinal(ubyte[] plaintext, size_t* written, const(ubyte)[] mac, size_t maclen)
425     {
426         assert(initialized, "State not initialized");
427         assert(written !is null, "Written pointer cannot be null");
428         assert(maclen >= aegis128x2_ABYTES_MIN && maclen <= aegis128x2_ABYTES_MAX, "Invalid MAC length");
429         return aegis128x2_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
430             written, mac.ptr, maclen);
431     }
432 }
433 
434 /**
435  * AEGIS-128x2 MAC state.
436  *
437  * Manages the lifecycle of an `aegis128x2_mac_state` object in a `@nogc` and `@trusted` context.
438  *
439  * Examples:
440  * ---
441  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
442  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
443  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
444  * ubyte[16] mac;
445  *
446  * auto macState = Aegis128x2MACState(key, nonce);
447  * macState.update(message);
448  * macState.finalize(mac[], 16);
449  * ---
450  */
451 struct Aegis128x2MACState
452 {
453     private aegis128x2_mac_state state; // Underlying C MAC state
454     private bool initialized; // Tracks initialization status
455 
456     /**
457      * Initializes the AEGIS-128x2 MAC state with the provided key and nonce.
458      *
459      * Params:
460      *   key = The key (must be `aegis128x2_KEYBYTES` long).
461      *   nonce = The nonce (must be `aegis128x2_NPUBBYTES` long).
462      *
463      * Throws:
464      *   AssertError if key or nonce lengths are invalid.
465      */
466     @nogc @trusted
467     this(const(ubyte)[] key, const(ubyte)[] nonce)
468     {
469         assert(key.length == aegis128x2_KEYBYTES, "Key length must be aegis128x2_KEYBYTES");
470         assert(nonce.length == aegis128x2_NPUBBYTES, "Nonce length must be aegis128x2_NPUBBYTES");
471 
472         memset(&state, 0, aegis128x2_mac_state.sizeof);
473         aegis128x2_mac_init(&state, key.ptr, nonce.ptr);
474         initialized = true;
475     }
476 
477     /**
478      * Destructor to clean up the MAC state.
479      */
480     @nogc @trusted ~this()
481     {
482         if (initialized)
483         {
484             aegis128x2_mac_reset(&state);
485             memset(&state, 0, aegis128x2_mac_state.sizeof);
486             initialized = false;
487         }
488     }
489 
490     /**
491      * Disabled copy constructor to prevent state duplication.
492      */
493     @disable this(this);
494 
495     /**
496      * Updates the MAC state with a message chunk.
497      *
498      * Params:
499      *   message = The message to process.
500      *
501      * Returns:
502      *   0 on success, or a negative error code on failure.
503      */
504     @nogc @trusted
505     int update(const(ubyte)[] message)
506     {
507         assert(initialized, "MAC state not initialized");
508         return aegis128x2_mac_update(&state, message.ptr, message.length);
509     }
510 
511     /**
512      * Finalizes the MAC computation, producing the MAC.
513      *
514      * Params:
515      *   mac = Buffer to store the MAC (must be `aegis128x2_ABYTES_MIN` to `aegis128x2_ABYTES_MAX`).
516      *   maclen = Length of the MAC.
517      *
518      * Returns:
519      *   0 on success, or a negative error code on failure.
520      */
521     @nogc @trusted
522     int finalize(ubyte[] mac, size_t maclen)
523     {
524         assert(initialized, "MAC state not initialized");
525         assert(maclen >= aegis128x2_ABYTES_MIN && maclen <= aegis128x2_ABYTES_MAX, "Invalid MAC length");
526         return aegis128x2_mac_final(&state, mac.ptr, maclen);
527     }
528 
529     /**
530      * Verifies a MAC against the computed MAC.
531      *
532      * Params:
533      *   mac = The MAC to verify (must be `aegis128x2_ABYTES_MIN` to `aegis128x2_ABYTES_MAX`).
534      *   maclen = Length of the MAC.
535      *
536      * Returns:
537      *   0 if valid, -1 if verification fails, or another negative error code on failure.
538      */
539     @nogc @trusted
540     int verify(const(ubyte)[] mac, size_t maclen)
541     {
542         assert(initialized, "MAC state not initialized");
543         assert(maclen >= aegis128x2_ABYTES_MIN && maclen <= aegis128x2_ABYTES_MAX, "Invalid MAC length");
544         return aegis128x2_mac_verify(&state, mac.ptr, maclen);
545     }
546 
547     /**
548      * Resets the MAC state for reuse with the same key and nonce.
549      */
550     @nogc @trusted
551     void reset()
552     {
553         assert(initialized, "MAC state not initialized");
554         aegis128x2_mac_reset(&state);
555     }
556 }
557 
558 /**
559  * AEGIS-128x4 encryption/decryption state.
560  *
561  * Manages the lifecycle of an `aegis128x4_state` object in a `@nogc` and `@trusted` context.
562  *
563  * Examples:
564  * ---
565  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
566  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
567  * ubyte[] ad = [0x41, 0x42, 0x43]; // Associated data
568  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
569  * ubyte[128] ciphertext;
570  * ubyte[16] mac;
571  * size_t written;
572  *
573  * auto state = Aegis128x4State(key, nonce, ad);
574  * state.encryptUpdate(ciphertext[], &written, message);
575  * state.encryptDetachedFinal(ciphertext[written .. $], &written, mac[], 16);
576  * ---
577  */
578 struct Aegis128x4State
579 {
580     private aegis128x4_state state; // Underlying C state
581     private bool initialized; // Tracks initialization status
582 
583     /**
584      * Initializes the AEGIS-128x4 state with the provided key, nonce, and associated data.
585      *
586      * Params:
587      *   key = The encryption key (must be `aegis128x4_KEYBYTES` long).
588      *   nonce = The nonce (must be `aegis128x4_NPUBBYTES` long).
589      *   ad = The associated data (optional, can be empty).
590      *
591      * Throws:
592      *   AssertError if key or nonce lengths are invalid.
593      */
594     @nogc @trusted
595     this(const(ubyte)[] key, const(ubyte)[] nonce, const(ubyte)[] ad = null)
596     {
597         assert(key.length == aegis128x4_KEYBYTES, "Key length must be aegis128x4_KEYBYTES");
598         assert(nonce.length == aegis128x4_NPUBBYTES, "Nonce length must be aegis128x4_NPUBBYTES");
599 
600         memset(&state, 0, aegis128x4_state.sizeof);
601         aegis128x4_state_init(&state, ad.ptr, ad.length, nonce.ptr, key.ptr);
602         initialized = true;
603     }
604 
605     /**
606      * Destructor to clean up the state.
607      */
608     @nogc @trusted ~this()
609     {
610         if (initialized)
611         {
612             memset(&state, 0, aegis128x4_state.sizeof);
613             initialized = false;
614         }
615     }
616 
617     /**
618      * Disabled copy constructor to prevent state duplication.
619      */
620     @disable this(this);
621 
622     /**
623      * Encrypts a message chunk, producing ciphertext.
624      *
625      * Params:
626      *   ciphertext = Buffer to store the ciphertext.
627      *   written = Pointer to store the number of bytes written.
628      *   message = The plaintext message to encrypt.
629      *
630      * Returns:
631      *   0 on success, or a negative error code on failure.
632      */
633     @nogc @trusted
634     int encryptUpdate(ubyte[] ciphertext, size_t* written, const(ubyte)[] message)
635     {
636         assert(initialized, "State not initialized");
637         assert(written !is null, "Written pointer cannot be null");
638         return aegis128x4_state_encrypt_update(&state, ciphertext.ptr, ciphertext.length,
639             written, message.ptr, message.length);
640     }
641 
642     /**
643      * Finalizes encryption in detached mode, producing the final ciphertext and MAC.
644      *
645      * Params:
646      *   ciphertext = Buffer for remaining ciphertext.
647      *   written = Pointer to store the number of bytes written.
648      *   mac = Buffer for the MAC (must be `aegis128x4_ABYTES_MIN` to `aegis128x4_ABYTES_MAX`).
649      *   maclen = Length of the MAC.
650      *
651      * Returns:
652      *   0 on success, or a negative error code on failure.
653      */
654     @nogc @trusted
655     int encryptDetachedFinal(ubyte[] ciphertext, size_t* written, ubyte[] mac, size_t maclen)
656     {
657         assert(initialized, "State not initialized");
658         assert(written !is null, "Written pointer cannot be null");
659         assert(maclen >= aegis128x4_ABYTES_MIN && maclen <= aegis128x4_ABYTES_MAX, "Invalid MAC length");
660         return aegis128x4_state_encrypt_detached_final(&state, ciphertext.ptr, ciphertext.length,
661             written, mac.ptr, maclen);
662     }
663 
664     /**
665      * Decrypts a ciphertext chunk, producing plaintext.
666      *
667      * Params:
668      *   plaintext = Buffer to store the plaintext.
669      *   written = Pointer to store the number of bytes written.
670      *   ciphertext = The ciphertext to decrypt.
671      *
672      * Returns:
673      *   0 on success, or a negative error code on failure.
674      */
675     @nogc @trusted
676     int decryptDetachedUpdate(ubyte[] plaintext, size_t* written, const(ubyte)[] ciphertext)
677     {
678         assert(initialized, "State not initialized");
679         assert(written !is null, "Written pointer cannot be null");
680         return aegis128x4_state_decrypt_detached_update(&state, plaintext.ptr, plaintext.length,
681             written, ciphertext.ptr, ciphertext.length);
682     }
683 
684     /**
685      * Finalizes decryption in detached mode, verifying the MAC and producing final plaintext.
686      *
687      * Params:
688      *   plaintext = Buffer for remaining plaintext.
689      *   written = Pointer to store the number of bytes written.
690      *   mac = The MAC to verify (must be `aegis128x4_ABYTES_MIN` to `aegis128x4_ABYTES_MAX`).
691      *   maclen = Length of the MAC.
692      *
693      * Returns:
694      *   0 on success, -1 if MAC verification fails, or another negative error code on failure.
695      */
696     @nogc @trusted
697     int decryptDetachedFinal(ubyte[] plaintext, size_t* written, const(ubyte)[] mac, size_t maclen)
698     {
699         assert(initialized, "State not initialized");
700         assert(written !is null, "Written pointer cannot be null");
701         assert(maclen >= aegis128x4_ABYTES_MIN && maclen <= aegis128x4_ABYTES_MAX, "Invalid MAC length");
702         return aegis128x4_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
703             written, mac.ptr, maclen);
704     }
705 }
706 
707 /**
708  * AEGIS-128x4 MAC state.
709  *
710  * Manages the lifecycle of an `aegis128x4_mac_state` object in a `@nogc` and `@trusted` context.
711  *
712  * Examples:
713  * ---
714  * ubyte[16] key = [0x01, 0x02, ..., 0x10];
715  * ubyte[16] nonce = [0x00, 0x01, ..., 0x0F];
716  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
717  * ubyte[16] mac;
718  *
719  * auto macState = Aegis128x4MACState(key, nonce);
720  * macState.update(message);
721  * macState.finalize(mac[], 16);
722  * ---
723  */
724 struct Aegis128x4MACState
725 {
726     private aegis128x4_mac_state state; // Underlying C MAC state
727     private bool initialized; // Tracks initialization status
728 
729     /**
730      * Initializes the AEGIS-128x4 MAC state with the provided key and nonce.
731      *
732      * Params:
733      *   key = The key (must be `aegis128x4_KEYBYTES` long).
734      *   nonce = The nonce (must be `aegis128x4_NPUBBYTES` long).
735      *
736      * Throws:
737      *   AssertError if key or nonce lengths are invalid.
738      */
739     @nogc @trusted
740     this(const(ubyte)[] key, const(ubyte)[] nonce)
741     {
742         assert(key.length == aegis128x4_KEYBYTES, "Key length must be aegis128x4_KEYBYTES");
743         assert(nonce.length == aegis128x4_NPUBBYTES, "Nonce length must be aegis128x4_NPUBBYTES");
744 
745         memset(&state, 0, aegis128x4_mac_state.sizeof);
746         aegis128x4_mac_init(&state, key.ptr, nonce.ptr);
747         initialized = true;
748     }
749 
750     /**
751      * Destructor to clean up the MAC state.
752      */
753     @nogc @trusted ~this()
754     {
755         if (initialized)
756         {
757             aegis128x4_mac_reset(&state);
758             memset(&state, 0, aegis128x4_mac_state.sizeof);
759             initialized = false;
760         }
761     }
762 
763     /**
764      * Disabled copy constructor to prevent state duplication.
765      */
766     @disable this(this);
767 
768     /**
769      * Updates the MAC state with a message chunk.
770      *
771      * Params:
772      *   message = The message to process.
773      *
774      * Returns:
775      *   0 on success, or a negative error code on failure.
776      */
777     @nogc @trusted
778     int update(const(ubyte)[] message)
779     {
780         assert(initialized, "MAC state not initialized");
781         return aegis128x4_mac_update(&state, message.ptr, message.length);
782     }
783 
784     /**
785      * Finalizes the MAC computation, producing the MAC.
786      *
787      * Params:
788      *   mac = Buffer to store the MAC (must be `aegis128x4_ABYTES_MIN` to `aegis128x4_ABYTES_MAX`).
789      *   maclen = Length of the MAC.
790      *
791      * Returns:
792      *   0 on success, or a negative error code on failure.
793      */
794     @nogc @trusted
795     int finalize(ubyte[] mac, size_t maclen)
796     {
797         assert(initialized, "MAC state not initialized");
798         assert(maclen >= aegis128x4_ABYTES_MIN && maclen <= aegis128x4_ABYTES_MAX, "Invalid MAC length");
799         return aegis128x4_mac_final(&state, mac.ptr, maclen);
800     }
801 
802     /**
803      * Verifies a MAC against the computed MAC.
804      *
805      * Params:
806      *   mac = The MAC to verify (must be `aegis128x4_ABYTES_MIN` to `aegis128x4_ABYTES_MAX`).
807      *   maclen = Length of the MAC.
808      *
809      * Returns:
810      *   0 if valid, -1 if verification fails, or another negative error code on failure.
811      */
812     @nogc @trusted
813     int verify(const(ubyte)[] mac, size_t maclen)
814     {
815         assert(initialized, "MAC state not initialized");
816         assert(maclen >= aegis128x4_ABYTES_MIN && maclen <= aegis128x4_ABYTES_MAX, "Invalid MAC length");
817         return aegis128x4_mac_verify(&state, mac.ptr, maclen);
818     }
819 
820     /**
821      * Resets the MAC state for reuse with the same key and nonce.
822      */
823     @nogc @trusted
824     void reset()
825     {
826         assert(initialized, "MAC state not initialized");
827         aegis128x4_mac_reset(&state);
828     }
829 }
830 
831 version (unittest)
832 {
833     @trusted
834     @("AEGIS128")
835     unittest
836     {
837         import std.random : uniform;
838 
839         // Initialize AEGIS library
840         assert(aegis_init() == 0, "AEGIS initialization failed");
841 
842         // Common test data
843         enum size_t bufferSize = 256; // Sufficient for test message and tail bytes
844         ubyte[aegis128l_KEYBYTES] key;
845         foreach (ref k; key)
846             k = cast(ubyte) uniform(0, bufferSize);
847         ubyte[aegis128l_NPUBBYTES] nonce;
848         foreach (ref n; nonce)
849             n = cast(ubyte) uniform(0, bufferSize);
850         ubyte[] ad = cast(ubyte[]) "ABC".ptr;
851         ubyte[] message = cast(ubyte[]) "Hello".ptr;
852         ubyte[bufferSize] ciphertext;
853         ubyte[aegis128l_ABYTES_MIN] macMin;
854         ubyte[aegis128l_ABYTES_MAX] macMax;
855         size_t written;
856 
857         // Test Aegis128LState (encryption/decryption)
858         {
859             // Encryption
860             auto encState = Aegis128LState(key, nonce, ad);
861             written = 0;
862             assert(encState.encryptUpdate(ciphertext[], &written, message) == 0,
863                 "Aegis128LState: Encryption update failed");
864             assert(written <= ciphertext.length, "Aegis128LState: Written bytes exceed buffer");
865             size_t totalWritten = written;
866 
867             assert(encState.encryptDetachedFinal(ciphertext[totalWritten .. $], &written,
868                     macMin[], aegis128l_ABYTES_MIN) == 0, "Aegis128LState: Encryption finalization failed");
869             totalWritten += written;
870             assert(totalWritten <= ciphertext.length, "Aegis128LState: Total written bytes exceed buffer");
871 
872             // Decryption
873             ubyte[bufferSize] decrypted;
874             size_t decWritten;
875             auto decState = Aegis128LState(key, nonce, ad);
876             decWritten = 0;
877             assert(decState.decryptDetachedUpdate(decrypted[], &decWritten, ciphertext[0 .. totalWritten]) == 0,
878                 "Aegis128LState: Decryption update failed");
879             assert(decWritten <= decrypted.length, "Aegis128LState: Decrypted bytes exceed buffer");
880             size_t totalDecWritten = decWritten;
881 
882             assert(decState.decryptDetachedFinal(decrypted[totalDecWritten .. $], &decWritten,
883                     macMin[], aegis128l_ABYTES_MIN) == 0, "Aegis128LState: Decryption finalization failed");
884             totalDecWritten += decWritten;
885             assert(totalDecWritten <= decrypted.length, "Aegis128LState: Total decrypted bytes exceed buffer");
886             assert(totalDecWritten >= message.length, "Aegis128LState: Decrypted length too short");
887             assert(decrypted[0 .. message.length] == message, "Aegis128LState: Decrypted message mismatch");
888         }
889 
890         // Test Aegis128LMACState
891         {
892             import core.stdc.string : memcmp;
893 
894             auto macState = Aegis128LMACState(key, nonce);
895             assert(macState.update(message) == 0, "Aegis128LMACState: MAC update failed");
896             assert(macState.finalize(macMin[], aegis128l_ABYTES_MIN) == 0,
897                 "Aegis128LMACState: MAC finalization (min) failed");
898             assert(macState.finalize(macMax[], aegis128l_ABYTES_MAX) == 0,
899                 "Aegis128LMACState: MAC finalization (max) failed");
900 
901             // Verify MAC (streaming)
902             macState.reset();
903             assert(macState.update(message) == 0, "Aegis128LMACState: MAC update for verification failed");
904             assert(macState.verify(macMin[], aegis128l_ABYTES_MIN) == 0,
905                 "Aegis128LMACState: MAC verification (min) failed");
906 
907             // Test reset
908             macState.reset();
909             assert(macState.update(message) == 0, "Aegis128LMACState: MAC update after reset failed");
910             ubyte[aegis128l_ABYTES_MIN] newMac;
911             assert(macState.finalize(newMac[], aegis128l_ABYTES_MIN) == 0,
912                 "Aegis128LMACState: MAC finalization after reset failed");
913             assert(memcmp(macMin.ptr, newMac.ptr, aegis128l_ABYTES_MIN) == 0,
914                 "Aegis128LMACState: MACs do not match after reset");
915 
916             // Test invalid MAC
917             macState.reset();
918             assert(macState.update(message) == 0, "Aegis128LMACState: MAC update for invalid verification failed");
919             ubyte[aegis128l_ABYTES_MIN] wrongMac = macMin;
920             wrongMac[0] ^= 0xFF;
921             assert(macState.verify(wrongMac[], aegis128l_ABYTES_MIN) == -1,
922                 "Aegis128LMACState: Invalid MAC verification did not fail");
923         }
924     }
925 }