package bls12381

import (
	"encoding/binary"
	"fmt"
	"io"
	"math/big"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves/native"
)

// fp field element mod p
type fp [Limbs]uint64

var (
	modulus = fp{
		0xb9feffffffffaaab,
		0x1eabfffeb153ffff,
		0x6730d2a0f6b0f624,
		0x64774b84f38512bf,
		0x4b1ba7b6434bacd7,
		0x1a0111ea397fe69a,
	}
	halfModulus = fp{
		0xdcff_7fff_ffff_d556,
		0x0f55_ffff_58a9_ffff,
		0xb398_6950_7b58_7b12,
		0xb23b_a5c2_79c2_895f,
		0x258d_d3db_21a5_d66b,
		0x0d00_88f5_1cbf_f34d,
	}
	// 2^256 mod p
	r = fp{
		0x760900000002fffd,
		0xebf4000bc40c0002,
		0x5f48985753c758ba,
		0x77ce585370525745,
		0x5c071a97a256ec6d,
		0x15f65ec3fa80e493,
	}
	// 2^512 mod p
	r2 = fp{
		0xf4df1f341c341746,
		0x0a76e6a609d104f1,
		0x8de5476c4c95b6d5,
		0x67eb88a9939d83c0,
		0x9a793e85b519952d,
		0x11988fe592cae3aa,
	}
	// 2^768 mod p
	r3 = fp{
		0xed48ac6bd94ca1e0,
		0x315f831e03a7adf8,
		0x9a53352a615e29dd,
		0x34c04e5e921e1761,
		0x2512d43565724728,
		0x0aa6346091755d4d,
	}
	biModulus = new(big.Int).SetBytes([]byte{
		0x1a, 0x01, 0x11, 0xea, 0x39, 0x7f, 0xe6, 0x9a, 0x4b, 0x1b, 0xa7, 0xb6, 0x43, 0x4b, 0xac, 0xd7, 0x64, 0x77, 0x4b, 0x84, 0xf3, 0x85, 0x12, 0xbf, 0x67, 0x30, 0xd2, 0xa0, 0xf6, 0xb0, 0xf6, 0x24, 0x1e, 0xab, 0xff, 0xfe, 0xb1, 0x53, 0xff, 0xff, 0xb9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xab},
	)
)

// inv = -(p^{-1} mod 2^64) mod 2^64
const inv = 0x89f3_fffc_fffc_fffd
const hashBytes = 64

// IsZero returns 1 if fp == 0, 0 otherwise
func (f *fp) IsZero() int {
	t := f[0]
	t |= f[1]
	t |= f[2]
	t |= f[3]
	t |= f[4]
	t |= f[5]
	return int(((int64(t) | int64(-t)) >> 63) + 1)
}

// IsNonZero returns 1 if fp != 0, 0 otherwise
func (f *fp) IsNonZero() int {
	t := f[0]
	t |= f[1]
	t |= f[2]
	t |= f[3]
	t |= f[4]
	t |= f[5]
	return int(-((int64(t) | int64(-t)) >> 63))
}

// IsOne returns 1 if fp == 1, 0 otherwise
func (f *fp) IsOne() int {
	return f.Equal(&r)
}

// Cmp returns -1 if f < rhs
// 0 if f == rhs
// 1 if f > rhs
func (f *fp) Cmp(rhs *fp) int {
	gt := uint64(0)
	lt := uint64(0)
	for i := 5; i >= 0; i-- {
		// convert to two 64-bit numbers where
		// the leading bits are zeros and hold no meaning
		//  so rhs - f actually means gt
		// and f - rhs actually means lt.
		rhsH := rhs[i] >> 32
		rhsL := rhs[i] & 0xffffffff
		lhsH := f[i] >> 32
		lhsL := f[i] & 0xffffffff

		// Check the leading bit
		// if negative then f > rhs
		// if positive then f < rhs
		gt |= (rhsH - lhsH) >> 32 & 1 &^ lt
		lt |= (lhsH - rhsH) >> 32 & 1 &^ gt
		gt |= (rhsL - lhsL) >> 32 & 1 &^ lt
		lt |= (lhsL - rhsL) >> 32 & 1 &^ gt
	}
	// Make the result -1 for <, 0 for =, 1 for >
	return int(gt) - int(lt)
}

