// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mldsa

import (
	"crypto/internal/constanttime"
	"crypto/internal/fips140/sha3"
	"errors"
	"math/bits"
)

const (
	q        = 8380417    // 2²³ - 2¹³ + 1
	R        = 4294967296 // 2³²
	RR       = 2365951    // R² mod q, aka R in the Montgomery domain
	qNegInv  = 4236238847 // -q⁻¹ mod R (q * qNegInv ≡ -1 mod R)
	one      = 4193792    // R mod q, aka 1 in the Montgomery domain
	minusOne = 4186625    // (q - 1) * R mod q, aka -1 in the Montgomery domain
)

// fieldElement is an element n of ℤ_q in the Montgomery domain, represented as
// an integer x in [0, q) such that x ≡ n * R (mod q) where R = 2³².
type fieldElement uint32

var errUnreducedFieldElement = errors.New("mldsa: unreduced field element")

// fieldToMontgomery checks that a value a is < q, and converts it to
// Montgomery form.
func fieldToMontgomery(a uint32) (fieldElement, error) {
	if a >= q {
		return 0, errUnreducedFieldElement
	}
	// a * R² * R⁻¹ ≡ a * R (mod q)
	return fieldMontgomeryMul(fieldElement(a), RR), nil
}

// fieldSubToMontgomery converts a difference a - b to Montgomery form.
// a and b must be < q. (This bound can probably be relaxed.)
func fieldSubToMontgomery(a, b uint32) fieldElement {
	x := a - b + q
	return fieldMontgomeryMul(fieldElement(x), RR)
}

// fieldFromMontgomery converts a value a in Montgomery form back to
// standard representation.
func fieldFromMontgomery(a fieldElement) uint32 {
	// (a * R) * 1 * R⁻¹ ≡ a (mod q)
	return uint32(fieldMontgomeryReduce(uint64(a)))
}

// fieldCenteredMod returns r mod± q, the value r reduced to the range
// [−(q−1)/2, (q−1)/2].
func fieldCenteredMod(r fieldElement) int32 {
	x := int32(fieldFromMontgomery(r))
	// x <= q / 2 ? x : x - q
	return constantTimeSelectLessOrEqual(x, q/2, x, x-q)
}

// fieldInfinityNorm returns the infinity norm ||r||∞ of r, or the absolute
// value of r centered around 0.
func fieldInfinityNorm(r fieldElement) uint32 {
	x := int32(fieldFromMontgomery(r))
	// x <= q / 2 ? x : |x - q|
	// |x - q| = -(x - q) = q - x because x < q => x - q < 0
	return uint32(constantTimeSelectLessOrEqual(x, q/2, x, q-x))
}

// fieldReduceOnce reduces a value a < 2q.
func fieldReduceOnce(a uint32) fieldElement {
	x, b := bits.Sub64(uint64(a), uint64(q), 0)
	return fieldElement(x + b*q)
}

// fieldAdd returns a + b mod q.
func fieldAdd(a, b fieldElement) fieldElement {
	x := uint32(a + b)
	return fieldReduceOnce(x)
}

// fieldSub returns a - b mod q.
func fieldSub(a, b fieldElement) fieldElement {
	x := uint32(a - b + q)
	return fieldReduceOnce(x)
}

// fieldMontgomeryMul returns a * b * R⁻¹ mod q.
func fieldMontgomeryMul(a, b fieldElement) fieldElement {
	x := uint64(a) * uint64(b)
	return fieldMontgomeryReduce(x)
}

// fieldMontgomeryReduce returns x * R⁻¹ mod q for x < q * R.
func fieldMontgomeryReduce(x uint64) fieldElement {
	t := uint32(x) * qNegInv
	u := (x + uint64(t)*q) >> 32
	return fieldReduceOnce(uint32(u))
}

// fieldMontgomeryMulSub returns a * (b - c). This operation is fused to save a
// fieldReduceOnce after the subtraction.
func fieldMontgomeryMulSub(a, b, c fieldElement) fieldElement {
	x := uint64(a) * uint64(b-c+q)
	return fieldMontgomeryReduce(x)
}

