package _edwards25519

/*
This implements the edwards25519 composite-order group, primarily for
the purpose of implementing X25519, Ed25519, and ristretto255.  Use of
this package for other purposes is NOT RECOMMENDED.

See:
- https://eprint.iacr.org/2011/368.pdf
- https://datatracker.ietf.org/doc/html/rfc8032
- https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
*/

import "core:crypto"
import field "core:crypto/_fiat/field_curve25519"

zero_explicit :: crypto.zero_explicit

// Group_Element is an edwards25519 group element, as extended homogenous
// coordinates, which represents the affine point `(x, y)` as `(X, Y, Z, T)`,
// with the relations `x = X/Z`, `y = Y/Z`, and `x * y = T/Z`.
//
// d = -121665/121666 = 37095705934669439343138083508754565189542113879843219016388785533085940283555
// a = -1
//
// Notes:
// - There is considerable scope for optimization, however that
//   will not change the external API, and this is simple and reasonably
//   performant.
// - The API delibarately makes it hard to create arbitrary group
//   elements that are not on the curve.
// - The group element decoding routine takes the opinionated stance of
//   rejecting non-canonical encodings.

@(rodata)
FE_D := field.Tight_Field_Element {
	929955233495203,
	466365720129213,
	1662059464998953,
	2033849074728123,
	1442794654840575,
}
@(private, rodata)
FE_A := field.Tight_Field_Element {
	2251799813685228,
	2251799813685247,
	2251799813685247,
	2251799813685247,
	2251799813685247,
}
@(private, rodata)
FE_D2 := field.Tight_Field_Element {
	1859910466990425,
	932731440258426,
	1072319116312658,
	1815898335770999,
	633789495995903,
}
@(private, rodata)
GE_BASEPOINT := Group_Element {
	field.Tight_Field_Element {
		1738742601995546,
		1146398526822698,
		2070867633025821,
		562264141797630,
		587772402128613,
	},
	field.Tight_Field_Element {
		1801439850948184,
		1351079888211148,
		450359962737049,
		900719925474099,
		1801439850948198,
	},
	field.Tight_Field_Element{1, 0, 0, 0, 0},
	field.Tight_Field_Element {
		1841354044333475,
		16398895984059,
		755974180946558,
		900171276175154,
		1821297809914039,
	},
}
@(rodata)
GE_IDENTITY := Group_Element {
	field.Tight_Field_Element{0, 0, 0, 0, 0},
	field.Tight_Field_Element{1, 0, 0, 0, 0},
	field.Tight_Field_Element{1, 0, 0, 0, 0},
	field.Tight_Field_Element{0, 0, 0, 0, 0},
}

Group_Element :: struct {
	x: field.Tight_Field_Element,
	y: field.Tight_Field_Element,
	z: field.Tight_Field_Element,
	t: field.Tight_Field_Element,
}

ge_clear :: proc "contextless" (ge: ^Group_Element) {
	zero_explicit(ge, size_of(Group_Element))
}

ge_set :: proc "contextless" (ge, a: ^Group_Element) {
	field.fe_set(&ge.x, &a.x)
	field.fe_set(&ge.y, &a.y)
	field.fe_set(&ge.z, &a.z)
	field.fe_set(&ge.t, &a.t)
}

