// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni

/*
 * Derivative work of MuseAir v2 in the "long" (len > 32), "b128", and "bfast" configurations.
 *
 * Note that this specific MuseAir configuration didn't change from v1 to v2.
 *
 * This implementation also accepts a length <= 32, unlike the original MuseAir,
 * which switches to the short version for this length.
 *
 * https://github.com/eternal-io/museair
 */

/*
 * MuseAir v2
 * By K--Aethiax
 *
 * Released into the public domain under the CC0 1.0 license. To view a copy
 * of this license, visit: https://creativecommons.org/publicdomain/zero/1.0/
 */

/*
 * Analysis of the BFast variant and blinding multiplication
 *
 * Many fast non-cryptographic hashes (wyhash, rapidhash) use "folding
 * multiplication": fold_mul(state ^ input_a, input_b ^ SECRET), where both
 * the state and a public constant are XORed directly with input words.
 * This is vulnerable to "blinding multiplication": if any operand becomes
 * zero, the product is zero, and all accumulated state in that lane is lost.
 * An attacker who knows the SECRET constants can trivially craft an input
 * word that zeros out an operand, with probability 2^-64 per attempt (or
 * 2^-63 considering either operand).
 *
 * MuseAir BFast avoids this by using a circular accumulator with six
 * 64-bit state words. The core loop processes 12 input words per iteration:
 *
 *   state[0] ^= input[0];
 *   state[1] ^= input[1];
 *   (lo0, hi0) = wide_mul(state[0], state[1]);
 *   state[0] = lo5 ^ hi0;   // lo5 is from the PREVIOUS step
 *   ...repeat circularly for state[1] through state[5]...
 *
 * Inputs are mixed into the state (not XORed with public constants), so
 * an attacker must predict the evolving state to craft a blinding input.
 *
 * Even if a blinding multiplication occurs at the current step (one
 * operand is zero, making lo0 = hi0 = 0), the state update becomes:
 *
 *   state[0] = lo5 ^ 0 = lo5
 *
 * The state is NOT zeroed out: it inherits lo5 from the previous step,
 * which still carries entropy from all earlier inputs. The state only
 * zeros out if lo5 also happens to be zero.
 *
 * The probability of lo5 being zero equals the probability that the
 * low 64 bits of a 64x64->128 multiplication are zero. This happens
 * when the total number of trailing zero bits in both operands is >= 64.
 * For uniformly random operands, the exact count of such pairs in
 * {0..2^n-1}^2 is (n+2) * 2^(n-1). For n = 64 this gives 33 * 2^64
 * pairs out of 2^128 total, so:
 *
 *   P(lo64 = 0) = 33 * 2^-64 ~ 2^-59
 *
 * The overall probability of a catastrophic state zero-out is therefore:
 *
 *   P(blinding at current step) * P(lo5 = 0)
 *   ~ 2^-63 * 33 * 2^-64
 *   = 33 * 2^-127
 *   ~ 2^-122
 *
 * This is astronomically unlikely and completely irrelevant for detecting
 * silent data corruption, where inputs are real file data (not adversarial).
 *
 * The "Standard" variant of MuseAir replaces the assignment (=) with a
 * subtraction (-=) in the state update, making blinding multiplication
 * impossible at a performance cost.
 *
 * Why BFast was chosen over Standard for SnapRAID
 *
 * SnapRAID uses the hash exclusively to detect silent data corruption
 * (bit rot, firmware bugs, media degradation). The inputs are always
 * real file data read from disk, never adversarially crafted content.
 * In this context:
 *
 * - The 2^-122 blinding probability is irrelevant. Random bit errors
 *   produce uniformly random changes that cannot trigger a blinding
 *   multiplication. Only a deliberate attacker controlling the exact
 *   file content could attempt to exploit it.
 *
 * - Both variants pass all 250 SMHasher3 quality tests and provide
 *   identical error detection for random corruption.
 *
 * - BFast is measurably faster, and its structure is more amenable to
 *   loop unrolling.
 *
 * The Standard variant's only advantage, full immunity to adversarial
 * blinding, provides no benefit when the threat model is physical
 * data corruption rather than a malicious actor.
 *
 * Short-input extension (len <= 32)
 *
 * The original MuseAir algorithm uses a completely separate "Short"
 * hash function for inputs of 32 bytes or less. This implementation
 * instead zero-pads short inputs to 32 bytes and processes them
 * through the same "Long" code path:
 *
 *   if (len < 32) { memset(buf, 0, 32); memcpy(buf, p, len); }
 *
 * The motivation is to avoid maintaining two distinct hash algorithms.
 * Having a single code path reduces complexity and the potential for
 * algorithm defects. Hash functions generally provide a specialized
 * short version to improve performance for small keys, but this is
 * irrelevant for SnapRAID, which typically hashes 256 KB blocks.
 * Short inputs only occur as an exception.
 *
 * This extension is safe because the original length is mixed into
 * the finalization, independent of the zero-padded buffer:
 *
 *   rot = len & 63;
 *   i = rotl64(i, rot);   // left-rotate by original length
 *   j = rotr64(j, rot);   // right-rotate by original length
 *   k = k - len;          // subtract original length
 *
 * So even though zero-padded buffers for, say, len=1 and len=2 are
 * identical, the different rotation amounts and subtraction values
 * produce completely different hashes after the final multiplications.
 */

