//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

package camshoup

// Implements Camenisch-Shoup verifiable encryption based on the paper
// Practical Verifiable Encryption and Decryption of Discrete Logarithms
// see <https://www.shoup.net/papers/verenc.pdf> and
// <https://dominoweb.draco.res.ibm.com/reports/rz3730_revised.pdf>

import (
	"math/big"

	"git.sr.ht/~sircmpwn/go-bare"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
	crypto "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/paillier"
)

// PaillierGroup holds public values for Verifiable Encryption
// g and h correspond to the symbols with the same name in the paper.
// n = p * q, where p = 2p' + 1, q = 2q' + 1, p, q, p', q' are all prime
// See section 3.1 and 3.2 in verenc.pdf.
// nd4 = n / 4 integer division
// n2 = n^2
// nd4 = n^2 / 4 integer division
type PaillierGroup struct {
	g, h, n, nd4, n2d2, n2d4, n2, twoInvTwo *big.Int
}

type paillierMarshal struct {
	N []byte `bare:"n"`
	G []byte `bare:"g"`
}

// NewPaillierGroup creates a new Paillier group for verifiable encryption
// and generates safe primes for p and q.
func NewPaillierGroup() (*PaillierGroup, error) {
	return groupGenerator(crypto.GenerateSafePrime, paillier.PaillierPrimeBits)
}

// NewPaillierGroupWithPrimes create a new Paillier group for verifiable encryption
// Order n^2 where n = p * q
func NewPaillierGroupWithPrimes(p, q *big.Int) (*PaillierGroup, error) {
	n := new(big.Int).Mul(p, q)
	n2 := new(big.Int).Mul(n, n)
	gTick, err := crypto.Rand(n2)
	if err != nil {
		return nil, err
	}
	twoInvTwo := new(big.Int).ModInverse(big.NewInt(2), n)
	// 2n^2
	twoN2 := new(big.Int).Lsh(n2, 1)
	return &PaillierGroup{
		g:         new(big.Int).Exp(gTick, twoN2, n2),
		h:         new(big.Int).Add(n, big.NewInt(1)),
		n:         n,
		nd4:       new(big.Int).Rsh(n, 1),
		n2:        n2,
		n2d2:      new(big.Int).Rsh(n2, 1),
		n2d4:      new(big.Int).Rsh(n2, 2),
		twoInvTwo: new(big.Int).Lsh(twoInvTwo, 1),
	}, nil
}

// create two safe primes and generate a new Paillier group
func groupGenerator(genSafePrime func(uint) (*big.Int, error), bits uint) (*PaillierGroup, error) {
	values := make(chan *big.Int, 2)
	errors := make(chan error, 2)

	var p, q *big.Int

	for p == q {
		for range []int{1, 2} {
			go func() {
				value, err := genSafePrime(bits)
				values <- value
				errors <- err
			}()
		}

		for _, err := range []error{<-errors, <-errors} {
			if err != nil {
				return nil, err
			}
		}

		p, q = <-values, <-values
	}
	return NewPaillierGroupWithPrimes(p, q)
}

// Abs computes a mod n^2 where 0 < a < n^2 or
// (n^2 - a) mod n^2 if a > n^2/2
// See section 3.2
func (pg PaillierGroup) Abs(a *big.Int) *big.Int {
	tv := new(big.Int).Mod(a, pg.n2)

	// if a > n^2 / 2 then n^2 - a else a
	if tv.Cmp(pg.n2d2) == 1 {
		return new(big.Int).Sub(pg.n2, tv)
	} else {
		return tv
	}
}

// Exp computes base^exp mod n^2
func (pg PaillierGroup) Exp(base, exp *big.Int) *big.Int {
	return new(big.Int).Exp(base, exp, pg.n2)
}

func (pg PaillierGroup) Mul(lhs, rhs *big.Int) *big.Int {
	r := new(big.Int).Mul(lhs, rhs)
	return r.Mod(r, pg.n2)
}

// Inv computes val^-1 mod n^2
func (pg PaillierGroup) Inv(val *big.Int) *big.Int {
	return new(big.Int).ModInverse(val, pg.n2)
}

// Gexp computes g^exp mod n^2
func (pg PaillierGroup) Gexp(exp *big.Int) *big.Int {
	return new(big.Int).Exp(pg.g, exp, pg.n2)
}

// Hexp computes h^exp mod n^2
func (pg PaillierGroup) Hexp(exp *big.Int) *big.Int {
	return new(big.Int).Exp(pg.h, exp, pg.n2)
}

// Rand returns a random v ∈ [1, n^2 / 4)
func (pg PaillierGroup) Rand() (*big.Int, error) {
	return crypto.Rand(pg.n2d4)
}

// RandForEncrypt returns a random v ∈ [1, n / 4)
func (pg PaillierGroup) RandForEncrypt() (*big.Int, error) {
	return crypto.Rand(pg.nd4)
}

// MarshalBinary serializes a paillier group to a byte sequence
func (pg PaillierGroup) MarshalBinary() ([]byte, error) {
	// Only serialize what's needed
	// all values except g can be derived from n
	// g is a random value
	tv := new(paillierMarshal)
	tv.N = pg.n.Bytes()
	tv.G = pg.g.Bytes()
	return bare.Marshal(tv)
}

// UnmarshalBinary deserializes a paillier group from a byte sequence
func (pg *PaillierGroup) UnmarshalBinary(data []byte) error {
	tv := new(paillierMarshal)
	err := bare.Unmarshal(data, tv)
	if err != nil {
		return err
	}
	pg.n = new(big.Int).SetBytes(tv.N)
	pg.g = new(big.Int).SetBytes(tv.G)
	twoInvTwo := new(big.Int).ModInverse(big.NewInt(2), pg.n)
	pg.h = new(big.Int).Add(pg.n, big.NewInt(1))
	pg.n2 = new(big.Int).Mul(pg.n, pg.n)
	pg.nd4 = new(big.Int).Rsh(pg.n, 1)
	pg.n2d2 = new(big.Int).Rsh(pg.n2, 1)
	pg.n2d4 = new(big.Int).Rsh(pg.n2, 2)
	pg.twoInvTwo = new(big.Int).Lsh(twoInvTwo, 1)
	return nil
}

// Hash computes h(u, e, L) for encryption/decryption
func (pg PaillierGroup) Hash(u *big.Int, e []*big.Int, data []byte) (*big.Int, error) {
	if u == nil || len(e) == 0 || crypto.AnyNil(e...) {
		return nil, internal.ErrNilArguments
	}
	toHash := make([][]byte, len(e)+2)
	toHash[0] = u.Bytes()
	for i, ee := range e {
		toHash[i+1] = ee.Bytes()
	}
	toHash[len(toHash)-1] = data
	h, err := internal.Hash([]byte("Coinbase Hash 1.0"), toHash...)
	if err != nil {
		return nil, err
	}
	return new(big.Int).SetBytes(h), nil
}