@(require_results)
ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
	ensure_contextless(len(b) == 32, "edwards25519: invalid group element size")
	b_ := (^[32]byte)(raw_data(b))

	// Do the work in a scratch element, so that ge is unchanged on
	// failure.
	tmp: Group_Element = ---
	defer ge_clear(&tmp)
	field.fe_one(&tmp.z) // Z = 1

	// The encoding is the y-coordinate, with the x-coordinate polarity
	// (odd/even) encoded in the MSB.
	field.fe_from_bytes(&tmp.y, b_) // ignores high bit

	// Recover the candidate x-coordinate via the curve equation:
	// x^2 = (y^2 - 1) / (d * y^2 + 1) (mod p)

	fe_tmp := &tmp.t // Use this to store intermediaries.
	fe_one := &tmp.z

	// x = num = y^2 - 1
	field.fe_carry_square(fe_tmp, field.fe_relax_cast(&tmp.y)) // fe_tmp = y^2
	field.fe_carry_sub(&tmp.x, fe_tmp, fe_one)

	// den = d * y^2 + 1
	field.fe_carry_mul(fe_tmp, field.fe_relax_cast(fe_tmp), field.fe_relax_cast(&FE_D))
	field.fe_carry_add(fe_tmp, fe_tmp, fe_one)

	// x = invsqrt(den/num)
	is_square := field.fe_carry_sqrt_ratio_m1(
		&tmp.x,
		field.fe_relax_cast(&tmp.x),
		field.fe_relax_cast(fe_tmp),
	)
	if is_square == 0 {
		return false
	}

	// Pick the right x-coordinate.
	field.fe_cond_negate(&tmp.x, &tmp.x, int(b[31] >> 7))

	// t = x * y
	field.fe_carry_mul(&tmp.t, field.fe_relax_cast(&tmp.x), field.fe_relax_cast(&tmp.y))

	// Reject non-canonical encodings of ge.
	buf: [32]byte = ---
	field.fe_to_bytes(&buf, &tmp.y)
	buf[31] |= byte(field.fe_is_negative(&tmp.x)) << 7
	is_canonical := crypto.compare_constant_time(b, buf[:])

	ge_cond_assign(ge, &tmp, is_canonical)

	zero_explicit(&buf, size_of(buf))

	return is_canonical == 1
}

ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
	ensure_contextless(len(dst) == 32, "edwards25519: invalid group element size")
	dst_ := (^[32]byte)(raw_data(dst))

	// Convert the element to affine (x, y) representation.
	x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
	field.fe_carry_inv(&z_inv, field.fe_relax_cast(&ge.z))
	field.fe_carry_mul(&x, field.fe_relax_cast(&ge.x), field.fe_relax_cast(&z_inv))
	field.fe_carry_mul(&y, field.fe_relax_cast(&ge.y), field.fe_relax_cast(&z_inv))

	// Encode the y-coordinate.
	field.fe_to_bytes(dst_, &y)

	// Copy the least significant bit of the x-coordinate to the most
	// significant bit of the encoded y-coordinate.
	dst_[31] |= byte((x[0] & 1) << 7)

	field.fe_clear_vec([]^field.Tight_Field_Element{&x, &y, &z_inv})
}

ge_identity :: proc "contextless" (ge: ^Group_Element) {
	field.fe_zero(&ge.x)
	field.fe_one(&ge.y)
	field.fe_one(&ge.z)
	field.fe_zero(&ge.t)
}

ge_generator :: proc "contextless" (ge: ^Group_Element) {
	ge_set(ge, &GE_BASEPOINT)
}

Addend_Group_Element :: struct {
	y2_minus_x2:  field.Loose_Field_Element, // t1
	y2_plus_x2:   field.Loose_Field_Element, // t3
	k_times_t2:   field.Tight_Field_Element, // t4
	two_times_z2: field.Loose_Field_Element, // t5
}

ge_addend_set :: proc "contextless" (ge_a: ^Addend_Group_Element, ge: ^Group_Element) {
	field.fe_sub(&ge_a.y2_minus_x2, &ge.y, &ge.x)
	field.fe_add(&ge_a.y2_plus_x2, &ge.y, &ge.x)
	field.fe_carry_mul(&ge_a.k_times_t2, field.fe_relax_cast(&FE_D2), field.fe_relax_cast(&ge.t))
	field.fe_add(&ge_a.two_times_z2, &ge.z, &ge.z)
}

@(private)
ge_addend_conditional_assign :: proc "contextless" (ge_a, a: ^Addend_Group_Element, ctrl: int) {
	field.fe_cond_select(&ge_a.y2_minus_x2, &ge_a.y2_minus_x2, &a.y2_minus_x2, ctrl)
	field.fe_cond_select(&ge_a.y2_plus_x2, &ge_a.y2_plus_x2, &a.y2_plus_x2, ctrl)
	field.fe_cond_select(&ge_a.k_times_t2, &ge_a.k_times_t2, &a.k_times_t2, ctrl)
	field.fe_cond_select(&ge_a.two_times_z2, &ge_a.two_times_z2, &a.two_times_z2, ctrl)
}

@(private)
Add_Scratch :: struct {
	A, B, C, D: field.Tight_Field_Element,
	E, F, G, H: field.Loose_Field_Element,
	t0, t2:     field.Loose_Field_Element,
}

