1//! Bitsliced AES round function, for targets without hardware AES support.
2//! It has no data-dependent memory access, so it is constant time.
3
4const std = @import("../../std.zig");
5const builtin = @import("builtin");
6const mem = std.mem;
7
8/// The widest batch of AES blocks the target supports
9pub const Wide = if (builtin.cpu.arch == .wasm32 or builtin.cpu.arch == .wasm64 or @bitSizeOf(usize) >= 64) Batch(2) else Batch(1);
10
11/// Number of AES blocks we can process together
12pub const width = Wide.nblocks;
13
14/// A state holding a whole `width`-block batch
15pub const State = Wide.Bitsliced;
16
17/// A batch of `8 * lanes` blocks, one 32-bit lane per group of eight blocks
18pub fn Batch(comptime lanes: usize) type {
19 return struct {
20 pub const Word = @Int(.unsigned, 32 * lanes);
21 const Shift = std.math.Log2Int(Word);
22
23 const nblocks = 8 * lanes;
24 pub const Bitsliced = [32]Word;
25
26 /// Splat a 32-bit pattern across all lanes of a word
27 pub fn splatWord(x: u32) Word {
28 const t: [lanes]u32 = @splat(x);
29 return @bitCast(t);
30 }
31
32 fn laneRotl(x: Word, comptime n: u5) Word {
33 if (lanes == 1) return std.math.rotl(u32, x, n);
34 const lo_bits = (@as(u32, 1) << n) - 1;
35 const mask_lo = comptime splatWord(lo_bits);
36 const mask_hi = comptime splatWord(~lo_bits);
37 const l: Shift = n;
38 const r: Shift = @intCast(32 - @as(u32, n));
39 return ((x << l) & mask_hi) | ((x >> r) & mask_lo);
40 }
41
42 pub fn swapMove(a: *Word, b: *Word, comptime mask32: u32, comptime n: comptime_int) void {
43 const shift: Shift = n;
44 const mask = comptime splatWord(mask32);
45 const tmp = (b.* ^ (a.* >> shift)) & mask;
46 b.* ^= tmp;
47 a.* ^= tmp << shift;
48 }
49
50 fn sbox(u: []Word) void {
51 const s0 = u[4] ^ u[16];
52 const s1 = u[20] ^ u[28];
53 const s2 = u[12] ^ s0;
54 const s3 = u[0] ^ u[8];
55 const q0 = s1 ^ s2;
56 const s4 = u[0] ^ u[24];
57 const s5 = u[8] ^ u[24];
58 const s6 = u[12] ^ s1;
59 const s7 = u[20] ^ s3;
60 const q1 = s1 ^ s5;
61 const q2 = u[8] ^ q0;
62 const q3 = s4 ^ s2;
63 const q4 = s3 ^ q0;
64 const s8 = u[16] ^ s3;
65 const q5 = s6 ^ s8;
66 const q6 = u[8] ^ u[12];
67 const q7 = u[24] ^ s2;
68 const s9 = u[24] ^ s0;
69 const q8 = s3 ^ s9;
70 const q9 = s4 ^ s6;
71 const q10 = s0 ^ s5;
72 const q12 = u[28] ^ s2;
73 const q13 = u[4] ^ s7;
74 const q14 = u[28] ^ s3;
75 const q15 = s2 ^ s7;
76 const q16 = u[4] ^ s1;
77 const q17 = u[4] ^ u[28];
78 const q11 = u[20];
79
80 const t20 = q6 & q12;
81 const t21 = q3 & q14;
82 const t22 = q1 & q16;
83 const t23 = q2 & q17;
84 const x0 = ((q3 | q14) ^ (q0 & q7)) ^ (t20 ^ t22);
85 const x1 = ((q4 | q13) ^ (q10 & q11)) ^ (t21 ^ t20);
86 const x2 = ((q2 | q17) ^ (q5 & q9)) ^ (t21 ^ t22);
87 const x3 = ((q8 | q15) ^ t23) ^ (t21 ^ (q4 & q13));
88
89 const a = x1 & ~x3;
90 const b = x0 & ~x3;
91 const c = x3 & ~x1;
92 const d = x2 & ~x1;
93 const e = x0 ^ a;
94 const y0 = x3 ^ (x2 & ~e);
95 const f = x1 ^ b;
96 const y1 = c ^ (x2 & f);
97 const g = x2 ^ c;
98 const y2 = x1 ^ (x0 & ~g);
99 const h = x3 ^ d;
100 const y3 = a ^ (x0 & h);
101 const y02 = y2 ^ y0;
102 const y13 = y3 ^ y1;
103 const y23 = y3 ^ y2;
104 const y01 = y1 ^ y0;
105 const y00 = y02 ^ y13;
106
107 const a0 = y01 & q11;
108 const a1 = y0 & q12;
109 const a2 = y1 & q0;
110 const a3 = y23 & q17;
111 const a4 = y2 & q5;
112 const a5 = y3 & q15;
113 const a6 = y13 & q14;
114 const a7 = y00 & q16;
115 const a8 = y02 & q13;
116 const a9 = y01 & q7;
117 const a10 = y0 & q10;
118 const a11 = y1 & q6;
119 const a12 = y23 & q2;
120 const a13 = y2 & q9;
121 const a14 = y3 & q8;
122 const a15 = y13 & q3;
123 const a16 = y00 & q1;
124 const a17 = y02 & q4;
125
126 const r0 = a1 ^ a5;
127 const r1 = a9 ^ a15;
128 const r2 = a4 ^ r0;
129 const r3 = a2 ^ a10;
130 const r4 = a11 ^ a17;
131 const r5 = a8 ^ r1;
132 const r6 = a0 ^ a16;
133 const r7 = a7 ^ a13;
134 const r8 = a11 ^ a14;
135 const r9 = r3 ^ r4;
136 const r10 = r5 ^ r6;
137 const r11 = r2 ^ r9;
138 const r12 = a3 ^ r0;
139 const r13 = r7 ^ r8;
140 const r14 = r12 ^ r13;
141 u[0] = r10 ^ r14;
142 const r15 = a6 ^ a10;
143 const r16 = r15 ^ r2;
144 u[4] = ~(r10 ^ r16);
145 u[8] = ~(a2 ^ r2);
146 const r17 = a12 ^ a13;
147 const r18 = a15 ^ r17;
148 u[12] = r18 ^ r11;
149 const r19 = a1 ^ a14;
150 const r20 = a17 ^ r3;
151 const r21 = r7 ^ r19;
152 const r22 = r5 ^ r20;
153 u[16] = r21 ^ r22;
154 const r23 = a9 ^ a12;
155 u[20] = r8 ^ r23;
156 u[24] = ~(r1 ^ r4);
157 u[28] = ~(a16 ^ r11);
158 }
159
160 fn subBytes(st: *Bitsliced) void {
161 sbox(st[0..]);
162 sbox(st[1..]);
163 sbox(st[2..]);
164 sbox(st[3..]);
165 }
166
167 fn shiftRows(st: *Bitsliced) void {
168 var i: usize = 0;
169 while (i < 32) : (i += 4) {
170 st[i + 1] = laneRotl(st[i + 1], 24);
171 st[i + 2] = laneRotl(st[i + 2], 16);
172 st[i + 3] = laneRotl(st[i + 3], 8);
173 }
174 }
175
176 fn mixColumns(st: *Bitsliced) void {
177 const t2_0 = st[0] ^ st[1];
178 const t2_1 = st[1] ^ st[2];
179 const t2_2 = st[2] ^ st[3];
180 const t2_3 = st[3] ^ st[0];
181 var t0_0 = st[28] ^ st[29];
182 var t0_1 = st[29] ^ st[30];
183 var t0_2 = st[30] ^ st[31];
184 var t0_3 = st[31] ^ st[28];
185 var t = st[28];
186 st[28] = t2_0 ^ t0_2 ^ st[29];
187 st[29] = t2_1 ^ t0_2 ^ t;
188 t = st[30];
189 st[30] = t2_2 ^ t0_0 ^ st[31];
190 st[31] = t2_3 ^ t0_0 ^ t;
191 var t1_0 = st[24] ^ st[25];
192 var t1_1 = st[25] ^ st[26];
193 var t1_2 = st[26] ^ st[27];
194 var t1_3 = st[27] ^ st[24];
195 t = st[24];
196 st[24] = t0_0 ^ t2_0 ^ st[25] ^ t1_2;
197 var t_bis = st[25];
198 st[25] = t0_1 ^ t2_1 ^ t1_2 ^ t;
199 t = st[26];
200 st[26] = t0_2 ^ t2_2 ^ t1_3 ^ t_bis;
201 st[27] = t0_3 ^ t2_3 ^ t1_0 ^ t;
202 t0_0 = st[20] ^ st[21];
203 t0_1 = st[21] ^ st[22];
204 t0_2 = st[22] ^ st[23];
205 t0_3 = st[23] ^ st[20];
206 t = st[20];
207 st[20] = t1_0 ^ t0_1 ^ st[23];
208 t_bis = st[21];
209 st[21] = t1_1 ^ t0_2 ^ t;
210 t = st[22];
211 st[22] = t1_2 ^ t0_3 ^ t_bis;
212 st[23] = t1_3 ^ t0_0 ^ t;
213 t1_0 = st[16] ^ st[17];
214 t1_1 = st[17] ^ st[18];
215 t1_2 = st[18] ^ st[19];
216 t1_3 = st[19] ^ st[16];
217 t = st[16];
218 st[16] = t0_0 ^ t2_0 ^ t1_1 ^ st[19];
219 t_bis = st[17];
220 st[17] = t0_1 ^ t2_1 ^ t1_2 ^ t;
221 t = st[18];
222 st[18] = t0_2 ^ t2_2 ^ t1_3 ^ t_bis;
223 st[19] = t0_3 ^ t2_3 ^ t1_0 ^ t;
224 t0_0 = st[12] ^ st[13];
225 t0_1 = st[13] ^ st[14];
226 t0_2 = st[14] ^ st[15];
227 t0_3 = st[15] ^ st[12];
228 t = st[12];
229 st[12] = t1_0 ^ t2_0 ^ t0_1 ^ st[15];
230 t_bis = st[13];
231 st[13] = t1_1 ^ t2_1 ^ t0_2 ^ t;
232 t = st[14];
233 st[14] = t1_2 ^ t2_2 ^ t0_3 ^ t_bis;
234 st[15] = t1_3 ^ t2_3 ^ t0_0 ^ t;
235 t1_0 = st[8] ^ st[9];
236 t1_1 = st[9] ^ st[10];
237 t1_2 = st[10] ^ st[11];
238 t1_3 = st[11] ^ st[8];
239 t = st[8];
240 st[8] = t0_0 ^ t1_1 ^ st[11];
241 t_bis = st[9];
242 st[9] = t0_1 ^ t1_2 ^ t;
243 t = st[10];
244 st[10] = t0_2 ^ t1_3 ^ t_bis;
245 st[11] = t0_3 ^ t1_0 ^ t;
246 t0_0 = st[4] ^ st[5];
247 t0_1 = st[5] ^ st[6];
248 t0_2 = st[6] ^ st[7];
249 t0_3 = st[7] ^ st[4];
250 t = st[4];
251 st[4] = t1_0 ^ t0_1 ^ st[7];
252 t_bis = st[5];
253 st[5] = t1_1 ^ t0_2 ^ t;
254 t = st[6];
255 st[6] = t1_2 ^ t0_3 ^ t_bis;
256 st[7] = t1_3 ^ t0_0 ^ t;
257 t = st[0];
258 st[0] = t0_0 ^ t2_1 ^ st[3];
259 t_bis = st[1];
260 st[1] = t0_1 ^ t2_2 ^ t;
261 t = st[2];
262 st[2] = t0_2 ^ t2_3 ^ t_bis;
263 st[3] = t0_3 ^ t2_0 ^ t;
264 }
265
266 /// One keyless AES round (SubBytes, ShiftRows, MixColumns) over the whole batch
267 pub fn round(st: *Bitsliced) void {
268 subBytes(st);
269 shiftRows(st);
270 mixColumns(st);
271 }
272
273 /// Add a round key, which must also be in bitsliced form
274 pub fn addRoundKey(st: *Bitsliced, round_key: *const Bitsliced) void {
275 for (0..32) |i| st[i] ^= round_key[i];
276 }
277
278 /// Orthogonalization (converts blocks-in-words to bit slices)
279 pub fn packState(st: *Bitsliced) void {
280 var i: usize = 0;
281 while (i < 32) : (i += 4) {
282 swapMove(&st[i], &st[i + 1], 0x00ff00ff, 8);
283 swapMove(&st[i + 2], &st[i + 3], 0x00ff00ff, 8);
284 swapMove(&st[i], &st[i + 2], 0x0000ffff, 16);
285 swapMove(&st[i + 1], &st[i + 3], 0x0000ffff, 16);
286 }
287 for (0..4) |k| {
288 swapMove(&st[k + 4], &st[k], 0x55555555, 1);
289 swapMove(&st[k + 12], &st[k + 8], 0x55555555, 1);
290 swapMove(&st[k + 20], &st[k + 16], 0x55555555, 1);
291 swapMove(&st[k + 28], &st[k + 24], 0x55555555, 1);
292 swapMove(&st[k + 8], &st[k], 0x33333333, 2);
293 swapMove(&st[k + 12], &st[k + 4], 0x33333333, 2);
294 swapMove(&st[k + 24], &st[k + 16], 0x33333333, 2);
295 swapMove(&st[k + 28], &st[k + 20], 0x33333333, 2);
296 swapMove(&st[k + 16], &st[k], 0x0f0f0f0f, 4);
297 swapMove(&st[k + 20], &st[k + 4], 0x0f0f0f0f, 4);
298 swapMove(&st[k + 24], &st[k + 8], 0x0f0f0f0f, 4);
299 swapMove(&st[k + 28], &st[k + 12], 0x0f0f0f0f, 4);
300 }
301 }
302
303 /// Inverse of `packState`
304 pub fn unpackState(st: *Bitsliced) void {
305 for (0..4) |k| {
306 swapMove(&st[k + 4], &st[k], 0x55555555, 1);
307 swapMove(&st[k + 12], &st[k + 8], 0x55555555, 1);
308 swapMove(&st[k + 20], &st[k + 16], 0x55555555, 1);
309 swapMove(&st[k + 28], &st[k + 24], 0x55555555, 1);
310 swapMove(&st[k + 8], &st[k], 0x33333333, 2);
311 swapMove(&st[k + 12], &st[k + 4], 0x33333333, 2);
312 swapMove(&st[k + 24], &st[k + 16], 0x33333333, 2);
313 swapMove(&st[k + 28], &st[k + 20], 0x33333333, 2);
314 swapMove(&st[k + 16], &st[k], 0x0f0f0f0f, 4);
315 swapMove(&st[k + 20], &st[k + 4], 0x0f0f0f0f, 4);
316 swapMove(&st[k + 24], &st[k + 8], 0x0f0f0f0f, 4);
317 swapMove(&st[k + 28], &st[k + 12], 0x0f0f0f0f, 4);
318 }
319 var i: usize = 0;
320 while (i < 32) : (i += 4) {
321 swapMove(&st[i], &st[i + 2], 0x0000ffff, 16);
322 swapMove(&st[i + 1], &st[i + 3], 0x0000ffff, 16);
323 swapMove(&st[i], &st[i + 1], 0x00ff00ff, 8);
324 swapMove(&st[i + 2], &st[i + 3], 0x00ff00ff, 8);
325 }
326 }
327
328 /// Convert `nblocks` little-endian blocks into the packed state.
329 /// Blocks`8*g .. 8*g+7` land in lane `g` of each word.
330 fn pack(bytes: *const [nblocks * 16]u8) Bitsliced {
331 var st: Bitsliced = @splat(0);
332 inline for (0..nblocks) |bl| {
333 const g = bl / 8;
334 const p = bl % 8;
335 inline for (0..4) |wd| {
336 const v = mem.readInt(u32, bytes[bl * 16 + wd * 4 ..][0..4], .little);
337 st[p * 4 + wd] |= @as(Word, v) << (32 * g);
338 }
339 }
340 packState(&st);
341 return st;
342 }
343
344 /// Inverse of `pack`
345 fn unpack(st: *Bitsliced) [nblocks * 16]u8 {
346 unpackState(st);
347 var bytes: [nblocks * 16]u8 = undefined;
348 inline for (0..nblocks) |bl| {
349 const g = bl / 8;
350 const p = bl % 8;
351 inline for (0..4) |wd| {
352 const v: u32 = @truncate(st[p * 4 + wd] >> (32 * g));
353 mem.writeInt(u32, bytes[bl * 16 + wd * 4 ..][0..4], v, .little);
354 }
355 }
356 return bytes;
357 }
358
359 /// Precompute where key bit `i` of a round key lands after packing
360 const broadcast_loc = blk: {
361 @setEvalBranchQuota(200000);
362 var loc: [128]struct { word: u8, shift: u5 } = undefined;
363 for (0..128) |i| {
364 var st: Bitsliced = @splat(0);
365 const word = (i / 8) / 4;
366 const bit: u5 = @intCast(((i / 8) % 4) * 8 + (i % 8));
367 for (0..8) |b| st[b * 4 + word] |= @as(Word, 1) << bit;
368 packState(&st);
369 found: for (0..32) |w| {
370 for (0..4) |byte| {
371 if ((st[w] >> @intCast(byte * 8)) & 0xff == 0xff) {
372 loc[i] = .{ .word = @intCast(w), .shift = @intCast(byte * 8) };
373 break :found;
374 }
375 }
376 }
377 }
378 break :blk loc;
379 };
380
381 /// Pack a round key and broadcast it to all blocks, so it can be directly XORed into a packed state
382 fn packRoundKey(round_key: *const [16]u8) Bitsliced {
383 var st: Bitsliced = @splat(0);
384 inline for (0..16) |pos| {
385 const byte = round_key[pos];
386 inline for (0..8) |bit| {
387 const smear: Word = 0 -% @as(Word, (byte >> bit) & 1);
388 const loc = broadcast_loc[pos * 8 + bit];
389 st[loc.word] |= smear & (splatWord(0xff) << loc.shift);
390 }
391 }
392 return st;
393 }
394
395 /// Pack a whole key schedule into the bitsliced representation.
396 pub fn packKeys(round_keys: anytype) [round_keys.len]Bitsliced {
397 var keys: [round_keys.len]Bitsliced = undefined;
398 inline for (&keys, 0..) |*k, r| {
399 const bytes = round_keys[r].toBytes();
400 k.* = packRoundKey(&bytes);
401 }
402 return keys;
403 }
404
405 /// Run the full AES cipher over the whole batch in place
406 pub fn encrypt(comptime rounds: usize, keys: *const [rounds + 1]Bitsliced, data: *[nblocks * 16]u8) void {
407 var st = pack(data);
408 addRoundKey(&st, &keys[0]);
409 inline for (1..rounds) |r| {
410 round(&st);
411 addRoundKey(&st, &keys[r]);
412 }
413 subBytes(&st);
414 shiftRows(&st);
415 addRoundKey(&st, &keys[rounds]);
416 data.* = unpack(&st);
417 }
418
419 fn invShiftRows(st: *Bitsliced) void {
420 var i: usize = 0;
421 while (i < 32) : (i += 4) {
422 st[i + 1] = laneRotl(st[i + 1], 8);
423 st[i + 2] = laneRotl(st[i + 2], 16);
424 st[i + 3] = laneRotl(st[i + 3], 24);
425 }
426 }
427
428 /// Precompute the `mixColumns` inverse matrix
429 const mix_inv: [32]u32 = blk: {
430 @setEvalBranchQuota(200000);
431 var m: [32]u32 = @splat(0);
432 for (0..32) |i| {
433 var st: Bitsliced = @splat(0);
434 st[i] = 1;
435 mixColumns(&st);
436 for (0..32) |o| {
437 if (st[o] & 1 == 1) m[o] |= @as(u32, 1) << @intCast(i);
438 }
439 }
440 var inv: [32]u32 = undefined;
441 for (0..32) |k| inv[k] = @as(u32, 1) << @intCast(k);
442 for (0..32) |col| {
443 var piv = col;
444 while (m[piv] & (@as(u32, 1) << @intCast(col)) == 0) piv += 1;
445 const tm = m[col];
446 m[col] = m[piv];
447 m[piv] = tm;
448 const ti = inv[col];
449 inv[col] = inv[piv];
450 inv[piv] = ti;
451 for (0..32) |r| {
452 if (r != col and (m[r] & (@as(u32, 1) << @intCast(col))) != 0) {
453 m[r] ^= m[col];
454 inv[r] ^= inv[col];
455 }
456 }
457 }
458 break :blk inv;
459 };
460
461 fn invMixColumns(st: *Bitsliced) void {
462 @setEvalBranchQuota(20000);
463 var out: Bitsliced = @splat(0);
464 inline for (0..32) |o| {
465 inline for (0..32) |j| {
466 if (mix_inv[o] & (@as(u32, 1) << @intCast(j)) != 0) out[o] ^= st[j];
467 }
468 }
469 st.* = out;
470 }
471
472 /// Linear map for the inverse S-box
473 fn invAffineGroup(st: *Bitsliced, comptime g: usize) void {
474 const p = [8]usize{ g + 28, g + 24, g + 20, g + 16, g + 12, g + 8, g + 4, g };
475 const a0 = ~st[p[0]];
476 const a1 = ~st[p[1]];
477 const a2 = st[p[2]];
478 const a3 = st[p[3]];
479 const a4 = st[p[4]];
480 const a5 = ~st[p[5]];
481 const a6 = ~st[p[6]];
482 const a7 = st[p[7]];
483 st[p[7]] = a1 ^ a4 ^ a6;
484 st[p[6]] = a0 ^ a3 ^ a5;
485 st[p[5]] = a7 ^ a2 ^ a4;
486 st[p[4]] = a6 ^ a1 ^ a3;
487 st[p[3]] = a5 ^ a0 ^ a2;
488 st[p[2]] = a4 ^ a7 ^ a1;
489 st[p[1]] = a3 ^ a6 ^ a0;
490 st[p[0]] = a2 ^ a5 ^ a7;
491 }
492
493 /// Compact inverse S-box: we apply a fixed linear map, the forward S-box, then the same linear map
494 /// Trick from Thomas Pornin
495 fn invSubBytes(st: *Bitsliced) void {
496 inline for (0..4) |g| invAffineGroup(st, g);
497 subBytes(st);
498 inline for (0..4) |g| invAffineGroup(st, g);
499 }
500
501 /// Full inverse AES cipher over the whole batch in bitsliced representation
502 pub fn decrypt(comptime rounds: usize, keys: *const [rounds + 1]Bitsliced, data: *[nblocks * 16]u8) void {
503 var st = pack(data);
504 addRoundKey(&st, &keys[rounds]);
505 comptime var r: usize = rounds - 1;
506 inline while (r > 0) : (r -= 1) {
507 invShiftRows(&st);
508 invSubBytes(&st);
509 addRoundKey(&st, &keys[r]);
510 invMixColumns(&st);
511 }
512 invShiftRows(&st);
513 invSubBytes(&st);
514 addRoundKey(&st, &keys[0]);
515 data.* = unpack(&st);
516 }
517 };
518}
519
520const testing = std.testing;
521
522test "bitslice cross-check against table AES" {
523 const key: [16]u8 = @splat(0x37);
524 const ref = std.crypto.core.aes.Aes128.initEnc(key);
525 const keys = Wide.packKeys(ref.key_schedule.round_keys[0..]);
526
527 var data: [width * 16]u8 = undefined;
528 for (0..width * 16) |i| data[i] = @truncate(i *% 101 +% 7);
529
530 var expected: [width * 16]u8 = undefined;
531 for (0..width) |b| {
532 var blk: [16]u8 = data[b * 16 ..][0..16].*;
533 ref.encrypt(&blk, &blk);
534 expected[b * 16 ..][0..16].* = blk;
535 }
536
537 Wide.encrypt(10, &keys, &data);
538 try testing.expectEqualSlices(u8, &expected, &data);
539}
540
541test "bitslice decrypt cross-check against table AES" {
542 inline for (.{ std.crypto.core.aes.Aes128, std.crypto.core.aes.Aes256 }) |Aes| {
543 const key: [Aes.key_bits / 8]u8 = @splat(0x4d);
544 const enc = Aes.initEnc(key);
545 const dec = Aes.initDec(key);
546 const keys = Wide.packKeys(enc.key_schedule.round_keys[0..]);
547
548 var data: [width * 16]u8 = undefined;
549 for (0..width * 16) |i| data[i] = @truncate(i *% 211 +% 13);
550
551 var expected: [width * 16]u8 = undefined;
552 for (0..width) |b| {
553 var blk: [16]u8 = data[b * 16 ..][0..16].*;
554 dec.decrypt(&blk, &blk);
555 expected[b * 16 ..][0..16].* = blk;
556 }
557
558 Wide.decrypt(Aes.rounds, &keys, &data);
559 try testing.expectEqualSlices(u8, &expected, &data);
560 }
561}