/*
*******************************************************************************
\file ecp.c
\brief Elliptic curves over prime fields
\project bee2 [cryptographic library]
\created 2012.06.26
\version 2025.09.29
\copyright The Bee2 authors
\license Licensed under the Apache License, Version 2.0 (see LICENSE.txt).
*******************************************************************************
*/

#include "bee2/core/mem.h"
#include "bee2/core/util.h"
#include "bee2/math/ecp.h"
#include "bee2/math/gfp.h"
#include "bee2/math/pri.h"
#include "bee2/math/ww.h"
#include "bee2/math/zm.h"
#include "bee2/math/zz.h"

/*
*******************************************************************************
Общие положения

Ссылки на все реализованные алгоритмы имеются на сайте
http://www.hyperelliptic.org/efd. Там же можно найти соглашения по
обозначению сложности алгоритмов. В этих обозначениях фигурируют
следующие формальные выражения:
	add -- сложение или вычитание \mod p,
	c -- умножение на малую константу c (2,3,...) \mod p,
	half -- умножение на 2^{-1} \mod p,
	*A -- умножение на коэффициент A \mod p,
	S -- возведение в квадрат \mod p,
	M -- умножение \mod p,
	D -- деление \mod p.

При общей оценке сложности считается, что 1D = 100M и 1S = 1M.
Аддитивные операции игнорируются.

Используются обозначения:
	A <- A + A -- сложение аффинных точек,
	A <- 2A -- удвоение аффинной точки;
	P <- P + P -- сложение проективных точек;
	P <- P + A -- добавление к проективной точке аффинной;
	P <- 2P -- удвоение проективной точки;
	P <- 2A -- удвоение аффинной точки с переходом к проективным координатам.
*******************************************************************************
*/

#define ecpSeemsOnA(a, ec)\
	(zmIsIn(ecX(a), (ec)->f) && zmIsIn(ecY(a, (ec)->f->n), (ec)->f))

#define ecpSeemsOn3(a, ec)\
	(ecpSeemsOnA(a, ec) && zmIsIn(ecZ(a, (ec)->f->n), (ec)->f))

/*
*******************************************************************************
Якобиановы координаты:
	x = X / Z^2, y = Y / Z^3,
	-(X : Y : Z) = (X : -Y : Z).

В функции ecpDblJ() выполняется удвоение P <- 2P. Реализован
алгоритм dbl-1998-hnm [Hasegawa T., Nakajima J., Matsui M. A Practical
Implementation of Elliptic Curve Cryptosystems over GF(p) on a
16-bit Microcomputer. Public Key Cryptography, 1998].
Сложность алгоритма:
	3M + 6S + 1*A + 1half + 6add + 3*2 \approx 9M.

\todo Сравнить dbl-1998-hnm с dbl-2007-bl, сложность которого
	1M + 8S + 1A + 10add + 1*8 + 2*2 + 1*3.

В функции ecpDblJA3() выполняется удвоение P <- 2P для случая A = -3.
Реализован алгоритм dbl-1998-hnm2 [Hasegawa T., Nakajima J., Matsui M.
A Practical Implementation of Elliptic Curve Cryptosystems over GF(p)
on a 16-bit Microcomputer. Public Key Cryptography, 1998].
Сложность алгоритма:
	4M + 4S + 1*half + 7add + 3*2 \approx 8M.

\todo Сравнить dbl-1998-hnm2 с dbl-2001-b, сложность которого
	3M +5S + 8add + 1*4 + 2*8 + 1*3.

В функции ecpDblAJ() выполняется удвоение P <- 2A. Реализован алгоритм 
mdbl-2007-bl [Bernstein-Lange, 2007]. Сложность алгоритма:
	1M + 5S + 7add + 1*8 + 3*2 + 1*3 \approx 6M.

В функции ecpAddJ() выполняется сложение P <- P + P. Реализован алгоритм 
add-2007-bl [Bernstein-Lange, 2007]. Сложность алгоритма:
	11M + 5S + 9add + 4*2 \approx 16M.

\todo Сравнить add-2007-bl с add-1998-cmo-2, сложность которого
	12M + 4S + 6add + 1*2.

В функции ecpAddAJ() выполняется сложение P <- P + A.
Реализован алгоритм madd-2004-hmv [Hankerson D., Menezes A., Vanstone S.
Guide to Elliptic Curve Cryptography, Springer, 2004].
Сложность алгоритма:
	8M + 3S + 6add + 1*2 \approx 11M.

В функции ecpTplJ() выполняется утроение P <- 3P. Реализован алгоритм 
tpl-2007-bl [Bernstein-Lange, 2007]. Сложность алгоритма
	5M + 10S + 1*A + 15add + 2*4 + 1*6 + 1*8 + 1*16 + 1*3 \approx 15M.

В функции ecpTplJA3() выполняется утроение P <- 3P для случая A = -3. 
Реализован алгоритм tpl-2007-bl-2 [Bernstein-Lange, 2007]. 
Сложность алгоритма
	7M + 7S + 13add + 2*4 + 1*8 + 1*12 + 1*16 + 1*3 \approx 14M.

Целевые функции ci(l), определенные в описании реализации ecMul() в ec.c, 
принимают следующий вид:
	c1(l) = l/3 11;
	c2(l, w) = 103 + (2^{w-2} - 2)102 + l/(w + 1) 11;
	c3(l, w) = 6 + (2^{w-2} - 2)16 + l/(w + 1) 16.

Расчеты показывают, что
	с1(l) <= min_w c3(l), l <= 81,
	min_w c3(l, w) <= m	in_w c2(l, w), l <= 899.
Поэтому для практически используемых размерностей l (192 <= l <= 571)
первые две стратегии являются проигрышными. Реализована только стратегия 3.

\todo Сравнить madd-2004-hmv с madd-2007-bl, сложность которого
	7M + 4S + 9add + 1*4 + 3*2.

\todo При выполнении функции ecpAddAJ() может выясниться, что
складываются одинаковые точки. В этом случае вызывается функция
ecpDblAJ(). Подумать, как "помочь" этой функции, передав уже
рассчитанные значения, например, za^2. Аналогичное замечание касается
функции ecpAddJ().
*******************************************************************************
*/

// [3n]b <- [2n]a (P <- A)
static bool_t ecpFromAJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOnA(a, ec));
	ASSERT(a == b || wwIsDisjoint2(a, 2 * n, b, 3 * n));
	// xb <- xa
	qrCopy(ecX(b), ecX(a), ec->f);
	// yb <- ya
	qrCopy(ecY(b, n), ecY(a, n), ec->f);
	// zb <- unity
	qrSetUnity(ecZ(b, n), ec->f);
	return TRUE;
}

#define ecpToAJ_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n)