// Equal returns 1 if fp == rhs, 0 otherwise
func (f *fp) Equal(rhs *fp) int {
	t := f[0] ^ rhs[0]
	t |= f[1] ^ rhs[1]
	t |= f[2] ^ rhs[2]
	t |= f[3] ^ rhs[3]
	t |= f[4] ^ rhs[4]
	t |= f[5] ^ rhs[5]
	return int(((int64(t) | int64(-t)) >> 63) + 1)
}

// LexicographicallyLargest returns 1 if
// this element is strictly lexicographically larger than its negation
// 0 otherwise
func (f *fp) LexicographicallyLargest() int {
	var ff fp
	ff.fromMontgomery(f)

	_, borrow := sbb(ff[0], halfModulus[0], 0)
	_, borrow = sbb(ff[1], halfModulus[1], borrow)
	_, borrow = sbb(ff[2], halfModulus[2], borrow)
	_, borrow = sbb(ff[3], halfModulus[3], borrow)
	_, borrow = sbb(ff[4], halfModulus[4], borrow)
	_, borrow = sbb(ff[5], halfModulus[5], borrow)

	return (int(borrow) - 1) & 1
}

// Sgn0 returns the lowest bit value
func (f *fp) Sgn0() int {
	t := new(fp).fromMontgomery(f)
	return int(t[0] & 1)
}

// SetOne fp = r
func (f *fp) SetOne() *fp {
	f[0] = r[0]
	f[1] = r[1]
	f[2] = r[2]
	f[3] = r[3]
	f[4] = r[4]
	f[5] = r[5]
	return f
}

// SetZero fp = 0
func (f *fp) SetZero() *fp {
	f[0] = 0
	f[1] = 0
	f[2] = 0
	f[3] = 0
	f[4] = 0
	f[5] = 0
	return f
}

// SetUint64 fp = rhs
func (f *fp) SetUint64(rhs uint64) *fp {
	f[0] = rhs
	f[1] = 0
	f[2] = 0
	f[3] = 0
	f[4] = 0
	f[5] = 0
	return f.toMontgomery(f)
}

// Random generates a random field element
func (f *fp) Random(reader io.Reader) (*fp, error) {
	var t [WideFieldBytes]byte
	n, err := reader.Read(t[:])
	if err != nil {
		return nil, err
	}
	if n != WideFieldBytes {
		return nil, fmt.Errorf("can only read %d when %d are needed", n, WideFieldBytes)
	}
	return f.Hash(t[:]), nil
}

// Hash converts the byte sequence into a field element
func (f *fp) Hash(input []byte) *fp {
	dst := []byte("BLS12381_XMD:SHA-256_SSWU_RO_")
	xmd := native.ExpandMsgXmd(native.EllipticPointHasherSha256(), input, dst, hashBytes)
	var t [WideFieldBytes]byte
	copy(t[:hashBytes], internal.ReverseScalarBytes(xmd))
	return f.SetBytesWide(&t)
}

// toMontgomery converts this field to montgomery form
func (f *fp) toMontgomery(a *fp) *fp {
	// arg.R^0 * R^2 / R = arg.R
	return f.Mul(a, &r2)
}

// fromMontgomery converts this field from montgomery form
func (f *fp) fromMontgomery(a *fp) *fp {
	// Mul by 1 is division by 2^256 mod q
	//out.Mul(arg, &[native.FieldLimbs]uint64{1, 0, 0, 0})
	return f.montReduce(&[Limbs * 2]uint64{a[0], a[1], a[2], a[3], a[4], a[5], 0, 0, 0, 0, 0, 0})
}

