fips-collection 1.0.0__tar.gz
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.
- fips_collection-1.0.0/LICENSE +21 -0
- fips_collection-1.0.0/PKG-INFO +114 -0
- fips_collection-1.0.0/README.md +96 -0
- fips_collection-1.0.0/pyproject.toml +46 -0
- fips_collection-1.0.0/setup.cfg +4 -0
- fips_collection-1.0.0/src/fips/FIPS204/__init__.py +10 -0
- fips_collection-1.0.0/src/fips/FIPS204/auxilary.py +369 -0
- fips_collection-1.0.0/src/fips/FIPS204/encode.py +300 -0
- fips_collection-1.0.0/src/fips/FIPS204/hash.py +39 -0
- fips_collection-1.0.0/src/fips/FIPS204/hint.py +200 -0
- fips_collection-1.0.0/src/fips/FIPS204/main.py +439 -0
- fips_collection-1.0.0/src/fips/FIPS204/ntt.py +278 -0
- fips_collection-1.0.0/src/fips/FIPS204/pack.py +276 -0
- fips_collection-1.0.0/src/fips/FIPS204/parameter.py +50 -0
- fips_collection-1.0.0/src/fips/FIPS204/sample.py +291 -0
- fips_collection-1.0.0/src/fips/__init__.py +9 -0
- fips_collection-1.0.0/src/fips_collection.egg-info/PKG-INFO +114 -0
- fips_collection-1.0.0/src/fips_collection.egg-info/SOURCES.txt +19 -0
- fips_collection-1.0.0/src/fips_collection.egg-info/dependency_links.txt +1 -0
- fips_collection-1.0.0/src/fips_collection.egg-info/top_level.txt +1 -0
- fips_collection-1.0.0/tests/test_FIPS204.py +417 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Pratap Singh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fips-collection
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Educational implementations of NIST FIPS cryptographic standard.
|
|
5
|
+
Author: Aditya Pratap Singh
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kyuuaditya/fips-collection-python
|
|
8
|
+
Project-URL: Documentation, https://kyuuaditya.github.io/fips-collection-python/
|
|
9
|
+
Project-URL: Repository, https://github.com/kyuuaditya/fips-collection-python
|
|
10
|
+
Keywords: cryptography,fips,nist,ml-dsa,mldsa,post-quantum,pqc
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Security :: Cryptography
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
[](https://github.com/kyuuaditya/fips-collection-python/blob/main/LICENSE)
|
|
20
|
+
|
|
21
|
+
# FIPS Standards Implementations
|
|
22
|
+
|
|
23
|
+
> [!CAUTION]
|
|
24
|
+
> :warning: **Under no circumstances should this be used for cryptographic
|
|
25
|
+
applications.** :warning:
|
|
26
|
+
>
|
|
27
|
+
> This is an educational resource and has not been designed to be secure against any form of side-channel attack. The intended use of this project is for learning and experimenting with FIPS Algorithms.
|
|
28
|
+
|
|
29
|
+
This repository contains a **python** implementation of:
|
|
30
|
+
|
|
31
|
+
1. **ML-DSA** the NIST Module-Lattice-Based Digital Signature Standard following
|
|
32
|
+
the [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final).
|
|
33
|
+
|
|
34
|
+
## Licenses
|
|
35
|
+
|
|
36
|
+
This project is licensed under the [License MIT](https://github.com/kyuuaditya/fips-collection-python/blob/main/LICENSE).
|
|
37
|
+
|
|
38
|
+
## Disclaimer
|
|
39
|
+
|
|
40
|
+
This implementation follows instructions given in FIPS 204 as it is for all algorithms.
|
|
41
|
+
|
|
42
|
+
With sole exception of Algorithm 43 BitRev as zeta values are precomputed and hardcoded.
|
|
43
|
+
|
|
44
|
+
## Documentation
|
|
45
|
+
|
|
46
|
+
Complete documentation of this repository is available at https://kyuuaditya.github.io/fips-collection-python/
|
|
47
|
+
|
|
48
|
+
## Using fips
|
|
49
|
+
|
|
50
|
+
### FIPS 204 - MLDSA
|
|
51
|
+
|
|
52
|
+
The `MLDSA` class contains 3 main functions:
|
|
53
|
+
|
|
54
|
+
- `MLDSAKeyGen()`: generates a public-private keypair `(public_key, secret_key)`
|
|
55
|
+
- `MLDSASign(secret_key, Message, ctx)`: generates an MLDSA signature `sigature`
|
|
56
|
+
from the message `message` and bit-packed secret key `secret_key`.
|
|
57
|
+
- `MLDSAVerify(public_key, message, signature)`: verifies a `signature` rho for a `message` M.
|
|
58
|
+
|
|
59
|
+
To use `FIPS 204 - MLDSA` simply import as follows:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from fips import MLDSA_44, MLDSA_65, MLDSA_87
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Example Use Case
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from fips import MLDSA_44
|
|
69
|
+
|
|
70
|
+
mldsa128 = MLDSA_44
|
|
71
|
+
|
|
72
|
+
# Generate the public-private key pair.
|
|
73
|
+
public_key, secret_key = mldsa128.MLDSAKeyGen()
|
|
74
|
+
|
|
75
|
+
context = b'ab'
|
|
76
|
+
message = "du bis gut"
|
|
77
|
+
message_spoof = "you are good"
|
|
78
|
+
|
|
79
|
+
# Generate the signature.
|
|
80
|
+
signature = mldsa128.MLDSASign(secret_key, mldsa128.auxilary.BytesToBits(message.encode()), context)
|
|
81
|
+
|
|
82
|
+
# Verification will only pass with the correct corresponding public key.
|
|
83
|
+
assert mldsa128.MLDSAVerify(public_key, mldsa128.auxilary.BytesToBits(message.encode()), signature, context)
|
|
84
|
+
|
|
85
|
+
# Verification will fail with an altered message.
|
|
86
|
+
assert not MLDSA_44.MLDSAVerify(public_key, MLDSA_44.auxilary.BytesToBits(message_spoof.encode()), signature, context)
|
|
87
|
+
|
|
88
|
+
# Verification will fail with any other public key.
|
|
89
|
+
public_key_new, secret_key_new = MLDSA_44.MLDSAKeyGen()
|
|
90
|
+
assert not MLDSA_44.MLDSAVerify(public_key_new, MLDSA_44.auxilary.BytesToBits(message.encode()), signature, context)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The above example would also work with the other NIST levels
|
|
94
|
+
`MLDSA_65` and `MLDSA_87`.
|
|
95
|
+
|
|
96
|
+
#### Hash ML-DSA
|
|
97
|
+
|
|
98
|
+
Algorithm 4 and 5 of FIPS 204 are not yet added to this implementation.
|
|
99
|
+
|
|
100
|
+
## Benchmarks
|
|
101
|
+
|
|
102
|
+
`FIPS 204 - MLDSA` Performance:
|
|
103
|
+
|
|
104
|
+
| | `MLDSA_44` | `MLDSA_65` | `MLDSA_87` |
|
|
105
|
+
|--------------------------|------------|------------|------------|
|
|
106
|
+
| `KeyGen()` Average Time | 8.4 ms | 14.1 ms | 22.3 ms |
|
|
107
|
+
| `Sign()` Average Time | 78.0 ms | 127.1 ms | 144.3 ms |
|
|
108
|
+
| `Verify()` Average Time | 20.9 ms | 29.9 ms | 43.6 ms |
|
|
109
|
+
|
|
110
|
+
Data recorded using a Ryzen 7 4800H CPU averaged over 1000 calls.
|
|
111
|
+
|
|
112
|
+
## Colaborations
|
|
113
|
+
|
|
114
|
+
Feel free to modify and share improvements.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[](https://github.com/kyuuaditya/fips-collection-python/blob/main/LICENSE)
|
|
2
|
+
|
|
3
|
+
# FIPS Standards Implementations
|
|
4
|
+
|
|
5
|
+
> [!CAUTION]
|
|
6
|
+
> :warning: **Under no circumstances should this be used for cryptographic
|
|
7
|
+
applications.** :warning:
|
|
8
|
+
>
|
|
9
|
+
> This is an educational resource and has not been designed to be secure against any form of side-channel attack. The intended use of this project is for learning and experimenting with FIPS Algorithms.
|
|
10
|
+
|
|
11
|
+
This repository contains a **python** implementation of:
|
|
12
|
+
|
|
13
|
+
1. **ML-DSA** the NIST Module-Lattice-Based Digital Signature Standard following
|
|
14
|
+
the [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final).
|
|
15
|
+
|
|
16
|
+
## Licenses
|
|
17
|
+
|
|
18
|
+
This project is licensed under the [License MIT](https://github.com/kyuuaditya/fips-collection-python/blob/main/LICENSE).
|
|
19
|
+
|
|
20
|
+
## Disclaimer
|
|
21
|
+
|
|
22
|
+
This implementation follows instructions given in FIPS 204 as it is for all algorithms.
|
|
23
|
+
|
|
24
|
+
With sole exception of Algorithm 43 BitRev as zeta values are precomputed and hardcoded.
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
Complete documentation of this repository is available at https://kyuuaditya.github.io/fips-collection-python/
|
|
29
|
+
|
|
30
|
+
## Using fips
|
|
31
|
+
|
|
32
|
+
### FIPS 204 - MLDSA
|
|
33
|
+
|
|
34
|
+
The `MLDSA` class contains 3 main functions:
|
|
35
|
+
|
|
36
|
+
- `MLDSAKeyGen()`: generates a public-private keypair `(public_key, secret_key)`
|
|
37
|
+
- `MLDSASign(secret_key, Message, ctx)`: generates an MLDSA signature `sigature`
|
|
38
|
+
from the message `message` and bit-packed secret key `secret_key`.
|
|
39
|
+
- `MLDSAVerify(public_key, message, signature)`: verifies a `signature` rho for a `message` M.
|
|
40
|
+
|
|
41
|
+
To use `FIPS 204 - MLDSA` simply import as follows:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from fips import MLDSA_44, MLDSA_65, MLDSA_87
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Example Use Case
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from fips import MLDSA_44
|
|
51
|
+
|
|
52
|
+
mldsa128 = MLDSA_44
|
|
53
|
+
|
|
54
|
+
# Generate the public-private key pair.
|
|
55
|
+
public_key, secret_key = mldsa128.MLDSAKeyGen()
|
|
56
|
+
|
|
57
|
+
context = b'ab'
|
|
58
|
+
message = "du bis gut"
|
|
59
|
+
message_spoof = "you are good"
|
|
60
|
+
|
|
61
|
+
# Generate the signature.
|
|
62
|
+
signature = mldsa128.MLDSASign(secret_key, mldsa128.auxilary.BytesToBits(message.encode()), context)
|
|
63
|
+
|
|
64
|
+
# Verification will only pass with the correct corresponding public key.
|
|
65
|
+
assert mldsa128.MLDSAVerify(public_key, mldsa128.auxilary.BytesToBits(message.encode()), signature, context)
|
|
66
|
+
|
|
67
|
+
# Verification will fail with an altered message.
|
|
68
|
+
assert not MLDSA_44.MLDSAVerify(public_key, MLDSA_44.auxilary.BytesToBits(message_spoof.encode()), signature, context)
|
|
69
|
+
|
|
70
|
+
# Verification will fail with any other public key.
|
|
71
|
+
public_key_new, secret_key_new = MLDSA_44.MLDSAKeyGen()
|
|
72
|
+
assert not MLDSA_44.MLDSAVerify(public_key_new, MLDSA_44.auxilary.BytesToBits(message.encode()), signature, context)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The above example would also work with the other NIST levels
|
|
76
|
+
`MLDSA_65` and `MLDSA_87`.
|
|
77
|
+
|
|
78
|
+
#### Hash ML-DSA
|
|
79
|
+
|
|
80
|
+
Algorithm 4 and 5 of FIPS 204 are not yet added to this implementation.
|
|
81
|
+
|
|
82
|
+
## Benchmarks
|
|
83
|
+
|
|
84
|
+
`FIPS 204 - MLDSA` Performance:
|
|
85
|
+
|
|
86
|
+
| | `MLDSA_44` | `MLDSA_65` | `MLDSA_87` |
|
|
87
|
+
|--------------------------|------------|------------|------------|
|
|
88
|
+
| `KeyGen()` Average Time | 8.4 ms | 14.1 ms | 22.3 ms |
|
|
89
|
+
| `Sign()` Average Time | 78.0 ms | 127.1 ms | 144.3 ms |
|
|
90
|
+
| `Verify()` Average Time | 20.9 ms | 29.9 ms | 43.6 ms |
|
|
91
|
+
|
|
92
|
+
Data recorded using a Ryzen 7 4800H CPU averaged over 1000 calls.
|
|
93
|
+
|
|
94
|
+
## Colaborations
|
|
95
|
+
|
|
96
|
+
Feel free to modify and share improvements.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fips-collection"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Educational implementations of NIST FIPS cryptographic standard."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
|
|
15
|
+
authors = [
|
|
16
|
+
{name = "Aditya Pratap Singh"}
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
keywords = [
|
|
20
|
+
"cryptography",
|
|
21
|
+
"fips",
|
|
22
|
+
"nist",
|
|
23
|
+
"ml-dsa",
|
|
24
|
+
"mldsa",
|
|
25
|
+
"post-quantum",
|
|
26
|
+
"pqc"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Topic :: Security :: Cryptography",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
dependencies = []
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/kyuuaditya/fips-collection-python"
|
|
39
|
+
Documentation = "https://kyuuaditya.github.io/fips-collection-python/"
|
|
40
|
+
Repository = "https://github.com/kyuuaditya/fips-collection-python"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
package-dir = {"" = "src"}
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
class AUXILARY:
|
|
2
|
+
"""
|
|
3
|
+
This class provides subroutines utilized by MLDSA, including function for data-type converstions and arithmetic.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
def __init__(self, parameter: dict[str, int]):
|
|
7
|
+
self.q = parameter["q"]
|
|
8
|
+
self.N = parameter["N"]
|
|
9
|
+
self.eta = parameter["eta"]
|
|
10
|
+
|
|
11
|
+
def IntegerToBits(self, x: int, alpha: int):
|
|
12
|
+
"""
|
|
13
|
+
Algorithm 9
|
|
14
|
+
|
|
15
|
+
Computes the base-2 representation of x mod 2^alpha in ``little-endian`` order.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
x (``int``): A ``non-negative`` integer.
|
|
19
|
+
alpha (``int``): Number of ``bits`` to represent.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
str (``bits``): ``Bitstring`` of length ``alpha`` in ``little-endian`` order.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ValueError: If x is ``negative`` or alpha is not a positive integer.
|
|
26
|
+
ValueError: If x is ``too big`` to be represented in alpha bits.
|
|
27
|
+
TypeError: If x or alpha is ``not an integer``.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
if x < 0:
|
|
31
|
+
raise ValueError("x must be non-negative.")
|
|
32
|
+
if alpha <= 0:
|
|
33
|
+
raise ValueError("alpha must be a positive integer.")
|
|
34
|
+
if x >= 2 ** alpha:
|
|
35
|
+
raise ValueError(f"x = {x} cannot be represented in {alpha} bits.")
|
|
36
|
+
|
|
37
|
+
x_mod = x
|
|
38
|
+
bits:list[str] = []
|
|
39
|
+
for _ in range(alpha):
|
|
40
|
+
bits.append(str(x_mod % 2))
|
|
41
|
+
x_mod //= 2
|
|
42
|
+
|
|
43
|
+
return ''.join(bits)
|
|
44
|
+
|
|
45
|
+
def BitsToInteger(self, y: str, alpha: int) -> int:
|
|
46
|
+
"""
|
|
47
|
+
Algorithm 10
|
|
48
|
+
|
|
49
|
+
Computes the ``integer`` value expressed by a bit string using ``little-endian`` order.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
y (``bitstring``): ``Bitstring`` to convert to an integer.
|
|
53
|
+
alpha (``int``): Number of ``bits`` to consider.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
integer (``int``): The ``integer`` value represented by the ``bitstring``.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If the length of ``y`` does not match alpha or if ``y`` contains invalid characters.
|
|
60
|
+
TypeError: If ``y`` is not a ``string`` or alpha is not an ``integer``.
|
|
61
|
+
"""
|
|
62
|
+
if alpha <= 0:
|
|
63
|
+
raise ValueError("alpha must be a positive integer.")
|
|
64
|
+
if len(y) != alpha:
|
|
65
|
+
raise ValueError(f"Bit string y must have exactly {alpha} bits.")
|
|
66
|
+
if any(bit not in "01" for bit in y):
|
|
67
|
+
raise ValueError("Bit string y must contain only '0' and '1' characters.")
|
|
68
|
+
|
|
69
|
+
x = 0
|
|
70
|
+
for i in range(1, alpha + 1):
|
|
71
|
+
bit = int(y[alpha - i])
|
|
72
|
+
x = 2 * x + bit
|
|
73
|
+
return x
|
|
74
|
+
|
|
75
|
+
def IntegerToBytes(self, x: int, alpha: int) -> bytes:
|
|
76
|
+
"""
|
|
77
|
+
Algorithm 11
|
|
78
|
+
|
|
79
|
+
Computes a base-256 representation of x mod 256^alpha in ``little-endian`` order.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
x (``int``): A ``non-negative`` integer.
|
|
83
|
+
alpha (``int``): Number of ``bytes`` in the output.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
bytes (``bytes``): ``Bytestring`` of length alpha in ``little-endian`` order.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: x = _ cannot be represented in _ bytes.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
return x.to_bytes(alpha, byteorder = "little")
|
|
93
|
+
except OverflowError:
|
|
94
|
+
raise ValueError(f"x = {x} cannot be represented in {alpha} bytes.")
|
|
95
|
+
|
|
96
|
+
def BitsToBytes(self, y: str) -> bytes:
|
|
97
|
+
"""
|
|
98
|
+
Algorithm 12
|
|
99
|
+
|
|
100
|
+
Converts a ``bitstring`` y into a ``bytestring`` using ``little-endian`` order.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
y (``str``): ``Bitstring`` consisting of ``0`` and ``1``.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
bytes (``bytes``): ``Bytestring`` of length ceil ``(len(y) / 8)``.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
TypeError: If y is ``not a string``.
|
|
110
|
+
ValueError: If y contains characters other than ``0`` or ``1``.
|
|
111
|
+
"""
|
|
112
|
+
if any(bit not in "01" for bit in y):
|
|
113
|
+
raise ValueError("Bit string y must contain only '0' and '1'.")
|
|
114
|
+
|
|
115
|
+
alpha = len(y)
|
|
116
|
+
byte_len = (alpha + 7) // 8 # Equivalent to ceil(alpha / 8)
|
|
117
|
+
z = [0] * byte_len
|
|
118
|
+
|
|
119
|
+
for i in range(alpha):
|
|
120
|
+
byte_index = i // 8
|
|
121
|
+
bit_index = i % 8
|
|
122
|
+
z[byte_index] |= int(y[i]) << bit_index
|
|
123
|
+
|
|
124
|
+
return bytes(z)
|
|
125
|
+
|
|
126
|
+
def BytesToBits(self, z: bytes) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Algorithm 13
|
|
129
|
+
|
|
130
|
+
Converts a ``bytestring`` ``z`` into a ``bitstring`` in ``little-endian`` order.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
z (``bytes``): A ``bytestring``.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str (bit string): A ``bitstring`` of length ``8 * len(z)``, in ``little-endian`` order.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
TypeError: If ``z`` is not a ``bytes`` object.
|
|
140
|
+
"""
|
|
141
|
+
if not isinstance(z, (bytes, bytearray)):
|
|
142
|
+
raise TypeError("Input z must be a bytes or bytearray object.")
|
|
143
|
+
|
|
144
|
+
bits: list[str] = []
|
|
145
|
+
for byte in z:
|
|
146
|
+
for i in range(8):
|
|
147
|
+
bits.append(str((byte >> i) & 1)) # Little-endian bit order
|
|
148
|
+
|
|
149
|
+
return ''.join(bits)
|
|
150
|
+
|
|
151
|
+
def CoeffFromThreeBytes(self, b0: int, b1: int, b2:int) -> int | None:
|
|
152
|
+
"""
|
|
153
|
+
Algorithm 14
|
|
154
|
+
|
|
155
|
+
Generates an element of {``0``, ``1``, ``2``, ... , ``q - 1``} U { ``None`` }
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
b0 (``int``): first byte
|
|
159
|
+
b1 (``int``): second byte
|
|
160
|
+
b2 (``int``): third byte
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
z (``int``): sampled coefficient or ``None`` if rejected.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
TypeError: if any of ``b0``, ``b1``, ``b2`` is ``not an integer``.
|
|
167
|
+
"""
|
|
168
|
+
# checks for validity of inputs.
|
|
169
|
+
for i, b in enumerate((b0, b1, b2), start = 0):
|
|
170
|
+
if not (0 <= b <= 255):
|
|
171
|
+
raise ValueError (f"b{i} must be in the range 0 - 255.")
|
|
172
|
+
|
|
173
|
+
# line 1: make a copy of b2.
|
|
174
|
+
b2_prime = b2
|
|
175
|
+
|
|
176
|
+
# line 2 to 4: making sure b2_prime is 7 bits, not 8.
|
|
177
|
+
if b2_prime > 127:
|
|
178
|
+
b2_prime = b2_prime - 128
|
|
179
|
+
|
|
180
|
+
# line 5: evaluate z for sampling.
|
|
181
|
+
z = (b2_prime << 16) + (b1 << 8) + b0
|
|
182
|
+
|
|
183
|
+
# line 6 to 8: reject the sample z if it's greater than q.
|
|
184
|
+
if z < self.q:
|
|
185
|
+
return z # accept sample
|
|
186
|
+
else:
|
|
187
|
+
return None # reject sample
|
|
188
|
+
|
|
189
|
+
def CoeffFromHalfByte(self, b: int) -> int | None:
|
|
190
|
+
"""
|
|
191
|
+
Algorithm 15
|
|
192
|
+
|
|
193
|
+
Let ``eta`` ∈ {2, 4}.
|
|
194
|
+
|
|
195
|
+
Generates an element of {``-eta``, ``-eta + 1``, ... , ``eta``} U { ``None`` }
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
b (``int``): an integer in the range ``0 - 15``.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
z (``int``): sampled coefficient or ``None`` if rejected.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
TypeError: if b is ``not an integer``.
|
|
205
|
+
ValueError: if b is ``not`` in the range ``0 - 15``.
|
|
206
|
+
"""
|
|
207
|
+
if not (0 <= b <= 15):
|
|
208
|
+
raise ValueError (f"{b} must be in the range 0 - 15.")
|
|
209
|
+
|
|
210
|
+
# line 1 and 2: rejection sampline from {-2, ... , 2 }
|
|
211
|
+
if self.eta == 2 and b < 15:
|
|
212
|
+
return 2 - (b % 5)
|
|
213
|
+
|
|
214
|
+
# line 3: rejection sampline from {-4, ... , 4 }
|
|
215
|
+
elif self.eta == 4 and b < 9:
|
|
216
|
+
return 4 - b
|
|
217
|
+
|
|
218
|
+
# line 4: sample is just rejected.
|
|
219
|
+
else:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def CenteredModulus(self, z: int) -> int:
|
|
223
|
+
"""
|
|
224
|
+
Additional Helper Function 1
|
|
225
|
+
|
|
226
|
+
Computes the centered modulus ``z mod± q``.
|
|
227
|
+
|
|
228
|
+
Maps each integer ``x`` to the unique ``r`` in ::
|
|
229
|
+
|
|
230
|
+
[-(q-1)/2, (q-1)/2]
|
|
231
|
+
|
|
232
|
+
such that ::
|
|
233
|
+
|
|
234
|
+
x ≡ r (mod q).
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
z(``int``): An integer
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
CenteredModulus(``int``)
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
TypeError: If ``z`` is ``not an integer``.
|
|
244
|
+
"""
|
|
245
|
+
half_q = (self.q - 1) // 2
|
|
246
|
+
|
|
247
|
+
return (z + half_q) % self.q - half_q
|
|
248
|
+
|
|
249
|
+
def CenteredModulusList(self, z: list[int]) -> list[int]:
|
|
250
|
+
"""
|
|
251
|
+
Additional Helper Function 2
|
|
252
|
+
|
|
253
|
+
Computes the centered modulus ``z mod± q`` for a ``list`` of ``Integers``.
|
|
254
|
+
|
|
255
|
+
Maps each integer ``x`` to the unique ``r`` in ::
|
|
256
|
+
|
|
257
|
+
[-(q-1)/2, (q-1)/2]
|
|
258
|
+
|
|
259
|
+
such that ::
|
|
260
|
+
|
|
261
|
+
x ≡ r (mod q).
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
z(``list[int]``): A list of ``integers``.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
CenteredModulus(``list[int]``)
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
TypeError: If ``z`` is not a ``list[int]``.
|
|
271
|
+
"""
|
|
272
|
+
half_q = (self.q - 1) // 2
|
|
273
|
+
|
|
274
|
+
return [(x + half_q) % self.q - half_q for x in z]
|
|
275
|
+
|
|
276
|
+
def CenteredModulusMatrix(self, z: list[list[int]]) -> list[list[int]]:
|
|
277
|
+
"""
|
|
278
|
+
Additional Helper Function 3
|
|
279
|
+
|
|
280
|
+
Computes the centered modulus ``z mod± q`` for a ``matrix`` of ``Integers``.
|
|
281
|
+
|
|
282
|
+
Maps each integer ``x`` to the unique ``r`` in ::
|
|
283
|
+
|
|
284
|
+
[-(q-1)/2, (q-1)/2]
|
|
285
|
+
|
|
286
|
+
such that ::
|
|
287
|
+
|
|
288
|
+
x ≡ r (mod q).
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
z(``list[list[int]]``): A matrix of ``integers``.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
CenteredModulus(``list[list[int]]``)
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
TypeError: If ``z`` is not a ``list[list[int]]``.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
half_q = (self.q - 1) // 2
|
|
301
|
+
|
|
302
|
+
return [[(x + half_q) % self.q - half_q for x in z[k]] for k in range(len(z))]
|
|
303
|
+
|
|
304
|
+
def abs_for_list (self, z: list[int]) -> list[int]:
|
|
305
|
+
"""
|
|
306
|
+
Additional Helper Function 4
|
|
307
|
+
|
|
308
|
+
Computes the absolute values of a ``list`` of integers.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
z (``list[int]``): A ``list`` of integers.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
|z| (``list[int]``): A ``list`` containing the ``absolute`` values of the input ``integers``.
|
|
315
|
+
"""
|
|
316
|
+
for p in range (len(z)):
|
|
317
|
+
z[p] = abs(z[p])
|
|
318
|
+
|
|
319
|
+
return z
|
|
320
|
+
|
|
321
|
+
def InfinityNorm(self, z: list[list[int]]) -> int:
|
|
322
|
+
"""
|
|
323
|
+
Additional Helper Function 5
|
|
324
|
+
|
|
325
|
+
Compute the ``L-Infinity Norm`` of a ``Matrix``.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
z (``list[list[int]]``): A ``Matrix`` of integers.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
infinity_norm (``int``): The infinity norm (``maximum`` absolute value) among all lists.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
TypeError: If ``z`` is not a list of lists of ``integers``.
|
|
335
|
+
TypeError: If any element in the sublists is ``not an integer``.
|
|
336
|
+
TypeError: If elements of ``z[x]`` are not lists.
|
|
337
|
+
"""
|
|
338
|
+
max_value = 0
|
|
339
|
+
|
|
340
|
+
for i in range (len(z)):
|
|
341
|
+
if max_value < max(self.abs_for_list(self.CenteredModulusList(z[i]))):
|
|
342
|
+
max_value = max(self.abs_for_list(self.CenteredModulusList(z[i])))
|
|
343
|
+
|
|
344
|
+
return max_value # returns the max value among all lists.
|
|
345
|
+
|
|
346
|
+
def CalcOnes(self, h: list[list[int]]) -> int:
|
|
347
|
+
"""
|
|
348
|
+
Additional Helper Function 6
|
|
349
|
+
|
|
350
|
+
Compute the number of ``1`` s inside a ``list[list[int]]`` .
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
h (``list[list[int]]``): A ``matrix`` containing ``0`` s and ``1`` s.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Count(``int``): The count of ``1`` s in the ``matrix``.
|
|
357
|
+
|
|
358
|
+
Raises:
|
|
359
|
+
TypeError: If ``h`` is not a ``matrix`` of integers.
|
|
360
|
+
TypeError: If any element in the sublists is not an integer .
|
|
361
|
+
TypeError: If elements of ``h[x]`` are not ``0`` or ``1``.
|
|
362
|
+
"""
|
|
363
|
+
count = 0
|
|
364
|
+
for i in range(len(h)):
|
|
365
|
+
for j in range(len(h[i])):
|
|
366
|
+
if h[i][j] == 1:
|
|
367
|
+
count = count + 1
|
|
368
|
+
|
|
369
|
+
return count
|