// [2n]b <- [3n]a (A <- P)
static bool_t ecpToAJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(a == b || wwIsDisjoint2(a, 3 * n, b, 2 * n));
	// разметить стек
	memSlice(stack,
		ecpToAJ_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &stack);
	// a == O => b <- O
	if (qrIsZero(ecZ(a, n), ec->f))
		return FALSE;
	// t1 <- za^{-1}
	qrInv(t1, ecZ(a, n), ec->f, stack);
	// t2 <- t1^2
	qrSqr(t2, t1, ec->f, stack);
	// xb <- xa t2
	qrMul(ecX(b), ecX(a), t2, ec->f, stack);
	// t2 <- t1 t2
	qrMul(t2, t1, t2, ec->f, stack);
	// yb <- ya t2
	qrMul(ecY(b, n), ecY(a, n), t2, ec->f, stack);
	// b != O
	return TRUE;
}

static size_t ecpToAJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpToAJ_local(n), 
		f_deep,
		SIZE_MAX);
}

// [3n]b <- -[3n]a (P <- -P)
static void ecpNegJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// xb <- xa
	qrCopy(ecX(b), ecX(a), ec->f);
	// yb <- -ya
	zmNeg(ecY(b, n), ecY(a, n), ec->f);
	// zb <- za
	qrCopy(ecZ(b, n), ecZ(a, n), ec->f);
}

#define ecpDblJ_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n)

// [3n]b <- 2[3n]a (P <- 2P)
static void ecpDblJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpDblJ_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &stack);
	// za == 0 или ya == 0? => b <- O
	if (qrIsZero(ecZ(a, n), ec->f) || qrIsZero(ecY(a, n), ec->f))
	{
		qrSetZero(ecZ(b, n), ec->f);
		return;
	}
	// t1 <- za^2
	qrSqr(t1, ecZ(a, n), ec->f, stack);
	// zb <- ya za
	qrMul(ecZ(b, n), ecY(a, n), ecZ(a, n), ec->f, stack);
	// zb <- 2 zb
	gfpDouble(ecZ(b, n), ecZ(b, n), ec->f);
	// t1 <- t1^2
	qrSqr(t1, t1, ec->f, stack);
	// t1 <- A t1
	qrMul(t1, ec->A, t1, ec->f, stack);
	// t2 <- xa^2
	qrSqr(t2, ecX(a), ec->f, stack);
	// t1 <- t1 + t2
	zmAdd(t1, t1, t2, ec->f);
	// t2 <- 2 t2
	gfpDouble(t2, t2, ec->f);
	// t1 <- t1 + t2
	zmAdd(t1, t1, t2, ec->f);
	// yb <- 2 ya
	gfpDouble(ecY(b, n), ecY(a, n), ec->f);
	// yb <- yb^2
	qrSqr(ecY(b, n), ecY(b, n), ec->f, stack);
	// t2 <- yb^2
	qrSqr(t2, ecY(b, n), ec->f, stack);
	// t2 <- t2 / 2
	gfpHalf(t2, t2, ec->f);
	// yb <- yb xa
	qrMul(ecY(b, n), ecY(b, n), ecX(a), ec->f, stack);
	// xb <- t1^2
	qrSqr(ecX(b), t1, ec->f, stack);
	// xb <- xb - yb
	zmSub(ecX(b), ecX(b), ecY(b, n), ec->f);
	// xb <- xb - yb
	zmSub(ecX(b), ecX(b), ecY(b, n), ec->f);
	// yb <- yb - xb
	zmSub(ecY(b, n), ecY(b, n), ecX(b), ec->f);
	// yb <- yb t1
	qrMul(ecY(b, n), ecY(b, n), t1, ec->f, stack);
	// yb <- yb - t2
	zmSub(ecY(b, n), ecY(b, n), t2, ec->f);
}

static size_t ecpDblJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpDblJ_local(n), 
		f_deep,
		SIZE_MAX);
}

#define ecpDblJA3_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n)

// [3n]b <- 2[3n]a (P <- 2P, A = -3)
static void ecpDblJA3(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpDblJA3_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &stack);
	// za == 0 или ya == 0? => b <- O
	if (qrIsZero(ecZ(a, n), ec->f) || qrIsZero(ecY(a, n), ec->f))
	{
		qrSetZero(ecZ(b, n), ec->f);
		return;
	}
	// t1 <- za^2
	qrSqr(t1, ecZ(a, n), ec->f, stack);
	// zb <- ya za
	qrMul(ecZ(b, n), ecY(a, n), ecZ(a, n), ec->f, stack);
	// zb <- 2 zb
	gfpDouble(ecZ(b, n), ecZ(b, n), ec->f);
	// t2 <- xa - t1
	zmSub(t2, ecX(a), t1, ec->f);
	// t1 <- xa + t1
	zmAdd(t1, ecX(a), t1, ec->f);
	// t2 <- t1 t2
	qrMul(t2, t1, t2, ec->f, stack);
	// t1 <- 2 t2
	gfpDouble(t1, t2, ec->f);
	// t1 <- t1 + t2
	zmAdd(t1, t1, t2, ec->f);
	// yb <- 2 ya
	gfpDouble(ecY(b, n), ecY(a, n), ec->f);
	// yb <- yb^2
	qrSqr(ecY(b, n), ecY(b, n), ec->f, stack);
	// t2 <- yb^2
	qrSqr(t2, ecY(b, n), ec->f, stack);
	// t2 <- t2 / 2
	gfpHalf(t2, t2, ec->f);
	// yb <- yb xa
	qrMul(ecY(b, n), ecY(b, n), ecX(a), ec->f, stack);
	// xb <- t1^2
	qrSqr(ecX(b), t1, ec->f, stack);
	// xb <- xb - yb
	zmSub(ecX(b), ecX(b), ecY(b, n), ec->f);
	// xb <- xb - yb
	zmSub(ecX(b), ecX(b), ecY(b, n), ec->f);
	// yb <- yb - xb
	zmSub(ecY(b, n), ecY(b, n), ecX(b), ec->f);
	// yb <- yb t1
	qrMul(ecY(b, n), ecY(b, n), t1, ec->f, stack);
	// yb <- yb - t2
	zmSub(ecY(b, n), ecY(b, n), t2, ec->f);
}

static size_t ecpDblJA3_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpDblJA3_local(n), 
		f_deep,
		SIZE_MAX);
}

#define ecpDblAJ_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n),\
/* t4 */	O_OF_W(n)