// Neg performs modular negation
func (f *fp) Neg(a *fp) *fp {
	// Subtract `arg` from `modulus`. Ignore final borrow
	// since it can't underflow.
	var t [Limbs]uint64
	var borrow uint64
	t[0], borrow = sbb(modulus[0], a[0], 0)
	t[1], borrow = sbb(modulus[1], a[1], borrow)
	t[2], borrow = sbb(modulus[2], a[2], borrow)
	t[3], borrow = sbb(modulus[3], a[3], borrow)
	t[4], borrow = sbb(modulus[4], a[4], borrow)
	t[5], _ = sbb(modulus[5], a[5], borrow)

	// t could be `modulus` if `arg`=0. Set mask=0 if self=0
	// and 0xff..ff if `arg`!=0
	mask := a[0] | a[1] | a[2] | a[3] | a[4] | a[5]
	mask = -((mask | -mask) >> 63)
	f[0] = t[0] & mask
	f[1] = t[1] & mask
	f[2] = t[2] & mask
	f[3] = t[3] & mask
	f[4] = t[4] & mask
	f[5] = t[5] & mask
	return f
}

// Square performs modular square
func (f *fp) Square(a *fp) *fp {
	var r [2 * Limbs]uint64
	var carry uint64

	r[1], carry = mac(0, a[0], a[1], 0)
	r[2], carry = mac(0, a[0], a[2], carry)
	r[3], carry = mac(0, a[0], a[3], carry)
	r[4], carry = mac(0, a[0], a[4], carry)
	r[5], r[6] = mac(0, a[0], a[5], carry)

	r[3], carry = mac(r[3], a[1], a[2], 0)
	r[4], carry = mac(r[4], a[1], a[3], carry)
	r[5], carry = mac(r[5], a[1], a[4], carry)
	r[6], r[7] = mac(r[6], a[1], a[5], carry)

	r[5], carry = mac(r[5], a[2], a[3], 0)
	r[6], carry = mac(r[6], a[2], a[4], carry)
	r[7], r[8] = mac(r[7], a[2], a[5], carry)

	r[7], carry = mac(r[7], a[3], a[4], 0)
	r[8], r[9] = mac(r[8], a[3], a[5], carry)

	r[9], r[10] = mac(r[9], a[4], a[5], 0)

	r[11] = r[10] >> 63
	r[10] = (r[10] << 1) | r[9]>>63
	r[9] = (r[9] << 1) | r[8]>>63
	r[8] = (r[8] << 1) | r[7]>>63
	r[7] = (r[7] << 1) | r[6]>>63
	r[6] = (r[6] << 1) | r[5]>>63
	r[5] = (r[5] << 1) | r[4]>>63
	r[4] = (r[4] << 1) | r[3]>>63
	r[3] = (r[3] << 1) | r[2]>>63
	r[2] = (r[2] << 1) | r[1]>>63
	r[1] = r[1] << 1

	r[0], carry = mac(0, a[0], a[0], 0)
	r[1], carry = adc(0, r[1], carry)
	r[2], carry = mac(r[2], a[1], a[1], carry)
	r[3], carry = adc(0, r[3], carry)
	r[4], carry = mac(r[4], a[2], a[2], carry)
	r[5], carry = adc(0, r[5], carry)
	r[6], carry = mac(r[6], a[3], a[3], carry)
	r[7], carry = adc(0, r[7], carry)
	r[8], carry = mac(r[8], a[4], a[4], carry)
	r[9], carry = adc(0, r[9], carry)
	r[10], carry = mac(r[10], a[5], a[5], carry)
	r[11], _ = adc(0, r[11], carry)

	return f.montReduce(&r)
}

// Double this element
func (f *fp) Double(a *fp) *fp {
	return f.Add(a, a)
}

