blob: cfa78e4318b1eb2c401b90d74fc227bb33ac027e [file] [log] [blame] [edit]
// 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))
}