// [3n]b <- 2[2n]a (P <- 2A)
static void ecpDblAJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	word* t4;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOnA(a, ec));
	ASSERT(a == b || wwIsDisjoint2(a, 2 * n, b, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpDblAJ_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &t4, &stack);
	// ya == 0? => b <- O
	if (qrIsZero(ecY(a, n), ec->f))
	{
		qrSetZero(ecZ(b, n), ec->f);
		return;
	}
	// t1 <- xa^2 [X1^2 = XX]
	qrSqr(t1, ecX(a), ec->f, stack);
	// t2 <- ya^2 [Y1^2 = YY]
	qrSqr(t2, ecY(a, n), ec->f, stack);
	// t3 <- t2^2 [YY^2 = YYYY]
	qrSqr(t3, t2, ec->f, stack);
	// t2 <- t2 + xa [X1 + YY]
	zmAdd(t2, t2, ecX(a), ec->f);
	// t2 <- t2^2 [(X1 + YY)^2]
	qrSqr(t2, t2, ec->f, stack);
	// t2 <- t2 - t1 [(X1 + YY)^2 - XX]
	zmSub(t2, t2, t1, ec->f);
	// t2 <- t2 - t3 [(X1 + YY)^2 - XX - YYYY]
	zmSub(t2, t2, t3, ec->f);
	// t2 <- 2 t2 [2((X1 + YY)^2 - XX - YYYY) = S]
	gfpDouble(t2, t2, ec->f);
	// t4 <- 2 t1 [2 XX]
	gfpDouble(t4, t1, ec->f);
	// t4 <- t4 + t1 [3 XX]
	zmAdd(t4, t4, t1, ec->f);
	// t4 <- t4 + A [3 XX + A = M]
	zmAdd(t4, t4, ec->A, ec->f);
	// t1 <- 2 t2 [2S]
	gfpDouble(t1, t2, ec->f);
	// xb <- t4^2 [M^2]
	qrSqr(ecX(b), t4, ec->f, stack);
	// xb <- xb - t1 [M^2 - 2S = T]
	zmSub(ecX(b), ecX(b), t1, ec->f);
	// zb <- 2 ya [2Y1]
	gfpDouble(ecZ(b, n), ecY(a, n), ec->f);
	// t2 <- t2 - xb [S - T]
	zmSub(t2, t2, ecX(b), ec->f);
	// yb <- t4 t2 [M(S - T)]
	qrMul(ecY(b, n), t4, t2, ec->f, stack);
	// t3 <- 2 t3 [2 YYYY]
	gfpDouble(t3, t3, ec->f);
	// t3 <- 2 t3 [4 YYYY]
	gfpDouble(t3, t3, ec->f);
	// t3 <- 2 t3 [8 YYYY]
	gfpDouble(t3, t3, ec->f);
	// yb <- yb - t3 [M(S - T) - 8 YYYY]
	zmSub(ecY(b, n), ecY(b, n), t3, ec->f);
}

static size_t ecpDblAJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpDblAJ_local(n), 
		f_deep,
		SIZE_MAX);
}

#define ecpAddJ_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n),\
/* t4 */	O_OF_W(n)

// [3n]c <- [3n]a + [3n]b (P <- P + P)
static void ecpAddJ(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	word* t4;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(ecpSeemsOn3(b, ec));
	ASSERT(wwIsSameOrDisjoint(a, c, 3 * n));
	ASSERT(wwIsSameOrDisjoint(b, c, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpAddJ_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &t4, &stack);
	// a == O => c <- b
	if (qrIsZero(ecZ(a, n), ec->f))
	{
		wwCopy(c, b, 3 * n);
		return;
	}
	// b == O => c <- a
	if (qrIsZero(ecZ(b, n), ec->f))
	{
		wwCopy(c, a, 3 * n);
		return;
	}
	// t1 <- za^2 [Z1Z1]
	qrSqr(t1, ecZ(a, n), ec->f, stack);
	// t2 <- zb^2 [Z2Z2]
	qrSqr(t2, ecZ(b, n), ec->f, stack);
	// t3 <- zb t2 [Z2 Z2Z2]
	qrMul(t3, ecZ(b, n), t2, ec->f, stack);
	// t3 <- ya t3 [Y1 Z2 Z2Z2 = S1]
	qrMul(t3, ecY(a, n), t3, ec->f, stack);
	// t4 <- za t1 [Z1 Z1Z1]
	qrMul(t4, ecZ(a, n), t1, ec->f, stack);
	// t4 <- yb t4 [Y2 Z1 Z1Z1 = S2]
	qrMul(t4, ecY(b, n), t4, ec->f, stack);
	// zc <- za + zb [Z1 + Z2]
	zmAdd(ecZ(c, n), ecZ(a, n), ecZ(b, n), ec->f);
	// zc <- zc^2 [(Z1 + Z2)^2]
	qrSqr(ecZ(c, n), ecZ(c, n), ec->f, stack);
	// zc <- zc - t1 [(Z1 + Z2)^2 - Z1Z1]
	zmSub(ecZ(c, n), ecZ(c, n), t1, ec->f);
	// zc <- zc - t2 [(Z1 + Z2)^2 - Z1Z1 - Z2Z2]
	zmSub(ecZ(c, n), ecZ(c, n), t2, ec->f);
	// t1 <- xb t1 [X1 Z2Z2 = U2]
	qrMul(t1, ecX(b), t1, ec->f, stack);
	// t2 <- xa t2 [X2 Z1Z1 = U1]
	qrMul(t2, ecX(a), t2, ec->f, stack);
	// t1 <- t1 - t2 [U2 - U1 = H]
	zmSub(t1, t1, t2, ec->f);
	// t1 == 0 => xa zb^2 == xb za^2
	if (qrIsZero(t1, ec->f))
	{
		// t3 == t4 => ya zb^3 == yb za^3 => a == b => c <- 2a
		if (qrCmp(t3, t4, ec->f) == 0)
			ecpDblJ(c, c == a ? b : a, ec, stack);
		// t3 != t4 => a == -b => c <- O
		else
			qrSetZero(ecZ(c, n), ec->f);
		return;
	}
	// zc <- zc t1 [((Z1 + Z2)^2 - Z1Z1 - Z2Z2)H = Z3]
	qrMul(ecZ(c, n), ecZ(c, n), t1, ec->f, stack);
	// t4 <- t4 - t3 [S2 - S1]
	zmSub(t4, t4, t3, ec->f);
	// t4 <- 2 t4 [2(S2 - S1) = r]
	gfpDouble(t4, t4, ec->f);
	// yc <- 2 t1 [2H]
	gfpDouble(ecY(c, n), t1, ec->f);
	// yc <- yc^2 [(2H)^2 = I]
	qrSqr(ecY(c, n), ecY(c, n), ec->f, stack);
	// t1 <- t1 yc [H I = J]
	qrMul(t1, t1, ecY(c, n), ec->f, stack);
	// yc <- t2 yc [U1 I = V]
	qrMul(ecY(c, n), t2, ecY(c, n), ec->f, stack);
	// t2 <- 2 yc [2 V]
	gfpDouble(t2, ecY(c, n), ec->f);
	// xc <- t4^2 [r^2]
	qrSqr(ecX(c), t4, ec->f, stack);
	// xc <- xc - t1 [r^2 - J]
	zmSub(ecX(c), ecX(c), t1, ec->f);
	// xc <- xc - t2 [r^2 - J - 2V = X3]
	zmSub(ecX(c), ecX(c), t2, ec->f);
	// yc <- yc - xc [V - X3]
	zmSub(ecY(c, n), ecY(c, n), ecX(c), ec->f);
	// yc <- t4 yc [r(V - X3)]
	qrMul(ecY(c, n), t4, ecY(c, n), ec->f, stack);
	// t3 <- 2 t3 [2S1]
	gfpDouble(t3, t3, ec->f);
	// t3 <- t3 t1 [2S1 J]
	qrMul(t3, t3, t1, ec->f, stack);
	// yc <- yc - t3 [r(V - X3) - 2 S1 J]
	zmSub(ecY(c, n), ecY(c, n), t3, ec->f);
}

static size_t ecpAddJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpAddJ_local(n), 
		utilMax(2,
			f_deep,
			ecpDblJ_deep(n, f_deep)),
		SIZE_MAX);
}

