Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e935fc4
initial ml-dsa notes
ounsworth Feb 6, 2026
ef10159
crate re-raming
ounsworth Feb 6, 2026
1bb10ce
Merge branch 'main' into feature/mikeo/mldsa
ounsworth Feb 6, 2026
db338af
이슈 "https://github.com/bcgit/bc-rust/issues/6" 수정 사항 반영
Quant-TheodoreFelix Feb 12, 2026
d36ae7e
u64에 대한 `Condition` 구현체 생성
Quant-TheodoreFelix Feb 12, 2026
0082c0d
i64에 대한 `Condition` 구현체 TRUE 상수값 -1로 변경 및 `ct_tests` 테스트 추가 및 수정
Quant-TheodoreFelix Feb 12, 2026
db02d4d
ML-DSA progress 1
ounsworth Feb 15, 2026
2080f0a
ML-DSA type system figured out
ounsworth Feb 16, 2026
933a999
ML-DSA progress #2
ounsworth Feb 22, 2026
a3d4608
mldsa keygen working
ounsworth Feb 27, 2026
12e0454
mldsa cleanup and performance optimizations
ounsworth Feb 27, 2026
df4ec01
partial implementation of MLDSA.sign
ounsworth Mar 2, 2026
d1353db
before trying an overhaul to the MLDSA type system
ounsworth Mar 4, 2026
45370bf
re-worked the MLDSA type system
ounsworth Mar 4, 2026
a827979
mldsa.sign minimally working
ounsworth Mar 8, 2026
c3f29e0
ML-DSA Signing (minimally) working)
ounsworth Mar 13, 2026
4b53872
Renamed package and added MLDSA.sign bench code
ounsworth Mar 13, 2026
50c23bb
mldsa optimization
ounsworth Mar 13, 2026
b2c93c0
some refactoring
ounsworth Mar 13, 2026
3f437a3
some refactoring
ounsworth Mar 13, 2026
31b2367
ML-DSA-44 signing and verification works
ounsworth Mar 16, 2026
65e4e29
ML-DSA WORKS!!!
ounsworth Mar 16, 2026
b32618c
benches for mldsa verify
ounsworth Mar 16, 2026
5a435bf
ML-DSA unit tests
ounsworth Mar 16, 2026
04c5cf6
progress on ml-dsa unit tests
ounsworth Mar 17, 2026
3af0eb2
Greatly reduced boilerplate in ml_dsa_keys.rs
ounsworth Mar 17, 2026
c6db684
Finished de-duplicating ML-DSA code
ounsworth Mar 17, 2026
4b21d5d
Finished massive refactor on mldsa.rs to remove duplicate code
ounsworth Mar 17, 2026
09e5a18
cleaned up zeroization
ounsworth Mar 18, 2026
88e5674
Fixed mldsa benches. Deleted nursery version
ounsworth Mar 18, 2026
755a43c
resolved a bunch of todo's
ounsworth Mar 18, 2026
17c39d7
after some mutants testing
ounsworth Mar 18, 2026
932b2e9
hashml-dsa implemented, but no tests yet
ounsworth Mar 18, 2026
68e7a8c
HashML-DSA implemented
ounsworth Mar 19, 2026
96a23fc
Wrote test harness for bc-test-data
ounsworth Mar 19, 2026
aa8c7a3
Fixed the Drop Bounds thing. Some tests still failing
ounsworth Mar 19, 2026
e4f505b
fixed a bug in the HashML-DSA-87 test
ounsworth Mar 19, 2026
acbeec6
Validated against the full bc-test-data tests that mutants is right t…
ounsworth Mar 19, 2026
889466c
Deleted local copy of bc-test-data files
ounsworth Mar 19, 2026
66f858d
some incomplete work on cli and keygen_sk_only; need to snapshot
ounsworth Mar 20, 2026
c1a0e11
first pass at creating the low-memory sign_mu_deterministic_out
ounsworth Mar 20, 2026
ccfba7b
memory optimized sign_from_seed
ounsworth Mar 20, 2026
90179c2
memory optimized sign_from_seed
ounsworth Mar 20, 2026
0f51f3b
memory optimization achieved!
ounsworth Mar 20, 2026
459b495
Memory optimization complete!
ounsworth Mar 20, 2026
f5d2e11
Partway through CLI for ML-DSA. Need to pause
ounsworth Mar 20, 2026
3148b4d
cli working for mldsa44
ounsworth Mar 21, 2026
0c58cd5
Refactored PHSignature to be a subtrait of Signature
ounsworth Mar 21, 2026
4d996a7
cli for ml-dsa done
ounsworth Mar 21, 2026
c4db2b4
Merge remote-tracking branch 'upstream/main'
Quant-TheodoreFelix Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bouncycastle-factory = { path = "./crypto/factory", version = "0.1.1"}
bouncycastle-hex = { path = "./crypto/hex", version = "0.1.1" }
bouncycastle-hkdf = { path = "./crypto/hkdf", version = "0.1.1"}
bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.1"}
bouncycastle-mldsa = { path = "./crypto/mldsa", version = "0.1.2" }
bouncycastle-rng = { path = "./crypto/rng", version = "0.1.1" }
bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.1"}
bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.1"}
Expand Down Expand Up @@ -42,6 +43,7 @@ bouncycastle-factory.workspace = true
bouncycastle-hex.workspace = true
bouncycastle-hkdf.workspace = true
bouncycastle-hmac.workspace = true
bouncycastle-mldsa.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
17 changes: 17 additions & 0 deletions alpha_0.1.2_release_notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TODO
[remove this section before publication]