// Mul performs modular multiplication
func (f *fp) Mul(arg1, arg2 *fp) *fp {
	// Schoolbook multiplication
	var r [2 * Limbs]uint64
	var carry uint64

	r[0], carry = mac(0, arg1[0], arg2[0], 0)
	r[1], carry = mac(0, arg1[0], arg2[1], carry)
	r[2], carry = mac(0, arg1[0], arg2[2], carry)
	r[3], carry = mac(0, arg1[0], arg2[3], carry)
	r[4], carry = mac(0, arg1[0], arg2[4], carry)
	r[5], r[6] = mac(0, arg1[0], arg2[5], carry)

	r[1], carry = mac(r[1], arg1[1], arg2[0], 0)
	r[2], carry = mac(r[2], arg1[1], arg2[1], carry)
	r[3], carry = mac(r[3], arg1[1], arg2[2], carry)
	r[4], carry = mac(r[4], arg1[1], arg2[3], carry)
	r[5], carry = mac(r[5], arg1[1], arg2[4], carry)
	r[6], r[7] = mac(r[6], arg1[1], arg2[5], carry)

	r[2], carry = mac(r[2], arg1[2], arg2[0], 0)
	r[3], carry = mac(r[3], arg1[2], arg2[1], carry)
	r[4], carry = mac(r[4], arg1[2], arg2[2], carry)
	r[5], carry = mac(r[5], arg1[2], arg2[3], carry)
	r[6], carry = mac(r[6], arg1[2], arg2[4], carry)
	r[7], r[8] = mac(r[7], arg1[2], arg2[5], carry)

	r[3], carry = mac(r[3], arg1[3], arg2[0], 0)
	r[4], carry = mac(r[4], arg1[3], arg2[1], carry)
	r[5], carry = mac(r[5], arg1[3], arg2[2], carry)
	r[6], carry = mac(r[6], arg1[3], arg2[3], carry)
	r[7], carry = mac(r[7], arg1[3], arg2[4], carry)
	r[8], r[9] = mac(r[8], arg1[3], arg2[5], carry)

	r[4], carry = mac(r[4], arg1[4], arg2[0], 0)
	r[5], carry = mac(r[5], arg1[4], arg2[1], carry)
	r[6], carry = mac(r[6], arg1[4], arg2[2], carry)
	r[7], carry = mac(r[7], arg1[4], arg2[3], carry)
	r[8], carry = mac(r[8], arg1[4], arg2[4], carry)
	r[9], r[10] = mac(r[9], arg1[4], arg2[5], carry)

	r[5], carry = mac(r[5], arg1[5], arg2[0], 0)
	r[6], carry = mac(r[6], arg1[5], arg2[1], carry)
	r[7], carry = mac(r[7], arg1[5], arg2[2], carry)
	r[8], carry = mac(r[8], arg1[5], arg2[3], carry)
	r[9], carry = mac(r[9], arg1[5], arg2[4], carry)
	r[10], r[11] = mac(r[10], arg1[5], arg2[5], carry)

	return f.montReduce(&r)
}

// MulBy3b returns arg * 12 or 3 * b
func (f *fp) MulBy3b(arg *fp) *fp {
	var a, t fp
	a.Double(arg) // 2
	t.Double(&a)  // 4
	a.Double(&t)  // 8
	a.Add(&a, &t) // 12
	return f.Set(&a)
}

// Add performs modular addition
func (f *fp) Add(arg1, arg2 *fp) *fp {
	var t fp
	var carry uint64

	t[0], carry = adc(arg1[0], arg2[0], 0)
	t[1], carry = adc(arg1[1], arg2[1], carry)
	t[2], carry = adc(arg1[2], arg2[2], carry)
	t[3], carry = adc(arg1[3], arg2[3], carry)
	t[4], carry = adc(arg1[4], arg2[4], carry)
	t[5], _ = adc(arg1[5], arg2[5], carry)

	// Subtract the modulus to ensure the value
	// is smaller.
	return f.Sub(&t, &modulus)
}

// Sub performs modular subtraction
func (f *fp) Sub(arg1, arg2 *fp) *fp {
	d0, borrow := sbb(arg1[0], arg2[0], 0)
	d1, borrow := sbb(arg1[1], arg2[1], borrow)
	d2, borrow := sbb(arg1[2], arg2[2], borrow)
	d3, borrow := sbb(arg1[3], arg2[3], borrow)
	d4, borrow := sbb(arg1[4], arg2[4], borrow)
	d5, borrow := sbb(arg1[5], arg2[5], borrow)

	// If underflow occurred on the final limb, borrow 0xff...ff, otherwise
	// borrow = 0x00...00. Conditionally mask to add the modulus
	borrow = -borrow
	d0, carry := adc(d0, modulus[0]&borrow, 0)
	d1, carry = adc(d1, modulus[1]&borrow, carry)
	d2, carry = adc(d2, modulus[2]&borrow, carry)
	d3, carry = adc(d3, modulus[3]&borrow, carry)
	d4, carry = adc(d4, modulus[4]&borrow, carry)
	d5, _ = adc(d5, modulus[5]&borrow, carry)

	f[0] = d0
	f[1] = d1
	f[2] = d2
	f[3] = d3
	f[4] = d4
	f[5] = d5
	return f
}