#define ecpAddAJ_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n),\
/* t4 */	O_OF_W(n)

// [3n]c <- [3n]a + [2n]b (P <- P + A)
static void ecpAddAJ(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	word* t4;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(ecpSeemsOnA(b, ec));
	ASSERT(wwIsSameOrDisjoint(a, c, 3 * n));
	ASSERT(b == c || wwIsDisjoint2(b, 2 * n, c, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpAddAJ_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &t4, &stack);
	// a == O => c <- (xb : yb : 1)
	if (qrIsZero(ecZ(a, n), ec->f))
	{
		qrCopy(ecX(c), ecX(b), ec->f);
		qrCopy(ecY(c, n), ecY(b, n), ec->f);
		qrSetUnity(ecZ(c, n), ec->f);
		return;
	}
	// t1 <- za^2
	qrSqr(t1, ecZ(a, n), ec->f, stack);
	// t2 <- t1 za
	qrMul(t2, t1, ecZ(a, n), ec->f, stack);
	// t1 <- t1 xb
	qrMul(t1, t1, ecX(b), ec->f, stack);
	// t2 <- t2 yb
	qrMul(t2, t2, ecY(b, n), ec->f, stack);
	// t1 <- t1 - xa
	zmSub(t1, t1, ecX(a), ec->f);
	// t2 <- t2 - ya
	zmSub(t2, t2, ecY(a, n), ec->f);
	// t1 == 0?
	if (qrIsZero(t1, ec->f))
	{
		// t2 == 0 => c <- 2(xb : yb : 1)
		if (qrIsZero(t2, ec->f))
			ecpDblAJ(c, b, ec, stack);
		// t2 != 0 => c <- O
		else
			qrSetZero(ecZ(c, n), ec->f);
		return;
	}
	// zc <- t1 za
	qrMul(ecZ(c, n), t1, ecZ(a, n), ec->f, stack);
	// t3 <- t1^2
	qrSqr(t3, t1, ec->f, stack);
	// t4 <- t1 t3
	qrMul(t4, t1, t3, ec->f, stack);
	// t3 <- t3 xa
	qrMul(t3, t3, ecX(a), ec->f, stack);
	// t1 <- 2 t3
	gfpDouble(t1, t3, ec->f);
	// xc <- t2^2
	qrSqr(ecX(c), t2, ec->f, stack);
	// xc <- xc - t1
	zmSub(ecX(c), ecX(c), t1, ec->f);
	// xc <- xc - t4
	zmSub(ecX(c), ecX(c), t4, ec->f);
	// t3 <- t3 - xc
	zmSub(t3, t3, ecX(c), ec->f);
	// t3 <- t3 t2
	qrMul(t3, t3, t2, ec->f, stack);
	// t4 <- t4 ya
	qrMul(t4, t4, ecY(a, n), ec->f, stack);
	// yc <- t3 - t4
	zmSub(ecY(c, n), t3, t4, ec->f);
}

static size_t ecpAddAJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpAddAJ_local(n), 
		utilMax(2,
			f_deep,
			ecpDblJ_deep(n, f_deep)),
		SIZE_MAX);
}

#define ecpSubJ_local(n)\
/* t */		O_OF_W(3 * n)