// fieldMontgomeryAddMul returns a * b + c * d. This operation is fused to save
// a fieldReduceOnce and a fieldReduce.
func fieldMontgomeryAddMul(a, b, c, d fieldElement) fieldElement {
	x := uint64(a) * uint64(b)
	x += uint64(c) * uint64(d)
	return fieldMontgomeryReduce(x)
}

const n = 256

// ringElement is a polynomial, an element of R_q.
type ringElement [n]fieldElement

// polyAdd adds two ringElements or nttElements.
func polyAdd[T ~[n]fieldElement](a, b T) (s T) {
	for i := range s {
		s[i] = fieldAdd(a[i], b[i])
	}
	return s
}

// polySub subtracts two ringElements or nttElements.
func polySub[T ~[n]fieldElement](a, b T) (s T) {
	for i := range s {
		s[i] = fieldSub(a[i], b[i])
	}
	return s
}

// nttElement is an NTT representation, an element of T_q.
type nttElement [n]fieldElement

// zetas are the values ζ^BitRev₈(k) mod q for each index k, converted to the
// Montgomery domain.
var zetas = [256]fieldElement{4193792, 25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468, 1826347, 2353451, 8021166, 6288512, 3119733, 5495562, 3111497, 2680103, 2725464, 1024112, 7300517, 3585928, 7830929, 7260833, 2619752, 6271868, 6262231, 4520680, 6980856, 5102745, 1757237, 8360995, 4010497, 280005, 2706023, 95776, 3077325, 3530437, 6718724, 4788269, 5842901, 3915439, 4519302, 5336701, 3574422, 5512770, 3539968, 8079950, 2348700, 7841118, 6681150, 6736599, 3505694, 4558682, 3507263, 6239768, 6779997, 3699596, 811944, 531354, 954230, 3881043, 3900724, 5823537, 2071892, 5582638, 4450022, 6851714, 4702672, 5339162, 6927966, 3475950, 2176455, 6795196, 7122806, 1939314, 4296819, 7380215, 5190273, 5223087, 4747489, 126922, 3412210, 7396998, 2147896, 2715295, 5412772, 4686924, 7969390, 5903370, 7709315, 7151892, 8357436, 7072248, 7998430, 1349076, 1852771, 6949987, 5037034, 264944, 508951, 3097992, 44288, 7280319, 904516, 3958618, 4656075, 8371839, 1653064, 5130689, 2389356, 8169440, 759969, 7063561, 189548, 4827145, 3159746, 6529015, 5971092, 8202977, 1315589, 1341330, 1285669, 6795489, 7567685, 6940675, 5361315, 4499357, 4751448, 3839961, 2091667, 3407706, 2316500, 3817976, 5037939, 2244091, 5933984, 4817955, 266997, 2434439, 7144689, 3513181, 4860065, 4621053, 7183191, 5187039, 900702, 1859098, 909542, 819034, 495491, 6767243, 8337157, 7857917, 7725090, 5257975, 2031748, 3207046, 4823422, 7855319, 7611795, 4784579, 342297, 286988, 5942594, 4108315, 3437287, 5038140, 1735879, 203044, 2842341, 2691481, 5790267, 1265009, 4055324, 1247620, 2486353, 1595974, 4613401, 1250494, 2635921, 4832145, 5386378, 1869119, 1903435, 7329447, 7047359, 1237275, 5062207, 6950192, 7929317, 1312455, 3306115, 6417775, 7100756, 1917081, 5834105, 7005614, 1500165, 777191, 2235880, 3406031, 7838005, 5548557, 6709241, 6533464, 5796124, 4656147, 594136, 4603424, 6366809, 2432395, 2454455, 8215696, 1957272, 3369112, 185531, 7173032, 5196991, 162844, 1616392, 3014001, 810149, 1652634, 4686184, 6581310, 5341501, 3523897, 3866901, 269760, 2213111, 7404533, 1717735, 472078, 7953734, 1723600, 6577327, 1910376, 6712985, 7276084, 8119771, 4546524, 5441381, 6144432, 7959518, 6094090, 183443, 7403526, 1612842, 4834730, 7826001, 3919660, 8332111, 7018208, 3937738, 1400424, 7534263, 1976782}