// Sqrt performs modular square root
func (f *fp) Sqrt(a *fp) (*fp, int) {
	// Shank's method, as p = 3 (mod 4). This means
	// exponentiate by (p+1)/4. This only works for elements
	// that are actually quadratic residue,
	// so check the result at the end.
	var c, z fp
	z.pow(a, &fp{
		0xee7fbfffffffeaab,
		0x07aaffffac54ffff,
		0xd9cc34a83dac3d89,
		0xd91dd2e13ce144af,
		0x92c6e9ed90d2eb35,
		0x0680447a8e5ff9a6,
	})

	c.Square(&z)
	wasSquare := c.Equal(a)
	f.CMove(f, &z, wasSquare)
	return f, wasSquare
}

// Invert performs modular inverse
func (f *fp) Invert(a *fp) (*fp, int) {
	// Exponentiate by p - 2
	t := &fp{}
	t.pow(a, &fp{
		0xb9feffffffffaaa9,
		0x1eabfffeb153ffff,
		0x6730d2a0f6b0f624,
		0x64774b84f38512bf,
		0x4b1ba7b6434bacd7,
		0x1a0111ea397fe69a,
	})
	wasInverted := a.IsNonZero()
	f.CMove(a, t, wasInverted)
	return f, wasInverted
}

// SetBytes converts a little endian byte array into a field element
// return 0 if the bytes are not in the field, 1 if they are
func (f *fp) SetBytes(arg *[FieldBytes]byte) (*fp, int) {
	var borrow uint64
	t := &fp{}

	t[0] = binary.LittleEndian.Uint64(arg[:8])
	t[1] = binary.LittleEndian.Uint64(arg[8:16])
	t[2] = binary.LittleEndian.Uint64(arg[16:24])
	t[3] = binary.LittleEndian.Uint64(arg[24:32])
	t[4] = binary.LittleEndian.Uint64(arg[32:40])
	t[5] = binary.LittleEndian.Uint64(arg[40:])

	// Try to subtract the modulus
	_, borrow = sbb(t[0], modulus[0], 0)
	_, borrow = sbb(t[1], modulus[1], borrow)
	_, borrow = sbb(t[2], modulus[2], borrow)
	_, borrow = sbb(t[3], modulus[3], borrow)
	_, borrow = sbb(t[4], modulus[4], borrow)
	_, borrow = sbb(t[5], modulus[5], borrow)

	// If the element is smaller than modulus then the
	// subtraction will underflow, producing a borrow value
	// of 1. Otherwise, it'll be zero.
	mask := int(borrow)
	return f.CMove(f, t.toMontgomery(t), mask), mask
}

