-
Notifications
You must be signed in to change notification settings - Fork 16
/
test_dilithium.py
182 lines (145 loc) · 5.51 KB
/
test_dilithium.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import unittest
import os
from dilithium_py.dilithium import Dilithium2, Dilithium3, Dilithium5
from dilithium_py.drbg.aes256_ctr_drbg import AES256_CTR_DRBG
def parse_kat_data(data):
"""
Helper function to parse data from KAT
file to bytes in a dictionary
"""
parsed_data = {}
count_blocks = data.split("\n\n")
for block in count_blocks[1:-1]:
block_data = block.split("\n")
count, seed, mlen, msg, pk, sk, smlen, sm = [
line.split(" = ")[-1] for line in block_data
]
parsed_data[int(count)] = {
"seed": bytes.fromhex(seed),
"msg": bytes.fromhex(msg),
"mlen": int(mlen),
"pk": bytes.fromhex(pk),
"sk": bytes.fromhex(sk),
"sm": bytes.fromhex(sm),
}
return parsed_data
class TestDilithium(unittest.TestCase):
"""
Test Dilithium for internal
consistency by generating signatures
and verifying them!
"""
def generic_test_dilithium(self, Dilithium):
msg = b"Signed by dilithium" + os.urandom(16)
# Perform signature process
pk, sk = Dilithium.keygen()
sig = Dilithium.sign(sk, msg)
check_verify = Dilithium.verify(pk, msg, sig)
# Generate some fail cases
pk_bad, _ = Dilithium.keygen()
check_wrong_pk = Dilithium.verify(pk_bad, msg, sig)
check_wrong_msg = Dilithium.verify(pk, b"", sig)
# Check that signature works
self.assertTrue(check_verify)
# Check changing the key breaks verify
self.assertFalse(check_wrong_pk)
# Check changing the message breaks verify
self.assertFalse(check_wrong_msg)
def test_dilithium2(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium2)
def test_dilithium3(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium3)
def test_dilithium5(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium5)
class TestDilithiumDRBG(unittest.TestCase):
"""
Ensure that deterministic DRBG is deterministic!
Uses AES256 CTR DRBG for randomness.
Note: requires pycryptodome for AES impl.
"""
def generic_test_dilithium(self, Dilithium):
"""
First we generate five pk,sk pairs
from the same seed and make sure
they're all the same
"""
seed = os.urandom(48)
pk_output = []
for _ in range(5):
Dilithium.set_drbg_seed(seed)
pk, sk = Dilithium.keygen()
pk_output.append(pk + sk)
self.assertEqual(len(pk_output), 5)
self.assertEqual(len(set(pk_output)), 1)
"""
Now given a fixed keypair make sure
that all the signatures are the same
and that they all verify correctly!
"""
sig_output = []
seed = os.urandom(48)
msg = b"Signed by Dilithium" + os.urandom(32)
pk, sk = Dilithium.keygen()
for _ in range(5):
Dilithium.set_drbg_seed(seed)
sig = Dilithium.sign(sk, msg)
verify = Dilithium.verify(pk, msg, sig)
# Check signature worked
self.assertTrue(verify)
sig_output.append(sig)
# Make sure all five signatures are the same
self.assertEqual(len(sig_output), 5)
self.assertEqual(len(set(sig_output)), 1)
def test_dilithium2(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium2)
def test_dilithium3(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium3)
def test_dilithium5(self):
for _ in range(5):
self.generic_test_dilithium(Dilithium5)
class TestKnownTestValuesDilithium(unittest.TestCase):
def generic_test_dilithium(self, Dilithium, file_name):
entropy_input = bytes([i for i in range(48)])
drbg = AES256_CTR_DRBG(entropy_input)
with open(f"assets/{file_name}") as f:
# extract data from KAT
kat_data = f.read()
parsed_data = parse_kat_data(kat_data)
for count in range(100):
data = parsed_data[count]
seed = drbg.random_bytes(48)
self.assertEqual(data["seed"], seed)
msg_len = data["mlen"]
msg = drbg.random_bytes(msg_len)
self.assertEqual(data["msg"], msg)
Dilithium.set_drbg_seed(seed)
pk, sk = Dilithium.keygen()
# Check that the keygen matches
self.assertEqual(data["pk"], pk)
self.assertEqual(data["sk"], sk)
# Check that the signature matches
sm_KAT = data["sm"]
sig_KAT = sm_KAT[:-msg_len]
# sm_KAT has message as the last mlen bytes
self.assertEqual(msg, sm_KAT[-msg_len:])
# Ensure that a generated signature matches
# the one extracted from the KAT
sig = Dilithium.sign(sk, msg)
self.assertEqual(sig, sig_KAT)
# Finally, make sure that the signature is
# valid for the message
verify_KAT = Dilithium.verify(pk, msg, sig)
self.assertTrue(verify_KAT)
def test_dilithium2(self):
self.generic_test_dilithium(Dilithium2, "PQCsignKAT_Dilithium2.rsp")
def test_dilithium3(self):
self.generic_test_dilithium(Dilithium3, "PQCsignKAT_Dilithium3.rsp")
def test_dilithium5(self):
self.generic_test_dilithium(Dilithium5, "PQCsignKAT_Dilithium5.rsp")
if __name__ == "__main__":
unittest.main()