/*
 * sip_kat.c -- live correctness verification of sys/crypto/siphash/siphash.c
 * (same-source userland-object pattern).
 *
 * Ground truth:
 *   1. The 64 official reference vectors (veorq/SipHash repo; also in-tree
 *      sys/crypto/siphash/siphash_test.c) for SipHash-2-4.
 *   2. An independently written from-the-paper reference implementation,
 *      cross-matched for SipHash-2-4 AND SipHash-1-3 (the wg_cookie variant)
 *      over all lengths 0..255 with multiple keys.
 *   3. Chunked-update equivalence (exercises SipBuf partial-word paths).
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include <crypto/siphash/siphash.h>

static int fails = 0;
static long checks = 0;

/* ---- 64 official vectors, key = 000102..0f, input = first i bytes of
 *      the 00..ff sequence, SipHash-2-4, little-endian output ---- */
static const uint8_t vectors[64][8] = {
  {0x31,0x0e,0x0e,0xdd,0x47,0xdb,0x6f,0x72},
  {0xfd,0x67,0xdc,0x93,0xc5,0x39,0xf8,0x74},
  {0x5a,0x4f,0xa9,0xd9,0x09,0x80,0x6c,0x0d},
  {0x2d,0x7e,0xfb,0xd7,0x96,0x66,0x67,0x85},
  {0xb7,0x87,0x71,0x27,0xe0,0x94,0x27,0xcf},
  {0x8d,0xa6,0x99,0xcd,0x64,0x55,0x76,0x18},
  {0xce,0xe3,0xfe,0x58,0x6e,0x46,0xc9,0xcb},
  {0x37,0xd1,0x01,0x8b,0xf5,0x00,0x02,0xab},
  {0x62,0x24,0x93,0x9a,0x79,0xf5,0xf5,0x93},
  {0xb0,0xe4,0xa9,0x0b,0xdf,0x82,0x00,0x9e},
  {0xf3,0xb9,0xdd,0x94,0xc5,0xbb,0x5d,0x7a},
  {0xa7,0xad,0x6b,0x22,0x46,0x2f,0xb3,0xf4},
  {0xfb,0xe5,0x0e,0x86,0xbc,0x8f,0x1e,0x75},
  {0x90,0x3d,0x84,0xc0,0x27,0x56,0xea,0x14},
  {0xee,0xf2,0x7a,0x8e,0x90,0xca,0x23,0xf7},
  {0xe5,0x45,0xbe,0x49,0x61,0xca,0x29,0xa1},
  {0xdb,0x9b,0xc2,0x57,0x7f,0xcc,0x2a,0x3f},
  {0x94,0x47,0xbe,0x2c,0xf5,0xe9,0x9a,0x69},
  {0x9c,0xd3,0x8d,0x96,0xf0,0xb3,0xc1,0x4b},
  {0xbd,0x61,0x79,0xa7,0x1d,0xc9,0x6d,0xbb},
  {0x98,0xee,0xa2,0x1a,0xf2,0x5c,0xd6,0xbe},
  {0xc7,0x67,0x3b,0x2e,0xb0,0xcb,0xf2,0xd0},
  {0x88,0x3e,0xa3,0xe3,0x95,0x67,0x53,0x93},
  {0xc8,0xce,0x5c,0xcd,0x8c,0x03,0x0c,0xa8},
  {0x94,0xaf,0x49,0xf6,0xc6,0x50,0xad,0xb8},
  {0xea,0xb8,0x85,0x8a,0xde,0x92,0xe1,0xbc},
  {0xf3,0x15,0xbb,0x5b,0xb8,0x35,0xd8,0x17},
  {0xad,0xcf,0x6b,0x07,0x63,0x61,0x2e,0x2f},
  {0xa5,0xc9,0x1d,0xa7,0xac,0xaa,0x4d,0xde},
  {0x71,0x65,0x95,0x87,0x66,0x50,0xa2,0xa6},
  {0x28,0xef,0x49,0x5c,0x53,0xa3,0x87,0xad},
  {0x42,0xc3,0x41,0xd8,0xfa,0x92,0xd8,0x32},
  {0xce,0x7c,0xf2,0x72,0x2f,0x51,0x27,0x71},
  {0xe3,0x78,0x59,0xf9,0x46,0x23,0xf3,0xa7},
  {0x38,0x12,0x05,0xbb,0x1a,0xb0,0xe0,0x12},
  {0xae,0x97,0xa1,0x0f,0xd4,0x34,0xe0,0x15},
  {0xb4,0xa3,0x15,0x08,0xbe,0xff,0x4d,0x31},
  {0x81,0x39,0x62,0x29,0xf0,0x90,0x79,0x02},
  {0x4d,0x0c,0xf4,0x9e,0xe5,0xd4,0xdc,0xca},
  {0x5c,0x73,0x33,0x6a,0x76,0xd8,0xbf,0x9a},
  {0xd0,0xa7,0x04,0x53,0x6b,0xa9,0x3e,0x0e},
  {0x92,0x59,0x58,0xfc,0xd6,0x42,0x0c,0xad},
  {0xa9,0x15,0xc2,0x9b,0xc8,0x06,0x73,0x18},
  {0x95,0x2b,0x79,0xf3,0xbc,0x0a,0xa6,0xd4},
  {0xf2,0x1d,0xf2,0xe4,0x1d,0x45,0x35,0xf9},
  {0x87,0x57,0x75,0x19,0x04,0x8f,0x53,0xa9},
  {0x10,0xa5,0x6c,0xf5,0xdf,0xcd,0x9a,0xdb},
  {0xeb,0x75,0x09,0x5c,0xcd,0x98,0x6c,0xd0},
  {0x51,0xa9,0xcb,0x9e,0xcb,0xa3,0x12,0xe6},
  {0x96,0xaf,0xad,0xfc,0x2c,0xe6,0x66,0xc7},
  {0x72,0xfe,0x52,0x97,0x5a,0x43,0x64,0xee},
  {0x5a,0x16,0x45,0xb2,0x76,0xd5,0x92,0xa1},
  {0xb2,0x74,0xcb,0x8e,0xbf,0x87,0x87,0x0a},
  {0x6f,0x9b,0xb4,0x20,0x3d,0xe7,0xb3,0x81},
  {0xea,0xec,0xb2,0xa3,0x0b,0x22,0xa8,0x7f},
  {0x99,0x24,0xa4,0x3c,0xc1,0x31,0x57,0x24},
  {0xbd,0x83,0x8d,0x3a,0xaf,0xbf,0x8d,0xb7},
  {0x0b,0x1a,0x2a,0x32,0x65,0xd5,0x1a,0xea},
  {0x13,0x50,0x79,0xa3,0x23,0x1c,0xe6,0x60},
  {0x93,0x2b,0x28,0x46,0xe4,0xd7,0x06,0x66},
  {0xe1,0x91,0x5f,0x5c,0xb1,0xec,0xa4,0x6c},
  {0xf3,0x25,0x96,0x5c,0xa1,0x6d,0x62,0x9f},
  {0x57,0x5f,0xf2,0x8e,0x60,0x38,0x1b,0xe5},
  {0x72,0x45,0x06,0xeb,0x4c,0x32,0x8a,0x95}
};

