// Copyright 2025 Sun Yimin. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package mldsa import ( "crypto/subtle" "errors" "github.com/emmansun/gmsm/internal/alias" ) // simpleBitPack10Bits encodes a polynomial f into a byte slice // // See FIPS 204, Algorithm 16, SimpleBitPack(w, b) where b = 10 bits // i.e. Use 10 bits from each coefficient and pack them into bytes // So every 4 coefficients (c0..c3) fit into 5 bytes. // // |c0||c1||c2||c3| // |\ |\ |\ |\ // |8|2 6|4 4|6 2|8| func simpleBitPack10Bits(s []byte, f ringElement) []byte { s, b := alias.SliceForAppend(s, encodingSize10) for i := 0; i < n; i += 4 { var x uint64 x |= uint64(f[i]) x |= uint64(f[i+1]) << 10 x |= uint64(f[i+2]) << 20 x |= uint64(f[i+3]) << 30 b[0] = uint8(x) b[1] = uint8(x >> 8) b[2] = uint8(x >> 16) b[3] = uint8(x >> 24) b[4] = uint8(x >> 32) b = b[5:] } return s } // simpleBitUnpack10Bits decodes a byte slice into a polynomial f // See FIPS 204, Algorithm 18, SimpleBitUnpack(w, b) where b = 10 bits func simpleBitUnpack10Bits(b []byte, f *ringElement) { const mask = 0x3FF for i := 0; i < n; i += 4 { x := uint64(b[0]) | (uint64(b[1]) << 8) | (uint64(b[2]) << 16) | (uint64(b[3]) << 24) | (uint64(b[4]) << 32) b = b[5:] f[i] = fieldElement(x & mask) f[i+1] = fieldElement((x >> 10) & mask) f[i+2] = fieldElement((x >> 20) & mask) f[i+3] = fieldElement((x >> 30) & mask) } } // simpleBitPack4Bits encodes a polynomial into a byte string, assuming that all coefficients are // in the range 0..15 (4 bits). // // See FIPS 204, Algorithm 16, SimpleBitPack(w, b) where b = 4 bits // // i.e. Use 4 bits from each coefficient and pack them into bytes // So every 2 coefficients fit into 1 byte. // // This is used to encode w1 when signing with ML-DSA-65 and ML-DSA-87 func simpleBitPack4Bits(s []byte, f ringElement) []byte { s, b := alias.SliceForAppend(s, encodingSize4) for i := 0; i < n; i += 2 { b[0] = uint8(f[i]) | (uint8(f[i+1]) << 4) b = b[1:] } return s } // simpleBitPack6Bits encodes a polynomial into a byte string, assuming that all coefficients are // in the range 0..43 (6 bits). // // See FIPS 204, Algorithm 16, SimpleBitPack(w, b) where b = 43 // // i.e. Use 6 bits from each coefficient and pack them into bytes // So every 4 coefficients fit into 3 bytes. // // |c0||c1||c2||c3| // | /| /\ / // |6 2|4 4|2 6| // // This is used to encode w1 when signing with ML-DSA-44 func simpleBitPack6Bits(s []byte, f ringElement) []byte { s, b := alias.SliceForAppend(s, encodingSize6) for i := 0; i < n; i += 4 { var x uint64 x = uint64(f[i]) x |= uint64(f[i+1]) << 6 x |= uint64(f[i+2]) << 12 x |= uint64(f[i+3]) << 18 b[0] = uint8(x) b[1] = uint8(x >> 8) b[2] = uint8(x >> 16) b = b[3:] } return s } // bitPackSigned2 encodes a polynomial f into a byte slice, assuming that all // coefficients are in the range -2..2. // See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = b = 2. // // This is used to encode the private key polynomial elements of s1 and s2 // for ML-DSA-44 and ML-DSA-87 (i.e. eta = 2) // Use 3 bits from each coefficient and pack them into bytes // So every 8 coefficients fit into 3 bytes. // // |c0 c1 c2 c3 c4 c5 c6 c7| // | / / | | / / | | / // |3 3 2| 1 3 3 1| 2 3 3| func bitPackSigned2(s []byte, f ringElement) []byte { s, b := alias.SliceForAppend(s, encodingSize3) for i := 0; i < n; i += 8 { var x uint32 x |= uint32(fieldSub(2, f[i])) x |= uint32(fieldSub(2, f[i+1])) << 3 x |= uint32(fieldSub(2, f[i+2])) << 6 x |= uint32(fieldSub(2, f[i+3])) << 9 x |= uint32(fieldSub(2, f[i+4])) << 12 x |= uint32(fieldSub(2, f[i+5])) << 15 x |= uint32(fieldSub(2, f[i+6])) << 18 x |= uint32(fieldSub(2, f[i+7])) << 21 b[0] = uint8(x) b[1] = uint8(x >> 8) b[2] = uint8(x >> 16) b = b[3:] } return s } // bitUnpackSigned2 decodes a byte slice into a polynomial f // See FIPS 204, Algorithm 19, BitUnpack(w, a, b). where a = b = 2. func bitUnpackSigned2(b []byte) (ringElement, error) { const bitsMask = 0x7 var f ringElement for i := 0; i < n; i += 8 { x := uint32(b[0]) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) msbs := x & 0o44444444 mask := (msbs >> 1) | (msbs >> 2) if subtle.ConstantTimeEq(int32(mask&x), 0) == 0 { return ringElement{}, errors.New("mldsa: invalid encoding") } b = b[3:] f[i] = fieldSub(2, fieldElement(x&bitsMask)) f[i+1] = fieldSub(2, fieldElement((x>>3)&bitsMask)) f[i+2] = fieldSub(2, fieldElement((x>>6)&bitsMask)) f[i+3] = fieldSub(2, fieldElement((x>>9)&bitsMask)) f[i+4] = fieldSub(2, fieldElement((x>>12)&bitsMask)) f[i+5] = fieldSub(2, fieldElement((x>>15)&bitsMask)) f[i+6] = fieldSub(2, fieldElement((x>>18)&bitsMask)) f[i+7] = fieldSub(2, fieldElement((x>>21)&bitsMask)) } return f, nil } // bitPackSigned4 encodes a polynomial into a byte string, assuming that all // coefficients are in the range -4..4. // See FIPS 204, Algorithm 17, BitPack(w, a, b). (a = 4, b = 4) // // It uses a nibble from each coefficient and packs them into bytes // So every 2 coefficients fit into 1 byte. // // This is used to encode the private key polynomial elements of s1 and s2 // for ML-DSA-65 (i.e. eta = 4) func bitPackSigned4(s []byte, f ringElement) []byte { s, b := alias.SliceForAppend(s, encodingSize4) for i := 0; i < n; i += 2 { b[0] = uint8(fieldSub(4, f[i])) | (uint8(fieldSub(4, f[i+1])) << 4) b = b[1:] } return s } // bitUnpackSigned4 reverses the procedure of bitPackSigned4(). // See FIPS 204, Algorithm 19, BitUnpack(v, a, b) where a = b = 4. func bitUnpackSigned4(b []byte) (ringElement, error) { const bitsMask = 0xF var f ringElement for i := 0; i < n; i += 8 { x := uint32(b[0]) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24) // None of the nibbles may be >= 9. So if the MSB of any nibble is set, // none of the other bits may be set. First, select all the MSBs. msbs := x & 0x88888888 // For each nibble where the MSB is set, form a mask of all the other bits. mask := (msbs >> 1) | (msbs >> 2) | (msbs >> 3) if subtle.ConstantTimeEq(int32(mask&x), 0) == 0 { return ringElement{}, errors.New("mldsa: invalid encoding") } b = b[4:] f[i] = fieldSub(4, fieldElement(x&bitsMask)) f[i+1] = fieldSub(4, fieldElement((x>>4)&bitsMask)) f[i+2] = fieldSub(4, fieldElement((x>>8)&bitsMask)) f[i+3] = fieldSub(4, fieldElement((x>>12)&bitsMask)) f[i+4] = fieldSub(4, fieldElement((x>>16)&bitsMask)) f[i+5] = fieldSub(4, fieldElement((x>>20)&bitsMask)) f[i+6] = fieldSub(4, fieldElement((x>>24)&bitsMask)) f[i+7] = fieldSub(4, fieldElement((x>>28)&bitsMask)) } return f, nil } // bitPackSigned4196 encodes a polynomial f into a byte slice, assuming that all // coefficients are in the range (-2^12 + 1)..2^12. // See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = 2^12 - 1, b = 2^12. // // This is used to encode the LSB of the public key polynomial elements of t0 // which are encoded as part of the encoded private key. // // The code below packs them into 2 64 bits blocks by doing.. // // z0 z1 z2 z3 z4 z5 z6 z7 0 // | | | | / \ | | | | // // |13 13 13 13 12 |1 13 13 13 24 func bitPackSigned4096(s []byte, f ringElement) []byte { const r = 4096 // 2^12 s, b := alias.SliceForAppend(s, encodingSize13) for i := 0; i < n; i += 8 { var x1, x2, a uint64 x1 = uint64(fieldSub(r, f[i])) x1 |= uint64(fieldSub(r, f[i+1])) << 13 x1 |= uint64(fieldSub(r, f[i+2])) << 26 x1 |= uint64(fieldSub(r, f[i+3])) << 39 a = uint64(fieldSub(r, f[i+4])) x1 |= a << 52 x2 = a >> 12 x2 |= uint64(fieldSub(r, f[i+5])) << 1 x2 |= uint64(fieldSub(r, f[i+6])) << 14 x2 |= uint64(fieldSub(r, f[i+7])) << 27 b[0] = uint8(x1) b[1] = uint8(x1 >> 8) b[2] = uint8(x1 >> 16) b[3] = uint8(x1 >> 24) b[4] = uint8(x1 >> 32) b[5] = uint8(x1 >> 40) b[6] = uint8(x1 >> 48) b[7] = uint8(x1 >> 56) b[8] = uint8(x2) b[9] = uint8(x2 >> 8) b[10] = uint8(x2 >> 16) b[11] = uint8(x2 >> 24) b[12] = uint8(x2 >> 32) b = b[13:] } return s } // bitUnpackSigned4096 decodes a byte slice into a polynomial f // See FIPS 204, Algorithm 19, BitUnpack(w, a, b). where a = 2^12 - 1, b = 2^12. func bitUnpackSigned4096(b []byte, f *ringElement) error { const bitsMask = 0x1FFF // 2^13-1 const r = 4096 // 2^12 for i := 0; i < n; i += 8 { x1 := uint64(b[0]) | (uint64(b[1]) << 8) | (uint64(b[2]) << 16) | (uint64(b[3]) << 24) | (uint64(b[4]) << 32) | (uint64(b[5]) << 40) | (uint64(b[6]) << 48) | (uint64(b[7]) << 56) x2 := uint64(b[8]) | (uint64(b[9]) << 8) | (uint64(b[10]) << 16) | (uint64(b[11]) << 24) | (uint64(b[12]) << 32) b = b[13:] f[i] = fieldSub(r, fieldElement(x1&bitsMask)) f[i+1] = fieldSub(r, fieldElement((x1>>13)&bitsMask)) f[i+2] = fieldSub(r, fieldElement((x1>>26)&bitsMask)) f[i+3] = fieldSub(r, fieldElement((x1>>39)&bitsMask)) f[i+4] = fieldSub(r, fieldElement((x1>>52 | (x2 << 12 & bitsMask)))) f[i+5] = fieldSub(r, fieldElement((x2>>1)&bitsMask)) f[i+6] = fieldSub(r, fieldElement((x2>>14)&bitsMask)) f[i+7] = fieldSub(r, fieldElement((x2>>27)&bitsMask)) } return nil } // bitPackSignedTwoPower17 encodes a polynomial into a byte string, assuming that all // coefficients are in the range (-2^17 + 1)..2^17. // See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = 2^17 - 1, b = 2^17. // // This is used to encode signatures for ML-DSA-44 (where gamma1 = 2^17) // // # Use 18 bits from each coefficient and pack them into bytes // // The code below packs every 4 (18 bit) coefficients into 9 bytes // // z0 z1 z2 z3 // | |\ | | \ // // |18 14|4 18 10| 8 func bitPackSignedTwoPower17(s []byte, f ringElement) []byte { const r = 131072 // 2^17 s, b := alias.SliceForAppend(s, encodingSize18) for i := 0; i < n; i += 4 { var x1, x2 uint64 x1 = uint64(fieldSub(r, f[i])) x1 |= uint64(fieldSub(r, f[i+1])) << 18 x1 |= uint64(fieldSub(r, f[i+2])) << 36 x2 = uint64(fieldSub(r, f[i+3])) x1 |= x2 << 54 x2 >>= 10 b[0] = uint8(x1) b[1] = uint8(x1 >> 8) b[2] = uint8(x1 >> 16) b[3] = uint8(x1 >> 24) b[4] = uint8(x1 >> 32) b[5] = uint8(x1 >> 40) b[6] = uint8(x1 >> 48) b[7] = uint8(x1 >> 56) b[8] = uint8(x2) b = b[9:] } return s } // bitUnpackSignedTwoPower17 decodes a byte slice into a polynomial f // See FIPS 204, Algorithm 19, BitUnpack(w, a, b). where a = 2^17 - 1, b = 2^17. func bitUnpackSignedTwoPower17(b []byte, f *ringElement) { const bitsMask = 0x3FFFF // 2^18-1 const r = 131072 // 2^17 for i := 0; i < n; i += 4 { x1 := uint64(b[0]) | (uint64(b[1]) << 8) | (uint64(b[2]) << 16) | (uint64(b[3]) << 24) | (uint64(b[4]) << 32) | (uint64(b[5]) << 40) | (uint64(b[6]) << 48) | (uint64(b[7]) << 56) x2 := uint64(b[8]) b = b[9:] f[i] = fieldSub(r, fieldElement(x1&bitsMask)) f[i+1] = fieldSub(r, fieldElement((x1>>18)&bitsMask)) f[i+2] = fieldSub(r, fieldElement((x1>>36)&bitsMask)) f[i+3] = fieldSub(r, fieldElement((x1>>54 | (x2 << 10 & bitsMask)))) } } // bitPackSignedTwoPower19 encodes a polynomial into a byte string, assuming that all // coefficients are in the range (-2^19 + 1)..2^19. // See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = 2^19 - 1, b = 2^19. // // This is used to encode signatures for ML-DSA-65 & ML-DSA-87 (gamma1 = 2^19) // // # Use 20 bits from each coefficient and pack them into bytes // // The code below packs every 4 (20 bit) coefficients into 10 bytes // // z0 z1 z2 z3 // | |\ | | \ // // |20 12|8 20 4|16 func bitPackSignedTwoPower19(s []byte, f ringElement) []byte { const r = 524288 // 2^19 s, b := alias.SliceForAppend(s, encodingSize20) for i := 0; i < n; i += 4 { var x1, x2 uint64 x1 = uint64(fieldSub(r, f[i])) x1 |= uint64(fieldSub(r, f[i+1])) << 20 x1 |= uint64(fieldSub(r, f[i+2])) << 40 x2 = uint64(fieldSub(r, f[i+3])) x1 |= x2 << 60 x2 >>= 4 b[0] = uint8(x1) b[1] = uint8(x1 >> 8) b[2] = uint8(x1 >> 16) b[3] = uint8(x1 >> 24) b[4] = uint8(x1 >> 32) b[5] = uint8(x1 >> 40) b[6] = uint8(x1 >> 48) b[7] = uint8(x1 >> 56) b[8] = uint8(x2) b[9] = uint8(x2 >> 8) b = b[10:] } return s } // bitUnpackSignedTwoPower19 decodes a byte slice into a polynomial f // See FIPS 204, Algorithm 19, BitUnpack(w, a, b). where a = 2^19 - 1, b = 2^19. // The coefficients are in the range (-2^19 + 1)..2^19 // and are represented as 20 bits. func bitUnpackSignedTwoPower19(b []byte, f *ringElement) { const bitsMask = 0xFFFFF // 2^20-1 const r = 524288 // 2^19 for i := 0; i < n; i += 4 { x1 := uint64(b[0]) | (uint64(b[1]) << 8) | (uint64(b[2]) << 16) | (uint64(b[3]) << 24) | (uint64(b[4]) << 32) | (uint64(b[5]) << 40) | (uint64(b[6]) << 48) | (uint64(b[7]) << 56) x2 := uint64(b[8]) | (uint64(b[9]) << 8) b = b[10:] f[i] = fieldSub(r, fieldElement(x1&bitsMask)) f[i+1] = fieldSub(r, fieldElement((x1>>20)&bitsMask)) f[i+2] = fieldSub(r, fieldElement((x1>>40)&bitsMask)) f[i+3] = fieldSub(r, fieldElement((x1>>60 | (x2 << 4 & bitsMask)))) } } // See FIPS 204, Algorithm 20, HintBitPack(). func hintBitPack(s []byte, hint []ringElement, omega int) []byte { k := len(hint) s, b := alias.SliceForAppend(s, omega+k) index := 0 for i := range k { for j := 0; j < n; j++ { if hint[i][j] != 0 { b[index] = byte(j) index++ } } b[omega+i] = byte(index) } return s } // See FIPS 204, Algorithm 21, HintBitUnpack(). func hintBitUnpack(b []byte, hint []ringElement, omega int) bool { k := len(hint) index := 0 first := 0 for i := range k { limit := int(b[omega+i]) if limit < index || limit > omega { return false } first = index for ; index < limit; index++ { bi := b[index] if index > first && b[index-1] >= bi { return false } hint[i][bi] = 1 } } for i := index; i < omega; i++ { if b[i] != 0 { return false } } return true }