    const std = @import("std");
    const debug = std.debug;
    const mem = std.mem;
    const math = std.math;
    const testing = std.testing;
    const AuthenticationError = std.crypto.errors.AuthenticationError;

    /// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks.
    /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf
    ///
    /// Note that ISAP is not suitable for high-performance applications.
    ///
    /// However:
    /// - if allowing physical access to the device is part of your threat model,
    /// - or if you need resistance against microcode/hardware-level side channel attacks,
    /// - or if software-induced fault attacks such as rowhammer are a concern,
    ///
    /// then you may consider ISAP for highly sensitive data.
    pub const IsapA128A = struct {
        pub const key_length = 16;
        pub const nonce_length = 16;
        pub const tag_length: usize = 16;

        const iv1 = [_]u8{ 0x01, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
        const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
        const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };

        const Block = [5]u64;

        block: Block,

        fn round(isap: *IsapA128A, rk: u64) void {
            var x = &isap.block;
            x[2] ^= rk;
            x[0] ^= x[4];
            x[4] ^= x[3];
            x[2] ^= x[1];
            var t = x.*;
            x[0] = t[0] ^ ((~t[1]) & t[2]);
            x[2] = t[2] ^ ((~t[3]) & t[4]);
            x[4] = t[4] ^ ((~t[0]) & t[1]);
            x[1] = t[1] ^ ((~t[2]) & t[3]);
            x[3] = t[3] ^ ((~t[4]) & t[0]);
            x[1] ^= x[0];
            t[1] = x[1];
            x[1] = math.rotr(u64, x[1], 39);
            x[3] ^= x[2];
            t[2] = x[2];
            x[2] = math.rotr(u64, x[2], 1);
            t[4] = x[4];
            t[2] ^= x[2];
            x[2] = math.rotr(u64, x[2], 5);
            t[3] = x[3];
            t[1] ^= x[1];
            x[3] = math.rotr(u64, x[3], 10);
            x[0] ^= x[4];
            x[4] = math.rotr(u64, x[4], 7);
            t[3] ^= x[3];
            x[2] ^= t[2];
            x[1] = math.rotr(u64, x[1], 22);
            t[0] = x[0];
            x[2] = ~x[2];
            x[3] = math.rotr(u64, x[3], 7);
            t[4] ^= x[4];
            x[4] = math.rotr(u64, x[4], 34);
            x[3] ^= t[3];
            x[1] ^= t[1];
            x[0] = math.rotr(u64, x[0], 19);
            x[4] ^= t[4];
            t[0] ^= x[0];
            x[0] = math.rotr(u64, x[0], 9);
            x[0] ^= t[0];
        }

        fn p12(isap: *IsapA128A) void {
            const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
            inline for (rks) |rk| {
                isap.round(rk);
            }
        }

        fn p6(isap: *IsapA128A) void {
            const rks = [6]u64{ 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
            inline for (rks) |rk| {
                isap.round(rk);
            }
        }

        fn p1(isap: *IsapA128A) void {
            isap.round(0x4b);
        }

        fn absorb(isap: *IsapA128A, m: []const u8) void {
            var block = &isap.block;
            var i: usize = 0;
            while (true) : (i += 8) {
                const left = m.len - i;
                if (left >= 8) {
                    block[0] ^= mem.readIntBig(u64, m[i..][0..8]);
                    isap.p12();
                    if (left == 8) {
                        block[0] ^= 0x8000000000000000;
                        isap.p12();
                        break;
                    }
                } else {
                    var padded = [_]u8{0} ** 8;
                    mem.copy(u8, padded[0..left], m[i..]);
                    padded[left] = 0x80;
                    block[0] ^= mem.readIntBig(u64, padded[0..]);
                    isap.p12();
                    break;
                }
            }
        }

        fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 {
            var isap = IsapA128A{
                .block = Block{
                    mem.readIntBig(u64, k[0..8]),
                    mem.readIntBig(u64, k[8..16]),
                    mem.readIntBig(u64, iv[0..8]),
                    0,
                    0,
                },
            };
            isap.p12();

            var i: usize = 0;
            while (i < y.len * 8 - 1) : (i += 1) {
                const cur_byte_pos = i / 8;
                const cur_bit_pos = @truncate(u3, 7 - (i % 8));
                const cur_bit = @as(u64, ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7);
                isap.block[0] ^= cur_bit << 56;
                isap.p1();
            }
            const cur_bit = @as(u64, (y[y.len - 1] & 1) << 7);
            isap.block[0] ^= cur_bit << 56;
            isap.p12();

            var out: [out_len]u8 = undefined;
            var j: usize = 0;
            while (j < out_len) : (j += 8) {
                mem.writeIntBig(u64, out[j..][0..8], isap.block[j / 8]);
            }
            std.crypto.utils.secureZero(u64, &isap.block);
            return out;
        }

        fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 {
            var isap = IsapA128A{
                .block = Block{
                    mem.readIntBig(u64, npub[0..8]),
                    mem.readIntBig(u64, npub[8..16]),
                    mem.readIntBig(u64, iv1[0..]),
                    0,
                    0,
                },
            };
            isap.p12();

            isap.absorb(ad);
            isap.block[4] ^= 1;
            isap.absorb(c);

            var y: [16]u8 = undefined;
            mem.writeIntBig(u64, y[0..8], isap.block[0]);
            mem.writeIntBig(u64, y[8..16], isap.block[1]);
            const nb = trickle(key, iv2, y[0..], 16);
            isap.block[0] = mem.readIntBig(u64, nb[0..8]);
            isap.block[1] = mem.readIntBig(u64, nb[8..16]);
            isap.p12();

            var tag: [16]u8 = undefined;
            mem.writeIntBig(u64, tag[0..8], isap.block[0]);
            mem.writeIntBig(u64, tag[8..16], isap.block[1]);
            std.crypto.utils.secureZero(u64, &isap.block);
            return tag;
        }

        fn xor(out: []u8, in: []const u8, npub: [16]u8, key: [16]u8) void {
            debug.assert(in.len == out.len);

            const nb = trickle(key, iv3, npub[0..], 24);
            var isap = IsapA128A{
                .block = Block{
                    mem.readIntBig(u64, nb[0..8]),
                    mem.readIntBig(u64, nb[8..16]),
                    mem.readIntBig(u64, nb[16..24]),
                    mem.readIntBig(u64, npub[0..8]),
                    mem.readIntBig(u64, npub[8..16]),
                },
            };
            isap.p6();

            var i: usize = 0;
            while (true) : (i += 8) {
                const left = in.len - i;
                if (left >= 8) {
                    mem.writeIntNative(u64, out[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, in[i..][0..8]));
                    if (left == 8) {
                        break;
                    }
                    isap.p6();
                } else {
                    var pad = [_]u8{0} ** 8;
                    mem.copy(u8, pad[0..left], in[i..][0..left]);
                    mem.writeIntNative(u64, pad[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, pad[i..][0..8]));
                    mem.copy(u8, out[i..][0..left], pad[0..left]);
                    break;
                }
            }
            std.crypto.utils.secureZero(u64, &isap.block);
        }

        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void {
            xor(c, m, npub, key);
            tag.* = mac(c, ad, npub, key);
        }

        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
            var computed_tag = mac(c, ad, npub, key);
            var acc: u8 = 0;
            for (computed_tag) |_, j| {
                acc |= (computed_tag[j] ^ tag[j]);
            }
            std.crypto.utils.secureZero(u8, &computed_tag);
            if (acc != 0) {
                return error.AuthenticationFailed;
            }
            xor(m, c, npub, key);
        }
    };

    test "ISAP" {
        const k = [_]u8{1} ** 16;
        const n = [_]u8{2} ** 16;
        var tag: [16]u8 = undefined;
        const ad = "ad";
        var msg = "test";
        var c: [msg.len]u8 = undefined;
        IsapA128A.encrypt(c[0..], &tag, msg[0..], ad, n, k);
        try testing.expect(mem.eql(u8, &[_]u8{ 0x8f, 0x68, 0x03, 0x8d }, c[0..]));
        try testing.expect(mem.eql(u8, &[_]u8{ 0x6c, 0x25, 0xe8, 0xe2, 0xe1, 0x1f, 0x38, 0xe9, 0x80, 0x75, 0xde, 0xd5, 0x2d, 0xb2, 0x31, 0x82 }, tag[0..]));
        try IsapA128A.decrypt(c[0..], c[0..], tag, ad, n, k);
        try testing.expect(mem.eql(u8, msg, c[0..]));
    }