//go:build gmp
// +build gmp

package internal

import (
	"crypto/rand"
	"io"
	"math/big"

	"xdx.jelly/xgcl/api/common"
	"xdx.jelly/xgcl/internal/gmp"
	"xdx.jelly/xgcl/internal/randutil"
)

type Int = gmp.Int

var bigOne = big.NewInt(1)

func FromBigInt(src *big.Int) *Int {
	dst := new(Int)
	dst.SetBytes(src.Bytes())
	return dst
}

func IntoBigInt(src *Int) *big.Int {
	dst := new(big.Int)
	dst.SetBytes(src.Bytes())
	return dst
}

func encrypt(c *big.Int, pub *PublicKey, m *big.Int) *big.Int {
	e := new(Int).SetInt64(int64(pub.E))
	cc := FromBigInt(c)
	cc.Exp(FromBigInt(m), e, FromBigInt(pub.N))
	c.SetBytes(cc.Bytes())
	return c
}

// decrypt performs an RSA decryption, resulting in a plaintext integer. If a
// random source is given, RSA blinding is used.
func decrypt(random io.Reader, priv *PrivateKey, c *big.Int) (m *big.Int, err error) {
	// TODO(agl): can we get away with reusing blinds?
	if c.Cmp(priv.N) > 0 {
		err = common.SDR_KEYERR
		return
	}
	if priv.N.Sign() == 0 {
		return nil, common.SDR_KEYERR
	}

	var ir *Int
	modulus := FromBigInt(priv.N)
	d := FromBigInt(priv.D)
	// if random != nil {
	if false {
		randutil.MaybeReadByte(random)

		// Blinding enabled. Blinding involves multiplying c by r^e.
		// Then the decryption operation performs (m^e * r^e)^d mod n
		// which equals mr mod n. The factor of r can then be removed
		// by multiplying by the multiplicative inverse of r.
		var r *Int
		ir = new(Int)

		for {
			var R *big.Int
			R, err = rand.Int(random, priv.N)
			if err != nil {
				return
			}
			if R.Sign() == 0 {
				R = bigOne
			}
			r = FromBigInt(R)
			ok := ir.ModInverse(r, modulus)
			if ok != nil {
				break
			}
		}
		bigE := new(Int).SetInt64(int64(priv.E))
		rpowe := new(Int).Exp(r, bigE, modulus) // N != 0
		cCopy := new(Int).Set(FromBigInt(c))
		cCopy.Mul(cCopy, rpowe)
		cCopy.Mod(cCopy, modulus)
		c = IntoBigInt(cCopy)
	}

	var mm *Int
	if priv.Precomputed.Dp == nil {
		m = IntoBigInt(new(Int).Exp(FromBigInt(c), d, modulus))
	} else {
		// We have the precalculated values needed for the CRT.
		cc := FromBigInt(c)
		primes0 := FromBigInt(priv.Primes[0])
		primes1 := FromBigInt(priv.Primes[1])
		mm = new(Int).Exp(cc, FromBigInt(priv.Precomputed.Dp), primes0)
		mm2 := new(Int).Exp(cc, FromBigInt(priv.Precomputed.Dq), primes1)
		mm.Sub(mm, mm2)
		if mm.Sign() < 0 {
			mm.Add(mm, primes0)
		}
		mm.Mul(mm, FromBigInt(priv.Precomputed.Qinv))
		mm.Mod(mm, primes0)
		mm.Mul(mm, primes1)
		mm.Add(mm, mm2)

		for i, values := range priv.Precomputed.CRTValues {
			prime := FromBigInt(priv.Primes[2+i])
			mm2.Exp(cc, FromBigInt(values.Exp), prime)
			mm2.Sub(mm2, mm)
			mm2.Mul(mm2, FromBigInt(values.Coeff))
			mm2.Mod(mm2, prime)
			if mm2.Sign() < 0 {
				mm2.Add(mm2, prime)
			}
			mm2.Mul(mm2, FromBigInt(values.R))
			mm.Add(mm, mm2)
		}
	}

	if ir != nil {
		// Unblind.
		mm.Mul(mm, ir)
		mm.Mod(mm, modulus)
	}
	m = IntoBigInt(mm)

	return
}