// ntt maps a ringElement to its nttElement representation.
//
// It implements NTT, according to FIPS 203, Algorithm 9.
func ntt(f ringElement) nttElement {
	var m uint8

	for len := 128; len >= 8; len /= 2 {
		for start := 0; start < 256; start += 2 * len {
			m++
			zeta := zetas[m]

			// Bounds check elimination hint.
			f, flen := f[start:start+len], f[start+len:start+len+len]
			for j := 0; j < len; j += 2 {
				t := fieldMontgomeryMul(zeta, flen[j])
				flen[j] = fieldSub(f[j], t)
				f[j] = fieldAdd(f[j], t)

				// Unroll by 2 for performance.
				t = fieldMontgomeryMul(zeta, flen[j+1])
				flen[j+1] = fieldSub(f[j+1], t)
				f[j+1] = fieldAdd(f[j+1], t)
			}
		}
	}

	// Unroll len = 4, 2, and 1.
	for start := 0; start < 256; start += 8 {
		m++
		zeta := zetas[m]

		t := fieldMontgomeryMul(zeta, f[start+4])
		f[start+4] = fieldSub(f[start], t)
		f[start] = fieldAdd(f[start], t)

		t = fieldMontgomeryMul(zeta, f[start+5])
		f[start+5] = fieldSub(f[start+1], t)
		f[start+1] = fieldAdd(f[start+1], t)

		t = fieldMontgomeryMul(zeta, f[start+6])
		f[start+6] = fieldSub(f[start+2], t)
		f[start+2] = fieldAdd(f[start+2], t)

		t = fieldMontgomeryMul(zeta, f[start+7])
		f[start+7] = fieldSub(f[start+3], t)
		f[start+3] = fieldAdd(f[start+3], t)
	}
	for start := 0; start < 256; start += 4 {
		m++
		zeta := zetas[m]

		t := fieldMontgomeryMul(zeta, f[start+2])
		f[start+2] = fieldSub(f[start], t)
		f[start] = fieldAdd(f[start], t)

		t = fieldMontgomeryMul(zeta, f[start+3])
		f[start+3] = fieldSub(f[start+1], t)
		f[start+1] = fieldAdd(f[start+1], t)
	}
	for start := 0; start < 256; start += 2 {
		m++
		zeta := zetas[m]

		t := fieldMontgomeryMul(zeta, f[start+1])
		f[start+1] = fieldSub(f[start], t)
		f[start] = fieldAdd(f[start], t)
	}

	return nttElement(f)
}