// [3n]c <- [3n]a - [3n]b (P <- P - P)
static void ecpSubJ(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t;			/* [3n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(ecpSeemsOn3(b, ec));
	ASSERT(wwIsSameOrDisjoint(a, c, 3 * n));
	ASSERT(wwIsSameOrDisjoint(b, c, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpSubJ_local(n), SIZE_0, SIZE_MAX,
		&t, &stack);
	// t <- -b
	qrCopy(ecX(t), ecX(b), ec->f);
	zmNeg(ecY(t, n), ecY(b, n), ec->f);
	qrCopy(ecZ(t, n), ecZ(b, n), ec->f);
	// c <- a + t
	ecpAddJ(c, a, t, ec, stack);
}

static size_t ecpSubJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpSubJ_local(n), 
		ecpAddJ_deep(n, f_deep),
		SIZE_MAX);
}

#define ecpSubAJ_local(n)\
/* t */		O_OF_W(2 * n)

// [3n]c <- [3n]a - [2n]b (P <- P - A)
static void ecpSubAJ(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t;			/* [3n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(ecpSeemsOnA(b, ec));
	ASSERT(wwIsSameOrDisjoint(a, c, 3 * n));
	ASSERT(b == c || wwIsDisjoint2(b, 2 * n, c, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpSubAJ_local(n), SIZE_0, SIZE_MAX,
		&t, &stack);
	// t <- -b
	qrCopy(ecX(t), ecX(b), ec->f);
	zmNeg(ecY(t, n), ecY(b, n), ec->f);
	// c <- a + t
	ecpAddAJ(c, a, t, ec, stack);
}

static size_t ecpSubAJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpSubAJ_local(n), 
		ecpAddAJ_deep(n, f_deep),
		SIZE_MAX);
}

#define ecpTplJ_local(n)\
/* t0 */	O_OF_W(n),\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n),\
/* t4 */	O_OF_W(n),\
/* t5 */	O_OF_W(n),\
/* t6 */	O_OF_W(n),\
/* t7 */	O_OF_W(n)

// [3n]b <- 3[3n]a (P <- 3P)
static void ecpTplJ(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t0;			/* [n] */
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	word* t4;			/* [n] */
	word* t5;			/* [n] */
	word* t6;			/* [n] */
	word* t7;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpTplJ_local(n), SIZE_0, SIZE_MAX,
		&t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &stack);
	// t0 <- xa^2 [XX]
	qrSqr(t0, ecX(a), ec->f, stack);
	// t1 <- ya^2 [YY]
	qrSqr(t1, ecY(a, n), ec->f, stack);
	// t2 <- za^2 [ZZ]
	qrSqr(t2, ecZ(a, n), ec->f, stack);
	// t3 <- t1^2 [YYYY]
	qrSqr(t3, t1, ec->f, stack);
	// t4 <- 3 t0 + A t2^2 [M]
	qrSqr(t4, t2, ec->f, stack);
	qrMul(t4, t4, ec->A, ec->f, stack);
	gfpDouble(t5, t0, ec->f);
	zmAdd(t5, t0, t5, ec->f);
	zmAdd(t4, t4, t5, ec->f);
	// t5 <- t4^2 [MM]
	qrSqr(t5, t4, ec->f, stack);
	// t6 <- 6((xa + t1)^2 - t0 - t3) - t5 [E]
	zmAdd(t6, ecX(a), t1, ec->f);
	qrSqr(t6, t6, ec->f, stack);
	zmSub(t6, t6, t0, ec->f);
	zmSub(t6, t6, t3, ec->f);
	gfpDouble(t7, t6, ec->f);
	zmAdd(t6, t6, t7, ec->f);
	gfpDouble(t6, t6, ec->f);
	zmSub(t6, t6, t5, ec->f);
	// t7 <- t6^2 [EE]
	qrSqr(t7, t6, ec->f, stack);
	// t3 <- 16 t3 [T]
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	// zb <- (za + t6)^2 - t2 - t7
	zmAdd(ecZ(b, n), ecZ(a, n), t6, ec->f);
	qrSqr(ecZ(b, n), ecZ(b, n), ec->f, stack);
	zmSub(ecZ(b, n), ecZ(b, n), t2, ec->f);
	zmSub(ecZ(b, n), ecZ(b, n), t7, ec->f);
	// t2 <- (t4 + t6)^2 - t5 - t7 - t3 [U] 
	zmAdd(t2, t4, t6, ec->f);
	qrSqr(t2, t2, ec->f, stack);
	zmSub(t2, t2, t5, ec->f);
	zmSub(t2, t2, t7, ec->f);
	zmSub(t2, t2, t3, ec->f);
	// yb <- 8 ya (t2(t3 - t2) - t6 t7)
	zmSub(t3, t3, t2, ec->f);
	qrMul(t3, t2, t3, ec->f, stack);
	qrMul(t6, t6, t7, ec->f, stack);
	zmSub(t3, t3, t6, ec->f);
	qrMul(ecY(b, n), ecY(a, n), t3, ec->f, stack);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	// xb <- 4 (xa t7 - 4 t1 t2)
	qrMul(t1, t1, t2, ec->f, stack);
	gfpDouble(t1, t1, ec->f);
	gfpDouble(t1, t1, ec->f);
	qrMul(ecX(b), ecX(a), t7, ec->f, stack);
	zmSub(ecX(b), ecX(b), t1, ec->f);
	gfpDouble(ecX(b), ecX(b), ec->f);
	gfpDouble(ecX(b), ecX(b), ec->f);
}

static size_t ecpTplJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpTplJ_local(n), 
		f_deep,
		SIZE_MAX);
}

#define ecpTplJA3_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n),\
/* t4 */	O_OF_W(n),\
/* t5 */	O_OF_W(n),\
/* t6 */	O_OF_W(n),\
/* t7 */	O_OF_W(n)

// [3n]b <- 3[3n]a (P <- 3P)
static void ecpTplJA3(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	word* t4;			/* [n] */
	word* t5;			/* [n] */
	word* t6;			/* [n] */
	word* t7;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec) && ec->d == 3);
	ASSERT(ecpSeemsOn3(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// разметить стек
	memSlice(stack,
		ecpTplJA3_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &t4, &t5, &t6, &t7, &stack);
	// t1 <- ya^2 [YY]
	qrSqr(t1, ecY(a, n), ec->f, stack);
	// t2 <- za^2 [ZZ]
	qrSqr(t2, ecZ(a, n), ec->f, stack);
	// t3 <- t1^2 [YYYY]
	qrSqr(t3, t1, ec->f, stack);
	// t4 <- 3(xa - t2)(xa + t2) [M]
	zmSub(t4, ecX(a), t2, ec->f);
	zmAdd(t5, ecX(a), t2, ec->f);
	qrMul(t4, t4, t5, ec->f, stack);
	gfpDouble(t5, t4, ec->f);
	zmAdd(t4, t4, t5, ec->f);
	// t5 <- t4^2 [MM]
	qrSqr(t5, t4, ec->f, stack);
	// t6 <- 12 xa t1 - t5 [E]
	qrMul(t6, ecX(a), t1, ec->f, stack);
	gfpDouble(t7, t6, ec->f);
	zmAdd(t6, t6, t7, ec->f);
	gfpDouble(t6, t6, ec->f);
	gfpDouble(t6, t6, ec->f);
	zmSub(t6, t6, t5, ec->f);
	// t7 <- t6^2 [EE]
	qrSqr(t7, t6, ec->f, stack);
	// t3 <- 16 t3 [T]
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	gfpDouble(t3, t3, ec->f);
	// zb <- (za + t6)^2 - t2 - t7
	zmAdd(ecZ(b, n), ecZ(a, n), t6, ec->f);
	qrSqr(ecZ(b, n), ecZ(b, n), ec->f, stack);
	zmSub(ecZ(b, n), ecZ(b, n), t2, ec->f);
	zmSub(ecZ(b, n), ecZ(b, n), t7, ec->f);
	// t2 <- (t4 + t6)^2 - t5 - t7 - t3 [U] 
	zmAdd(t2, t4, t6, ec->f);
	qrSqr(t2, t2, ec->f, stack);
	zmSub(t2, t2, t5, ec->f);
	zmSub(t2, t2, t7, ec->f);
	zmSub(t2, t2, t3, ec->f);
	// yb <- 8 ya (t2(t3 - t2) - t6 t7)
	zmSub(t3, t3, t2, ec->f);
	qrMul(t3, t2, t3, ec->f, stack);
	qrMul(t6, t6, t7, ec->f, stack);
	zmSub(t3, t3, t6, ec->f);
	qrMul(ecY(b, n), ecY(a, n), t3, ec->f, stack);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	gfpDouble(ecY(b, n), ecY(b, n), ec->f);
	// xb <- 4 (xa t7 - 4 t1 t2)
	qrMul(t1, t1, t2, ec->f, stack);
	gfpDouble(t1, t1, ec->f);
	gfpDouble(t1, t1, ec->f);
	qrMul(ecX(b), ecX(a), t7, ec->f, stack);
	zmSub(ecX(b), ecX(b), t1, ec->f);
	gfpDouble(ecX(b), ecX(b), ec->f);
	gfpDouble(ecX(b), ecX(b), ec->f);
}

static size_t ecpTplJA3_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpTplJA3_local(n), 
		f_deep,
		SIZE_MAX);
}

