diff --git a/sign/dilithium/example_test.go b/sign/dilithium/example_test.go index 4f4de0d99..bf687b202 100644 --- a/sign/dilithium/example_test.go +++ b/sign/dilithium/example_test.go @@ -51,6 +51,6 @@ func Example() { fmt.Printf("O.K.") // Output: - // Supported modes: [Dilithium2 Dilithium2-AES Dilithium3 Dilithium3-AES Dilithium5 Dilithium5-AES] + // Supported modes: [Dilithium2 Dilithium2-AES Dilithium3 Dilithium3-AES Dilithium5 Dilithium5-AES ML-DSA-44 ML-DSA-65 ML-DSA-87] // O.K. } diff --git a/sign/dilithium/gen.go b/sign/dilithium/gen.go index c0d0df378..64d134714 100644 --- a/sign/dilithium/gen.go +++ b/sign/dilithium/gen.go @@ -14,12 +14,11 @@ import ( "strings" "text/template" - "github.com/cloudflare/circl/sign/dilithium/internal/common/params" + "github.com/cloudflare/circl/sign/internal/dilithium/params" ) type Mode struct { Name string - UseAES bool K int L int Eta int @@ -28,26 +27,47 @@ type Mode struct { Tau int Gamma1Bits int Gamma2 int + TRSize int + CTildeSize int } func (m Mode) Pkg() string { return strings.ToLower(m.Mode()) } +func (m Mode) PkgPath() string { + if m.NIST() { + return path.Join("..", "mldsa", m.Pkg()) + } + + return m.Pkg() +} + func (m Mode) Impl() string { return "impl" + m.Mode() } func (m Mode) Mode() string { + if m.NIST() { + return strings.ReplaceAll(m.Name, "-", "") + } + return strings.ReplaceAll(strings.ReplaceAll(m.Name, "Dilithium", "Mode"), "-AES", "AES") } +func (m Mode) UseAES() bool { + return strings.HasSuffix(m.Name, "-AES") +} + +func (m Mode) NIST() bool { + return strings.HasPrefix(m.Name, "ML-DSA-") +} + var ( Modes = []Mode{ { Name: "Dilithium2", - UseAES: false, K: 4, L: 4, Eta: 2, @@ -56,10 +76,11 @@ var ( Tau: 39, Gamma1Bits: 17, Gamma2: (params.Q - 1) / 88, + TRSize: 32, + CTildeSize: 32, }, { Name: "Dilithium2-AES", - UseAES: true, K: 4, L: 4, Eta: 2, @@ -68,10 +89,11 @@ var ( Tau: 39, Gamma1Bits: 17, Gamma2: (params.Q - 1) / 88, + TRSize: 32, + CTildeSize: 32, }, { Name: "Dilithium3", - UseAES: false, K: 6, L: 5, Eta: 4, @@ -80,10 +102,11 @@ var ( Tau: 49, Gamma1Bits: 19, Gamma2: (params.Q - 1) / 32, + TRSize: 32, + CTildeSize: 32, }, { Name: "Dilithium3-AES", - UseAES: true, K: 6, L: 5, Eta: 4, @@ -92,10 +115,11 @@ var ( Tau: 49, Gamma1Bits: 19, Gamma2: (params.Q - 1) / 32, + TRSize: 32, + CTildeSize: 32, }, { Name: "Dilithium5", - UseAES: false, K: 8, L: 7, Eta: 2, @@ -104,10 +128,11 @@ var ( Tau: 60, Gamma1Bits: 19, Gamma2: (params.Q - 1) / 32, + TRSize: 32, + CTildeSize: 32, }, { Name: "Dilithium5-AES", - UseAES: true, K: 8, L: 7, Eta: 2, @@ -116,6 +141,47 @@ var ( Tau: 60, Gamma1Bits: 19, Gamma2: (params.Q - 1) / 32, + TRSize: 32, + CTildeSize: 32, + }, + { + Name: "ML-DSA-44", + K: 4, + L: 4, + Eta: 2, + DoubleEtaBits: 3, + Omega: 80, + Tau: 39, + Gamma1Bits: 17, + Gamma2: (params.Q - 1) / 88, + TRSize: 64, + CTildeSize: 32, + }, + { + Name: "ML-DSA-65", + K: 6, + L: 5, + Eta: 4, + DoubleEtaBits: 4, + Omega: 55, + Tau: 49, + Gamma1Bits: 19, + Gamma2: (params.Q - 1) / 32, + TRSize: 64, + CTildeSize: 48, + }, + { + Name: "ML-DSA-87", + K: 8, + L: 7, + Eta: 2, + DoubleEtaBits: 3, + Omega: 75, + Tau: 60, + Gamma1Bits: 19, + Gamma2: (params.Q - 1) / 32, + TRSize: 64, + CTildeSize: 64, }, } TemplateWarning = "// Code generated from" @@ -153,7 +219,7 @@ func generateParamsFiles() { if offset == -1 { panic("Missing template warning in params.templ.go") } - err = os.WriteFile(mode.Pkg()+"/internal/params.go", + err = os.WriteFile(mode.PkgPath()+"/internal/params.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) @@ -206,7 +272,7 @@ func generateModePackageFiles() { if offset == -1 { panic("Missing template warning in modePkg.templ.go") } - err = os.WriteFile(mode.Pkg()+"/dilithium.go", []byte(res[offset:]), 0o644) + err = os.WriteFile(mode.PkgPath()+"/dilithium.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) } @@ -246,10 +312,10 @@ func generateSourceFiles() { continue } - fs, err = os.ReadDir(path.Join(mode.Pkg(), "internal")) + fs, err = os.ReadDir(path.Join(mode.PkgPath(), "internal")) for _, f := range fs { name := f.Name() - fn := path.Join(mode.Pkg(), "internal", name) + fn := path.Join(mode.PkgPath(), "internal", name) if ignored(name) { continue } @@ -273,7 +339,7 @@ func generateSourceFiles() { } } for name, expected := range files { - fn := path.Join(mode.Pkg(), "internal", name) + fn := path.Join(mode.PkgPath(), "internal", name) expected = []byte(fmt.Sprintf( "%s mode3/internal/%s by gen.go\n\n%s", TemplateWarning, diff --git a/sign/dilithium/kat_test.go b/sign/dilithium/kat_test.go index 2280e988d..dae3690ac 100644 --- a/sign/dilithium/kat_test.go +++ b/sign/dilithium/kat_test.go @@ -6,24 +6,32 @@ package dilithium import ( "crypto/sha256" "fmt" + "strings" "testing" "github.com/cloudflare/circl/internal/nist" ) func TestPQCgenKATSign(t *testing.T) { - // Generated from reference implementation commit 61b51a71701b8ae9f546a1e5, - // which can be found at https://github.com/pq-crystals/dilithium for _, tc := range []struct { name string want string }{ + // Generated from reference implementation commit 61b51a71701b8ae9f546a1e5, + // which can be found at https://github.com/pq-crystals/dilithium {"Dilithium2", "38ed991c5ca11e39ab23945ca37af89e059d16c5474bf8ba96b15cb4e948af2a"}, {"Dilithium3", "8196b32212753f525346201ffec1c7a0a852596fa0b57bd4e2746231dab44d55"}, {"Dilithium5", "7ded97a6e6c809b43b54c248171d7504fa6a0cab651bf288bb00034782667481"}, {"Dilithium2-AES", "b6673f8da5bba7dfae63adbbdf559f4fcfb715d1f91da98d4b52e26203d69196"}, {"Dilithium3-AES", "482f4d672a9f1dc38cc8bcf8b1731b03fe99fcb6f2b73aa4a376b99faf89ccbe"}, {"Dilithium5-AES", "54dfa85013d1b3da4f1d7c6dd270bc91a083cfece3d320c97906da125fd2a48f"}, + + // Generated from reference implementation commit e7bed6258b9a3703ce78d4ec3, + // which can be found on the standard branch + // of https://github.com/pq-crystals/dilithium + {"ML-DSA-44", "4657f244d1204e5847b3cacea4fc6116579571bee8ac89b8cba6771f303ee260"}, + {"ML-DSA-65", "99a95d7ef804020a666f455c5003232d0c0200dfc4f5df85dceb8f56256dcba8"}, + {"ML-DSA-87", "3377835fffb7cf9aac52947225c8974335bc05532ddf672a8b706ab8991435a2"}, } { t.Run(tc.name, func(t *testing.T) { mode := ModeByName(tc.name) @@ -38,7 +46,18 @@ func TestPQCgenKATSign(t *testing.T) { } f := sha256.New() g := nist.NewDRBG(&seed) - fmt.Fprintf(f, "# %s\n\n", tc.name) + nameInKat := tc.name + if !strings.HasPrefix(tc.name, "Dilithium") { + switch tc.name { + case "ML-DSA-44": + nameInKat = "Dilithium2" + case "ML-DSA-65": + nameInKat = "Dilithium3" + case "ML-DSA-87": + nameInKat = "Dilithium5" + } + } + fmt.Fprintf(f, "# %s\n\n", nameInKat) for i := 0; i < 100; i++ { mlen := 33 * (i + 1) g.Fill(seed[:]) diff --git a/sign/dilithium/mldsa44.go b/sign/dilithium/mldsa44.go new file mode 100644 index 000000000..1cbaf0174 --- /dev/null +++ b/sign/dilithium/mldsa44.go @@ -0,0 +1,91 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package dilithium + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mldsa/mldsa44" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// implMLDSA44 implements the mode.Mode interface for ML-DSA-44. +type implMLDSA44 struct{} + +// MLDSA44 is ML-DSA-44. +var MLDSA44 Mode = &implMLDSA44{} + +func (m *implMLDSA44) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mldsa44.GenerateKey(rand) +} + +func (m *implMLDSA44) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != common.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", common.SeedSize)) + } + seedBuf := [common.SeedSize]byte{} + copy(seedBuf[:], seed) + return mldsa44.NewKeyFromSeed(&seedBuf) +} + +func (m *implMLDSA44) Sign(sk PrivateKey, msg []byte) []byte { + isk := sk.(*mldsa44.PrivateKey) + ret := [mldsa44.SignatureSize]byte{} + mldsa44.SignTo(isk, msg, ret[:]) + return ret[:] +} + +func (m *implMLDSA44) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mldsa44.PublicKey) + return mldsa44.Verify(ipk, msg, signature) +} + +func (m *implMLDSA44) PublicKeyFromBytes(data []byte) PublicKey { + var ret mldsa44.PublicKey + if len(data) != mldsa44.PublicKeySize { + panic("packed public key must be of mldsa44.PublicKeySize bytes") + } + var buf [mldsa44.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA44) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mldsa44.PrivateKey + if len(data) != mldsa44.PrivateKeySize { + panic("packed public key must be of mldsa44.PrivateKeySize bytes") + } + var buf [mldsa44.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA44) SeedSize() int { + return common.SeedSize +} + +func (m *implMLDSA44) PublicKeySize() int { + return mldsa44.PublicKeySize +} + +func (m *implMLDSA44) PrivateKeySize() int { + return mldsa44.PrivateKeySize +} + +func (m *implMLDSA44) SignatureSize() int { + return mldsa44.SignatureSize +} + +func (m *implMLDSA44) Name() string { + return "ML-DSA-44" +} + +func init() { + modes["ML-DSA-44"] = MLDSA44 +} diff --git a/sign/dilithium/mldsa65.go b/sign/dilithium/mldsa65.go new file mode 100644 index 000000000..8c39363e8 --- /dev/null +++ b/sign/dilithium/mldsa65.go @@ -0,0 +1,91 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package dilithium + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mldsa/mldsa65" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// implMLDSA65 implements the mode.Mode interface for ML-DSA-65. +type implMLDSA65 struct{} + +// MLDSA65 is ML-DSA-65. +var MLDSA65 Mode = &implMLDSA65{} + +func (m *implMLDSA65) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mldsa65.GenerateKey(rand) +} + +func (m *implMLDSA65) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != common.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", common.SeedSize)) + } + seedBuf := [common.SeedSize]byte{} + copy(seedBuf[:], seed) + return mldsa65.NewKeyFromSeed(&seedBuf) +} + +func (m *implMLDSA65) Sign(sk PrivateKey, msg []byte) []byte { + isk := sk.(*mldsa65.PrivateKey) + ret := [mldsa65.SignatureSize]byte{} + mldsa65.SignTo(isk, msg, ret[:]) + return ret[:] +} + +func (m *implMLDSA65) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mldsa65.PublicKey) + return mldsa65.Verify(ipk, msg, signature) +} + +func (m *implMLDSA65) PublicKeyFromBytes(data []byte) PublicKey { + var ret mldsa65.PublicKey + if len(data) != mldsa65.PublicKeySize { + panic("packed public key must be of mldsa65.PublicKeySize bytes") + } + var buf [mldsa65.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA65) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mldsa65.PrivateKey + if len(data) != mldsa65.PrivateKeySize { + panic("packed public key must be of mldsa65.PrivateKeySize bytes") + } + var buf [mldsa65.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA65) SeedSize() int { + return common.SeedSize +} + +func (m *implMLDSA65) PublicKeySize() int { + return mldsa65.PublicKeySize +} + +func (m *implMLDSA65) PrivateKeySize() int { + return mldsa65.PrivateKeySize +} + +func (m *implMLDSA65) SignatureSize() int { + return mldsa65.SignatureSize +} + +func (m *implMLDSA65) Name() string { + return "ML-DSA-65" +} + +func init() { + modes["ML-DSA-65"] = MLDSA65 +} diff --git a/sign/dilithium/mldsa87.go b/sign/dilithium/mldsa87.go new file mode 100644 index 000000000..bc125430b --- /dev/null +++ b/sign/dilithium/mldsa87.go @@ -0,0 +1,91 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package dilithium + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mldsa/mldsa87" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// implMLDSA87 implements the mode.Mode interface for ML-DSA-87. +type implMLDSA87 struct{} + +// MLDSA87 is ML-DSA-87. +var MLDSA87 Mode = &implMLDSA87{} + +func (m *implMLDSA87) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mldsa87.GenerateKey(rand) +} + +func (m *implMLDSA87) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != common.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", common.SeedSize)) + } + seedBuf := [common.SeedSize]byte{} + copy(seedBuf[:], seed) + return mldsa87.NewKeyFromSeed(&seedBuf) +} + +func (m *implMLDSA87) Sign(sk PrivateKey, msg []byte) []byte { + isk := sk.(*mldsa87.PrivateKey) + ret := [mldsa87.SignatureSize]byte{} + mldsa87.SignTo(isk, msg, ret[:]) + return ret[:] +} + +func (m *implMLDSA87) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mldsa87.PublicKey) + return mldsa87.Verify(ipk, msg, signature) +} + +func (m *implMLDSA87) PublicKeyFromBytes(data []byte) PublicKey { + var ret mldsa87.PublicKey + if len(data) != mldsa87.PublicKeySize { + panic("packed public key must be of mldsa87.PublicKeySize bytes") + } + var buf [mldsa87.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA87) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mldsa87.PrivateKey + if len(data) != mldsa87.PrivateKeySize { + panic("packed public key must be of mldsa87.PrivateKeySize bytes") + } + var buf [mldsa87.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMLDSA87) SeedSize() int { + return common.SeedSize +} + +func (m *implMLDSA87) PublicKeySize() int { + return mldsa87.PublicKeySize +} + +func (m *implMLDSA87) PrivateKeySize() int { + return mldsa87.PrivateKeySize +} + +func (m *implMLDSA87) SignatureSize() int { + return mldsa87.SignatureSize +} + +func (m *implMLDSA87) Name() string { + return "ML-DSA-87" +} + +func init() { + modes["ML-DSA-87"] = MLDSA87 +} diff --git a/sign/dilithium/mode2.go b/sign/dilithium/mode2.go index e88d090b0..7e27eff2c 100644 --- a/sign/dilithium/mode2.go +++ b/sign/dilithium/mode2.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode2" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode2 implements the mode.Mode interface for Dilithium2. diff --git a/sign/dilithium/mode2/dilithium.go b/sign/dilithium/mode2/dilithium.go index 13ac869a5..16ce78351 100644 --- a/sign/dilithium/mode2/dilithium.go +++ b/sign/dilithium/mode2/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode2 implements the CRYSTALS-Dilithium signature scheme Dilithium2 // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode2 import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode2/internal" + ) const ( diff --git a/sign/dilithium/mode2/internal/dilithium.go b/sign/dilithium/mode2/internal/dilithium.go index 79a17d504..7869d0475 100644 --- a/sign/dilithium/mode2/internal/dilithium.go +++ b/sign/dilithium/mode2/internal/dilithium.go @@ -8,7 +8,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -29,13 +29,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -49,7 +49,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -59,7 +59,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -71,14 +71,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -89,11 +89,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -115,7 +115,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -125,8 +125,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -138,8 +138,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -250,7 +250,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -279,7 +279,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -330,6 +330,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -368,7 +373,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode2/internal/dilithium_test.go b/sign/dilithium/mode2/internal/dilithium_test.go index ca347a831..825e7da08 100644 --- a/sign/dilithium/mode2/internal/dilithium_test.go +++ b/sign/dilithium/mode2/internal/dilithium_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode2/internal/mat.go b/sign/dilithium/mode2/internal/mat.go index cb473c149..ceaf634fa 100644 --- a/sign/dilithium/mode2/internal/mat.go +++ b/sign/dilithium/mode2/internal/mat.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode2/internal/pack.go b/sign/dilithium/mode2/internal/pack.go index 0285dfd8e..1854b4197 100644 --- a/sign/dilithium/mode2/internal/pack.go +++ b/sign/dilithium/mode2/internal/pack.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode2/internal/pack_test.go b/sign/dilithium/mode2/internal/pack_test.go index 24f568e4d..f952c6a09 100644 --- a/sign/dilithium/mode2/internal/pack_test.go +++ b/sign/dilithium/mode2/internal/pack_test.go @@ -5,7 +5,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode2/internal/params.go b/sign/dilithium/mode2/internal/params.go index b49db55b3..74810fbad 100644 --- a/sign/dilithium/mode2/internal/params.go +++ b/sign/dilithium/mode2/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 39 Gamma1Bits = 17 Gamma2 = 95232 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode2/internal/params_test.go b/sign/dilithium/mode2/internal/params_test.go index 6dbc04cb8..f1f1715b7 100644 --- a/sign/dilithium/mode2/internal/params_test.go +++ b/sign/dilithium/mode2/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode2/internal/rounding.go b/sign/dilithium/mode2/internal/rounding.go index 71360cb29..58123c090 100644 --- a/sign/dilithium/mode2/internal/rounding.go +++ b/sign/dilithium/mode2/internal/rounding.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode2/internal/rounding_test.go b/sign/dilithium/mode2/internal/rounding_test.go index 5824f2656..ad653ca3f 100644 --- a/sign/dilithium/mode2/internal/rounding_test.go +++ b/sign/dilithium/mode2/internal/rounding_test.go @@ -6,7 +6,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode2/internal/sample.go b/sign/dilithium/mode2/internal/sample.go index c47185ada..62c261332 100644 --- a/sign/dilithium/mode2/internal/sample.go +++ b/sign/dilithium/mode2/internal/sample.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -246,12 +246,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -260,7 +260,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -327,7 +327,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode2/internal/sample_test.go b/sign/dilithium/mode2/internal/sample_test.go index 1a0a4f72c..2059599eb 100644 --- a/sign/dilithium/mode2/internal/sample_test.go +++ b/sign/dilithium/mode2/internal/sample_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -155,7 +155,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -203,10 +203,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -219,7 +219,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -231,7 +231,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode2/internal/vec.go b/sign/dilithium/mode2/internal/vec.go index f52973e45..d07d3b245 100644 --- a/sign/dilithium/mode2/internal/vec.go +++ b/sign/dilithium/mode2/internal/vec.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/mode2aes.go b/sign/dilithium/mode2aes.go index e3c996d92..e973a70f6 100644 --- a/sign/dilithium/mode2aes.go +++ b/sign/dilithium/mode2aes.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode2aes" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode2AES implements the mode.Mode interface for Dilithium2-AES. diff --git a/sign/dilithium/mode2aes/dilithium.go b/sign/dilithium/mode2aes/dilithium.go index 0b44077bb..93e5058d5 100644 --- a/sign/dilithium/mode2aes/dilithium.go +++ b/sign/dilithium/mode2aes/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode2aes implements the CRYSTALS-Dilithium signature scheme Dilithium2-AES // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode2aes import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode2aes/internal" + ) const ( diff --git a/sign/dilithium/mode2aes/internal/dilithium.go b/sign/dilithium/mode2aes/internal/dilithium.go index 79a17d504..7869d0475 100644 --- a/sign/dilithium/mode2aes/internal/dilithium.go +++ b/sign/dilithium/mode2aes/internal/dilithium.go @@ -8,7 +8,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -29,13 +29,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -49,7 +49,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -59,7 +59,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -71,14 +71,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -89,11 +89,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -115,7 +115,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -125,8 +125,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -138,8 +138,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -250,7 +250,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -279,7 +279,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -330,6 +330,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -368,7 +373,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode2aes/internal/dilithium_test.go b/sign/dilithium/mode2aes/internal/dilithium_test.go index ca347a831..825e7da08 100644 --- a/sign/dilithium/mode2aes/internal/dilithium_test.go +++ b/sign/dilithium/mode2aes/internal/dilithium_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode2aes/internal/mat.go b/sign/dilithium/mode2aes/internal/mat.go index cb473c149..ceaf634fa 100644 --- a/sign/dilithium/mode2aes/internal/mat.go +++ b/sign/dilithium/mode2aes/internal/mat.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode2aes/internal/pack.go b/sign/dilithium/mode2aes/internal/pack.go index 0285dfd8e..1854b4197 100644 --- a/sign/dilithium/mode2aes/internal/pack.go +++ b/sign/dilithium/mode2aes/internal/pack.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode2aes/internal/pack_test.go b/sign/dilithium/mode2aes/internal/pack_test.go index 24f568e4d..f952c6a09 100644 --- a/sign/dilithium/mode2aes/internal/pack_test.go +++ b/sign/dilithium/mode2aes/internal/pack_test.go @@ -5,7 +5,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode2aes/internal/params.go b/sign/dilithium/mode2aes/internal/params.go index 751ad149b..77305b51b 100644 --- a/sign/dilithium/mode2aes/internal/params.go +++ b/sign/dilithium/mode2aes/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 39 Gamma1Bits = 17 Gamma2 = 95232 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode2aes/internal/params_test.go b/sign/dilithium/mode2aes/internal/params_test.go index 50665d6a9..14d70d1a7 100644 --- a/sign/dilithium/mode2aes/internal/params_test.go +++ b/sign/dilithium/mode2aes/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode2aes/internal/rounding.go b/sign/dilithium/mode2aes/internal/rounding.go index 71360cb29..58123c090 100644 --- a/sign/dilithium/mode2aes/internal/rounding.go +++ b/sign/dilithium/mode2aes/internal/rounding.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode2aes/internal/rounding_test.go b/sign/dilithium/mode2aes/internal/rounding_test.go index 5824f2656..ad653ca3f 100644 --- a/sign/dilithium/mode2aes/internal/rounding_test.go +++ b/sign/dilithium/mode2aes/internal/rounding_test.go @@ -6,7 +6,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode2aes/internal/sample.go b/sign/dilithium/mode2aes/internal/sample.go index c47185ada..62c261332 100644 --- a/sign/dilithium/mode2aes/internal/sample.go +++ b/sign/dilithium/mode2aes/internal/sample.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -246,12 +246,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -260,7 +260,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -327,7 +327,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode2aes/internal/sample_test.go b/sign/dilithium/mode2aes/internal/sample_test.go index 1a0a4f72c..2059599eb 100644 --- a/sign/dilithium/mode2aes/internal/sample_test.go +++ b/sign/dilithium/mode2aes/internal/sample_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -155,7 +155,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -203,10 +203,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -219,7 +219,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -231,7 +231,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode2aes/internal/vec.go b/sign/dilithium/mode2aes/internal/vec.go index f52973e45..d07d3b245 100644 --- a/sign/dilithium/mode2aes/internal/vec.go +++ b/sign/dilithium/mode2aes/internal/vec.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/mode3.go b/sign/dilithium/mode3.go index fb45a29c2..147678612 100644 --- a/sign/dilithium/mode3.go +++ b/sign/dilithium/mode3.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode3" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode3 implements the mode.Mode interface for Dilithium3. diff --git a/sign/dilithium/mode3/dilithium.go b/sign/dilithium/mode3/dilithium.go index 3ab9d6b53..de98b5dd2 100644 --- a/sign/dilithium/mode3/dilithium.go +++ b/sign/dilithium/mode3/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode3 implements the CRYSTALS-Dilithium signature scheme Dilithium3 // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode3 import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode3/internal" + ) const ( diff --git a/sign/dilithium/mode3/internal/dilithium.go b/sign/dilithium/mode3/internal/dilithium.go index 59e43cc13..da4ec6262 100644 --- a/sign/dilithium/mode3/internal/dilithium.go +++ b/sign/dilithium/mode3/internal/dilithium.go @@ -6,7 +6,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -27,13 +27,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -47,7 +47,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -57,7 +57,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -69,14 +69,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -87,11 +87,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -113,7 +113,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -123,8 +123,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -136,8 +136,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -248,7 +248,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -277,7 +277,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -328,6 +328,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -366,7 +371,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode3/internal/dilithium_test.go b/sign/dilithium/mode3/internal/dilithium_test.go index 05320495d..9707772be 100644 --- a/sign/dilithium/mode3/internal/dilithium_test.go +++ b/sign/dilithium/mode3/internal/dilithium_test.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode3/internal/mat.go b/sign/dilithium/mode3/internal/mat.go index ce39b7f9a..3d6cc47b2 100644 --- a/sign/dilithium/mode3/internal/mat.go +++ b/sign/dilithium/mode3/internal/mat.go @@ -1,7 +1,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode3/internal/pack.go b/sign/dilithium/mode3/internal/pack.go index 6a152d73e..d9c80d0e1 100644 --- a/sign/dilithium/mode3/internal/pack.go +++ b/sign/dilithium/mode3/internal/pack.go @@ -1,7 +1,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode3/internal/pack_test.go b/sign/dilithium/mode3/internal/pack_test.go index 1b6b2a613..d8143c810 100644 --- a/sign/dilithium/mode3/internal/pack_test.go +++ b/sign/dilithium/mode3/internal/pack_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode3/internal/params.go b/sign/dilithium/mode3/internal/params.go index c22925234..77452958f 100644 --- a/sign/dilithium/mode3/internal/params.go +++ b/sign/dilithium/mode3/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 49 Gamma1Bits = 19 Gamma2 = 261888 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode3/internal/params_test.go b/sign/dilithium/mode3/internal/params_test.go index 43d91c3f4..f1c93e297 100644 --- a/sign/dilithium/mode3/internal/params_test.go +++ b/sign/dilithium/mode3/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode3/internal/rounding.go b/sign/dilithium/mode3/internal/rounding.go index f44c951d2..4005e569c 100644 --- a/sign/dilithium/mode3/internal/rounding.go +++ b/sign/dilithium/mode3/internal/rounding.go @@ -1,7 +1,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode3/internal/rounding_test.go b/sign/dilithium/mode3/internal/rounding_test.go index 387815592..8d2310cb2 100644 --- a/sign/dilithium/mode3/internal/rounding_test.go +++ b/sign/dilithium/mode3/internal/rounding_test.go @@ -4,7 +4,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode3/internal/sample.go b/sign/dilithium/mode3/internal/sample.go index 256ec731f..b2b0bbe13 100644 --- a/sign/dilithium/mode3/internal/sample.go +++ b/sign/dilithium/mode3/internal/sample.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -244,12 +244,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -258,7 +258,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -325,7 +325,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode3/internal/sample_test.go b/sign/dilithium/mode3/internal/sample_test.go index bae9d4055..1adfaf535 100644 --- a/sign/dilithium/mode3/internal/sample_test.go +++ b/sign/dilithium/mode3/internal/sample_test.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -153,7 +153,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -201,10 +201,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -217,7 +217,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -229,7 +229,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode3/internal/vec.go b/sign/dilithium/mode3/internal/vec.go index 1a5fe4ca5..5817acb7f 100644 --- a/sign/dilithium/mode3/internal/vec.go +++ b/sign/dilithium/mode3/internal/vec.go @@ -1,7 +1,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/mode3aes.go b/sign/dilithium/mode3aes.go index c4cca1863..bdbe19020 100644 --- a/sign/dilithium/mode3aes.go +++ b/sign/dilithium/mode3aes.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode3aes" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode3AES implements the mode.Mode interface for Dilithium3-AES. diff --git a/sign/dilithium/mode3aes/dilithium.go b/sign/dilithium/mode3aes/dilithium.go index afc5e3e92..0503f7673 100644 --- a/sign/dilithium/mode3aes/dilithium.go +++ b/sign/dilithium/mode3aes/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode3aes implements the CRYSTALS-Dilithium signature scheme Dilithium3-AES // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode3aes import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode3aes/internal" + ) const ( diff --git a/sign/dilithium/mode3aes/internal/dilithium.go b/sign/dilithium/mode3aes/internal/dilithium.go index 79a17d504..7869d0475 100644 --- a/sign/dilithium/mode3aes/internal/dilithium.go +++ b/sign/dilithium/mode3aes/internal/dilithium.go @@ -8,7 +8,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -29,13 +29,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -49,7 +49,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -59,7 +59,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -71,14 +71,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -89,11 +89,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -115,7 +115,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -125,8 +125,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -138,8 +138,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -250,7 +250,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -279,7 +279,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -330,6 +330,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -368,7 +373,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode3aes/internal/dilithium_test.go b/sign/dilithium/mode3aes/internal/dilithium_test.go index ca347a831..825e7da08 100644 --- a/sign/dilithium/mode3aes/internal/dilithium_test.go +++ b/sign/dilithium/mode3aes/internal/dilithium_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode3aes/internal/mat.go b/sign/dilithium/mode3aes/internal/mat.go index cb473c149..ceaf634fa 100644 --- a/sign/dilithium/mode3aes/internal/mat.go +++ b/sign/dilithium/mode3aes/internal/mat.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode3aes/internal/pack.go b/sign/dilithium/mode3aes/internal/pack.go index 0285dfd8e..1854b4197 100644 --- a/sign/dilithium/mode3aes/internal/pack.go +++ b/sign/dilithium/mode3aes/internal/pack.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode3aes/internal/pack_test.go b/sign/dilithium/mode3aes/internal/pack_test.go index 24f568e4d..f952c6a09 100644 --- a/sign/dilithium/mode3aes/internal/pack_test.go +++ b/sign/dilithium/mode3aes/internal/pack_test.go @@ -5,7 +5,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode3aes/internal/params.go b/sign/dilithium/mode3aes/internal/params.go index 7523dc09e..425cea3b3 100644 --- a/sign/dilithium/mode3aes/internal/params.go +++ b/sign/dilithium/mode3aes/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 49 Gamma1Bits = 19 Gamma2 = 261888 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode3aes/internal/params_test.go b/sign/dilithium/mode3aes/internal/params_test.go index fc246ce73..8e71ef60d 100644 --- a/sign/dilithium/mode3aes/internal/params_test.go +++ b/sign/dilithium/mode3aes/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode3aes/internal/rounding.go b/sign/dilithium/mode3aes/internal/rounding.go index 71360cb29..58123c090 100644 --- a/sign/dilithium/mode3aes/internal/rounding.go +++ b/sign/dilithium/mode3aes/internal/rounding.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode3aes/internal/rounding_test.go b/sign/dilithium/mode3aes/internal/rounding_test.go index 5824f2656..ad653ca3f 100644 --- a/sign/dilithium/mode3aes/internal/rounding_test.go +++ b/sign/dilithium/mode3aes/internal/rounding_test.go @@ -6,7 +6,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode3aes/internal/sample.go b/sign/dilithium/mode3aes/internal/sample.go index c47185ada..62c261332 100644 --- a/sign/dilithium/mode3aes/internal/sample.go +++ b/sign/dilithium/mode3aes/internal/sample.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -246,12 +246,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -260,7 +260,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -327,7 +327,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode3aes/internal/sample_test.go b/sign/dilithium/mode3aes/internal/sample_test.go index 1a0a4f72c..2059599eb 100644 --- a/sign/dilithium/mode3aes/internal/sample_test.go +++ b/sign/dilithium/mode3aes/internal/sample_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -155,7 +155,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -203,10 +203,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -219,7 +219,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -231,7 +231,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode3aes/internal/vec.go b/sign/dilithium/mode3aes/internal/vec.go index f52973e45..d07d3b245 100644 --- a/sign/dilithium/mode3aes/internal/vec.go +++ b/sign/dilithium/mode3aes/internal/vec.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/mode5.go b/sign/dilithium/mode5.go index d6b7a9c73..c9b55dd30 100644 --- a/sign/dilithium/mode5.go +++ b/sign/dilithium/mode5.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode5" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode5 implements the mode.Mode interface for Dilithium5. diff --git a/sign/dilithium/mode5/dilithium.go b/sign/dilithium/mode5/dilithium.go index 3a6a0cb93..7271ad17d 100644 --- a/sign/dilithium/mode5/dilithium.go +++ b/sign/dilithium/mode5/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode5 implements the CRYSTALS-Dilithium signature scheme Dilithium5 // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode5 import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode5/internal" + ) const ( diff --git a/sign/dilithium/mode5/internal/dilithium.go b/sign/dilithium/mode5/internal/dilithium.go index 79a17d504..7869d0475 100644 --- a/sign/dilithium/mode5/internal/dilithium.go +++ b/sign/dilithium/mode5/internal/dilithium.go @@ -8,7 +8,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -29,13 +29,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -49,7 +49,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -59,7 +59,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -71,14 +71,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -89,11 +89,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -115,7 +115,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -125,8 +125,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -138,8 +138,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -250,7 +250,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -279,7 +279,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -330,6 +330,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -368,7 +373,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode5/internal/dilithium_test.go b/sign/dilithium/mode5/internal/dilithium_test.go index ca347a831..825e7da08 100644 --- a/sign/dilithium/mode5/internal/dilithium_test.go +++ b/sign/dilithium/mode5/internal/dilithium_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode5/internal/mat.go b/sign/dilithium/mode5/internal/mat.go index cb473c149..ceaf634fa 100644 --- a/sign/dilithium/mode5/internal/mat.go +++ b/sign/dilithium/mode5/internal/mat.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode5/internal/pack.go b/sign/dilithium/mode5/internal/pack.go index 0285dfd8e..1854b4197 100644 --- a/sign/dilithium/mode5/internal/pack.go +++ b/sign/dilithium/mode5/internal/pack.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode5/internal/pack_test.go b/sign/dilithium/mode5/internal/pack_test.go index 24f568e4d..f952c6a09 100644 --- a/sign/dilithium/mode5/internal/pack_test.go +++ b/sign/dilithium/mode5/internal/pack_test.go @@ -5,7 +5,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode5/internal/params.go b/sign/dilithium/mode5/internal/params.go index bad43e320..e8a2b143e 100644 --- a/sign/dilithium/mode5/internal/params.go +++ b/sign/dilithium/mode5/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 60 Gamma1Bits = 19 Gamma2 = 261888 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode5/internal/params_test.go b/sign/dilithium/mode5/internal/params_test.go index daabe6969..ac891117e 100644 --- a/sign/dilithium/mode5/internal/params_test.go +++ b/sign/dilithium/mode5/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode5/internal/rounding.go b/sign/dilithium/mode5/internal/rounding.go index 71360cb29..58123c090 100644 --- a/sign/dilithium/mode5/internal/rounding.go +++ b/sign/dilithium/mode5/internal/rounding.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode5/internal/rounding_test.go b/sign/dilithium/mode5/internal/rounding_test.go index 5824f2656..ad653ca3f 100644 --- a/sign/dilithium/mode5/internal/rounding_test.go +++ b/sign/dilithium/mode5/internal/rounding_test.go @@ -6,7 +6,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode5/internal/sample.go b/sign/dilithium/mode5/internal/sample.go index c47185ada..62c261332 100644 --- a/sign/dilithium/mode5/internal/sample.go +++ b/sign/dilithium/mode5/internal/sample.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -246,12 +246,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -260,7 +260,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -327,7 +327,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode5/internal/sample_test.go b/sign/dilithium/mode5/internal/sample_test.go index 1a0a4f72c..2059599eb 100644 --- a/sign/dilithium/mode5/internal/sample_test.go +++ b/sign/dilithium/mode5/internal/sample_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -155,7 +155,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -203,10 +203,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -219,7 +219,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -231,7 +231,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode5/internal/vec.go b/sign/dilithium/mode5/internal/vec.go index f52973e45..d07d3b245 100644 --- a/sign/dilithium/mode5/internal/vec.go +++ b/sign/dilithium/mode5/internal/vec.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/mode5aes.go b/sign/dilithium/mode5aes.go index 258b8a825..0dfd3854f 100644 --- a/sign/dilithium/mode5aes.go +++ b/sign/dilithium/mode5aes.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" "github.com/cloudflare/circl/sign/dilithium/mode5aes" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // implMode5AES implements the mode.Mode interface for Dilithium5-AES. diff --git a/sign/dilithium/mode5aes/dilithium.go b/sign/dilithium/mode5aes/dilithium.go index 2a0f05e7a..c0300f2e6 100644 --- a/sign/dilithium/mode5aes/dilithium.go +++ b/sign/dilithium/mode5aes/dilithium.go @@ -1,9 +1,11 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. + // mode5aes implements the CRYSTALS-Dilithium signature scheme Dilithium5-AES // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + package mode5aes import ( @@ -11,8 +13,10 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/sign/dilithium/mode5aes/internal" + ) const ( diff --git a/sign/dilithium/mode5aes/internal/dilithium.go b/sign/dilithium/mode5aes/internal/dilithium.go index 79a17d504..7869d0475 100644 --- a/sign/dilithium/mode5aes/internal/dilithium.go +++ b/sign/dilithium/mode5aes/internal/dilithium.go @@ -8,7 +8,7 @@ import ( "io" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) const ( @@ -29,13 +29,13 @@ const ( Alpha = 2 * Gamma2 // Size of a packed private key - PrivateKeySize = 32 + 32 + 32 + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K // Size of a packed public key PublicKeySize = 32 + common.PolyT1Size*K // Size of a packed signature - SignatureSize = L*PolyLeGamma1Size + Omega + K + 32 + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize // Size of packed w₁ PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 @@ -49,7 +49,7 @@ type PublicKey struct { // Cached values t1p [common.PolyT1Size * K]byte A *Mat - tr *[32]byte + tr *[TRSize]byte } // PrivateKey is the type of Dilithium private keys. @@ -59,7 +59,7 @@ type PrivateKey struct { s1 VecL s2 VecK t0 VecK - tr [32]byte + tr [TRSize]byte // Cached values A Mat // ExpandA(ρ) @@ -71,14 +71,14 @@ type PrivateKey struct { type unpackedSignature struct { z VecL hint VecK - c [32]byte + c [CTildeSize]byte } // Packs the signature into buf. func (sig *unpackedSignature) Pack(buf []byte) { copy(buf[:], sig.c[:]) - sig.z.PackLeGamma1(buf[32:]) - sig.hint.PackHint(buf[32+L*PolyLeGamma1Size:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) } // Sets sig to the signature encoded in the buffer. @@ -89,11 +89,11 @@ func (sig *unpackedSignature) Unpack(buf []byte) bool { return false } copy(sig.c[:], buf[:]) - sig.z.UnpackLeGamma1(buf[32:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) if sig.z.Exceeds(Gamma1 - Beta) { return false } - if !sig.hint.UnpackHint(buf[32+L*PolyLeGamma1Size:]) { + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { return false } return true @@ -115,7 +115,7 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { pk.A.Derive(&pk.rho) // tr = CRH(ρ ‖ t1) = CRH(pk) - pk.tr = new([32]byte) + pk.tr = new([TRSize]byte) h := sha3.NewShake256() _, _ = h.Write(buf[:]) _, _ = h.Read(pk.tr[:]) @@ -125,8 +125,8 @@ func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { copy(buf[:32], sk.rho[:]) copy(buf[32:64], sk.key[:]) - copy(buf[64:96], sk.tr[:]) - offset := 96 + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize sk.s1.PackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.PackLeqEta(buf[offset:]) @@ -138,8 +138,8 @@ func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { copy(sk.rho[:], buf[:32]) copy(sk.key[:], buf[32:64]) - copy(sk.tr[:], buf[64:96]) - offset := 96 + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize sk.s1.UnpackLeqEta(buf[offset:]) offset += PolyLeqEtaSize * L sk.s2.UnpackLeqEta(buf[offset:]) @@ -250,7 +250,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { var zh VecL var Az, Az2dct1, w1 VecK var ch common.Poly - var cp [32]byte + var cp [CTildeSize]byte var w1Packed [PolyW1Size * K]byte // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β @@ -279,7 +279,7 @@ func Verify(pk *PublicKey, msg []byte, signature []byte) bool { // which is small enough for NTT(). Az2dct1.MulBy2toD(&pk.t1) Az2dct1.NTT() - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() for i := 0; i < K; i++ { Az2dct1[i].MulHat(&Az2dct1[i], &ch) @@ -330,6 +330,11 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { // ρ' = CRH(key ‖ μ) h.Reset() _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } _, _ = h.Write(mu[:]) _, _ = h.Read(rhop[:]) @@ -368,7 +373,7 @@ func SignTo(sk *PrivateKey, msg []byte, signature []byte) { _, _ = h.Write(w1Packed[:]) _, _ = h.Read(sig.c[:]) - PolyDeriveUniformBall(&ch, &sig.c) + PolyDeriveUniformBall(&ch, sig.c[:32]) ch.NTT() // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. diff --git a/sign/dilithium/mode5aes/internal/dilithium_test.go b/sign/dilithium/mode5aes/internal/dilithium_test.go index ca347a831..825e7da08 100644 --- a/sign/dilithium/mode5aes/internal/dilithium_test.go +++ b/sign/dilithium/mode5aes/internal/dilithium_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Checks whether p is normalized. Only used in tests. diff --git a/sign/dilithium/mode5aes/internal/mat.go b/sign/dilithium/mode5aes/internal/mat.go index cb473c149..ceaf634fa 100644 --- a/sign/dilithium/mode5aes/internal/mat.go +++ b/sign/dilithium/mode5aes/internal/mat.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A k by l matrix of polynomials. diff --git a/sign/dilithium/mode5aes/internal/pack.go b/sign/dilithium/mode5aes/internal/pack.go index 0285dfd8e..1854b4197 100644 --- a/sign/dilithium/mode5aes/internal/pack.go +++ b/sign/dilithium/mode5aes/internal/pack.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Writes p with norm less than or equal η into buf, which must be of diff --git a/sign/dilithium/mode5aes/internal/pack_test.go b/sign/dilithium/mode5aes/internal/pack_test.go index 24f568e4d..f952c6a09 100644 --- a/sign/dilithium/mode5aes/internal/pack_test.go +++ b/sign/dilithium/mode5aes/internal/pack_test.go @@ -5,7 +5,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestPolyPackLeqEta(t *testing.T) { diff --git a/sign/dilithium/mode5aes/internal/params.go b/sign/dilithium/mode5aes/internal/params.go index 419cb2a2b..340b6d73a 100644 --- a/sign/dilithium/mode5aes/internal/params.go +++ b/sign/dilithium/mode5aes/internal/params.go @@ -13,4 +13,7 @@ const ( Tau = 60 Gamma1Bits = 19 Gamma2 = 261888 + NIST = false + TRSize = 32 + CTildeSize = 32 ) diff --git a/sign/dilithium/mode5aes/internal/params_test.go b/sign/dilithium/mode5aes/internal/params_test.go index a5e746f68..57e14f820 100644 --- a/sign/dilithium/mode5aes/internal/params_test.go +++ b/sign/dilithium/mode5aes/internal/params_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Tests specific to the current mode diff --git a/sign/dilithium/mode5aes/internal/rounding.go b/sign/dilithium/mode5aes/internal/rounding.go index 71360cb29..58123c090 100644 --- a/sign/dilithium/mode5aes/internal/rounding.go +++ b/sign/dilithium/mode5aes/internal/rounding.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, diff --git a/sign/dilithium/mode5aes/internal/rounding_test.go b/sign/dilithium/mode5aes/internal/rounding_test.go index 5824f2656..ad653ca3f 100644 --- a/sign/dilithium/mode5aes/internal/rounding_test.go +++ b/sign/dilithium/mode5aes/internal/rounding_test.go @@ -6,7 +6,7 @@ import ( "flag" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") diff --git a/sign/dilithium/mode5aes/internal/sample.go b/sign/dilithium/mode5aes/internal/sample.go index c47185ada..62c261332 100644 --- a/sign/dilithium/mode5aes/internal/sample.go +++ b/sign/dilithium/mode5aes/internal/sample.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" "github.com/cloudflare/circl/simd/keccakf1600" ) @@ -246,12 +246,12 @@ func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { // Can only be called when DeriveX4Available is true. // // This function is currently not used (yet). -func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { var perm keccakf1600.StateX4 state := perm.Initialize(false) // Absorb the seed in the four states - for i := 0; i < 4; i++ { + for i := 0; i < 32/8; i++ { v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) for j := 0; j < 4; j++ { state[i*4+j] = v @@ -260,7 +260,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // SHAKE256 domain separator and padding for j := 0; j < 4; j++ { - state[4*4+j] ^= 0x1f + state[(32/8)*4+j] ^= 0x1f state[16*4+j] ^= 0x80 << 56 } perm.Permute() @@ -327,7 +327,7 @@ func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed *[32]byte) { // Samples p uniformly with τ non-zero coefficients in {q-1,1}. // // The polynomial p will be normalized. -func PolyDeriveUniformBall(p *common.Poly, seed *[32]byte) { +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { var buf [136]byte // SHAKE-256 rate is 136 h := sha3.NewShake256() diff --git a/sign/dilithium/mode5aes/internal/sample_test.go b/sign/dilithium/mode5aes/internal/sample_test.go index 1a0a4f72c..2059599eb 100644 --- a/sign/dilithium/mode5aes/internal/sample_test.go +++ b/sign/dilithium/mode5aes/internal/sample_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "testing" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) func TestVectorDeriveUniform(t *testing.T) { @@ -155,7 +155,7 @@ func TestDeriveUniformBall(t *testing.T) { var seed [32]byte for i := 0; i < 100; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) nonzero := 0 for j := 0; j < common.N; j++ { if p[j] != 0 { @@ -203,10 +203,10 @@ func TestDeriveUniformBallX4(t *testing.T) { var seed [32]byte PolyDeriveUniformBallX4( [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, - &seed, + seed[:], ) for j := 0; j < 4; j++ { - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) if ps[j] != p { t.Fatalf("%d\n%v\n%v", j, ps[j], p) } @@ -219,7 +219,7 @@ func BenchmarkPolyDeriveUniformBall(b *testing.B) { var w1 VecK for i := 0; i < b.N; i++ { w1[0][0] = uint32(i) - PolyDeriveUniformBall(&p, &seed) + PolyDeriveUniformBall(&p, seed[:]) } } @@ -231,7 +231,7 @@ func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { w1[0][0] = uint32(i) PolyDeriveUniformBallX4( [4]*common.Poly{&p, &p, &p, &p}, - &seed, + seed[:], ) } } diff --git a/sign/dilithium/mode5aes/internal/vec.go b/sign/dilithium/mode5aes/internal/vec.go index f52973e45..d07d3b245 100644 --- a/sign/dilithium/mode5aes/internal/vec.go +++ b/sign/dilithium/mode5aes/internal/vec.go @@ -3,7 +3,7 @@ package internal import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // A vector of L polynomials. diff --git a/sign/dilithium/templates/mode.templ.go b/sign/dilithium/templates/mode.templ.go index 8c76b2512..fb7930de9 100644 --- a/sign/dilithium/templates/mode.templ.go +++ b/sign/dilithium/templates/mode.templ.go @@ -9,15 +9,21 @@ package dilithium import ( "fmt" "io" - - "github.com/cloudflare/circl/sign/dilithium/internal/common" +{{ if .NIST }} + "github.com/cloudflare/circl/sign/mldsa/{{.Pkg}}" +{{ else }} "github.com/cloudflare/circl/sign/dilithium/{{.Pkg}}" +{{- end }} + common "github.com/cloudflare/circl/sign/internal/dilithium" ) // {{.Impl}} implements the mode.Mode interface for {{.Name}}. type {{.Impl}} struct{} - +{{ if .NIST }} +// {{.Mode}} is {{.Name}}. +{{- else }} // {{.Mode}} is Dilithium in mode "{{.Name}}". +{{- end }} var {{.Mode}} Mode = &{{.Impl}}{} func (m *{{.Impl}}) GenerateKey(rand io.Reader) ( diff --git a/sign/dilithium/templates/modePkg.templ.go b/sign/dilithium/templates/modePkg.templ.go index 29dc73556..fbaa89746 100644 --- a/sign/dilithium/templates/modePkg.templ.go +++ b/sign/dilithium/templates/modePkg.templ.go @@ -4,10 +4,14 @@ // Code generated from modePkg.templ.go. DO NOT EDIT. +{{ if .NIST }} +// {{.Pkg}} implements NIST signature scheme {{.Name}} as defined in FIPS204. +{{ else }} // {{.Pkg}} implements the CRYSTALS-Dilithium signature scheme {{.Name}} // as submitted to round3 of the NIST PQC competition and described in // // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf +{{ end }} package {{.Pkg}} import ( @@ -15,8 +19,12 @@ import ( "errors" "io" - "github.com/cloudflare/circl/sign/dilithium/internal/common" + common "github.com/cloudflare/circl/sign/internal/dilithium" +{{ if .NIST }} + "github.com/cloudflare/circl/sign/mldsa/{{.Pkg}}/internal" +{{ else }} "github.com/cloudflare/circl/sign/dilithium/{{.Pkg}}/internal" +{{ end }} ) const ( diff --git a/sign/dilithium/templates/params.templ.go b/sign/dilithium/templates/params.templ.go index 1e0331c25..faeae3aac 100644 --- a/sign/dilithium/templates/params.templ.go +++ b/sign/dilithium/templates/params.templ.go @@ -17,4 +17,7 @@ const ( Tau = {{.Tau}} Gamma1Bits = {{.Gamma1Bits}} Gamma2 = {{.Gamma2}} + NIST = {{.NIST}} + TRSize = {{.TRSize}} + CTildeSize = {{.CTildeSize}} ) diff --git a/sign/dilithium/internal/common/aes.go b/sign/internal/dilithium/aes.go similarity index 98% rename from sign/dilithium/internal/common/aes.go rename to sign/internal/dilithium/aes.go index f5de25425..895a20597 100644 --- a/sign/dilithium/internal/common/aes.go +++ b/sign/internal/dilithium/aes.go @@ -1,4 +1,4 @@ -package common +package dilithium import ( "crypto/aes" diff --git a/sign/dilithium/internal/common/amd64.go b/sign/internal/dilithium/amd64.go similarity index 99% rename from sign/dilithium/internal/common/amd64.go rename to sign/internal/dilithium/amd64.go index a58e4bf46..7941b26ab 100644 --- a/sign/dilithium/internal/common/amd64.go +++ b/sign/internal/dilithium/amd64.go @@ -1,7 +1,7 @@ //go:build amd64 // +build amd64 -package common +package dilithium import ( "golang.org/x/sys/cpu" diff --git a/sign/dilithium/internal/common/amd64.s b/sign/internal/dilithium/amd64.s similarity index 100% rename from sign/dilithium/internal/common/amd64.s rename to sign/internal/dilithium/amd64.s diff --git a/sign/dilithium/internal/common/asm/go.mod b/sign/internal/dilithium/asm/go.mod similarity index 59% rename from sign/dilithium/internal/common/asm/go.mod rename to sign/internal/dilithium/asm/go.mod index 5638957e6..41948fce3 100644 --- a/sign/dilithium/internal/common/asm/go.mod +++ b/sign/internal/dilithium/asm/go.mod @@ -1,4 +1,4 @@ -module github.com/cloudflare/circl/sign/dilithium/internal/common/asm +module github.com/cloudflare/circl/sign/internal/dilithium/asm go 1.21 @@ -12,4 +12,4 @@ require ( golang.org/x/tools v0.17.0 // indirect ) -replace github.com/cloudflare/circl => ../../../../../ +replace github.com/cloudflare/circl => ../../../../ diff --git a/sign/dilithium/internal/common/asm/go.sum b/sign/internal/dilithium/asm/go.sum similarity index 100% rename from sign/dilithium/internal/common/asm/go.sum rename to sign/internal/dilithium/asm/go.sum diff --git a/sign/dilithium/internal/common/asm/src.go b/sign/internal/dilithium/asm/src.go similarity index 99% rename from sign/dilithium/internal/common/asm/src.go rename to sign/internal/dilithium/asm/src.go index 834efff93..8ff84c91a 100644 --- a/sign/dilithium/internal/common/asm/src.go +++ b/sign/internal/dilithium/asm/src.go @@ -1,4 +1,4 @@ -//go:generate go run src.go -out ../amd64.s -stubs ../stubs_amd64.go -pkg common +//go:generate go run src.go -out ../amd64.s -stubs ../stubs_amd64.go -pkg dilithium // AVX2 optimized version of Poly.[Inv]NTT(). See the comments on the generic // implementation for details on the maths involved. @@ -9,7 +9,7 @@ import ( . "github.com/mmcloughlin/avo/operand" // nolint:golint,stylecheck . "github.com/mmcloughlin/avo/reg" // nolint:golint,stylecheck - "github.com/cloudflare/circl/sign/dilithium/internal/common/params" + "github.com/cloudflare/circl/sign/internal/dilithium/params" ) // XXX align Poly on 16 bytes such that we can use aligned moves diff --git a/sign/dilithium/internal/common/field.go b/sign/internal/dilithium/field.go similarity index 98% rename from sign/dilithium/internal/common/field.go rename to sign/internal/dilithium/field.go index 2aab16ecb..c2fc6ca72 100644 --- a/sign/dilithium/internal/common/field.go +++ b/sign/internal/dilithium/field.go @@ -1,4 +1,4 @@ -package common +package dilithium // Returns a y with y < 2q and y = x mod q. // Note that in general *not*: ReduceLe2Q(ReduceLe2Q(x)) == x. diff --git a/sign/dilithium/internal/common/field_test.go b/sign/internal/dilithium/field_test.go similarity index 98% rename from sign/dilithium/internal/common/field_test.go rename to sign/internal/dilithium/field_test.go index f9864eabe..85db87960 100644 --- a/sign/dilithium/internal/common/field_test.go +++ b/sign/internal/dilithium/field_test.go @@ -1,4 +1,4 @@ -package common +package dilithium import ( "crypto/rand" diff --git a/sign/dilithium/internal/common/generic.go b/sign/internal/dilithium/generic.go similarity index 99% rename from sign/dilithium/internal/common/generic.go rename to sign/internal/dilithium/generic.go index f10545246..4f02272b6 100644 --- a/sign/dilithium/internal/common/generic.go +++ b/sign/internal/dilithium/generic.go @@ -1,7 +1,7 @@ //go:build !amd64 // +build !amd64 -package common +package dilithium // Execute an in-place forward NTT on as. // diff --git a/sign/dilithium/internal/common/ntt.go b/sign/internal/dilithium/ntt.go similarity index 99% rename from sign/dilithium/internal/common/ntt.go rename to sign/internal/dilithium/ntt.go index 6f5370ae0..1568ccb4b 100644 --- a/sign/dilithium/internal/common/ntt.go +++ b/sign/internal/dilithium/ntt.go @@ -1,4 +1,4 @@ -package common +package dilithium // Zetas lists precomputed powers of the root of unity in Montgomery // representation used for the NTT: diff --git a/sign/dilithium/internal/common/ntt_test.go b/sign/internal/dilithium/ntt_test.go similarity index 98% rename from sign/dilithium/internal/common/ntt_test.go rename to sign/internal/dilithium/ntt_test.go index 4524f245d..71d736a92 100644 --- a/sign/dilithium/internal/common/ntt_test.go +++ b/sign/internal/dilithium/ntt_test.go @@ -1,4 +1,4 @@ -package common +package dilithium import "testing" diff --git a/sign/dilithium/internal/common/pack.go b/sign/internal/dilithium/pack.go similarity index 99% rename from sign/dilithium/internal/common/pack.go rename to sign/internal/dilithium/pack.go index 6c366089c..4b952a004 100644 --- a/sign/dilithium/internal/common/pack.go +++ b/sign/internal/dilithium/pack.go @@ -1,4 +1,4 @@ -package common +package dilithium // Sets p to the polynomial whose coefficients are less than 1024 encoded // into buf (which must be of size PolyT1Size). diff --git a/sign/dilithium/internal/common/pack_test.go b/sign/internal/dilithium/pack_test.go similarity index 96% rename from sign/dilithium/internal/common/pack_test.go rename to sign/internal/dilithium/pack_test.go index 140d3f134..37d37ce46 100644 --- a/sign/dilithium/internal/common/pack_test.go +++ b/sign/internal/dilithium/pack_test.go @@ -1,4 +1,4 @@ -package common +package dilithium import "testing" diff --git a/sign/dilithium/internal/common/params.go b/sign/internal/dilithium/params.go similarity index 79% rename from sign/dilithium/internal/common/params.go rename to sign/internal/dilithium/params.go index 7713c6da9..f423217f0 100644 --- a/sign/dilithium/internal/common/params.go +++ b/sign/internal/dilithium/params.go @@ -1,7 +1,7 @@ -package common +package dilithium import ( - "github.com/cloudflare/circl/sign/dilithium/internal/common/params" + "github.com/cloudflare/circl/sign/internal/dilithium/params" ) const ( diff --git a/sign/dilithium/internal/common/params/params.go b/sign/internal/dilithium/params/params.go similarity index 100% rename from sign/dilithium/internal/common/params/params.go rename to sign/internal/dilithium/params/params.go diff --git a/sign/dilithium/internal/common/poly.go b/sign/internal/dilithium/poly.go similarity index 99% rename from sign/dilithium/internal/common/poly.go rename to sign/internal/dilithium/poly.go index 7ac8e57da..96c0551b3 100644 --- a/sign/dilithium/internal/common/poly.go +++ b/sign/internal/dilithium/poly.go @@ -1,4 +1,4 @@ -package common +package dilithium // An element of our base ring R which are polynomials over Z_q modulo // the equation Xᴺ = -1, where q=2²³ - 2¹³ + 1 and N=256. diff --git a/sign/dilithium/internal/common/poly_test.go b/sign/internal/dilithium/poly_test.go similarity index 99% rename from sign/dilithium/internal/common/poly_test.go rename to sign/internal/dilithium/poly_test.go index 5601aa276..42e5fc46d 100644 --- a/sign/dilithium/internal/common/poly_test.go +++ b/sign/internal/dilithium/poly_test.go @@ -1,4 +1,4 @@ -package common +package dilithium import "testing" diff --git a/sign/dilithium/internal/common/stubs_amd64.go b/sign/internal/dilithium/stubs_amd64.go similarity index 91% rename from sign/dilithium/internal/common/stubs_amd64.go rename to sign/internal/dilithium/stubs_amd64.go index 97451507f..6ae5401ab 100644 --- a/sign/dilithium/internal/common/stubs_amd64.go +++ b/sign/internal/dilithium/stubs_amd64.go @@ -1,8 +1,8 @@ -// Code generated by command: go run src.go -out ../amd64.s -stubs ../stubs_amd64.go -pkg common. DO NOT EDIT. +// Code generated by command: go run src.go -out ../amd64.s -stubs ../stubs_amd64.go -pkg dilithium. DO NOT EDIT. //go:build amd64 -package common +package dilithium //go:noescape func nttAVX2(p *[256]uint32) diff --git a/sign/mldsa/doc.go b/sign/mldsa/doc.go new file mode 100644 index 000000000..d5dce5749 --- /dev/null +++ b/sign/mldsa/doc.go @@ -0,0 +1,2 @@ +// mldsa implements NIST post-quantum signature scheme ML-DSA. +package mldsa diff --git a/sign/mldsa/mldsa44/dilithium.go b/sign/mldsa/mldsa44/dilithium.go new file mode 100644 index 000000000..71e2b7906 --- /dev/null +++ b/sign/mldsa/mldsa44/dilithium.go @@ -0,0 +1,182 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + + +// mldsa44 implements NIST signature scheme ML-DSA-44 as defined in FIPS204. + +package mldsa44 + +import ( + "crypto" + "errors" + "io" + + common "github.com/cloudflare/circl/sign/internal/dilithium" + + "github.com/cloudflare/circl/sign/mldsa/mldsa44/internal" + +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = common.SeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of ML-DSA-44 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of ML-DSA-44 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// SignTo signs the given message and writes the signature into signature. +// It will panic if signature is not of length at least SignatureSize. +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + internal.SignTo( + (*internal.PrivateKey)(sk), + msg, + signature, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + (*internal.PublicKey)(pk), + msg, + signature, + ) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + +// Packs the public key. +func (pk *PublicKey) Bytes() []byte { + var buf [PublicKeySize]byte + pk.Pack(&buf) + return buf[:] +} + +// Packs the private key. +func (sk *PrivateKey) Bytes() []byte { + var buf [PrivateKeySize]byte + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mldsa44.PublicKeySize bytes") + } + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PrivateKeySize { + return errors.New("packed private key must be of mldsa44.PrivateKeySize bytes") + } + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. rand is ignored. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level SignTo function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + var sig [SignatureSize]byte + + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("dilithium: cannot sign hashed message") + } + + SignTo(sk, msg, sig[:]) + return sig[:], nil +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*PublicKey)((*internal.PrivateKey)(sk).Public()) +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mldsa/mldsa44/internal/dilithium.go b/sign/mldsa/mldsa44/internal/dilithium.go new file mode 100644 index 000000000..7869d0475 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/dilithium.go @@ -0,0 +1,482 @@ +// Code generated from mode3/internal/dilithium.go by gen.go + +package internal + +import ( + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +const ( + // Size of a packed polynomial of norm ≤η. + // (Note that the formula is not valid in general.) + PolyLeqEtaSize = (common.N * DoubleEtaBits) / 8 + + // β = τη, the maximum size of c s₂. + Beta = Tau * Eta + + // γ₁ range of y + Gamma1 = 1 << Gamma1Bits + + // Size of packed polynomial of norm <γ₁ such as z + PolyLeGamma1Size = (Gamma1Bits + 1) * common.N / 8 + + // α = 2γ₂ parameter for decompose + Alpha = 2 * Gamma2 + + // Size of a packed private key + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + + // Size of a packed public key + PublicKeySize = 32 + common.PolyT1Size*K + + // Size of a packed signature + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize + + // Size of packed w₁ + PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 +) + +// PublicKey is the type of Dilithium public keys. +type PublicKey struct { + rho [32]byte + t1 VecK + + // Cached values + t1p [common.PolyT1Size * K]byte + A *Mat + tr *[TRSize]byte +} + +// PrivateKey is the type of Dilithium private keys. +type PrivateKey struct { + rho [32]byte + key [32]byte + s1 VecL + s2 VecK + t0 VecK + tr [TRSize]byte + + // Cached values + A Mat // ExpandA(ρ) + s1h VecL // NTT(s₁) + s2h VecK // NTT(s₂) + t0h VecK // NTT(t₀) +} + +type unpackedSignature struct { + z VecL + hint VecK + c [CTildeSize]byte +} + +// Packs the signature into buf. +func (sig *unpackedSignature) Pack(buf []byte) { + copy(buf[:], sig.c[:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) +} + +// Sets sig to the signature encoded in the buffer. +// +// Returns whether buf contains a properly packed signature. +func (sig *unpackedSignature) Unpack(buf []byte) bool { + if len(buf) < SignatureSize { + return false + } + copy(sig.c[:], buf[:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) + if sig.z.Exceeds(Gamma1 - Beta) { + return false + } + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { + return false + } + return true +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:32], pk.rho[:]) + copy(buf[32:], pk.t1p[:]) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.rho[:], buf[:32]) + copy(pk.t1p[:], buf[32:]) + + pk.t1.UnpackT1(pk.t1p[:]) + pk.A = new(Mat) + pk.A.Derive(&pk.rho) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + pk.tr = new([TRSize]byte) + h := sha3.NewShake256() + _, _ = h.Write(buf[:]) + _, _ = h.Read(pk.tr[:]) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:32], sk.rho[:]) + copy(buf[32:64], sk.key[:]) + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize + sk.s1.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.PackT0(buf[offset:]) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.rho[:], buf[:32]) + copy(sk.key[:], buf[32:64]) + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize + sk.s1.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.UnpackT0(buf[offset:]) + + // Cached values + sk.A.Derive(&sk.rho) + sk.t0h = sk.t0 + sk.t0h.NTT() + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [32]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(&seed) + return pk, sk, nil +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[common.SeedSize]byte) (*PublicKey, *PrivateKey) { + var eSeed [128]byte // expanded seed + var pk PublicKey + var sk PrivateKey + var sSeed [64]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(eSeed[:]) + + copy(pk.rho[:], eSeed[:32]) + copy(sSeed[:], eSeed[32:96]) + copy(sk.key[:], eSeed[96:]) + copy(sk.rho[:], pk.rho[:]) + + sk.A.Derive(&pk.rho) + + for i := uint16(0); i < L; i++ { + PolyDeriveUniformLeqEta(&sk.s1[i], &sSeed, i) + } + + for i := uint16(0); i < K; i++ { + PolyDeriveUniformLeqEta(&sk.s2[i], &sSeed, i+L) + } + + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() + + sk.computeT0andT1(&sk.t0, &pk.t1) + + sk.t0h = sk.t0 + sk.t0h.NTT() + + // Complete public key far enough to be packed + pk.t1.PackT1(pk.t1p[:]) + pk.A = &sk.A + + // Finish private key + var packedPk [PublicKeySize]byte + pk.Pack(&packedPk) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + h.Reset() + _, _ = h.Write(packedPk[:]) + _, _ = h.Read(sk.tr[:]) + + // Finish cache of public key + pk.tr = &sk.tr + + return &pk, &sk +} + +// Computes t0 and t1 from sk.s1h, sk.s2 and sk.A. +func (sk *PrivateKey) computeT0andT1(t0, t1 *VecK) { + var t VecK + + // Set t to A s₁ + s₂ + for i := 0; i < K; i++ { + PolyDotHat(&t[i], &sk.A[i], &sk.s1h) + t[i].ReduceLe2Q() + t[i].InvNTT() + } + t.Add(&t, &sk.s2) + t.Normalize() + + // Compute t₀, t₁ = Power2Round(t) + t.Power2Round(t0, t1) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + var sig unpackedSignature + var mu [64]byte + var zh VecL + var Az, Az2dct1, w1 VecK + var ch common.Poly + var cp [CTildeSize]byte + var w1Packed [PolyW1Size * K]byte + + // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β + // and ensured that there at most ω ones in pk.hint. + if !sig.Unpack(signature) { + return false + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(pk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // Compute Az + zh = sig.z + zh.NTT() + + for i := 0; i < K; i++ { + PolyDotHat(&Az[i], &pk.A[i], &zh) + } + + // Next, we compute Az - 2ᵈ·c·t₁. + // Note that the coefficients of t₁ are bounded by 256 = 2⁹, + // so the coefficients of Az2dct1 will bounded by 2⁹⁺ᵈ = 2²³ < 2q, + // which is small enough for NTT(). + Az2dct1.MulBy2toD(&pk.t1) + Az2dct1.NTT() + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + for i := 0; i < K; i++ { + Az2dct1[i].MulHat(&Az2dct1[i], &ch) + } + Az2dct1.Sub(&Az, &Az2dct1) + Az2dct1.ReduceLe2Q() + Az2dct1.InvNTT() + Az2dct1.NormalizeAssumingLe2Q() + + // UseHint(pk.hint, Az - 2ᵈ·c·t₁) + // = UseHint(pk.hint, w - c·s₂ + c·t₀) + // = UseHint(pk.hint, r + c·t₀) + // = r₁ = w₁. + w1.UseHint(&Az2dct1, &sig.hint) + w1.PackW1(w1Packed[:]) + + // c' = H(μ, w₁) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(cp[:]) + + return sig.c == cp +} + +// SignTo signs the given message and writes the signature into signature. +// +//nolint:funlen +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + var mu, rhop [64]byte + var w1Packed [PolyW1Size * K]byte + var y, yh VecL + var w, w0, w1, w0mcs2, ct0, w0mcs2pct0 VecK + var ch common.Poly + var yNonce uint16 + var sig unpackedSignature + + if len(signature) < SignatureSize { + panic("Signature does not fit in that byteslice") + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(sk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // ρ' = CRH(key ‖ μ) + h.Reset() + _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } + _, _ = h.Write(mu[:]) + _, _ = h.Read(rhop[:]) + + // Main rejection loop + attempt := 0 + for { + attempt++ + if attempt >= 576 { + // Depending on the mode, one try has a chance between 1/7 and 1/4 + // of succeeding. Thus it is safe to say that 576 iterations + // are enough as (6/7)⁵⁷⁶ < 2⁻¹²⁸. + panic("This should only happen 1 in 2^{128}: something is wrong.") + } + + // y = ExpandMask(ρ', key) + VecLDeriveUniformLeGamma1(&y, &rhop, yNonce) + yNonce += uint16(L) + + // Set w to A y + yh = y + yh.NTT() + for i := 0; i < K; i++ { + PolyDotHat(&w[i], &sk.A[i], &yh) + w[i].ReduceLe2Q() + w[i].InvNTT() + } + + // Decompose w into w₀ and w₁ + w.NormalizeAssumingLe2Q() + w.Decompose(&w0, &w1) + + // c~ = H(μ ‖ w₁) + w1.PackW1(w1Packed[:]) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(sig.c[:]) + + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + + // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. + // + // By Lemma 3 of the specification this is equivalent to checking that + // both ‖ r₀ ‖_∞ < γ₂ - β and r₁ = w₁, for the decomposition + // w - c·s₂ = r₁ α + r₀ as computed by decompose(). + // See also §4.1 of the specification. + for i := 0; i < K; i++ { + w0mcs2[i].MulHat(&ch, &sk.s2h[i]) + w0mcs2[i].InvNTT() + } + w0mcs2.Sub(&w0, &w0mcs2) + w0mcs2.Normalize() + + if w0mcs2.Exceeds(Gamma2 - Beta) { + continue + } + + // z = y + c·s₁ + for i := 0; i < L; i++ { + sig.z[i].MulHat(&ch, &sk.s1h[i]) + sig.z[i].InvNTT() + } + sig.z.Add(&sig.z, &y) + sig.z.Normalize() + + // Ensure ‖z‖_∞ < γ₁ - β + if sig.z.Exceeds(Gamma1 - Beta) { + continue + } + + // Compute c·t₀ + for i := 0; i < K; i++ { + ct0[i].MulHat(&ch, &sk.t0h[i]) + ct0[i].InvNTT() + } + ct0.NormalizeAssumingLe2Q() + + // Ensure ‖c·t₀‖_∞ < γ₂. + if ct0.Exceeds(Gamma2) { + continue + } + + // Create the hint to be able to reconstruct w₁ from w - c·s₂ + c·t0. + // Note that we're not using makeHint() in the obvious way as we + // do not know whether ‖ sc·s₂ - c·t₀ ‖_∞ < γ₂. Instead we note + // that our makeHint() is actually the same as a makeHint for a + // different decomposition: + // + // Earlier we ensured indirectly with a check that r₁ = w₁ where + // r = w - c·s₂. Hence r₀ = r - r₁ α = w - c·s₂ - w₁ α = w₀ - c·s₂. + // Thus MakeHint(w₀ - c·s₂ + c·t₀, w₁) = MakeHint(r0 + c·t₀, r₁) + // and UseHint(w - c·s₂ + c·t₀, w₁) = UseHint(r + c·t₀, r₁). + // As we just ensured that ‖ c·t₀ ‖_∞ < γ₂ our usage is correct. + w0mcs2pct0.Add(&w0mcs2, &ct0) + w0mcs2pct0.NormalizeAssumingLe2Q() + hintPop := sig.hint.MakeHint(&w0mcs2pct0, &w1) + if hintPop > Omega { + continue + } + + break + } + + sig.Pack(signature[:]) +} + +// Computes the public key corresponding to this private key. +func (sk *PrivateKey) Public() *PublicKey { + var t0 VecK + pk := &PublicKey{ + rho: sk.rho, + A: &sk.A, + tr: &sk.tr, + } + sk.computeT0andT1(&t0, &pk.t1) + pk.t1.PackT1(pk.t1p[:]) + return pk +} + +// Equal returns whether the two public keys are equal +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.rho == other.rho && pk.t1 == other.t1 +} + +// Equal returns whether the two private keys are equal +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + ret := (subtle.ConstantTimeCompare(sk.rho[:], other.rho[:]) & + subtle.ConstantTimeCompare(sk.key[:], other.key[:]) & + subtle.ConstantTimeCompare(sk.tr[:], other.tr[:])) + + acc := uint32(0) + for i := 0; i < L; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s1[i][j] ^ other.s1[i][j] + } + } + for i := 0; i < K; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s2[i][j] ^ other.s2[i][j] + acc |= sk.t0[i][j] ^ other.t0[i][j] + } + } + return (ret & subtle.ConstantTimeEq(int32(acc), 0)) == 1 +} diff --git a/sign/mldsa/mldsa44/internal/dilithium_test.go b/sign/mldsa/mldsa44/internal/dilithium_test.go new file mode 100644 index 000000000..825e7da08 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/dilithium_test.go @@ -0,0 +1,144 @@ +// Code generated from mode3/internal/dilithium_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Checks whether p is normalized. Only used in tests. +func PolyNormalized(p *common.Poly) bool { + p2 := *p + p2.Normalize() + return p2 == *p +} + +func BenchmarkSkUnpack(b *testing.B) { + var buf [PrivateKeySize]byte + var sk PrivateKey + for i := 0; i < b.N; i++ { + sk.Unpack(&buf) + } +} + +func BenchmarkPkUnpack(b *testing.B) { + var buf [PublicKeySize]byte + var pk PublicKey + for i := 0; i < b.N; i++ { + pk.Unpack(&buf) + } +} + +func BenchmarkVerify(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of verification (as in the reference + // implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + pk, sk := NewKeyFromSeed(&seed) + SignTo(sk, msg[:], sig[:]) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // We should generate a new signature for every verify attempt, + // as this influences the time a little bit. This difference, however, + // is small and generating a new signature in between creates a lot + // pressure on the allocator which makes an accurate measurement hard. + Verify(pk, msg[:], sig[:]) + } +} + +func BenchmarkSign(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of signing (as in the reference implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + _, sk := NewKeyFromSeed(&seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + SignTo(sk, msg[:], sig[:]) + } +} + +func BenchmarkGenerateKey(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + NewKeyFromSeed(&seed) + } +} + +func BenchmarkPublicFromPrivate(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + b.StopTimer() + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, sk := NewKeyFromSeed(&seed) + b.StartTimer() + sk.Public() + } +} + +func TestSignThenVerifyAndPkSkPacking(t *testing.T) { + var seed [common.SeedSize]byte + var sig [SignatureSize]byte + var msg [8]byte + var pkb [PublicKeySize]byte + var skb [PrivateKeySize]byte + var pk2 PublicKey + var sk2 PrivateKey + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + if !sk.Equal(sk) { + t.Fatal() + } + for j := uint64(0); j < 10; j++ { + binary.LittleEndian.PutUint64(msg[:], j) + SignTo(sk, msg[:], sig[:]) + if !Verify(pk, msg[:], sig[:]) { + t.Fatal() + } + } + pk.Pack(&pkb) + pk2.Unpack(&pkb) + if !pk.Equal(&pk2) { + t.Fatal() + } + sk.Pack(&skb) + sk2.Unpack(&skb) + if !sk.Equal(&sk2) { + t.Fatal() + } + } +} + +func TestPublicFromPrivate(t *testing.T) { + var seed [common.SeedSize]byte + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + pk2 := sk.Public() + if !pk.Equal(pk2) { + t.Fatal() + } + } +} + +func TestGamma1Size(t *testing.T) { + var expected int + switch Gamma1Bits { + case 17: + expected = 576 + case 19: + expected = 640 + } + if expected != PolyLeGamma1Size { + t.Fatal() + } +} diff --git a/sign/mldsa/mldsa44/internal/mat.go b/sign/mldsa/mldsa44/internal/mat.go new file mode 100644 index 000000000..ceaf634fa --- /dev/null +++ b/sign/mldsa/mldsa44/internal/mat.go @@ -0,0 +1,59 @@ +// Code generated from mode3/internal/mat.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A k by l matrix of polynomials. +type Mat [K]VecL + +// Expands the given seed to a complete matrix. +// +// This function is called ExpandA in the specification. +func (m *Mat) Derive(seed *[32]byte) { + if !DeriveX4Available { + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + PolyDeriveUniform(&m[i][j], seed, (i<<8)+j) + } + } + return + } + + idx := 0 + var nonces [4]uint16 + var ps [4]*common.Poly + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + nonces[idx] = (i << 8) + j + ps[idx] = &m[i][j] + idx++ + if idx == 4 { + idx = 0 + PolyDeriveUniformX4(ps, seed, nonces) + } + } + } + if idx != 0 { + for i := idx; i < 4; i++ { + ps[i] = nil + } + PolyDeriveUniformX4(ps, seed, nonces) + } +} + +// Set p to the inner product of a and b using pointwise multiplication. +// +// Assumes a and b are in Montgomery form and their coefficients are +// pairwise sufficiently small to multiply, see Poly.MulHat(). Resulting +// coefficients are bounded by 2Lq. +func PolyDotHat(p *common.Poly, a, b *VecL) { + var t common.Poly + *p = common.Poly{} // zero p + for i := 0; i < L; i++ { + t.MulHat(&a[i], &b[i]) + p.Add(&t, p) + } +} diff --git a/sign/mldsa/mldsa44/internal/pack.go b/sign/mldsa/mldsa44/internal/pack.go new file mode 100644 index 000000000..1854b4197 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/pack.go @@ -0,0 +1,270 @@ +// Code generated from mode3/internal/pack.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Writes p with norm less than or equal η into buf, which must be of +// size PolyLeqEtaSize. +// +// Assumes coefficients of p are not normalized, but in [q-η,q+η]. +func PolyPackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + buf[i] = (byte(common.Q+Eta-p[j]) | + byte(common.Q+Eta-p[j+1])<<4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + buf[i] = (byte(common.Q+Eta-p[j]) | + (byte(common.Q+Eta-p[j+1]) << 3) | + (byte(common.Q+Eta-p[j+2]) << 6)) + buf[i+1] = ((byte(common.Q+Eta-p[j+2]) >> 2) | + (byte(common.Q+Eta-p[j+3]) << 1) | + (byte(common.Q+Eta-p[j+4]) << 4) | + (byte(common.Q+Eta-p[j+5]) << 7)) + buf[i+2] = ((byte(common.Q+Eta-p[j+5]) >> 1) | + (byte(common.Q+Eta-p[j+6]) << 2) | + (byte(common.Q+Eta-p[j+7]) << 5)) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Sets p to the polynomial of norm less than or equal η encoded in the +// given buffer of size PolyLeqEtaSize. +// +// Output coefficients of p are not normalized, but in [q-η,q+η] provided +// buf was created using PackLeqEta. +// +// Beware, for arbitrary buf the coefficients of p might end up in +// the interval [q-2^b,q+2^b] where b is the least b with η≤2^b. +func PolyUnpackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + p[j] = common.Q + Eta - uint32(buf[i]&15) + p[j+1] = common.Q + Eta - uint32(buf[i]>>4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + p[j] = common.Q + Eta - uint32(buf[i]&7) + p[j+1] = common.Q + Eta - uint32((buf[i]>>3)&7) + p[j+2] = common.Q + Eta - uint32((buf[i]>>6)|((buf[i+1]<<2)&7)) + p[j+3] = common.Q + Eta - uint32((buf[i+1]>>1)&7) + p[j+4] = common.Q + Eta - uint32((buf[i+1]>>4)&7) + p[j+5] = common.Q + Eta - uint32((buf[i+1]>>7)|((buf[i+2]<<1)&7)) + p[j+6] = common.Q + Eta - uint32((buf[i+2]>>2)&7) + p[j+7] = common.Q + Eta - uint32((buf[i+2]>>5)&7) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Writes v with coefficients in {0, 1} of which at most ω non-zero +// to buf, which must have length ω+k. +func (v *VecK) PackHint(buf []byte) { + // The packed hint starts with the indices of the non-zero coefficients + // For instance: + // + // (x⁵⁶ + x¹⁰⁰, x²⁵⁵, 0, x² + x²³, x¹) + // + // Yields + // + // 56, 100, 255, 2, 23, 1 + // + // Then we pad with zeroes until we have a list of ω items: + // // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0 + // + // Then we finish with a list of the switch-over-indices in this + // list between polynomials, so: + // + // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0, 2, 3, 3, 5, 6 + + off := uint8(0) + for i := 0; i < K; i++ { + for j := uint16(0); j < common.N; j++ { + if v[i][j] != 0 { + buf[off] = uint8(j) + off++ + } + } + buf[Omega+i] = off + } + for ; off < Omega; off++ { + buf[off] = 0 + } +} + +// Sets v to the vector encoded using VecK.PackHint() +// +// Returns whether unpacking was successful. +func (v *VecK) UnpackHint(buf []byte) bool { + // A priori, there would be several reasonable ways to encode the same + // hint vector. We take care to only allow only one encoding, to ensure + // "strong unforgeability". + // + // See PackHint() source for description of the encoding. + *v = VecK{} // zero v + prevSOP := uint8(0) // previous switch-over-point + for i := 0; i < K; i++ { + SOP := buf[Omega+i] + if SOP < prevSOP || SOP > Omega { + return false // ensures switch-over-points are increasing + } + for j := prevSOP; j < SOP; j++ { + if j > prevSOP && buf[j] <= buf[j-1] { + return false // ensures indices are increasing (within a poly) + } + v[i][buf[j]] = 1 + } + prevSOP = SOP + } + for j := prevSOP; j < Omega; j++ { + if buf[j] != 0 { + return false // ensures padding indices are zero + } + } + + return true +} + +// Sets p to the polynomial packed into buf by PolyPackLeGamma1. +// +// p will be normalized. +func PolyUnpackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0x3) << 16) + p1 := uint32(buf[i+2]>>2) | (uint32(buf[i+3]) << 6) | + (uint32(buf[i+4]&0xf) << 14) + p2 := uint32(buf[i+4]>>4) | (uint32(buf[i+5]) << 4) | + (uint32(buf[i+6]&0x3f) << 12) + p3 := uint32(buf[i+6]>>6) | (uint32(buf[i+7]) << 2) | + (uint32(buf[i+8]) << 10) + + // coefficients in [0,…,2γ₁) + p0 = Gamma1 - p0 // (-γ₁,…,γ₁] + p1 = Gamma1 - p1 + p2 = Gamma1 - p2 + p3 = Gamma1 - p3 + + p0 += uint32(int32(p0)>>31) & common.Q // normalize + p1 += uint32(int32(p1)>>31) & common.Q + p2 += uint32(int32(p2)>>31) & common.Q + p3 += uint32(int32(p3)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + p[j+2] = p2 + p[j+3] = p3 + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0xf) << 16) + p1 := uint32(buf[i+2]>>4) | (uint32(buf[i+3]) << 4) | + (uint32(buf[i+4]) << 12) + + p0 = Gamma1 - p0 + p1 = Gamma1 - p1 + + p0 += uint32(int32(p0)>>31) & common.Q + p1 += uint32(int32(p1)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Writes p whose coefficients are in (-γ₁,γ₁] into buf +// which has to be of length PolyLeGamma1Size. +// +// Assumes p is normalized. +func PolyPackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + // coefficients in [0,…,γ₁] ∪ (q-γ₁,…,q) + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := Gamma1 - p[j] // [0,…,γ₁] ∪ (γ₁-q,…,2γ₁-q) + p0 += uint32(int32(p0)>>31) & common.Q // [0,…,2γ₁) + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + p2 := Gamma1 - p[j+2] + p2 += uint32(int32(p2)>>31) & common.Q + p3 := Gamma1 - p[j+3] + p3 += uint32(int32(p3)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<2) + buf[i+3] = byte(p1 >> 6) + buf[i+4] = byte(p1>>14) | byte(p2<<4) + buf[i+5] = byte(p2 >> 4) + buf[i+6] = byte(p2>>12) | byte(p3<<6) + buf[i+7] = byte(p3 >> 2) + buf[i+8] = byte(p3 >> 10) + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + // Coefficients are in [0, γ₁] ∪ (Q-γ₁, Q) + p0 := Gamma1 - p[j] + p0 += uint32(int32(p0)>>31) & common.Q + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<4) + buf[i+3] = byte(p1 >> 4) + buf[i+4] = byte(p1 >> 12) + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Pack w₁ into buf, which must be of length PolyW1Size. +// +// Assumes w₁ is normalized. +func PolyPackW1(p *common.Poly, buf []byte) { + if Gamma1Bits == 19 { + p.PackLe16(buf) + } else if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyW1Size; i += 3 { + buf[i] = byte(p[j]) | byte(p[j+1]<<6) + buf[i+1] = byte(p[j+1]>>2) | byte(p[j+2]<<4) + buf[i+2] = byte(p[j+2]>>4) | byte(p[j+3]<<2) + j += 4 + } + } else { + panic("unsupported γ₁") + } +} diff --git a/sign/mldsa/mldsa44/internal/pack_test.go b/sign/mldsa/mldsa44/internal/pack_test.go new file mode 100644 index 000000000..f952c6a09 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/pack_test.go @@ -0,0 +1,93 @@ +// Code generated from mode3/internal/pack_test.go by gen.go + +package internal + +import ( + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestPolyPackLeqEta(t *testing.T) { + var p1, p2 common.Poly + var seed [64]byte + var buf [PolyLeqEtaSize]byte + + for i := uint16(0); i < 100; i++ { + // Note that DeriveUniformLeqEta sets p to the right kind of + // unnormalized vector. + PolyDeriveUniformLeqEta(&p1, &seed, i) + for j := 0; j < PolyLeqEtaSize; j++ { + if p1[j] < common.Q-Eta || p1[j] > common.Q+Eta { + t.Fatalf("DerveUniformLeqEta out of bounds") + } + } + PolyPackLeqEta(&p1, buf[:]) + PolyUnpackLeqEta(&p2, buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT1(t *testing.T) { + var p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p1, &seed, i) + p1.Normalize() + for j := 0; j < common.N; j++ { + p1[j] &= 0x1ff + } + p1.PackT1(buf[:]) + p2.UnpackT1(buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT0(t *testing.T) { + var p, p0, p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT0Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p, &seed, i) + p.Normalize() + p.Power2Round(&p0, &p1) + + p0.PackT0(buf[:]) + p2.UnpackT0(buf[:]) + if p0 != p2 { + t.Fatalf("%v !=\n%v", p0, p2) + } + } +} + +func BenchmarkUnpackLeGamma1(b *testing.B) { + var p common.Poly + var buf [PolyLeGamma1Size]byte + for i := 0; i < b.N; i++ { + PolyUnpackLeGamma1(&p, buf[:]) + } +} + +func TestPolyPackLeGamma1(t *testing.T) { + var p0, p1 common.Poly + var seed [64]byte + var buf [PolyLeGamma1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniformLeGamma1(&p0, &seed, i) + p0.Normalize() + + PolyPackLeGamma1(&p0, buf[:]) + PolyUnpackLeGamma1(&p1, buf[:]) + if p0 != p1 { + t.Fatalf("%v != %v", p0, p1) + } + } +} diff --git a/sign/mldsa/mldsa44/internal/params.go b/sign/mldsa/mldsa44/internal/params.go new file mode 100644 index 000000000..fec07cbbe --- /dev/null +++ b/sign/mldsa/mldsa44/internal/params.go @@ -0,0 +1,19 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Name = "ML-DSA-44" + UseAES = false + K = 4 + L = 4 + Eta = 2 + DoubleEtaBits = 3 + Omega = 80 + Tau = 39 + Gamma1Bits = 17 + Gamma2 = 95232 + NIST = true + TRSize = 64 + CTildeSize = 32 +) diff --git a/sign/mldsa/mldsa44/internal/rounding.go b/sign/mldsa/mldsa44/internal/rounding.go new file mode 100644 index 000000000..58123c090 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/rounding.go @@ -0,0 +1,142 @@ +// Code generated from mode3/internal/rounding.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, +// except for when we would have a₁ = (q-1)/α in which case a₁=0 is taken +// and -α/2 ≤ a₀ < 0. Returns a₀ + q. Note 0 ≤ a₁ < (q-1)/α. +// Recall α = 2γ₂. +func decompose(a uint32) (a0plusQ, a1 uint32) { + // a₁ = ⌈a / 128⌉ + a1 = (a + 127) >> 7 + + if Alpha == 523776 { + // 1025/2²² is close enough to 1/4092 so that a₁ + // becomes a/α rounded down. + a1 = ((a1*1025 + (1 << 21)) >> 22) + + // For the corner-case a₁ = (q-1)/α = 16, we have to set a₁=0. + a1 &= 15 + } else if Alpha == 190464 { + // 1488/2²⁴ is close enough to 1/1488 so that a₁ + // becomes a/α rounded down. + a1 = ((a1 * 11275) + (1 << 23)) >> 24 + + // For the corner-case a₁ = (q-1)/α = 44, we have to set a₁=0. + a1 ^= uint32(int32(43-a1)>>31) & a1 + } else { + panic("unsupported α") + } + + a0plusQ = a - a1*Alpha + + // In the corner-case, when we set a₁=0, we will incorrectly + // have a₀ > (q-1)/2 and we'll need to subtract q. As we + // return a₀ + q, that comes down to adding q if a₀ < (q-1)/2. + a0plusQ += uint32(int32(a0plusQ-(common.Q-1)/2)>>31) & common.Q + + return +} + +// Assume 0 ≤ r, f < Q with ‖f‖_∞ ≤ α/2. Decompose r as r = r1*α + r0 as +// computed by decompose(). Write r' := r - f (mod Q). Now, decompose +// r'=r-f again as r' = r'1*α + r'0 using decompose(). As f is small, we +// have r'1 = r1 + h, where h ∈ {-1, 0, 1}. makeHint() computes |h| +// given z0 := r0 - f (mod Q) and r1. With |h|, which is called the hint, +// we can reconstruct r1 using only r' = r - f, which is done by useHint(). +// To wit: +// +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// +// Assumes 0 ≤ z0 < Q. +func makeHint(z0, r1 uint32) uint32 { + // If -α/2 < r0 - f ≤ α/2, then r1*α + r0 - f is a valid decomposition of r' + // with the restrictions of decompose() and so r'1 = r1. So the hint + // should be 0. This is covered by the first two inequalities. + // There is one other case: if r0 - f = -α/2, then r1*α + r0 - f is also + // a valid decomposition if r1 = 0. In the other cases a one is carried + // and the hint should be 1. + if z0 <= Gamma2 || z0 > common.Q-Gamma2 || (z0 == common.Q-Gamma2 && r1 == 0) { + return 0 + } + return 1 +} + +// Uses the hint created by makeHint() to reconstruct r1 from r'=r-f; see +// documentation of makeHint() for context. +// Assumes 0 ≤ r' < Q. +func useHint(rp uint32, hint uint32) uint32 { + rp0plusQ, rp1 := decompose(rp) + if hint == 0 { + return rp1 + } + if rp0plusQ > common.Q { + return (rp1 + 1) & 15 + } + return (rp1 - 1) & 15 +} + +// Sets p to the hint polynomial for p0 the modified low bits and p1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint polynomial. +func PolyMakeHint(p, p0, p1 *common.Poly) (pop uint32) { + for i := 0; i < common.N; i++ { + h := makeHint(p0[i], p1[i]) + pop += h + p[i] = h + } + return +} + +// Computes corrections to the high bits of the polynomial q according +// to the hints in h and sets p to the corrected high bits. Returns p. +func PolyUseHint(p, q, hint *common.Poly) { + var q0PlusQ common.Poly + + // See useHint() and makeHint() for an explanation. We reimplement it + // here so that we can call Poly.Decompose(), which might be way faster + // than calling decompose() in a loop (for instance when having AVX2.) + + PolyDecompose(q, &q0PlusQ, p) + + for i := 0; i < common.N; i++ { + if hint[i] == 0 { + continue + } + if Gamma2 == 261888 { + if q0PlusQ[i] > common.Q { + p[i] = (p[i] + 1) & 15 + } else { + p[i] = (p[i] - 1) & 15 + } + } else if Gamma2 == 95232 { + if q0PlusQ[i] > common.Q { + if p[i] == 43 { + p[i] = 0 + } else { + p[i]++ + } + } else { + if p[i] == 0 { + p[i] = 43 + } else { + p[i]-- + } + } + } else { + panic("unsupported γ₂") + } + } +} + +// Splits each of the coefficients of p using decompose. +func PolyDecompose(p, p0PlusQ, p1 *common.Poly) { + for i := 0; i < common.N; i++ { + p0PlusQ[i], p1[i] = decompose(p[i]) + } +} diff --git a/sign/mldsa/mldsa44/internal/rounding_test.go b/sign/mldsa/mldsa44/internal/rounding_test.go new file mode 100644 index 000000000..ad653ca3f --- /dev/null +++ b/sign/mldsa/mldsa44/internal/rounding_test.go @@ -0,0 +1,81 @@ +// Code generated from mode3/internal/rounding_test.go by gen.go + +package internal + +import ( + "flag" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") + +func TestDecompose(t *testing.T) { + for a := uint32(0); a < common.Q; a++ { + a0PlusQ, a1 := decompose(a) + a0 := int32(a0PlusQ) - int32(common.Q) + recombined := a0 + int32(Alpha*a1) + if a1 == 0 && recombined < 0 { + recombined += common.Q + if -(Alpha/2) > a0 || a0 >= 0 { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } else { + if (-(Alpha / 2) >= a0) || (a0 > Alpha/2) { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } + if int32(a) != recombined { + t.Fatalf("decompose(%v) doesn't recombine %v %v", a, a0, a1) + } + } +} + +func TestMakeHint(t *testing.T) { + if !*runVeryLongTest { + t.SkipNow() + } + for w := uint32(0); w < common.Q; w++ { + w0, w1 := decompose(w) + for fn := uint32(0); fn <= Gamma2; fn++ { + fsign := false + for { + var f uint32 + if fsign { + if fn == 0 { + break + } + f = common.Q - fn + } else { + f = fn + } + + hint := makeHint(common.ReduceLe2Q(w0+common.Q-f), w1) + w1p := useHint(common.ReduceLe2Q(w+common.Q-f), hint) + if w1p != w1 { + t.Fatal() + } + + if fsign { + break + } + fsign = true + } + } + } +} + +func BenchmarkDecompose(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyDecompose(&p, &p0, &p1) + } +} + +func BenchmarkMakeHint(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyMakeHint(&p, &p0, &p1) + } +} diff --git a/sign/mldsa/mldsa44/internal/sample.go b/sign/mldsa/mldsa44/internal/sample.go new file mode 100644 index 000000000..62c261332 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/sample.go @@ -0,0 +1,370 @@ +// Code generated from mode3/internal/sample.go by gen.go + +package internal + +import ( + "encoding/binary" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/simd/keccakf1600" +) + +// DeriveX4Available indicates whether the system supports the quick fourway +// sampling variants like PolyDeriveUniformX4. +var DeriveX4Available = keccakf1600.IsEnabledX4() && !UseAES + +// For each i, sample ps[i] uniformly from the given seed and nonces[i]. +// ps[i] may be nil and is ignored in that case. +// +// Can only be called when DeriveX4Available is true. +func PolyDeriveUniformX4(ps [4]*common.Poly, seed *[32]byte, nonces [4]uint16) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 4; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // Absorb the nonces, the SHAKE128 domain separator (0b1111), the + // start of the padding (0b...001) and the end of the padding 0b100... + // Recall that the rate of SHAKE128 is 168 --- i.e. 21 uint64s. + for j := 0; j < 4; j++ { + state[4*4+j] = uint64(nonces[j]) | (0x1f << 16) + state[20*4+j] = 0x80 << 56 + } + + var idx [4]int // indices into ps + for j := 0; j < 4; j++ { + if ps[j] == nil { + idx[j] = common.N // mark nil polynomial as completed + } + } + + done := false + for !done { + // Applies KeccaK-f[1600] to state to get the next 21 uint64s of each + // of the four SHAKE128 streams. + perm.Permute() + + done = true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + for i := 0; i < 7; i++ { + var t [8]uint32 + t[0] = uint32(state[i*3*4+j] & 0x7fffff) + t[1] = uint32((state[i*3*4+j] >> 24) & 0x7fffff) + t[2] = uint32((state[i*3*4+j] >> 48) | + ((state[(i*3+1)*4+j] & 0x7f) << 16)) + t[3] = uint32((state[(i*3+1)*4+j] >> 8) & 0x7fffff) + t[4] = uint32((state[(i*3+1)*4+j] >> 32) & 0x7fffff) + t[5] = uint32((state[(i*3+1)*4+j] >> 56) | + ((state[(i*3+2)*4+j] & 0x7fff) << 8)) + t[6] = uint32((state[(i*3+2)*4+j] >> 16) & 0x7fffff) + t[7] = uint32((state[(i*3+2)*4+j] >> 40) & 0x7fffff) + + for k := 0; k < 8; k++ { + if t[k] < common.Q { + ps[j][idx[j]] = t[k] + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + } + done = false + } + } +} + +// Sample p uniformly from the given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniform(p *common.Poly, seed *[32]byte, nonce uint16) { + var i, length int + var buf [12 * 16]byte // fits 168B SHAKE-128 rate and 12 16B AES blocks + + if UseAES { + length = 12 * 16 + } else { + length = 168 + } + + sample := func() { + // Note that 3 divides into 168 and 12*16, so we use up buf completely. + for j := 0; j < length && i < common.N; j += 3 { + t := (uint32(buf[j]) | (uint32(buf[j+1]) << 8) | + (uint32(buf[j+2]) << 16)) & 0x7fffff + + // We use rejection sampling + if t < common.Q { + p[i] = t + i++ + } + } + } + + if UseAES { + h := common.NewAesStream128(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [32 + 2]byte // 32 byte seed + uint16 nonce + h := sha3.NewShake128() + copy(iv[:32], seed[:]) + iv[32] = uint8(nonce) + iv[33] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:168]) + sample() + } + } +} + +// Sample p uniformly with coefficients of norm less than or equal η, +// using the given seed and nonce. +// +// p will not be normalized, but will have coefficients in [q-η,q+η]. +func PolyDeriveUniformLeqEta(p *common.Poly, seed *[64]byte, nonce uint16) { + // Assumes 2 < η < 8. + var i, length int + var buf [9 * 16]byte // fits 136B SHAKE-256 rate and 9 16B AES blocks + + if UseAES { + length = 9 * 16 + } else { + length = 136 + } + + sample := func() { + // We use rejection sampling + for j := 0; j < length && i < common.N; j++ { + t1 := uint32(buf[j]) & 15 + t2 := uint32(buf[j]) >> 4 + if Eta == 2 { // branch is eliminated by compiler + if t1 <= 14 { + t1 -= ((205 * t1) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 14 && i < common.N { + t2 -= ((205 * t2) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t2 + i++ + } + } else if Eta == 4 { + if t1 <= 2*Eta { + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 2*Eta && i < common.N { + p[i] = common.Q + Eta - t2 + i++ + } + } else { + panic("unsupported η") + } + } + } + + if UseAES { + h := common.NewAesStream256(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [64 + 2]byte // 64 byte seed + uint16 nonce + + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + + // 136 is SHAKE-256 rate + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:136]) + sample() + } + } +} + +// Sample v[i] uniformly with coefficients in (-γ₁,…,γ₁] using the +// given seed and nonce+i +// +// p will be normalized. +func VecLDeriveUniformLeGamma1(v *VecL, seed *[64]byte, nonce uint16) { + for i := 0; i < L; i++ { + PolyDeriveUniformLeGamma1(&v[i], seed, nonce+uint16(i)) + } +} + +// Sample p uniformly with coefficients in (-γ₁,…,γK1s] using the +// given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { + var buf [PolyLeGamma1Size]byte + + if UseAES { + h := common.NewAesStream256(seed, nonce) + h.SqueezeInto(buf[:]) + } else { + var iv [66]byte + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + _, _ = h.Read(buf[:]) + } + + PolyUnpackLeGamma1(p, buf[:]) +} + +// For each i, sample ps[i] uniformly with τ non-zero coefficients in {q-1,1} +// using the given seed and w1[i]. ps[i] may be nil and is ignored +// in that case. ps[i] will be normalized. +// +// Can only be called when DeriveX4Available is true. +// +// This function is currently not used (yet). +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 32/8; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // SHAKE256 domain separator and padding + for j := 0; j < 4; j++ { + state[(32/8)*4+j] ^= 0x1f + state[16*4+j] ^= 0x80 << 56 + } + perm.Permute() + + var signs [4]uint64 + var idx [4]uint16 // indices into ps + + for j := 0; j < 4; j++ { + if ps[j] != nil { + signs[j] = state[j] + *ps[j] = common.Poly{} // zero ps[j] + idx[j] = common.N - Tau + } else { + idx[j] = common.N // mark as completed + } + } + + stateOffset := 1 + for { + done := true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + + for i := stateOffset; i < 17; i++ { + var bs [8]byte + binary.LittleEndian.PutUint64(bs[:], state[4*i+j]) + for k := 0; k < 8; k++ { + b := uint16(bs[k]) + + if b > idx[j] { + continue + } + + ps[j][idx[j]] = ps[j][b] + ps[j][b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + ps[j][b] ^= uint32((-(signs[j] & 1)) & (1 | (common.Q - 1))) + signs[j] >>= 1 + + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + + done = false + } + + if done { + break + } + + perm.Permute() + stateOffset = 0 + } +} + +// Samples p uniformly with τ non-zero coefficients in {q-1,1}. +// +// The polynomial p will be normalized. +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { + var buf [136]byte // SHAKE-256 rate is 136 + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(buf[:]) + + // Essentially we generate a sequence of τ ones or minus ones, + // prepend 196 zeroes and shuffle the concatenation using the + // usual algorithm (Fisher--Yates.) + signs := binary.LittleEndian.Uint64(buf[:]) + bufOff := 8 // offset into buf + + *p = common.Poly{} // zero p + for i := uint16(common.N - Tau); i < common.N; i++ { + var b uint16 + + // Find location of where to move the new coefficient to using + // rejection sampling. + for { + if bufOff >= 136 { + _, _ = h.Read(buf[:]) + bufOff = 0 + } + + b = uint16(buf[bufOff]) + bufOff++ + + if b <= i { + break + } + } + + p[i] = p[b] + p[b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + p[b] ^= uint32((-(signs & 1)) & (1 | (common.Q - 1))) + signs >>= 1 + } +} diff --git a/sign/mldsa/mldsa44/internal/sample_test.go b/sign/mldsa/mldsa44/internal/sample_test.go new file mode 100644 index 000000000..2059599eb --- /dev/null +++ b/sign/mldsa/mldsa44/internal/sample_test.go @@ -0,0 +1,266 @@ +// Code generated from mode3/internal/sample_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestVectorDeriveUniform(t *testing.T) { + var p, p2 common.Poly + var seed [32]byte + if UseAES { + p2 = common.Poly{ + 6724291, 310295, 6949524, 4464039, 1482136, 2522903, + 7025059, 3006320, 7286364, 7516512, 3361305, 1955529, + 4765954, 1725325, 6933066, 4299100, 6625173, 4272792, + 583034, 4971409, 2259140, 7715362, 3975394, 2341624, + 5481174, 8150082, 365246, 5491939, 1083120, 7517301, + 3104783, 2475292, 184149, 6425226, 4591622, 5964030, + 4729604, 5471092, 1828227, 1082044, 2516245, 1692580, + 3274844, 5443294, 7256740, 4989638, 3191250, 7479519, + 5124211, 5603858, 1230692, 2513454, 2828034, 4254312, + 1512596, 5245430, 5517392, 2814840, 932545, 6826733, + 3511094, 4075348, 3233981, 7268882, 2913733, 4870249, + 4123492, 8124406, 4016949, 5478752, 2750895, 603525, + 5724798, 3985430, 3483012, 6434230, 3136996, 8297976, + 4107616, 7307748, 6962904, 7544473, 1193110, 3448595, + 4814773, 5607932, 8221314, 1054046, 1541208, 1866050, + 8227412, 2925778, 5293953, 2065416, 4972769, 3616283, + 7990594, 1105530, 7121836, 1170740, 7417431, 633146, + 253820, 7235019, 3539504, 6807707, 451390, 5481526, + 2859902, 1063061, 4579730, 7126652, 7033767, 4294814, + 1414604, 7620048, 1953268, 8304556, 1156814, 1182881, + 5311519, 3057534, 5277666, 682843, 2070398, 2874278, + 4859533, 6376664, 6694074, 1590242, 2620706, 8331066, + 5643845, 5037538, 2891516, 7004879, 3754327, 5031296, + 5463118, 2420870, 8116529, 5517696, 7435129, 3873963, + 710407, 713806, 175647, 4274571, 2655021, 7319503, + 3027243, 7129679, 4213435, 2429323, 4643873, 4568526, + 649664, 1720514, 6497260, 2683517, 7672754, 7105190, + 3148405, 5898369, 5667677, 8050874, 1587139, 7315260, + 4337416, 2202680, 2338714, 557467, 6752058, 2469794, + 485071, 1617604, 3590498, 2151466, 2005823, 7727956, + 7776292, 6783433, 6787146, 1732833, 3596857, 7436284, + 4483349, 4970142, 4472608, 6478342, 1236215, 5695744, + 2280717, 2889355, 3233946, 5187812, 978685, 5177364, + 2922353, 4824807, 5302883, 6739803, 8092453, 5883903, + 816553, 6041174, 8317591, 1459178, 5332455, 1835058, + 1368601, 2820950, 3479224, 2589540, 7992934, 3421045, + 4657128, 8292902, 4153567, 3553988, 7830320, 6722913, + 2555309, 4149801, 8328975, 1560545, 7757473, 3106458, + 4310856, 7135453, 3481032, 652626, 1841361, 8126828, + 6250018, 300536, 7380070, 8174419, 1418793, 6208185, + 3906256, 6679016, 1605701, 3561489, 5819724, 5746996, + 8044214, 7087187, 7102330, 4962927, 4253983, 7108567, + 4119736, 6584065, 441634, 6941656, + } + } else { + p2 = common.Poly{ + 2901364, 562527, 5258502, 3885002, 4190126, 4460268, 6884052, + 3514511, 5383040, 213206, 2155865, 5179607, 3551954, 2312357, + 6066350, 8126097, 1179080, 4787182, 6552182, 6713644, + 1561067, 7626063, 7859743, 5052321, 7032876, 7815031, 157938, + 1865184, 490802, 5717642, 3451902, 7000218, 3743250, 1677431, + 1875427, 5596150, 671623, 3819041, 6247594, 1014875, 4933545, + 7122446, 6682963, 3388398, 3335295, 943002, 1145083, 3113071, + 105967, 1916675, 7474561, 1107006, 700548, 2147909, 1603855, + 5049181, 437882, 6118899, 5656914, 6731065, 3066622, 865453, + 5427634, 981549, 4650873, 861291, 4003872, 5104220, 6171453, + 3723302, 7426315, 6137283, 4874820, 6052561, 53441, 5032874, + 5614778, 2248550, 1756499, 8280764, 8263880, 7600081, + 5118374, 795344, 7543392, 6869925, 1841187, 4181568, 584562, + 7483939, 4938664, 6863397, 5126354, 5218129, 6236086, + 4149293, 379169, 4368487, 7490569, 3409215, 1580463, 3081737, + 1278732, 7109719, 7371700, 2097931, 399836, 1700274, 7188595, + 6830029, 1548850, 6593138, 6849097, 1518037, 2859442, + 7772265, 7325153, 3281191, 7856131, 4995056, 4684325, + 1351194, 8223904, 6817307, 2484146, 131782, 397032, 7436778, + 7973479, 3171829, 5624626, 3540123, 7150120, 8313283, + 3604714, 1043574, 117692, 7797783, 7909392, 903315, 7335342, + 7501562, 5826142, 2709813, 8245473, 2369045, 2782257, + 5762833, 6474114, 6862031, 424522, 594248, 2626630, 7659983, + 5642869, 4075194, 1592129, 245547, 5271031, 3205046, 982375, + 267873, 1286496, 7230481, 3208972, 7485411, 676111, 4944500, + 2959742, 5934456, 1414847, 6067948, 1709895, 4648315, 126008, + 8258986, 2183134, 2302072, 4674924, 4306056, 7465311, + 6500270, 4247428, 4016815, 4973426, 294287, 2456847, 3289700, + 2732169, 1159447, 5569724, 140001, 3237977, 8007761, 5874533, + 255652, 3119586, 2102434, 6248250, 8152822, 8006066, 7708625, + 6997719, 6260212, 6186962, 6636650, 7836834, 7998017, + 2061516, 1197591, 1706544, 733027, 2392907, 2700000, 8254598, + 4488002, 160495, 2985325, 2036837, 2703633, 6406550, 3579947, + 6195178, 5552390, 6804584, 6305468, 5731980, 6095195, + 3323409, 1322661, 6690942, 3374630, 5615167, 479044, 3136054, + 4380418, 2833144, 7829577, 1770522, 6056687, 240415, 14780, + 3740517, 5224226, 3547288, 2083124, 4699398, 3654239, + 5624978, 585593, 3655369, 2281739, 3338565, 1908093, 7784706, + 4352830, + } + } + for i := 0; i < 32; i++ { + seed[i] = byte(i) + } + PolyDeriveUniform(&p, &seed, 30000) + if p != p2 { + t.Fatalf("%v != %v", p, p2) + } +} + +func TestDeriveUniform(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniform(&p, &seed, uint16(i)) + if !PolyNormalized(&p) { + t.Fatal() + } + } +} + +func TestDeriveUniformLeqEta(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeqEta(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if p[j] < common.Q-Eta || p[j] > common.Q+Eta { + t.Fatal() + } + } + } +} + +func TestDeriveUniformLeGamma1(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if (p[j] > Gamma1 && p[j] <= common.Q-Gamma1) || p[j] >= common.Q { + t.Fatal() + } + } + } +} + +func TestDeriveUniformBall(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformBall(&p, seed[:]) + nonzero := 0 + for j := 0; j < common.N; j++ { + if p[j] != 0 { + if p[j] != 1 && p[j] != common.Q-1 { + t.Fatal() + } + nonzero++ + } + } + if nonzero != Tau { + t.Fatal() + } + } +} + +func TestDeriveUniformX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + nonces := [4]uint16{12345, 54321, 13532, 37377} + + for i := 0; i < len(seed); i++ { + seed[i] = byte(i) + } + + PolyDeriveUniformX4([4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, &seed, + nonces) + for i := 0; i < 4; i++ { + PolyDeriveUniform(&p, &seed, nonces[i]) + if ps[i] != p { + t.Fatal() + } + } +} + +func TestDeriveUniformBallX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + PolyDeriveUniformBallX4( + [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, + seed[:], + ) + for j := 0; j < 4; j++ { + PolyDeriveUniformBall(&p, seed[:]) + if ps[j] != p { + t.Fatalf("%d\n%v\n%v", j, ps[j], p) + } + } +} + +func BenchmarkPolyDeriveUniformBall(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBall(&p, seed[:]) + } +} + +func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBallX4( + [4]*common.Poly{&p, &p, &p, &p}, + seed[:], + ) + } +} + +func BenchmarkPolyDeriveUniform(b *testing.B) { + var seed [32]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniform(&p, &seed, uint16(i)) + } +} + +func BenchmarkPolyDeriveUniformX4(b *testing.B) { + if !DeriveX4Available { + b.SkipNow() + } + var seed [32]byte + var p [4]common.Poly + for i := 0; i < b.N; i++ { + nonce := uint16(4 * i) + PolyDeriveUniformX4([4]*common.Poly{&p[0], &p[1], &p[2], &p[3]}, + &seed, [4]uint16{nonce, nonce + 1, nonce + 2, nonce + 3}) + } +} + +func BenchmarkPolyDeriveUniformLeGamma1(b *testing.B) { + var seed [64]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + } +} diff --git a/sign/mldsa/mldsa44/internal/vec.go b/sign/mldsa/mldsa44/internal/vec.go new file mode 100644 index 000000000..d07d3b245 --- /dev/null +++ b/sign/mldsa/mldsa44/internal/vec.go @@ -0,0 +1,281 @@ +// Code generated from mode3/internal/vec.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A vector of L polynomials. +type VecL [L]common.Poly + +// A vector of K polynomials. +type VecK [K]common.Poly + +// Normalize the polynomials in this vector. +func (v *VecL) Normalize() { + for i := 0; i < L; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecL) NormalizeAssumingLe2Q() { + for i := 0; i < L; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecL) Add(w, u *VecL) { + for i := 0; i < L; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecL) NTT() { + for i := 0; i < L; i++ { + v[i].NTT() + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecL) Exceeds(bound uint32) bool { + for i := 0; i < L; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Power2Round(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Decompose(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecL) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeqEta(). +func (v *VecL) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sequentially packs each polynomial using PolyPackLeGamma1(). +func (v *VecL) PackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeGamma1(). +func (v *VecL) UnpackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Normalize the polynomials in this vector. +func (v *VecK) Normalize() { + for i := 0; i < K; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecK) NormalizeAssumingLe2Q() { + for i := 0; i < K; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecK) Add(w, u *VecK) { + for i := 0; i < K; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecK) Exceeds(bound uint32) bool { + for i := 0; i < K; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Power2Round(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Decompose(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sets v to the hint vector for v0 the modified low bits and v1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint vector. +func (v *VecK) MakeHint(v0, v1 *VecK) (pop uint32) { + for i := 0; i < K; i++ { + pop += PolyMakeHint(&v[i], &v0[i], &v1[i]) + } + return +} + +// Computes corrections to the high bits of the polynomials in the vector +// w using the hints in h and sets v to the corrected high bits. Returns v. +// See useHint(). +func (v *VecK) UseHint(q, hint *VecK) *VecK { + for i := 0; i < K; i++ { + PolyUseHint(&v[i], &q[i], &hint[i]) + } + return v +} + +// Sequentially packs each polynomial using Poly.PackT1(). +func (v *VecK) PackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sets v to the vector packed into buf by PackT1(). +func (v *VecK) UnpackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sequentially packs each polynomial using Poly.PackT0(). +func (v *VecK) PackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sets v to the vector packed into buf by PackT0(). +func (v *VecK) UnpackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecK) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecK.PackLeqEta(). +func (v *VecK) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecK) NTT() { + for i := 0; i < K; i++ { + v[i].NTT() + } +} + +// Sequentially packs each polynomial using PolyPackW1(). +func (v *VecK) PackW1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackW1(&v[i], buf[offset:]) + offset += PolyW1Size + } +} + +// Sets v to a - b. +// +// Warning: assumes coefficients of the polynomials of b are less than 2q. +func (v *VecK) Sub(a, b *VecK) { + for i := 0; i < K; i++ { + v[i].Sub(&a[i], &b[i]) + } +} + +// Sets v to 2ᵈ w without reducing. +func (v *VecK) MulBy2toD(w *VecK) { + for i := 0; i < K; i++ { + v[i].MulBy2toD(&w[i]) + } +} + +// Applies InvNTT componentwise. See Poly.InvNTT() for details. +func (v *VecK) InvNTT() { + for i := 0; i < K; i++ { + v[i].InvNTT() + } +} + +// Applies Poly.ReduceLe2Q() componentwise. +func (v *VecK) ReduceLe2Q() { + for i := 0; i < K; i++ { + v[i].ReduceLe2Q() + } +} diff --git a/sign/mldsa/mldsa65/dilithium.go b/sign/mldsa/mldsa65/dilithium.go new file mode 100644 index 000000000..9411e84a7 --- /dev/null +++ b/sign/mldsa/mldsa65/dilithium.go @@ -0,0 +1,182 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + + +// mldsa65 implements NIST signature scheme ML-DSA-65 as defined in FIPS204. + +package mldsa65 + +import ( + "crypto" + "errors" + "io" + + common "github.com/cloudflare/circl/sign/internal/dilithium" + + "github.com/cloudflare/circl/sign/mldsa/mldsa65/internal" + +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = common.SeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of ML-DSA-65 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of ML-DSA-65 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// SignTo signs the given message and writes the signature into signature. +// It will panic if signature is not of length at least SignatureSize. +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + internal.SignTo( + (*internal.PrivateKey)(sk), + msg, + signature, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + (*internal.PublicKey)(pk), + msg, + signature, + ) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + +// Packs the public key. +func (pk *PublicKey) Bytes() []byte { + var buf [PublicKeySize]byte + pk.Pack(&buf) + return buf[:] +} + +// Packs the private key. +func (sk *PrivateKey) Bytes() []byte { + var buf [PrivateKeySize]byte + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mldsa65.PublicKeySize bytes") + } + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PrivateKeySize { + return errors.New("packed private key must be of mldsa65.PrivateKeySize bytes") + } + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. rand is ignored. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level SignTo function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + var sig [SignatureSize]byte + + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("dilithium: cannot sign hashed message") + } + + SignTo(sk, msg, sig[:]) + return sig[:], nil +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*PublicKey)((*internal.PrivateKey)(sk).Public()) +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mldsa/mldsa65/internal/dilithium.go b/sign/mldsa/mldsa65/internal/dilithium.go new file mode 100644 index 000000000..7869d0475 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/dilithium.go @@ -0,0 +1,482 @@ +// Code generated from mode3/internal/dilithium.go by gen.go + +package internal + +import ( + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +const ( + // Size of a packed polynomial of norm ≤η. + // (Note that the formula is not valid in general.) + PolyLeqEtaSize = (common.N * DoubleEtaBits) / 8 + + // β = τη, the maximum size of c s₂. + Beta = Tau * Eta + + // γ₁ range of y + Gamma1 = 1 << Gamma1Bits + + // Size of packed polynomial of norm <γ₁ such as z + PolyLeGamma1Size = (Gamma1Bits + 1) * common.N / 8 + + // α = 2γ₂ parameter for decompose + Alpha = 2 * Gamma2 + + // Size of a packed private key + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + + // Size of a packed public key + PublicKeySize = 32 + common.PolyT1Size*K + + // Size of a packed signature + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize + + // Size of packed w₁ + PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 +) + +// PublicKey is the type of Dilithium public keys. +type PublicKey struct { + rho [32]byte + t1 VecK + + // Cached values + t1p [common.PolyT1Size * K]byte + A *Mat + tr *[TRSize]byte +} + +// PrivateKey is the type of Dilithium private keys. +type PrivateKey struct { + rho [32]byte + key [32]byte + s1 VecL + s2 VecK + t0 VecK + tr [TRSize]byte + + // Cached values + A Mat // ExpandA(ρ) + s1h VecL // NTT(s₁) + s2h VecK // NTT(s₂) + t0h VecK // NTT(t₀) +} + +type unpackedSignature struct { + z VecL + hint VecK + c [CTildeSize]byte +} + +// Packs the signature into buf. +func (sig *unpackedSignature) Pack(buf []byte) { + copy(buf[:], sig.c[:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) +} + +// Sets sig to the signature encoded in the buffer. +// +// Returns whether buf contains a properly packed signature. +func (sig *unpackedSignature) Unpack(buf []byte) bool { + if len(buf) < SignatureSize { + return false + } + copy(sig.c[:], buf[:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) + if sig.z.Exceeds(Gamma1 - Beta) { + return false + } + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { + return false + } + return true +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:32], pk.rho[:]) + copy(buf[32:], pk.t1p[:]) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.rho[:], buf[:32]) + copy(pk.t1p[:], buf[32:]) + + pk.t1.UnpackT1(pk.t1p[:]) + pk.A = new(Mat) + pk.A.Derive(&pk.rho) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + pk.tr = new([TRSize]byte) + h := sha3.NewShake256() + _, _ = h.Write(buf[:]) + _, _ = h.Read(pk.tr[:]) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:32], sk.rho[:]) + copy(buf[32:64], sk.key[:]) + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize + sk.s1.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.PackT0(buf[offset:]) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.rho[:], buf[:32]) + copy(sk.key[:], buf[32:64]) + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize + sk.s1.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.UnpackT0(buf[offset:]) + + // Cached values + sk.A.Derive(&sk.rho) + sk.t0h = sk.t0 + sk.t0h.NTT() + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [32]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(&seed) + return pk, sk, nil +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[common.SeedSize]byte) (*PublicKey, *PrivateKey) { + var eSeed [128]byte // expanded seed + var pk PublicKey + var sk PrivateKey + var sSeed [64]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(eSeed[:]) + + copy(pk.rho[:], eSeed[:32]) + copy(sSeed[:], eSeed[32:96]) + copy(sk.key[:], eSeed[96:]) + copy(sk.rho[:], pk.rho[:]) + + sk.A.Derive(&pk.rho) + + for i := uint16(0); i < L; i++ { + PolyDeriveUniformLeqEta(&sk.s1[i], &sSeed, i) + } + + for i := uint16(0); i < K; i++ { + PolyDeriveUniformLeqEta(&sk.s2[i], &sSeed, i+L) + } + + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() + + sk.computeT0andT1(&sk.t0, &pk.t1) + + sk.t0h = sk.t0 + sk.t0h.NTT() + + // Complete public key far enough to be packed + pk.t1.PackT1(pk.t1p[:]) + pk.A = &sk.A + + // Finish private key + var packedPk [PublicKeySize]byte + pk.Pack(&packedPk) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + h.Reset() + _, _ = h.Write(packedPk[:]) + _, _ = h.Read(sk.tr[:]) + + // Finish cache of public key + pk.tr = &sk.tr + + return &pk, &sk +} + +// Computes t0 and t1 from sk.s1h, sk.s2 and sk.A. +func (sk *PrivateKey) computeT0andT1(t0, t1 *VecK) { + var t VecK + + // Set t to A s₁ + s₂ + for i := 0; i < K; i++ { + PolyDotHat(&t[i], &sk.A[i], &sk.s1h) + t[i].ReduceLe2Q() + t[i].InvNTT() + } + t.Add(&t, &sk.s2) + t.Normalize() + + // Compute t₀, t₁ = Power2Round(t) + t.Power2Round(t0, t1) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + var sig unpackedSignature + var mu [64]byte + var zh VecL + var Az, Az2dct1, w1 VecK + var ch common.Poly + var cp [CTildeSize]byte + var w1Packed [PolyW1Size * K]byte + + // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β + // and ensured that there at most ω ones in pk.hint. + if !sig.Unpack(signature) { + return false + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(pk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // Compute Az + zh = sig.z + zh.NTT() + + for i := 0; i < K; i++ { + PolyDotHat(&Az[i], &pk.A[i], &zh) + } + + // Next, we compute Az - 2ᵈ·c·t₁. + // Note that the coefficients of t₁ are bounded by 256 = 2⁹, + // so the coefficients of Az2dct1 will bounded by 2⁹⁺ᵈ = 2²³ < 2q, + // which is small enough for NTT(). + Az2dct1.MulBy2toD(&pk.t1) + Az2dct1.NTT() + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + for i := 0; i < K; i++ { + Az2dct1[i].MulHat(&Az2dct1[i], &ch) + } + Az2dct1.Sub(&Az, &Az2dct1) + Az2dct1.ReduceLe2Q() + Az2dct1.InvNTT() + Az2dct1.NormalizeAssumingLe2Q() + + // UseHint(pk.hint, Az - 2ᵈ·c·t₁) + // = UseHint(pk.hint, w - c·s₂ + c·t₀) + // = UseHint(pk.hint, r + c·t₀) + // = r₁ = w₁. + w1.UseHint(&Az2dct1, &sig.hint) + w1.PackW1(w1Packed[:]) + + // c' = H(μ, w₁) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(cp[:]) + + return sig.c == cp +} + +// SignTo signs the given message and writes the signature into signature. +// +//nolint:funlen +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + var mu, rhop [64]byte + var w1Packed [PolyW1Size * K]byte + var y, yh VecL + var w, w0, w1, w0mcs2, ct0, w0mcs2pct0 VecK + var ch common.Poly + var yNonce uint16 + var sig unpackedSignature + + if len(signature) < SignatureSize { + panic("Signature does not fit in that byteslice") + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(sk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // ρ' = CRH(key ‖ μ) + h.Reset() + _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } + _, _ = h.Write(mu[:]) + _, _ = h.Read(rhop[:]) + + // Main rejection loop + attempt := 0 + for { + attempt++ + if attempt >= 576 { + // Depending on the mode, one try has a chance between 1/7 and 1/4 + // of succeeding. Thus it is safe to say that 576 iterations + // are enough as (6/7)⁵⁷⁶ < 2⁻¹²⁸. + panic("This should only happen 1 in 2^{128}: something is wrong.") + } + + // y = ExpandMask(ρ', key) + VecLDeriveUniformLeGamma1(&y, &rhop, yNonce) + yNonce += uint16(L) + + // Set w to A y + yh = y + yh.NTT() + for i := 0; i < K; i++ { + PolyDotHat(&w[i], &sk.A[i], &yh) + w[i].ReduceLe2Q() + w[i].InvNTT() + } + + // Decompose w into w₀ and w₁ + w.NormalizeAssumingLe2Q() + w.Decompose(&w0, &w1) + + // c~ = H(μ ‖ w₁) + w1.PackW1(w1Packed[:]) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(sig.c[:]) + + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + + // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. + // + // By Lemma 3 of the specification this is equivalent to checking that + // both ‖ r₀ ‖_∞ < γ₂ - β and r₁ = w₁, for the decomposition + // w - c·s₂ = r₁ α + r₀ as computed by decompose(). + // See also §4.1 of the specification. + for i := 0; i < K; i++ { + w0mcs2[i].MulHat(&ch, &sk.s2h[i]) + w0mcs2[i].InvNTT() + } + w0mcs2.Sub(&w0, &w0mcs2) + w0mcs2.Normalize() + + if w0mcs2.Exceeds(Gamma2 - Beta) { + continue + } + + // z = y + c·s₁ + for i := 0; i < L; i++ { + sig.z[i].MulHat(&ch, &sk.s1h[i]) + sig.z[i].InvNTT() + } + sig.z.Add(&sig.z, &y) + sig.z.Normalize() + + // Ensure ‖z‖_∞ < γ₁ - β + if sig.z.Exceeds(Gamma1 - Beta) { + continue + } + + // Compute c·t₀ + for i := 0; i < K; i++ { + ct0[i].MulHat(&ch, &sk.t0h[i]) + ct0[i].InvNTT() + } + ct0.NormalizeAssumingLe2Q() + + // Ensure ‖c·t₀‖_∞ < γ₂. + if ct0.Exceeds(Gamma2) { + continue + } + + // Create the hint to be able to reconstruct w₁ from w - c·s₂ + c·t0. + // Note that we're not using makeHint() in the obvious way as we + // do not know whether ‖ sc·s₂ - c·t₀ ‖_∞ < γ₂. Instead we note + // that our makeHint() is actually the same as a makeHint for a + // different decomposition: + // + // Earlier we ensured indirectly with a check that r₁ = w₁ where + // r = w - c·s₂. Hence r₀ = r - r₁ α = w - c·s₂ - w₁ α = w₀ - c·s₂. + // Thus MakeHint(w₀ - c·s₂ + c·t₀, w₁) = MakeHint(r0 + c·t₀, r₁) + // and UseHint(w - c·s₂ + c·t₀, w₁) = UseHint(r + c·t₀, r₁). + // As we just ensured that ‖ c·t₀ ‖_∞ < γ₂ our usage is correct. + w0mcs2pct0.Add(&w0mcs2, &ct0) + w0mcs2pct0.NormalizeAssumingLe2Q() + hintPop := sig.hint.MakeHint(&w0mcs2pct0, &w1) + if hintPop > Omega { + continue + } + + break + } + + sig.Pack(signature[:]) +} + +// Computes the public key corresponding to this private key. +func (sk *PrivateKey) Public() *PublicKey { + var t0 VecK + pk := &PublicKey{ + rho: sk.rho, + A: &sk.A, + tr: &sk.tr, + } + sk.computeT0andT1(&t0, &pk.t1) + pk.t1.PackT1(pk.t1p[:]) + return pk +} + +// Equal returns whether the two public keys are equal +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.rho == other.rho && pk.t1 == other.t1 +} + +// Equal returns whether the two private keys are equal +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + ret := (subtle.ConstantTimeCompare(sk.rho[:], other.rho[:]) & + subtle.ConstantTimeCompare(sk.key[:], other.key[:]) & + subtle.ConstantTimeCompare(sk.tr[:], other.tr[:])) + + acc := uint32(0) + for i := 0; i < L; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s1[i][j] ^ other.s1[i][j] + } + } + for i := 0; i < K; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s2[i][j] ^ other.s2[i][j] + acc |= sk.t0[i][j] ^ other.t0[i][j] + } + } + return (ret & subtle.ConstantTimeEq(int32(acc), 0)) == 1 +} diff --git a/sign/mldsa/mldsa65/internal/dilithium_test.go b/sign/mldsa/mldsa65/internal/dilithium_test.go new file mode 100644 index 000000000..825e7da08 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/dilithium_test.go @@ -0,0 +1,144 @@ +// Code generated from mode3/internal/dilithium_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Checks whether p is normalized. Only used in tests. +func PolyNormalized(p *common.Poly) bool { + p2 := *p + p2.Normalize() + return p2 == *p +} + +func BenchmarkSkUnpack(b *testing.B) { + var buf [PrivateKeySize]byte + var sk PrivateKey + for i := 0; i < b.N; i++ { + sk.Unpack(&buf) + } +} + +func BenchmarkPkUnpack(b *testing.B) { + var buf [PublicKeySize]byte + var pk PublicKey + for i := 0; i < b.N; i++ { + pk.Unpack(&buf) + } +} + +func BenchmarkVerify(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of verification (as in the reference + // implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + pk, sk := NewKeyFromSeed(&seed) + SignTo(sk, msg[:], sig[:]) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // We should generate a new signature for every verify attempt, + // as this influences the time a little bit. This difference, however, + // is small and generating a new signature in between creates a lot + // pressure on the allocator which makes an accurate measurement hard. + Verify(pk, msg[:], sig[:]) + } +} + +func BenchmarkSign(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of signing (as in the reference implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + _, sk := NewKeyFromSeed(&seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + SignTo(sk, msg[:], sig[:]) + } +} + +func BenchmarkGenerateKey(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + NewKeyFromSeed(&seed) + } +} + +func BenchmarkPublicFromPrivate(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + b.StopTimer() + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, sk := NewKeyFromSeed(&seed) + b.StartTimer() + sk.Public() + } +} + +func TestSignThenVerifyAndPkSkPacking(t *testing.T) { + var seed [common.SeedSize]byte + var sig [SignatureSize]byte + var msg [8]byte + var pkb [PublicKeySize]byte + var skb [PrivateKeySize]byte + var pk2 PublicKey + var sk2 PrivateKey + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + if !sk.Equal(sk) { + t.Fatal() + } + for j := uint64(0); j < 10; j++ { + binary.LittleEndian.PutUint64(msg[:], j) + SignTo(sk, msg[:], sig[:]) + if !Verify(pk, msg[:], sig[:]) { + t.Fatal() + } + } + pk.Pack(&pkb) + pk2.Unpack(&pkb) + if !pk.Equal(&pk2) { + t.Fatal() + } + sk.Pack(&skb) + sk2.Unpack(&skb) + if !sk.Equal(&sk2) { + t.Fatal() + } + } +} + +func TestPublicFromPrivate(t *testing.T) { + var seed [common.SeedSize]byte + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + pk2 := sk.Public() + if !pk.Equal(pk2) { + t.Fatal() + } + } +} + +func TestGamma1Size(t *testing.T) { + var expected int + switch Gamma1Bits { + case 17: + expected = 576 + case 19: + expected = 640 + } + if expected != PolyLeGamma1Size { + t.Fatal() + } +} diff --git a/sign/mldsa/mldsa65/internal/mat.go b/sign/mldsa/mldsa65/internal/mat.go new file mode 100644 index 000000000..ceaf634fa --- /dev/null +++ b/sign/mldsa/mldsa65/internal/mat.go @@ -0,0 +1,59 @@ +// Code generated from mode3/internal/mat.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A k by l matrix of polynomials. +type Mat [K]VecL + +// Expands the given seed to a complete matrix. +// +// This function is called ExpandA in the specification. +func (m *Mat) Derive(seed *[32]byte) { + if !DeriveX4Available { + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + PolyDeriveUniform(&m[i][j], seed, (i<<8)+j) + } + } + return + } + + idx := 0 + var nonces [4]uint16 + var ps [4]*common.Poly + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + nonces[idx] = (i << 8) + j + ps[idx] = &m[i][j] + idx++ + if idx == 4 { + idx = 0 + PolyDeriveUniformX4(ps, seed, nonces) + } + } + } + if idx != 0 { + for i := idx; i < 4; i++ { + ps[i] = nil + } + PolyDeriveUniformX4(ps, seed, nonces) + } +} + +// Set p to the inner product of a and b using pointwise multiplication. +// +// Assumes a and b are in Montgomery form and their coefficients are +// pairwise sufficiently small to multiply, see Poly.MulHat(). Resulting +// coefficients are bounded by 2Lq. +func PolyDotHat(p *common.Poly, a, b *VecL) { + var t common.Poly + *p = common.Poly{} // zero p + for i := 0; i < L; i++ { + t.MulHat(&a[i], &b[i]) + p.Add(&t, p) + } +} diff --git a/sign/mldsa/mldsa65/internal/pack.go b/sign/mldsa/mldsa65/internal/pack.go new file mode 100644 index 000000000..1854b4197 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/pack.go @@ -0,0 +1,270 @@ +// Code generated from mode3/internal/pack.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Writes p with norm less than or equal η into buf, which must be of +// size PolyLeqEtaSize. +// +// Assumes coefficients of p are not normalized, but in [q-η,q+η]. +func PolyPackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + buf[i] = (byte(common.Q+Eta-p[j]) | + byte(common.Q+Eta-p[j+1])<<4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + buf[i] = (byte(common.Q+Eta-p[j]) | + (byte(common.Q+Eta-p[j+1]) << 3) | + (byte(common.Q+Eta-p[j+2]) << 6)) + buf[i+1] = ((byte(common.Q+Eta-p[j+2]) >> 2) | + (byte(common.Q+Eta-p[j+3]) << 1) | + (byte(common.Q+Eta-p[j+4]) << 4) | + (byte(common.Q+Eta-p[j+5]) << 7)) + buf[i+2] = ((byte(common.Q+Eta-p[j+5]) >> 1) | + (byte(common.Q+Eta-p[j+6]) << 2) | + (byte(common.Q+Eta-p[j+7]) << 5)) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Sets p to the polynomial of norm less than or equal η encoded in the +// given buffer of size PolyLeqEtaSize. +// +// Output coefficients of p are not normalized, but in [q-η,q+η] provided +// buf was created using PackLeqEta. +// +// Beware, for arbitrary buf the coefficients of p might end up in +// the interval [q-2^b,q+2^b] where b is the least b with η≤2^b. +func PolyUnpackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + p[j] = common.Q + Eta - uint32(buf[i]&15) + p[j+1] = common.Q + Eta - uint32(buf[i]>>4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + p[j] = common.Q + Eta - uint32(buf[i]&7) + p[j+1] = common.Q + Eta - uint32((buf[i]>>3)&7) + p[j+2] = common.Q + Eta - uint32((buf[i]>>6)|((buf[i+1]<<2)&7)) + p[j+3] = common.Q + Eta - uint32((buf[i+1]>>1)&7) + p[j+4] = common.Q + Eta - uint32((buf[i+1]>>4)&7) + p[j+5] = common.Q + Eta - uint32((buf[i+1]>>7)|((buf[i+2]<<1)&7)) + p[j+6] = common.Q + Eta - uint32((buf[i+2]>>2)&7) + p[j+7] = common.Q + Eta - uint32((buf[i+2]>>5)&7) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Writes v with coefficients in {0, 1} of which at most ω non-zero +// to buf, which must have length ω+k. +func (v *VecK) PackHint(buf []byte) { + // The packed hint starts with the indices of the non-zero coefficients + // For instance: + // + // (x⁵⁶ + x¹⁰⁰, x²⁵⁵, 0, x² + x²³, x¹) + // + // Yields + // + // 56, 100, 255, 2, 23, 1 + // + // Then we pad with zeroes until we have a list of ω items: + // // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0 + // + // Then we finish with a list of the switch-over-indices in this + // list between polynomials, so: + // + // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0, 2, 3, 3, 5, 6 + + off := uint8(0) + for i := 0; i < K; i++ { + for j := uint16(0); j < common.N; j++ { + if v[i][j] != 0 { + buf[off] = uint8(j) + off++ + } + } + buf[Omega+i] = off + } + for ; off < Omega; off++ { + buf[off] = 0 + } +} + +// Sets v to the vector encoded using VecK.PackHint() +// +// Returns whether unpacking was successful. +func (v *VecK) UnpackHint(buf []byte) bool { + // A priori, there would be several reasonable ways to encode the same + // hint vector. We take care to only allow only one encoding, to ensure + // "strong unforgeability". + // + // See PackHint() source for description of the encoding. + *v = VecK{} // zero v + prevSOP := uint8(0) // previous switch-over-point + for i := 0; i < K; i++ { + SOP := buf[Omega+i] + if SOP < prevSOP || SOP > Omega { + return false // ensures switch-over-points are increasing + } + for j := prevSOP; j < SOP; j++ { + if j > prevSOP && buf[j] <= buf[j-1] { + return false // ensures indices are increasing (within a poly) + } + v[i][buf[j]] = 1 + } + prevSOP = SOP + } + for j := prevSOP; j < Omega; j++ { + if buf[j] != 0 { + return false // ensures padding indices are zero + } + } + + return true +} + +// Sets p to the polynomial packed into buf by PolyPackLeGamma1. +// +// p will be normalized. +func PolyUnpackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0x3) << 16) + p1 := uint32(buf[i+2]>>2) | (uint32(buf[i+3]) << 6) | + (uint32(buf[i+4]&0xf) << 14) + p2 := uint32(buf[i+4]>>4) | (uint32(buf[i+5]) << 4) | + (uint32(buf[i+6]&0x3f) << 12) + p3 := uint32(buf[i+6]>>6) | (uint32(buf[i+7]) << 2) | + (uint32(buf[i+8]) << 10) + + // coefficients in [0,…,2γ₁) + p0 = Gamma1 - p0 // (-γ₁,…,γ₁] + p1 = Gamma1 - p1 + p2 = Gamma1 - p2 + p3 = Gamma1 - p3 + + p0 += uint32(int32(p0)>>31) & common.Q // normalize + p1 += uint32(int32(p1)>>31) & common.Q + p2 += uint32(int32(p2)>>31) & common.Q + p3 += uint32(int32(p3)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + p[j+2] = p2 + p[j+3] = p3 + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0xf) << 16) + p1 := uint32(buf[i+2]>>4) | (uint32(buf[i+3]) << 4) | + (uint32(buf[i+4]) << 12) + + p0 = Gamma1 - p0 + p1 = Gamma1 - p1 + + p0 += uint32(int32(p0)>>31) & common.Q + p1 += uint32(int32(p1)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Writes p whose coefficients are in (-γ₁,γ₁] into buf +// which has to be of length PolyLeGamma1Size. +// +// Assumes p is normalized. +func PolyPackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + // coefficients in [0,…,γ₁] ∪ (q-γ₁,…,q) + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := Gamma1 - p[j] // [0,…,γ₁] ∪ (γ₁-q,…,2γ₁-q) + p0 += uint32(int32(p0)>>31) & common.Q // [0,…,2γ₁) + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + p2 := Gamma1 - p[j+2] + p2 += uint32(int32(p2)>>31) & common.Q + p3 := Gamma1 - p[j+3] + p3 += uint32(int32(p3)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<2) + buf[i+3] = byte(p1 >> 6) + buf[i+4] = byte(p1>>14) | byte(p2<<4) + buf[i+5] = byte(p2 >> 4) + buf[i+6] = byte(p2>>12) | byte(p3<<6) + buf[i+7] = byte(p3 >> 2) + buf[i+8] = byte(p3 >> 10) + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + // Coefficients are in [0, γ₁] ∪ (Q-γ₁, Q) + p0 := Gamma1 - p[j] + p0 += uint32(int32(p0)>>31) & common.Q + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<4) + buf[i+3] = byte(p1 >> 4) + buf[i+4] = byte(p1 >> 12) + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Pack w₁ into buf, which must be of length PolyW1Size. +// +// Assumes w₁ is normalized. +func PolyPackW1(p *common.Poly, buf []byte) { + if Gamma1Bits == 19 { + p.PackLe16(buf) + } else if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyW1Size; i += 3 { + buf[i] = byte(p[j]) | byte(p[j+1]<<6) + buf[i+1] = byte(p[j+1]>>2) | byte(p[j+2]<<4) + buf[i+2] = byte(p[j+2]>>4) | byte(p[j+3]<<2) + j += 4 + } + } else { + panic("unsupported γ₁") + } +} diff --git a/sign/mldsa/mldsa65/internal/pack_test.go b/sign/mldsa/mldsa65/internal/pack_test.go new file mode 100644 index 000000000..f952c6a09 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/pack_test.go @@ -0,0 +1,93 @@ +// Code generated from mode3/internal/pack_test.go by gen.go + +package internal + +import ( + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestPolyPackLeqEta(t *testing.T) { + var p1, p2 common.Poly + var seed [64]byte + var buf [PolyLeqEtaSize]byte + + for i := uint16(0); i < 100; i++ { + // Note that DeriveUniformLeqEta sets p to the right kind of + // unnormalized vector. + PolyDeriveUniformLeqEta(&p1, &seed, i) + for j := 0; j < PolyLeqEtaSize; j++ { + if p1[j] < common.Q-Eta || p1[j] > common.Q+Eta { + t.Fatalf("DerveUniformLeqEta out of bounds") + } + } + PolyPackLeqEta(&p1, buf[:]) + PolyUnpackLeqEta(&p2, buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT1(t *testing.T) { + var p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p1, &seed, i) + p1.Normalize() + for j := 0; j < common.N; j++ { + p1[j] &= 0x1ff + } + p1.PackT1(buf[:]) + p2.UnpackT1(buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT0(t *testing.T) { + var p, p0, p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT0Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p, &seed, i) + p.Normalize() + p.Power2Round(&p0, &p1) + + p0.PackT0(buf[:]) + p2.UnpackT0(buf[:]) + if p0 != p2 { + t.Fatalf("%v !=\n%v", p0, p2) + } + } +} + +func BenchmarkUnpackLeGamma1(b *testing.B) { + var p common.Poly + var buf [PolyLeGamma1Size]byte + for i := 0; i < b.N; i++ { + PolyUnpackLeGamma1(&p, buf[:]) + } +} + +func TestPolyPackLeGamma1(t *testing.T) { + var p0, p1 common.Poly + var seed [64]byte + var buf [PolyLeGamma1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniformLeGamma1(&p0, &seed, i) + p0.Normalize() + + PolyPackLeGamma1(&p0, buf[:]) + PolyUnpackLeGamma1(&p1, buf[:]) + if p0 != p1 { + t.Fatalf("%v != %v", p0, p1) + } + } +} diff --git a/sign/mldsa/mldsa65/internal/params.go b/sign/mldsa/mldsa65/internal/params.go new file mode 100644 index 000000000..c7872fc11 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/params.go @@ -0,0 +1,19 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Name = "ML-DSA-65" + UseAES = false + K = 6 + L = 5 + Eta = 4 + DoubleEtaBits = 4 + Omega = 55 + Tau = 49 + Gamma1Bits = 19 + Gamma2 = 261888 + NIST = true + TRSize = 64 + CTildeSize = 48 +) diff --git a/sign/mldsa/mldsa65/internal/rounding.go b/sign/mldsa/mldsa65/internal/rounding.go new file mode 100644 index 000000000..58123c090 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/rounding.go @@ -0,0 +1,142 @@ +// Code generated from mode3/internal/rounding.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, +// except for when we would have a₁ = (q-1)/α in which case a₁=0 is taken +// and -α/2 ≤ a₀ < 0. Returns a₀ + q. Note 0 ≤ a₁ < (q-1)/α. +// Recall α = 2γ₂. +func decompose(a uint32) (a0plusQ, a1 uint32) { + // a₁ = ⌈a / 128⌉ + a1 = (a + 127) >> 7 + + if Alpha == 523776 { + // 1025/2²² is close enough to 1/4092 so that a₁ + // becomes a/α rounded down. + a1 = ((a1*1025 + (1 << 21)) >> 22) + + // For the corner-case a₁ = (q-1)/α = 16, we have to set a₁=0. + a1 &= 15 + } else if Alpha == 190464 { + // 1488/2²⁴ is close enough to 1/1488 so that a₁ + // becomes a/α rounded down. + a1 = ((a1 * 11275) + (1 << 23)) >> 24 + + // For the corner-case a₁ = (q-1)/α = 44, we have to set a₁=0. + a1 ^= uint32(int32(43-a1)>>31) & a1 + } else { + panic("unsupported α") + } + + a0plusQ = a - a1*Alpha + + // In the corner-case, when we set a₁=0, we will incorrectly + // have a₀ > (q-1)/2 and we'll need to subtract q. As we + // return a₀ + q, that comes down to adding q if a₀ < (q-1)/2. + a0plusQ += uint32(int32(a0plusQ-(common.Q-1)/2)>>31) & common.Q + + return +} + +// Assume 0 ≤ r, f < Q with ‖f‖_∞ ≤ α/2. Decompose r as r = r1*α + r0 as +// computed by decompose(). Write r' := r - f (mod Q). Now, decompose +// r'=r-f again as r' = r'1*α + r'0 using decompose(). As f is small, we +// have r'1 = r1 + h, where h ∈ {-1, 0, 1}. makeHint() computes |h| +// given z0 := r0 - f (mod Q) and r1. With |h|, which is called the hint, +// we can reconstruct r1 using only r' = r - f, which is done by useHint(). +// To wit: +// +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// +// Assumes 0 ≤ z0 < Q. +func makeHint(z0, r1 uint32) uint32 { + // If -α/2 < r0 - f ≤ α/2, then r1*α + r0 - f is a valid decomposition of r' + // with the restrictions of decompose() and so r'1 = r1. So the hint + // should be 0. This is covered by the first two inequalities. + // There is one other case: if r0 - f = -α/2, then r1*α + r0 - f is also + // a valid decomposition if r1 = 0. In the other cases a one is carried + // and the hint should be 1. + if z0 <= Gamma2 || z0 > common.Q-Gamma2 || (z0 == common.Q-Gamma2 && r1 == 0) { + return 0 + } + return 1 +} + +// Uses the hint created by makeHint() to reconstruct r1 from r'=r-f; see +// documentation of makeHint() for context. +// Assumes 0 ≤ r' < Q. +func useHint(rp uint32, hint uint32) uint32 { + rp0plusQ, rp1 := decompose(rp) + if hint == 0 { + return rp1 + } + if rp0plusQ > common.Q { + return (rp1 + 1) & 15 + } + return (rp1 - 1) & 15 +} + +// Sets p to the hint polynomial for p0 the modified low bits and p1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint polynomial. +func PolyMakeHint(p, p0, p1 *common.Poly) (pop uint32) { + for i := 0; i < common.N; i++ { + h := makeHint(p0[i], p1[i]) + pop += h + p[i] = h + } + return +} + +// Computes corrections to the high bits of the polynomial q according +// to the hints in h and sets p to the corrected high bits. Returns p. +func PolyUseHint(p, q, hint *common.Poly) { + var q0PlusQ common.Poly + + // See useHint() and makeHint() for an explanation. We reimplement it + // here so that we can call Poly.Decompose(), which might be way faster + // than calling decompose() in a loop (for instance when having AVX2.) + + PolyDecompose(q, &q0PlusQ, p) + + for i := 0; i < common.N; i++ { + if hint[i] == 0 { + continue + } + if Gamma2 == 261888 { + if q0PlusQ[i] > common.Q { + p[i] = (p[i] + 1) & 15 + } else { + p[i] = (p[i] - 1) & 15 + } + } else if Gamma2 == 95232 { + if q0PlusQ[i] > common.Q { + if p[i] == 43 { + p[i] = 0 + } else { + p[i]++ + } + } else { + if p[i] == 0 { + p[i] = 43 + } else { + p[i]-- + } + } + } else { + panic("unsupported γ₂") + } + } +} + +// Splits each of the coefficients of p using decompose. +func PolyDecompose(p, p0PlusQ, p1 *common.Poly) { + for i := 0; i < common.N; i++ { + p0PlusQ[i], p1[i] = decompose(p[i]) + } +} diff --git a/sign/mldsa/mldsa65/internal/rounding_test.go b/sign/mldsa/mldsa65/internal/rounding_test.go new file mode 100644 index 000000000..ad653ca3f --- /dev/null +++ b/sign/mldsa/mldsa65/internal/rounding_test.go @@ -0,0 +1,81 @@ +// Code generated from mode3/internal/rounding_test.go by gen.go + +package internal + +import ( + "flag" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") + +func TestDecompose(t *testing.T) { + for a := uint32(0); a < common.Q; a++ { + a0PlusQ, a1 := decompose(a) + a0 := int32(a0PlusQ) - int32(common.Q) + recombined := a0 + int32(Alpha*a1) + if a1 == 0 && recombined < 0 { + recombined += common.Q + if -(Alpha/2) > a0 || a0 >= 0 { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } else { + if (-(Alpha / 2) >= a0) || (a0 > Alpha/2) { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } + if int32(a) != recombined { + t.Fatalf("decompose(%v) doesn't recombine %v %v", a, a0, a1) + } + } +} + +func TestMakeHint(t *testing.T) { + if !*runVeryLongTest { + t.SkipNow() + } + for w := uint32(0); w < common.Q; w++ { + w0, w1 := decompose(w) + for fn := uint32(0); fn <= Gamma2; fn++ { + fsign := false + for { + var f uint32 + if fsign { + if fn == 0 { + break + } + f = common.Q - fn + } else { + f = fn + } + + hint := makeHint(common.ReduceLe2Q(w0+common.Q-f), w1) + w1p := useHint(common.ReduceLe2Q(w+common.Q-f), hint) + if w1p != w1 { + t.Fatal() + } + + if fsign { + break + } + fsign = true + } + } + } +} + +func BenchmarkDecompose(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyDecompose(&p, &p0, &p1) + } +} + +func BenchmarkMakeHint(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyMakeHint(&p, &p0, &p1) + } +} diff --git a/sign/mldsa/mldsa65/internal/sample.go b/sign/mldsa/mldsa65/internal/sample.go new file mode 100644 index 000000000..62c261332 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/sample.go @@ -0,0 +1,370 @@ +// Code generated from mode3/internal/sample.go by gen.go + +package internal + +import ( + "encoding/binary" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/simd/keccakf1600" +) + +// DeriveX4Available indicates whether the system supports the quick fourway +// sampling variants like PolyDeriveUniformX4. +var DeriveX4Available = keccakf1600.IsEnabledX4() && !UseAES + +// For each i, sample ps[i] uniformly from the given seed and nonces[i]. +// ps[i] may be nil and is ignored in that case. +// +// Can only be called when DeriveX4Available is true. +func PolyDeriveUniformX4(ps [4]*common.Poly, seed *[32]byte, nonces [4]uint16) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 4; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // Absorb the nonces, the SHAKE128 domain separator (0b1111), the + // start of the padding (0b...001) and the end of the padding 0b100... + // Recall that the rate of SHAKE128 is 168 --- i.e. 21 uint64s. + for j := 0; j < 4; j++ { + state[4*4+j] = uint64(nonces[j]) | (0x1f << 16) + state[20*4+j] = 0x80 << 56 + } + + var idx [4]int // indices into ps + for j := 0; j < 4; j++ { + if ps[j] == nil { + idx[j] = common.N // mark nil polynomial as completed + } + } + + done := false + for !done { + // Applies KeccaK-f[1600] to state to get the next 21 uint64s of each + // of the four SHAKE128 streams. + perm.Permute() + + done = true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + for i := 0; i < 7; i++ { + var t [8]uint32 + t[0] = uint32(state[i*3*4+j] & 0x7fffff) + t[1] = uint32((state[i*3*4+j] >> 24) & 0x7fffff) + t[2] = uint32((state[i*3*4+j] >> 48) | + ((state[(i*3+1)*4+j] & 0x7f) << 16)) + t[3] = uint32((state[(i*3+1)*4+j] >> 8) & 0x7fffff) + t[4] = uint32((state[(i*3+1)*4+j] >> 32) & 0x7fffff) + t[5] = uint32((state[(i*3+1)*4+j] >> 56) | + ((state[(i*3+2)*4+j] & 0x7fff) << 8)) + t[6] = uint32((state[(i*3+2)*4+j] >> 16) & 0x7fffff) + t[7] = uint32((state[(i*3+2)*4+j] >> 40) & 0x7fffff) + + for k := 0; k < 8; k++ { + if t[k] < common.Q { + ps[j][idx[j]] = t[k] + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + } + done = false + } + } +} + +// Sample p uniformly from the given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniform(p *common.Poly, seed *[32]byte, nonce uint16) { + var i, length int + var buf [12 * 16]byte // fits 168B SHAKE-128 rate and 12 16B AES blocks + + if UseAES { + length = 12 * 16 + } else { + length = 168 + } + + sample := func() { + // Note that 3 divides into 168 and 12*16, so we use up buf completely. + for j := 0; j < length && i < common.N; j += 3 { + t := (uint32(buf[j]) | (uint32(buf[j+1]) << 8) | + (uint32(buf[j+2]) << 16)) & 0x7fffff + + // We use rejection sampling + if t < common.Q { + p[i] = t + i++ + } + } + } + + if UseAES { + h := common.NewAesStream128(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [32 + 2]byte // 32 byte seed + uint16 nonce + h := sha3.NewShake128() + copy(iv[:32], seed[:]) + iv[32] = uint8(nonce) + iv[33] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:168]) + sample() + } + } +} + +// Sample p uniformly with coefficients of norm less than or equal η, +// using the given seed and nonce. +// +// p will not be normalized, but will have coefficients in [q-η,q+η]. +func PolyDeriveUniformLeqEta(p *common.Poly, seed *[64]byte, nonce uint16) { + // Assumes 2 < η < 8. + var i, length int + var buf [9 * 16]byte // fits 136B SHAKE-256 rate and 9 16B AES blocks + + if UseAES { + length = 9 * 16 + } else { + length = 136 + } + + sample := func() { + // We use rejection sampling + for j := 0; j < length && i < common.N; j++ { + t1 := uint32(buf[j]) & 15 + t2 := uint32(buf[j]) >> 4 + if Eta == 2 { // branch is eliminated by compiler + if t1 <= 14 { + t1 -= ((205 * t1) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 14 && i < common.N { + t2 -= ((205 * t2) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t2 + i++ + } + } else if Eta == 4 { + if t1 <= 2*Eta { + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 2*Eta && i < common.N { + p[i] = common.Q + Eta - t2 + i++ + } + } else { + panic("unsupported η") + } + } + } + + if UseAES { + h := common.NewAesStream256(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [64 + 2]byte // 64 byte seed + uint16 nonce + + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + + // 136 is SHAKE-256 rate + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:136]) + sample() + } + } +} + +// Sample v[i] uniformly with coefficients in (-γ₁,…,γ₁] using the +// given seed and nonce+i +// +// p will be normalized. +func VecLDeriveUniformLeGamma1(v *VecL, seed *[64]byte, nonce uint16) { + for i := 0; i < L; i++ { + PolyDeriveUniformLeGamma1(&v[i], seed, nonce+uint16(i)) + } +} + +// Sample p uniformly with coefficients in (-γ₁,…,γK1s] using the +// given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { + var buf [PolyLeGamma1Size]byte + + if UseAES { + h := common.NewAesStream256(seed, nonce) + h.SqueezeInto(buf[:]) + } else { + var iv [66]byte + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + _, _ = h.Read(buf[:]) + } + + PolyUnpackLeGamma1(p, buf[:]) +} + +// For each i, sample ps[i] uniformly with τ non-zero coefficients in {q-1,1} +// using the given seed and w1[i]. ps[i] may be nil and is ignored +// in that case. ps[i] will be normalized. +// +// Can only be called when DeriveX4Available is true. +// +// This function is currently not used (yet). +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 32/8; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // SHAKE256 domain separator and padding + for j := 0; j < 4; j++ { + state[(32/8)*4+j] ^= 0x1f + state[16*4+j] ^= 0x80 << 56 + } + perm.Permute() + + var signs [4]uint64 + var idx [4]uint16 // indices into ps + + for j := 0; j < 4; j++ { + if ps[j] != nil { + signs[j] = state[j] + *ps[j] = common.Poly{} // zero ps[j] + idx[j] = common.N - Tau + } else { + idx[j] = common.N // mark as completed + } + } + + stateOffset := 1 + for { + done := true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + + for i := stateOffset; i < 17; i++ { + var bs [8]byte + binary.LittleEndian.PutUint64(bs[:], state[4*i+j]) + for k := 0; k < 8; k++ { + b := uint16(bs[k]) + + if b > idx[j] { + continue + } + + ps[j][idx[j]] = ps[j][b] + ps[j][b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + ps[j][b] ^= uint32((-(signs[j] & 1)) & (1 | (common.Q - 1))) + signs[j] >>= 1 + + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + + done = false + } + + if done { + break + } + + perm.Permute() + stateOffset = 0 + } +} + +// Samples p uniformly with τ non-zero coefficients in {q-1,1}. +// +// The polynomial p will be normalized. +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { + var buf [136]byte // SHAKE-256 rate is 136 + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(buf[:]) + + // Essentially we generate a sequence of τ ones or minus ones, + // prepend 196 zeroes and shuffle the concatenation using the + // usual algorithm (Fisher--Yates.) + signs := binary.LittleEndian.Uint64(buf[:]) + bufOff := 8 // offset into buf + + *p = common.Poly{} // zero p + for i := uint16(common.N - Tau); i < common.N; i++ { + var b uint16 + + // Find location of where to move the new coefficient to using + // rejection sampling. + for { + if bufOff >= 136 { + _, _ = h.Read(buf[:]) + bufOff = 0 + } + + b = uint16(buf[bufOff]) + bufOff++ + + if b <= i { + break + } + } + + p[i] = p[b] + p[b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + p[b] ^= uint32((-(signs & 1)) & (1 | (common.Q - 1))) + signs >>= 1 + } +} diff --git a/sign/mldsa/mldsa65/internal/sample_test.go b/sign/mldsa/mldsa65/internal/sample_test.go new file mode 100644 index 000000000..2059599eb --- /dev/null +++ b/sign/mldsa/mldsa65/internal/sample_test.go @@ -0,0 +1,266 @@ +// Code generated from mode3/internal/sample_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestVectorDeriveUniform(t *testing.T) { + var p, p2 common.Poly + var seed [32]byte + if UseAES { + p2 = common.Poly{ + 6724291, 310295, 6949524, 4464039, 1482136, 2522903, + 7025059, 3006320, 7286364, 7516512, 3361305, 1955529, + 4765954, 1725325, 6933066, 4299100, 6625173, 4272792, + 583034, 4971409, 2259140, 7715362, 3975394, 2341624, + 5481174, 8150082, 365246, 5491939, 1083120, 7517301, + 3104783, 2475292, 184149, 6425226, 4591622, 5964030, + 4729604, 5471092, 1828227, 1082044, 2516245, 1692580, + 3274844, 5443294, 7256740, 4989638, 3191250, 7479519, + 5124211, 5603858, 1230692, 2513454, 2828034, 4254312, + 1512596, 5245430, 5517392, 2814840, 932545, 6826733, + 3511094, 4075348, 3233981, 7268882, 2913733, 4870249, + 4123492, 8124406, 4016949, 5478752, 2750895, 603525, + 5724798, 3985430, 3483012, 6434230, 3136996, 8297976, + 4107616, 7307748, 6962904, 7544473, 1193110, 3448595, + 4814773, 5607932, 8221314, 1054046, 1541208, 1866050, + 8227412, 2925778, 5293953, 2065416, 4972769, 3616283, + 7990594, 1105530, 7121836, 1170740, 7417431, 633146, + 253820, 7235019, 3539504, 6807707, 451390, 5481526, + 2859902, 1063061, 4579730, 7126652, 7033767, 4294814, + 1414604, 7620048, 1953268, 8304556, 1156814, 1182881, + 5311519, 3057534, 5277666, 682843, 2070398, 2874278, + 4859533, 6376664, 6694074, 1590242, 2620706, 8331066, + 5643845, 5037538, 2891516, 7004879, 3754327, 5031296, + 5463118, 2420870, 8116529, 5517696, 7435129, 3873963, + 710407, 713806, 175647, 4274571, 2655021, 7319503, + 3027243, 7129679, 4213435, 2429323, 4643873, 4568526, + 649664, 1720514, 6497260, 2683517, 7672754, 7105190, + 3148405, 5898369, 5667677, 8050874, 1587139, 7315260, + 4337416, 2202680, 2338714, 557467, 6752058, 2469794, + 485071, 1617604, 3590498, 2151466, 2005823, 7727956, + 7776292, 6783433, 6787146, 1732833, 3596857, 7436284, + 4483349, 4970142, 4472608, 6478342, 1236215, 5695744, + 2280717, 2889355, 3233946, 5187812, 978685, 5177364, + 2922353, 4824807, 5302883, 6739803, 8092453, 5883903, + 816553, 6041174, 8317591, 1459178, 5332455, 1835058, + 1368601, 2820950, 3479224, 2589540, 7992934, 3421045, + 4657128, 8292902, 4153567, 3553988, 7830320, 6722913, + 2555309, 4149801, 8328975, 1560545, 7757473, 3106458, + 4310856, 7135453, 3481032, 652626, 1841361, 8126828, + 6250018, 300536, 7380070, 8174419, 1418793, 6208185, + 3906256, 6679016, 1605701, 3561489, 5819724, 5746996, + 8044214, 7087187, 7102330, 4962927, 4253983, 7108567, + 4119736, 6584065, 441634, 6941656, + } + } else { + p2 = common.Poly{ + 2901364, 562527, 5258502, 3885002, 4190126, 4460268, 6884052, + 3514511, 5383040, 213206, 2155865, 5179607, 3551954, 2312357, + 6066350, 8126097, 1179080, 4787182, 6552182, 6713644, + 1561067, 7626063, 7859743, 5052321, 7032876, 7815031, 157938, + 1865184, 490802, 5717642, 3451902, 7000218, 3743250, 1677431, + 1875427, 5596150, 671623, 3819041, 6247594, 1014875, 4933545, + 7122446, 6682963, 3388398, 3335295, 943002, 1145083, 3113071, + 105967, 1916675, 7474561, 1107006, 700548, 2147909, 1603855, + 5049181, 437882, 6118899, 5656914, 6731065, 3066622, 865453, + 5427634, 981549, 4650873, 861291, 4003872, 5104220, 6171453, + 3723302, 7426315, 6137283, 4874820, 6052561, 53441, 5032874, + 5614778, 2248550, 1756499, 8280764, 8263880, 7600081, + 5118374, 795344, 7543392, 6869925, 1841187, 4181568, 584562, + 7483939, 4938664, 6863397, 5126354, 5218129, 6236086, + 4149293, 379169, 4368487, 7490569, 3409215, 1580463, 3081737, + 1278732, 7109719, 7371700, 2097931, 399836, 1700274, 7188595, + 6830029, 1548850, 6593138, 6849097, 1518037, 2859442, + 7772265, 7325153, 3281191, 7856131, 4995056, 4684325, + 1351194, 8223904, 6817307, 2484146, 131782, 397032, 7436778, + 7973479, 3171829, 5624626, 3540123, 7150120, 8313283, + 3604714, 1043574, 117692, 7797783, 7909392, 903315, 7335342, + 7501562, 5826142, 2709813, 8245473, 2369045, 2782257, + 5762833, 6474114, 6862031, 424522, 594248, 2626630, 7659983, + 5642869, 4075194, 1592129, 245547, 5271031, 3205046, 982375, + 267873, 1286496, 7230481, 3208972, 7485411, 676111, 4944500, + 2959742, 5934456, 1414847, 6067948, 1709895, 4648315, 126008, + 8258986, 2183134, 2302072, 4674924, 4306056, 7465311, + 6500270, 4247428, 4016815, 4973426, 294287, 2456847, 3289700, + 2732169, 1159447, 5569724, 140001, 3237977, 8007761, 5874533, + 255652, 3119586, 2102434, 6248250, 8152822, 8006066, 7708625, + 6997719, 6260212, 6186962, 6636650, 7836834, 7998017, + 2061516, 1197591, 1706544, 733027, 2392907, 2700000, 8254598, + 4488002, 160495, 2985325, 2036837, 2703633, 6406550, 3579947, + 6195178, 5552390, 6804584, 6305468, 5731980, 6095195, + 3323409, 1322661, 6690942, 3374630, 5615167, 479044, 3136054, + 4380418, 2833144, 7829577, 1770522, 6056687, 240415, 14780, + 3740517, 5224226, 3547288, 2083124, 4699398, 3654239, + 5624978, 585593, 3655369, 2281739, 3338565, 1908093, 7784706, + 4352830, + } + } + for i := 0; i < 32; i++ { + seed[i] = byte(i) + } + PolyDeriveUniform(&p, &seed, 30000) + if p != p2 { + t.Fatalf("%v != %v", p, p2) + } +} + +func TestDeriveUniform(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniform(&p, &seed, uint16(i)) + if !PolyNormalized(&p) { + t.Fatal() + } + } +} + +func TestDeriveUniformLeqEta(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeqEta(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if p[j] < common.Q-Eta || p[j] > common.Q+Eta { + t.Fatal() + } + } + } +} + +func TestDeriveUniformLeGamma1(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if (p[j] > Gamma1 && p[j] <= common.Q-Gamma1) || p[j] >= common.Q { + t.Fatal() + } + } + } +} + +func TestDeriveUniformBall(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformBall(&p, seed[:]) + nonzero := 0 + for j := 0; j < common.N; j++ { + if p[j] != 0 { + if p[j] != 1 && p[j] != common.Q-1 { + t.Fatal() + } + nonzero++ + } + } + if nonzero != Tau { + t.Fatal() + } + } +} + +func TestDeriveUniformX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + nonces := [4]uint16{12345, 54321, 13532, 37377} + + for i := 0; i < len(seed); i++ { + seed[i] = byte(i) + } + + PolyDeriveUniformX4([4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, &seed, + nonces) + for i := 0; i < 4; i++ { + PolyDeriveUniform(&p, &seed, nonces[i]) + if ps[i] != p { + t.Fatal() + } + } +} + +func TestDeriveUniformBallX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + PolyDeriveUniformBallX4( + [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, + seed[:], + ) + for j := 0; j < 4; j++ { + PolyDeriveUniformBall(&p, seed[:]) + if ps[j] != p { + t.Fatalf("%d\n%v\n%v", j, ps[j], p) + } + } +} + +func BenchmarkPolyDeriveUniformBall(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBall(&p, seed[:]) + } +} + +func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBallX4( + [4]*common.Poly{&p, &p, &p, &p}, + seed[:], + ) + } +} + +func BenchmarkPolyDeriveUniform(b *testing.B) { + var seed [32]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniform(&p, &seed, uint16(i)) + } +} + +func BenchmarkPolyDeriveUniformX4(b *testing.B) { + if !DeriveX4Available { + b.SkipNow() + } + var seed [32]byte + var p [4]common.Poly + for i := 0; i < b.N; i++ { + nonce := uint16(4 * i) + PolyDeriveUniformX4([4]*common.Poly{&p[0], &p[1], &p[2], &p[3]}, + &seed, [4]uint16{nonce, nonce + 1, nonce + 2, nonce + 3}) + } +} + +func BenchmarkPolyDeriveUniformLeGamma1(b *testing.B) { + var seed [64]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + } +} diff --git a/sign/mldsa/mldsa65/internal/vec.go b/sign/mldsa/mldsa65/internal/vec.go new file mode 100644 index 000000000..d07d3b245 --- /dev/null +++ b/sign/mldsa/mldsa65/internal/vec.go @@ -0,0 +1,281 @@ +// Code generated from mode3/internal/vec.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A vector of L polynomials. +type VecL [L]common.Poly + +// A vector of K polynomials. +type VecK [K]common.Poly + +// Normalize the polynomials in this vector. +func (v *VecL) Normalize() { + for i := 0; i < L; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecL) NormalizeAssumingLe2Q() { + for i := 0; i < L; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecL) Add(w, u *VecL) { + for i := 0; i < L; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecL) NTT() { + for i := 0; i < L; i++ { + v[i].NTT() + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecL) Exceeds(bound uint32) bool { + for i := 0; i < L; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Power2Round(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Decompose(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecL) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeqEta(). +func (v *VecL) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sequentially packs each polynomial using PolyPackLeGamma1(). +func (v *VecL) PackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeGamma1(). +func (v *VecL) UnpackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Normalize the polynomials in this vector. +func (v *VecK) Normalize() { + for i := 0; i < K; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecK) NormalizeAssumingLe2Q() { + for i := 0; i < K; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecK) Add(w, u *VecK) { + for i := 0; i < K; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecK) Exceeds(bound uint32) bool { + for i := 0; i < K; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Power2Round(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Decompose(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sets v to the hint vector for v0 the modified low bits and v1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint vector. +func (v *VecK) MakeHint(v0, v1 *VecK) (pop uint32) { + for i := 0; i < K; i++ { + pop += PolyMakeHint(&v[i], &v0[i], &v1[i]) + } + return +} + +// Computes corrections to the high bits of the polynomials in the vector +// w using the hints in h and sets v to the corrected high bits. Returns v. +// See useHint(). +func (v *VecK) UseHint(q, hint *VecK) *VecK { + for i := 0; i < K; i++ { + PolyUseHint(&v[i], &q[i], &hint[i]) + } + return v +} + +// Sequentially packs each polynomial using Poly.PackT1(). +func (v *VecK) PackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sets v to the vector packed into buf by PackT1(). +func (v *VecK) UnpackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sequentially packs each polynomial using Poly.PackT0(). +func (v *VecK) PackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sets v to the vector packed into buf by PackT0(). +func (v *VecK) UnpackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecK) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecK.PackLeqEta(). +func (v *VecK) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecK) NTT() { + for i := 0; i < K; i++ { + v[i].NTT() + } +} + +// Sequentially packs each polynomial using PolyPackW1(). +func (v *VecK) PackW1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackW1(&v[i], buf[offset:]) + offset += PolyW1Size + } +} + +// Sets v to a - b. +// +// Warning: assumes coefficients of the polynomials of b are less than 2q. +func (v *VecK) Sub(a, b *VecK) { + for i := 0; i < K; i++ { + v[i].Sub(&a[i], &b[i]) + } +} + +// Sets v to 2ᵈ w without reducing. +func (v *VecK) MulBy2toD(w *VecK) { + for i := 0; i < K; i++ { + v[i].MulBy2toD(&w[i]) + } +} + +// Applies InvNTT componentwise. See Poly.InvNTT() for details. +func (v *VecK) InvNTT() { + for i := 0; i < K; i++ { + v[i].InvNTT() + } +} + +// Applies Poly.ReduceLe2Q() componentwise. +func (v *VecK) ReduceLe2Q() { + for i := 0; i < K; i++ { + v[i].ReduceLe2Q() + } +} diff --git a/sign/mldsa/mldsa87/dilithium.go b/sign/mldsa/mldsa87/dilithium.go new file mode 100644 index 000000000..a0aa0cf99 --- /dev/null +++ b/sign/mldsa/mldsa87/dilithium.go @@ -0,0 +1,182 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + + +// mldsa87 implements NIST signature scheme ML-DSA-87 as defined in FIPS204. + +package mldsa87 + +import ( + "crypto" + "errors" + "io" + + common "github.com/cloudflare/circl/sign/internal/dilithium" + + "github.com/cloudflare/circl/sign/mldsa/mldsa87/internal" + +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = common.SeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of ML-DSA-87 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of ML-DSA-87 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// SignTo signs the given message and writes the signature into signature. +// It will panic if signature is not of length at least SignatureSize. +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + internal.SignTo( + (*internal.PrivateKey)(sk), + msg, + signature, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + (*internal.PublicKey)(pk), + msg, + signature, + ) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + +// Packs the public key. +func (pk *PublicKey) Bytes() []byte { + var buf [PublicKeySize]byte + pk.Pack(&buf) + return buf[:] +} + +// Packs the private key. +func (sk *PrivateKey) Bytes() []byte { + var buf [PrivateKeySize]byte + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mldsa87.PublicKeySize bytes") + } + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PrivateKeySize { + return errors.New("packed private key must be of mldsa87.PrivateKeySize bytes") + } + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. rand is ignored. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level SignTo function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + var sig [SignatureSize]byte + + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("dilithium: cannot sign hashed message") + } + + SignTo(sk, msg, sig[:]) + return sig[:], nil +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*PublicKey)((*internal.PrivateKey)(sk).Public()) +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mldsa/mldsa87/internal/dilithium.go b/sign/mldsa/mldsa87/internal/dilithium.go new file mode 100644 index 000000000..7869d0475 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/dilithium.go @@ -0,0 +1,482 @@ +// Code generated from mode3/internal/dilithium.go by gen.go + +package internal + +import ( + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +const ( + // Size of a packed polynomial of norm ≤η. + // (Note that the formula is not valid in general.) + PolyLeqEtaSize = (common.N * DoubleEtaBits) / 8 + + // β = τη, the maximum size of c s₂. + Beta = Tau * Eta + + // γ₁ range of y + Gamma1 = 1 << Gamma1Bits + + // Size of packed polynomial of norm <γ₁ such as z + PolyLeGamma1Size = (Gamma1Bits + 1) * common.N / 8 + + // α = 2γ₂ parameter for decompose + Alpha = 2 * Gamma2 + + // Size of a packed private key + PrivateKeySize = 32 + 32 + TRSize + PolyLeqEtaSize*(L+K) + common.PolyT0Size*K + + // Size of a packed public key + PublicKeySize = 32 + common.PolyT1Size*K + + // Size of a packed signature + SignatureSize = L*PolyLeGamma1Size + Omega + K + CTildeSize + + // Size of packed w₁ + PolyW1Size = (common.N * (common.QBits - Gamma1Bits)) / 8 +) + +// PublicKey is the type of Dilithium public keys. +type PublicKey struct { + rho [32]byte + t1 VecK + + // Cached values + t1p [common.PolyT1Size * K]byte + A *Mat + tr *[TRSize]byte +} + +// PrivateKey is the type of Dilithium private keys. +type PrivateKey struct { + rho [32]byte + key [32]byte + s1 VecL + s2 VecK + t0 VecK + tr [TRSize]byte + + // Cached values + A Mat // ExpandA(ρ) + s1h VecL // NTT(s₁) + s2h VecK // NTT(s₂) + t0h VecK // NTT(t₀) +} + +type unpackedSignature struct { + z VecL + hint VecK + c [CTildeSize]byte +} + +// Packs the signature into buf. +func (sig *unpackedSignature) Pack(buf []byte) { + copy(buf[:], sig.c[:]) + sig.z.PackLeGamma1(buf[CTildeSize:]) + sig.hint.PackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) +} + +// Sets sig to the signature encoded in the buffer. +// +// Returns whether buf contains a properly packed signature. +func (sig *unpackedSignature) Unpack(buf []byte) bool { + if len(buf) < SignatureSize { + return false + } + copy(sig.c[:], buf[:]) + sig.z.UnpackLeGamma1(buf[CTildeSize:]) + if sig.z.Exceeds(Gamma1 - Beta) { + return false + } + if !sig.hint.UnpackHint(buf[CTildeSize+L*PolyLeGamma1Size:]) { + return false + } + return true +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:32], pk.rho[:]) + copy(buf[32:], pk.t1p[:]) +} + +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.rho[:], buf[:32]) + copy(pk.t1p[:], buf[32:]) + + pk.t1.UnpackT1(pk.t1p[:]) + pk.A = new(Mat) + pk.A.Derive(&pk.rho) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + pk.tr = new([TRSize]byte) + h := sha3.NewShake256() + _, _ = h.Write(buf[:]) + _, _ = h.Read(pk.tr[:]) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:32], sk.rho[:]) + copy(buf[32:64], sk.key[:]) + copy(buf[64:64+TRSize], sk.tr[:]) + offset := 64 + TRSize + sk.s1.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.PackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.PackT0(buf[offset:]) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.rho[:], buf[:32]) + copy(sk.key[:], buf[32:64]) + copy(sk.tr[:], buf[64:64+TRSize]) + offset := 64 + TRSize + sk.s1.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * L + sk.s2.UnpackLeqEta(buf[offset:]) + offset += PolyLeqEtaSize * K + sk.t0.UnpackT0(buf[offset:]) + + // Cached values + sk.A.Derive(&sk.rho) + sk.t0h = sk.t0 + sk.t0h.NTT() + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [32]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(&seed) + return pk, sk, nil +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed *[common.SeedSize]byte) (*PublicKey, *PrivateKey) { + var eSeed [128]byte // expanded seed + var pk PublicKey + var sk PrivateKey + var sSeed [64]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(eSeed[:]) + + copy(pk.rho[:], eSeed[:32]) + copy(sSeed[:], eSeed[32:96]) + copy(sk.key[:], eSeed[96:]) + copy(sk.rho[:], pk.rho[:]) + + sk.A.Derive(&pk.rho) + + for i := uint16(0); i < L; i++ { + PolyDeriveUniformLeqEta(&sk.s1[i], &sSeed, i) + } + + for i := uint16(0); i < K; i++ { + PolyDeriveUniformLeqEta(&sk.s2[i], &sSeed, i+L) + } + + sk.s1h = sk.s1 + sk.s1h.NTT() + sk.s2h = sk.s2 + sk.s2h.NTT() + + sk.computeT0andT1(&sk.t0, &pk.t1) + + sk.t0h = sk.t0 + sk.t0h.NTT() + + // Complete public key far enough to be packed + pk.t1.PackT1(pk.t1p[:]) + pk.A = &sk.A + + // Finish private key + var packedPk [PublicKeySize]byte + pk.Pack(&packedPk) + + // tr = CRH(ρ ‖ t1) = CRH(pk) + h.Reset() + _, _ = h.Write(packedPk[:]) + _, _ = h.Read(sk.tr[:]) + + // Finish cache of public key + pk.tr = &sk.tr + + return &pk, &sk +} + +// Computes t0 and t1 from sk.s1h, sk.s2 and sk.A. +func (sk *PrivateKey) computeT0andT1(t0, t1 *VecK) { + var t VecK + + // Set t to A s₁ + s₂ + for i := 0; i < K; i++ { + PolyDotHat(&t[i], &sk.A[i], &sk.s1h) + t[i].ReduceLe2Q() + t[i].InvNTT() + } + t.Add(&t, &sk.s2) + t.Normalize() + + // Compute t₀, t₁ = Power2Round(t) + t.Power2Round(t0, t1) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + var sig unpackedSignature + var mu [64]byte + var zh VecL + var Az, Az2dct1, w1 VecK + var ch common.Poly + var cp [CTildeSize]byte + var w1Packed [PolyW1Size * K]byte + + // Note that Unpack() checked whether ‖z‖_∞ < γ₁ - β + // and ensured that there at most ω ones in pk.hint. + if !sig.Unpack(signature) { + return false + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(pk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // Compute Az + zh = sig.z + zh.NTT() + + for i := 0; i < K; i++ { + PolyDotHat(&Az[i], &pk.A[i], &zh) + } + + // Next, we compute Az - 2ᵈ·c·t₁. + // Note that the coefficients of t₁ are bounded by 256 = 2⁹, + // so the coefficients of Az2dct1 will bounded by 2⁹⁺ᵈ = 2²³ < 2q, + // which is small enough for NTT(). + Az2dct1.MulBy2toD(&pk.t1) + Az2dct1.NTT() + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + for i := 0; i < K; i++ { + Az2dct1[i].MulHat(&Az2dct1[i], &ch) + } + Az2dct1.Sub(&Az, &Az2dct1) + Az2dct1.ReduceLe2Q() + Az2dct1.InvNTT() + Az2dct1.NormalizeAssumingLe2Q() + + // UseHint(pk.hint, Az - 2ᵈ·c·t₁) + // = UseHint(pk.hint, w - c·s₂ + c·t₀) + // = UseHint(pk.hint, r + c·t₀) + // = r₁ = w₁. + w1.UseHint(&Az2dct1, &sig.hint) + w1.PackW1(w1Packed[:]) + + // c' = H(μ, w₁) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(cp[:]) + + return sig.c == cp +} + +// SignTo signs the given message and writes the signature into signature. +// +//nolint:funlen +func SignTo(sk *PrivateKey, msg []byte, signature []byte) { + var mu, rhop [64]byte + var w1Packed [PolyW1Size * K]byte + var y, yh VecL + var w, w0, w1, w0mcs2, ct0, w0mcs2pct0 VecK + var ch common.Poly + var yNonce uint16 + var sig unpackedSignature + + if len(signature) < SignatureSize { + panic("Signature does not fit in that byteslice") + } + + // μ = CRH(tr ‖ msg) + h := sha3.NewShake256() + _, _ = h.Write(sk.tr[:]) + _, _ = h.Write(msg) + _, _ = h.Read(mu[:]) + + // ρ' = CRH(key ‖ μ) + h.Reset() + _, _ = h.Write(sk.key[:]) + if NIST { + // We implement the deterministic variant where rnd is all zeroes. + // TODO expose randomized variant? + _, _ = h.Write(make([]byte, 32)) + } + _, _ = h.Write(mu[:]) + _, _ = h.Read(rhop[:]) + + // Main rejection loop + attempt := 0 + for { + attempt++ + if attempt >= 576 { + // Depending on the mode, one try has a chance between 1/7 and 1/4 + // of succeeding. Thus it is safe to say that 576 iterations + // are enough as (6/7)⁵⁷⁶ < 2⁻¹²⁸. + panic("This should only happen 1 in 2^{128}: something is wrong.") + } + + // y = ExpandMask(ρ', key) + VecLDeriveUniformLeGamma1(&y, &rhop, yNonce) + yNonce += uint16(L) + + // Set w to A y + yh = y + yh.NTT() + for i := 0; i < K; i++ { + PolyDotHat(&w[i], &sk.A[i], &yh) + w[i].ReduceLe2Q() + w[i].InvNTT() + } + + // Decompose w into w₀ and w₁ + w.NormalizeAssumingLe2Q() + w.Decompose(&w0, &w1) + + // c~ = H(μ ‖ w₁) + w1.PackW1(w1Packed[:]) + h.Reset() + _, _ = h.Write(mu[:]) + _, _ = h.Write(w1Packed[:]) + _, _ = h.Read(sig.c[:]) + + PolyDeriveUniformBall(&ch, sig.c[:32]) + ch.NTT() + + // Ensure ‖ w₀ - c·s2 ‖_∞ < γ₂ - β. + // + // By Lemma 3 of the specification this is equivalent to checking that + // both ‖ r₀ ‖_∞ < γ₂ - β and r₁ = w₁, for the decomposition + // w - c·s₂ = r₁ α + r₀ as computed by decompose(). + // See also §4.1 of the specification. + for i := 0; i < K; i++ { + w0mcs2[i].MulHat(&ch, &sk.s2h[i]) + w0mcs2[i].InvNTT() + } + w0mcs2.Sub(&w0, &w0mcs2) + w0mcs2.Normalize() + + if w0mcs2.Exceeds(Gamma2 - Beta) { + continue + } + + // z = y + c·s₁ + for i := 0; i < L; i++ { + sig.z[i].MulHat(&ch, &sk.s1h[i]) + sig.z[i].InvNTT() + } + sig.z.Add(&sig.z, &y) + sig.z.Normalize() + + // Ensure ‖z‖_∞ < γ₁ - β + if sig.z.Exceeds(Gamma1 - Beta) { + continue + } + + // Compute c·t₀ + for i := 0; i < K; i++ { + ct0[i].MulHat(&ch, &sk.t0h[i]) + ct0[i].InvNTT() + } + ct0.NormalizeAssumingLe2Q() + + // Ensure ‖c·t₀‖_∞ < γ₂. + if ct0.Exceeds(Gamma2) { + continue + } + + // Create the hint to be able to reconstruct w₁ from w - c·s₂ + c·t0. + // Note that we're not using makeHint() in the obvious way as we + // do not know whether ‖ sc·s₂ - c·t₀ ‖_∞ < γ₂. Instead we note + // that our makeHint() is actually the same as a makeHint for a + // different decomposition: + // + // Earlier we ensured indirectly with a check that r₁ = w₁ where + // r = w - c·s₂. Hence r₀ = r - r₁ α = w - c·s₂ - w₁ α = w₀ - c·s₂. + // Thus MakeHint(w₀ - c·s₂ + c·t₀, w₁) = MakeHint(r0 + c·t₀, r₁) + // and UseHint(w - c·s₂ + c·t₀, w₁) = UseHint(r + c·t₀, r₁). + // As we just ensured that ‖ c·t₀ ‖_∞ < γ₂ our usage is correct. + w0mcs2pct0.Add(&w0mcs2, &ct0) + w0mcs2pct0.NormalizeAssumingLe2Q() + hintPop := sig.hint.MakeHint(&w0mcs2pct0, &w1) + if hintPop > Omega { + continue + } + + break + } + + sig.Pack(signature[:]) +} + +// Computes the public key corresponding to this private key. +func (sk *PrivateKey) Public() *PublicKey { + var t0 VecK + pk := &PublicKey{ + rho: sk.rho, + A: &sk.A, + tr: &sk.tr, + } + sk.computeT0andT1(&t0, &pk.t1) + pk.t1.PackT1(pk.t1p[:]) + return pk +} + +// Equal returns whether the two public keys are equal +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.rho == other.rho && pk.t1 == other.t1 +} + +// Equal returns whether the two private keys are equal +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + ret := (subtle.ConstantTimeCompare(sk.rho[:], other.rho[:]) & + subtle.ConstantTimeCompare(sk.key[:], other.key[:]) & + subtle.ConstantTimeCompare(sk.tr[:], other.tr[:])) + + acc := uint32(0) + for i := 0; i < L; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s1[i][j] ^ other.s1[i][j] + } + } + for i := 0; i < K; i++ { + for j := 0; j < common.N; j++ { + acc |= sk.s2[i][j] ^ other.s2[i][j] + acc |= sk.t0[i][j] ^ other.t0[i][j] + } + } + return (ret & subtle.ConstantTimeEq(int32(acc), 0)) == 1 +} diff --git a/sign/mldsa/mldsa87/internal/dilithium_test.go b/sign/mldsa/mldsa87/internal/dilithium_test.go new file mode 100644 index 000000000..825e7da08 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/dilithium_test.go @@ -0,0 +1,144 @@ +// Code generated from mode3/internal/dilithium_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Checks whether p is normalized. Only used in tests. +func PolyNormalized(p *common.Poly) bool { + p2 := *p + p2.Normalize() + return p2 == *p +} + +func BenchmarkSkUnpack(b *testing.B) { + var buf [PrivateKeySize]byte + var sk PrivateKey + for i := 0; i < b.N; i++ { + sk.Unpack(&buf) + } +} + +func BenchmarkPkUnpack(b *testing.B) { + var buf [PublicKeySize]byte + var pk PublicKey + for i := 0; i < b.N; i++ { + pk.Unpack(&buf) + } +} + +func BenchmarkVerify(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of verification (as in the reference + // implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + pk, sk := NewKeyFromSeed(&seed) + SignTo(sk, msg[:], sig[:]) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // We should generate a new signature for every verify attempt, + // as this influences the time a little bit. This difference, however, + // is small and generating a new signature in between creates a lot + // pressure on the allocator which makes an accurate measurement hard. + Verify(pk, msg[:], sig[:]) + } +} + +func BenchmarkSign(b *testing.B) { + // Note that the expansion of the matrix A is done at Unpacking/Keygen + // instead of at the moment of signing (as in the reference implementation.) + var seed [32]byte + var msg [8]byte + var sig [SignatureSize]byte + _, sk := NewKeyFromSeed(&seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + SignTo(sk, msg[:], sig[:]) + } +} + +func BenchmarkGenerateKey(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + NewKeyFromSeed(&seed) + } +} + +func BenchmarkPublicFromPrivate(b *testing.B) { + var seed [32]byte + for i := 0; i < b.N; i++ { + b.StopTimer() + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, sk := NewKeyFromSeed(&seed) + b.StartTimer() + sk.Public() + } +} + +func TestSignThenVerifyAndPkSkPacking(t *testing.T) { + var seed [common.SeedSize]byte + var sig [SignatureSize]byte + var msg [8]byte + var pkb [PublicKeySize]byte + var skb [PrivateKeySize]byte + var pk2 PublicKey + var sk2 PrivateKey + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + if !sk.Equal(sk) { + t.Fatal() + } + for j := uint64(0); j < 10; j++ { + binary.LittleEndian.PutUint64(msg[:], j) + SignTo(sk, msg[:], sig[:]) + if !Verify(pk, msg[:], sig[:]) { + t.Fatal() + } + } + pk.Pack(&pkb) + pk2.Unpack(&pkb) + if !pk.Equal(&pk2) { + t.Fatal() + } + sk.Pack(&skb) + sk2.Unpack(&skb) + if !sk.Equal(&sk2) { + t.Fatal() + } + } +} + +func TestPublicFromPrivate(t *testing.T) { + var seed [common.SeedSize]byte + for i := uint64(0); i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], i) + pk, sk := NewKeyFromSeed(&seed) + pk2 := sk.Public() + if !pk.Equal(pk2) { + t.Fatal() + } + } +} + +func TestGamma1Size(t *testing.T) { + var expected int + switch Gamma1Bits { + case 17: + expected = 576 + case 19: + expected = 640 + } + if expected != PolyLeGamma1Size { + t.Fatal() + } +} diff --git a/sign/mldsa/mldsa87/internal/mat.go b/sign/mldsa/mldsa87/internal/mat.go new file mode 100644 index 000000000..ceaf634fa --- /dev/null +++ b/sign/mldsa/mldsa87/internal/mat.go @@ -0,0 +1,59 @@ +// Code generated from mode3/internal/mat.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A k by l matrix of polynomials. +type Mat [K]VecL + +// Expands the given seed to a complete matrix. +// +// This function is called ExpandA in the specification. +func (m *Mat) Derive(seed *[32]byte) { + if !DeriveX4Available { + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + PolyDeriveUniform(&m[i][j], seed, (i<<8)+j) + } + } + return + } + + idx := 0 + var nonces [4]uint16 + var ps [4]*common.Poly + for i := uint16(0); i < K; i++ { + for j := uint16(0); j < L; j++ { + nonces[idx] = (i << 8) + j + ps[idx] = &m[i][j] + idx++ + if idx == 4 { + idx = 0 + PolyDeriveUniformX4(ps, seed, nonces) + } + } + } + if idx != 0 { + for i := idx; i < 4; i++ { + ps[i] = nil + } + PolyDeriveUniformX4(ps, seed, nonces) + } +} + +// Set p to the inner product of a and b using pointwise multiplication. +// +// Assumes a and b are in Montgomery form and their coefficients are +// pairwise sufficiently small to multiply, see Poly.MulHat(). Resulting +// coefficients are bounded by 2Lq. +func PolyDotHat(p *common.Poly, a, b *VecL) { + var t common.Poly + *p = common.Poly{} // zero p + for i := 0; i < L; i++ { + t.MulHat(&a[i], &b[i]) + p.Add(&t, p) + } +} diff --git a/sign/mldsa/mldsa87/internal/pack.go b/sign/mldsa/mldsa87/internal/pack.go new file mode 100644 index 000000000..1854b4197 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/pack.go @@ -0,0 +1,270 @@ +// Code generated from mode3/internal/pack.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Writes p with norm less than or equal η into buf, which must be of +// size PolyLeqEtaSize. +// +// Assumes coefficients of p are not normalized, but in [q-η,q+η]. +func PolyPackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + buf[i] = (byte(common.Q+Eta-p[j]) | + byte(common.Q+Eta-p[j+1])<<4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + buf[i] = (byte(common.Q+Eta-p[j]) | + (byte(common.Q+Eta-p[j+1]) << 3) | + (byte(common.Q+Eta-p[j+2]) << 6)) + buf[i+1] = ((byte(common.Q+Eta-p[j+2]) >> 2) | + (byte(common.Q+Eta-p[j+3]) << 1) | + (byte(common.Q+Eta-p[j+4]) << 4) | + (byte(common.Q+Eta-p[j+5]) << 7)) + buf[i+2] = ((byte(common.Q+Eta-p[j+5]) >> 1) | + (byte(common.Q+Eta-p[j+6]) << 2) | + (byte(common.Q+Eta-p[j+7]) << 5)) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Sets p to the polynomial of norm less than or equal η encoded in the +// given buffer of size PolyLeqEtaSize. +// +// Output coefficients of p are not normalized, but in [q-η,q+η] provided +// buf was created using PackLeqEta. +// +// Beware, for arbitrary buf the coefficients of p might end up in +// the interval [q-2^b,q+2^b] where b is the least b with η≤2^b. +func PolyUnpackLeqEta(p *common.Poly, buf []byte) { + if DoubleEtaBits == 4 { // compiler eliminates branch + j := 0 + for i := 0; i < PolyLeqEtaSize; i++ { + p[j] = common.Q + Eta - uint32(buf[i]&15) + p[j+1] = common.Q + Eta - uint32(buf[i]>>4) + j += 2 + } + } else if DoubleEtaBits == 3 { + j := 0 + for i := 0; i < PolyLeqEtaSize; i += 3 { + p[j] = common.Q + Eta - uint32(buf[i]&7) + p[j+1] = common.Q + Eta - uint32((buf[i]>>3)&7) + p[j+2] = common.Q + Eta - uint32((buf[i]>>6)|((buf[i+1]<<2)&7)) + p[j+3] = common.Q + Eta - uint32((buf[i+1]>>1)&7) + p[j+4] = common.Q + Eta - uint32((buf[i+1]>>4)&7) + p[j+5] = common.Q + Eta - uint32((buf[i+1]>>7)|((buf[i+2]<<1)&7)) + p[j+6] = common.Q + Eta - uint32((buf[i+2]>>2)&7) + p[j+7] = common.Q + Eta - uint32((buf[i+2]>>5)&7) + j += 8 + } + } else { + panic("eta not supported") + } +} + +// Writes v with coefficients in {0, 1} of which at most ω non-zero +// to buf, which must have length ω+k. +func (v *VecK) PackHint(buf []byte) { + // The packed hint starts with the indices of the non-zero coefficients + // For instance: + // + // (x⁵⁶ + x¹⁰⁰, x²⁵⁵, 0, x² + x²³, x¹) + // + // Yields + // + // 56, 100, 255, 2, 23, 1 + // + // Then we pad with zeroes until we have a list of ω items: + // // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0 + // + // Then we finish with a list of the switch-over-indices in this + // list between polynomials, so: + // + // 56, 100, 255, 2, 23, 1, 0, 0, ..., 0, 2, 3, 3, 5, 6 + + off := uint8(0) + for i := 0; i < K; i++ { + for j := uint16(0); j < common.N; j++ { + if v[i][j] != 0 { + buf[off] = uint8(j) + off++ + } + } + buf[Omega+i] = off + } + for ; off < Omega; off++ { + buf[off] = 0 + } +} + +// Sets v to the vector encoded using VecK.PackHint() +// +// Returns whether unpacking was successful. +func (v *VecK) UnpackHint(buf []byte) bool { + // A priori, there would be several reasonable ways to encode the same + // hint vector. We take care to only allow only one encoding, to ensure + // "strong unforgeability". + // + // See PackHint() source for description of the encoding. + *v = VecK{} // zero v + prevSOP := uint8(0) // previous switch-over-point + for i := 0; i < K; i++ { + SOP := buf[Omega+i] + if SOP < prevSOP || SOP > Omega { + return false // ensures switch-over-points are increasing + } + for j := prevSOP; j < SOP; j++ { + if j > prevSOP && buf[j] <= buf[j-1] { + return false // ensures indices are increasing (within a poly) + } + v[i][buf[j]] = 1 + } + prevSOP = SOP + } + for j := prevSOP; j < Omega; j++ { + if buf[j] != 0 { + return false // ensures padding indices are zero + } + } + + return true +} + +// Sets p to the polynomial packed into buf by PolyPackLeGamma1. +// +// p will be normalized. +func PolyUnpackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0x3) << 16) + p1 := uint32(buf[i+2]>>2) | (uint32(buf[i+3]) << 6) | + (uint32(buf[i+4]&0xf) << 14) + p2 := uint32(buf[i+4]>>4) | (uint32(buf[i+5]) << 4) | + (uint32(buf[i+6]&0x3f) << 12) + p3 := uint32(buf[i+6]>>6) | (uint32(buf[i+7]) << 2) | + (uint32(buf[i+8]) << 10) + + // coefficients in [0,…,2γ₁) + p0 = Gamma1 - p0 // (-γ₁,…,γ₁] + p1 = Gamma1 - p1 + p2 = Gamma1 - p2 + p3 = Gamma1 - p3 + + p0 += uint32(int32(p0)>>31) & common.Q // normalize + p1 += uint32(int32(p1)>>31) & common.Q + p2 += uint32(int32(p2)>>31) & common.Q + p3 += uint32(int32(p3)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + p[j+2] = p2 + p[j+3] = p3 + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + p0 := uint32(buf[i]) | (uint32(buf[i+1]) << 8) | + (uint32(buf[i+2]&0xf) << 16) + p1 := uint32(buf[i+2]>>4) | (uint32(buf[i+3]) << 4) | + (uint32(buf[i+4]) << 12) + + p0 = Gamma1 - p0 + p1 = Gamma1 - p1 + + p0 += uint32(int32(p0)>>31) & common.Q + p1 += uint32(int32(p1)>>31) & common.Q + + p[j] = p0 + p[j+1] = p1 + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Writes p whose coefficients are in (-γ₁,γ₁] into buf +// which has to be of length PolyLeGamma1Size. +// +// Assumes p is normalized. +func PolyPackLeGamma1(p *common.Poly, buf []byte) { + if Gamma1Bits == 17 { + j := 0 + // coefficients in [0,…,γ₁] ∪ (q-γ₁,…,q) + for i := 0; i < PolyLeGamma1Size; i += 9 { + p0 := Gamma1 - p[j] // [0,…,γ₁] ∪ (γ₁-q,…,2γ₁-q) + p0 += uint32(int32(p0)>>31) & common.Q // [0,…,2γ₁) + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + p2 := Gamma1 - p[j+2] + p2 += uint32(int32(p2)>>31) & common.Q + p3 := Gamma1 - p[j+3] + p3 += uint32(int32(p3)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<2) + buf[i+3] = byte(p1 >> 6) + buf[i+4] = byte(p1>>14) | byte(p2<<4) + buf[i+5] = byte(p2 >> 4) + buf[i+6] = byte(p2>>12) | byte(p3<<6) + buf[i+7] = byte(p3 >> 2) + buf[i+8] = byte(p3 >> 10) + + j += 4 + } + } else if Gamma1Bits == 19 { + j := 0 + for i := 0; i < PolyLeGamma1Size; i += 5 { + // Coefficients are in [0, γ₁] ∪ (Q-γ₁, Q) + p0 := Gamma1 - p[j] + p0 += uint32(int32(p0)>>31) & common.Q + p1 := Gamma1 - p[j+1] + p1 += uint32(int32(p1)>>31) & common.Q + + buf[i+0] = byte(p0) + buf[i+1] = byte(p0 >> 8) + buf[i+2] = byte(p0>>16) | byte(p1<<4) + buf[i+3] = byte(p1 >> 4) + buf[i+4] = byte(p1 >> 12) + + j += 2 + } + } else { + panic("γ₁ not supported") + } +} + +// Pack w₁ into buf, which must be of length PolyW1Size. +// +// Assumes w₁ is normalized. +func PolyPackW1(p *common.Poly, buf []byte) { + if Gamma1Bits == 19 { + p.PackLe16(buf) + } else if Gamma1Bits == 17 { + j := 0 + for i := 0; i < PolyW1Size; i += 3 { + buf[i] = byte(p[j]) | byte(p[j+1]<<6) + buf[i+1] = byte(p[j+1]>>2) | byte(p[j+2]<<4) + buf[i+2] = byte(p[j+2]>>4) | byte(p[j+3]<<2) + j += 4 + } + } else { + panic("unsupported γ₁") + } +} diff --git a/sign/mldsa/mldsa87/internal/pack_test.go b/sign/mldsa/mldsa87/internal/pack_test.go new file mode 100644 index 000000000..f952c6a09 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/pack_test.go @@ -0,0 +1,93 @@ +// Code generated from mode3/internal/pack_test.go by gen.go + +package internal + +import ( + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestPolyPackLeqEta(t *testing.T) { + var p1, p2 common.Poly + var seed [64]byte + var buf [PolyLeqEtaSize]byte + + for i := uint16(0); i < 100; i++ { + // Note that DeriveUniformLeqEta sets p to the right kind of + // unnormalized vector. + PolyDeriveUniformLeqEta(&p1, &seed, i) + for j := 0; j < PolyLeqEtaSize; j++ { + if p1[j] < common.Q-Eta || p1[j] > common.Q+Eta { + t.Fatalf("DerveUniformLeqEta out of bounds") + } + } + PolyPackLeqEta(&p1, buf[:]) + PolyUnpackLeqEta(&p2, buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT1(t *testing.T) { + var p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p1, &seed, i) + p1.Normalize() + for j := 0; j < common.N; j++ { + p1[j] &= 0x1ff + } + p1.PackT1(buf[:]) + p2.UnpackT1(buf[:]) + if p1 != p2 { + t.Fatalf("%v != %v", p1, p2) + } + } +} + +func TestPolyPackT0(t *testing.T) { + var p, p0, p1, p2 common.Poly + var seed [32]byte + var buf [common.PolyT0Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniform(&p, &seed, i) + p.Normalize() + p.Power2Round(&p0, &p1) + + p0.PackT0(buf[:]) + p2.UnpackT0(buf[:]) + if p0 != p2 { + t.Fatalf("%v !=\n%v", p0, p2) + } + } +} + +func BenchmarkUnpackLeGamma1(b *testing.B) { + var p common.Poly + var buf [PolyLeGamma1Size]byte + for i := 0; i < b.N; i++ { + PolyUnpackLeGamma1(&p, buf[:]) + } +} + +func TestPolyPackLeGamma1(t *testing.T) { + var p0, p1 common.Poly + var seed [64]byte + var buf [PolyLeGamma1Size]byte + + for i := uint16(0); i < 100; i++ { + PolyDeriveUniformLeGamma1(&p0, &seed, i) + p0.Normalize() + + PolyPackLeGamma1(&p0, buf[:]) + PolyUnpackLeGamma1(&p1, buf[:]) + if p0 != p1 { + t.Fatalf("%v != %v", p0, p1) + } + } +} diff --git a/sign/mldsa/mldsa87/internal/params.go b/sign/mldsa/mldsa87/internal/params.go new file mode 100644 index 000000000..e60c4d35f --- /dev/null +++ b/sign/mldsa/mldsa87/internal/params.go @@ -0,0 +1,19 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Name = "ML-DSA-87" + UseAES = false + K = 8 + L = 7 + Eta = 2 + DoubleEtaBits = 3 + Omega = 75 + Tau = 60 + Gamma1Bits = 19 + Gamma2 = 261888 + NIST = true + TRSize = 64 + CTildeSize = 64 +) diff --git a/sign/mldsa/mldsa87/internal/rounding.go b/sign/mldsa/mldsa87/internal/rounding.go new file mode 100644 index 000000000..58123c090 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/rounding.go @@ -0,0 +1,142 @@ +// Code generated from mode3/internal/rounding.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// Splits 0 ≤ a < q into a₀ and a₁ with a = a₁*α + a₀ with -α/2 < a₀ ≤ α/2, +// except for when we would have a₁ = (q-1)/α in which case a₁=0 is taken +// and -α/2 ≤ a₀ < 0. Returns a₀ + q. Note 0 ≤ a₁ < (q-1)/α. +// Recall α = 2γ₂. +func decompose(a uint32) (a0plusQ, a1 uint32) { + // a₁ = ⌈a / 128⌉ + a1 = (a + 127) >> 7 + + if Alpha == 523776 { + // 1025/2²² is close enough to 1/4092 so that a₁ + // becomes a/α rounded down. + a1 = ((a1*1025 + (1 << 21)) >> 22) + + // For the corner-case a₁ = (q-1)/α = 16, we have to set a₁=0. + a1 &= 15 + } else if Alpha == 190464 { + // 1488/2²⁴ is close enough to 1/1488 so that a₁ + // becomes a/α rounded down. + a1 = ((a1 * 11275) + (1 << 23)) >> 24 + + // For the corner-case a₁ = (q-1)/α = 44, we have to set a₁=0. + a1 ^= uint32(int32(43-a1)>>31) & a1 + } else { + panic("unsupported α") + } + + a0plusQ = a - a1*Alpha + + // In the corner-case, when we set a₁=0, we will incorrectly + // have a₀ > (q-1)/2 and we'll need to subtract q. As we + // return a₀ + q, that comes down to adding q if a₀ < (q-1)/2. + a0plusQ += uint32(int32(a0plusQ-(common.Q-1)/2)>>31) & common.Q + + return +} + +// Assume 0 ≤ r, f < Q with ‖f‖_∞ ≤ α/2. Decompose r as r = r1*α + r0 as +// computed by decompose(). Write r' := r - f (mod Q). Now, decompose +// r'=r-f again as r' = r'1*α + r'0 using decompose(). As f is small, we +// have r'1 = r1 + h, where h ∈ {-1, 0, 1}. makeHint() computes |h| +// given z0 := r0 - f (mod Q) and r1. With |h|, which is called the hint, +// we can reconstruct r1 using only r' = r - f, which is done by useHint(). +// To wit: +// +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// +// Assumes 0 ≤ z0 < Q. +func makeHint(z0, r1 uint32) uint32 { + // If -α/2 < r0 - f ≤ α/2, then r1*α + r0 - f is a valid decomposition of r' + // with the restrictions of decompose() and so r'1 = r1. So the hint + // should be 0. This is covered by the first two inequalities. + // There is one other case: if r0 - f = -α/2, then r1*α + r0 - f is also + // a valid decomposition if r1 = 0. In the other cases a one is carried + // and the hint should be 1. + if z0 <= Gamma2 || z0 > common.Q-Gamma2 || (z0 == common.Q-Gamma2 && r1 == 0) { + return 0 + } + return 1 +} + +// Uses the hint created by makeHint() to reconstruct r1 from r'=r-f; see +// documentation of makeHint() for context. +// Assumes 0 ≤ r' < Q. +func useHint(rp uint32, hint uint32) uint32 { + rp0plusQ, rp1 := decompose(rp) + if hint == 0 { + return rp1 + } + if rp0plusQ > common.Q { + return (rp1 + 1) & 15 + } + return (rp1 - 1) & 15 +} + +// Sets p to the hint polynomial for p0 the modified low bits and p1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint polynomial. +func PolyMakeHint(p, p0, p1 *common.Poly) (pop uint32) { + for i := 0; i < common.N; i++ { + h := makeHint(p0[i], p1[i]) + pop += h + p[i] = h + } + return +} + +// Computes corrections to the high bits of the polynomial q according +// to the hints in h and sets p to the corrected high bits. Returns p. +func PolyUseHint(p, q, hint *common.Poly) { + var q0PlusQ common.Poly + + // See useHint() and makeHint() for an explanation. We reimplement it + // here so that we can call Poly.Decompose(), which might be way faster + // than calling decompose() in a loop (for instance when having AVX2.) + + PolyDecompose(q, &q0PlusQ, p) + + for i := 0; i < common.N; i++ { + if hint[i] == 0 { + continue + } + if Gamma2 == 261888 { + if q0PlusQ[i] > common.Q { + p[i] = (p[i] + 1) & 15 + } else { + p[i] = (p[i] - 1) & 15 + } + } else if Gamma2 == 95232 { + if q0PlusQ[i] > common.Q { + if p[i] == 43 { + p[i] = 0 + } else { + p[i]++ + } + } else { + if p[i] == 0 { + p[i] = 43 + } else { + p[i]-- + } + } + } else { + panic("unsupported γ₂") + } + } +} + +// Splits each of the coefficients of p using decompose. +func PolyDecompose(p, p0PlusQ, p1 *common.Poly) { + for i := 0; i < common.N; i++ { + p0PlusQ[i], p1[i] = decompose(p[i]) + } +} diff --git a/sign/mldsa/mldsa87/internal/rounding_test.go b/sign/mldsa/mldsa87/internal/rounding_test.go new file mode 100644 index 000000000..ad653ca3f --- /dev/null +++ b/sign/mldsa/mldsa87/internal/rounding_test.go @@ -0,0 +1,81 @@ +// Code generated from mode3/internal/rounding_test.go by gen.go + +package internal + +import ( + "flag" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +var runVeryLongTest = flag.Bool("very-long", false, "runs very long tests") + +func TestDecompose(t *testing.T) { + for a := uint32(0); a < common.Q; a++ { + a0PlusQ, a1 := decompose(a) + a0 := int32(a0PlusQ) - int32(common.Q) + recombined := a0 + int32(Alpha*a1) + if a1 == 0 && recombined < 0 { + recombined += common.Q + if -(Alpha/2) > a0 || a0 >= 0 { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } else { + if (-(Alpha / 2) >= a0) || (a0 > Alpha/2) { + t.Fatalf("decompose(%v): a0 out of bounds", a) + } + } + if int32(a) != recombined { + t.Fatalf("decompose(%v) doesn't recombine %v %v", a, a0, a1) + } + } +} + +func TestMakeHint(t *testing.T) { + if !*runVeryLongTest { + t.SkipNow() + } + for w := uint32(0); w < common.Q; w++ { + w0, w1 := decompose(w) + for fn := uint32(0); fn <= Gamma2; fn++ { + fsign := false + for { + var f uint32 + if fsign { + if fn == 0 { + break + } + f = common.Q - fn + } else { + f = fn + } + + hint := makeHint(common.ReduceLe2Q(w0+common.Q-f), w1) + w1p := useHint(common.ReduceLe2Q(w+common.Q-f), hint) + if w1p != w1 { + t.Fatal() + } + + if fsign { + break + } + fsign = true + } + } + } +} + +func BenchmarkDecompose(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyDecompose(&p, &p0, &p1) + } +} + +func BenchmarkMakeHint(b *testing.B) { + var p, p0, p1 common.Poly + for i := 0; i < b.N; i++ { + PolyMakeHint(&p, &p0, &p1) + } +} diff --git a/sign/mldsa/mldsa87/internal/sample.go b/sign/mldsa/mldsa87/internal/sample.go new file mode 100644 index 000000000..62c261332 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/sample.go @@ -0,0 +1,370 @@ +// Code generated from mode3/internal/sample.go by gen.go + +package internal + +import ( + "encoding/binary" + + "github.com/cloudflare/circl/internal/sha3" + common "github.com/cloudflare/circl/sign/internal/dilithium" + "github.com/cloudflare/circl/simd/keccakf1600" +) + +// DeriveX4Available indicates whether the system supports the quick fourway +// sampling variants like PolyDeriveUniformX4. +var DeriveX4Available = keccakf1600.IsEnabledX4() && !UseAES + +// For each i, sample ps[i] uniformly from the given seed and nonces[i]. +// ps[i] may be nil and is ignored in that case. +// +// Can only be called when DeriveX4Available is true. +func PolyDeriveUniformX4(ps [4]*common.Poly, seed *[32]byte, nonces [4]uint16) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 4; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // Absorb the nonces, the SHAKE128 domain separator (0b1111), the + // start of the padding (0b...001) and the end of the padding 0b100... + // Recall that the rate of SHAKE128 is 168 --- i.e. 21 uint64s. + for j := 0; j < 4; j++ { + state[4*4+j] = uint64(nonces[j]) | (0x1f << 16) + state[20*4+j] = 0x80 << 56 + } + + var idx [4]int // indices into ps + for j := 0; j < 4; j++ { + if ps[j] == nil { + idx[j] = common.N // mark nil polynomial as completed + } + } + + done := false + for !done { + // Applies KeccaK-f[1600] to state to get the next 21 uint64s of each + // of the four SHAKE128 streams. + perm.Permute() + + done = true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + for i := 0; i < 7; i++ { + var t [8]uint32 + t[0] = uint32(state[i*3*4+j] & 0x7fffff) + t[1] = uint32((state[i*3*4+j] >> 24) & 0x7fffff) + t[2] = uint32((state[i*3*4+j] >> 48) | + ((state[(i*3+1)*4+j] & 0x7f) << 16)) + t[3] = uint32((state[(i*3+1)*4+j] >> 8) & 0x7fffff) + t[4] = uint32((state[(i*3+1)*4+j] >> 32) & 0x7fffff) + t[5] = uint32((state[(i*3+1)*4+j] >> 56) | + ((state[(i*3+2)*4+j] & 0x7fff) << 8)) + t[6] = uint32((state[(i*3+2)*4+j] >> 16) & 0x7fffff) + t[7] = uint32((state[(i*3+2)*4+j] >> 40) & 0x7fffff) + + for k := 0; k < 8; k++ { + if t[k] < common.Q { + ps[j][idx[j]] = t[k] + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + } + done = false + } + } +} + +// Sample p uniformly from the given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniform(p *common.Poly, seed *[32]byte, nonce uint16) { + var i, length int + var buf [12 * 16]byte // fits 168B SHAKE-128 rate and 12 16B AES blocks + + if UseAES { + length = 12 * 16 + } else { + length = 168 + } + + sample := func() { + // Note that 3 divides into 168 and 12*16, so we use up buf completely. + for j := 0; j < length && i < common.N; j += 3 { + t := (uint32(buf[j]) | (uint32(buf[j+1]) << 8) | + (uint32(buf[j+2]) << 16)) & 0x7fffff + + // We use rejection sampling + if t < common.Q { + p[i] = t + i++ + } + } + } + + if UseAES { + h := common.NewAesStream128(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [32 + 2]byte // 32 byte seed + uint16 nonce + h := sha3.NewShake128() + copy(iv[:32], seed[:]) + iv[32] = uint8(nonce) + iv[33] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:168]) + sample() + } + } +} + +// Sample p uniformly with coefficients of norm less than or equal η, +// using the given seed and nonce. +// +// p will not be normalized, but will have coefficients in [q-η,q+η]. +func PolyDeriveUniformLeqEta(p *common.Poly, seed *[64]byte, nonce uint16) { + // Assumes 2 < η < 8. + var i, length int + var buf [9 * 16]byte // fits 136B SHAKE-256 rate and 9 16B AES blocks + + if UseAES { + length = 9 * 16 + } else { + length = 136 + } + + sample := func() { + // We use rejection sampling + for j := 0; j < length && i < common.N; j++ { + t1 := uint32(buf[j]) & 15 + t2 := uint32(buf[j]) >> 4 + if Eta == 2 { // branch is eliminated by compiler + if t1 <= 14 { + t1 -= ((205 * t1) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 14 && i < common.N { + t2 -= ((205 * t2) >> 10) * 5 // reduce mod 5 + p[i] = common.Q + Eta - t2 + i++ + } + } else if Eta == 4 { + if t1 <= 2*Eta { + p[i] = common.Q + Eta - t1 + i++ + } + if t2 <= 2*Eta && i < common.N { + p[i] = common.Q + Eta - t2 + i++ + } + } else { + panic("unsupported η") + } + } + } + + if UseAES { + h := common.NewAesStream256(seed, nonce) + + for i < common.N { + h.SqueezeInto(buf[:length]) + sample() + } + } else { + var iv [64 + 2]byte // 64 byte seed + uint16 nonce + + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + + // 136 is SHAKE-256 rate + _, _ = h.Write(iv[:]) + + for i < common.N { + _, _ = h.Read(buf[:136]) + sample() + } + } +} + +// Sample v[i] uniformly with coefficients in (-γ₁,…,γ₁] using the +// given seed and nonce+i +// +// p will be normalized. +func VecLDeriveUniformLeGamma1(v *VecL, seed *[64]byte, nonce uint16) { + for i := 0; i < L; i++ { + PolyDeriveUniformLeGamma1(&v[i], seed, nonce+uint16(i)) + } +} + +// Sample p uniformly with coefficients in (-γ₁,…,γK1s] using the +// given seed and nonce. +// +// p will be normalized. +func PolyDeriveUniformLeGamma1(p *common.Poly, seed *[64]byte, nonce uint16) { + var buf [PolyLeGamma1Size]byte + + if UseAES { + h := common.NewAesStream256(seed, nonce) + h.SqueezeInto(buf[:]) + } else { + var iv [66]byte + h := sha3.NewShake256() + copy(iv[:64], seed[:]) + iv[64] = uint8(nonce) + iv[65] = uint8(nonce >> 8) + _, _ = h.Write(iv[:]) + _, _ = h.Read(buf[:]) + } + + PolyUnpackLeGamma1(p, buf[:]) +} + +// For each i, sample ps[i] uniformly with τ non-zero coefficients in {q-1,1} +// using the given seed and w1[i]. ps[i] may be nil and is ignored +// in that case. ps[i] will be normalized. +// +// Can only be called when DeriveX4Available is true. +// +// This function is currently not used (yet). +func PolyDeriveUniformBallX4(ps [4]*common.Poly, seed []byte) { + var perm keccakf1600.StateX4 + state := perm.Initialize(false) + + // Absorb the seed in the four states + for i := 0; i < 32/8; i++ { + v := binary.LittleEndian.Uint64(seed[8*i : 8*(i+1)]) + for j := 0; j < 4; j++ { + state[i*4+j] = v + } + } + + // SHAKE256 domain separator and padding + for j := 0; j < 4; j++ { + state[(32/8)*4+j] ^= 0x1f + state[16*4+j] ^= 0x80 << 56 + } + perm.Permute() + + var signs [4]uint64 + var idx [4]uint16 // indices into ps + + for j := 0; j < 4; j++ { + if ps[j] != nil { + signs[j] = state[j] + *ps[j] = common.Poly{} // zero ps[j] + idx[j] = common.N - Tau + } else { + idx[j] = common.N // mark as completed + } + } + + stateOffset := 1 + for { + done := true + + PolyLoop: + for j := 0; j < 4; j++ { + if idx[j] == common.N { + continue + } + + for i := stateOffset; i < 17; i++ { + var bs [8]byte + binary.LittleEndian.PutUint64(bs[:], state[4*i+j]) + for k := 0; k < 8; k++ { + b := uint16(bs[k]) + + if b > idx[j] { + continue + } + + ps[j][idx[j]] = ps[j][b] + ps[j][b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + ps[j][b] ^= uint32((-(signs[j] & 1)) & (1 | (common.Q - 1))) + signs[j] >>= 1 + + idx[j]++ + if idx[j] == common.N { + continue PolyLoop + } + } + } + + done = false + } + + if done { + break + } + + perm.Permute() + stateOffset = 0 + } +} + +// Samples p uniformly with τ non-zero coefficients in {q-1,1}. +// +// The polynomial p will be normalized. +func PolyDeriveUniformBall(p *common.Poly, seed []byte) { + var buf [136]byte // SHAKE-256 rate is 136 + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(buf[:]) + + // Essentially we generate a sequence of τ ones or minus ones, + // prepend 196 zeroes and shuffle the concatenation using the + // usual algorithm (Fisher--Yates.) + signs := binary.LittleEndian.Uint64(buf[:]) + bufOff := 8 // offset into buf + + *p = common.Poly{} // zero p + for i := uint16(common.N - Tau); i < common.N; i++ { + var b uint16 + + // Find location of where to move the new coefficient to using + // rejection sampling. + for { + if bufOff >= 136 { + _, _ = h.Read(buf[:]) + bufOff = 0 + } + + b = uint16(buf[bufOff]) + bufOff++ + + if b <= i { + break + } + } + + p[i] = p[b] + p[b] = 1 + // Takes least significant bit of signs and uses it for the sign. + // Note 1 ^ (1 | (Q-1)) = Q-1. + p[b] ^= uint32((-(signs & 1)) & (1 | (common.Q - 1))) + signs >>= 1 + } +} diff --git a/sign/mldsa/mldsa87/internal/sample_test.go b/sign/mldsa/mldsa87/internal/sample_test.go new file mode 100644 index 000000000..2059599eb --- /dev/null +++ b/sign/mldsa/mldsa87/internal/sample_test.go @@ -0,0 +1,266 @@ +// Code generated from mode3/internal/sample_test.go by gen.go + +package internal + +import ( + "encoding/binary" + "testing" + + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +func TestVectorDeriveUniform(t *testing.T) { + var p, p2 common.Poly + var seed [32]byte + if UseAES { + p2 = common.Poly{ + 6724291, 310295, 6949524, 4464039, 1482136, 2522903, + 7025059, 3006320, 7286364, 7516512, 3361305, 1955529, + 4765954, 1725325, 6933066, 4299100, 6625173, 4272792, + 583034, 4971409, 2259140, 7715362, 3975394, 2341624, + 5481174, 8150082, 365246, 5491939, 1083120, 7517301, + 3104783, 2475292, 184149, 6425226, 4591622, 5964030, + 4729604, 5471092, 1828227, 1082044, 2516245, 1692580, + 3274844, 5443294, 7256740, 4989638, 3191250, 7479519, + 5124211, 5603858, 1230692, 2513454, 2828034, 4254312, + 1512596, 5245430, 5517392, 2814840, 932545, 6826733, + 3511094, 4075348, 3233981, 7268882, 2913733, 4870249, + 4123492, 8124406, 4016949, 5478752, 2750895, 603525, + 5724798, 3985430, 3483012, 6434230, 3136996, 8297976, + 4107616, 7307748, 6962904, 7544473, 1193110, 3448595, + 4814773, 5607932, 8221314, 1054046, 1541208, 1866050, + 8227412, 2925778, 5293953, 2065416, 4972769, 3616283, + 7990594, 1105530, 7121836, 1170740, 7417431, 633146, + 253820, 7235019, 3539504, 6807707, 451390, 5481526, + 2859902, 1063061, 4579730, 7126652, 7033767, 4294814, + 1414604, 7620048, 1953268, 8304556, 1156814, 1182881, + 5311519, 3057534, 5277666, 682843, 2070398, 2874278, + 4859533, 6376664, 6694074, 1590242, 2620706, 8331066, + 5643845, 5037538, 2891516, 7004879, 3754327, 5031296, + 5463118, 2420870, 8116529, 5517696, 7435129, 3873963, + 710407, 713806, 175647, 4274571, 2655021, 7319503, + 3027243, 7129679, 4213435, 2429323, 4643873, 4568526, + 649664, 1720514, 6497260, 2683517, 7672754, 7105190, + 3148405, 5898369, 5667677, 8050874, 1587139, 7315260, + 4337416, 2202680, 2338714, 557467, 6752058, 2469794, + 485071, 1617604, 3590498, 2151466, 2005823, 7727956, + 7776292, 6783433, 6787146, 1732833, 3596857, 7436284, + 4483349, 4970142, 4472608, 6478342, 1236215, 5695744, + 2280717, 2889355, 3233946, 5187812, 978685, 5177364, + 2922353, 4824807, 5302883, 6739803, 8092453, 5883903, + 816553, 6041174, 8317591, 1459178, 5332455, 1835058, + 1368601, 2820950, 3479224, 2589540, 7992934, 3421045, + 4657128, 8292902, 4153567, 3553988, 7830320, 6722913, + 2555309, 4149801, 8328975, 1560545, 7757473, 3106458, + 4310856, 7135453, 3481032, 652626, 1841361, 8126828, + 6250018, 300536, 7380070, 8174419, 1418793, 6208185, + 3906256, 6679016, 1605701, 3561489, 5819724, 5746996, + 8044214, 7087187, 7102330, 4962927, 4253983, 7108567, + 4119736, 6584065, 441634, 6941656, + } + } else { + p2 = common.Poly{ + 2901364, 562527, 5258502, 3885002, 4190126, 4460268, 6884052, + 3514511, 5383040, 213206, 2155865, 5179607, 3551954, 2312357, + 6066350, 8126097, 1179080, 4787182, 6552182, 6713644, + 1561067, 7626063, 7859743, 5052321, 7032876, 7815031, 157938, + 1865184, 490802, 5717642, 3451902, 7000218, 3743250, 1677431, + 1875427, 5596150, 671623, 3819041, 6247594, 1014875, 4933545, + 7122446, 6682963, 3388398, 3335295, 943002, 1145083, 3113071, + 105967, 1916675, 7474561, 1107006, 700548, 2147909, 1603855, + 5049181, 437882, 6118899, 5656914, 6731065, 3066622, 865453, + 5427634, 981549, 4650873, 861291, 4003872, 5104220, 6171453, + 3723302, 7426315, 6137283, 4874820, 6052561, 53441, 5032874, + 5614778, 2248550, 1756499, 8280764, 8263880, 7600081, + 5118374, 795344, 7543392, 6869925, 1841187, 4181568, 584562, + 7483939, 4938664, 6863397, 5126354, 5218129, 6236086, + 4149293, 379169, 4368487, 7490569, 3409215, 1580463, 3081737, + 1278732, 7109719, 7371700, 2097931, 399836, 1700274, 7188595, + 6830029, 1548850, 6593138, 6849097, 1518037, 2859442, + 7772265, 7325153, 3281191, 7856131, 4995056, 4684325, + 1351194, 8223904, 6817307, 2484146, 131782, 397032, 7436778, + 7973479, 3171829, 5624626, 3540123, 7150120, 8313283, + 3604714, 1043574, 117692, 7797783, 7909392, 903315, 7335342, + 7501562, 5826142, 2709813, 8245473, 2369045, 2782257, + 5762833, 6474114, 6862031, 424522, 594248, 2626630, 7659983, + 5642869, 4075194, 1592129, 245547, 5271031, 3205046, 982375, + 267873, 1286496, 7230481, 3208972, 7485411, 676111, 4944500, + 2959742, 5934456, 1414847, 6067948, 1709895, 4648315, 126008, + 8258986, 2183134, 2302072, 4674924, 4306056, 7465311, + 6500270, 4247428, 4016815, 4973426, 294287, 2456847, 3289700, + 2732169, 1159447, 5569724, 140001, 3237977, 8007761, 5874533, + 255652, 3119586, 2102434, 6248250, 8152822, 8006066, 7708625, + 6997719, 6260212, 6186962, 6636650, 7836834, 7998017, + 2061516, 1197591, 1706544, 733027, 2392907, 2700000, 8254598, + 4488002, 160495, 2985325, 2036837, 2703633, 6406550, 3579947, + 6195178, 5552390, 6804584, 6305468, 5731980, 6095195, + 3323409, 1322661, 6690942, 3374630, 5615167, 479044, 3136054, + 4380418, 2833144, 7829577, 1770522, 6056687, 240415, 14780, + 3740517, 5224226, 3547288, 2083124, 4699398, 3654239, + 5624978, 585593, 3655369, 2281739, 3338565, 1908093, 7784706, + 4352830, + } + } + for i := 0; i < 32; i++ { + seed[i] = byte(i) + } + PolyDeriveUniform(&p, &seed, 30000) + if p != p2 { + t.Fatalf("%v != %v", p, p2) + } +} + +func TestDeriveUniform(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniform(&p, &seed, uint16(i)) + if !PolyNormalized(&p) { + t.Fatal() + } + } +} + +func TestDeriveUniformLeqEta(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeqEta(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if p[j] < common.Q-Eta || p[j] > common.Q+Eta { + t.Fatal() + } + } + } +} + +func TestDeriveUniformLeGamma1(t *testing.T) { + var p common.Poly + var seed [64]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + for j := 0; j < common.N; j++ { + if (p[j] > Gamma1 && p[j] <= common.Q-Gamma1) || p[j] >= common.Q { + t.Fatal() + } + } + } +} + +func TestDeriveUniformBall(t *testing.T) { + var p common.Poly + var seed [32]byte + for i := 0; i < 100; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + PolyDeriveUniformBall(&p, seed[:]) + nonzero := 0 + for j := 0; j < common.N; j++ { + if p[j] != 0 { + if p[j] != 1 && p[j] != common.Q-1 { + t.Fatal() + } + nonzero++ + } + } + if nonzero != Tau { + t.Fatal() + } + } +} + +func TestDeriveUniformX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + nonces := [4]uint16{12345, 54321, 13532, 37377} + + for i := 0; i < len(seed); i++ { + seed[i] = byte(i) + } + + PolyDeriveUniformX4([4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, &seed, + nonces) + for i := 0; i < 4; i++ { + PolyDeriveUniform(&p, &seed, nonces[i]) + if ps[i] != p { + t.Fatal() + } + } +} + +func TestDeriveUniformBallX4(t *testing.T) { + if !DeriveX4Available { + t.SkipNow() + } + var ps [4]common.Poly + var p common.Poly + var seed [32]byte + PolyDeriveUniformBallX4( + [4]*common.Poly{&ps[0], &ps[1], &ps[2], &ps[3]}, + seed[:], + ) + for j := 0; j < 4; j++ { + PolyDeriveUniformBall(&p, seed[:]) + if ps[j] != p { + t.Fatalf("%d\n%v\n%v", j, ps[j], p) + } + } +} + +func BenchmarkPolyDeriveUniformBall(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBall(&p, seed[:]) + } +} + +func BenchmarkPolyDeriveUniformBallX4(b *testing.B) { + var seed [32]byte + var p common.Poly + var w1 VecK + for i := 0; i < b.N; i++ { + w1[0][0] = uint32(i) + PolyDeriveUniformBallX4( + [4]*common.Poly{&p, &p, &p, &p}, + seed[:], + ) + } +} + +func BenchmarkPolyDeriveUniform(b *testing.B) { + var seed [32]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniform(&p, &seed, uint16(i)) + } +} + +func BenchmarkPolyDeriveUniformX4(b *testing.B) { + if !DeriveX4Available { + b.SkipNow() + } + var seed [32]byte + var p [4]common.Poly + for i := 0; i < b.N; i++ { + nonce := uint16(4 * i) + PolyDeriveUniformX4([4]*common.Poly{&p[0], &p[1], &p[2], &p[3]}, + &seed, [4]uint16{nonce, nonce + 1, nonce + 2, nonce + 3}) + } +} + +func BenchmarkPolyDeriveUniformLeGamma1(b *testing.B) { + var seed [64]byte + var p common.Poly + for i := 0; i < b.N; i++ { + PolyDeriveUniformLeGamma1(&p, &seed, uint16(i)) + } +} diff --git a/sign/mldsa/mldsa87/internal/vec.go b/sign/mldsa/mldsa87/internal/vec.go new file mode 100644 index 000000000..d07d3b245 --- /dev/null +++ b/sign/mldsa/mldsa87/internal/vec.go @@ -0,0 +1,281 @@ +// Code generated from mode3/internal/vec.go by gen.go + +package internal + +import ( + common "github.com/cloudflare/circl/sign/internal/dilithium" +) + +// A vector of L polynomials. +type VecL [L]common.Poly + +// A vector of K polynomials. +type VecK [K]common.Poly + +// Normalize the polynomials in this vector. +func (v *VecL) Normalize() { + for i := 0; i < L; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecL) NormalizeAssumingLe2Q() { + for i := 0; i < L; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecL) Add(w, u *VecL) { + for i := 0; i < L; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecL) NTT() { + for i := 0; i < L; i++ { + v[i].NTT() + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecL) Exceeds(bound uint32) bool { + for i := 0; i < L; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Power2Round(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecL) Decompose(v0PlusQ, v1 *VecL) { + for i := 0; i < L; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecL) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeqEta(). +func (v *VecL) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sequentially packs each polynomial using PolyPackLeGamma1(). +func (v *VecL) PackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyPackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Sets v to the polynomials packed in buf using VecL.PackLeGamma1(). +func (v *VecL) UnpackLeGamma1(buf []byte) { + offset := 0 + for i := 0; i < L; i++ { + PolyUnpackLeGamma1(&v[i], buf[offset:]) + offset += PolyLeGamma1Size + } +} + +// Normalize the polynomials in this vector. +func (v *VecK) Normalize() { + for i := 0; i < K; i++ { + v[i].Normalize() + } +} + +// Normalize the polynomials in this vector assuming their coefficients +// are already bounded by 2q. +func (v *VecK) NormalizeAssumingLe2Q() { + for i := 0; i < K; i++ { + v[i].NormalizeAssumingLe2Q() + } +} + +// Sets v to w + u. Does not normalize. +func (v *VecK) Add(w, u *VecK) { + for i := 0; i < K; i++ { + v[i].Add(&w[i], &u[i]) + } +} + +// Checks whether any of the coefficients exceeds the given bound in supnorm +// +// Requires the vector to be normalized. +func (v *VecK) Exceeds(bound uint32) bool { + for i := 0; i < K; i++ { + if v[i].Exceeds(bound) { + return true + } + } + return false +} + +// Applies Poly.Power2Round componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Power2Round(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + v[i].Power2Round(&v0PlusQ[i], &v1[i]) + } +} + +// Applies Poly.Decompose componentwise. +// +// Requires the vector to be normalized. +func (v *VecK) Decompose(v0PlusQ, v1 *VecK) { + for i := 0; i < K; i++ { + PolyDecompose(&v[i], &v0PlusQ[i], &v1[i]) + } +} + +// Sets v to the hint vector for v0 the modified low bits and v1 +// the unmodified high bits --- see makeHint(). +// +// Returns the number of ones in the hint vector. +func (v *VecK) MakeHint(v0, v1 *VecK) (pop uint32) { + for i := 0; i < K; i++ { + pop += PolyMakeHint(&v[i], &v0[i], &v1[i]) + } + return +} + +// Computes corrections to the high bits of the polynomials in the vector +// w using the hints in h and sets v to the corrected high bits. Returns v. +// See useHint(). +func (v *VecK) UseHint(q, hint *VecK) *VecK { + for i := 0; i < K; i++ { + PolyUseHint(&v[i], &q[i], &hint[i]) + } + return v +} + +// Sequentially packs each polynomial using Poly.PackT1(). +func (v *VecK) PackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sets v to the vector packed into buf by PackT1(). +func (v *VecK) UnpackT1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT1(buf[offset:]) + offset += common.PolyT1Size + } +} + +// Sequentially packs each polynomial using Poly.PackT0(). +func (v *VecK) PackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].PackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sets v to the vector packed into buf by PackT0(). +func (v *VecK) UnpackT0(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + v[i].UnpackT0(buf[offset:]) + offset += common.PolyT0Size + } +} + +// Sequentially packs each polynomial using Poly.PackLeqEta(). +func (v *VecK) PackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Sets v to the polynomials packed in buf using VecK.PackLeqEta(). +func (v *VecK) UnpackLeqEta(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyUnpackLeqEta(&v[i], buf[offset:]) + offset += PolyLeqEtaSize + } +} + +// Applies NTT componentwise. See Poly.NTT() for details. +func (v *VecK) NTT() { + for i := 0; i < K; i++ { + v[i].NTT() + } +} + +// Sequentially packs each polynomial using PolyPackW1(). +func (v *VecK) PackW1(buf []byte) { + offset := 0 + for i := 0; i < K; i++ { + PolyPackW1(&v[i], buf[offset:]) + offset += PolyW1Size + } +} + +// Sets v to a - b. +// +// Warning: assumes coefficients of the polynomials of b are less than 2q. +func (v *VecK) Sub(a, b *VecK) { + for i := 0; i < K; i++ { + v[i].Sub(&a[i], &b[i]) + } +} + +// Sets v to 2ᵈ w without reducing. +func (v *VecK) MulBy2toD(w *VecK) { + for i := 0; i < K; i++ { + v[i].MulBy2toD(&w[i]) + } +} + +// Applies InvNTT componentwise. See Poly.InvNTT() for details. +func (v *VecK) InvNTT() { + for i := 0; i < K; i++ { + v[i].InvNTT() + } +} + +// Applies Poly.ReduceLe2Q() componentwise. +func (v *VecK) ReduceLe2Q() { + for i := 0; i < K; i++ { + v[i].ReduceLe2Q() + } +}