mldsa 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mldsa/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """Pure-Python implementation of ML-DSA (FIPS 204) signature verification."""
2
+
3
+ from .mldsa import (
4
+ InvalidContextError,
5
+ InvalidVerificationKeyError,
6
+ ParameterSet,
7
+ VerificationError,
8
+ VerificationKey,
9
+ )
10
+
11
+ __all__ = [
12
+ "InvalidContextError",
13
+ "InvalidVerificationKeyError",
14
+ "ParameterSet",
15
+ "VerificationError",
16
+ "VerificationKey",
17
+ ]
mldsa/mldsa.py ADDED
@@ -0,0 +1,442 @@
1
+ # mldsa-py by Filippo Valsorda is marked CC0 1.0 Universal. To view a copy of
2
+ # this mark, visit https://creativecommons.org/publicdomain/zero/1.0/
3
+ #
4
+ # Alternatively, you may use this source code under the terms of the 0BSD
5
+ # license that can be found in the LICENSE file.
6
+
7
+ """Pure-Python implementation of ML-DSA (FIPS 204) signature verification."""
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from dataclasses import dataclass
13
+ from enum import Enum
14
+ from hashlib import shake_128, shake_256
15
+
16
+ if sys.version_info >= (3, 11):
17
+ from typing import Self
18
+ else:
19
+ from typing_extensions import Self
20
+
21
+ if sys.version_info >= (3, 12):
22
+ from collections.abc import Buffer
23
+ from typing import override
24
+ else:
25
+ from typing_extensions import Buffer, override
26
+
27
+ __all__ = [
28
+ "InvalidContextError",
29
+ "InvalidVerificationKeyError",
30
+ "ParameterSet",
31
+ "VerificationError",
32
+ "VerificationKey",
33
+ ]
34
+
35
+ Q = 8380417
36
+ N = 256
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class _Parameters:
41
+ name: str
42
+ verification_key_size: int
43
+ signature_size: int
44
+ k: int
45
+ l: int
46
+ η: int
47
+ γ1: int
48
+ γ2: int
49
+ λ: int
50
+ τ: int
51
+ ω: int
52
+
53
+
54
+ class ParameterSet(Enum):
55
+ """ML-DSA parameter sets as defined in FIPS 204."""
56
+
57
+ ML_DSA_44 = _Parameters(name="ML-DSA-44", verification_key_size=1312, signature_size=2420,
58
+ k=4, l=4, η=2, γ1=17, γ2=(Q - 1) // 88, λ=128, τ=39, ω=80) # fmt: skip
59
+ ML_DSA_65 = _Parameters(name="ML-DSA-65", verification_key_size=1952, signature_size=3309,
60
+ k=6, l=5, η=4, γ1=19, γ2=(Q - 1) // 32, λ=192, τ=49, ω=55) # fmt: skip
61
+ ML_DSA_87 = _Parameters(name="ML-DSA-87", verification_key_size=2592, signature_size=4627,
62
+ k=8, l=7, η=2, γ1=19, γ2=(Q - 1) // 32, λ=256, τ=60, ω=75) # fmt: skip
63
+
64
+ @property
65
+ def verification_key_size(self) -> int:
66
+ """The encoded verification key size in bytes."""
67
+ return self.value.verification_key_size
68
+
69
+ @property
70
+ def signature_size(self) -> int:
71
+ """The signature size in bytes."""
72
+ return self.value.signature_size
73
+
74
+ @override
75
+ def __str__(self) -> str:
76
+ """Return the human-readable parameter set name, e.g. ``ML-DSA-44``."""
77
+ return self.value.name
78
+
79
+ @override
80
+ def __repr__(self) -> str:
81
+ """Return a concise representation, e.g. ``<ParameterSet.ML_DSA_44>``."""
82
+ return f"<{type(self).__name__}.{self.name}>"
83
+
84
+
85
+ class VerificationError(Exception):
86
+ """Raised when signature verification fails."""
87
+
88
+
89
+ class InvalidVerificationKeyError(ValueError):
90
+ """Raised when a verification key is invalid."""
91
+
92
+
93
+ class InvalidContextError(ValueError):
94
+ """Raised when a context string is invalid."""
95
+
96
+
97
+ class VerificationKey:
98
+ """An ML-DSA verification key."""
99
+
100
+ _p: _Parameters
101
+
102
+ def __init__(self, pk: Buffer, /, *, parameters: ParameterSet | None = None) -> None:
103
+ """Decode an ML-DSA verification key.
104
+
105
+ If *parameters* is ``None``, the parameter set is inferred from
106
+ the length of *pk*.
107
+
108
+ Raises:
109
+ InvalidVerificationKeyError: If the key is the wrong size or doesn't
110
+ match the specified parameter set.
111
+ """
112
+ pk = memoryview(pk).cast("B")
113
+ if parameters is None:
114
+ size_to_params = {p.verification_key_size: p for p in ParameterSet}
115
+ if len(pk) not in size_to_params:
116
+ raise InvalidVerificationKeyError(f"unexpected verification key size {len(pk)}")
117
+ parameters = size_to_params[len(pk)]
118
+ self._p = parameters.value
119
+
120
+ if len(pk) != self._p.verification_key_size:
121
+ raise InvalidVerificationKeyError(
122
+ f"expected {self._p.verification_key_size} bytes, got {len(pk)}"
123
+ )
124
+ self._enc = bytes(pk)
125
+ self._tr = public_key_hash(pk)
126
+ ρ = bytes(pk[:32])
127
+ pkv = memoryview(pk[32:])
128
+
129
+ self._t1: list[NTTPoly] = [] # NTT(t₁ ⋅ 2ᵈ)
130
+ for _ in range(self._p.k):
131
+ self._t1.append(ntt(Poly([F(z.v << 13) for z in unpack(bytes(pkv[:320]), N, 10)])))
132
+ pkv = pkv[320:]
133
+
134
+ self._A: list[list[NTTPoly]] = [[] for _ in range(self._p.k)]
135
+ for r in range(self._p.k):
136
+ for s in range(self._p.l):
137
+ self._A[r].append(sample_ntt(ρ, s, r))
138
+
139
+ def __bytes__(self) -> bytes:
140
+ """Return the encoded verification key."""
141
+ return self._enc
142
+
143
+ @override
144
+ def __repr__(self) -> str:
145
+ """Return a concise representation, e.g. ``<VerificationKey ML-DSA-44>``."""
146
+ return f"<{type(self).__name__} {self._p.name}>"
147
+
148
+ @property
149
+ def parameters(self) -> ParameterSet:
150
+ """The parameter set of this key."""
151
+ return ParameterSet(self._p)
152
+
153
+ def verify(self, signature: Buffer, message: Buffer, *, context: Buffer = b"") -> None:
154
+ """Verify a signature over *message*.
155
+
156
+ Raises:
157
+ VerificationError: If the signature is invalid.
158
+ InvalidContextError: If the context is too long (more than 255 bytes).
159
+ """
160
+ signature = memoryview(signature).cast("B")
161
+ μ = message_hash(self._tr, message, context)
162
+
163
+ if len(signature) != self._p.signature_size:
164
+ raise VerificationError(
165
+ f"invalid signature size: expected {self._p.signature_size} bytes, got {len(signature)}"
166
+ )
167
+ ch = bytes(signature[: self._p.λ // 4])
168
+ sigv = memoryview(signature[self._p.λ // 4 :])
169
+ z: list[Poly] = []
170
+ for _ in range(self._p.l):
171
+ length = (self._p.γ1 + 1) * N // 8
172
+ z.append(Poly(unpack_signed(bytes(sigv[:length]), N, self._p.γ1 + 1)))
173
+ sigv = sigv[length:]
174
+ h: list[list[int]] = [[0] * N for _ in range(self._p.k)]
175
+ idx = 0
176
+ for i in range(self._p.k):
177
+ limit = sigv[self._p.ω + i]
178
+ if limit < idx or limit > self._p.ω:
179
+ raise VerificationError("invalid signature encoding")
180
+ first = idx
181
+ while idx < limit:
182
+ if idx > first and sigv[idx - 1] >= sigv[idx]:
183
+ raise VerificationError("invalid signature encoding")
184
+ h[i][sigv[idx]] = 1
185
+ idx += 1
186
+ for i in range(idx, self._p.ω):
187
+ if sigv[i] != 0:
188
+ raise VerificationError("invalid signature encoding")
189
+
190
+ c = ntt(sample_in_ball(ch, self._p))
191
+
192
+ z_hat = [ntt(x) for x in z]
193
+ w: list[Poly] = [] # Â ∘ NTT(z) − NTT(c) ∘ NTT(t₁ ⋅ 2ᵈ)
194
+ for i in range(self._p.k):
195
+ w_hat = NTTPoly.zero()
196
+ for j in range(self._p.l):
197
+ w_hat += z_hat[j] * self._A[i][j]
198
+ w_hat -= c * self._t1[i]
199
+ w.append(inverse_ntt(w_hat))
200
+
201
+ w1 = [use_hint(w[i], h[i], self._p) for i in range(self._p.k)]
202
+
203
+ H = shake_256()
204
+ H.update(μ)
205
+ w1_bit_length = ((Q - 1) // (2 * self._p.γ2) - 1).bit_length()
206
+ for i in range(self._p.k):
207
+ H.update(pack(w1[i], w1_bit_length))
208
+ if H.digest(self._p.λ // 4) != ch:
209
+ raise VerificationError("invalid signature")
210
+
211
+ β = self._p.τ * self._p.η
212
+ γ1 = 1 << self._p.γ1
213
+ γ1β = γ1 - β
214
+
215
+ for v in z:
216
+ if any(x.infinity_norm() >= γ1β for x in v.cs):
217
+ raise VerificationError("invalid signature")
218
+
219
+
220
+ def public_key_hash(pk: Buffer) -> bytes:
221
+ h = shake_256()
222
+ h.update(pk)
223
+ return h.digest(64)
224
+
225
+
226
+ def message_hash(tr: bytes, m: Buffer, ctx: Buffer) -> bytes:
227
+ ctx = memoryview(ctx).cast("B")
228
+ if len(ctx) > 255:
229
+ raise InvalidContextError(f"expected context of at most 255 bytes, got {len(ctx)}")
230
+ h = shake_256()
231
+ h.update(tr)
232
+ h.update(b"\x00")
233
+ h.update(bytes([len(ctx)]))
234
+ h.update(ctx)
235
+ h.update(m)
236
+ return h.digest(64)
237
+
238
+
239
+ class F:
240
+ __slots__ = ("v",)
241
+
242
+ def __init__(self, v: int) -> None:
243
+ assert 0 <= v < Q
244
+ self.v = v
245
+
246
+ @classmethod
247
+ def reduce(cls, v: int) -> F:
248
+ return cls(v % Q)
249
+
250
+ def __add__(self, other: F) -> F:
251
+ return F.reduce(self.v + other.v)
252
+
253
+ def __sub__(self, other: F) -> F:
254
+ return F.reduce(self.v - other.v)
255
+
256
+ def __mul__(self, other: F) -> F:
257
+ return F.reduce(self.v * other.v)
258
+
259
+ def infinity_norm(self) -> int:
260
+ return self.v if self.v <= Q // 2 else Q - self.v
261
+
262
+
263
+ def centered_mod(v: int, m: int) -> int:
264
+ r = v % m
265
+ if r > m // 2:
266
+ r -= m
267
+ return r
268
+
269
+
270
+ def decompose(r: F, p: _Parameters) -> tuple[int, int]:
271
+ r0 = centered_mod(r.v, 2 * p.γ2)
272
+ if r.v - r0 == Q - 1:
273
+ return 0, r0 - 1
274
+ r1 = (r.v - r0) // (2 * p.γ2)
275
+ return r1, r0
276
+
277
+
278
+ def unpack(buf: bytes, n: int, bit_length: int) -> list[F]:
279
+ assert n * bit_length == len(buf) * 8
280
+ res: list[F] = []
281
+ acc = 0
282
+ acc_len = 0
283
+ for b in buf:
284
+ acc |= b << acc_len
285
+ acc_len += 8
286
+ while acc_len >= bit_length:
287
+ res.append(F(acc & ((1 << bit_length) - 1)))
288
+ acc >>= bit_length
289
+ acc_len -= bit_length
290
+ return res
291
+
292
+
293
+ def unpack_signed(buf: bytes, n: int, bit_length: int) -> list[F]:
294
+ b = F(1 << (bit_length - 1))
295
+ return [b - x for x in unpack(buf, n, bit_length)]
296
+
297
+
298
+ def pack(cs: list[int], bit_length: int) -> bytes:
299
+ acc = 0
300
+ acc_len = 0
301
+ res = bytearray()
302
+ for c in cs:
303
+ acc |= c << acc_len
304
+ acc_len += bit_length
305
+ while acc_len >= 8:
306
+ res.append(acc & 0xFF)
307
+ acc >>= 8
308
+ acc_len -= 8
309
+ if acc_len > 0:
310
+ res.append(acc & 0xFF)
311
+ return bytes(res)
312
+
313
+
314
+ class Poly:
315
+ __slots__ = ("cs",)
316
+ cs: list[F]
317
+
318
+ def __init__(self, cs: list[F]) -> None:
319
+ assert len(cs) == N
320
+ self.cs = cs
321
+
322
+ def __add__(self, other: Self) -> Self:
323
+ if type(self) is not type(other):
324
+ return NotImplemented
325
+ return type(self)([a + b for a, b in zip(self.cs, other.cs)])
326
+
327
+ def __sub__(self, other: Self) -> Self:
328
+ if type(self) is not type(other):
329
+ return NotImplemented
330
+ return type(self)([a - b for a, b in zip(self.cs, other.cs)])
331
+
332
+
333
+ class NTTPoly:
334
+ __slots__ = ("cs",)
335
+ cs: list[int] # don't use F to avoid function call overhead in hot loops
336
+
337
+ def __init__(self, cs: list[int]) -> None:
338
+ assert len(cs) == N
339
+ self.cs = cs
340
+
341
+ @classmethod
342
+ def zero(cls) -> Self:
343
+ return cls([0 for _ in range(N)])
344
+
345
+ def __iadd__(self, other: Self) -> Self:
346
+ if type(self) is not type(other):
347
+ return NotImplemented
348
+ for i in range(N):
349
+ self.cs[i] = (self.cs[i] + other.cs[i]) % Q
350
+ return self
351
+
352
+ def __isub__(self, other: Self) -> Self:
353
+ if type(self) is not type(other):
354
+ return NotImplemented
355
+ for i in range(N):
356
+ self.cs[i] = (self.cs[i] - other.cs[i]) % Q
357
+ return self
358
+
359
+ def __mul__(self, other: NTTPoly) -> NTTPoly:
360
+ if type(self) is not type(other):
361
+ return NotImplemented
362
+ return NTTPoly([a * b % Q for a, b in zip(self.cs, other.cs)])
363
+
364
+
365
+ def ntt(f: Poly) -> NTTPoly:
366
+ m = 0
367
+ w = [c.v for c in f.cs]
368
+ for len in [128, 64, 32, 16, 8, 4, 2, 1]:
369
+ for start in range(0, N, 2 * len):
370
+ m += 1
371
+ zeta = ZETAS[m]
372
+ for j in range(start, start + len):
373
+ t = zeta * w[j + len] % Q
374
+ w[j + len] = (w[j] - t) % Q
375
+ w[j] = (w[j] + t) % Q
376
+ return NTTPoly(w)
377
+
378
+
379
+ def inverse_ntt(f: NTTPoly) -> Poly:
380
+ m = 255
381
+ w = [c for c in f.cs]
382
+ for len in [1, 2, 4, 8, 16, 32, 64, 128]:
383
+ for start in range(0, N, 2 * len):
384
+ zeta = ZETAS[m]
385
+ m -= 1
386
+ for j in range(start, start + len):
387
+ t = w[j]
388
+ w[j] = (t + w[j + len]) % Q
389
+ w[j + len] = zeta * (w[j + len] - t) % Q
390
+ return Poly([F(v * 8347681 % Q) for v in w])
391
+
392
+
393
+ def sample_ntt(ρ: bytes, s: int, r: int) -> NTTPoly:
394
+ G = shake_128()
395
+ G.update(ρ)
396
+ G.update(bytes([s, r]))
397
+ buf = G.digest(894)
398
+
399
+ a: list[int] = []
400
+ while len(a) < N:
401
+ v = int.from_bytes(buf[:3], "little") & 0x7FFFFF
402
+ buf = buf[3:]
403
+ if v < Q:
404
+ a.append(v)
405
+ return NTTPoly(a)
406
+
407
+
408
+ def sample_in_ball(rho: bytes, p: _Parameters) -> Poly:
409
+ G = shake_256()
410
+ G.update(rho)
411
+ buf = G.digest(221)
412
+ s = buf[:8]
413
+ j = memoryview(buf)[8:]
414
+
415
+ c = [F(0) for _ in range(N)]
416
+ for i in range(256 - p.τ, 256):
417
+ while j[0] > i:
418
+ j = j[1:]
419
+ c[i] = c[j[0]]
420
+ bit_idx = i + p.τ - 256
421
+ bit = (s[bit_idx // 8] >> (bit_idx % 8)) & 1
422
+ c[j[0]] = F(1) if bit == 0 else F(Q - 1)
423
+ j = j[1:]
424
+
425
+ return Poly(c)
426
+
427
+
428
+ def use_hint(w: Poly, h: list[int], p: _Parameters) -> list[int]:
429
+ m = (Q - 1) // (2 * p.γ2)
430
+ w1: list[int] = []
431
+ for i in range(N):
432
+ r1, r0 = decompose(w.cs[i], p)
433
+ if h[i] == 0:
434
+ w1.append(r1)
435
+ elif r0 > 0:
436
+ w1.append((r1 + 1) % m)
437
+ else:
438
+ w1.append((r1 - 1) % m)
439
+ return w1
440
+
441
+
442
+ ZETAS = [1, 4808194, 3765607, 3761513, 5178923, 5496691, 5234739, 5178987, 7778734, 3542485, 2682288, 2129892, 3764867, 7375178, 557458, 7159240, 5010068, 4317364, 2663378, 6705802, 4855975, 7946292, 676590, 7044481, 5152541, 1714295, 2453983, 1460718, 7737789, 4795319, 2815639, 2283733, 3602218, 3182878, 2740543, 4793971, 5269599, 2101410, 3704823, 1159875, 394148, 928749, 1095468, 4874037, 2071829, 4361428, 3241972, 2156050, 3415069, 1759347, 7562881, 4805951, 3756790, 6444618, 6663429, 4430364, 5483103, 3192354, 556856, 3870317, 2917338, 1853806, 3345963, 1858416, 3073009, 1277625, 5744944, 3852015, 4183372, 5157610, 5258977, 8106357, 2508980, 2028118, 1937570, 4564692, 2811291, 5396636, 7270901, 4158088, 1528066, 482649, 1148858, 5418153, 7814814, 169688, 2462444, 5046034, 4213992, 4892034, 1987814, 5183169, 1736313, 235407, 5130263, 3258457, 5801164, 1787943, 5989328, 6125690, 3482206, 4197502, 7080401, 6018354, 7062739, 2461387, 3035980, 621164, 3901472, 7153756, 2925816, 3374250, 1356448, 5604662, 2683270, 5601629, 4912752, 2312838, 7727142, 7921254, 348812, 8052569, 1011223, 6026202, 4561790, 6458164, 6143691, 1744507, 1753, 6444997, 5720892, 6924527, 2660408, 6600190, 8321269, 2772600, 1182243, 87208, 636927, 4415111, 4423672, 6084020, 5095502, 4663471, 8352605, 822541, 1009365, 5926272, 6400920, 1596822, 4423473, 4620952, 6695264, 4969849, 2678278, 4611469, 4829411, 635956, 8129971, 5925040, 4234153, 6607829, 2192938, 6653329, 2387513, 4768667, 8111961, 5199961, 3747250, 2296099, 1239911, 4541938, 3195676, 2642980, 1254190, 8368000, 2998219, 141835, 8291116, 2513018, 7025525, 613238, 7070156, 6161950, 7921677, 6458423, 4040196, 4908348, 2039144, 6500539, 7561656, 6201452, 6757063, 2105286, 6006015, 6346610, 586241, 7200804, 527981, 5637006, 6903432, 1994046, 2491325, 6987258, 507927, 7192532, 7655613, 6545891, 5346675, 8041997, 2647994, 3009748, 5767564, 4148469, 749577, 4357667, 3980599, 2569011, 6764887, 1723229, 1665318, 2028038, 1163598, 5011144, 3994671, 8368538, 7009900, 3020393, 3363542, 214880, 545376, 7609976, 3105558, 7277073, 508145, 7826699, 860144, 3430436, 140244, 6866265, 6195333, 3123762, 2358373, 6187330, 5365997, 6663603, 2926054, 7987710, 8077412, 3531229, 4405932, 4606686, 1900052, 7598542, 1054478, 7648983] # fmt: skip # ruff: ignore[line-too-long]
mldsa/py.typed ADDED
File without changes
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: mldsa
3
+ Version: 1.0.0
4
+ Summary: ML-DSA (FIPS 204) post-quantum signature verification (pure Python, no dependencies)
5
+ Author: Filippo Valsorda
6
+ License-Expression: CC0-1.0 OR 0BSD
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Security :: Cryptography
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: typing-extensions>=4.6 ; python_full_version < '3.12'
23
+ Requires-Python: >=3.8
24
+ Project-URL: Homepage, https://github.com/FiloSottile/mldsa-py
25
+ Project-URL: Source, https://github.com/FiloSottile/mldsa-py
26
+ Project-URL: Issues, https://github.com/FiloSottile/mldsa-py/issues
27
+ Description-Content-Type: text/markdown
28
+
29
+ # mldsa-py
30
+
31
+ ```
32
+ pip install mldsa
33
+ ```
34
+
35
+ This is a pure-Python production implementation of ML-DSA (FIPS 204)
36
+ post-quantum signature verification.
37
+
38
+ It does not provide key or signature generation, because secrets can't be
39
+ handled in constant-time in Python.
40
+
41
+ ```python
42
+ from mldsa import VerificationKey, VerificationError
43
+
44
+ vk = VerificationKey(verification_key_bytes)
45
+
46
+ try:
47
+ vk.verify(signature, message)
48
+ except VerificationError:
49
+ print("invalid signature!")
50
+ ```
51
+
52
+ The parameter set (ML-DSA-44, ML-DSA-65, or ML-DSA-87) is inferred from the
53
+ verification key size, or it can be specified explicitly.
54
+
55
+ ```python
56
+ from mldsa import ParameterSet, VerificationKey
57
+
58
+ vk = VerificationKey(verification_key_bytes, parameters=ParameterSet.ML_DSA_87)
59
+ vk.verify(signature, message, context=b"example.com/foo token")
60
+ ```
61
+
62
+ The non-test code is [a single-file module](https://github.com/FiloSottile/mldsa-py/blob/main/src/mldsa/mldsa.py)
63
+ of less than 500 lines, with no dependencies.
64
+
65
+ It works with Python 3.8 and later.
66
+
67
+ ## Development
68
+
69
+ To run tests, use
70
+
71
+ ```bash
72
+ uv run ruff check
73
+ uv run ruff format --check
74
+ uv run ty check
75
+ uv run pytest
76
+ go install github.com/FiloSottile/mostly-harmless/muzoo@latest
77
+ muzoo -mutations tests/testdata/mutations test -- uv run pytest -x
78
+ ```
79
+
80
+ This project uses tests from [Wycheproof](https://github.com/C2SP/wycheproof).
81
+
82
+ ## License
83
+
84
+ This work is marked CC0 1.0 Universal. To view a copy of this mark, visit
85
+ [creativecommons.org](https://creativecommons.org/publicdomain/zero/1.0/).
86
+
87
+ Alternatively, you may use this source code under the terms of the 0BSD license
88
+ that can be found in the LICENSE file.
89
+ In short, you can do whatever you want with this code.
@@ -0,0 +1,7 @@
1
+ mldsa/__init__.py,sha256=NaKLnkcMufb6w4j-jIBVjBi-X8mzb-ZcVslFRmj26Jc,367
2
+ mldsa/mldsa.py,sha256=PjX8JWhLUwVuenfN3yvXHvyuJFjP_qw5mhG54q8Xkp8,15401
3
+ mldsa/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ mldsa-1.0.0.dist-info/licenses/LICENSE,sha256=dLNEpDKIKDyniWs42Fm9a5EDURC7nGmFavvGCgH_ZeQ,611
5
+ mldsa-1.0.0.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
6
+ mldsa-1.0.0.dist-info/METADATA,sha256=eNw0EXHXSuKhSYGOfl25zqgytFAyy757DpHXx3BlyDg,2906
7
+ mldsa-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.30
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,10 @@
1
+ Permission to use, copy, modify, and/or distribute this software for
2
+ any purpose with or without fee is hereby granted.
3
+
4
+ THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
5
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
6
+ OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
7
+ FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
8
+ DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
9
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
10
+ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.