diff --git a/lib/std/hash/poly1305.c3 b/lib/std/hash/poly1305.c3 new file mode 100644 index 000000000..b3ebb3e69 --- /dev/null +++ b/lib/std/hash/poly1305.c3 @@ -0,0 +1,171 @@ +// Copyright (c) 2025 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// Poly1305 code dedicated from repo: https://github.com/NotsoanoNimus/chacha20_aead.c3l (but massively cleaned) +module std::hash::poly1305; + + +<* The fixed output length of Poly1305 tags (hashes). *> +const TAG_SIZE = 16; + +<* Fixed length of a Poly1305 block. *> +const BLOCK_SIZE = 16; + +<* Fixed length of the required Poly1305 key, *> +const KEY_SIZE = 32; + + +struct Poly1305 +{ + ulong[3] h; // hash internal state + uint128 r; // secret portion of key + uint128 nonce; // initialization vector, derived from key + char[TAG_SIZE] temp; // last partial ingestion state + usz num; // index into last partial ingestion + // Additional, cached state information: + ulong r0; + ulong r1; + ulong s1; +} + + +<* Constant-time carrying computation for block permutations. *> +macro ulong constant_time_carry(ulong a, ulong b) @local +{ + return (a ^ ((a ^ b) | ((a - b) ^ b))) >> (bitsizeof(ulong) - 1); +} + + +<* + Compute a Poly1305 message authentication code for the input with the given key (secret + nonce) value. + Note that this construct SHOULD NOT be used with the 'Hmac' module; it is its own MAC package. + + @param[in] input + @param key +*> +fn char[TAG_SIZE] hash(char[] input, char[KEY_SIZE] key) +{ + Poly1305 p @noinit; + p.init(key); + p.update(input); + return p.final(); +} + +<* Alias for the `hash` function; Message Authentication Code. *> +alias mac = hash; + +<* Alias for the `hash` function; "tag" generation. *> +alias tag = hash; + + +fn void Poly1305.init(&self, char[KEY_SIZE] key) +{ + *self = { // implicitly clears state as well + .r = @unaligned_load(*(uint128*)&key[ 0], 1) & 0x0ffffffc_0ffffffc_0ffffffc_0fffffff, // clamped per spec + .nonce = @unaligned_load(*(uint128*)&key[16], 1) + }; + + self.r0 = @unaligned_load(((ulong*)&self.r)[0], 1); + self.r1 = @unaligned_load(((ulong*)&self.r)[1], 1); + + self.s1 = self.r1 + (self.r1 >> 2); +} + + +fn void Poly1305.update(&self, char[] input) +{ + if (self.num) // currently between consuming full blocks? + { + usz rem = BLOCK_SIZE - self.num; + if (input.len < rem) + { + self.temp[self.num:input.len] = input[..]; // saving another partial block + self.num += input.len; // move index forward + return; + } + // ingest up to a block size to finish the partial, then advance the slice ptr + self.temp[self.num:rem] = input[:rem]; + self.blocks(self.temp[..]); + input = input[rem..]; + } + + usz even_length = input.len - (input.len % BLOCK_SIZE); + if (even_length >= BLOCK_SIZE) + { + self.blocks(input[:even_length]); // consume blocks + input = input[even_length..]; // scroll to end (remainder) + } + + if (input.len) self.temp[:input.len] = input[..]; // keep remainder (uneven block sizes) + + self.num = input.len; +} + + +fn char[TAG_SIZE] Poly1305.final(&self) +{ + if (self.num) // consume any leftovers + { + self.temp[self.num++] = 1; // partial blocks must end with 0x01 + self.temp[self.num..] = {}; // explicit zeros on the rest + self.blocks(self.temp[..], 0); // chomp + } + + uint128 t = (uint128)self.h[0] + 5; + ulong g0 = (ulong)t; + + t = (uint128)self.h[1] + (t >> 64); + ulong g1 = (ulong)t; + + ulong mask = 0 - ((self.h[2] + (ulong)(t >> 64)) >> 2); + self.h[0] = (self.h[0] & ~mask) | (g0 & mask); + self.h[1] = (self.h[1] & ~mask) | (g1 & mask); + + t = (uint128)self.h[0] + (ulong)self.nonce; + self.h[0] = (ulong)t; + + t = (uint128)self.h[1] + (ulong)(self.nonce >> 64) + (t >> 64); + self.h[1] = (ulong)t; + + // Store, clear context, return. + uint128 result = ((uint128)self.h[1] << 64) + self.h[0]; + *self = {}; + return @as_char_view(result)[:TAG_SIZE]; +} + + +fn void Poly1305.blocks(&self, char[] input, ulong pad_bit = 1) @local +{ + for (; input.len >= BLOCK_SIZE; input = input[BLOCK_SIZE..]) + { + ulong i0 = @unaligned_load(*(ulong*)&input[0], 1); + ulong i1 = @unaligned_load(*(ulong*)&input[8], 1); + + uint128 d0 = (uint128)self.h[0] + i0; + self.h[0] = (ulong)d0; + + uint128 d1 = (uint128)self.h[1] + (d0 >> 64) + i1; + self.h[1] = (ulong)d1; + + self.h[2] += (ulong)(d1 >> 64) + pad_bit; + + d0 = ((uint128)self.h[0] * self.r0) + ((uint128)self.h[1] * self.s1); + d1 = ((uint128)self.h[0] * self.r1) + ((uint128)self.h[1] * self.r0) + ((uint128)self.h[2] * self.s1); + self.h[2] = (self.h[2] * self.r0); + + self.h[0] = (ulong)d0; + + d1 = d1 + (d0 >> 64); + self.h[1] = (ulong)d1; + self.h[2] = self.h[2] + (ulong)(d1 >> 64); + + ulong c = (self.h[2] >> 2) + (self.h[2] & ~(ulong)3); + + self.h[2] &= 3; + self.h[0] += c; + c = constant_time_carry(self.h[0], c); + self.h[1] += c; + self.h[2] += constant_time_carry(self.h[1], c); + } +} diff --git a/releasenotes.md b/releasenotes.md index 58a251c78..af958b537 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -27,6 +27,7 @@ - Buffered/UnbufferedChannel, and both ThreadPools have `@maydiscard` on a set of functions. They will retunr void in 0.8.0. - Pthread bindings correctly return Errno instead of CInt. - Return of Thread `join()` is now "@maydiscard". +- Add `poly1305` one-time Message Authentication Code and associated tests. #2639 ## 0.7.8 Change list diff --git a/test/unit/stdlib/hash/poly1305.c3 b/test/unit/stdlib/hash/poly1305.c3 new file mode 100644 index 000000000..34b0240a2 --- /dev/null +++ b/test/unit/stdlib/hash/poly1305.c3 @@ -0,0 +1,212 @@ +// Copyright (c) 2025 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// Poly1305 code dedicated from repo: https://github.com/NotsoanoNimus/chacha20_aead.c3l (but massively cleaned) +module std::hash::poly1305_test; +import std::hash::poly1305; + +macro @run(char[] plaintext, char[poly1305::KEY_SIZE] key, char[poly1305::TAG_SIZE] expected_tag) @private +{ + char[poly1305::TAG_SIZE] tag = poly1305::hash(plaintext, key); + test::@check(tag == expected_tag, "`poly1305::hash()`: Did not get expected tag."); +} + + +// Actual tests go here... +// +// You should check out RFC 8439 for more information. +// NOTE: from RFC tests, `len - 1` (or `[..^1]`) is used to get rid of the string's null-terminator per test requirements. +// +// Extra tests taken from Google's BoringSSL: +// https://github.com/google/boringssl/blob/main/crypto/poly1305/poly1305_tests.txt +module std::hash::poly1305_test @test; +import std::hash::poly1305; +import std::math; + +fn void rfc8439_s2_5_2() => @run( + "Cryptographic Forum Research Group"[..^1], + { 0x85, 0xd6, 0xbe, 0x78, 0x57, 0x55, 0x6d, 0x33, 0x7f, 0x44, 0x52, 0xfe, 0x42, 0xd5, 0x06, 0xa8, 0x01, 0x03, 0x80, 0x8a, 0xfb, 0x0d, 0xb2, 0xfd, 0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b }, + { 0xa8, 0x06, 0x1d, 0xc1, 0x30, 0x51, 0x36, 0xc6, 0xc2, 0x2b, 0x8b, 0xaf, 0x0c, 0x01, 0x27, 0xa9 } +); + +fn void rfc8439_sA_3_1() => @run( + ((char[64]){ [0..63] = 0 })[..], // 64 null bytes + {}, // empty key + { [0..15] = 0 }, // empty tag is expected +); + +fn void rfc8439_sA_3_2() => @run( + `Any submission to the IETF intended by the Contributor for publication as all or part of an IETF Internet-Draft or RFC and any statement made within the context of an IETF activity is considered an "IETF Contribution". Such statements include oral statements in IETF sessions, as well as written and electronic communications made at any time or place, which are addressed to`[..^1], + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70, 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e }, + { 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70, 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e } +); + +fn void rfc8439_sA_3_3() => @run( + `Any submission to the IETF intended by the Contributor for publication as all or part of an IETF Internet-Draft or RFC and any statement made within the context of an IETF activity is considered an "IETF Contribution". Such statements include oral statements in IETF sessions, as well as written and electronic communications made at any time or place, which are addressed to`[..^1], + { 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70, 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0xf3, 0x47, 0x7e, 0x7c, 0xd9, 0x54, 0x17, 0xaf, 0x89, 0xa6, 0xb8, 0x79, 0x4c, 0x31, 0x0c, 0xf0 } +); + +fn void rfc8439_sA_3_4() => @run( + "'Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe."[..^1], + { 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0 }, + { 0x45, 0x41, 0x66, 0x9a, 0x7e, 0xaa, 0xee, 0x61, 0xe7, 0x08, 0xdc, 0x7c, 0xbc, 0xc5, 0xeb, 0x62 }, +); + +// RFC NOTE: If one uses 130-bit partial reduction, does the code handle the case where partially reduced final result is not fully reduced? +fn void rfc8439_sA_3_5() => @run( + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +// RFC NOTE: What happens if addition of s overflows module 2^128? +fn void rfc8439_sA_3_6() => @run( + { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +// RFC NOTE: What happens if data limb is all ones and there is carry from lower limb? +fn void rfc8439_sA_3_7() => @run( + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +// RFC NOTE: What happens if final result from polynomial part is exactly 2^130-5? +fn void rfc8439_sA_3_8() => @run( + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfb, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + }, + { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +// RFC NOTE: What happens if final result from polynomial part is exactly 2^130-6? +fn void rfc8439_sA_3_9() => @run( + { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } +); + +// RFC NOTE: What happens if 5*H+L-type reduction produces 131-bit intermediate result? +fn void rfc8439_sA_3_10() => @run( + { + 0xe3, 0x35, 0x94, 0xd7, 0x50, 0x5e, 0x43, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x94, 0xd7, 0x50, 0x5e, 0x43, 0x79, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +// RFC NOTE: What happens if 5*H+L-type reduction produces 131-bit final result? +fn void rfc8439_sA_3_11() => @run( + { + 0xe3, 0x35, 0x94, 0xd7, 0x50, 0x5e, 0x43, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x94, 0xd7, 0x50, 0x5e, 0x43, 0x79, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +); + +fn void boringssl_misc_regressions_1() => @run( + x'cccccccccccccccccccccccccccccccccccccccccccccccccc80ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccceccccccccccccccccccccccccccccccccccccc5cccccccccccccccccccccccccccccccccccccccccce3ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaccccccccccccccccccccce6cccccccccc000000afccccccccccccccccccfffffff5000000000000000000000000000000000000000000000000000000ffffffe70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000719205a8521dfc', + x'7f1b02640000000000000000000000000000000000000000cccccccccccccccc', + x'8559b876eceed66eb37798c0457baff9' +); + +fn void boringssl_misc_regressions_2() => @run( + x'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000800264', + x'e00016000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaa', + x'00bd1258978e205444c9aaaa82006fed' +); + +fn void boringssl_misc_regressions_3() => @run( + x'02fc', + x'0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', + x'06120c0c0c0c0c0c0c0c0c0c0c0c0c0c' +); + +fn void boringssl_misc_regressions_4() => @run( + x'7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7a7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b5c7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b6e7b007b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7a7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b5c7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b6e7b001300000000b300000000000000000000000000000000000000000000f20000000000000000000000000000000000002000efff0009000000000000000000000000100000000009000000640000000000000000000000001300000000b300000000000000000000000000000000000000000000f20000000000000000000000000000000000002000efff00090000000000000000007a000010000000000900000064000000000000000000000000000000000000000000000000fc', + x'00ff000000000000000000000000000000000000001e00000000000000007b7b', + x'33205bbf9e9f8f7212ab9e2ab9b7e4a5' +); + +fn void boringssl_misc_regressions_5() => @run( + x'89dab80b7717c1db5db437860a3f70218e93e1b8f461fb677f16f35f6f87e2a91c99bc3a47ace47640cc95c345be5ecca5a3523c35cc01893af0b64a620334270372ec12482d1b1e363561698a578b359803495bb4e2ef1930b17a5190b580f141300df30adbeca28f6427a8bc1a999fd51c554a017d095d8c3e3127daf9f595', + x'2d773be37adb1e4d683bf0075e79c4ee037918535a7f99ccb7040fb5f5f43aea', + x'c85d15ed44c378d6b00e23064c7bcd51' +); + + +// Streaming tests. +fn void boringssl_misc_regressions_1_streamed() +{ + char[poly1305::KEY_SIZE] key = x'7f1b02640000000000000000000000000000000000000000cccccccccccccccc'; + Poly1305 p @noinit; + p.init(key); + p.update(x'cccccccccccccccccccccccccccccccccccccccccccccccccc'); + p.update(x'80ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccce'); + p.update(x'cc'); + p.update(x'ccccccccccccccccccccccccccccccccccc5cccccccccccccccccccccccccccccccccccccccccce3ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccac'); + p.update(x'cccccccccccccccccccce6cccccccccc000000afccccccccccccccccccfffffff5000000000000000000000000000000000000000000000000000000ffffffe7'); + p.update(x'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); + p.update(x'719205a8521d'); + p.update(x'fc'); + test::@check(p.final() == x'8559b876eceed66eb37798c0457baff9', "Unequal Stream vs. Fixed hash."); +} + +fn void boringssl_misc_regressions_5_streamed() +{ + char[poly1305::KEY_SIZE] key = x'2d773be37adb1e4d683bf0075e79c4ee037918535a7f99ccb7040fb5f5f43aea'; + Poly1305 p @noinit; + p.init(key); + p.update(x'89dab80b7717c1db5db437860a3f70218e93e1b8'); + p.update(x'f461'); + p.update(x'fb677f16f35f6f87e2a91c99bc3a47ace47640cc95c345be5ecca5a3523c35cc01893af0b64a620334270372ec12482d1b1e363561698a578b359803495bb4e2ef1930'); + p.update(x'b17a5190b580f141300df30adbeca28f6427a8bc1a999fd51c554a017d095d8c3e3127daf9f595'); + test::@check(p.final() == x'c85d15ed44c378d6b00e23064c7bcd51', "Unequal Stream vs. Fixed hash."); +} + + +// Equivalencies. +const TEST_ITERATIONS = 2050; +fn void equivalencies() +{ + Lcg64Random rand; + random::seed(&rand, 0x1337_aaa6_f4a0_1337); + + char[poly1305::KEY_SIZE] key = x'2d773be37adb1e4d683bf0075e79c4ee037918535a7f99ccb7040fb5f5f43aea'; + char[TEST_ITERATIONS] plain = { [0..(TEST_ITERATIONS - 1)] = rand.next_byte() }; + + usz j = 0; + for (usz i = 2; i < TEST_ITERATIONS; ++i, j = 0) + { + Poly1305 p @noinit; + p.init(key); + while (j < i) + { + usz jump = max(1, min(rand.next_byte(), i - 1 - j)); + p.update(plain[j:jump]); + j += jump; + } + + char[poly1305::TAG_SIZE] streamed = p.final(); + char[poly1305::TAG_SIZE] fixed = poly1305::tag(plain[:i], key); + + test::@check(streamed == fixed, "Streamed vs. Fixed for random inputs not equal at index %d", i); + } +} +