mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
172 lines
5.0 KiB
Plaintext
172 lines
5.0 KiB
Plaintext
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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 = mem::load((uint128*)&key[ 0], 1) & 0x0ffffffc_0ffffffc_0ffffffc_0fffffff, // clamped per spec
|
|
.nonce = mem::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 = mem::load((ulong*)&input[0], 1);
|
|
ulong i1 = mem::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);
|
|
}
|
|
}
|