package _weierstrass

/*
This implements prime order short Weierstrass curves defined over a field
k with char(k) != 2, 3 (`y^2 = x^3 + ax + b`). for the purpose of
implementing ECDH and ECDSA.  Use of this package for other purposes is
NOT RECOMMENDED.

As an explicit simplicity/performance tradeoff, projective representation
was chosen so that it is possible to use the complete addition
formulas.

See:
- https://eprint.iacr.org/2015/1060.pdf
- https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html

WARNING: The point addition and doubling formulas are specialized for
`a = -3`, which covers secp256r1, secp384r1, secp521r1, FRP256v1, SM2,
and GOST 34.10.  The brainpool curves and secp256k1 are NOT SUPPORTED
and would require slightly different formulas.
*/

Point_p256r1 :: struct {
	x: Field_Element_p256r1,
	y: Field_Element_p256r1,
	z: Field_Element_p256r1,
}

Point_p384r1 :: struct {
	x: Field_Element_p384r1,
	y: Field_Element_p384r1,
	z: Field_Element_p384r1,
}

@(require_results)
pt_set_xy_bytes :: proc "contextless" (p: ^$T, x_raw, y_raw: []byte) -> bool {
	when T == Point_p256r1 {
		FE_SZ :: FE_SIZE_P256R1
		x, y: Field_Element_p256r1
		defer fe_clear_vec([]^Field_Element_p256r1{&x, &y})
	} else when T == Point_p384r1 {
		FE_SZ :: FE_SIZE_P384R1
		x, y: Field_Element_p384r1
		defer fe_clear_vec([]^Field_Element_p384r1{&x, &y})
	} else {
		#panic("weierstrass: invalid curve")
	}

	if len(x_raw) != FE_SZ || len(y_raw) != FE_SZ {
		return false
	}

	if !fe_set_bytes(&x, x_raw) {
		return false
	}
	if !fe_set_bytes(&y, y_raw) {
		return false
	}
	if !is_on_curve(&x, &y) {
		return false
	}

	fe_set(&p.x, &x)
	fe_set(&p.y, &y)
	fe_one(&p.z)

	return true
}

@(require_results)
pt_set_x_bytes :: proc "contextless" (p: ^$T, x_raw: []byte, y_is_odd: int) -> bool {
	when T == Point_p256r1 {
		FE_SZ :: FE_SIZE_P256R1
		x, y, yy, y_neg: Field_Element_p256r1
		defer fe_clear_vec([]^Field_Element_p256r1{&x, &y, &yy, &y_neg})
	} else when T == Point_p384r1 {
		FE_SZ :: FE_SIZE_P384R1
		x, y, yy, y_neg: Field_Element_p384r1
		defer fe_clear_vec([]^Field_Element_p384r1{&x, &y, &yy, &y_neg})
	} else {
		#panic("weierstrass: invalid curve")
	}

	if len(x_raw) != FE_SZ {
		return false
	}

	if !fe_set_bytes(&x, x_raw) {
		return false
	}
	set_yy_candidate(&yy, &x)
	if fe_sqrt(&y, &yy) != 1 {
		return false
	}

	// Pick the correct y-coordinate.
	fe_negate(&y_neg, &y)
	parity_neq := (y_is_odd ~ fe_is_odd(&y)) & 1

	fe_set(&p.x, &x)
	fe_cond_select(&p.y, &y, &y_neg, parity_neq)
	fe_one(&p.z)

	return true
}

@(require_results)
pt_bytes :: proc "contextless" (x, y: []byte, p: ^$T) -> bool {
	when T == Point_p256r1 {
		FE_SZ :: FE_SIZE_P256R1
	} else when T == Point_p384r1 {
		FE_SZ :: FE_SIZE_P384R1
	} else {
		#panic("weierstrass: invalid curve")
	}

	if pt_is_identity(p) == 1 {
		return false
	}

	// Convert to affine coordinates.
	pt_rescale(p, p)

	switch len(x) {
	case 0:
	case FE_SZ:
		fe_bytes(x, &p.x)
	case:
		panic_contextless("weierstrass: invalid x buffer")
	}
	switch len(y) {
	case 0:
	case FE_SZ:
		fe_bytes(y, &p.y)
	case:
		panic_contextless("weierstrass: invalid y buffer")
	}

	return true
}

pt_set :: proc "contextless" (p, q: ^$T) {
	fe_set(&p.x, &q.x)
	fe_set(&p.y, &q.y)
	fe_set(&p.z, &q.z)
}

pt_identity :: proc "contextless" (p: ^$T) {
	fe_zero(&p.x)
	fe_one(&p.y)
	fe_zero(&p.z)
}