ge_add :: proc "contextless" (ge, a, b: ^Group_Element) {
	b_: Addend_Group_Element = ---
	ge_addend_set(&b_, b)

	scratch: Add_Scratch = ---
	ge_add_addend(ge, a, &b_, &scratch)

	zero_explicit(&b_, size_of(Addend_Group_Element))
	zero_explicit(&scratch, size_of(Add_Scratch))
}

@(private)
ge_add_addend :: proc "contextless" (
	ge, a: ^Group_Element,
	b: ^Addend_Group_Element,
	scratch: ^Add_Scratch,
) {
	// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
	// Assumptions: k=2*d.
	//
	// t0 = Y1-X1
	// t1 = Y2-X2
	// A = t0*t1
	// t2 = Y1+X1
	// t3 = Y2+X2
	// B = t2*t3
	// t4 = k*T2
	// C = T1*t4
	// t5 = 2*Z2
	// D = Z1*t5
	// E = B-A
	// F = D-C
	// G = D+C
	// H = B+A
	// X3 = E*F
	// Y3 = G*H
	// T3 = E*H
	// Z3 = F*G
	//
	// In order to make the scalar multiply faster, the addend is provided
	// as a `Addend_Group_Element` with t1, t3, t4, and t5 precomputed, as
	// it is trivially obvious that those are the only values used by the
	// formula that are directly dependent on `b`, and are only dependent
	// on `b` and constants.  This saves 1 sub, 2 adds, and 1 multiply,
	// each time the intermediate representation can be reused.

	A, B, C, D := &scratch.A, &scratch.B, &scratch.C, &scratch.D
	E, F, G, H := &scratch.E, &scratch.F, &scratch.G, &scratch.H
	t0, t2 := &scratch.t0, &scratch.t2

	field.fe_sub(t0, &a.y, &a.x)
	t1 := &b.y2_minus_x2
	field.fe_carry_mul(A, t0, t1)
	field.fe_add(t2, &a.y, &a.x)
	t3 := &b.y2_plus_x2
	field.fe_carry_mul(B, t2, t3)
	t4 := &b.k_times_t2
	field.fe_carry_mul(C, field.fe_relax_cast(&a.t), field.fe_relax_cast(t4))
	t5 := &b.two_times_z2
	field.fe_carry_mul(D, field.fe_relax_cast(&a.z), t5)
	field.fe_sub(E, B, A)
	field.fe_sub(F, D, C)
	field.fe_add(G, D, C)
	field.fe_add(H, B, A)
	field.fe_carry_mul(&ge.x, E, F)
	field.fe_carry_mul(&ge.y, G, H)
	field.fe_carry_mul(&ge.t, E, H)
	field.fe_carry_mul(&ge.z, F, G)
}

@(private)
Double_Scratch :: struct {
	A, B, C, D, G: field.Tight_Field_Element,
	t0, t2, t3:    field.Tight_Field_Element,
	E, F, H:       field.Loose_Field_Element,
	t1:            field.Loose_Field_Element,
}

ge_double :: proc "contextless" (ge, a: ^Group_Element, scratch: ^Double_Scratch = nil) {
	// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd
	//
	// A = X1^2
	// B = Y1^2
	// t0 = Z1^2
	// C = 2*t0
	// D = a*A
	// t1 = X1+Y1
	// t2 = t1^2
	// t3 = t2-A
	// E = t3-B
	// G = D+B
	// F = G-C
	// H = D-B
	// X3 = E*F
	// Y3 = G*H
	// T3 = E*H
	// Z3 = F*G

	sanitize, scratch := scratch == nil, scratch
	if sanitize {
		tmp: Double_Scratch = ---
		scratch = &tmp
	}

	A, B, C, D, G := &scratch.A, &scratch.B, &scratch.C, &scratch.D, &scratch.G
	t0, t2, t3 := &scratch.t0, &scratch.t2, &scratch.t3
	E, F, H := &scratch.E, &scratch.F, &scratch.H
	t1 := &scratch.t1

	field.fe_carry_square(A, field.fe_relax_cast(&a.x))
	field.fe_carry_square(B, field.fe_relax_cast(&a.y))
	field.fe_carry_square(t0, field.fe_relax_cast(&a.z))
	field.fe_carry_add(C, t0, t0)
	field.fe_carry_mul(D, field.fe_relax_cast(&FE_A), field.fe_relax_cast(A))
	field.fe_add(t1, &a.x, &a.y)
	field.fe_carry_square(t2, t1)
	field.fe_carry_sub(t3, t2, A)
	field.fe_sub(E, t3, B)
	field.fe_carry_add(G, D, B)
	field.fe_sub(F, G, C)
	field.fe_sub(H, D, B)
	G_ := field.fe_relax_cast(G)
	field.fe_carry_mul(&ge.x, E, F)
	field.fe_carry_mul(&ge.y, G_, H)
	field.fe_carry_mul(&ge.t, E, H)
	field.fe_carry_mul(&ge.z, F, G_)

	if sanitize {
		zero_explicit(scratch, size_of(Double_Scratch))
	}
}

