/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/ext..
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ECDH/ECIES/ECDSA API Functions */

package bls48581

//import "fmt"
import "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves/native/bls48581/ext"

const INVALID_PUBLIC_KEY int = -2
const ERROR int = -3

//const INVALID int = -4
const EFS int = int(MODBYTES)
const EGS int = int(MODBYTES)

// Transform a point multiplier to RFC7748 form
func RFC7748(r *BIG) {
	lg := 0
	t := NewBIGint(1)
	c := CURVE_Cof_I
	for c != 1 {
		lg++
		c /= 2
	}
	n := uint(8*EGS - lg + 1)
	r.mod2m(n)
	t.shl(n)
	r.Add(t)
	c = r.lastbits(lg)
	r.dec(c)
}

/* return true if S is in ranger 0 < S < order , else return false */
func ECDH_IN_RANGE(S []byte) bool {
	r := NewBIGints(CURVE_Order)
	s := FromBytes(S)
	if s.IsZero() {
		return false
	}
	if Comp(s, r) >= 0 {
		return false
	}
	return true
}

/* Calculate a public/private EC GF(p) key pair W,S where W=S.G mod EC(p),
 * where S is the secret key and W is the public key
 * and G is fixed generator.
 * If RNG is NULL then the private key is provided externally in S
 * otherwise it is generated randomly internally */
func ECDH_KEY_PAIR_GENERATE(RNG *ext.RAND, S []byte, W []byte) int {
	res := 0
	var s *BIG
	var G *ECP

	G = ECP_generator()
	r := NewBIGints(CURVE_Order)

	if RNG == nil {
		s = FromBytes(S)
	} else {
		if CURVETYPE != WEIERSTRASS {
			s = Random(RNG) // from random bytes
		} else {
			s = Randomnum(r, RNG) // Removes biases
		}
	}

	if CURVETYPE != WEIERSTRASS {
		RFC7748(s) // For Montgomery or Edwards, apply RFC7748 transformation
	}

	s.ToBytes(S)
	WP := G.clmul(s, r)
	WP.ToBytes(W, false) // To use point compression on public keys, change to true

	return res
}

/* validate public key */
func ECDH_PUBLIC_KEY_VALIDATE(W []byte) int {
	WP := ECP_fromBytes(W)
	res := 0

	r := NewBIGints(CURVE_Order)

	if WP.Is_infinity() {
		res = INVALID_PUBLIC_KEY
	}
	if res == 0 {

		q := NewBIGints(Modulus)
		nb := q.nbits()
		k := NewBIGint(1)
		k.shl(uint((nb + 4) / 2))
		k.Add(q)
		k.div(r)

		for k.parity() == 0 {
			k.shr(1)
			WP.Dbl()
		}

		if !k.isunity() {
			WP = WP.lmul(k)
		}
		if WP.Is_infinity() {
			res = INVALID_PUBLIC_KEY
		}

	}
	return res
}

/* IEEE-1363 Diffie-Hellman online calculation Z=S.WD */
// type = 0 is just x coordinate output
// type = 1 for standard compressed output
// type = 2 for standard uncompress output 04|x|y
func ECDH_ECPSVDP_DH(S []byte, WD []byte, Z []byte, typ int) int {
	res := 0

	s := FromBytes(S)

	W := ECP_fromBytes(WD)
	if W.Is_infinity() {
		res = ERROR
	}

	if res == 0 {
		r := NewBIGints(CURVE_Order)
		W = W.clmul(s, r)
		if W.Is_infinity() {
			res = ERROR
		} else {
			if CURVETYPE != MONTGOMERY {
				if typ > 0 {
					if typ == 1 {
						W.ToBytes(Z, true)
					} else {
						W.ToBytes(Z, false)
					}
				} else {
					W.GetX().ToBytes(Z)
				}
				return res
			} else {
				W.GetX().ToBytes(Z)
			}
		}
	}
	return res
}

/* IEEE ECDSA Signature, C and D are signature on F using private key S */
func ECDH_ECPSP_DSA(sha int, RNG *ext.RAND, S []byte, F []byte, C []byte, D []byte) int {
	var T [EGS]byte

	B := ext.GPhashit(ext.MC_SHA2, sha, EGS, 0, F, -1, nil)
	G := ECP_generator()

	r := NewBIGints(CURVE_Order)
	s := FromBytes(S)
	f := FromBytes(B[:])

	c := NewBIGint(0)
	d := NewBIGint(0)
	V := NewECP()

	for d.IsZero() {
		u := Randomnum(r, RNG)
		w := Randomnum(r, RNG) /* IMPORTANT - side channel masking to protect invmodp() */

		V.Copy(G)
		V = V.clmul(u, r)
		vx := V.GetX()
		c.copy(vx)
		c.Mod(r)
		if c.IsZero() {
			continue
		}
		u.copy(Modmul(u, w, r))
		u.Invmodp(r)
		d.copy(Modmul(s, c, r))
		d.copy(ModAdd(d, f, r))
		d.copy(Modmul(d, w, r))
		d.copy(Modmul(u, d, r))
	}

	c.ToBytes(T[:])
	for i := 0; i < EGS; i++ {
		C[i] = T[i]
	}
	d.ToBytes(T[:])
	for i := 0; i < EGS; i++ {
		D[i] = T[i]
	}
	return 0
}