pt_generator :: proc "contextless" (p: ^$T) {
	fe_gen_x(&p.x)
	fe_gen_y(&p.y)
	fe_one(&p.z)
}

pt_clear :: proc "contextless" (p: ^$T) {
	fe_clear(&p.x)
	fe_clear(&p.y)
	fe_clear(&p.z)
}

pt_clear_vec :: proc "contextless" (arg: []^$T) {
	for p in arg {
		pt_clear(p)
	}
}

pt_add :: proc "contextless" (p, a, b: ^$T) {
	// Algorithm 4 from "Complete addition formulas for prime
	// order elliptic curves" by Renes, Costello, and Batina.
	//
	// The formula is complete in that it is valid for all a and b,
	// without exceptions or extra assumptions about the inputs.
	//
	// The operation costs are `12M + 2mb + 29a`.

	when T == Point_p256r1 {
		t0, t1, t2, t3, t4, b_fe: Field_Element_p256r1
		x3, y3, z3: Field_Element_p256r1
		defer fe_clear_vec([]^Field_Element_p256r1{&t0, &t1, &t2, &t3, &t4, &x3, &y3, &z3})
	} else when T == Point_p384r1 {
		t0, t1, t2, t3, t4, b_fe: Field_Element_p384r1
		x3, y3, z3: Field_Element_p384r1
		defer fe_clear_vec([]^Field_Element_p384r1{&t0, &t1, &t2, &t3, &t4, &x3, &y3, &z3})
	} else {
		#panic("weierstrass: invalid curve")
	}

	x1, y1, z1 := &a.x, &a.y, &a.z
	x2, y2, z2 := &b.x, &b.y, &b.z

	fe_b(&b_fe)

	// t0 := X1 * X2 ; t1 := Y1 * Y2 ; t2 := Z1 * Z2 ;
	fe_mul(&t0, x1, x2)
	fe_mul(&t1, y1, y2)
	fe_mul(&t2, z1, z2)

	// t3 := X1 + Y1 ; t4 := X2 + Y2 ; t3 := t3 * t4 ;
	fe_add(&t3, x1, y1)
	fe_add(&t4, x2, y2)
	fe_mul(&t3, &t3, &t4)

	// t4 := t0 + t1 ; t3 := t3 - t4 ; t4 := Y1 + Z1 ;
	fe_add(&t4, &t0, &t1)
	fe_sub(&t3, &t3, &t4)
	fe_add(&t4, y1, z1)

	// X3 := Y2 + Z2 ; t4 := t4 * X3 ; X3 := t1 + t2 ;
	fe_add(&x3, y2, z2)
	fe_mul(&t4, &t4, &x3)
	fe_add(&x3, &t1, &t2)

	// t4 := t4 - X3 ; X3 := X1 + Z1 ; Y3 := X2 + Z2 ;
	fe_sub(&t4, &t4, &x3)
	fe_add(&x3, x1, z1)
	fe_add(&y3, x2, z2)

	// X3 := X3 * Y3 ; Y3 := t0 + t2 ; Y3 := X3 - Y3 ;
	fe_mul(&x3, &x3, &y3)
	fe_add(&y3, &t0, &t2)
	fe_sub(&y3, &x3, &y3)

	// Z3 := b * t2 ; X3 := Y3 - Z3 ; Z3 := X3 + X3 ;
	fe_mul(&z3, &b_fe, &t2)
	fe_sub(&x3, &y3, &z3)
	fe_add(&z3, &x3, &x3)

	// X3 := X3 + Z3 ; Z3 := t1 - X3 ; X3 := t1 + X3 ;
	fe_add(&x3, &x3, &z3)
	fe_sub(&z3, &t1, &x3)
	fe_add(&x3, &t1, &x3)

	// Y3 := b * Y3 ; t1 := t2 + t2 ; t2 := t1 + t2 ;
	fe_mul(&y3, &b_fe, &y3)
	fe_add(&t1, &t2, &t2)
	fe_add(&t2, &t1, &t2)

	// Y3 := Y3 - t2 ; Y3 := Y3 - t0 ; t1 := Y3 + Y3 ;
	fe_sub(&y3, &y3, &t2)
	fe_sub(&y3, &y3, &t0)
	fe_add(&t1, &y3, &y3)

	// Y3 := t1 + Y3 ; t1 := t0 + t0 ; t0 := t1 + t0 ;
	fe_add(&y3, &t1, &y3)
	fe_add(&t1, &t0, &t0)
	fe_add(&t0, &t1, &t0)

	// t0 := t0 - t2 ; t1 := t4 * Y3 ; t2 := t0 * Y3 ;
	fe_sub(&t0, &t0, &t2)
	fe_mul(&t1, &t4, &y3)
	fe_mul(&t2, &t0, &y3)

	// Y3 := X3 * Z3 ; Y3 := Y3 + t2 ; X3 := t3 * X3 ;
	fe_mul(&y3, &x3, &z3)
	fe_add(&y3, &y3, &t2)
	fe_mul(&x3, &t3, &x3)

	// X3 := X3 - t1 ; Z3 := t4 * Z3 ; t1 := t3 * t0 ;
	fe_sub(&x3, &x3, &t1)
	fe_mul(&z3, &t4, &z3)
	fe_mul(&t1, &t3, &t0)

	// Z3 := Z3 + t1 ;
	fe_add(&z3, &z3, &t1)

	// return X3 , Y3 , Z3 ;
	fe_set(&p.x, &x3)
	fe_set(&p.y, &y3)
	fe_set(&p.z, &z3)
}

