authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-04 14:03:54+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-04 14:03:54+02:00
loge38b151e614eecf572f791b5712d2e6edd33dd84
tree9f1dfb2ee7f8c209da83c865fbdd34ff10e0f845
parent25cafe0b345635dfaa533557f7185e76d5f405ef
parent0adecad9e65ca85b2853db4f264fc7762a2d5983

Merge pull request 'crypto.aegis: add specialized implementation for CPUs without AES' (#36998) from jedisct1/zig:bitslicedaegis into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36998

2 files changed, 495 insertions(+), 2 deletions(-)

lib/std/crypto/aegis.zig+44-2
...@@ -26,6 +26,10 @@ const crypto = std.crypto;...@@ -26,6 +26,10 @@ const crypto = std.crypto;
26const mem = std.mem;26const mem = std.mem;
27const assert = std.debug.assert;27const assert = std.debug.assert;
28const AuthenticationError = crypto.errors.AuthenticationError;28const AuthenticationError = crypto.errors.AuthenticationError;
29const bitsliced = @import("aegis/bitsliced.zig");
30
31const use_bitsliced = !crypto.core.aes.has_hardware_support and
32 std.options.side_channels_mitigations != .none;
2933
30/// AEGIS-128X4 with a 128 bit tag34/// AEGIS-128X4 with a 128 bit tag
31pub const Aegis128X4 = Aegis128XGeneric(4, 128);35pub const Aegis128X4 = Aegis128XGeneric(4, 128);
...@@ -276,7 +280,7 @@ fn Aegis128XGeneric(comptime degree: u7, comptime tag_bits: u9) type {...@@ -276,7 +280,7 @@ fn Aegis128XGeneric(comptime degree: u7, comptime tag_bits: u9) type {
276 comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits280 comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits
277281
278 return struct {282 return struct {
279 const State = State128X(degree);283 const State = if (use_bitsliced) bitsliced.State128X(degree) else State128X(degree);
280284
281 pub const tag_length = tag_bits / 8;285 pub const tag_length = tag_bits / 8;
282 pub const nonce_length = 16;286 pub const nonce_length = 16;
...@@ -571,7 +575,7 @@ fn Aegis256XGeneric(comptime degree: u7, comptime tag_bits: u9) type {...@@ -571,7 +575,7 @@ fn Aegis256XGeneric(comptime degree: u7, comptime tag_bits: u9) type {
571 comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits575 comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits
572576
573 return struct {577 return struct {
574 const State = State256X(degree);578 const State = if (use_bitsliced) bitsliced.State256X(degree) else State256X(degree);
575579
576 pub const tag_length = tag_bits / 8;580 pub const tag_length = tag_bits / 8;
577 pub const nonce_length = 32;581 pub const nonce_length = 32;
...@@ -819,6 +823,44 @@ fn AegisMac(comptime T: type) type {...@@ -819,6 +823,44 @@ fn AegisMac(comptime T: type) type {
819const htest = @import("test.zig");823const htest = @import("test.zig");
820const testing = std.testing;824const testing = std.testing;
821825
826fn hashStateOutputs(comptime State: type, key: anytype, nonce: anytype) [32]u8 {
827 var msg: [State.rate]u8 = undefined;
828 for (&msg, 0..) |*x, i| x.* = @truncate(i *% 31 +% 5);
829 var out: [State.rate]u8 = undefined;
830 var h: crypto.hash.sha2.Sha256 = .init(.{});
831
832 var st = State.init(key, nonce);
833 st.absorb(&msg);
834 st.enc(&out, &msg);
835 h.update(&out);
836 st.dec(&out, &msg);
837 h.update(&out);
838 st.decLast(out[3..], msg[3..]);
839 h.update(out[3..]);
840 inline for (.{ 128, 256 }) |tag_bits| {
841 var st2 = st;
842 h.update(&st2.finalize(tag_bits, 33, 12345));
843 st2 = st;
844 h.update(&st2.finalizeMac(tag_bits, 54321));
845 }
846 return h.finalResult();
847}
848
849test "Aegis bitsliced states" {
850 const key = "0123456789abcdef0123456789ABCDEF";
851 const nonce = "nonce0123456789Nnonce0123456789N";
852 inline for (.{ 1, 2, 4 }) |degree| {
853 try testing.expectEqual(
854 hashStateOutputs(State128X(degree), key[0..16].*, nonce[0..16].*),
855 hashStateOutputs(bitsliced.State128X(degree), key[0..16].*, nonce[0..16].*),
856 );
857 try testing.expectEqual(
858 hashStateOutputs(State256X(degree), key.*, nonce.*),
859 hashStateOutputs(bitsliced.State256X(degree), key.*, nonce.*),
860 );
861 }
862}
863
822test "Aegis128L test vector 1" {864test "Aegis128L test vector 1" {
823 const key: [Aegis128L.key_length]u8 = [_]u8{ 0x10, 0x01 } ++ @as([14]u8, @splat(0x00));865 const key: [Aegis128L.key_length]u8 = [_]u8{ 0x10, 0x01 } ++ @as([14]u8, @splat(0x00));
824 const nonce: [Aegis128L.nonce_length]u8 = [_]u8{ 0x10, 0x00, 0x02 } ++ @as([13]u8, @splat(0x00));866 const nonce: [Aegis128L.nonce_length]u8 = [_]u8{ 0x10, 0x00, 0x02 } ++ @as([13]u8, @splat(0x00));
lib/std/crypto/aegis/bitsliced.zig created+451
...@@ -0,0 +1,451 @@
1//! Constant-time AEGIS states for targets without AES instructions.
2//!
3//! We keep the AEGIS state in bitsliced form as much as possible.
4
5const std = @import("../../std.zig");
6const mem = std.mem;
7const bitsliced_aes = @import("../aes/bitsliced.zig");
8
9const c1_bytes = [16]u8{ 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1, 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd };
10const c2_bytes = [16]u8{ 0x0, 0x1, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62 };
11
12fn Lanes(comptime degree: u7) type {
13 return struct {
14 const Aes = bitsliced_aes.Batch(degree);
15 const Word = Aes.Word;
16 const block_length = @as(usize, degree) * 16;
17
18 fn mask(comptime b: u8) Word {
19 return comptime Aes.splatWord(@as(u32, 0x01010101) * b);
20 }
21
22 fn loadBlock(bytes: *const [block_length]u8) [4]Word {
23 var w: [4]Word = @splat(0);
24 inline for (0..degree) |g| {
25 inline for (0..4) |wd| {
26 const v = mem.readInt(u32, bytes[g * 16 + wd * 4 ..][0..4], .little);
27 w[wd] |= @as(Word, v) << (32 * g);
28 }
29 }
30 return w;
31 }
32
33 fn storeBlock(bytes: *[block_length]u8, w: [4]Word) void {
34 inline for (0..degree) |g| {
35 inline for (0..4) |wd| {
36 const v: u32 = @truncate(w[wd] >> (32 * g));
37 mem.writeInt(u32, bytes[g * 16 + wd * 4 ..][0..4], v, .little);
38 }
39 }
40 }
41
42 fn broadcastBlock(bytes: *const [16]u8) [4]Word {
43 var w: [4]Word = undefined;
44 inline for (&w, 0..) |*x, wd| {
45 x.* = Aes.splatWord(mem.readInt(u32, bytes[wd * 4 ..][0..4], .little));
46 }
47 return w;
48 }
49
50 /// Returns two little-endian 64-bit lengths in every lane.
51 fn sizesBlock(a: u64, b: u64) [4]Word {
52 return .{
53 Aes.splatWord(@truncate(a)),
54 Aes.splatWord(@truncate(a >> 32)),
55 Aes.splatWord(@truncate(b)),
56 Aes.splatWord(@truncate(b >> 32)),
57 };
58 }
59
60 fn xorBlocks(a: [4]Word, b: [4]Word) [4]Word {
61 var w: [4]Word = undefined;
62 inline for (&w, a, b) |*x, y, z| x.* = y ^ z;
63 return w;
64 }
65
66 fn contextMask(comptime slots: [2]usize) Aes.Bitsliced {
67 @setEvalBranchQuota(50000);
68 var ctx: [4]Word = @splat(0);
69 for (0..degree) |g| ctx[0] |= ((@as(Word, degree) - 1) * 256 + g) << (32 * g);
70 return packBlocks(&slots, .{ ctx, ctx });
71 }
72
73 const Swap = struct { usize, usize, u32, comptime_int };
74
75 /// Swaps, in execution order.
76 const pack_swaps = swaps: {
77 var swaps: []const Swap = &.{};
78 for (0..8) |g| {
79 const i = g * 4;
80 swaps = swaps ++ [_]Swap{
81 .{ i, i + 1, 0x00ff00ff, 8 },
82 .{ i + 2, i + 3, 0x00ff00ff, 8 },
83 .{ i, i + 2, 0x0000ffff, 16 },
84 .{ i + 1, i + 3, 0x0000ffff, 16 },
85 };
86 }
87 for (0..4) |k| {
88 swaps = swaps ++ [_]Swap{
89 .{ k + 4, k, 0x55555555, 1 },
90 .{ k + 12, k + 8, 0x55555555, 1 },
91 .{ k + 20, k + 16, 0x55555555, 1 },
92 .{ k + 28, k + 24, 0x55555555, 1 },
93 .{ k + 8, k, 0x33333333, 2 },
94 .{ k + 12, k + 4, 0x33333333, 2 },
95 .{ k + 24, k + 16, 0x33333333, 2 },
96 .{ k + 28, k + 20, 0x33333333, 2 },
97 .{ k + 16, k, 0x0f0f0f0f, 4 },
98 .{ k + 20, k + 4, 0x0f0f0f0f, 4 },
99 .{ k + 24, k + 8, 0x0f0f0f0f, 4 },
100 .{ k + 28, k + 12, 0x0f0f0f0f, 4 },
101 };
102 }
103 break :swaps swaps;
104 };
105
106 /// Returns the swaps needed when only `slots` contain input blocks.
107 fn nonzeroSwaps(comptime slots: []const usize) []const Swap {
108 var nonzero: [32]bool = @splat(false);
109 for (slots) |p| nonzero[p * 4 ..][0..4].* = @splat(true);
110 var swaps: []const Swap = &.{};
111 for (pack_swaps) |s| {
112 if (!nonzero[s[0]] and !nonzero[s[1]]) continue;
113 nonzero[s[0]] = true;
114 nonzero[s[1]] = true;
115 swaps = swaps ++ [_]Swap{s};
116 }
117 return swaps;
118 }
119
120 /// Packs blocks in `slots`, skipping unnecessary swaps.
121 fn packBlocks(comptime slots: []const usize, blocks: [slots.len][4]Word) Aes.Bitsliced {
122 var st: Aes.Bitsliced = @splat(0);
123 inline for (slots, blocks) |p, b| st[p * 4 ..][0..4].* = b;
124 inline for (comptime nonzeroSwaps(slots)) |s| Aes.swapMove(&st[s[0]], &st[s[1]], s[2], s[3]);
125 return st;
126 }
127
128 fn unpackBlocks(st: Aes.Bitsliced, comptime slots: []const usize) [slots.len][4]Word {
129 const swaps = comptime nonzeroSwaps(slots);
130 var u = st;
131 inline for (0..swaps.len) |i| {
132 const s = swaps[swaps.len - 1 - i];
133 Aes.swapMove(&u[s[0]], &u[s[1]], s[2], s[3]);
134 }
135 var blocks: [slots.len][4]Word = undefined;
136 inline for (&blocks, slots) |*b, p| b.* = u[p * 4 ..][0..4].*;
137 return blocks;
138 }
139
140 fn unpacked(words: Aes.Bitsliced) Aes.Bitsliced {
141 var u = words;
142 Aes.unpackState(&u);
143 return u;
144 }
145
146 /// Combines each range of state blocks into a unique tag.
147 fn laneTags(words: Aes.Bitsliced, comptime ranges: []const [2]usize) [ranges.len][block_length]u8 {
148 const u = unpacked(words);
149 var tags: [ranges.len][block_length]u8 = undefined;
150 inline for (ranges, &tags) |r, *t| {
151 var acc: [4]Word = @splat(0);
152 inline for (r[0]..r[1]) |p| acc = xorBlocks(acc, u[p * 4 ..][0..4].*);
153 storeBlock(t, acc);
154 }
155 return tags;
156 }
157
158 /// XORs all lane tags when `fold_lanes` is true; otherwise we return lane 0's tag.
159 fn tag(words: Aes.Bitsliced, comptime ranges: []const [2]usize, comptime fold_lanes: bool) [ranges.len * 16]u8 {
160 var out: [ranges.len * 16]u8 = undefined;
161 for (laneTags(words, ranges), 0..) |t, i| {
162 const lanes: [degree]@Vector(16, u8) = @bitCast(t);
163 var folded = lanes[0];
164 if (fold_lanes) {
165 for (lanes[1..]) |lane| folded ^= lane;
166 }
167 out[i * 16 ..][0..16].* = folded;
168 }
169 return out;
170 }
171 };
172}
173
174pub fn State128X(comptime degree: u7) type {
175 return struct {
176 const State = @This();
177 const V = Lanes(degree);
178 const Aes = V.Aes;
179 const Word = V.Word;
180
181 /// bit 7 - i of each byte belongs to state block i.
182 words: Aes.Bitsliced,
183
184 const aes_block_length = V.block_length;
185 pub const rate = aes_block_length * 2;
186 pub const alignment = @alignOf(Word);
187
188 const context_mask = V.contextMask(.{ 3, 7 });
189
190 pub fn init(key: [16]u8, nonce: [16]u8) State {
191 const c1 = V.broadcastBlock(&c1_bytes);
192 const c2 = V.broadcastBlock(&c2_bytes);
193 const key_block = V.broadcastBlock(&key);
194 const nonce_block = V.broadcastBlock(&nonce);
195 const kxn = V.xorBlocks(key_block, nonce_block);
196 var state = State{ .words = V.packBlocks(&.{ 0, 1, 2, 3, 4, 5, 6, 7 }, .{
197 kxn,
198 c1,
199 c2,
200 c1,
201 kxn,
202 V.xorBlocks(key_block, c2),
203 V.xorBlocks(key_block, c1),
204 V.xorBlocks(key_block, c2),
205 }) };
206 const input = packRate(nonce_block, key_block);
207 for (0..10) |_| {
208 inline for (&state.words, context_mask) |*w, m| w.* ^= m;
209 state.update(&input);
210 }
211 return state;
212 }
213
214 fn update(state: *State, input: *const Aes.Bitsliced) void {
215 var st1 = state.words;
216 Aes.round(&st1);
217 inline for (&state.words, st1, input) |*w, r, in| {
218 w.* ^= (((r & V.mask(0xfe)) >> 1) | ((r & V.mask(0x01)) << 7)) ^ in;
219 }
220 }
221
222 fn packRate(m0: [4]Word, m1: [4]Word) Aes.Bitsliced {
223 return V.packBlocks(&.{ 0, 4 }, .{ m0, m1 });
224 }
225
226 fn keystream(state: *const State) [2][4]Word {
227 var z: Aes.Bitsliced = undefined;
228 inline for (&z, &state.words) |*zw, x| {
229 zw.* = ((x & V.mask(0x02)) << 6) ^ ((x & V.mask(0x40)) << 1) ^
230 (((x & V.mask(0x20)) << 2) & ((x & V.mask(0x10)) << 3)) ^
231 ((x & V.mask(0x20)) >> 2) ^ ((x & V.mask(0x04)) << 1) ^
232 (((x & V.mask(0x02)) << 2) & ((x & V.mask(0x01)) << 3));
233 }
234 return V.unpackBlocks(z, &.{ 0, 4 });
235 }
236
237 pub fn absorb(state: *State, src: *const [rate]u8) void {
238 const input = packRate(V.loadBlock(src[0..aes_block_length]), V.loadBlock(src[aes_block_length..rate]));
239 state.update(&input);
240 }
241
242 pub fn enc(state: *State, dst: *[rate]u8, src: *const [rate]u8) void {
243 const z = state.keystream();
244 const m0 = V.loadBlock(src[0..aes_block_length]);
245 const m1 = V.loadBlock(src[aes_block_length..rate]);
246 const input = packRate(m0, m1);
247 state.update(&input);
248 V.storeBlock(dst[0..aes_block_length], V.xorBlocks(m0, z[0]));
249 V.storeBlock(dst[aes_block_length..rate], V.xorBlocks(m1, z[1]));
250 }
251
252 pub fn dec(state: *State, dst: *[rate]u8, src: *const [rate]u8) void {
253 const z = state.keystream();
254 const m0 = V.xorBlocks(V.loadBlock(src[0..aes_block_length]), z[0]);
255 const m1 = V.xorBlocks(V.loadBlock(src[aes_block_length..rate]), z[1]);
256 const input = packRate(m0, m1);
257 state.update(&input);
258 V.storeBlock(dst[0..aes_block_length], m0);
259 V.storeBlock(dst[aes_block_length..rate], m1);
260 }
261
262 pub fn decLast(state: *State, dst: []u8, src: []const u8) void {
263 const z = state.keystream();
264 var pad: [rate]u8 = undefined;
265 V.storeBlock(pad[0..aes_block_length], z[0]);
266 V.storeBlock(pad[aes_block_length..rate], z[1]);
267 for (pad[0..src.len], src) |*p, x| p.* ^= x;
268 @memcpy(dst, pad[0..src.len]);
269 @memset(pad[src.len..], 0);
270 state.absorb(&pad);
271 }
272
273 fn finalizeRounds(state: *State, sizes: [4]Word) void {
274 const t = V.xorBlocks(sizes, V.unpacked(state.words)[8..12].*);
275 const input = packRate(t, t);
276 for (0..7) |_| state.update(&input);
277 }
278
279 fn tagRanges(comptime tag_bits: u9) []const [2]usize {
280 return switch (tag_bits) {
281 128 => &.{.{ 0, 7 }},
282 256 => &.{ .{ 0, 4 }, .{ 4, 8 } },
283 else => unreachable,
284 };
285 }
286
287 pub fn finalize(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 {
288 state.finalizeRounds(V.sizesBlock(@as(u64, adlen) * 8, @as(u64, mlen) * 8));
289 return V.tag(state.words, tagRanges(tag_bits), true);
290 }
291
292 pub fn finalizeMac(state: *State, comptime tag_bits: u9, datalen: usize) [tag_bits / 8]u8 {
293 state.finalizeRounds(V.sizesBlock(@as(u64, datalen) * 8, tag_bits));
294 if (degree > 1) {
295 const tags = V.laneTags(state.words, tagRanges(tag_bits));
296 var v: [rate]u8 = @splat(0);
297 switch (tag_bits) {
298 128 => for (0..degree / 2) |d| {
299 v[0..16].* = tags[0][d * 32 ..][0..16].*;
300 v[rate / 2 ..][0..16].* = tags[0][d * 32 ..][16..32].*;
301 state.absorb(&v);
302 },
303 256 => for (1..degree) |d| {
304 v[0..16].* = tags[0][d * 16 ..][0..16].*;
305 v[rate / 2 ..][0..16].* = tags[1][d * 16 ..][0..16].*;
306 state.absorb(&v);
307 },
308 else => unreachable,
309 }
310 state.finalizeRounds(V.sizesBlock(degree, tag_bits));
311 }
312 return V.tag(state.words, tagRanges(tag_bits), false);
313 }
314 };
315}
316
317pub fn State256X(comptime degree: u7) type {
318 return struct {
319 const State = @This();
320 const V = Lanes(degree);
321 const Aes = V.Aes;
322 const Word = V.Word;
323
324 /// bit 7 - i of each byte belongs to state block i.
325 words: Aes.Bitsliced,
326
327 pub const rate = V.block_length;
328 pub const alignment = @alignOf(Word);
329
330 const context_mask = V.contextMask(.{ 3, 5 });
331
332 pub fn init(key: [32]u8, nonce: [32]u8) State {
333 const c1 = V.broadcastBlock(&c1_bytes);
334 const c2 = V.broadcastBlock(&c2_bytes);
335 const key_block1 = V.broadcastBlock(key[0..16]);
336 const key_block2 = V.broadcastBlock(key[16..32]);
337 const nonce_block1 = V.broadcastBlock(nonce[0..16]);
338 const nonce_block2 = V.broadcastBlock(nonce[16..32]);
339 const kxn1 = V.xorBlocks(key_block1, nonce_block1);
340 const kxn2 = V.xorBlocks(key_block2, nonce_block2);
341 var state = State{ .words = V.packBlocks(&.{ 0, 1, 2, 3, 4, 5 }, .{
342 kxn1,
343 kxn2,
344 c1,
345 c2,
346 V.xorBlocks(key_block1, c2),
347 V.xorBlocks(key_block2, c1),
348 }) };
349 const inputs = [4]Aes.Bitsliced{
350 packRate(key_block1),
351 packRate(key_block2),
352 packRate(kxn1),
353 packRate(kxn2),
354 };
355 for (0..4) |_| {
356 for (&inputs) |*input| {
357 inline for (&state.words, context_mask) |*w, m| w.* ^= m;
358 state.update(input);
359 }
360 }
361 return state;
362 }
363
364 fn update(state: *State, input: *const Aes.Bitsliced) void {
365 var st1 = state.words;
366 Aes.round(&st1);
367 inline for (&state.words, st1, input) |*w, r, in| {
368 w.* ^= (((r & V.mask(0xf8)) >> 1) | ((r & V.mask(0x04)) << 5)) ^ in;
369 }
370 }
371
372 fn packRate(m: [4]Word) Aes.Bitsliced {
373 return V.packBlocks(&.{0}, .{m});
374 }
375
376 fn keystream(state: *const State) [4]Word {
377 var z: Aes.Bitsliced = undefined;
378 inline for (&z, &state.words) |*zw, x| {
379 zw.* = ((x & V.mask(0x40)) << 1) ^ ((x & V.mask(0x08)) << 4) ^
380 ((x & V.mask(0x04)) << 5) ^
381 (((x & V.mask(0x20)) << 2) & ((x & V.mask(0x10)) << 3));
382 }
383 return V.unpackBlocks(z, &.{0})[0];
384 }
385
386 pub fn absorb(state: *State, src: *const [rate]u8) void {
387 const input = packRate(V.loadBlock(src));
388 state.update(&input);
389 }
390
391 pub fn enc(state: *State, dst: *[rate]u8, src: *const [rate]u8) void {
392 const z = state.keystream();
393 const m = V.loadBlock(src);
394 const input = packRate(m);
395 state.update(&input);
396 V.storeBlock(dst, V.xorBlocks(m, z));
397 }
398
399 pub fn dec(state: *State, dst: *[rate]u8, src: *const [rate]u8) void {
400 const z = state.keystream();
401 const m = V.xorBlocks(V.loadBlock(src), z);
402 const input = packRate(m);
403 state.update(&input);
404 V.storeBlock(dst, m);
405 }
406
407 pub fn decLast(state: *State, dst: []u8, src: []const u8) void {
408 var pad: [rate]u8 = undefined;
409 V.storeBlock(&pad, state.keystream());
410 for (pad[0..src.len], src) |*p, x| p.* ^= x;
411 @memcpy(dst, pad[0..src.len]);
412 @memset(pad[src.len..], 0);
413 state.absorb(&pad);
414 }
415
416 fn finalizeRounds(state: *State, sizes: [4]Word) void {
417 const t = V.xorBlocks(sizes, V.unpacked(state.words)[12..16].*);
418 const input = packRate(t);
419 for (0..7) |_| state.update(&input);
420 }
421
422 fn tagRanges(comptime tag_bits: u9) []const [2]usize {
423 return switch (tag_bits) {
424 128 => &.{.{ 0, 6 }},
425 256 => &.{ .{ 0, 3 }, .{ 3, 6 } },
426 else => unreachable,
427 };
428 }
429
430 pub fn finalize(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 {
431 state.finalizeRounds(V.sizesBlock(@as(u64, adlen) * 8, @as(u64, mlen) * 8));
432 return V.tag(state.words, tagRanges(tag_bits), true);
433 }
434
435 pub fn finalizeMac(state: *State, comptime tag_bits: u9, datalen: usize) [tag_bits / 8]u8 {
436 state.finalizeRounds(V.sizesBlock(@as(u64, datalen) * 8, tag_bits));
437 if (degree > 1) {
438 const tags = V.laneTags(state.words, tagRanges(tag_bits));
439 var v: [rate]u8 = @splat(0);
440 for (1..degree) |d| {
441 for (tags) |t| {
442 v[0..16].* = t[d * 16 ..][0..16].*;
443 state.absorb(&v);
444 }
445 }
446 state.finalizeRounds(V.sizesBlock(degree, tag_bits));
447 }
448 return V.tag(state.words, tagRanges(tag_bits), false);
449 }
450 };
451}