ge_negate :: proc "contextless" (ge, a: ^Group_Element) {
	field.fe_carry_opp(&ge.x, &a.x)
	field.fe_set(&ge.y, &a.y)
	field.fe_set(&ge.z, &a.z)
	field.fe_carry_opp(&ge.t, &a.t)
}

ge_cond_negate :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
	tmp: Group_Element = ---
	ge_negate(&tmp, a)
	ge_cond_assign(ge, &tmp, ctrl)

	ge_clear(&tmp)
}

ge_cond_assign :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
	field.fe_cond_assign(&ge.x, &a.x, ctrl)
	field.fe_cond_assign(&ge.y, &a.y, ctrl)
	field.fe_cond_assign(&ge.z, &a.z, ctrl)
	field.fe_cond_assign(&ge.t, &a.t, ctrl)
}

ge_cond_select :: proc "contextless" (ge, a, b: ^Group_Element, ctrl: int) {
	field.fe_cond_select(&ge.x, &a.x, &b.x, ctrl)
	field.fe_cond_select(&ge.y, &a.y, &b.y, ctrl)
	field.fe_cond_select(&ge.z, &a.z, &b.z, ctrl)
	field.fe_cond_select(&ge.t, &a.t, &b.t, ctrl)
}

@(require_results)
ge_equal :: proc "contextless" (a, b: ^Group_Element) -> int {
	// (x, y) ?= (x', y') -> (X/Z, Y/Z) ?= (X'/Z', Y'/Z')
	// X/Z ?= X'/Z', Y/Z ?= Y'/Z' -> X*Z' ?= X'*Z, Y*Z' ?= Y'*Z
	ax_bz, bx_az, ay_bz, by_az: field.Tight_Field_Element = ---, ---, ---, ---
	field.fe_carry_mul(&ax_bz, field.fe_relax_cast(&a.x), field.fe_relax_cast(&b.z))
	field.fe_carry_mul(&bx_az, field.fe_relax_cast(&b.x), field.fe_relax_cast(&a.z))
	field.fe_carry_mul(&ay_bz, field.fe_relax_cast(&a.y), field.fe_relax_cast(&b.z))
	field.fe_carry_mul(&by_az, field.fe_relax_cast(&b.y), field.fe_relax_cast(&a.z))

	ret := field.fe_equal(&ax_bz, &bx_az) & field.fe_equal(&ay_bz, &by_az)

	field.fe_clear_vec([]^field.Tight_Field_Element{&ax_bz, &ay_bz, &bx_az, &by_az})

	return ret
}

@(require_results)
ge_is_small_order :: proc "contextless" (ge: ^Group_Element) -> bool {
	tmp: Group_Element = ---
	ge_double(&tmp, ge)
	ge_double(&tmp, &tmp)
	ge_double(&tmp, &tmp)
	return ge_equal(&tmp, &GE_IDENTITY) == 1
}

@(require_results)
ge_in_prime_order_subgroup_vartime :: proc "contextless" (ge: ^Group_Element) -> bool {
	// This is currently *very* expensive.  The faster method would be
	// something like (https://eprint.iacr.org/2022/1164.pdf), however
	// that is a ~50% speedup, and a lot of added complexity for something
	// that is better solved by "just use ristretto255".
	tmp: Group_Element = ---
	ge_scalarmult_raw(&tmp, ge, &SC_ELL, true)
	return ge_equal(&tmp, &GE_IDENTITY) == 1
}