// inverseNTT maps a nttElement back to the ringElement it represents.
//
// It implements NTT⁻¹, according to FIPS 203, Algorithm 10.
func inverseNTT(f nttElement) ringElement {
	var m uint8 = 255

	// Unroll len = 1, 2, and 4.
	for start := 0; start < 256; start += 2 {
		zeta := zetas[m]
		m--

		t := f[start]
		f[start] = fieldAdd(t, f[start+1])
		f[start+1] = fieldMontgomeryMulSub(zeta, f[start+1], t)
	}
	for start := 0; start < 256; start += 4 {
		zeta := zetas[m]
		m--

		t := f[start]
		f[start] = fieldAdd(t, f[start+2])
		f[start+2] = fieldMontgomeryMulSub(zeta, f[start+2], t)

		t = f[start+1]
		f[start+1] = fieldAdd(t, f[start+3])
		f[start+3] = fieldMontgomeryMulSub(zeta, f[start+3], t)
	}
	for start := 0; start < 256; start += 8 {
		zeta := zetas[m]
		m--

		t := f[start]
		f[start] = fieldAdd(t, f[start+4])
		f[start+4] = fieldMontgomeryMulSub(zeta, f[start+4], t)

		t = f[start+1]
		f[start+1] = fieldAdd(t, f[start+5])
		f[start+5] = fieldMontgomeryMulSub(zeta, f[start+5], t)

		t = f[start+2]
		f[start+2] = fieldAdd(t, f[start+6])
		f[start+6] = fieldMontgomeryMulSub(zeta, f[start+6], t)

		t = f[start+3]
		f[start+3] = fieldAdd(t, f[start+7])
		f[start+7] = fieldMontgomeryMulSub(zeta, f[start+7], t)
	}

	for len := 8; len < 256; len *= 2 {
		for start := 0; start < 256; start += 2 * len {
			zeta := zetas[m]
			m--

			// Bounds check elimination hint.
			f, flen := f[start:start+len], f[start+len:start+len+len]
			for j := 0; j < len; j += 2 {
				t := f[j]
				f[j] = fieldAdd(t, flen[j])
				// -z * (t - flen[j]) = z * (flen[j] - t)
				flen[j] = fieldMontgomeryMulSub(zeta, flen[j], t)

				// Unroll by 2 for performance.
				t = f[j+1]
				f[j+1] = fieldAdd(t, flen[j+1])
				flen[j+1] = fieldMontgomeryMulSub(zeta, flen[j+1], t)
			}
		}
	}

	for i := range f {
		f[i] = fieldMontgomeryMul(f[i], 16382) // 16382 = 256⁻¹ * R mod q
	}
	return ringElement(f)
}

// nttMul multiplies two nttElements.
func nttMul(a, b nttElement) (p nttElement) {
	for i := range p {
		p[i] = fieldMontgomeryMul(a[i], b[i])
	}
	return p
}

// sampleNTT samples an nttElement uniformly at random from the seed rho and the
// indices s and r. It implements Step 3 of ExpandA, RejNTTPoly, and
// CoeffFromThreeBytes from FIPS 204, passing in ρ, s, and r instead of ρ'.
func sampleNTT(rho []byte, s, r byte) nttElement {
	G := sha3.NewShake128()
	G.Write(rho)
	G.Write([]byte{s, r})

	var a nttElement
	var j int         // index into a
	var buf [168]byte // buffered reads from B, matching the rate of SHAKE-128
	off := len(buf)   // index into buf, starts in a "buffer fully consumed" state
	for j < n {
		if off >= len(buf) {
			G.Read(buf[:])
			off = 0
		}
		v := uint32(buf[off]) | uint32(buf[off+1])<<8 | uint32(buf[off+2])<<16
		off += 3
		f, err := fieldToMontgomery(v & 0b01111111_11111111_11111111) // 23 bits
		if err != nil {
			continue
		}
		a[j] = f
		j++
	}
	return a
}

// sampleBoundedPoly samples a ringElement with coefficients in [−η, η] from the
// seed rho and the index r. It implements RejBoundedPoly and CoeffFromHalfByte
// from FIPS 204, passing in ρ and r separately from ExpandS.
func sampleBoundedPoly(rho []byte, r byte, p parameters) ringElement {
	H := sha3.NewShake256()
	H.Write(rho)
	H.Write([]byte{r, 0}) // IntegerToBytes(r, 2)

	var a ringElement
	var j int
	var buf [136]byte // buffered reads from H, matching the rate of SHAKE-256
	off := len(buf)   // index into buf, starts in a "buffer fully consumed" state
	for {
		if off >= len(buf) {
			H.Read(buf[:])
			off = 0
		}
		z0 := buf[off] & 0x0F
		z1 := buf[off] >> 4
		off++
		coeff, ok := coeffFromHalfByte(z0, p)
		if ok {
			a[j] = coeff
			j++
		}
		if j >= len(a) {
			break
		}
		coeff, ok = coeffFromHalfByte(z1, p)
		if ok {
			a[j] = coeff
			j++
		}
		if j >= len(a) {
			break
		}
	}
	return a
}