@(private)
pt_add_mixed :: proc "contextless" (p, a: ^$T, x2, y2: ^$U) {
	// Algorithm 5 from "Complete addition formulas for prime
	// order elliptic curves" by Renes, Costello, and Batina.
	//
	// The formula is mixed in that it assumes the z-coordinate
	// of the addend (`Z2`) is `1`, meaning that it CAN NOT
	// handle the addend being the point at infinity.
	//
	// The operation costs are `11M + 2mb + 23a` saving
	// `1M + 6a` over `pt_add`.

	when T == Point_p256r1 && U == Field_Element_p256r1 {
		t0, t1, t2, t3, t4, b_fe: Field_Element_p256r1
		x3, y3, z3: Field_Element_p256r1
		defer fe_clear_vec([]^Field_Element_p256r1{&t0, &t1, &t2, &t3, &t4, &x3, &y3, &z3})
	} else when T == Point_p384r1 {
		t0, t1, t2, t3, t4, b_fe: Field_Element_p384r1
		x3, y3, z3: Field_Element_p384r1
		defer fe_clear_vec([]^Field_Element_p384r1{&t0, &t1, &t2, &t3, &t4, &x3, &y3, &z3})
	} else {
		#panic("weierstrass: invalid curve")
	}

	x1, y1, z1 := &a.x, &a.y, &a.z

	fe_b(&b_fe)

	// t0 := X1 * X2 ; t1 := Y1 * Y2 ; t3 := X2 + Y2 ;
	fe_mul(&t0, x1, x2)
	fe_mul(&t1, y1, y2)
	fe_add(&t3, x2, y2)

	// t4 := X1 + Y1 ; t3 := t3 * t4 ; t4 := t0 + t1 ;
	fe_add(&t4, x1, y1)
	fe_mul(&t3, &t3, &t4)
	fe_add(&t4, &t0, &t1)

	// t3 := t3 − t4 ; t4 := Y2 * Z1 ; t4 := t4 + Y1 ;
	fe_sub(&t3, &t3, &t4)
	fe_mul(&t4, y2, z1)
	fe_add(&t4, &t4, y1)

	// Y3 := X2 * Z1 ; Y3 := Y3 + X1 ; Z3 := b * Z1 ;
	fe_mul(&y3, x2, z1)
	fe_add(&y3, &y3, x1)
	fe_mul(&z3, &b_fe, z1)

	// X3 := Y3 − Z3 ; Z3 := X3 + X3 ; X3 := X3 + Z3 ;
	fe_sub(&x3, &y3, &z3)
	fe_add(&z3, &x3, &x3)
	fe_add(&x3, &x3, &z3)

	// Z3 := t1 − X3 ; X3 := t1 + X3 ;. Y3 := b * Y3 ;
	fe_sub(&z3, &t1, &x3)
	fe_add(&x3, &t1, &x3)
	fe_mul(&y3, &b_fe, &y3)

	// t1 := Z1 + Z1 ; t2 := t1 + Z1 ; Y3 := Y3 − t2 ;
	fe_add(&t1, z1, z1)
	fe_add(&t2, &t1, z1)
	fe_sub(&y3, &y3, &t2)

	// Y3 := Y3 − t0 ; t1 := Y3 + Y3 ; Y3 := t1 + Y3 ;
	fe_sub(&y3, &y3, &t0)
	fe_add(&t1, &y3, &y3)
	fe_add(&y3, &t1, &y3)

	// t1 := t0 + t0 ; t0 := t1 + t0 ; t0 := t0 − t2 ;
	fe_add(&t1, &t0, &t0)
	fe_add(&t0, &t1, &t0)
	fe_sub(&t0, &t0, &t2)

	// t1 := t4 * Y3 ; t2 := t0 * Y3 ; Y3 := X3 * Z3 ;
	fe_mul(&t1, &t4, &y3)
	fe_mul(&t2, &t0, &y3)
	fe_mul(&y3, &x3, &z3)

	// Y3 := Y3 + t2 ; X3 := t3 * X3 ; X3 := X3 − t1 ;
	fe_add(&y3, &y3, &t2)
	fe_mul(&x3, &t3, &x3)
	fe_sub(&x3, &x3, &t1)

	// Z3 := t4 * Z3 ; t1 := t3 * t0 ; Z3 := Z3 + t1 ;
	fe_mul(&z3, &t4, &z3)
	fe_mul(&t1, &t3, &t0)
	fe_add(&z3, &z3, &t1)

	// return X3 , Y3 , Z3 ;
	fe_set(&p.x, &x3)
	fe_set(&p.y, &y3)
	fe_set(&p.z, &z3)
}