[ ] Finish ML-DSA
[ ] Ensure that all crates have `#![forbid(missing_docs)]`
[ ] Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto
[ ] Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<>
[ ] Enhance the default HashDRBG instantiation to take in NIST-compatible CPU jitter entropy
[ ] Get an opinion from Bob Beck or Dennis about the factories ... Are they worth it?
[ ] Do a pass over KeyMaterial, taking into account Dennis Jackson's input (maybe ping him for a phone call?)
[ ] Open github issues

# 0.1.2 Features / Changelog

* ML-DSA
* Github issues resolved:
* #2, or whatever
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cli"
version = "0.1.0"
version = "0.1.2"
edition.workspace = true

[dependencies]
Expand Down
42 changes: 26 additions & 16 deletions cli/src/encoders_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,60 @@ pub(crate) fn hex_encode_cmd() {
// Stream from stdin to stdout in chunks of 1 kb
let mut buf: [u8; 1024] = [0u8; 1024];
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
while bytes_read == 1024 {
print!("{}", hex::encode(&buf));
while bytes_read != 0 {
io::stdout().write_all(
hex::encode(&buf[..bytes_read]).as_bytes()
).expect("Failed to write to stdout");

bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
}
print!("{}", hex::encode(&buf));
}

pub(crate) fn hex_decode_cmd() {
// Stream from stdin to stdout in chunks of 1 kb
let mut buf: [u8; 1024] = [0u8; 1024];
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
let mut chunk_str: String = String::from_utf8(Vec::from(buf.as_slice())).unwrap();
while bytes_read == 1024 {
io::stdout().write_all(&*hex::decode(chunk_str.as_str()).unwrap()).expect("Failed to write to stdout");
while bytes_read != 0 {
let chunk_str: String = String::from_utf8(
Vec::from(&buf[..bytes_read])
).expect("Input was not valid utf8.");

io::stdout().write_all(
&*hex::decode(chunk_str.as_str()).expect("Input was not valid hex.")
).expect("Failed to write to stdout");

bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
chunk_str = String::from_utf8(Vec::from(buf.as_slice())).unwrap();
}
io::stdout().write_all(&*hex::decode(chunk_str.as_str()).unwrap()).expect("Failed to write to stdout");
}

pub(crate) fn base64_encode_cmd() {
let mut encoder = base64::Base64Encoder::new();
// Stream from stdin to stdout in chunks of 1 kb
let mut buf: [u8; 1024] = [0u8; 1024];
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
while bytes_read == 1024 {
print!("{}", encoder.do_update(&buf));
while bytes_read != 0 {
io::stdout().write_all(
encoder.do_update(&buf[..bytes_read]).as_bytes()
).expect("Failed to write to stdout");

bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
}
print!("{}", encoder.do_final(&buf[..bytes_read]));
}

pub(crate) fn base64_decode_cmd() {
// Stream from stdin to stdout in chunks of 1 kb
let mut buf: [u8; 1024] = [0u8; 1024];
let mut decoder = base64::Base64Decoder::new(true);
let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
let mut chunk_str: String = String::from_utf8(Vec::from(buf.as_slice())).unwrap();
while bytes_read == 1024 {
io::stdout().write_all(decoder.do_update(chunk_str.as_str()).unwrap().as_slice()).expect("Failed to write to stdout");
while bytes_read != 0 {
let chunk_str: String = String::from_utf8(
Vec::from(&buf[..bytes_read])
).expect("Input was not valid utf8.");

io::stdout().write_all(
decoder.do_update(chunk_str.as_str()).expect("Input was not valid base64.").as_slice()
).expect("Failed to write to stdout");

bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
chunk_str = String::from_utf8(Vec::from(buf.as_slice())).unwrap();
}
io::stdout().write_all(decoder.do_final(chunk_str.as_str()).unwrap().as_slice()).expect("Failed to write to stdout");
}
6 changes: 3 additions & 3 deletions cli/src/hkdf_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{fs, io};
use std::io::Write;
use std::process::exit;

use bouncycastle::core_interface::key_material::{KeyMaterialInternal, KeyType};
use bouncycastle::core_interface::key_material::{KeyMaterialSized, KeyType};
use bouncycastle::core_interface::traits::KeyMaterial;
use bouncycastle::hex;
use bouncycastle::hkdf;
Expand All @@ -19,7 +19,7 @@ pub(crate) fn hkdf_cmd(hkdfname: &str,
let salt_bytes: Vec<u8>;
let ikm_bytes: Vec<u8>;
let additional_input_bytes: Vec<u8>;
let mut out_key = KeyMaterialInternal::<1024>::new();
let mut out_key = KeyMaterialSized::<1024>::new();

if len > 1024 {
eprintln!("Error: The CLI only supports output lengths up to 128 bytes (1024 bits).");
Expand All @@ -40,7 +40,7 @@ pub(crate) fn hkdf_cmd(hkdfname: &str,
eprintln!("Error: The CLI only supports HKDF salts up to 128 bytes (1024 bytes).");
exit(-1);
}
let mut salt_key = KeyMaterialInternal::<1024>::from_bytes(&salt_bytes).unwrap();
let mut salt_key = KeyMaterialSized::<1024>::from_bytes(&salt_bytes).unwrap();
// force it just so the CLI behaves properly even with all-zero or zero-length keys
salt_key.allow_hazardous_operations();
salt_key.convert_key_type(KeyType::MACKey).unwrap();
Expand Down
199 changes: 198 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ mod sha2_cmd;
mod mac_cmd;
mod hkdf_cmd;
mod rng_cmd;
mod mldsa_cmd;

use clap::{Parser, Subcommand};
use std::io;
use std::io::Write;
use clap::{Parser, Subcommand, ValueEnum};
use crate::mac_cmd::HMACVariant;

#[derive(Parser)]
Expand Down Expand Up @@ -269,6 +272,194 @@ enum Subcommands {
/// Output in hex format.
x: bool,
},

/// The ML-DSA-44 signature algorithm.
MLDSA44 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The ML-DSA-65 signature algorithm.
MLDSA65 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The ML-DSA-87 signature algorithm.
MLDSA87 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The HashML-DSA-44 signature algorithm.
HashMLDSA44 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The HashML-DSA-65 signature algorithm.
HashMLDSA65 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},

/// The HashML-DSA87 signature algorithm.
HashMLDSA87 {
action: MLDSAAction,

#[arg(long)]
/// The file containing context string (in hex) for signing or verifying
ctxfile: Option<String>,

#[arg(long)]
/// The private key file (in hex or binary) for signing
skfile: Option<String>,

#[arg(long)]
/// The public key file (in hex or binary) for verifying
pkfile: Option<String>,

#[arg(long)]
/// The signature value file (in hex or binary) for verifying
sigfile: Option<String>,


#[arg(short)]
/// Output in hex format.
x: bool,
},
}

#[derive(ValueEnum, Clone, Debug)]
pub(crate) enum MLDSAAction {
/// Generate and output a new private key
Keygen,
/// Generate and output a private key from a seed read from stdin.
/// Accepts either binary or hex.
KeygenFromSeed,
/// Generate and output a new public key from a private key read from stdin.
/// Accepts either binary or hex.
PkFromSk,
/// Accepts a sk and pk, and checks that they match.
CheckConsistency,
/// Sign a message read from stdin with a private key file and output the signature.
/// Accepts private key as full or seed, binary or hex.
Sign,
/// Verify a message read from stdin with a public key file and a signature file
/// Accepts the public key and signature as binary or hex.
Verify,
}

pub(crate) fn write_bytes_or_hex(bytes: &[u8], output_hex: bool) {
// first flush stdout to ensure any buffered data is written
io::stdout().flush().unwrap();
if output_hex {
for b in bytes.iter() {
print!("{b:02x}");
}
} else {
io::stdout().write_all(bytes).unwrap();
}
}

fn main() {
Expand All @@ -294,6 +485,12 @@ fn main() {
Some(Subcommands::HKDF_SHA256 { salt, salt_file, ikm, ikm_file, additional_input, additional_input_file, len, x}) => { hkdf_cmd::hkdf_cmd("HKDF-SHA256", salt, salt_file, ikm, ikm_file, additional_input, additional_input_file, *len, *x)},
Some(Subcommands::HKDF_SHA512 { salt, salt_file, ikm, ikm_file, additional_input, additional_input_file, len, x}) => { hkdf_cmd::hkdf_cmd("HKDF-SHA512", salt, salt_file, ikm, ikm_file, additional_input, additional_input_file, *len, *x)},
Some(Subcommands::RNG { len, x}) => { rng_cmd::rng_cmd(*len, *x)},
Some(Subcommands::MLDSA44 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::mldsa44_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
Some(Subcommands::MLDSA65 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::mldsa65_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
Some(Subcommands::MLDSA87 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::mldsa87_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
Some(Subcommands::HashMLDSA44 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::hash_mldsa44_sha512_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
Some(Subcommands::HashMLDSA65 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::hash_mldsa65_sha512_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
Some(Subcommands::HashMLDSA87 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::hash_mldsa87_sha512_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); }
None => { eprintln!("No command provided. See -h") },
}
}
Loading