// sampleInBall samples a ringElement with coefficients in {−1, 0, 1}, and τ
// non-zero coefficients. It is not constant-time.
func sampleInBall(rho []byte, p parameters) ringElement {
	H := sha3.NewShake256()
	H.Write(rho)
	s := make([]byte, 8)
	H.Read(s)

	var c ringElement
	for i := 256 - p.τ; i < 256; i++ {
		j := make([]byte, 1)
		H.Read(j)
		for j[0] > byte(i) {
			H.Read(j)
		}
		c[i] = c[j[0]]
		// c[j] = (−1) ^ h[i+τ−256], where h are the bits in s in little-endian.
		// That is, -1⁰ = 1 if the bit is 0, -1¹ = -1 if it is 1.
		bitIdx := i + p.τ - 256
		bit := (s[bitIdx/8] >> (bitIdx % 8)) & 1
		if bit == 0 {
			c[j[0]] = one
		} else {
			c[j[0]] = minusOne
		}
	}

	return c
}

// coeffFromHalfByte implements CoeffFromHalfByte from FIPS 204.
//
// It maps a value in [0, 15] to a coefficient in [−η, η]
func coeffFromHalfByte(b byte, p parameters) (fieldElement, bool) {
	if b > 15 {
		panic("internal error: half-byte out of range")
	}
	switch p.η {
	case 2:
		// Return z = 2 − (b mod 5), which maps from
		//
		//     b = ( 14, 13, 12, 11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0 )
		//
		// to
		//
		//   b%5 = (  4,  3,  2,  1,  0,  4,  3,  2,  1,  0,  4,  3,  2,  1,  0 )
		//
		// to
		//
		//     z = ( -2, -1,  0,  1,  2, -2, -1,  0,  1,  2, -2, -1,  0,  1,  2 )
		//
		if b > 14 {
			return 0, false
		}
		// Calculate b % 5 with Barrett reduction, to avoid a potentially
		// variable-time division.
		const barrettMultiplier = 0x3334 // ⌈2¹⁶ / 5⌉
		const barrettShift = 16          // log₂(2¹⁶)
		quotient := (uint32(b) * barrettMultiplier) >> barrettShift
		remainder := uint32(b) - quotient*5
		return fieldSubToMontgomery(2, remainder), true
	case 4:
		// Return z = 4 − b, which maps from
		//
		//   b = (  8,  7,  6,  5,  4,  3,  2,  1,  0 )
		//
		// to
		//
		//   z = ( −4, -3, -2, -1,  0,  1,  2,  3,  4 )
		//
		if b > 8 {
			return 0, false
		}
		return fieldSubToMontgomery(4, uint32(b)), true
	default:
		panic("internal error: unsupported η")
	}
}

// power2Round implements Power2Round from FIPS 204.
//
// It separates the bottom d = 13 bits of each 23-bit coefficient, rounding the
// high part based on the low part, and correcting the low part accordingly.
func power2Round(r fieldElement) (hi uint16, lo fieldElement) {
	rr := fieldFromMontgomery(r)
	// Add 2¹² - 1 to round up r1 by one if r0 > 2¹².
	// r is at most 2²³ - 2¹³ + 1, so rr + (2¹² - 1) won't overflow 23 bits.
	r1 := rr + 1<<12 - 1
	r1 >>= 13
	// r1 <= 2¹⁰ - 1
	// r1 * 2¹³ <= (2¹⁰ - 1) * 2¹³ = 2²³ - 2¹³ < q
	r0 := fieldSubToMontgomery(rr, r1<<13)
	return uint16(r1), r0
}

// highBits implements HighBits from FIPS 204.
func highBits(r ringElement, p parameters) [n]byte {
	var w [n]byte
	switch p.γ2 {
	case 32:
		for i := range n {
			w[i] = highBits32(fieldFromMontgomery(r[i]))
		}
	case 88:
		for i := range n {
			w[i] = highBits88(fieldFromMontgomery(r[i]))
		}
	default:
		panic("mldsa: internal error: unsupported γ2")
	}
	return w
}

