7
7
> [ !CAUTION]
8
8
> :warning : ** Under no circumstances should this be used for cryptographic
9
9
applications.** :warning :
10
- >
10
+ >
11
11
> This is an educational resource and has not been designed to be secure
12
12
> against any form of side-channel attack. The intended use of this project
13
13
> is for learning and experimenting with ML-DSA and Dilithium
@@ -22,16 +22,16 @@ This repository contains a pure python implementation of both:
22
22
23
23
** Note** : This project has followed
24
24
[ ` kyber-py ` ] ( https://github.com/GiacomoPope/kyber-py ) which is a pure-python
25
- implementation of CRYSTALS-Kyber and ML-KEM and reuses a lot of code.
25
+ implementation of CRYSTALS-Kyber and ML-KEM and reuses a lot of code.
26
26
27
27
## Disclaimer
28
28
29
29
I have written ` dilithium-py ` as a way to learn about the way protocol works,
30
30
and to try and create a clean, well commented implementation which people can
31
31
learn from.
32
32
33
- This code is not constant time, or written to be performant. Rather, it was
34
- written so that reading though the pseudocode of the
33
+ This code is not constant time, or written to be performant. Rather, it was
34
+ written so that reading though the pseudocode of the
35
35
[ specification] ( https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf )
36
36
closely matches the code which we use within ` dilithium.py ` and supporting files.
37
37
@@ -68,22 +68,22 @@ The KAT files were either downloaded or generated:
68
68
69
69
### Generating KAT files for Dilithium
70
70
71
- This implementation is based off the most recent specification (v3.1).
72
- There were
73
- [ breaking changes] ( https://github.com/pq-crystals/dilithium/commit/e989e691ae3d3f5933d012ab074bdc413ebc6fad )
71
+ This implementation is based off the most recent specification (v3.1).
72
+ There were
73
+ [ breaking changes] ( https://github.com/pq-crystals/dilithium/commit/e989e691ae3d3f5933d012ab074bdc413ebc6fad )
74
74
to the KAT files submitted to NIST when Dilithium was updated to 3.1, so the
75
75
NIST KAT files will not match our code.
76
76
77
- To deal with this, we generated our own KAT files from the
77
+ To deal with this, we generated our own KAT files from the
78
78
[ reference implementation] ( https://github.com/pq-crystals/dilithium/releases/tag/v3.1 )
79
79
for version 3.1. These are the files inside [ assets] ( assets/ ) .
80
80
81
81
### Dependencies
82
82
83
83
Originally, as with ` kyber-py ` , this project was planned to have zero
84
- dependencies, however like ` kyber-py ` , to pass the KATs, I need a
84
+ dependencies, however like ` kyber-py ` , to pass the KATs, I need a
85
85
deterministic CSRNG. The reference implementation uses
86
- AES256 CTR DRBG. I have implemented this in [ ` ase256_ctr_drbg.py ` ] ( src/dilithium_py/drbg/ase256_ctr_drbg.py ) .
86
+ AES256 CTR DRBG. I have implemented this in [ ` ase256_ctr_drbg.py ` ] ( src/dilithium_py/drbg/ase256_ctr_drbg.py ) .
87
87
However, I have not implemented AES itself, instead I import this from ` pycryptodome ` .
88
88
89
89
To install dependencies, run ` pip install -r requirements.txt ` .
@@ -112,16 +112,16 @@ There are three functions exposed on the `ML_DSA` class which are intended
112
112
for use:
113
113
114
114
- ` ML_DSA.keygen() ` : generate a bit-packed keypair ` (pk, sk) `
115
- - ` ML_DSA.sign(sk, msg) ` : generate a bit-packed signature ` sig `
115
+ - ` ML_DSA.sign(sk, msg) ` : generate a bit-packed signature ` sig `
116
116
from the message ` msg ` and bit-packed secret key ` sk ` .
117
117
- ` ML_DSA.verify(pk, msg, sig) ` : verify that the bit-packed ` sig ` is
118
118
valid for a given message ` msg ` and bit-packed public key ` pk ` .
119
119
120
- To use ` ML_DSA() ` , it must be initialised with a dictionary of the
120
+ To use ` ML_DSA() ` , it must be initialised with a dictionary of the
121
121
protocol parameters. An example can be seen in ` DEFAULT_PARAMETERS ` in
122
122
the file [ ` ml_dsa.py ` ] ( src/dilithium_py/ml_dsa/default_parameters.py )
123
123
124
- Additionally, the class has been initialised with these default parameters,
124
+ Additionally, the class has been initialised with these default parameters,
125
125
so you can simply import the NIST level you want to play with:
126
126
127
127
#### Example
@@ -155,24 +155,24 @@ Some very rough benchmarks to give an idea about performance:
155
155
| ` Sign() ` Average Time | 36 ms | 62 ms | 75 ms |
156
156
| ` Verify() ` Median Time | 8 ms | 11 ms | 17 ms |
157
157
158
- All times recorded using a Intel Core i7-9750H CPU averaged over 1000 calls.
158
+ All times recorded using a Intel Core i7-9750H CPU averaged over 1000 calls.
159
159
160
160
### Dilithium
161
161
162
162
There are three functions exposed on the ` Dilithium ` class which are intended
163
163
for use:
164
164
165
165
- ` Dilithium.keygen() ` : generate a bit-packed keypair ` (pk, sk) `
166
- - ` Dilithium.sign(sk, msg) ` : generate a bit-packed signature ` sig `
166
+ - ` Dilithium.sign(sk, msg) ` : generate a bit-packed signature ` sig `
167
167
from the message ` msg ` and bit-packed secret key ` sk ` .
168
168
- ` Dilithium.verify(pk, msg, sig) ` : verify that the bit-packed ` sig ` is
169
169
valid for a given message ` msg ` and bit-packed public key ` pk ` .
170
170
171
- To use ` Dilithium() ` , it must be initialised with a dictionary of the
171
+ To use ` Dilithium() ` , it must be initialised with a dictionary of the
172
172
protocol parameters. An example can be seen in ` DEFAULT_PARAMETERS ` in
173
173
the file [ ` dilithium.py ` ] ( src/dilithium_py/dilithium/default_parameters.py )
174
174
175
- Additionally, the class has been initialised with these default parameters,
175
+ Additionally, the class has been initialised with these default parameters,
176
176
so you can simply import the NIST level you want to play with:
177
177
178
178
#### Example
@@ -288,11 +288,11 @@ def make_hint_optimised(z, r, a, q):
288
288
return 1
289
289
```
290
290
291
- In particular, when ` z = q-1 ` , ` make_hint() ` will return ` 1 ` , while the ` make_hint_optimised() ` returns ` 0 ` .
291
+ In particular, when ` z = q-1 ` , ` make_hint() ` will return ` 1 ` , while the ` make_hint_optimised() ` returns ` 0 ` .
292
292
293
293
As this optimisation is present in most implementations, this has caused a
294
294
confusion about whether ` make_hint() ` is correct as the output is different to
295
- ` make_hint_optimised() ` .
295
+ ` make_hint_optimised() ` .
296
296
297
297
It's important to realise the output of these make hints functions is different
298
298
with the same input vectors, but the hint vector will be identical in the cases that:
@@ -307,12 +307,12 @@ pass.)
307
307
308
308
### Polynomials
309
309
310
- The file [ ` polynomials.py ` ] ( src/dilithium_py/polynomials/polynomials_generic.py ) contains the classes
311
- ` PolynomialRing ` and
312
- ` Polynomial ` . This implements the univariate polynomial ring
310
+ The file [ ` polynomials.py ` ] ( src/dilithium_py/polynomials/polynomials_generic.py ) contains the classes
311
+ ` GenericPolynomialRing ` and
312
+ ` GenericPolynomial ` . This implements the univariate polynomial ring
313
313
314
314
$$
315
- R_q = \mathbb{F}_q[X] /(X^n + 1)
315
+ R_q = \mathbb{F}_q[X] /(X^n + 1)
316
316
$$
317
317
318
318
The implementation is inspired by ` SageMath ` and you can create the
@@ -321,7 +321,7 @@ ring $R_{11} = \mathbb{F}_{11}[X] /(X^8 + 1)$ in the following way:
321
321
#### Example
322
322
323
323
``` python
324
- >> > R = PolynomialRing (11 , 8 )
324
+ >> > R = GenericPolynomialRing (11 , 8 )
325
325
>> > x = R.gen()
326
326
>> > f = 3 * x** 3 + 4 * x** 7
327
327
>> > g = R.random_element(); g
@@ -336,25 +336,25 @@ ring $R_{11} = \mathbb{F}_{11}[X] /(X^8 + 1)$ in the following way:
336
336
337
337
### Modules
338
338
339
- The file [ ` modules.py ` ] ( src/dilithium_py/modules/modules_generic.py ) contains the classes ` Module ` and ` Matrix ` .
339
+ The file [ ` modules.py ` ] ( src/dilithium_py/modules/modules_generic.py ) contains the classes ` GenericModule ` and ` GenericMatrix ` .
340
340
A module is a generalisation of a vector space, where the field
341
- of scalars is replaced with a ring. In the case of Dilithium, we
342
- need the module with the ring $R_q$ as described above.
341
+ of scalars is replaced with a ring. In the case of Dilithium, we
342
+ need the module with the ring $R_q$ as described above.
343
343
344
- ` Matrix ` allows elements of the module to be of size $m \times n$
344
+ ` GenericMatrix ` allows elements of the module to be of size $m \times n$
345
345
For Dilithium, we need vectors of length $k$ and $l$ and a matrix
346
- of size $l \times k$.
346
+ of size $l \times k$.
347
347
348
- As an example of the operations we can perform with out ` Module `
348
+ As an example of the operations we can perform with out ` GenericModule `
349
349
lets revisit the ring from the previous example:
350
350
351
351
#### Example
352
352
353
353
``` python
354
- >> > R = PolynomialRing (11 , 8 )
354
+ >> > R = GenericPolynomialRing (11 , 8 )
355
355
>> > x = R.gen()
356
356
>> >
357
- >> > M = Module (R)
357
+ >> > M = GenericModule (R)
358
358
>> > # We create a matrix by feeding the coefficients to M
359
359
>> > A = M([[x + 3 * x** 2 , 4 + 3 * x** 7 ], [3 * x** 3 + 9 * x** 7 , x** 4 ]])
360
360
>> > A
@@ -441,7 +441,7 @@ These functions extend to modules
441
441
True
442
442
```
443
443
444
- As operations on the module are just operations between elements,
444
+ As operations on the module are just operations between elements,
445
445
we expect a similar 100x speed up when working in NTT form:
446
446
447
447
``` py
0 commit comments