1pub mod wasm;
5
6mod tests;
7
8use ml_dsa::{
9 EncodedVerifyingKey, Generate, 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: MlDsaParams,
28{
29 let mut rng = UnwrapErr(getrandom::SysRng);
30 let key: ml_dsa::SigningKey<P> = Generate::generate_from_rng(&mut rng);
31
32 let seed: [u8; 32] = key.to_seed().into();
33 let vk = key.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: MlDsaParams,
48{
49 let seed_arr = ml_dsa::B32::from(*seed);
50 let sig_key = ml_dsa::SigningKey::<P>::from_seed(&seed_arr);
51
52 let vk = sig_key.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: MlDsaParams,
70{
71 let ctx = context.unwrap_or(&[]);
72 assert!(ctx.len() <= 255, "context must be at most 255 bytes");
73
74 let seed_arr = ml_dsa::B32::from(*seed);
75 let sig_key = ml_dsa::SigningKey::<P>::from_seed(&seed_arr);
76
77 let esk = sig_key.expanded_key();
78
79 let sig = esk
80 .sign_deterministic(message, ctx)
81 .expect("sign_deterministic failed despite valid context");
82
83 let mut sig_bytes = [0u8; SIG];
84
85 sig_bytes.copy_from_slice(&sig.encode());
86
87 sig_bytes
88}
89
90pub fn verify<P, const VK: usize, const SIG: usize>(
91 vk_bytes: &[u8; VK],
92 message: &[u8],
93 sig_bytes: &[u8; SIG],
94 context: Option<&[u8]>,
95) -> bool
96where
97 P: MlDsaParams,
98{
99 let ctx = context.unwrap_or(&[]);
100
101 let vk_encoded = match EncodedVerifyingKey::<P>::try_from(vk_bytes.as_slice()) {
102 Ok(v) => v,
103 Err(_) => return false,
104 };
105
106 let vk = VerifyingKey::<P>::decode(&vk_encoded);
107
108 let sig = match Signature::<P>::try_from(sig_bytes.as_slice()) {
109 Ok(s) => s,
110 Err(_) => return false,
111 };
112
113 vk.verify_with_context(message, ctx, &sig)
114}
115
116#[macro_export]
117macro_rules! impl_mldsa_variant {
118 ($variant:ident, $seed:expr, $sk:expr, $vk:expr, $sig:expr) => {
119 use ml_dsa::$variant;
120 use mldsa_core::KeyPair as CoreKeyPair;
121
122 #[cfg(all(
123 not(target_feature = "atomics"),
124 target_family = "wasm",
125 feature = "talc"
126 ))]
127 #[global_allocator]
128 static TALC: talc::wasm::WasmDynamicTalc = talc::wasm::new_wasm_dynamic_allocator();
129
130 pub const SEED_SIZE: usize = $seed;
131 pub const SIGNING_KEY_SIZE: usize = $sk;
132 pub const VERIFYING_KEY_SIZE: usize = $vk;
133 pub const SIGNATURE_SIZE: usize = $sig;
134
135 pub type KeyPair = CoreKeyPair<VERIFYING_KEY_SIZE>;
136
137 pub fn generate_keypair() -> KeyPair {
138 mldsa_core::generate_keypair::<$variant, VERIFYING_KEY_SIZE>()
139 }
140
141 pub fn generate_keypair_from_seed(seed: &[u8; SEED_SIZE]) -> KeyPair {
142 mldsa_core::generate_keypair_from_seed::<$variant, VERIFYING_KEY_SIZE>(seed)
143 }
144
145 pub fn sign(
146 seed: &[u8; SEED_SIZE],
147 message: &[u8],
148 context: Option<&[u8]>,
149 ) -> [u8; SIGNATURE_SIZE] {
150 mldsa_core::sign::<$variant, SIGNATURE_SIZE>(seed, message, context)
151 }
152
153 pub fn verify(
154 vk: &[u8; VERIFYING_KEY_SIZE],
155 message: &[u8],
156 sig: &[u8; SIGNATURE_SIZE],
157 context: Option<&[u8]>,
158 ) -> bool {
159 mldsa_core::verify::<$variant, VERIFYING_KEY_SIZE, SIGNATURE_SIZE>(
160 vk, message, sig, context,
161 )
162 }
163 };
164}