// useHint implements UseHint from FIPS 204.
//
// It is not constant-time.
func useHint(r ringElement, h [n]byte, p parameters) [n]byte {
	var w [n]byte
	switch p.γ2 {
	case 32:
		for i := range n {
			w[i] = useHint32(r[i], h[i])
		}
	case 88:
		for i := range n {
			w[i] = useHint88(r[i], h[i])
		}
	default:
		panic("mldsa: internal error: unsupported γ2")
	}
	return w
}

// makeHint implements MakeHint from FIPS 204.
func makeHint(ct0, w, cs2 ringElement, p parameters) (h [n]byte, count1s int) {
	switch p.γ2 {
	case 32:
		for i := range n {
			h[i] = makeHint32(ct0[i], w[i], cs2[i])
			count1s += int(h[i])
		}
	case 88:
		for i := range n {
			h[i] = makeHint88(ct0[i], w[i], cs2[i])
			count1s += int(h[i])
		}
	default:
		panic("mldsa: internal error: unsupported γ2")
	}
	return h, count1s
}

// highBits32 implements HighBits from FIPS 204 for γ2 = (q - 1) / 32.
func highBits32(x uint32) byte {
	// The implementation is based on the reference implementation and on
	// BoringSSL. There are exhaustive tests in TestDecompose that compare it to
	// a straightforward implementation of Decompose from the spec, so for our
	// purposes it only has to work and be constant-time.
	r1 := (x + 127) >> 7
	r1 = (r1*1025 + (1 << 21)) >> 22
	r1 &= 0b1111
	return byte(r1)
}

// decompose32 implements Decompose from FIPS 204 for γ2 = (q - 1) / 32.
//
// r1 is in [0, 15].
func decompose32(r fieldElement) (r1 byte, r0 int32) {
	x := fieldFromMontgomery(r)
	r1 = highBits32(x)

	// r - r1 * (2 * γ2) mod± q
	r0 = int32(x) - int32(r1)*2*(q-1)/32
	r0 = constantTimeSelectLessOrEqual(q/2+1, r0, r0-q, r0)

	return r1, r0
}

// useHint32 implements UseHint from FIPS 204 for γ2 = (q - 1) / 32.
func useHint32(r fieldElement, hint byte) byte {
	const m = 16 // (q − 1) / (2 * γ2)
	r1, r0 := decompose32(r)
	if hint == 1 {
		if r0 > 0 {
			r1 = (r1 + 1) % m
		} else {
			// Underflow is safe, because it operates modulo 256 (since the type
			// is byte), which is a multiple of m.
			r1 = (r1 - 1) % m
		}
	}
	return r1
}

// makeHint32 implements MakeHint from FIPS 204 for γ2 = (q - 1) / 32.
func makeHint32(ct0, w, cs2 fieldElement) byte {
	// v1 = HighBits(r + z) = HighBits(w - cs2 + ct0 - ct0) = HighBits(w - cs2)
	rPlusZ := fieldSub(w, cs2)
	v1 := highBits32(fieldFromMontgomery(rPlusZ))
	// r1 = HighBits(r) = HighBits(w - cs2 + ct0)
	r1 := highBits32(fieldFromMontgomery(fieldAdd(rPlusZ, ct0)))

	return byte(constanttime.ByteEq(v1, r1) ^ 1)
}

// highBits88 implements HighBits from FIPS 204 for γ2 = (q - 1) / 88.
func highBits88(x uint32) byte {
	// Like highBits32, this is exhaustively tested in TestDecompose.
	r1 := (x + 127) >> 7
	r1 = (r1*11275 + (1 << 23)) >> 24
	r1 = constantTimeSelectEqual(r1, 44, 0, r1)
	return byte(r1)
}

// decompose88 implements Decompose from FIPS 204 for γ2 = (q - 1) / 88.
//
// r1 is in [0, 43].
func decompose88(r fieldElement) (r1 byte, r0 int32) {
	x := fieldFromMontgomery(r)
	r1 = highBits88(x)

	// r - r1 * (2 * γ2) mod± q
	r0 = int32(x) - int32(r1)*2*(q-1)/88
	r0 = constantTimeSelectLessOrEqual(q/2+1, r0, r0-q, r0)

	return r1, r0
}

