/* $Id: t_mpTest.c $ */

/* Some tests of the BigDigits "mp" functions. Not exhaustive. */

/***** BEGIN LICENSE BLOCK *****
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright (c) 2001-16 David Ireland, D.I. Management Services Pty Limited
 * <http://www.di-mgt.com.au/bigdigits.html>. All rights reserved.
 *
 ***** END LICENSE BLOCK *****/
/*
 * Last updated:
 * $Date: 2016-03-31 09:51:00 $
 * $Revision: 2.6.1 $
 * $Author: dai $
 */


#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include <stdio.h>
#include "bigdigits.h"
#include "bigdigitsRand.h"

#define TEST_LEN 32

static int debug = 1;

DIGIT_T u[TEST_LEN];
DIGIT_T v[TEST_LEN];
DIGIT_T w[TEST_LEN]; 
DIGIT_T q[TEST_LEN];
DIGIT_T r[TEST_LEN];
DIGIT_T p1[TEST_LEN];
DIGIT_T p2[TEST_LEN];
DIGIT_T p3[TEST_LEN];
DIGIT_T g[TEST_LEN];
DIGIT_T a[TEST_LEN];
DIGIT_T n[TEST_LEN];

void ClearAll(void)
{
	mpSetZero(u, TEST_LEN);
	mpSetZero(v, TEST_LEN);
	mpSetZero(w, TEST_LEN);
	mpSetZero(q, TEST_LEN);
	mpSetZero(r, TEST_LEN);
	mpSetZero(g, TEST_LEN);
	mpSetZero(p1, TEST_LEN);
	mpSetZero(p2, TEST_LEN);
	mpSetZero(p3, TEST_LEN);
	mpSetZero(a, TEST_LEN);
	mpSetZero(n, TEST_LEN);
}

static int MakeMultiplePrime(DIGIT_T p[], size_t ndigits)
{	/* Returns a random prime number of ndigits */
	/*	WARNING: This is not cryptographically secure 
		because the random number generator isn't */
	size_t i;

	for (i = 0; i < ndigits; i++)
		p[i] = spBetterRand();

	/* Make sure the highest and low bits are set */
	p[ndigits - 1] |= HIBITMASK;
	p[0] |= 0x1;

	//printf("p="); mpPrintNL(p, ndigits);

	/* Check if prime */
	while (!mpIsPrime(p, ndigits, 10))
	{
		/* Keep adding 2 until find a prime */
		mpShortAdd(p, p, 2, ndigits);

		//printf("p="); mpPrintNL(p, ndigits);
		printf(".");

		/* Check for overflow */
		if (!(p[ndigits - 1] & HIBITMASK))
			return -1;	/* Failed to find a prime */
	}

	return 0;
}

size_t MakeMultipleRandom(DIGIT_T a[], size_t ndigits)
{
	/* Make a random number of up to ndigits digits */ 
	size_t i, n, bits;
	DIGIT_T mask;

	n = (size_t)spSimpleRand(1, ndigits);
	for (i = 0; i < n; i++)
		a[i] = spBetterRand();
	for (i = n; i < ndigits; i++)
		a[i] = 0;

	/*	Zero out a random number of bits in leading digit 
		about half the time */
	bits = (size_t)spSimpleRand(0, 2*BITS_PER_DIGIT);
	if (bits != 0 && bits < BITS_PER_DIGIT)
	{
		mask = HIBITMASK;
		for (i = 1; i < bits; i++)
		{
			mask |= (mask >> 1);
		}
		mask = ~mask;
		a[n-1] &= mask;
	}
	return n;
}

void ShowAdd(DIGIT_T w[], DIGIT_T u[], DIGIT_T v[], 
			 DIGIT_T carry, size_t ndigits)
{
	mpPrintHex("mpAdd: ", u, ndigits, " + ");
	mpPrintHex("", v, ndigits, " = ");
	mpPrintHex("", w, ndigits, ", ");
	printf("carry = %" PRIxBIGD "\n", carry);
}

void ShowMult(DIGIT_T w[], DIGIT_T u[], DIGIT_T v[], size_t ndigits)
{
	mpPrintHex("mpMultiply: ", u, ndigits, " x ");
	mpPrintHex("", v, ndigits, " = ");
	mpPrintHex("", w, ndigits*2, "\n");
}

