Skip to content

Commit 155bc02

Browse files
committed
ML-DSA: Add preliminary Wycheproof test vectors
1 parent c5816d7 commit 155bc02

10 files changed

Lines changed: 181 additions & 0 deletions
Binary file not shown.
Binary file not shown.
191 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
263 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
322 KB
Binary file not shown.

sign/schemes/wycheproof_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package schemes
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"os"
7+
"path"
8+
"strings"
9+
"testing"
10+
11+
"github.com/cloudflare/circl/internal/test"
12+
"github.com/cloudflare/circl/sign"
13+
)
14+
15+
const testDir = "testdata/wycheproof"
16+
17+
type Test struct {
18+
Msg test.HexBytes `json:"msg"`
19+
Sig test.HexBytes `json:"sig"`
20+
Result string `json:"result"`
21+
Ctx test.HexBytes `json:"ctx"`
22+
ID int `json:"tcId"`
23+
Comment string `json:"comment"`
24+
}
25+
26+
type TestGroup struct {
27+
PrivateKey test.HexBytes `json:"privateKey"`
28+
PrivateSeed test.HexBytes `json:"privateSeed"`
29+
PublicKey test.HexBytes `json:"publicKey"`
30+
Tests []Test `json:"tests"`
31+
Type string `json:"type"`
32+
}
33+
34+
type TestSet struct {
35+
Algorithm string `json:"algorithm"`
36+
TestGroups []TestGroup `json:"testGroups"`
37+
}
38+
39+
//nolint:gocyclo
40+
func runTest(t *testing.T, name string) {
41+
raw, err := test.ReadGzip(path.Join(testDir, name))
42+
if err != nil {
43+
t.Fatalf("ReadGzip(): %v", err)
44+
}
45+
46+
var ts TestSet
47+
if err := json.Unmarshal(raw, &ts); err != nil {
48+
t.Fatalf("json.Unmarshal(): %v", err)
49+
}
50+
51+
scheme := ByName(ts.Algorithm)
52+
if scheme == nil {
53+
t.Fatalf("Can't find scheme %s", ts.Algorithm)
54+
}
55+
56+
for _, tg := range ts.TestGroups {
57+
switch tg.Type {
58+
case "MlDsaSign":
59+
var sk sign.PrivateKey
60+
var skErr error
61+
if tg.PrivateKey == nil && tg.PrivateSeed == nil {
62+
t.Fatal("Neither private key or seed are set")
63+
}
64+
if tg.PrivateKey != nil && tg.PrivateSeed != nil {
65+
t.Fatal("Both private key and seed are set")
66+
}
67+
if tg.PublicKey != nil {
68+
t.Fatal("public key set")
69+
}
70+
if tg.PrivateSeed != nil {
71+
_, sk = scheme.DeriveKey(tg.PrivateSeed)
72+
} else {
73+
sk, skErr = scheme.UnmarshalBinaryPrivateKey(tg.PrivateKey)
74+
}
75+
for _, tc := range tg.Tests {
76+
// TODO The standards don't require rejecting private keys
77+
// with out of range s1/s2. Pending discussion on whether
78+
// we should reject them, we're skipping these testcases.
79+
if tc.Comment == "private key with s1 vector out of range" ||
80+
tc.Comment == "private key with s2 vector out of range" {
81+
continue
82+
}
83+
84+
if sk == nil {
85+
if tc.Result == "invalid" { //nolint:goconst
86+
continue
87+
}
88+
t.Fatalf("Couldn't parse private key: %v", skErr)
89+
}
90+
91+
sig, err := calmSign(
92+
scheme,
93+
sk,
94+
tc.Msg,
95+
&sign.SignatureOpts{Context: string(tc.Ctx)},
96+
)
97+
98+
if tc.Result == "invalid" {
99+
if err == nil {
100+
t.Fatalf("Expected error %v", tc.ID)
101+
}
102+
continue
103+
}
104+
105+
if err != nil || skErr != nil {
106+
t.Fatalf("Unexpected panic: %v", err)
107+
}
108+
109+
if !bytes.Equal(sig, tc.Sig) {
110+
t.Fatalf("Signature did not match: %v %v", sig, tc.Sig)
111+
}
112+
}
113+
case "MlDsaVerify":
114+
var pk sign.PublicKey
115+
var pkErr error
116+
if tg.PrivateKey != nil || tg.PrivateSeed != nil {
117+
t.Fatal("Private key set")
118+
}
119+
if tg.PublicKey == nil {
120+
t.Fatal("Public key not set")
121+
}
122+
pk, pkErr = scheme.UnmarshalBinaryPublicKey(tg.PublicKey)
123+
for _, tc := range tg.Tests {
124+
if pk == nil {
125+
if tc.Result == "invalid" {
126+
continue
127+
}
128+
t.Fatalf("Couldn't parse private key: %v", pkErr)
129+
}
130+
131+
ok := scheme.Verify(
132+
pk,
133+
tc.Msg,
134+
tc.Sig,
135+
&sign.SignatureOpts{Context: string(tc.Ctx)},
136+
)
137+
138+
if tc.Result == "invalid" {
139+
if ok {
140+
t.Fatalf("Expected failure %d", tc.ID)
141+
}
142+
continue
143+
}
144+
145+
if !ok {
146+
t.Fatal("expected success")
147+
}
148+
}
149+
default:
150+
t.Fatalf("Unknown test group type: %s", tg.Type)
151+
}
152+
}
153+
}
154+
155+
func calmSign(scheme sign.Scheme, sk sign.PrivateKey,
156+
msg []byte, opts *sign.SignatureOpts,
157+
) (sig []byte, err error) {
158+
defer func() {
159+
if r := recover(); r != nil {
160+
err = r.(error)
161+
}
162+
}()
163+
sig = scheme.Sign(sk, msg, opts)
164+
return
165+
}
166+
167+
func TestWycheproof(t *testing.T) {
168+
entries, err := os.ReadDir(testDir)
169+
if err != nil {
170+
t.Fatal(err)
171+
}
172+
173+
for _, entry := range entries {
174+
if !strings.HasSuffix(entry.Name(), ".json.gz") {
175+
continue
176+
}
177+
t.Run(entry.Name(), func(t *testing.T) {
178+
runTest(t, entry.Name())
179+
})
180+
}
181+
}

0 commit comments

Comments
 (0)