pt_double :: proc "contextless" (p, a: ^$T) {
	// Algorithm 6 from "Complete addition formulas for prime
	// order elliptic curves" by Renes, Costello, and Batina.
	//
	// The formula is complete in that it is valid for all a,
	// without exceptions or extra assumptions about the inputs.
	//
	// The operation costs are `8M + 3S + 2mb + 21a`.

	when T == Point_p256r1 {
		t0, t1, t2, t3, b_fe: Field_Element_p256r1
		x3, y3, z3: Field_Element_p256r1
		defer fe_clear_vec([]^Field_Element_p256r1{&t0, &t1, &t2, &t3, &x3, &y3, &z3})
	} else when T == Point_p384r1 {
		t0, t1, t2, t3, b_fe: Field_Element_p384r1
		x3, y3, z3: Field_Element_p384r1
		defer fe_clear_vec([]^Field_Element_p384r1{&t0, &t1, &t2, &t3, &x3, &y3, &z3})
	} else {
		#panic("weierstrass: invalid curve")
	}

	x, y, z := &a.x, &a.y, &a.z

	fe_b(&b_fe)

	// t0 := X ^2; t1 := Y ^2; t2 := Z ^2;
	fe_square(&t0, x)
	fe_square(&t1, y)
	fe_square(&t2, z)

	// t3 := X * Y ; t3 := t3 + t3 ; Z3 := X * Z ;
	fe_mul(&t3, x, y)
	fe_add(&t3, &t3, &t3)
	fe_mul(&z3, x, z)

	// Z3 := Z3 + Z3 ; Y3 := b * t2 ; Y3 := Y3 - Z3 ;
	fe_add(&z3, &z3, &z3)
	fe_mul(&y3, &b_fe, &t2)
	fe_sub(&y3, &y3, &z3)

	// X3 := Y3 + Y3 ; Y3 := X3 + Y3 ; X3 := t1 - Y3 ;
	fe_add(&x3, &y3, &y3)
	fe_add(&y3, &x3, &y3)
	fe_sub(&x3, &t1, &y3)

	// Y3 := t1 + Y3 ; Y3 := X3 * Y3 ; X3 := X3 * t3 ;
	fe_add(&y3, &t1, &y3)
	fe_mul(&y3, &x3, &y3)
	fe_mul(&x3, &x3, &t3)

	// t3 := t2 + t2 ; t2 := t2 + t3 ; Z3 := b * Z3 ;
	fe_add(&t3, &t2, &t2)
	fe_add(&t2, &t2, &t3)
	fe_mul(&z3, &b_fe, &z3)

	// Z3 := Z3 - t2 ; Z3 := Z3 - t0 ; t3 := Z3 + Z3 ;
	fe_sub(&z3, &z3, &t2)
	fe_sub(&z3, &z3, &t0)
	fe_add(&t3, &z3, &z3)

	// Z3 := Z3 + t3 ; t3 := t0 + t0 ; t0 := t3 + t0 ;
	fe_add(&z3, &z3, &t3)
	fe_add(&t3, &t0, &t0)
	fe_add(&t0, &t3, &t0)

	// t0 := t0 - t2 ; t0 := t0 * Z3 ; Y3 := Y3 + t0 ;
	fe_sub(&t0, &t0, &t2)
	fe_mul(&t0, &t0, &z3)
	fe_add(&y3, &y3, &t0)

	// t0 := Y * Z ; t0 := t0 + t0 ; Z3 := t0 * Z3 ;
	fe_mul(&t0, y, z)
	fe_add(&t0, &t0, &t0)
	fe_mul(&z3, &t0, &z3)

	// X3 := X3 - Z3 ; Z3 := t0 * t1 ; Z3 := Z3 + Z3 ;
	fe_sub(&x3, &x3, &z3)
	fe_mul(&z3, &t0, &t1)
	fe_add(&z3, &z3, &z3)

	// Z3 := Z3 + Z3 ;
	fe_add(&z3, &z3, &z3)

	// return X3 , Y3 , Z3 ;
	fe_set(&p.x, &x3)
	fe_set(&p.y, &y3)
	fe_set(&p.z, &z3)
}

