1 /*
2  * Boost Software License - Version 1.0
3  * https://www.boost.org/LICENSE_1_0.txt
4 */
5 module aegis.aegis256;
6 
7 /// AEGIS C bindings
8 import c.aegisc; // @system
9 import core.stdc.string : memset;
10 
11 /**
12  * AEGIS-256 encryption/decryption state.
13  *
14  * Manages the lifecycle of an `aegis256_state` object, ensuring proper initialization
15  * and cleanup in a `@nogc` and `@trusted` context.
16  *
17  * Examples:
18  * ---
19  * ubyte[32] key = [0x01, 0x02, ..., 0x20];
20  * ubyte[32] nonce = [0x00, 0x01, ..., 0x1F];
21  * ubyte[] ad = [0x41, 0x42, 0x43]; // "ABC"
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 = Aegis256State(key, nonce, ad);
28  * state.encryptUpdate(ciphertext[], &written, message);
29  * state.encryptDetachedFinal(ciphertext[written .. $], &written, mac[], 16);
30  * ---
31  */
32 struct Aegis256State
33 {
34     private aegis256_state state; // Underlying C state
35     private bool initialized; // Tracks initialization status
36 
37     /**
38      * Initializes the AEGIS-256 state with the provided key, nonce, and associated data.
39      *
40      * Params:
41      *   key = The encryption key (must be `aegis256_KEYBYTES` long).
42      *   nonce = The nonce (must be `aegis256_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 == aegis256_KEYBYTES, "Key length must be aegis256_KEYBYTES");
52         assert(nonce.length == aegis256_NPUBBYTES, "Nonce length must be aegis256_NPUBBYTES");
53 
54         memset(&state, 0, aegis256_state.sizeof);
55         aegis256_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, aegis256_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 aegis256_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 `aegis256_ABYTES_MIN` to `aegis256_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 >= aegis256_ABYTES_MIN && maclen <= aegis256_ABYTES_MAX, "Invalid MAC length");
114         return aegis256_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 aegis256_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 `aegis256_ABYTES_MIN` to `aegis256_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 >= aegis256_ABYTES_MIN && maclen <= aegis256_ABYTES_MAX, "Invalid MAC length");
156         return aegis256_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
157             written, mac.ptr, maclen);
158     }
159 }
160 
161 /**
162  * AEGIS-256 MAC state.
163  *
164  * Manages the lifecycle of an `aegis256_mac_state` object in a `@nogc` and `@trusted` context.
165  *
166  * Examples:
167  * ---
168  * ubyte[32] key = [0x01, 0x02, ..., 0x20];
169  * ubyte[32] nonce = [0x00, 0x01, ..., 0x1F];
170  * ubyte[] message = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
171  * ubyte[16] mac;
172  *
173  * auto macState = Aegis256MACState(key, nonce);
174  * macState.update(message);
175  * macState.finalize(mac[], 16);
176  * ---
177  */
178 struct Aegis256MACState
179 {
180     private aegis256_mac_state state; // Underlying C MAC state
181     private bool initialized; // Tracks initialization status
182 
183     /**
184      * Initializes the AEGIS-256 MAC state with the provided key and nonce.
185      *
186      * Params:
187      *   key = The key (must be `aegis256_KEYBYTES` long).
188      *   nonce = The nonce (must be `aegis256_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 == aegis256_KEYBYTES, "Key length must be aegis256_KEYBYTES");
197         assert(nonce.length == aegis256_NPUBBYTES, "Nonce length must be aegis256_NPUBBYTES");
198 
199         memset(&state, 0, aegis256_mac_state.sizeof);
200         aegis256_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             aegis256_mac_reset(&state);
212             memset(&state, 0, aegis256_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 aegis256_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 `aegis256_ABYTES_MIN` to `aegis256_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 >= aegis256_ABYTES_MIN && maclen <= aegis256_ABYTES_MAX, "Invalid MAC length");
253         return aegis256_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 `aegis256_ABYTES_MIN` to `aegis256_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 >= aegis256_ABYTES_MIN && maclen <= aegis256_ABYTES_MAX, "Invalid MAC length");
271         return aegis256_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         aegis256_mac_reset(&state);
282     }
283 }
284 
285 /**
286  * AEGIS-256x2 encryption/decryption state.
287  *
288  * Manages the lifecycle of an `aegis256x2_state` object in a `@nogc` and `@trusted` context.
289  */
290 struct Aegis256x2State
291 {
292     private aegis256x2_state state; // Underlying C state
293     private bool initialized; // Tracks initialization status
294 
295     /**
296      * Initializes the AEGIS-256x2 state with the provided key, nonce, and associated data.
297      *
298      * Params:
299      *   key = The encryption key (must be `aegis256x2_KEYBYTES` long).
300      *   nonce = The nonce (must be `aegis256x2_NPUBBYTES` long).
301      *   ad = The associated data (optional, can be empty).
302      *
303      * Throws:
304      *   AssertError if key or nonce lengths are invalid.
305      */
306     @nogc @trusted
307     this(const(ubyte)[] key, const(ubyte)[] nonce, const(ubyte)[] ad = null)
308     {
309         assert(key.length == aegis256x2_KEYBYTES, "Key length must be aegis256x2_KEYBYTES");
310         assert(nonce.length == aegis256x2_NPUBBYTES, "Nonce length must be aegis256x2_NPUBBYTES");
311 
312         memset(&state, 0, aegis256x2_state.sizeof);
313         aegis256x2_state_init(&state, ad.ptr, ad.length, nonce.ptr, key.ptr);
314         initialized = true;
315     }
316 
317     /**
318      * Destructor to clean up the state.
319      */
320     @nogc @trusted ~this()
321     {
322         if (initialized)
323         {
324             memset(&state, 0, aegis256x2_state.sizeof);
325             initialized = false;
326         }
327     }
328 
329     /**
330      * Disabled copy constructor to prevent state duplication.
331      */
332     @disable this(this);
333 
334     /**
335      * Encrypts a message chunk, producing ciphertext.
336      *
337      * Params:
338      *   ciphertext = Buffer to store the ciphertext.
339      *   written = Pointer to store the number of bytes written.
340      *   message = The plaintext message to encrypt.
341      *
342      * Returns:
343      *   0 on success, or a negative error code on failure.
344      */
345     @nogc @trusted
346     int encryptUpdate(ubyte[] ciphertext, size_t* written, const(ubyte)[] message)
347     {
348         assert(initialized, "State not initialized");
349         assert(written !is null, "Written pointer cannot be null");
350         return aegis256x2_state_encrypt_update(&state, ciphertext.ptr, ciphertext.length,
351             written, message.ptr, message.length);
352     }
353 
354     /**
355      * Finalizes encryption in detached mode, producing the final ciphertext and MAC.
356      *
357      * Params:
358      *   ciphertext = Buffer for remaining ciphertext.
359      *   written = Pointer to store the number of bytes written.
360      *   mac = Buffer for the MAC (must be `aegis256x2_ABYTES_MIN` to `aegis256x2_ABYTES_MAX`).
361      *   maclen = Length of the MAC.
362      *
363      * Returns:
364      *   0 on success, or a negative error code on failure.
365      */
366     @nogc @trusted
367     int encryptDetachedFinal(ubyte[] ciphertext, size_t* written, ubyte[] mac, size_t maclen)
368     {
369         assert(initialized, "State not initialized");
370         assert(written !is null, "Written pointer cannot be null");
371         assert(maclen >= aegis256x2_ABYTES_MIN && maclen <= aegis256x2_ABYTES_MAX, "Invalid MAC length");
372         return aegis256x2_state_encrypt_detached_final(&state, ciphertext.ptr, ciphertext.length,
373             written, mac.ptr, maclen);
374     }
375 
376     /**
377      * Decrypts a ciphertext chunk, producing plaintext.
378      *
379      * Params:
380      *   plaintext = Buffer to store the plaintext.
381      *   written = Pointer to store the number of bytes written.
382      *   ciphertext = The ciphertext to decrypt.
383      *
384      * Returns:
385      *   0 on success, or a negative error code on failure.
386      */
387     @nogc @trusted
388     int decryptDetachedUpdate(ubyte[] plaintext, size_t* written, const(ubyte)[] associated_data)
389     {
390         assert(initialized, "State not initialized");
391         assert(written !is null, "Written pointer cannot be null");
392         return aegis256x2_state_decrypt_detached_update(&state, plaintext.ptr, plaintext.length,
393             written, associated_data.ptr, associated_data.length);
394     }
395 
396     /**
397      * Finalizes decryption in detached mode, verifying the MAC and producing final plaintext.
398      *
399      * Params:
400      *   plaintext = Buffer for remaining plaintext.
401      *   written = Pointer to store the number of bytes written.
402      *   mac = The MAC to verify (must be `aegis256x2_ABYTES_MIN` to `aegis256x2_ABYTES_MAX`).
403      *   maclen = Length of the MAC.
404      *
405      * Returns:
406      *laws
407      *   0 on success, -1 if MAC verification fails, or another negative error code on failure.
408      */
409     @nogc @trusted
410     int decryptDetachedFinal(ubyte[] plaintext, size_t* written, const(ubyte)[] mac, size_t maclen)
411     {
412         assert(initialized, "State not initialized");
413         assert(written !is null, "Written pointer cannot be null");
414         assert(maclen >= aegis256x2_ABYTES_MIN && maclen <= aegis256x2_ABYTES_MAX, "Invalid MAC length");
415         return aegis256x2_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
416             written, mac.ptr, maclen);
417     }
418 }
419 
420 /**
421  * AEGIS-256x2 MAC state.
422  *
423  * Manages the lifecycle of an `aegis256x2_mac_state` object in a `@nogc` and `@trusted` context.
424  */
425 struct Aegis256x2MACState
426 {
427     private aegis256x2_mac_state state; // Underlying C MAC state
428     private bool initialized; // Tracks initialization status
429 
430     /**
431      * Initializes the AEGIS-256x2 MAC state with the provided key and nonce.
432      *
433      * Params:
434      *   key = The key (must be `aegis256x2_KEYBYTES` long).
435      *   nonce = The nonce (must be `aegis256x2_NPUBBYTES` long).
436      *
437      * Throws:
438      *   AssertError if key or nonce lengths are invalid.
439      */
440     @nogc @trusted
441     this(const(ubyte)[] key, const(ubyte)[] nonce)
442     {
443         assert(key.length == aegis256x2_KEYBYTES, "Key length must be aegis256x2_KEYBYTES");
444         assert(nonce.length == aegis256x2_NPUBBYTES, "Nonce length must be aegis256x2_NPUBBYTES");
445 
446         memset(&state, 0, aegis256x2_mac_state.sizeof);
447         aegis256x2_mac_init(&state, key.ptr, nonce.ptr);
448         initialized = true;
449     }
450 
451     /**
452      * Destructor to clean up the MAC state.
453      */
454     @nogc @trusted ~this()
455     {
456         if (initialized)
457         {
458             aegis256x2_mac_reset(&state);
459             memset(&state, 0, aegis256x2_mac_state.sizeof);
460             initialized = false;
461         }
462     }
463 
464     /**
465      * Disabled copy constructor to prevent state duplication.
466      */
467     @disable this(this);
468 
469     /**
470      * Updates the MAC state with a message chunk.
471      *
472      * Params:
473      *   message = The message to process.
474      *
475      * Returns:
476      *   0 on success, or a negative error code on failure.
477      */
478     @nogc @trusted
479     int update(const(ubyte)[] message)
480     {
481         assert(initialized, "MAC state not initialized");
482         return aegis256x2_mac_update(&state, message.ptr, message.length);
483     }
484 
485     /**
486      * Finalizes the MAC computation, producing the MAC.
487      *
488      * Params:
489      *   mac = Buffer to store the MAC (must be `aegis256x2_ABYTES_MIN` to `aegis256x2_ABYTES_MAX`).
490      *   maclen = Length of the MAC.
491      *
492      * Returns:
493      *   0 on success, or a negative error code on failure.
494      */
495     @nogc @trusted
496     int finalize(ubyte[] mac, size_t maclen)
497     {
498         assert(initialized, "MAC state not initialized");
499         assert(maclen >= aegis256x2_ABYTES_MIN && maclen <= aegis256x2_ABYTES_MAX, "Invalid MAC length");
500         return aegis256x2_mac_final(&state, mac.ptr, maclen);
501     }
502 
503     /**
504      * Verifies a MAC against the computed MAC.
505      *
506      * Params:
507      *   mac = The MAC to verify (must be `aegis256x2_ABYTES_MIN` to `aegis256x2_ABYTES_MAX`).
508      *   maclen = Length of the MAC.
509      *
510      * Returns:
511      *   0 if valid, -1 if verification fails, or another negative error code on failure.
512      */
513     @nogc @trusted
514     int verify(const(ubyte)[] mac, size_t maclen)
515     {
516         assert(initialized, "MAC state not initialized");
517         assert(maclen >= aegis256x2_ABYTES_MIN && maclen <= aegis256x2_ABYTES_MAX, "Invalid MAC length");
518         return aegis256x2_mac_verify(&state, mac.ptr, maclen);
519     }
520 
521     /**
522      * Resets the MAC state for reuse with the same key and nonce.
523      */
524     @nogc @trusted
525     void reset()
526     {
527         assert(initialized, "MAC state not initialized");
528         aegis256x2_mac_reset(&state);
529     }
530 }
531 
532 /**
533  * AEGIS-256x4 encryption/decryption state.
534  *
535  * Manages the lifecycle of an `aegis256x4_state` object in a `@nogc` and `@trusted` context.
536  */
537 struct Aegis256x4State
538 {
539     private aegis256x4_state state; // Underlying C state
540     private bool initialized; // Tracks initialization status
541 
542     /**
543      * Initializes the AEGIS-256x4 state with the provided key, nonce, and associated data.
544      *
545      * Params:
546      *   key = The encryption key (must be `aegis256x4_KEYBYTES` long).
547      *   nonce = The nonce (must be `aegis256x4_NPUBBYTES` long).
548      *   ad = The associated data (optional, can be empty).
549      *
550      * Throws:
551      *   AssertError if key or nonce lengths are invalid.
552      */
553     @nogc @trusted
554     this(const(ubyte)[] key, const(ubyte)[] nonce, const(ubyte)[] ad = null)
555     {
556         assert(key.length == aegis256x4_KEYBYTES, "Key length must be aegis256x4_KEYBYTES");
557         assert(nonce.length == aegis256x4_NPUBBYTES, "Nonce length must be aegis256x4_NPUBBYTES");
558 
559         memset(&state, 0, aegis256x4_state.sizeof);
560         aegis256x4_state_init(&state, ad.ptr, ad.length, nonce.ptr, key.ptr);
561         initialized = true;
562     }
563 
564     /**
565      * Destructor to clean up the state.
566      */
567     @nogc @trusted ~this()
568     {
569         if (initialized)
570         {
571             memset(&state, 0, aegis256x4_state.sizeof);
572             initialized = false;
573         }
574     }
575 
576     /**
577      * Disabled copy constructor to prevent state duplication.
578      */
579     @disable this(this);
580 
581     /**
582      * Encrypts a message chunk, producing ciphertext.
583      *
584      * Params:
585      *   ciphertext = Buffer to store the ciphertext.
586      *   written = Pointer to store the number of bytes written.
587      *   message = The plaintext message to encrypt.
588      *
589      * Returns:
590      *   0 on success, or a negative error code on failure.
591      */
592     @nogc @trusted
593     int encryptUpdate(ubyte[] ciphertext, size_t* written, const(ubyte)[] message)
594     {
595         assert(initialized, "State not initialized");
596         assert(written !is null, "Written pointer cannot be null");
597         return aegis256x4_state_encrypt_update(&state, ciphertext.ptr, ciphertext.length,
598             written, message.ptr, message.length);
599     }
600 
601     /**
602      * Finalizes encryption in detached mode, producing the final ciphertext and MAC.
603      *
604      * Params:
605      *   ciphertext = Buffer for remaining ciphertext.
606      *   written = Pointer to store the number of bytes written.
607      *   mac = Buffer for the MAC (must be `aegis256x4_ABYTES_MIN` to `aegis256x4_ABYTES_MAX`).
608      *   maclen = Length of the MAC.
609      *
610      * Returns:
611      *   0 on success, or a negative error code on failure.
612      */
613     @nogc @trusted
614     int encryptDetachedFinal(ubyte[] ciphertext, size_t* written, ubyte[] mac, size_t maclen)
615     {
616         assert(initialized, "State not initialized");
617         assert(written !is null, "Written pointer cannot be null");
618         assert(maclen >= aegis256x4_ABYTES_MIN && maclen <= aegis256x4_ABYTES_MAX, "Invalid MAC length");
619         return aegis256x4_state_encrypt_detached_final(&state, ciphertext.ptr, ciphertext.length,
620             written, mac.ptr, maclen);
621     }
622 
623     /**
624      * Decrypts a ciphertext chunk, producing plaintext.
625      *
626      * Params:
627      *   plaintext = Buffer to store the plaintext.
628      *   written = Pointer to store the number of bytes written.
629      *   ciphertext = The ciphertext to decrypt.
630      *
631      * Returns:
632      *   0 on success, or a negative error code on failure.
633      */
634     @nogc @trusted
635     int decryptDetachedUpdate(ubyte[] plaintext, size_t* written, const(ubyte)[] ciphertext)
636     {
637         assert(initialized, "State not initialized");
638         assert(written !is null, "Written pointer cannot be null");
639         return aegis256x4_state_decrypt_detached_update(&state, plaintext.ptr, plaintext.length,
640             written, ciphertext.ptr, ciphertext.length);
641     }
642 
643     /**
644      * Finalizes decryption in detached mode, verifying the MAC and producing final plaintext.
645      *
646      * Params:
647      *   plaintext = Buffer for remaining plaintext.
648      *   written = Pointer to store the number of bytes written.
649      *   mac = The MAC to verify (must be `aegis256x4_ABYTES_MIN` to `aegis256x4_ABYTES_MAX`).
650      *   maclen = Length of the MAC.
651      *
652      * Returns:
653      *   0 on success, -1 if MAC verification fails, or another negative error code on failure.
654      */
655     @nogc @trusted
656     int decryptDetachedFinal(ubyte[] plaintext, size_t* written, const(ubyte)[] mac, size_t maclen)
657     {
658         assert(initialized, "State not initialized");
659         assert(written !is null, "Written pointer cannot be null");
660         assert(maclen >= aegis256x4_ABYTES_MIN && maclen <= aegis256x4_ABYTES_MAX, "Invalid MAC length");
661         return aegis256x4_state_decrypt_detached_final(&state, plaintext.ptr, plaintext.length,
662             written, mac.ptr, maclen);
663     }
664 }
665 
666 /**
667  * AEGIS-256x4 MAC state.
668  *
669  * Manages the lifecycle of an `aegis256x4_mac_state` object in a `@nogc` and `@trusted` context.
670  */
671 struct Aegis256x4MACState
672 {
673     private aegis256x4_mac_state state; // Underlying C MAC state
674     private bool initialized; // Tracks initialization status
675 
676     /**
677      * Initializes the AEGIS-256x4 MAC state with the provided key and nonce.
678      *
679      * Params:
680      *   key = The key (must be `aegis256x4_KEYBYTES` long).
681      *   nonce = The nonce (must be `aegis256x4_NPUBBYTES` long).
682      *
683      * Throws:
684      *   AssertError if key or nonce lengths are invalid.
685      */
686     @nogc @trusted
687     this(const(ubyte)[] key, const(ubyte)[] nonce)
688     {
689         assert(key.length == aegis256x4_KEYBYTES, "Key length must be aegis256x4_KEYBYTES");
690         assert(nonce.length == aegis256x4_NPUBBYTES, "Nonce length must be aegis256x4_NPUBBYTES");
691 
692         memset(&state, 0, aegis256x4_mac_state.sizeof);
693         aegis256x4_mac_init(&state, key.ptr, nonce.ptr);
694         initialized = true;
695     }
696 
697     /**
698      * Destructor to clean up the MAC state.
699      */
700     @nogc @trusted ~this()
701     {
702         if (initialized)
703         {
704             aegis256x4_mac_reset(&state);
705             memset(&state, 0, aegis256x4_mac_state.sizeof);
706             initialized = false;
707         }
708     }
709 
710     /**
711      * Disabled copy constructor to prevent state duplication.
712      */
713     @disable this(this);
714 
715     /**
716      * Updates the MAC state with a message chunk.
717      *
718      * Params:
719      *   message = The message to process.
720      *
721      * Returns:
722      *   0 on success, or a negative error code on failure.
723      */
724     @nogc @trusted
725     int update(const(ubyte)[] message)
726     {
727         assert(initialized, "MAC state not initialized");
728         return aegis256x4_mac_update(&state, message.ptr, message.length);
729     }
730 
731     /**
732      * Finalizes the MAC computation, producing the MAC.
733      *
734      * Params:
735      *   mac = Buffer to store the MAC (must be `aegis256x4_ABYTES_MIN` to `aegis256x4_ABYTES_MAX`).
736      *   maclen = Length of the MAC.
737      *
738      * Returns:
739      *   0 on success, or a negative error code on failure.
740      */
741     @nogc @trusted
742     int finalize(ubyte[] mac, size_t maclen)
743     {
744         assert(initialized, "MAC state not initialized");
745         assert(maclen >= aegis256x4_ABYTES_MIN && maclen <= aegis256x4_ABYTES_MAX, "Invalid MAC length");
746         return aegis256x4_mac_final(&state, mac.ptr, maclen);
747     }
748 
749     /**
750      * Verifies a MAC against the computed MAC.
751      *
752      * Params:
753      *   mac = The MAC to verify (must be `aegis256x4_ABYTES_MIN` to `aegis256x4_ABYTES_MAX`).
754      *   maclen = Length of the MAC.
755      *
756      * Returns:
757      *   0 if valid, -1 if verification fails, or another negative error code on failure.
758      */
759     @nogc @trusted
760     int verify(const(ubyte)[] mac, size_t maclen)
761     {
762         assert(initialized, "MAC state not initialized");
763         assert(maclen >= aegis256x4_ABYTES_MIN && maclen <= aegis256x4_ABYTES_MAX, "Invalid MAC length");
764         return aegis256x4_mac_verify(&state, mac.ptr, maclen);
765     }
766 
767     /**
768      * Resets the MAC state for reuse with the same key and nonce.
769      */
770     @nogc @trusted
771     void reset()
772     {
773         assert(initialized, "MAC state not initialized");
774         aegis256x4_mac_reset(&state);
775     }
776 }
777 
778 version (unittest)
779 {
780     @trusted
781     @("AEGIS256")
782     unittest
783     {
784         import std.random : uniform;
785         import core.stdc.string : memcmp;
786 
787         // Initialize AEGIS library
788         assert(aegis_init() == 0, "AEGIS initialization failed");
789 
790         // Common test data
791         enum size_t bufferSize = 256; // Sufficient for test message and tail bytes
792         ubyte[aegis256_KEYBYTES] key;
793         foreach (ref k; key)
794             k = cast(ubyte) uniform(0, bufferSize);
795         ubyte[aegis256_NPUBBYTES] nonce;
796         foreach (ref n; nonce)
797             n = cast(ubyte) uniform(0, bufferSize);
798         ubyte[] ad = cast(ubyte[]) "ABC";
799         ubyte[] message = cast(ubyte[]) "Hello";
800         ubyte[bufferSize] ciphertext;
801         ubyte[aegis256_ABYTES_MIN] macMin;
802         ubyte[aegis256_ABYTES_MAX] macMax;
803         size_t written;
804 
805         // Test Aegis256State (encryption/decryption)
806         {
807             // Encryption
808             auto encState = Aegis256State(key, nonce, ad);
809             written = 0;
810             assert(encState.encryptUpdate(ciphertext[], &written, message) == 0,
811                 "Aegis256State: Encryption update failed");
812             assert(written <= ciphertext.length, "Aegis256State: Written bytes exceed buffer");
813             size_t totalWritten = written;
814 
815             assert(encState.encryptDetachedFinal(ciphertext[totalWritten .. $], &written,
816                     macMin[], aegis256_ABYTES_MIN) == 0, "Aegis256State: Encryption finalization failed");
817             totalWritten += written;
818             assert(totalWritten <= ciphertext.length, "Aegis256State: Total written bytes exceed buffer");
819 
820             // Decryption
821             ubyte[bufferSize] decrypted;
822             size_t decWritten;
823             auto decState = Aegis256State(key, nonce, ad);
824             decWritten = 0;
825             assert(decState.decryptDetachedUpdate(decrypted[], &decWritten, ciphertext[0 .. totalWritten]) == 0,
826                 "Aegis256State: Decryption update failed");
827             assert(decWritten <= decrypted.length, "Aegis256State: Decrypted bytes exceed buffer");
828             size_t totalDecWritten = decWritten;
829 
830             assert(decState.decryptDetachedFinal(decrypted[totalDecWritten .. $], &decWritten,
831                     macMin[], aegis256_ABYTES_MIN) == 0, "Aegis256State: Decryption finalization failed");
832             totalDecWritten += decWritten;
833             assert(totalDecWritten <= decrypted.length, "Aegis256State: Total decrypted bytes exceed buffer");
834             assert(totalDecWritten >= message.length, "Aegis256State: Decrypted length too short");
835             assert(decrypted[0 .. message.length] == message, "Aegis256State: Decrypted message mismatch");
836         }
837 
838         // Test Aegis256MACState
839         {
840             auto macState = Aegis256MACState(key, nonce);
841             assert(macState.update(message) == 0, "Aegis256MACState: MAC update failed");
842             assert(macState.finalize(macMin[], aegis256_ABYTES_MIN) == 0,
843                 "Aegis256MACState: MAC finalization (min) failed");
844             assert(macState.finalize(macMax[], aegis256_ABYTES_MAX) == 0,
845                 "Aegis256MACState: MAC finalization (max) failed");
846 
847             // Verify MAC (streaming)
848             macState.reset();
849             assert(macState.update(message) == 0, "Aegis256MACState: MAC update for verification failed");
850             assert(macState.verify(macMin[], aegis256_ABYTES_MIN) == 0,
851                 "Aegis256MACState: MAC verification (min) failed");
852 
853             // Test reset
854             macState.reset();
855             assert(macState.update(message) == 0, "Aegis256MACState: MAC update after reset failed");
856             ubyte[aegis256_ABYTES_MIN] newMac;
857             assert(macState.finalize(newMac[], aegis256_ABYTES_MIN) == 0,
858                 "Aegis256MACState: MAC finalization after reset failed");
859             assert(memcmp(macMin.ptr, newMac.ptr, aegis256_ABYTES_MIN) == 0,
860                 "Aegis256MACState: MACs do not match after reset");
861 
862             // Test invalid MAC
863             macState.reset();
864             assert(macState.update(message) == 0, "Aegis256MACState: MAC update for invalid verification failed");
865             ubyte[aegis256_ABYTES_MIN] wrongMac = macMin;
866             wrongMac[0] ^= 0xFF;
867             assert(macState.verify(wrongMac[], aegis256_ABYTES_MIN) == -1,
868                 "Aegis256MACState: Invalid MAC verification did not fail");
869         }
870     }
871 }