#define ecpCreateJ_state(n)\
/* A */			O_OF_W(n),\
/* B */			O_OF_W(n),\
/* base */		O_OF_W(2 * n),\
/* order */		O_OF_W(n + 1),\
/* params */	SIZE_0

#define ecpCreateJ_local(n)\
/* t */			O_OF_W(n)

bool_t ecpCreateJ(ec_o* ec, const qr_o* f, const octet A[], const octet B[], 
	void* stack)
{
	register bool_t bA3;
	word* t; 			/* [n] */
	// pre
	ASSERT(memIsValid(ec, sizeof(ec_o)));
	ASSERT(gfpIsOperable(f));
	ASSERT(memIsValid(A, f->no)); 
	ASSERT(memIsValid(B, f->no));
	// f->mod > 3?
	if (wwCmpW(f->mod, f->n, 3) <= 0)
		return FALSE;
	// обнулить
	memSetZero(ec, sizeof(ec_o));
	// зафиксировать размерности
	ec->d = 3;
	// запомнить базовое поле
	ec->f = f;
	// разметить состояние и стек
	memSlice(ec->descr,
		ecpCreateJ_state(f->n), SIZE_MAX,
		&ec->A, &ec->B, &ec->base, &ec->order, &ec->params);
	memSlice(stack,
		ecpCreateJ_local(f->n), SIZE_0, SIZE_MAX,
		&t, &stack);
	// сохранить коэффициенты
	if (!qrFrom(ec->A, A, ec->f, stack) || !qrFrom(ec->B, B, ec->f, stack))
		return FALSE;
	// t <- -3
	gfpDouble(t, f->unity, f);
	zmAdd(t, t, f->unity, f);
	zmNeg(t, t, f);
	// bA3 <- A == -3?
	bA3 = qrCmp(t, ec->A, f) == 0;
	// настроить интерфейсы
	ec->froma = ecpFromAJ;
	ec->toa = ecpToAJ;
	ec->neg = ecpNegJ;
	ec->add = ecpAddJ;
	ec->adda = ecpAddAJ;
	ec->sub = ecpSubJ;
	ec->suba = ecpSubAJ;
	ec->dbl = bA3 ? ecpDblJA3 : ecpDblJ;
	ec->dbla = ecpDblAJ;
	ec->tpl = bA3 ? ecpTplJA3 : ecpTplJ;
	ec->deep = utilMax(8,
		ecpToAJ_deep(f->n, f->deep),
		ecpAddJ_deep(f->n, f->deep),
		ecpAddAJ_deep(f->n, f->deep),
		ecpSubJ_deep(f->n, f->deep),
		ecpSubAJ_deep(f->n, f->deep),
		bA3 ? ecpDblJA3_deep(f->n, f->deep) : ecpDblJ_deep(f->n, f->deep),
		ecpDblAJ_deep(f->n, f->deep),
		bA3 ? ecpTplJA3_deep(f->n, f->deep) : ecpTplJ_deep(f->n, f->deep));
	// настроить
	ec->hdr.keep = sizeof(ec_o) + 
		memSliceSize(ecpCreateJ_state(f->n), SIZE_MAX);
	ec->hdr.p_count = 6;
	ec->hdr.o_count = 1;
	// все нормально
	CLEAN(bA3);
	return TRUE;
}

size_t ecpCreateJ_keep(size_t n)
{
	return sizeof(ec_o) + 
		memSliceSize(ecpCreateJ_state(n), SIZE_MAX);
}

size_t ecpCreateJ_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpCreateJ_local(n),
		utilMax(11,
			O_OF_W(n),
			ecpToAJ_deep(n, f_deep),
			ecpAddJ_deep(n, f_deep),
			ecpAddAJ_deep(n, f_deep),
			ecpSubJ_deep(n, f_deep),
			ecpSubAJ_deep(n, f_deep),
			ecpDblJ_deep(n, f_deep),
			ecpDblJA3_deep(n, f_deep),
			ecpDblAJ_deep(n, f_deep),
			ecpTplJ_deep(n, f_deep),
			ecpTplJA3_deep(n, f_deep)),
		SIZE_MAX);
}

/*
*******************************************************************************
Свойства кривой 
*******************************************************************************
*/

#define ecpIsValid_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n)

bool_t ecpIsValid(const ec_o* ec, void* stack)
{
	size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	// разметить стек
	memSlice(stack,
		ecpIsValid_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &stack);
	// кривая работоспособна?
	// поле ec->f корректно?
	// f->mod > 3?
	// ec->deep >= ec->f->deep?
	// A, B \in ec->f?
	if (!ecIsOperable2(ec) ||
		!gfpIsValid(ec->f, stack) ||
		wwCmpW(ec->f->mod, ec->f->n, 3) <= 0 ||
		ec->deep < ec->f->deep ||
		!zmIsIn(ec->A, ec->f) || 
		!zmIsIn(ec->B, ec->f))
		return FALSE;
	// t1 <- 4 A^3
	qrSqr(t1, ec->A, ec->f, stack);
	qrMul(t1, t1, ec->A, ec->f, stack);
	gfpDouble(t1, t1, ec->f);
	gfpDouble(t1, t1, ec->f);
	// t2 <- 27 B^2
	qrSqr(t2, ec->B, ec->f, stack);
	gfpDouble(t3, t2, ec->f);
	zmAdd(t2, t2, t3, ec->f);
	gfpDouble(t3, t2, ec->f);
	zmAdd(t2, t2, t3, ec->f);
	gfpDouble(t3, t2, ec->f);
	zmAdd(t2, t2, t3, ec->f);
	// t1 <- t1 + t2 [4 A^3 + 27 B^2 -- дискриминант]
	zmAdd(t1, t1, t2, ec->f);
	// t1 == 0 => сингулярная кривая
	return !qrIsZero(t1, ec->f);
}

size_t ecpIsValid_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpIsValid_local(n), 
		utilMax(2,
			f_deep,
			gfpIsValid_deep(n)),
		SIZE_MAX);
}

#define ecpGroupSeemsValid_local(n)\
/* t1 */	O_OF_W(n + 1),\
/* t2 */	O_OF_W(n + 2),\
/* t3 */	O_OF_W(2 * n)