pt_sub :: proc "contextless" (p, a, b: ^$T) {
	b_neg: T
	pt_negate(&b_neg, b)
	pt_add(p, a, &b_neg)

	fe_clear(&b_neg)
}

pt_negate :: proc "contextless" (p, a: ^$T) {
	fe_set(&p.x, &a.x)
	fe_negate(&p.y, &a.y)
	fe_set(&p.z, &a.z)
}

pt_rescale :: proc "contextless" (p, a: ^$T) {
	// A = 1/Z1
	// X3 = A*X1
	// Y3 = A*Y1
	// Z3 = 1
	//
	// As per "From A to Z: Projective coordinates leakage in the wild"
	// leaking the Z-coordinate is bad. The modular inversion algorithm
	// used in this library is based on Fermat's Little Theorem.
	//
	// See: https://eprint.iacr.org/2020/432.pdf

	was_identity := pt_is_identity(a)

	when T == Point_p256r1 {
		z_inv: Field_Element_p256r1
	} else when T == Point_p384r1 {
		z_inv: Field_Element_p384r1
	} else {
		#panic("weierstrass: invalid curve")
	}

	ident: T
	fe_inv(&z_inv, &a.z)
	fe_mul(&p.x, &a.x, &z_inv)
	fe_mul(&p.y, &a.y, &z_inv)
	fe_one(&p.z)

	pt_identity(&ident)
	pt_cond_select(p, p, &ident, was_identity)

	fe_clear(&z_inv)
}

pt_cond_select :: proc "contextless" (p, a, b: ^$T, ctrl: int) {
	fe_cond_select(&p.x, &a.x, &b.x, ctrl)
	fe_cond_select(&p.y, &a.y, &b.y, ctrl)
	fe_cond_select(&p.z, &a.z, &b.z, ctrl)
}

@(require_results)
pt_equal :: proc "contextless" (a, b: ^$T) -> int {
	when T == Point_p256r1 {
		x1z2, x2z1, y1z2, y2z1: Field_Element_p256r1
	} else when T == Point_p384r1 {
		x1z2, x2z1, y1z2, y2z1: Field_Element_p384r1
	} else {
		#panic("weierstrass: invalid curve")
	}

	// Check X1Z2 == X2Z1 && Y1Z2 == Y2Z1
	fe_mul(&x1z2, &a.x, &b.z)
	fe_mul(&x2z1, &b.x, &a.z)

	fe_mul(&y1z2, &a.y, &b.z)
	fe_mul(&y2z1, &b.y, &a.z)

	return fe_equal(&x1z2, &x2z1) & fe_equal(&y1z2, &y2z1)
}

@(require_results)
pt_is_identity :: proc "contextless" (p: ^$T) -> int {
	return fe_is_zero(&p.z)
}

@(require_results)
pt_is_y_odd :: proc "contextless" (p: ^$T) -> int {
	tmp: T
	defer pt_clear(&tmp)

	fe_set(&tmp, p)
	pt_rescale(&tmp)

	return fe_is_odd(&tmp.y)
}

@(private)
is_on_curve :: proc "contextless" (x, y: ^$T) -> bool {
	maybe_yy, yy: T
	defer fe_clear_vec([]^T{&maybe_yy, &yy})

	// RHS: x^3 + ax + b
	set_yy_candidate(&maybe_yy, x)

	// LHS: y^2
	fe_square(&yy, y)

	return fe_equal(&maybe_yy, &yy) == 1
}

@(private)
set_yy_candidate :: proc "contextless" (maybe_yy, x: ^$T) {
	// RHS: x^3 + ax + b
	rhs, tmp: T

	fe_square(&tmp, x)
	fe_mul(&rhs, &tmp, x)

	fe_a(&tmp)
	fe_mul(&tmp, &tmp, x)
	fe_add(&rhs, &rhs, &tmp)

	fe_b(&tmp)
	fe_add(maybe_yy, &rhs, &tmp)

	fe_clear(&rhs)
}
