Skip to main content

mldsa_core/
lib.rs

1// Copyright (c) 2026-present Thomas <tom@unebaguette.fr>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4pub mod wasm;
5
6pub mod tests;
7
8use ml_dsa::{
9    EncodedVerifyingKey, KeyGen, MlDsaParams, Signature, VerifyingKey, signature::Keypair,
10    signature::rand_core::UnwrapErr,
11};
12use zeroize::Zeroize;
13
14pub struct KeyPair<const VK: usize> {
15    pub seed: [u8; 32],
16    pub verifying_key: [u8; VK],
17}
18
19impl<const VK: usize> Drop for KeyPair<VK> {
20    fn drop(&mut self) {
21        self.seed.zeroize();
22    }
23}
24
25pub fn generate_keypair<P, const VK: usize>() -> KeyPair<VK>
26where
27    P: KeyGen<KeyPair = ml_dsa::SigningKey<P>>,
28    P: MlDsaParams,
29{
30    let mut rng = UnwrapErr(getrandom::SysRng);
31    let kp = P::key_gen(&mut rng);
32    let seed: [u8; 32] = kp.to_seed().into();
33    let vk = kp.verifying_key().encode();
34
35    let mut vk_bytes = [0u8; VK];
36
37    vk_bytes.copy_from_slice(&vk);
38
39    KeyPair {
40        seed,
41        verifying_key: vk_bytes,
42    }
43}
44
45pub fn generate_keypair_from_seed<P, const VK: usize>(seed: &[u8; 32]) -> KeyPair<VK>
46where
47    P: KeyGen<KeyPair = ml_dsa::SigningKey<P>>,
48    P: MlDsaParams,
49{
50    let seed_arr = ml_dsa::B32::from(*seed);
51    let kp = P::from_seed(&seed_arr);
52    let vk = kp.verifying_key().encode();
53    let mut vk_bytes = [0u8; VK];
54
55    vk_bytes.copy_from_slice(&vk);
56
57    KeyPair {
58        seed: *seed,
59        verifying_key: vk_bytes,
60    }
61}
62
63pub fn sign<P, const SIG: usize>(
64    seed: &[u8; 32],
65    message: &[u8],
66    context: Option<&[u8]>,
67) -> [u8; SIG]
68where
69    P: KeyGen<KeyPair = ml_dsa::SigningKey<P>>,
70    P: MlDsaParams,
71{
72    let ctx = context.unwrap_or(&[]);
73    assert!(ctx.len() <= 255, "context must be at most 255 bytes");
74
75    let seed_arr = ml_dsa::B32::from(*seed);
76    let kp = P::from_seed(&seed_arr);
77
78    let sig = kp
79        .signing_key()
80        .sign_deterministic(message, ctx)
81        .expect("sign_deterministic failed despite valid context");
82
83    let encoded = sig.encode();
84    let mut sig_bytes = [0u8; SIG];
85
86    sig_bytes.copy_from_slice(&encoded);
87
88    sig_bytes
89}
90
91pub fn verify<P, const VK: usize, const SIG: usize>(
92    vk_bytes: &[u8; VK],
93    message: &[u8],
94    sig_bytes: &[u8; SIG],
95    context: Option<&[u8]>,
96) -> bool
97where
98    P: MlDsaParams,
99{
100    let ctx = context.unwrap_or(&[]);
101
102    let vk_encoded = match EncodedVerifyingKey::<P>::try_from(vk_bytes.as_slice()) {
103        Ok(v) => v,
104        Err(_) => return false,
105    };
106
107    let vk = VerifyingKey::<P>::decode(&vk_encoded);
108
109    let sig = match Signature::<P>::try_from(sig_bytes.as_slice()) {
110        Ok(s) => s,
111        Err(_) => return false,
112    };
113
114    vk.verify_with_context(message, ctx, &sig)
115}
116
117#[macro_export]
118macro_rules! impl_mldsa_variant {
119    ($variant:ident, $seed:expr, $sk:expr, $vk:expr, $sig:expr) => {
120        use ml_dsa::$variant;
121        use mldsa_core::KeyPair as CoreKeyPair;
122
123        #[cfg(all(
124            not(target_feature = "atomics"),
125            target_family = "wasm",
126            feature = "talc"
127        ))]
128        #[global_allocator]
129        static TALC: talc::wasm::WasmDynamicTalc = talc::wasm::new_wasm_dynamic_allocator();
130
131        pub const SEED_SIZE: usize = $seed;
132        pub const SIGNING_KEY_SIZE: usize = $sk;
133        pub const VERIFYING_KEY_SIZE: usize = $vk;
134        pub const SIGNATURE_SIZE: usize = $sig;
135
136        pub type KeyPair = CoreKeyPair<VERIFYING_KEY_SIZE>;
137
138        pub fn generate_keypair() -> KeyPair {
139            mldsa_core::generate_keypair::<$variant, VERIFYING_KEY_SIZE>()
140        }
141
142        pub fn generate_keypair_from_seed(seed: &[u8; SEED_SIZE]) -> KeyPair {
143            mldsa_core::generate_keypair_from_seed::<$variant, VERIFYING_KEY_SIZE>(seed)
144        }
145
146        pub fn sign(
147            seed: &[u8; SEED_SIZE],
148            message: &[u8],
149            context: Option<&[u8]>,
150        ) -> [u8; SIGNATURE_SIZE] {
151            mldsa_core::sign::<$variant, SIGNATURE_SIZE>(seed, message, context)
152        }
153
154        pub fn verify(
155            vk: &[u8; VERIFYING_KEY_SIZE],
156            message: &[u8],
157            sig: &[u8; SIGNATURE_SIZE],
158            context: Option<&[u8]>,
159        ) -> bool {
160            mldsa_core::verify::<$variant, VERIFYING_KEY_SIZE, SIGNATURE_SIZE>(
161                vk, message, sig, context,
162            )
163        }
164    };
165}