// SetBytesWide takes 96 bytes as input and treats them as a 512-bit number.
// Attributed to https://github.com/zcash/pasta_curves/blob/main/src/fields/Fp.rs#L255
// We reduce an arbitrary 768-bit number by decomposing it into two 384-bit digits
// with the higher bits multiplied by 2^384. Thus, we perform two reductions
//
// 1. the lower bits are multiplied by r^2, as normal
// 2. the upper bits are multiplied by r^2 * 2^384 = r^3
//
// and computing their sum in the field. It remains to see that arbitrary 384-bit
// numbers can be placed into Montgomery form safely using the reduction. The
// reduction works so long as the product is less than r=2^384 multiplied by
// the modulus. This holds because for any `c` smaller than the modulus, we have
// that (2^384 - 1)*c is an acceptable product for the reduction. Therefore, the
// reduction always works so long as `c` is in the field; in this case it is either the
// constant `r2` or `r3`.
func (f *fp) SetBytesWide(a *[WideFieldBytes]byte) *fp {
	d0 := &fp{
		binary.LittleEndian.Uint64(a[:8]),
		binary.LittleEndian.Uint64(a[8:16]),
		binary.LittleEndian.Uint64(a[16:24]),
		binary.LittleEndian.Uint64(a[24:32]),
		binary.LittleEndian.Uint64(a[32:40]),
		binary.LittleEndian.Uint64(a[40:48]),
	}
	d1 := &fp{
		binary.LittleEndian.Uint64(a[48:56]),
		binary.LittleEndian.Uint64(a[56:64]),
		binary.LittleEndian.Uint64(a[64:72]),
		binary.LittleEndian.Uint64(a[72:80]),
		binary.LittleEndian.Uint64(a[80:88]),
		binary.LittleEndian.Uint64(a[88:96]),
	}
	// d0*r2 + d1*r3
	d0.Mul(d0, &r2)
	d1.Mul(d1, &r3)
	return f.Add(d0, d1)
}

// SetBigInt initializes an element from big.Int
// The value is reduced by the modulus
func (f *fp) SetBigInt(bi *big.Int) *fp {
	var buffer [FieldBytes]byte
	t := new(big.Int).Set(bi)
	t.Mod(t, biModulus)
	t.FillBytes(buffer[:])
	copy(buffer[:], internal.ReverseScalarBytes(buffer[:]))
	_, _ = f.SetBytes(&buffer)
	return f
}

// Set copies a into fp
func (f *fp) Set(a *fp) *fp {
	f[0] = a[0]
	f[1] = a[1]
	f[2] = a[2]
	f[3] = a[3]
	f[4] = a[4]
	f[5] = a[5]
	return f
}

// SetLimbs converts an array into a field element
// by converting to montgomery form
func (f *fp) SetLimbs(a *[Limbs]uint64) *fp {
	return f.toMontgomery((*fp)(a))
}

// SetRaw converts a raw array into a field element
// Assumes input is already in montgomery form
func (f *fp) SetRaw(a *[Limbs]uint64) *fp {
	f[0] = a[0]
	f[1] = a[1]
	f[2] = a[2]
	f[3] = a[3]
	f[4] = a[4]
	f[5] = a[5]
	return f
}

// Bytes converts a field element to a little endian byte array
func (f *fp) Bytes() [FieldBytes]byte {
	var out [FieldBytes]byte
	t := new(fp).fromMontgomery(f)
	binary.LittleEndian.PutUint64(out[:8], t[0])
	binary.LittleEndian.PutUint64(out[8:16], t[1])
	binary.LittleEndian.PutUint64(out[16:24], t[2])
	binary.LittleEndian.PutUint64(out[24:32], t[3])
	binary.LittleEndian.PutUint64(out[32:40], t[4])
	binary.LittleEndian.PutUint64(out[40:], t[5])
	return out
}

// BigInt converts this element into the big.Int struct
func (f *fp) BigInt() *big.Int {
	buffer := f.Bytes()
	return new(big.Int).SetBytes(internal.ReverseScalarBytes(buffer[:]))
}

// Raw converts this element into the a [FieldLimbs]uint64
func (f *fp) Raw() [Limbs]uint64 {
	t := new(fp).fromMontgomery(f)
	return *t
}

// CMove performs conditional select.
// selects arg1 if choice == 0 and arg2 if choice == 1
func (f *fp) CMove(arg1, arg2 *fp, choice int) *fp {
	mask := uint64(-choice)
	f[0] = arg1[0] ^ ((arg1[0] ^ arg2[0]) & mask)
	f[1] = arg1[1] ^ ((arg1[1] ^ arg2[1]) & mask)
	f[2] = arg1[2] ^ ((arg1[2] ^ arg2[2]) & mask)
	f[3] = arg1[3] ^ ((arg1[3] ^ arg2[3]) & mask)
	f[4] = arg1[4] ^ ((arg1[4] ^ arg2[4]) & mask)
	f[5] = arg1[5] ^ ((arg1[5] ^ arg2[5]) & mask)
	return f
}