/* IEEE1363 ECDSA Signature Verification. Signature C and D on F is verified using public key W */
func ECDH_ECPVP_DSA(sha int, W []byte, F []byte, C []byte, D []byte) int {
	res := 0

	B := ext.GPhashit(ext.MC_SHA2, sha, EGS, 0, F, -1, nil)

	G := ECP_generator()
	r := NewBIGints(CURVE_Order)

	c := FromBytes(C)
	d := FromBytes(D)
	f := FromBytes(B[:])

	if c.IsZero() || Comp(c, r) >= 0 || d.IsZero() || Comp(d, r) >= 0 {
		res = ERROR
	}

	if res == 0 {
		d.Invmodp(r)
		f.copy(Modmul(f, d, r))
		h2 := Modmul(c, d, r)

		WP := ECP_fromBytes(W)
		if WP.Is_infinity() {
			res = ERROR
		} else {
			P := NewECP()
			P.Copy(WP)

			P = P.Mul2(h2, G, f)

			if P.Is_infinity() {
				res = ERROR
			} else {
				d = P.GetX()
				d.Mod(r)

				if Comp(d, c) != 0 {
					res = ERROR
				}
			}
		}
	}

	return res
}

/* IEEE1363 ECIES encryption. Encryption of plaintext M uses public key W and produces ciphertext V,C,T */
func ECDH_ECIES_ENCRYPT(sha int, P1 []byte, P2 []byte, RNG *ext.RAND, W []byte, M []byte, V []byte, T []byte) []byte {
	var Z [EFS]byte
	var VZ [3*EFS + 1]byte
	var K1 [AESKEY]byte
	var K2 [AESKEY]byte
	var U [EGS]byte

	if ECDH_KEY_PAIR_GENERATE(RNG, U[:], V) != 0 {
		return nil
	}
	if ECDH_ECPSVDP_DH(U[:], W, Z[:], 0) != 0 {
		return nil
	}

	for i := 0; i < 2*EFS+1; i++ {
		VZ[i] = V[i]
	}
	for i := 0; i < EFS; i++ {
		VZ[2*EFS+1+i] = Z[i]
	}

	K := ext.KDF2(ext.MC_SHA2, sha, VZ[:], P1, 2*AESKEY)

	for i := 0; i < AESKEY; i++ {
		K1[i] = K[i]
		K2[i] = K[AESKEY+i]
	}

	C := ext.AES_CBC_IV0_ENCRYPT(K1[:], M)

	L2 := ext.InttoBytes(len(P2), 8)

	var AC []byte

	for i := 0; i < len(C); i++ {
		AC = append(AC, C[i])
	}
	for i := 0; i < len(P2); i++ {
		AC = append(AC, P2[i])
	}
	for i := 0; i < 8; i++ {
		AC = append(AC, L2[i])
	}

	ext.HMAC(ext.MC_SHA2, sha, T, len(T), K2[:], AC)

	return C
}

/* constant time n-byte compare */
func ncomp(T1 []byte, T2 []byte, n int) bool {
	res := 0
	for i := 0; i < n; i++ {
		res |= int(T1[i] ^ T2[i])
	}
	if res == 0 {
		return true
	}
	return false
}

/* IEEE1363 ECIES decryption. Decryption of ciphertext V,C,T using private key U outputs plaintext M */
func ECDH_ECIES_DECRYPT(sha int, P1 []byte, P2 []byte, V []byte, C []byte, T []byte, U []byte) []byte {
	var Z [EFS]byte
	var VZ [3*EFS + 1]byte
	var K1 [AESKEY]byte
	var K2 [AESKEY]byte

	var TAG []byte = T[:]

	if ECDH_ECPSVDP_DH(U, V, Z[:], 0) != 0 {
		return nil
	}

	for i := 0; i < 2*EFS+1; i++ {
		VZ[i] = V[i]
	}
	for i := 0; i < EFS; i++ {
		VZ[2*EFS+1+i] = Z[i]
	}

	K := ext.KDF2(ext.MC_SHA2, sha, VZ[:], P1, 2*AESKEY)

	for i := 0; i < AESKEY; i++ {
		K1[i] = K[i]
		K2[i] = K[AESKEY+i]
	}

	M := ext.AES_CBC_IV0_DECRYPT(K1[:], C)

	if M == nil {
		return nil
	}

	L2 := ext.InttoBytes(len(P2), 8)

	var AC []byte

	for i := 0; i < len(C); i++ {
		AC = append(AC, C[i])
	}
	for i := 0; i < len(P2); i++ {
		AC = append(AC, P2[i])
	}
	for i := 0; i < 8; i++ {
		AC = append(AC, L2[i])
	}

	ext.HMAC(ext.MC_SHA2, sha, TAG, len(TAG), K2[:], AC)

	if !ncomp(T, TAG, len(T)) {
		return nil
	}

	return M
}