/* ---- independent reference implementation, written from the paper ---- */
#define RROT(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
static void
ref_round(uint64_t v[4])
{
	v[0] += v[1]; v[2] += v[3];
	v[1] = RROT(v[1], 13); v[3] = RROT(v[3], 16);
	v[1] ^= v[0]; v[3] ^= v[2];
	v[0] = RROT(v[0], 32);
	v[2] += v[1]; v[0] += v[3];
	v[1] = RROT(v[1], 17); v[3] = RROT(v[3], 21);
	v[1] ^= v[2]; v[3] ^= v[0];
	v[2] = RROT(v[2], 32);
}

static uint64_t
le64(const uint8_t *p)
{
	uint64_t r = 0;
	int i;
	for (i = 7; i >= 0; i--)
		r = (r << 8) | p[i];
	return r;
}

static uint64_t
siphash_ref(int c, int d, const uint8_t key[16], const uint8_t *in, size_t len)
{
	uint64_t v[4], b, m;
	size_t i;
	int r;

	v[0] = 0x736f6d6570736575ULL ^ le64(key);
	v[1] = 0x646f72616e646f6dULL ^ le64(key + 8);
	v[2] = 0x6c7967656e657261ULL ^ le64(key);
	v[3] = 0x7465646279746573ULL ^ le64(key + 8);

	b = (uint64_t)len << 56;
	for (i = 0; i < (len & ~(size_t)7); i += 8) {
		m = le64(in + i);
		v[3] ^= m;
		for (r = 0; r < c; r++) ref_round(v);
		v[0] ^= m;
	}
	for (r = (int)(len - i) - 1; r >= 0; r--)	/* last partial word */
		b |= (uint64_t)in[i + r] << (8 * r);	/* byte j sits at 8*j (LE) */
	v[3] ^= b;
	for (r = 0; r < c; r++) ref_round(v);
	v[0] ^= b;
	v[2] ^= 0xff;
	for (r = 0; r < d; r++) ref_round(v);
	return v[0] ^ v[1] ^ v[2] ^ v[3];
}

