-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added scaffolding to get work started on ML-DSA. (#289)
- Loading branch information
Showing
22 changed files
with
4,352 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "libcrux-ml-dsa" | ||
version.workspace = true | ||
authors.workspace = true | ||
license.workspace = true | ||
homepage.workspace = true | ||
edition.workspace = true | ||
repository.workspace = true | ||
readme.workspace = true | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
|
||
[dev-dependencies] | ||
hex = { version = "0.4.3", features = ["serde"] } | ||
serde_json = { version = "1.0" } | ||
serde = { version = "1.0", features = ["derive"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub(crate) const FIELD_MODULUS_MINUS_ONE_BIT_LENGTH: usize = 23; | ||
|
||
pub(crate) const DROPPED_BITS_FROM_T: usize = 13; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
mod constants; | ||
mod ml_dsa_generic; | ||
|
||
pub mod ml_dsa_65; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use crate::constants::*; | ||
|
||
// ML-DSA-65 parameters | ||
|
||
const ROWS_IN_A: usize = 6; | ||
const COLUMNS_IN_A: usize = 5; | ||
|
||
const PUBLIC_KEY_SIZE: usize = | ||
32 + (32 * ROWS_IN_A * (FIELD_MODULUS_MINUS_ONE_BIT_LENGTH - DROPPED_BITS_FROM_T)); | ||
const SECRET_KEY_SIZE: usize = | ||
(32 + 32 + 64) + 32 * (((ROWS_IN_A + COLUMNS_IN_A) * 4) + (DROPPED_BITS_FROM_T * ROWS_IN_A)); | ||
|
||
pub struct MLDSA65KeyPair { | ||
pub secret_key: [u8; SECRET_KEY_SIZE], | ||
pub public_key: [u8; PUBLIC_KEY_SIZE], | ||
} | ||
|
||
/// Generate an ML-DSA-65 Key Pair | ||
pub fn generate_key_pair(randomness: [u8; 32]) -> MLDSA65KeyPair { | ||
let (secret_key, public_key) = | ||
crate::ml_dsa_generic::generate_key_pair::<SECRET_KEY_SIZE, PUBLIC_KEY_SIZE>(randomness); | ||
|
||
MLDSA65KeyPair { | ||
secret_key, | ||
public_key, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pub(crate) fn generate_key_pair<const SECRET_KEY_SIZE: usize, const PUBLIC_KEY_SIZE: usize>( | ||
randomness: [u8; 32], | ||
) -> ([u8; SECRET_KEY_SIZE], [u8; PUBLIC_KEY_SIZE]) { | ||
let _ = randomness; | ||
todo!(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
In order to regenerate the JSON KAT files for all parameter sets, simply run `./generate_kats.py`. | ||
|
||
N.B.: The ML-DSA implementation was taken from https://github.com/GiacomoPope/dilithium-py/pull/1 with some modifications. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import os | ||
from utils import xor_bytes | ||
from Crypto.Cipher import AES | ||
|
||
|
||
class AES256_CTR_DRBG: | ||
def __init__(self, seed=None, personalization=b""): | ||
self.seed_length = 48 | ||
self.reseed_interval = 2**48 | ||
self.key = bytes([0]) * 32 | ||
self.V = bytes([0]) * 16 | ||
self.entropy_input = self.__check_entropy_input(seed) | ||
|
||
seed_material = self.__instantiate(personalization=personalization) | ||
self.ctr_drbg_update(seed_material) | ||
self.reseed_ctr = 1 | ||
|
||
def __check_entropy_input(self, entropy_input): | ||
""" | ||
If no entropy given, us os.urandom, else | ||
check that the input is of the right length. | ||
""" | ||
if entropy_input is None: | ||
return os.urandom(self.seed_length) | ||
elif len(entropy_input) != self.seed_length: | ||
raise ValueError( | ||
f"The entropy input must be of length: {self.seed_length}. Input has length {len(entropy_input)}" | ||
) | ||
return entropy_input | ||
|
||
def __instantiate(self, personalization=b""): | ||
""" | ||
Combine the input seed and optional personalisation | ||
string into the seed material for the DRBG | ||
""" | ||
if len(personalization) > self.seed_length: | ||
raise ValueError( | ||
f"The Personalization String must be at most length: {self.seed_length}. Input has length {len(personalization)}" | ||
) | ||
elif len(personalization) < self.seed_length: | ||
personalization += bytes([0]) * (self.seed_length - len(personalization)) | ||
# debugging | ||
assert len(personalization) == self.seed_length | ||
return xor_bytes(self.entropy_input, personalization) | ||
|
||
def __increment_counter(self): | ||
int_V = int.from_bytes(self.V, "big") | ||
new_V = (int_V + 1) % 2 ** (8 * 16) | ||
self.V = new_V.to_bytes(16, byteorder="big") | ||
|
||
def ctr_drbg_update(self, provided_data): | ||
tmp = b"" | ||
cipher = AES.new(self.key, AES.MODE_ECB) | ||
# Collect bytes from AES ECB | ||
while len(tmp) != self.seed_length: | ||
self.__increment_counter() | ||
tmp += cipher.encrypt(self.V) | ||
|
||
# Take the first 48 bytes | ||
tmp = tmp[: self.seed_length] | ||
tmp = xor_bytes(tmp, provided_data) | ||
|
||
# Set the new values of key and V | ||
self.key = tmp[:32] | ||
self.V = tmp[32:] | ||
|
||
def reseed(self, additional_information=b""): | ||
""" | ||
Reseed the DRBG for when reseed_ctr hits the | ||
limit. | ||
""" | ||
seed_material = self.__instantiate(additional_information) | ||
self.ctr_drbg_update(seed_material) | ||
self.reseed_ctr = 1 | ||
|
||
def random_bytes(self, num_bytes, additional=None): | ||
if self.reseed_ctr >= self.reseed_interval: | ||
raise Warning("The DRBG has been exhausted! Reseed!") | ||
|
||
# Set the optional additional information | ||
if additional is None: | ||
additional = bytes([0]) * self.seed_length | ||
else: | ||
if len(additional) > self.seed_length: | ||
raise ValueError( | ||
f"The additional input must be of length at most: {self.seed_length}. Input has length {len(seed)}" | ||
) | ||
elif len(additional) < self.seed_length: | ||
additional += bytes([0]) * (self.seed_length - len(additional)) | ||
self.ctr_drbg_update(additional) | ||
|
||
# Collect bytes! | ||
tmp = b"" | ||
cipher = AES.new(self.key, AES.MODE_ECB) | ||
while len(tmp) < num_bytes: | ||
self.__increment_counter() | ||
tmp += cipher.encrypt(self.V) | ||
|
||
# Collect only the requested number of bits | ||
output_bytes = tmp[:num_bytes] | ||
self.ctr_drbg_update(additional) | ||
self.reseed_ctr += 1 | ||
return output_bytes |
Oops, something went wrong.