// useHint88 implements UseHint from FIPS 204 for γ2 = (q - 1) / 88.
func useHint88(r fieldElement, hint byte) byte {
	const m = 44 // (q − 1) / (2 * γ2)
	r1, r0 := decompose88(r)
	if hint == 1 {
		if r0 > 0 {
			// (r1 + 1) mod m, for r1 in [0, m-1]
			if r1 == m-1 {
				r1 = 0
			} else {
				r1++
			}
		} else {
			// (r1 - 1) % m, for r1 in [0, m-1]
			if r1 == 0 {
				r1 = m - 1
			} else {
				r1--
			}
		}
	}
	return r1
}

// makeHint88 implements MakeHint from FIPS 204 for γ2 = (q - 1) / 88.
func makeHint88(ct0, w, cs2 fieldElement) byte {
	// Same as makeHint32 above.
	rPlusZ := fieldSub(w, cs2)
	v1 := highBits88(fieldFromMontgomery(rPlusZ))
	r1 := highBits88(fieldFromMontgomery(fieldAdd(rPlusZ, ct0)))
	return byte(constanttime.ByteEq(v1, r1) ^ 1)
}

// bitPack implements BitPack(r mod± q, γ₁-1, γ₁), which packs the centered
// coefficients of r into little-endian γ1+1-bit chunks. It appends to buf.
//
// It must only be applied to r with coefficients in [−γ₁+1, γ₁], as
// guaranteed by the rejection conditions in Sign.
func bitPack(b []byte, r ringElement, p parameters) []byte {
	switch p.γ1 {
	case 17:
		return bitPack18(b, r)
	case 19:
		return bitPack20(b, r)
	default:
		panic("mldsa: internal error: unsupported γ1")
	}
}

// bitPack18 implements BitPack(r mod± q, 2¹⁷-1, 2¹⁷), which packs the centered
// coefficients of r into little-endian 18-bit chunks. It appends to buf.
//
// It must only be applied to r with coefficients in [−2¹⁷+1, 2¹⁷], as
// guaranteed by the rejection conditions in Sign.
func bitPack18(buf []byte, r ringElement) []byte {
	out, v := sliceForAppend(buf, 18*n/8)
	const b = 1 << 17
	for i := 0; i < n; i += 4 {
		// b - [−2¹⁷+1, 2¹⁷] = [0, 2²⁸-1]
		w0 := b - fieldCenteredMod(r[i])
		v[0] = byte(w0 << 0)
		v[1] = byte(w0 >> 8)
		v[2] = byte(w0 >> 16)
		w1 := b - fieldCenteredMod(r[i+1])
		v[2] |= byte(w1 << 2)
		v[3] = byte(w1 >> 6)
		v[4] = byte(w1 >> 14)
		w2 := b - fieldCenteredMod(r[i+2])
		v[4] |= byte(w2 << 4)
		v[5] = byte(w2 >> 4)
		v[6] = byte(w2 >> 12)
		w3 := b - fieldCenteredMod(r[i+3])
		v[6] |= byte(w3 << 6)
		v[7] = byte(w3 >> 2)
		v[8] = byte(w3 >> 10)
		v = v[4*18/8:]
	}
	return out
}

// bitPack20 implements BitPack(r mod± q, 2¹⁹-1, 2¹⁹), which packs the centered
// coefficients of r into little-endian 20-bit chunks. It appends to buf.
//
// It must only be applied to r with coefficients in [−2¹⁹+1, 2¹⁹], as
// guaranteed by the rejection conditions in Sign.
func bitPack20(buf []byte, r ringElement) []byte {
	out, v := sliceForAppend(buf, 20*n/8)
	const b = 1 << 19
	for i := 0; i < n; i += 2 {
		// b - [−2¹⁹+1, 2¹⁹] = [0, 2²⁰-1]
		w0 := b - fieldCenteredMod(r[i])
		v[0] = byte(w0 << 0)
		v[1] = byte(w0 >> 8)
		v[2] = byte(w0 >> 16)
		w1 := b - fieldCenteredMod(r[i+1])
		v[2] |= byte(w1 << 4)
		v[3] = byte(w1 >> 4)
		v[4] = byte(w1 >> 12)
		v = v[2*20/8:]
	}
	return out
}

