Skip to content

Commit 2b4626d

Browse files
bwesterbarmfazh
authored andcommitted
Add ML-KEM (FIPS 203).
We keep Kyber around (for now) as it's currently widely deployed. Code differences between them are minimal anyway. Tests against NIST's ACVP test vectors.
1 parent d26845f commit 2b4626d

File tree

26 files changed

+1783
-34
lines changed

26 files changed

+1783
-34
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Alternatively, look at the [Cloudflare Go](https://github.com/cloudflare/go/tree
8282
|:---:|
8383

8484
- [CSIDH](./dh/csidh): Post-Quantum Commutative Group Action ([CSIDH](https://csidh.isogeny.org/)).
85+
- [ML-KEM](./kem/mlkem): modes 512, 768, 1024 ([ML-KEM](https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf)).
8586
- [Kyber KEM](./kem/kyber): modes 512, 768, 1024 ([KYBER](https://pq-crystals.org/kyber/)).
8687
- [FrodoKEM](./kem/frodo): modes 640-SHAKE. ([FrodoKEM](https://frodokem.org/))
8788
- (**insecure, deprecated**) ~~[SIDH/SIKE](./kem/sike)~~: Supersingular Key Encapsulation with primes p434, p503, p751 ([SIKE](https://sike.org/)).

kem/kyber/gen.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ package main
77

88
import (
99
"bytes"
10+
"fmt"
1011
"go/format"
1112
"io/ioutil"
13+
"path"
1214
"strings"
1315
"text/template"
1416
)
@@ -17,15 +19,43 @@ type Instance struct {
1719
Name string
1820
}
1921

22+
func (m Instance) KemName() string {
23+
if m.NIST() {
24+
return m.Name
25+
}
26+
return m.Name + ".CCAKEM"
27+
}
28+
29+
func (m Instance) NIST() bool {
30+
return strings.HasPrefix(m.Name, "ML-KEM")
31+
}
32+
33+
func (m Instance) PkePkg() string {
34+
if !m.NIST() {
35+
return m.Pkg()
36+
}
37+
return strings.ReplaceAll(m.Pkg(), "mlkem", "kyber")
38+
}
39+
2040
func (m Instance) Pkg() string {
21-
return strings.ToLower(m.Name)
41+
return strings.ToLower(strings.ReplaceAll(m.Name, "-", ""))
42+
}
43+
44+
func (m Instance) PkgPath() string {
45+
if m.NIST() {
46+
return path.Join("..", "mlkem", m.Pkg())
47+
}
48+
return m.Pkg()
2249
}
2350

2451
var (
2552
Instances = []Instance{
2653
{Name: "Kyber512"},
2754
{Name: "Kyber768"},
2855
{Name: "Kyber1024"},
56+
{Name: "ML-KEM-512"},
57+
{Name: "ML-KEM-768"},
58+
{Name: "ML-KEM-1024"},
2959
}
3060
TemplateWarning = "// Code generated from"
3161
)
@@ -51,15 +81,15 @@ func generatePackageFiles() {
5181
// Formating output code
5282
code, err := format.Source(buf.Bytes())
5383
if err != nil {
54-
panic("error formating code")
84+
panic(fmt.Sprintf("error formating code: %v", err))
5585
}
5686

5787
res := string(code)
5888
offset := strings.Index(res, TemplateWarning)
5989
if offset == -1 {
6090
panic("Missing template warning in pkg.templ.go")
6191
}
62-
err = ioutil.WriteFile(mode.Pkg()+"/kyber.go", []byte(res[offset:]), 0o644)
92+
err = ioutil.WriteFile(mode.PkgPath()+"/kyber.go", []byte(res[offset:]), 0o644)
6393
if err != nil {
6494
panic(err)
6595
}

kem/kyber/kat_test.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"crypto/sha256"
99
"fmt"
10+
"strings"
1011
"testing"
1112

1213
"github.com/cloudflare/circl/internal/nist"
@@ -22,6 +23,12 @@ func TestPQCgenKATKem(t *testing.T) {
2223
{"Kyber1024", "89248f2f33f7f4f7051729111f3049c409a933ec904aedadf035f30fa5646cd5"},
2324
{"Kyber768", "a1e122cad3c24bc51622e4c242d8b8acbcd3f618fee4220400605ca8f9ea02c2"},
2425
{"Kyber512", "e9c2bd37133fcb40772f81559f14b1f58dccd1c816701be9ba6214d43baf4547"},
26+
27+
// TODO crossreference with standard branch of reference implementation
28+
// once they've added the final change: domain separation in K-PKE.KeyGen().
29+
{"ML-KEM-512", "a30184edee53b3b009356e1e31d7f9e93ce82550e3c622d7192e387b0cc84f2e"},
30+
{"ML-KEM-768", "729367b590637f4a93c68d5e4a4d2e2b4454842a52c9eec503e3a0d24cb66471"},
31+
{"ML-KEM-1024", "3fba7327d0320cb6134badf2a1bcb963a5b3c0026c7dece8f00d6a6155e47b33"},
2532
}
2633
for _, kat := range kats {
2734
kat := kat
@@ -45,18 +52,26 @@ func testPQCgenKATKem(t *testing.T, name, expected string) {
4552
}
4653
f := sha256.New()
4754
g := nist.NewDRBG(&seed)
48-
fmt.Fprintf(f, "# %s\n\n", name)
55+
56+
// The "standard" branch reference implementation still uses Kyber
57+
// as name instead of ML-KEM.
58+
fmt.Fprintf(f, "# %s\n\n", strings.ReplaceAll(name, "ML-KEM-", "Kyber"))
4959
for i := 0; i < 100; i++ {
5060
g.Fill(seed[:])
5161
fmt.Fprintf(f, "count = %d\n", i)
5262
fmt.Fprintf(f, "seed = %X\n", seed)
5363
g2 := nist.NewDRBG(&seed)
5464

55-
// This is not equivalent to g2.Fill(kseed[:]). As the reference
56-
// implementation calls randombytes twice generating the keypair,
57-
// we have to do that as well.
58-
g2.Fill(kseed[:32])
59-
g2.Fill(kseed[32:])
65+
if strings.HasPrefix(name, "ML-KEM") {
66+
// https://github.com/pq-crystals/kyber/commit/830e0ba1a7fdba6cde03f8139b0d41ad2102b860
67+
g2.Fill(kseed[:])
68+
} else {
69+
// This is not equivalent to g2.Fill(kseed[:]). As the reference
70+
// implementation calls randombytes twice generating the keypair,
71+
// we have to do that as well.
72+
g2.Fill(kseed[:32])
73+
g2.Fill(kseed[32:])
74+
}
6075

6176
g2.Fill(eseed)
6277
pk, sk := scheme.DeriveKeyPair(kseed)
@@ -73,6 +88,6 @@ func testPQCgenKATKem(t *testing.T, name, expected string) {
7388
fmt.Fprintf(f, "ss = %X\n\n", ss)
7489
}
7590
if fmt.Sprintf("%x", f.Sum(nil)) != expected {
76-
t.Fatal()
91+
t.Fatalf("%s %x %s", name, f.Sum(nil), expected)
7792
}
7893
}

kem/kyber/kyber1024/kyber.go

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kem/kyber/kyber512/kyber.go

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kem/kyber/kyber768/kyber.go

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)