bool_t ecpGroupSeemsValid(const ec_o* ec, void* stack)
{
	size_t n = ec->f->n;
	int cmp;
	word w;
	word* t1;			/* [n + 1] */
	word* t2;			/* [n + 2] */
	word* t3;			/* [2 * n] */
	// pre
	ASSERT(ecIsOperable(ec));
	// разметить стек
	memSlice(stack,
		ecpGroupSeemsValid_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &stack);
	// ecGroupIsOperable(ec) == TRUE? base \in ec?
	if (!ecGroupIsOperable(ec) ||
		!ecpIsOnA(ec->base, ec, stack))
		return FALSE;
	// [n + 2]t1 <- order * cofactor
	t1[n + 1] = zzMulW(t1, ec->order, n + 1, ec->cofactor);
	// t1 <- |t1 - (p + 1)|
	if (zzSubW2(t1, n + 2, 1))
		return FALSE;
	if (wwCmp2(t1, n + 2, ec->f->mod, n) >= 0)
		zzSubW2(t1 + n, 2, zzSub2(t1, ec->f->mod, n));
	else
		zzSub(t1, ec->f->mod, t1, n);
	// n <- длина t1
	n = wwWordSize(t1, n + 2);
	// n > ec->f->n => t1^2 > 4 p
	if (n > ec->f->n)
		return FALSE;
	// [2n]t2 <- ([n]t1)^2
	zzSqr(t2, t1, n, stack);
	// условие Хассе: t2 <= 4 p?
	w = wwGetBits(t2, 0, 2);
	wwShLo(t2, 2 * n, 2);
	cmp = wwCmp2(t2, 2 * n, ec->f->mod, ec->f->n);
	if (cmp > 0 || cmp == 0 && w != 0)
		return FALSE;
	// все нормально
	return TRUE;
}

size_t ecpGroupSeemsValid_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpGroupSeemsValid_local(n), 
		utilMax(2,
			ecpIsOnA_deep(n, f_deep),
			zzSqr_deep(n)),
		SIZE_MAX);
}

#define ecpGroupIsSafe_local(n1)\
/* t1 */	O_OF_W(n1),\
/* t2 */	O_OF_W(n1)

bool_t ecpGroupIsSafe(const ec_o* ec, size_t mov_threshold, void* stack)
{
	size_t n1 = ec->f->n + 1;
	word* t1;			/* [n1] */
	word* t2;			/* [n1] */
	// pre
	ASSERT(ecIsOperable(ec));
	ASSERT(ecGroupIsOperable(ec));
	// разметить стек
	memSlice(stack,
		ecpGroupIsSafe_local(n1), SIZE_0, SIZE_MAX,
		&t1, &t2, &stack);
	// order -- простое?
	n1 = wwWordSize(ec->order, n1);
	if (!priIsPrime(ec->order, n1, stack))
		return FALSE;
	// order == p?
	if (wwCmp2(ec->f->mod, ec->f->n, ec->order, n1) == 0)
		return FALSE;
	// проверка MOV
	if (mov_threshold)
	{
		zzMod(t1, ec->f->mod, ec->f->n, ec->order, n1, stack);
		wwCopy(t2, t1, n1);
		if (wwCmpW(t2, n1, 1) == 0)
			return FALSE;
		while (--mov_threshold)
		{
			zzMulMod(t2, t2, t1, ec->order, n1, stack);
			if (wwCmpW(t2, n1, 1) == 0)
				return FALSE;
		}
	}
	// все нормально
	return TRUE;
}

size_t ecpGroupIsSafe_deep(size_t n)
{
	const size_t n1 = n + 1;
	return memSliceSize(
		ecpGroupIsSafe_local(n1), 
		utilMax(2,
			priIsPrime_deep(n1),
			zzMod_deep(n, n1),
			zzMulMod_deep(n1)),
		SIZE_MAX);
}

/*
*******************************************************************************
Арифметика аффинных точек

Сложение A <- A + A:
	1D + 1S + 1M + 6add \approx 102M

Удвоение A <- 2A:
	1D + 2S + 1M + 5add + 1*3 + 1*2 \approx 103M
*******************************************************************************
*/

#define ecpIsOnA_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n)

bool_t ecpIsOnA(const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec));
	// разметить стек
	memSlice(stack,
		ecpIsOnA_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &stack);
	// xa, ya \in ec->f?
	if (!zmIsIn(ecX(a), ec->f) || !zmIsIn(ecY(a, n), ec->f))
		return FALSE;
	// t1 <- (xa^2 + A)xa + B
	qrSqr(t1, ecX(a), ec->f, stack);
	zmAdd(t1, t1, ec->A, ec->f);
	qrMul(t1, t1, ecX(a), ec->f, stack);
	zmAdd(t1, t1, ec->B, ec->f);
	// t2 <- ya^2
	qrSqr(t2, ecY(a, n), ec->f, stack);
	// t1 == t2?
	return qrCmp(t1, t2, ec->f) == 0;
}

size_t ecpIsOnA_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpIsOnA_local(n),
		f_deep,
		SIZE_MAX);
}

void ecpNegA(word b[], const word a[], const ec_o* ec)
{
	const size_t n = ec->f->n;
	// pre
	ASSERT(ecIsOperable(ec));
	ASSERT(ecpSeemsOnA(a, ec));
	ASSERT(wwIsSameOrDisjoint(a, b, 3 * n));
	// (xb, yb) <- (xa, -ya)
	qrCopy(ecX(b), ecX(a), ec->f);
	zmNeg(ecY(b, n), ecY(a, n), ec->f);
}

#define ecpAddAA_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n)

bool_t ecpAddAA(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec));
	ASSERT(ecpSeemsOnA(a, ec));
	ASSERT(ecpSeemsOnA(b, ec));
	// разметить стек
	memSlice(stack,
		ecpAddAA_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &stack);
	// xa != xb?
	if (qrCmp(ecX(a), ecX(b), ec->f) != 0)
	{
		// t1 <- xa - xb
		zmSub(t1, ecX(a), ecX(b), ec->f);
		// t2 <- ya - yb
		zmSub(t2, ecY(a, n), ecY(b, n), ec->f);
	}
	else
	{
		// ya != yb или yb == 0 => a == -b => a + b == O
		if (qrCmp(ecY(a, n), ecY(b, n), ec->f) != 0 || 
			qrIsZero(ecY(b, n), ec->f))
			return FALSE;
		// t2 <- 3 xa^2 + A
		qrSqr(t1, ecX(a), ec->f, stack);
		gfpDouble(t2, t1, ec->f);
		zmAdd(t2, t2, t1, ec->f);
		zmAdd(t2, t2, ec->A, ec->f);
		// t1 <- 2 ya
		gfpDouble(t1, ecY(a, n), ec->f);
	}
	// t2 <- t2 / t1 = \lambda
	qrDiv(t2, t2, t1, ec->f, stack);
	// t1 <- \lambda^2 - xa - xb = xc
	qrSqr(t1, t2, ec->f, stack);
	zmSub(t1, t1, ecX(a), ec->f);
	zmSub(t1, t1, ecX(b), ec->f);
	// t3 <- xa - xc
	zmSub(t3, ecX(a), t1, ec->f);
	// t2 <- \lambda(xa - xc) - ya
	qrMul(t2, t2, t3, ec->f, stack);
	zmSub(t2, t2, ecY(a, n), ec->f);
	// выгрузить результат
	qrCopy(ecX(c), t1, ec->f);
	qrCopy(ecY(c, n), t2, ec->f);
	return TRUE;
}

