/*
  This file is for secret-key generation
*/
// 20221230 djb: add linker lines

// linker define genpoly_gen
// linker use gf_mul gf_inv
// linker use vec128_mul_GF
// linker use vec128_mul_asm
// linker use transpose_64x64_asm

#include "sk_gen.h"

#include "randombytes.h"
#include "controlbits.h"
#include "transpose.h"
#include "params.h"
#include "vec128.h"
#include "util.h"
#include "gf.h"
#include "crypto_declassify.h"
#include "crypto_uint16.h"

static inline crypto_uint16 gf_is_zero_declassify(gf t)
{
  crypto_uint16 mask = crypto_uint16_zero_mask(t);
  crypto_declassify(&mask,sizeof mask);
  return mask;
}

/* input: v, a list of GF(2^m) elements in bitsliced form */
/* input: idx, an index */
/* return: the idx-th element in v */
static inline gf extract_gf(uint64_t v[GFBITS][2], int idx)
{
	int i;
	gf ret;	

	ret = 0;
	for (i = GFBITS-1; i >= 0; i--)
	{
		ret <<= 1;
		ret |= (v[i][idx/64] >> (idx&63)) & 1;
	}

	return ret;
}

/* same as extract_gf but reduces return value to 1 bit */
static inline uint64_t extract_bit(uint64_t v[GFBITS][2], int idx)
{
	int i;
	uint64_t ret;	

	ret = 0;
	for (i = GFBITS-1; i >= 0; i--)
		ret |= v[i][idx/64];

	return (ret >> (idx&63)) & 1;
}

static void transpose_128x128(uint64_t (*in)[2])
{
	int i, j, k;
	uint64_t m[2][2][64], t;

	for (i = 0; i < 2; i++)
	for (j = 0; j < 2; j++)
	for (k = 0; k < 64; k++)
		m[i][j][k] = in[64*i + k][j];

	for (k = 0; k < 64; k++)
	{
		t = m[0][1][k]; 
		    m[0][1][k] = m[1][0][k]; 
		                 m[1][0][k] = t;
	}

	for (i = 0; i < 2; i++)
	for (j = 0; j < 2; j++)
		transpose_64x64(m[i][j]);

	for (i = 0; i < 2; i++)
	for (j = 0; j < 2; j++)
	for (k = 0; k < 64; k++)
		in[64*i + k][j] = m[i][j][k];
}

/* input: f, element in GF((2^m)^t) */
/* output: out, minimal polynomial of f */
/* return: 0 for success and -1 for failure */
int genpoly_gen(gf *out, gf *f)
{
	int i, j, k;

	gf t, inv;

	uint64_t m;
	vec128 mask;

	union
	{
		uint64_t d[ GFBITS ][ 128 ][2];
		vec128   v[ GFBITS ][ 128 ];
	} buf;

	union
	{
		uint64_t d[GFBITS][2];
		vec128   v[GFBITS];
	} v;

	union 
	{
		uint64_t d[SYS_T][GFBITS][2];
		vec128   v[SYS_T][GFBITS];
	} mat;

	// fill matrix

	buf.v[0][0] = vec128_set2x(1, 0);
	for (i = 1; i < GFBITS; i++)
		buf.v[i][0] = vec128_setzero();

	for (j = 0; j < GFBITS; j++)
	{
		v.v[j] = vec128_setzero();

		for (i = SYS_T-1; i >= 0; i--)
		{
			v.d[j][1] <<= 1;
			v.d[j][1] |= v.d[j][0] >> 63;
			v.d[j][0] <<= 1;
			v.d[j][0] |= (f[i] >> j) & 1;
		}
	}
	
	for (i = 0; i < GFBITS; i++)
		buf.v[i][1] = v.v[i];		

	for (k = 2; k <= SYS_T; k++)
	{
		vec128_mul_GF(v.v, v.v, f);

		if (k < SYS_T)
		{
			for (i = 0; i < GFBITS; i++)
				buf.v[i][k] = v.v[i];		
		}
		else
		{
			for (i = 0; i < SYS_T; i++)
				out[i] = extract_gf(v.d, i);
		}
	}

	for (i = 0; i < GFBITS; i++)
		transpose_128x128(buf.d[i]);
	
	for (j = 0; j < SYS_T; j++)
	for (i = 0; i < GFBITS; i++)
		mat.v[j][i] = buf.v[i][j];

	// gaussian

	for (i = 0; i < SYS_T; i++)
	{
		for (j = i+1; j < SYS_T; j++)
		{
			m = extract_bit(mat.d[i], i);
			m -= 1;
			mask = vec128_set2x(m, m);

			for (k = 0; k < GFBITS; k++)
				mat.v[i][k] ^= mat.v[j][k] & mask;

			out[i] ^= out[j] & m;
		}

		//

		t = extract_gf(mat.d[i], i);

		if (gf_is_zero_declassify(t)) return -1; // return if not systematic

		//

		inv = gf_inv(t);
		vec128_mul_gf(mat.v[i], mat.v[i], inv);

		out[i] = gf_mul(out[i], inv);

		for (j = 0; j < SYS_T; j++)
		{
			if (j != i)
			{
				t = extract_gf(mat.d[j], i);

				vec128_mul_gf(v.v, mat.v[i], t);
				vec128_add(mat.v[j], mat.v[j], v.v);

				out[j] ^= gf_mul(out[i], t);
			}
		}
	}	

	return 0;
}