// CNeg conditionally negates a if choice == 1
func (f *fp) CNeg(a *fp, choice int) *fp {
	var t fp
	t.Neg(a)
	return f.CMove(f, &t, choice)
}

// Exp raises base^exp.
func (f *fp) Exp(base, exp *fp) *fp {
	e := (&fp{}).fromMontgomery(exp)
	return f.pow(base, e)
}

func (f *fp) pow(base, e *fp) *fp {
	var tmp, res fp
	res.SetOne()

	for i := len(e) - 1; i >= 0; i-- {
		for j := 63; j >= 0; j-- {
			res.Square(&res)
			tmp.Mul(&res, base)
			res.CMove(&res, &tmp, int(e[i]>>j)&1)
		}
	}
	f[0] = res[0]
	f[1] = res[1]
	f[2] = res[2]
	f[3] = res[3]
	f[4] = res[4]
	f[5] = res[5]
	return f
}

// montReduce performs the montgomery reduction
func (f *fp) montReduce(r *[2 * Limbs]uint64) *fp {
	// Taken from Algorithm 14.32 in Handbook of Applied Cryptography
	var r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, carry, k uint64
	var rr fp

	k = r[0] * inv
	_, carry = mac(r[0], k, modulus[0], 0)
	r1, carry = mac(r[1], k, modulus[1], carry)
	r2, carry = mac(r[2], k, modulus[2], carry)
	r3, carry = mac(r[3], k, modulus[3], carry)
	r4, carry = mac(r[4], k, modulus[4], carry)
	r5, carry = mac(r[5], k, modulus[5], carry)
	r6, r7 = adc(r[6], 0, carry)

	k = r1 * inv
	_, carry = mac(r1, k, modulus[0], 0)
	r2, carry = mac(r2, k, modulus[1], carry)
	r3, carry = mac(r3, k, modulus[2], carry)
	r4, carry = mac(r4, k, modulus[3], carry)
	r5, carry = mac(r5, k, modulus[4], carry)
	r6, carry = mac(r6, k, modulus[5], carry)
	r7, r8 = adc(r7, r[7], carry)

	k = r2 * inv
	_, carry = mac(r2, k, modulus[0], 0)
	r3, carry = mac(r3, k, modulus[1], carry)
	r4, carry = mac(r4, k, modulus[2], carry)
	r5, carry = mac(r5, k, modulus[3], carry)
	r6, carry = mac(r6, k, modulus[4], carry)
	r7, carry = mac(r7, k, modulus[5], carry)
	r8, r9 = adc(r8, r[8], carry)

	k = r3 * inv
	_, carry = mac(r3, k, modulus[0], 0)
	r4, carry = mac(r4, k, modulus[1], carry)
	r5, carry = mac(r5, k, modulus[2], carry)
	r6, carry = mac(r6, k, modulus[3], carry)
	r7, carry = mac(r7, k, modulus[4], carry)
	r8, carry = mac(r8, k, modulus[5], carry)
	r9, r10 = adc(r9, r[9], carry)

	k = r4 * inv
	_, carry = mac(r4, k, modulus[0], 0)
	r5, carry = mac(r5, k, modulus[1], carry)
	r6, carry = mac(r6, k, modulus[2], carry)
	r7, carry = mac(r7, k, modulus[3], carry)
	r8, carry = mac(r8, k, modulus[4], carry)
	r9, carry = mac(r9, k, modulus[5], carry)
	r10, r11 = adc(r10, r[10], carry)

	k = r5 * inv
	_, carry = mac(r5, k, modulus[0], 0)
	rr[0], carry = mac(r6, k, modulus[1], carry)
	rr[1], carry = mac(r7, k, modulus[2], carry)
	rr[2], carry = mac(r8, k, modulus[3], carry)
	rr[3], carry = mac(r9, k, modulus[4], carry)
	rr[4], carry = mac(r10, k, modulus[5], carry)
	rr[5], _ = adc(r11, r[11], carry)

	return f.Sub(&rr, &modulus)
}
