Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

AsconAead128

Ascon-AEAD128 as specified in NIST SP 800-232 Section 4

ascon.AsconAead128
pub const AsconAead128 = struct

File

lib/std/crypto/ascon.zig:317

Code

pub const AsconAead128 = struct {
    pub const tag_length = 16;
    pub const nonce_length = 16;
    pub const key_length = 16;
    pub const block_length = 16;

    const AeadState = struct {
        st: AsconState,
        k0: u64,
        k1: u64,

        /// Initialize AEAD state with key and nonce.
        ///
        /// Parameters:
        ///   - key: 16-byte secret key
        ///   - nonce: 16-byte nonce
        ///
        /// Returns: Initialized AEAD state ready for processing
        fn init(key: [16]u8, nonce: [16]u8) AeadState {
            const k0 = mem.readInt(u64, key[0..8], .little);
            const k1 = mem.readInt(u64, key[8..16], .little);
            const n0 = mem.readInt(u64, nonce[0..8], .little);
            const n1 = mem.readInt(u64, nonce[8..16], .little);

            // IV for Ascon-AEAD128 (Ascon-128a)
            const iv: u64 = 0x00001000808C0001;
            const words: [5]u64 = .{ iv, k0, k1, n0, n1 };

            var st = AsconState.initFromWords(words);
            st.permuteR(12);

            st.st[3] ^= k0;
            st.st[4] ^= k1;

            return AeadState{ .st = st, .k0 = k0, .k1 = k1 };
        }

        /// Process associated data for authentication.
        ///
        /// Parameters:
        ///   - ad: Associated data to authenticate
        ///
        /// Updates the state to include AD in authentication tag computation.
        fn processAd(self: *AeadState, ad: []const u8) void {
            if (ad.len == 0) return;

            var i: usize = 0;
            // Process full 128-bit blocks
            while (i + 16 <= ad.len) : (i += 16) {
                self.st.addBytes(ad[i..][0..16]);
                self.st.permuteR(8);
            }

            // Process final partial AD block
            const adrem = ad.len - i;
            if (adrem > 0) {
                if (adrem >= 8) {
                    var buf: [8]u8 = @splat(0);
                    @memcpy(buf[0..8], ad[i..][0..8]);
                    self.st.st[0] ^= mem.readInt(u64, &buf, .little);

                    buf = @splat(0);
                    @memcpy(buf[0 .. adrem - 8], ad[i + 8 ..]);
                    buf[adrem - 8] = 0x01;
                    self.st.st[1] ^= mem.readInt(u64, &buf, .little);
                } else {
                    var buf: [8]u8 = @splat(0);
                    @memcpy(buf[0..adrem], ad[i..]);
                    buf[adrem] = 0x01;
                    self.st.st[0] ^= mem.readInt(u64, &buf, .little);
                }
                self.st.permuteR(8);
            }
        }

        /// Finalize the AEAD operation and prepare tag.
        ///
        /// Applies final permutation and XORs key for tag generation.
        fn finalize(self: *AeadState) void {
            // XOR key before final permutation
            self.st.st[2] ^= self.k0;
            self.st.st[3] ^= self.k1;
            self.st.permuteR(12);

            // XOR key again for tag generation
            self.st.st[3] ^= self.k0;
            self.st.st[4] ^= self.k1;
        }
    };

    /// Encrypt a message with Ascon-AEAD128.
    ///
    /// Parameters:
    ///   - c: Output buffer for ciphertext (must be same length as m)
    ///   - tag: Output buffer for authentication tag (16 bytes)
    ///   - m: Plaintext message to encrypt
    ///   - ad: Associated data to authenticate but not encrypt
    ///   - npub: Public nonce (16 bytes, must be unique per message)
    ///   - k: Secret key (16 bytes)
    ///
    /// Note: The ciphertext and tag must be transmitted together for decryption
    pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
        debug.assert(c.len == m.len);

        var state = AeadState.init(k, npub);

        // Process associated data
        state.processAd(ad);

        // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
        state.st.st[4] ^= 0x8000000000000000;

        // Process plaintext
        var i: usize = 0;
        while (i + 16 <= m.len) : (i += 16) {
            state.st.addBytes(m[i..][0..16]);
            state.st.extractBytes(c[i..][0..16]);
            state.st.permuteR(8);
        }

        // Process final partial block
        const remaining = m.len - i;
        if (remaining > 8) {
            // Split between two words
            state.st.addBytes(m[i..][0..8]);
            state.st.extractBytes(c[i..][0..8]);

            var buf: [8]u8 = @splat(0);
            @memcpy(buf[0 .. remaining - 8], m[i + 8 ..]);
            const m1 = mem.readInt(u64, &buf, .little);
            state.st.st[1] ^= m1;
            mem.writeInt(u64, buf[0..], state.st.st[1], .little);
            @memcpy(c[i + 8 ..], buf[0 .. remaining - 8]);

            // Add padding
            state.st.st[1] ^= @as(u64, 0x01) << @intCast((remaining - 8) * 8);
        } else if (remaining == 8) {
            // Exactly 8 bytes - all in word 0, padding in word 1
            state.st.addBytes(m[i..][0..8]);
            state.st.extractBytes(c[i..][0..8]);

            // Add padding to word 1 at position 0
            state.st.st[1] ^= 0x01;
        } else if (remaining > 0) {
            // All in first word
            var temp: [8]u8 = @splat(0);
            @memcpy(temp[0..remaining], m[i..]);
            state.st.addBytes(&temp);
            state.st.extractBytes(c[i..][0..remaining]);
            // Add padding
            temp = @splat(0);
            temp[remaining] = 0x01;
            state.st.addBytes(&temp);
            // Second word stays zero
        } else {
            // Empty message or exact multiple - add padding block
            var padded: [16]u8 = @splat(0);
            padded[0] = 0x01;
            state.st.addBytes(&padded);
        }

        // Finalization
        state.finalize();

        // Extract tag
        mem.writeInt(u64, tag[0..8], state.st.st[3], .little);
        mem.writeInt(u64, tag[8..16], state.st.st[4], .little);
    }

    /// Decrypt a message with Ascon-AEAD128.
    ///
    /// Parameters:
    ///   - m: Output buffer for plaintext (must be same length as c)
    ///   - c: Ciphertext to decrypt
    ///   - tag: Authentication tag (16 bytes)
    ///   - ad: Associated data that was authenticated
    ///   - npub: Public nonce used during encryption (16 bytes)
    ///   - k: Secret key (16 bytes)
    ///
    /// Returns: AuthenticationError if tag verification fails
    ///
    /// Note: On authentication failure, the output buffer is securely zeroed
    pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
        debug.assert(m.len == c.len);

        var state = AeadState.init(k, npub);

        // Process associated data
        state.processAd(ad);

        // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
        state.st.st[4] ^= 0x8000000000000000;

        // Process ciphertext
        var i: usize = 0;
        while (i + 16 <= c.len) : (i += 16) {
            const ct_block = c[i..][0..16].*; // Save ciphertext block for in-place operation support
            state.st.xorBytes(m[i..][0..16], &ct_block);
            state.st.setBytes(&ct_block);
            state.st.permuteR(8);
        }

        // Final partial ciphertext block
        const crem = c.len - i;
        if (crem > 8) {
            // Save ciphertext for in-place operation support
            var saved_ct: [16]u8 = undefined;
            @memcpy(saved_ct[0..crem], c[i..]);

            const c0 = mem.readInt(u64, saved_ct[0..8], .little);
            state.st.st[0] ^= c0;
            mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
            state.st.st[0] = c0;

            var buf: [8]u8 = @splat(0);
            @memcpy(buf[0 .. crem - 8], saved_ct[8..][0 .. crem - 8]);
            const c1 = mem.readInt(u64, &buf, .little);
            const m1 = state.st.st[1] ^ c1;
            mem.writeInt(u64, buf[0..], m1, .little);
            @memcpy(m[i + 8 ..], buf[0 .. crem - 8]);

            // Replace only the bytes we've read, keeping upper bytes intact
            const mask = (@as(u64, 1) << @intCast((crem - 8) * 8)) - 1;
            state.st.st[1] = (state.st.st[1] & ~mask) | (c1 & mask);

            state.st.st[1] ^= @as(u64, 0x01) << @intCast((crem - 8) * 8);
        } else if (crem == 8) {
            // Exactly 8 bytes - process only word 0, add padding to word 1
            const saved_ct = c[i..][0..8].*;

            const c0 = mem.readInt(u64, &saved_ct, .little);
            state.st.st[0] ^= c0;
            mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
            state.st.st[0] = c0;

            // Add padding to word 1 at position 0
            state.st.st[1] ^= 0x01;
        } else if (crem > 0) {
            var buf: [8]u8 = @splat(0);
            @memcpy(buf[0..crem], c[i..]);
            const c0 = mem.readInt(u64, &buf, .little);
            const m0 = state.st.st[0] ^ c0;
            mem.writeInt(u64, buf[0..], m0, .little);
            @memcpy(m[i..], buf[0..crem]);

            // Replace only the bytes we've read, keeping upper bytes intact
            const mask = (@as(u64, 1) << @intCast(crem * 8)) - 1;
            state.st.st[0] = (state.st.st[0] & ~mask) | (c0 & mask);

            state.st.st[0] ^= @as(u64, 0x01) << @intCast(crem * 8);
        } else {
            state.st.st[0] ^= 0x01;
        }

        // Finalization
        state.finalize();

        // Verify tag
        var computed_tag: [tag_length]u8 = undefined;
        mem.writeInt(u64, computed_tag[0..8], state.st.st[3], .little);
        mem.writeInt(u64, computed_tag[8..16], state.st.st[4], .little);

        if (!crypto.timing_safe.eql([tag_length]u8, tag, computed_tag)) {
            crypto.secureZero(u8, m);
            return error.AuthenticationFailed;
        }
    }
}