// bitUnpack implements BitUnpack(v, 2^γ1-1, 2^γ1), which unpacks each γ1+1 bits
// in little-endian into a coefficient in [-2^γ1+1, 2^γ1].
func bitUnpack(v []byte, p parameters) ringElement {
	switch p.γ1 {
	case 17:
		return bitUnpack18(v)
	case 19:
		return bitUnpack20(v)
	default:
		panic("mldsa: internal error: unsupported γ1")
	}
}

// bitUnpack18 implements BitUnpack(v, 2¹⁷-1, 2¹⁷), which unpacks each 18 bits
// in little-endian into a coefficient in [-2¹⁷+1, 2¹⁷].
func bitUnpack18(v []byte) ringElement {
	if len(v) != 18*n/8 {
		panic("mldsa: internal error: invalid bitUnpack18 input length")
	}
	const b = 1 << 17
	const mask18 = 1<<18 - 1
	var r ringElement
	for i := 0; i < n; i += 4 {
		w0 := uint32(v[0]) | uint32(v[1])<<8 | uint32(v[2])<<16
		r[i+0] = fieldSubToMontgomery(b, w0&mask18)
		w1 := uint32(v[2])>>2 | uint32(v[3])<<6 | uint32(v[4])<<14
		r[i+1] = fieldSubToMontgomery(b, w1&mask18)
		w2 := uint32(v[4])>>4 | uint32(v[5])<<4 | uint32(v[6])<<12
		r[i+2] = fieldSubToMontgomery(b, w2&mask18)
		w3 := uint32(v[6])>>6 | uint32(v[7])<<2 | uint32(v[8])<<10
		r[i+3] = fieldSubToMontgomery(b, w3&mask18)
		v = v[4*18/8:]
	}
	return r
}

// bitUnpack20 implements BitUnpack(v, 2¹⁹-1, 2¹⁹), which unpacks each 20 bits
// in little-endian into a coefficient in [-2¹⁹+1, 2¹⁹].
func bitUnpack20(v []byte) ringElement {
	if len(v) != 20*n/8 {
		panic("mldsa: internal error: invalid bitUnpack20 input length")
	}
	const b = 1 << 19
	const mask20 = 1<<20 - 1
	var r ringElement
	for i := 0; i < n; i += 2 {
		w0 := uint32(v[0]) | uint32(v[1])<<8 | uint32(v[2])<<16
		r[i+0] = fieldSubToMontgomery(b, w0&mask20)
		w1 := uint32(v[2])>>4 | uint32(v[3])<<4 | uint32(v[4])<<12
		r[i+1] = fieldSubToMontgomery(b, w1&mask20)
		v = v[2*20/8:]
	}
	return r
}

// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
	if total := len(in) + n; cap(in) >= total {
		head = in[:total]
	} else {
		head = make([]byte, total)
		copy(head, in)
	}
	tail = head[len(in):]
	return
}

// constantTimeSelectLessOrEqual returns yes if a <= b, no otherwise, in constant time.
func constantTimeSelectLessOrEqual(a, b, yes, no int32) int32 {
	return int32(constanttime.Select(constanttime.LessOrEq(int(a), int(b)), int(yes), int(no)))
}

// constantTimeSelectEqual returns yes if a == b, no otherwise, in constant time.
func constantTimeSelectEqual(a, b, yes, no uint32) uint32 {
	return uint32(constanttime.Select(constanttime.Eq(int32(a), int32(b)), int(yes), int(no)))
}

// constantTimeAbs returns the absolute value of x in constant time.
func constantTimeAbs(x int32) uint32 {
	return uint32(constantTimeSelectLessOrEqual(0, x, x, -x))
}