size_t ecpAddAA_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpAddAA_local(n),
		f_deep,
		SIZE_MAX);
}

#define ecpSubAA_local(n)\
/* t1 */	O_OF_W(n),\
/* t2 */	O_OF_W(n),\
/* t3 */	O_OF_W(n)

bool_t ecpSubAA(word c[], const word a[], const word b[], const ec_o* ec,
	void* stack)
{
	const size_t n = ec->f->n;
	word* t1;			/* [n] */
	word* t2;			/* [n] */
	word* t3;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec));
	ASSERT(ecpSeemsOnA(a, ec));
	ASSERT(ecpSeemsOnA(b, ec));
	// разметить стек
	memSlice(stack,
		ecpSubAA_local(n), SIZE_0, SIZE_MAX,
		&t1, &t2, &t3, &stack);
	// xa != xb?
	if (qrCmp(ecX(a), ecX(b), ec->f) != 0)
	{
		// t1 <- xa - xb
		zmSub(t1, ecX(a), ecX(b), ec->f);
		// t2 <- ya + yb
		zmAdd(t2, ecY(a, n), ecY(b, n), ec->f);
	}
	else
	{
		// ya == yb => a == b => a - b == O
		if (qrCmp(ecY(a, n), ecY(b, n), ec->f) == 0)
			return FALSE;
		// здесь должно быть a == -b => a - b = 2a
		// t2 <- 3 xa^2 + A
		qrSqr(t1, ecX(a), ec->f, stack);
		gfpDouble(t2, t1, ec->f);
		zmAdd(t2, t2, t1, ec->f);
		zmAdd(t2, t2, ec->A, ec->f);
		// t1 <- 2 ya
		gfpDouble(t1, ecY(a, n), ec->f);
	}
	// t2 <- t2 / t1 = \lambda
	qrDiv(t2, t2, t1, ec->f, stack);
	// t1 <- \lambda^2 - xa - xb = xc
	qrSqr(t1, t2, ec->f, stack);
	zmSub(t1, t1, ecX(a), ec->f);
	zmSub(t1, t1, ecX(b), ec->f);
	// t3 <- xa - xc
	zmSub(t3, ecX(a), t1, ec->f);
	// t2 <- \lambda(xa - xc) - ya
	qrMul(t2, t2, t3, ec->f, stack);
	zmSub(t2, t2, ecY(a, n), ec->f);
	// выгрузить результат
	qrCopy(ecX(c), t1, ec->f);
	qrCopy(ecY(c, n), t2, ec->f);
	return TRUE;
}

size_t ecpSubAA_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpSubAA_local(n),
		f_deep,
		SIZE_MAX);
}

/*
*******************************************************************************
Алгоритм SWU

\todo Регуляризировать (qrIsUnitySafe).
*******************************************************************************
*/

#define ecpSWU_local(n)\
/* t */		O_OF_W(n),\
/* x1 */	O_OF_W(n),\
/* x2 */	O_OF_W(n),\
/* y */		O_OF_W(n),\
/* s */		O_OF_W(n)

void ecpSWU(word b[], const word a[], const ec_o* ec, void* stack)
{
	const size_t n = ec->f->n;
	register size_t mask;
	word* t;			/* [n] */
	word* x1;			/* [n] */
	word* x2;			/* [n] */
	word* y;			/* [n] */
	word* s;			/* [n] */
	// pre
	ASSERT(ecIsOperable(ec));
	ASSERT(zmIsIn(a, ec->f));
	ASSERT(wwGetBits(ec->f->mod, 0, 2) == 3);
	ASSERT(!qrIsZero(ec->A, ec->f) && !qrIsZero(ec->B, ec->f));
	// разметить стек
	memSlice(stack,
		ecpSWU_local(n), SIZE_0, SIZE_MAX,
		&t, &x1, &x2, &y, &s, &stack);
	// t <- -a^2
	qrSqr(t, a, ec->f, stack);
	zmNeg(t, t, ec->f);
	// s <- p - 2
	wwCopy(s, ec->f->mod, n);
	zzSubW2(s, n, 2);
	// x1 <- -B(1 + t + t^2)(A(t + t^2))^{p - 2} 
	qrSqr(x2, t, ec->f, stack);
	qrAdd(x2, x2, t, ec->f);
	qrMul(x1, x2, ec->A, ec->f, stack);
	qrPower(x1, x1, s, n, ec->f, stack);
	qrAddUnity(x2, x2, ec->f);
	qrMul(x1, x1, x2, ec->f, stack);
	qrMul(x1, x1, ec->B, ec->f, stack);
	zmNeg(x1, x1, ec->f);
	// y <- (x1)^3 + A x1 + B
	qrSqr(x2, x1, ec->f, stack);
	qrMul(x2, x2, x1, ec->f, stack);
	qrMul(y, x1, ec->A, ec->f, stack);
	qrAdd(y, y, x2, ec->f);
	qrAdd(y, y, ec->B, ec->f);
	// x2 <- x1 t
	qrMul(x2, x1, t, ec->f, stack);
	// t <- y^{(p - 1) - (p + 1) / 4} = y^{s - (p - 3) / 4}
	wwCopy(t, ec->f->mod, n);
	wwShLo(t, n, 2);
	zzSub(s, s, t, n);
	qrPower(t, y, s, n, ec->f, stack);
	// s <- a^3 y
	qrSqr(s, a, ec->f, stack);
	qrMul(s, s, a, ec->f, stack);
	qrMul(s, s, y, ec->f, stack);
	// mask <- t^2 y == 1 ? 0 : -1;
	qrSqr(b, t, ec->f, stack);
	qrMul(b, b, y, ec->f, stack);
	mask = qrIsUnity(b, ec->f) - SIZE_1;
	// b <- mask == 0 ? (x1, t y) : (x2, t s)
	qrCopy(ecX(b), x1 + (mask & n), ec->f);
	qrMul(ecY(b, n), t, y + (mask & n), ec->f, stack);
	// очистка
	CLEAN(mask);
}

size_t ecpSWU_deep(size_t n, size_t f_deep)
{
	return memSliceSize(
		ecpSWU_local(n),
		utilMax(2,
			f_deep,
			qrPower_deep(n, n, f_deep)),
		SIZE_MAX);
}