#define u64x(N) (N * 8)

/* AiryAi(0) fractional part calculated by Y-Cruncher */
static const uint64_t MUSEAIR_CONSTANT[13] = {
	0x5ae31e589c56e17aULL,
	0x96d7bb04e64f6da9ULL,
	0x7ab1006b26f9eb64ULL,
	0x21233394220b8457ULL,
	0x047cb9557c9f3b43ULL,
	0xd24f2590c0bcee28ULL,
	0x33ea8f71bb6016d8ULL,
	0xb5d2697595d0a01fULL,
	0x9bb30a32f00e2b4fULL,
	0x4acea09317a429d1ULL,
	0xc2b2435dfdd545c6ULL,
	0xfda811a785572a42ULL,
	0xe5f50676bf67137bULL
};

#define mult64_128(l, h, a, b) \
	do { \
		l = a; \
		h = b; \
		util_mum(&l, &h); \
	} while (0)

#define likely(a) tommy_likely(a)
#define unlikely(a) tommy_unlikely(a)

static const uint64_t MASK_I = 01555555555555555555555ULL;
static const uint64_t MASK_J = 01333333333333333333333ULL;
static const uint64_t MASK_K = 00666666666666666666666ULL;

static void MuseAirLoong(const void* bytes, size_t len, const uint8_t* seed, uint8_t* out)
{
	const uint8_t* p = bytes;
	size_t q = len;
	uint8_t buf[32];

	if (unlikely(q < 32)) {
		memset(buf, 0, 32);
		if (q > 0)
			memcpy(buf, p, q);
		p = buf;
		q = 32;
	}

	uint64_t i, j, k;
	uint64_t lo0, lo1, lo2, lo3, lo4, lo5 = MUSEAIR_CONSTANT[6];
	uint64_t hi0, hi1, hi2, hi3, hi4, hi5 = MUSEAIR_CONSTANT[6];
	uint64_t state[6] = { MUSEAIR_CONSTANT[0], MUSEAIR_CONSTANT[1], MUSEAIR_CONSTANT[2], MUSEAIR_CONSTANT[3], MUSEAIR_CONSTANT[4], MUSEAIR_CONSTANT[5] };

	uint64_t seed_a = util_read64(seed);
	uint64_t seed_b = util_read64(seed + 8);

	state[0] ^= seed_a & MASK_I;
	state[1] ^= seed_b & MASK_J;
	state[2] ^= seed_a & MASK_K;
	state[3] ^= seed_b & MASK_I;
	state[4] ^= seed_a & MASK_J;
	state[5] ^= seed_b & MASK_K;

	if (unlikely(q > u64x(12))) {
		do {
			state[0] ^= util_read64(p + u64x(0));
			state[1] ^= util_read64(p + u64x(1));
			mult64_128(lo0, hi0, state[0], state[1]);
			state[0] = lo5 ^ hi0;

			state[1] ^= util_read64(p + u64x(2));
			state[2] ^= util_read64(p + u64x(3));
			mult64_128(lo1, hi1, state[1], state[2]);
			state[1] = lo0 ^ hi1;

			state[2] ^= util_read64(p + u64x(4));
			state[3] ^= util_read64(p + u64x(5));
			mult64_128(lo2, hi2, state[2], state[3]);
			state[2] = lo1 ^ hi2;

			state[3] ^= util_read64(p + u64x(6));
			state[4] ^= util_read64(p + u64x(7));
			mult64_128(lo3, hi3, state[3], state[4]);
			state[3] = lo2 ^ hi3;

			state[4] ^= util_read64(p + u64x(8));
			state[5] ^= util_read64(p + u64x(9));
			mult64_128(lo4, hi4, state[4], state[5]);
			state[4] = lo3 ^ hi4;

			state[5] ^= util_read64(p + u64x(10));
			state[0] ^= util_read64(p + u64x(11));
			mult64_128(lo5, hi5, state[5], state[0]);
			state[5] = lo4 ^ hi5;

			p += u64x(12);
			q -= u64x(12);

		} while (likely(q > u64x(12)));

		state[0] ^= lo5;
	}

	lo0 = 0, lo1 = 0, lo2 = 0, lo3 = 0;
	hi0 = state[1];
	hi1 = state[2];
	hi2 = state[3];
	hi3 = state[4];

	if (q > u64x(4)) {
		state[0] ^= util_read64(p + u64x(0));
		state[1] ^= util_read64(p + u64x(1));
		mult64_128(lo0, hi0, state[0], state[1]);

		if (q > u64x(6)) {
			state[1] ^= util_read64(p + u64x(2));
			state[2] ^= util_read64(p + u64x(3));
			mult64_128(lo1, hi1, state[1], state[2]);

			if (q > u64x(8)) {
				state[2] ^= util_read64(p + u64x(4));
				state[3] ^= util_read64(p + u64x(5));
				mult64_128(lo2, hi2, state[2], state[3]);

				if (q > u64x(10)) {
					state[3] ^= util_read64(p + u64x(6));
					state[4] ^= util_read64(p + u64x(7));
					mult64_128(lo3, hi3, state[3], state[4]);
				}
			}
		}
	}

	state[4] ^= util_read64(p + q - u64x(4));
	state[5] ^= util_read64(p + q - u64x(3));
	mult64_128(lo4, hi4, state[4], state[5]);

	state[5] ^= util_read64(p + q - u64x(2));
	state[0] ^= util_read64(p + q - u64x(1));
	mult64_128(lo5, hi5, state[5], state[0]);

	i = (state[0] - state[1]) ^ MUSEAIR_CONSTANT[7];
	j = (state[2] - state[3]) ^ MUSEAIR_CONSTANT[8];
	k = (state[4] - state[5]) ^ MUSEAIR_CONSTANT[9];

	int rot = len & 63;
	i = util_rotl64(i, rot);
	j = util_rotr64(j, rot);
	k = k - len;

	i = i - (lo3 ^ hi3) - (lo4 ^ hi4);
	j = j - (lo5 ^ hi5) - (lo0 ^ hi0);
	k = k - (lo1 ^ hi1) - (lo2 ^ hi2);

	mult64_128(lo0, hi0, i, j);
	mult64_128(lo1, hi1, j, k);
	mult64_128(lo2, hi2, k, i);

	i = lo2 ^ hi0;
	j = lo0 ^ hi1;
	k = lo1 ^ hi2;

	mult64_128(lo3, hi3, i, MUSEAIR_CONSTANT[10]);
	mult64_128(lo4, hi4, j, MUSEAIR_CONSTANT[11]);
	mult64_128(lo5, hi5, k, MUSEAIR_CONSTANT[12]);

	util_write64(out, lo3 ^ hi4 ^ lo5);
	util_write64(out + 8, hi3 ^ lo4 ^ hi5);
}

