pragma solidity =0.8.4;

import "../EC/EllipticCurve.sol";

contract RangeProofMath is EllipticCurve {
	// elliptic curve point structure
	struct PointEC {
		uint256 x;
		uint256 y;
	}

	// set of data to verify the range proof, where
	// n is the number of bits in the secret
	// m - the number of participants to generate an aggregate range proof
	// k is the number of iterations for the compression operation
	struct SlotCount {
		uint256 n;
		uint256 m; //count Vj
		uint256 k; //count L and R
	}

	// chellenges for range proof verification
	struct SlotChallenge {
		uint256 y;
		uint256 z;
		uint256 x;
		uint256 w;
		uint256 delta;
	}

	// Range Proof structure
	struct SlotRangeProof {
		PointEC[] arrVj;
		PointEC ecA;
		PointEC ecS;
		PointEC ecT1;
		PointEC ecT2;
		uint256 utx;
		uint256 uttx;
		uint256 uee;
		PointEC[] arrLk;
		PointEC[] arrRk;
		uint256 ua;
		uint256 ub;
	}

	// Point to hash function
	function _hashPPToChallange(PointEC memory _pEC_1, PointEC memory _pEC_2)
		internal
		pure
		returns (uint256)
	{
		return uint256(sha256(abi.encode(_pEC_1.x, _pEC_1.y, _pEC_2.x, _pEC_2.y)));
	}

	// function for calculating '_negM' opposite to '_v' modulo 'nn', where
	// _negM + _v = 0 (mod nn)
	function _negMod(uint256 _v) internal pure returns (uint256 _negM) {
		_negM = mulmod(1, _v, nn);
		_negM = nn - _negM;
	}

	// function for calculating vector arrInvYn inverse to _arrMatr modulo _mod, where
	// [_arrMatr] = [arrInvYn] ^ (- 1) (mod _mod)
	function _invMatrMod(uint256[] memory _arrMatr, uint256 _mod)
		internal
		pure
		returns (uint256[] memory _arrInvMatr)
	{
		_arrInvMatr = new uint256[](_arrMatr.length);
		for (uint8 i = 0; i < _arrMatr.length; i++) {
			_arrInvMatr[i] = invMod(_arrMatr[i], _mod);
		}
	}

	// '_paramSHA' - input to start generation
	// function for generating a point on an elliptic curve
	function _genPointEC(bytes memory _paramSHA) internal pure returns (PointEC memory _pEC) {
		uint256 _x;
		uint256 _y;
		uint8 _i;
		while (_i < 256) {
			_x = addmod(uint256(0), uint256(sha256(abi.encodePacked(_i, _paramSHA))), pp);
			_y = eDeriveY(2, _x);
			if (eIsOnCurve(_x, _y) == true) {
				_pEC.x = _x;
				_pEC.y = _y;
				return _pEC;
			}
			_i++;
		}
	}

	// '_paramSHA' - input to start generation
	// '_n' - vector size
	// function for generating a vector of elliptic curve points
	function _genMatrixECPoints(bytes memory _paramSHA, uint256 _n)
		internal
		pure
		returns (PointEC[] memory _matrixECPoints)
	{
		_matrixECPoints = new PointEC[](_n);
		for (uint8 i = 0; i < _n; i++) {
			_matrixECPoints[i] = _genPointEC(abi.encodePacked(_paramSHA, i));
			if (_matrixECPoints[i].y < uint256(pp / 2)) {
				_matrixECPoints[i].y = pp - _matrixECPoints[i].y;
			}
		}
	}

	// function for checking the equality of two points of an elliptic curve
	function _equalPointEC(PointEC memory _pEC1, PointEC memory _pEC2)
		internal
		pure
		returns (bool _isEq)
	{
		_isEq = (_pEC1.x == _pEC2.x) && (_pEC1.y == _pEC2.y);
	}

	// function for calculation vector u, parameter for 'inner product' verification
	function _calc_matrix_u(
		uint256 _k,
		PointEC[] memory _arrLk,
		PointEC[] memory _arrRk
	) internal pure returns (uint256[] memory _arrUk) {
		_arrUk = new uint256[](_k);
		for (uint8 i = 0; i < _k; i++) {
			_arrUk[i] = mulmod(1, _hashPPToChallange(_arrLk[i], _arrRk[i]), nn);
		}
	}

	// function for calculation delta, parameter for 'polinom tx' verification
	function _calc_delta(SlotCount memory _count, SlotChallenge memory _challenge)
		internal
		pure
		returns (uint256 _delta)
	{
		uint256 _n = _count.n;
		uint256 _m = _count.m;

		uint256 _y = _challenge.y;
		uint256 _z = _challenge.z;

		uint256 _v1 = 1; //y^n
		uint256 _v2 = 1; //2^n

		uint256 _spm1 = 1; // <1, y^n>
		uint256 _spm2 = 1; // <1, 2^n>

		uint256 _zz = mulmod(_z, _z, nn); //z^2 mod k

		// _zz = addmod(_negMod(_zz), _z, nn); // z-z^2

		// (z-z^2) * <1, y ^ n*m> === (z-z^2)* m * <1, y^n>
		for (uint8 i = 1; i < _n; i++) {
			_v1 = mulmod(_v1, _y, nn);
			_spm1 = addmod(_spm1, _v1, nn); // <1, y^n>

			_v2 = mulmod(_v2, 2, nn);
			_spm2 = addmod(_spm2, _v2, nn); // <1, 2^n>
		}
		_delta = mulmod(mulmod(_spm1, _m, nn), addmod(_negMod(_zz), _z, nn), nn); // (z-z^2)* m * <1, y^n>

		// -z^3*summ(z^j)*<1, 2^nm> == -summ(z^j) * z^3 * m * <1, 2^n>

		_spm1 = 1;
		for (uint256 j = 1; j < _m; j++) {
			_v1 = mulmod(_v1, _z, nn); //z^j
			_spm1 = addmod(_spm1, _v1, nn); // summ(z^j)
		}
		_spm1 = mulmod(mulmod(_zz, _z, nn), _spm1, nn);
		_spm1 = mulmod(_spm1, mulmod(_spm2, _m, nn), nn);

		_delta = addmod(_delta, _negMod(_spm1), nn);
	}

	// function for calculation challange y
	function _calcChallY(PointEC memory _ecA, PointEC memory _ecS)
		internal
		pure
		returns (uint256 _chall)
	{
		require(eIsOnCurve(_ecA.x, _ecA.y), "Argument '_ecA' is not ec point");
		require(eIsOnCurve(_ecS.x, _ecS.y), "Argument '_ecS' is not ec point");
		_chall = addmod(0, _hashPPToChallange(_ecA, _ecS), nn);
	}

	// function for calculation challange z
	function _calcChallZ(
		PointEC memory _ecA,
		PointEC memory _ecS,
		uint256 _y
	) internal pure returns (uint256 _chall) {
		_chall = addmod(0, uint256(sha256(abi.encode(_ecA.x, _ecA.y, _ecS.x, _ecS.y, _y))), nn);
	}

	// function for calculation challange x
	function _calcChallX(PointEC memory _ecT1, PointEC memory _ecT2)
		internal
		pure
		returns (uint256 _chall)
	{
		require(eIsOnCurve(_ecT1.x, _ecT1.y), "Argument '_ecT1' is not ec point");
		require(eIsOnCurve(_ecT2.x, _ecT2.y), "Argument '_ecT2' is not ec point");
		_chall = addmod(0, _hashPPToChallange(_ecT1, _ecT2), nn);
	}

	// function for calculation challange w
	function _calcChallW(
		uint256 _tx,
		uint256 _ttx,
		uint256 _ee
	) internal pure returns (uint256 _chall) {
		_chall = addmod(0, uint256(sha256(abi.encode(_tx, _ttx, _ee))), nn);
	}

	// function for calculation number of parameters for the SlotCount structure
	function _calc_counters(
		PointEC[] memory _Vj,
		PointEC[] memory _Lk,
		PointEC[] memory _Rk
	) internal pure returns (SlotCount memory _count) {
		//m- number of participants
		_count.m = _Vj.length;
		require(_count.m > 0, "Array Vj is empty");

		_count.k = _Lk.length;
		require(_count.k > 0, "Arrays Lk and Rk with an error");
		require(_count.k == _Rk.length, "Rk and Lk have different sizes");

		//n- length 'v' in bits or k = log2(n) => n = 2^k
		_count.n = 2**_count.k;
	}

	// function for calculation vector with size _n from the powers of _v modulo _mod,
	// where _v ^ i (mod _mod), i = {0, 1, _n-1}
	function _gen_matrix_product(
		uint8 _n,
		uint256 _v,
		uint256 _mod
	) internal pure returns (uint256[] memory arrProd) {
		arrProd = new uint256[](_n);
		arrProd[0] = 1;
		for (uint8 i = 1; i < _n; i++) {
			arrProd[i] = mulmod(arrProd[i - 1], _v, _mod);
		}
	}
}