void ShowDiv(DIGIT_T q[], DIGIT_T r[], DIGIT_T u[], DIGIT_T v[], size_t ndigits)
{
	mpPrintHex("mpDivide: ", u, ndigits, " / ");
	mpPrintHex("", v, ndigits, " = ");
	mpPrintHex("", q, ndigits, " ");
	mpPrintHex("rem ", r, ndigits, "\n");
}

int main(void)
{
	DIGIT_T carry, m;
	int jac;
	size_t len;
	size_t NDIGITS;

	/*	Force linker to include copyright notice in 
		executable object image
	*/
	copyright_notice();
		
	ClearAll();

	printf("Testing BIGDIGITS 'mp' functions.\n");

	/* Start easy: 1 + 1 = 2 */
	mpSetDigit(u, 1, TEST_LEN);	/* u = 1 */
	carry = mpAdd(w, u, u, TEST_LEN);	/* w = u + u */
	ShowAdd(w, u, u, carry, TEST_LEN);
	/* Check that w == 2 */
	assert(mpShortCmp(w, 2, TEST_LEN) == 0);

	/* ---- Add and subtract ---- */
	/* Add two random numbers w = u + v */
	MakeMultipleRandom(u, TEST_LEN-1);
	MakeMultipleRandom(v, TEST_LEN-1);
	carry = mpAdd(w, u, v, TEST_LEN);

	/* r = w - v */
	carry = mpSubtract(r, w, v, TEST_LEN);

	/* Check r == u */
	assert(mpEqual(r, u, TEST_LEN));
	printf("Add and subtract worked OK\n");
	ClearAll();

	/* ---- Multiply and divide ---- */
	/* Multiply two random numbers w = u * v */
	MakeMultipleRandom(u, TEST_LEN / 2);
	MakeMultipleRandom(v, TEST_LEN / 2);
	mpMultiply(w, u, v, TEST_LEN / 2);
	if (debug) ShowMult(w, u, v, TEST_LEN / 2);

	/* q = w / v, r = w % v */
	mpDivide(q, r, w, TEST_LEN, v, TEST_LEN / 2);
	/* Check q == u and r == 0 */
	if (debug) mpPrintHex("q=", q, TEST_LEN / 2, "\n");
	assert(mpEqual(q, u, TEST_LEN / 2));
	assert(mpIsZero(r, TEST_LEN / 2));

	ClearAll();

	mpSetDigit(a, 0, TEST_LEN);
	mpSetZero(n, TEST_LEN);
	assert(mpEqual(a, n, TEST_LEN));
	
	ClearAll();

	/* Pick two random numbers u, v */
	MakeMultipleRandom(u, TEST_LEN/2);
	MakeMultipleRandom(v, TEST_LEN/2);
	/* Divide one by the other: q = u / v, r = u % v */
	mpDivide(q, r, u, TEST_LEN/2, v, TEST_LEN/2);
	if (debug) ShowDiv(q, r, u, v, TEST_LEN/2);
	/* Check w = q * v + r == u */
	mpMultiply(g, q, v, TEST_LEN/2);
	mpAdd(w, g, r, TEST_LEN/2);
	assert(mpEqual(w, u, TEST_LEN/2));
	printf("Multiply and divide worked OK\n");

	/* ---- Greatest Common Divisor ---- */
	/* Pick 3x random primes p1, p2, p3 */
	printf("Creating 3 prime numbers (be patient):\n");
	MakeMultiplePrime(p1, TEST_LEN / 2);
	printf("(1)\n");
	MakeMultiplePrime(p2, TEST_LEN / 2);
	printf("(2)\n");
	MakeMultiplePrime(p3, TEST_LEN / 2);
	printf("(3)\n");

	/* Calculate two products from these primes */
	/* u = p1 * p2 */
	mpMultiply(u, p1, p2, TEST_LEN / 2);
	/* v = p1 * p3 */
	mpMultiply(v, p1, p3, TEST_LEN / 2);
	/* Now calculate g = gcd(u, v) */
	mpGcd(g, u, v, TEST_LEN);
	/* And check that g == p1 */
	assert(mpEqual(g, p1, TEST_LEN)); 
	printf("Greatest Common Divisor worked OK\n");

	/* ---- Modular Inverse ---- */
	/* Use previous prime as modulus, v */
	mpSetEqual(v, p1, TEST_LEN);

	/* Pick a small multiplier, m */
	m = spSimpleRand(1, MAX_DIGIT);

	/* Set u = (vm - 1) */
	mpShortMult(u, v, m, TEST_LEN);
	mpShortSub(u, u, 1, TEST_LEN);

	mpModInv(w, u, v, TEST_LEN);
	/* Check that g = (w * u) mod v = 1 */
	mpModMult(g, w, u, v, TEST_LEN);
	assert((mpShortCmp(g, 1, TEST_LEN) == 0));
	printf("Modular inverse worked OK\n");

	/* Compute some Jacobi and Legendre symbol values */
	mpSetDigit(a, 158, TEST_LEN);
	mpSetDigit(n, 235, TEST_LEN);
	jac = mpJacobi(a, n, TEST_LEN);
	//printf("Jacobi(158, 235)=%d\n", jac);
	assert(-1 == jac);
	mpSetDigit(a, 2183, TEST_LEN);
	mpSetDigit(n, 9907, TEST_LEN);
	jac = mpJacobi(a, n, TEST_LEN);
	//printf("Jacobi(2183, 9907)=%d\n", jac);
	assert(1 == jac);
	mpSetDigit(a, 1001, TEST_LEN);
	jac = mpJacobi(a, n, TEST_LEN);
	//printf("Jacobi(1001, 9907)=%d\n", jac);
	assert(-1 == jac);
	mpShortMult(a, n, 10000, TEST_LEN);
	jac = mpJacobi(a, n, TEST_LEN);
	//printf("Jacobi(10000 * 9907, 9907)=%d\n", jac);
	assert(0 == jac);
	printf("Jacobi symbol tests worked OK\n");

	/* ---- Square, square root and cube root ---- */
	printf("\nSquare roots, etc...\n");
	/* Pick a random number u */
	MakeMultipleRandom(u, TEST_LEN/2);
	mpPrintHex("u=", u, TEST_LEN/2, "\n");
	/* Compute square */
	mpSquare(v, u, TEST_LEN/2);
	mpPrintHex("u^2=", v, TEST_LEN, "\n");
	/* Compute square root */
	mpSqrt(r, v, TEST_LEN);
	mpPrintHex("sqrt(u^2)=", r, TEST_LEN, "\n");
	/* Now compute square root of v - 1 */
	mpShortSub(v, v, 1, TEST_LEN);
	mpSqrt(w, v, TEST_LEN);
	mpPrintHex("sqrt(u^2-1)=", w, TEST_LEN, "\n");
	/* This should be one less than before */
	mpSubtract(g, r, w, TEST_LEN);
	mpPrintHex("difference=", g, TEST_LEN, " (expecting 1)\n");
	assert(mpShortCmp(g, 1, TEST_LEN) == 0);

	/* Compute cube */
	ClearAll();
	/* Pick another random number u */
	len = TEST_LEN/4;
	MakeMultipleRandom(u, len);
	mpPrintHex("u=", u, len, "\n");
	/* Compute cube (NB we use LEN/4 to avoid overflow on 2nd multiplication) */
	mpSquare(w, u, len);
	len = 2 * len;
	mpMultiply(v, w, u, len);
	mpPrintHex("u^3=", v, TEST_LEN, "\n");
	/* Compute cube root */
	mpCubeRoot(r, v, TEST_LEN);
	mpPrintHex("cuberoot(u^3)=", r, TEST_LEN, "\n");
	assert(mpCompare(r, u, TEST_LEN) == 0);

	/* Now compute cube root of v - 1 */
	mpShortSub(v, v, 1, TEST_LEN);
	mpCubeRoot(w, v, TEST_LEN);
	mpPrintHex("cuberoot(u^3-1)=", w, TEST_LEN, "\n");
	/* This should be one less than before */
	mpSubtract(g, r, w, TEST_LEN);
	mpPrintHex("difference=", g, TEST_LEN, " (expecting 1)\n");
	assert(mpShortCmp(g, 1, TEST_LEN) == 0);

	
	printf("Testing signed arithmetic...\n");
	NDIGITS = 4;
	mpSetDigit(u, 2, NDIGITS);
	mpSetDigit(v, 5, NDIGITS);
	mpSubtract(w, u, v, NDIGITS);
	mpPrintDecimalSigned("signed w=", w, NDIGITS, "\n");
	mpPrintHex("unsigned w=0x", w, NDIGITS, "\n");


	/* Display version number */
	printf("\nVersion=%d\n", mpVersion());

	/* For further checks do RSA calculation - see t_mpRSA.c */
	printf("OK, successfully completed tests.\n");
	
	return 0;
}