static unsigned long long rs = 0x13198A2E03707344ULL;
static unsigned long long
xr(void)
{
	rs = rs * 6364136223846793005ULL + 1442695040888963407ULL;
	return rs >> 17;
}

static void
chk(const char *label, uint64_t got, uint64_t want)
{
	checks++;
	if (got != want) {
		fails++;
		printf("FAIL %-34s got %016llx want %016llx\n", label,
		    (unsigned long long)got, (unsigned long long)want);
	}
}

int
main(void)
{
	uint8_t in[256], k[16], out[8];
	SIPHASH_CTX ctx;
	int i, j;

	for (i = 0; i < 256; i++)
		in[i] = (uint8_t)i;
	for (i = 0; i < 16; i++)
		k[i] = (uint8_t)i;

	printf("== 64 official reference vectors (SipHash-2-4) ==\n");
	for (i = 0; i < 64; i++) {
		uint64_t r = SipHashX(&ctx, 2, 4, k, in, i);
		uint64_t want = 0;
		int b;
		for (b = 7; b >= 0; b--)
			want = (want << 8) | vectors[i][b];	/* LE */
		chk("official-2-4", r, want);
	}
	printf("   (all 64 checked: %s)\n", fails ? "SEE FAILURES" : "match");

	printf("== kernel vs from-paper reference, SipHash-2-4 & 1-3 ==\n");
	/* lengths 0..255, three keys (sequential, two pseudo-random) */
	for (i = 0; i < 256; i++) {
		uint64_t k24 = SipHashX(&ctx, 2, 4, k, in, i);
		uint64_t k13 = SipHashX(&ctx, 1, 3, k, in, i);
		chk("ref-2-4", k24, siphash_ref(2, 4, k, in, i));
		chk("ref-1-3", k13, siphash_ref(1, 3, k, in, i));
	}
	for (j = 0; j < 2; j++) {
		for (i = 0; i < 16; i++)
			k[i] = (uint8_t)(xr() >> 11);
		for (i = 0; i < 256; i++) {
			chk("ref-2-4-rnd", SipHashX(&ctx, 2, 4, k, in, i),
			    siphash_ref(2, 4, k, in, i));
			chk("ref-1-3-rnd", SipHashX(&ctx, 1, 3, k, in, i),
			    siphash_ref(1, 3, k, in, i));
		}
	}
	printf("   (512+512 cross-checks done)\n");

	printf("== chunked-update equivalence (partial-word buffer paths) ==\n");
	for (i = 0; i < 256; i++) {
		size_t split = (size_t)(xr() % (i + 1));
		uint64_t one, two;

		for (j = 0; j < 16; j++)
			k[j] = (uint8_t)(xr() >> 11);
		one = SipHashX(&ctx, 2, 4, k, in, i);
		SipHash_InitX(&ctx, 2, 4);
		SipHash_SetKey(&ctx, k);
		if (split)
			SipHash_Update(&ctx, in, split);
		SipHash_Update(&ctx, in + split, i - split);
		two = SipHash_End(&ctx);
		checks++;
		if (one != two) {
			fails++;
			printf("FAIL chunked len=%d split=%zu\n", i, split);
		}
		/* 3-way split too */
		if (i >= 2) {
			size_t s1 = (size_t)(xr() % i), s2;
			s2 = (size_t)(xr() % (i - s1));
			SipHash_InitX(&ctx, 1, 3);
			SipHash_SetKey(&ctx, k);
			SipHash_Update(&ctx, in, s1);
			SipHash_Update(&ctx, in + s1, s2);
			SipHash_Update(&ctx, in + s1 + s2, i - s1 - s2);
			two = SipHash_End(&ctx);
			checks++;
			if (SipHashX(&ctx, 1, 3, k, in, i) != two) {
				fails++;
				printf("FAIL chunked3 len=%d\n", i);
			}
		}
	}
	printf("   (chunked equivalence done)\n");

	printf("== SipHash_Final byte encoding ==\n");
	{
		uint64_t r = SipHashX(&ctx, 2, 4, k, in, 5);
		SipHash_InitX(&ctx, 2, 4);
		SipHash_SetKey(&ctx, k);
		SipHash_Update(&ctx, in, 5);
		SipHash_Final(out, &ctx);
		chk("final-le", le64(out), r);
	}

	printf("%s: %ld checks, %d failures\n",
	    fails ? "RESULT: FAIL" : "RESULT: PASS", checks, fails);
	return fails ? 1 : 0;
}
