@@ -19,7 +19,6 @@ pub const State = struct {
19
19
pub const BLOCKBYTES = 48 ;
20
20
pub const RATE = 16 ;
21
21
22
- // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
23
22
data : [BLOCKBYTES / 4 ]u32 ,
24
23
25
24
const Self = @This ();
@@ -168,3 +167,222 @@ test "hash" {
168
167
hash (& md , & msg );
169
168
htest .assertEqual ("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44" , & md );
170
169
}
170
+
171
+ pub const Aead = struct {
172
+ /// ad: Associated Data
173
+ /// npub: public nonce
174
+ /// k: private key
175
+ fn init (ad : []const u8 , npub : [16 ]u8 , k : [32 ]u8 ) State {
176
+ var state = State {
177
+ .data = undefined ,
178
+ };
179
+ const buf = state .toSlice ();
180
+
181
+ // Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
182
+ // followed by a 32-byte key.
183
+ assert (npub .len + k .len == State .BLOCKBYTES );
184
+ std .mem .copy (u8 , buf [0.. npub .len ], & npub );
185
+ std .mem .copy (u8 , buf [npub .len .. npub .len + k .len ], & k );
186
+
187
+ // It then applies the Gimli permutation.
188
+ state .permute ();
189
+
190
+ {
191
+ // Gimli-Cipher then handles each block of associated data, including
192
+ // exactly one final non-full block, in the same way as Gimli-Hash.
193
+ var data = ad ;
194
+ while (data .len >= State .RATE ) : (data = data [State .RATE .. ]) {
195
+ for (buf [0.. State .RATE ]) | * p , i | {
196
+ p .* ^= data [i ];
197
+ }
198
+ state .permute ();
199
+ }
200
+ for (buf [0.. data .len ]) | * p , i | {
201
+ p .* ^= data [i ];
202
+ }
203
+
204
+ // XOR 1 into the next byte of the state
205
+ buf [data .len ] ^= 1 ;
206
+ // XOR 1 into the last byte of the state, position 47.
207
+ buf [buf .len - 1 ] ^= 1 ;
208
+
209
+ state .permute ();
210
+ }
211
+
212
+ return state ;
213
+ }
214
+
215
+ /// c: ciphertext: output buffer should be of size m.len
216
+ /// at: authentication tag: output MAC
217
+ /// m: message
218
+ /// ad: Associated Data
219
+ /// npub: public nonce
220
+ /// k: private key
221
+ pub fn encrypt (c : []u8 , at : * [State .RATE ]u8 , m : []const u8 , ad : []const u8 , npub : [16 ]u8 , k : [32 ]u8 ) void {
222
+ assert (c .len == m .len );
223
+
224
+ var state = Aead .init (ad , npub , k );
225
+ const buf = state .toSlice ();
226
+
227
+ // Gimli-Cipher then handles each block of plaintext, including
228
+ // exactly one final non-full block, in the same way as Gimli-Hash.
229
+ // Whenever a plaintext byte is XORed into a state byte, the new state
230
+ // byte is output as ciphertext.
231
+ var in = m ;
232
+ var out = c ;
233
+ while (in .len >= State .RATE ) : ({
234
+ in = in [State .RATE .. ];
235
+ out = out [State .RATE .. ];
236
+ }) {
237
+ for (buf [0.. State .RATE ]) | * p , i | {
238
+ p .* ^= in [i ];
239
+ out [i ] = p .* ;
240
+ }
241
+ state .permute ();
242
+ }
243
+ for (buf [0.. in .len ]) | * p , i | {
244
+ p .* ^= in [i ];
245
+ out [i ] = p .* ;
246
+ }
247
+
248
+ // XOR 1 into the next byte of the state
249
+ buf [in .len ] ^= 1 ;
250
+ // XOR 1 into the last byte of the state, position 47.
251
+ buf [buf .len - 1 ] ^= 1 ;
252
+
253
+ state .permute ();
254
+
255
+ // After the final non-full block of plaintext, the first 16 bytes
256
+ // of the state are output as an authentication tag.
257
+ @memcpy (at , buf .ptr , State .RATE );
258
+ }
259
+
260
+ /// m: message: output buffer should be of size c.len
261
+ /// c: ciphertext
262
+ /// at: authentication tag
263
+ /// ad: Associated Data
264
+ /// npub: public nonce
265
+ /// k: private key
266
+ /// NOTE: the check of the authentication tag is currently not done in constant time
267
+ pub fn decrypt (m : []u8 , c : []const u8 , at : [State .RATE ]u8 , ad : []u8 , npub : [16 ]u8 , k : [32 ]u8 ) ! void {
268
+ assert (c .len == m .len );
269
+
270
+ var state = Aead .init (ad , npub , k );
271
+ const buf = state .toSlice ();
272
+
273
+ var in = c ;
274
+ var out = m ;
275
+ while (in .len >= State .RATE ) : ({
276
+ in = in [State .RATE .. ];
277
+ out = out [State .RATE .. ];
278
+ }) {
279
+ for (buf [0.. State .RATE ]) | * p , i | {
280
+ out [i ] = p .* ^ in [i ];
281
+ p .* = in [i ];
282
+ }
283
+ state .permute ();
284
+ }
285
+ for (buf [0.. in .len ]) | * p , i | {
286
+ out [i ] = p .* ^ in [i ];
287
+ p .* = in [i ];
288
+ }
289
+
290
+ // XOR 1 into the next byte of the state
291
+ buf [in .len ] ^= 1 ;
292
+ // XOR 1 into the last byte of the state, position 47.
293
+ buf [buf .len - 1 ] ^= 1 ;
294
+
295
+ state .permute ();
296
+
297
+ // After the final non-full block of plaintext, the first 16 bytes
298
+ // of the state are the authentication tag.
299
+ // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
300
+ if (! mem .eql (u8 , buf [0.. State .RATE ], & at )) {
301
+ @memset (m .ptr , undefined , m .len );
302
+ return error .InvalidMessage ;
303
+ }
304
+ }
305
+ };
306
+
307
+ test "cipher" {
308
+ var key : [32 ]u8 = undefined ;
309
+ try std .fmt .hexToBytes (& key , "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" );
310
+ var nonce : [16 ]u8 = undefined ;
311
+ try std .fmt .hexToBytes (& nonce , "000102030405060708090A0B0C0D0E0F" );
312
+ { // test vector (1) from NIST KAT submission.
313
+ const ad : [0 ]u8 = undefined ;
314
+ const pt : [0 ]u8 = undefined ;
315
+
316
+ var ct : [pt .len ]u8 = undefined ;
317
+ var at : [16 ]u8 = undefined ;
318
+ Aead .encrypt (& ct , & at , & pt , & ad , nonce , key );
319
+ htest .assertEqual ("" , & ct );
320
+ htest .assertEqual ("14DA9BB7120BF58B985A8E00FDEBA15B" , & at );
321
+
322
+ var pt2 : [pt .len ]u8 = undefined ;
323
+ try Aead .decrypt (& pt2 , & ct , at , & ad , nonce , key );
324
+ testing .expectEqualSlices (u8 , & pt , & pt2 );
325
+ }
326
+ { // test vector (34) from NIST KAT submission.
327
+ const ad : [0 ]u8 = undefined ;
328
+ var pt : [2 / 2 ]u8 = undefined ;
329
+ try std .fmt .hexToBytes (& pt , "00" );
330
+
331
+ var ct : [pt .len ]u8 = undefined ;
332
+ var at : [16 ]u8 = undefined ;
333
+ Aead .encrypt (& ct , & at , & pt , & ad , nonce , key );
334
+ htest .assertEqual ("7F" , & ct );
335
+ htest .assertEqual ("80492C317B1CD58A1EDC3A0D3E9876FC" , & at );
336
+
337
+ var pt2 : [pt .len ]u8 = undefined ;
338
+ try Aead .decrypt (& pt2 , & ct , at , & ad , nonce , key );
339
+ testing .expectEqualSlices (u8 , & pt , & pt2 );
340
+ }
341
+ { // test vector (106) from NIST KAT submission.
342
+ var ad : [12 / 2 ]u8 = undefined ;
343
+ try std .fmt .hexToBytes (& ad , "000102030405" );
344
+ var pt : [6 / 2 ]u8 = undefined ;
345
+ try std .fmt .hexToBytes (& pt , "000102" );
346
+
347
+ var ct : [pt .len ]u8 = undefined ;
348
+ var at : [16 ]u8 = undefined ;
349
+ Aead .encrypt (& ct , & at , & pt , & ad , nonce , key );
350
+ htest .assertEqual ("484D35" , & ct );
351
+ htest .assertEqual ("030BBEA23B61C00CED60A923BDCF9147" , & at );
352
+
353
+ var pt2 : [pt .len ]u8 = undefined ;
354
+ try Aead .decrypt (& pt2 , & ct , at , & ad , nonce , key );
355
+ testing .expectEqualSlices (u8 , & pt , & pt2 );
356
+ }
357
+ { // test vector (790) from NIST KAT submission.
358
+ var ad : [60 / 2 ]u8 = undefined ;
359
+ try std .fmt .hexToBytes (& ad , "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D" );
360
+ var pt : [46 / 2 ]u8 = undefined ;
361
+ try std .fmt .hexToBytes (& pt , "000102030405060708090A0B0C0D0E0F10111213141516" );
362
+
363
+ var ct : [pt .len ]u8 = undefined ;
364
+ var at : [16 ]u8 = undefined ;
365
+ Aead .encrypt (& ct , & at , & pt , & ad , nonce , key );
366
+ htest .assertEqual ("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1" , & ct );
367
+ htest .assertEqual ("DFE23F1642508290D68245279558B2FB" , & at );
368
+
369
+ var pt2 : [pt .len ]u8 = undefined ;
370
+ try Aead .decrypt (& pt2 , & ct , at , & ad , nonce , key );
371
+ testing .expectEqualSlices (u8 , & pt , & pt2 );
372
+ }
373
+ { // test vector (1057) from NIST KAT submission.
374
+ const ad : [0 ]u8 = undefined ;
375
+ var pt : [64 / 2 ]u8 = undefined ;
376
+ try std .fmt .hexToBytes (& pt , "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" );
377
+
378
+ var ct : [pt .len ]u8 = undefined ;
379
+ var at : [16 ]u8 = undefined ;
380
+ Aead .encrypt (& ct , & at , & pt , & ad , nonce , key );
381
+ htest .assertEqual ("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F" , & ct );
382
+ htest .assertEqual ("7256456FA935AC34BBF55AE135F33257" , & at );
383
+
384
+ var pt2 : [pt .len ]u8 = undefined ;
385
+ try Aead .decrypt (& pt2 , & ct , at , & ad , nonce , key );
386
+ testing .expectEqualSlices (u8 , & pt , & pt2 );
387
+ }
388
+ }
0 commit comments