hoike_sign/
ml_dsa_bridge.rs1use ml_dsa as mldsa;
8
9pub struct MlDsaSigner<P: mldsa::MlDsaParams> {
11 inner: mldsa::SigningKey<P>,
12 sig_alg_oid: const_oid::ObjectIdentifier,
13}
14
15#[derive(Clone)]
17pub struct MlDsaSignatureBytes {
18 bytes: Vec<u8>,
19}
20
21impl From<MlDsaSignatureBytes> for Vec<u8> {
22 fn from(sig: MlDsaSignatureBytes) -> Vec<u8> {
23 sig.bytes
24 }
25}
26
27pub const ML_DSA_44_OID: &str = "2.16.840.1.101.3.4.3.17";
28pub const ML_DSA_65_OID: &str = "2.16.840.1.101.3.4.3.18";
29pub const ML_DSA_87_OID: &str = "2.16.840.1.101.3.4.3.19";
30
31impl<P: mldsa::MlDsaParams> MlDsaSigner<P> {
32 pub fn new(inner: mldsa::SigningKey<P>, oid_str: &str) -> Self {
33 let sig_alg_oid = const_oid::ObjectIdentifier::new_unwrap(oid_str);
34 MlDsaSigner { inner, sig_alg_oid }
35 }
36}
37
38impl<P> signature::Signer<MlDsaSignatureBytes> for MlDsaSigner<P>
41where
42 P: mldsa::MlDsaParams,
43 mldsa::SigningKey<P>: mldsa::Signer<mldsa::Signature<P>>,
44{
45 fn try_sign(&self, msg: &[u8]) -> Result<MlDsaSignatureBytes, signature::Error> {
46 use mldsa::Signer as Signer3;
47 let sig: mldsa::Signature<P> = self
48 .inner
49 .try_sign(msg)
50 .map_err(|_| signature::Error::new())?;
51 use mldsa::SignatureEncoding;
52 let repr = sig.to_bytes();
53 let slice: &[u8] = repr.as_ref();
54 Ok(MlDsaSignatureBytes {
55 bytes: slice.to_vec(),
56 })
57 }
58}
59
60impl signature::SignatureEncoding for MlDsaSignatureBytes {
63 type Repr = Vec<u8>;
64}
65
66impl TryFrom<&[u8]> for MlDsaSignatureBytes {
67 type Error = signature::Error;
68 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
69 Ok(MlDsaSignatureBytes {
70 bytes: bytes.to_vec(),
71 })
72 }
73}
74
75impl AsRef<[u8]> for MlDsaSignatureBytes {
76 fn as_ref(&self) -> &[u8] {
77 &self.bytes
78 }
79}
80
81impl spki::SignatureBitStringEncoding for MlDsaSignatureBytes {
84 fn to_bitstring(&self) -> der::Result<der::asn1::BitString> {
85 der::asn1::BitString::from_bytes(&self.bytes)
86 }
87}
88
89impl<P> spki::DynSignatureAlgorithmIdentifier for MlDsaSigner<P>
92where
93 P: mldsa::MlDsaParams,
94 mldsa::SigningKey<P>: mldsa::Signer<mldsa::Signature<P>>,
95{
96 fn signature_algorithm_identifier(&self) -> spki::Result<spki::AlgorithmIdentifierOwned> {
97 Ok(spki::AlgorithmIdentifierOwned {
98 oid: self.sig_alg_oid,
99 parameters: None,
100 })
101 }
102}
103
104pub fn ml_dsa_44_signer(seed: &[u8; 32]) -> MlDsaSigner<mldsa::MlDsa44> {
107 let sk = mldsa::SigningKey::<mldsa::MlDsa44>::from_seed(seed.into());
108 MlDsaSigner::new(sk, ML_DSA_44_OID)
109}
110
111pub fn ml_dsa_65_signer(seed: &[u8; 32]) -> MlDsaSigner<mldsa::MlDsa65> {
112 let sk = mldsa::SigningKey::<mldsa::MlDsa65>::from_seed(seed.into());
113 MlDsaSigner::new(sk, ML_DSA_65_OID)
114}
115
116pub fn ml_dsa_87_signer(seed: &[u8; 32]) -> MlDsaSigner<mldsa::MlDsa87> {
117 let sk = mldsa::SigningKey::<mldsa::MlDsa87>::from_seed(seed.into());
118 MlDsaSigner::new(sk, ML_DSA_87_OID)
119}
120
121pub enum MlDsaSignerVariant {
125 MlDsa44(MlDsaSigner<mldsa::MlDsa44>),
126 MlDsa65(MlDsaSigner<mldsa::MlDsa65>),
127 MlDsa87(MlDsaSigner<mldsa::MlDsa87>),
128}
129
130impl MlDsaSignerVariant {
131 pub fn algorithm_name(&self) -> &'static str {
132 match self {
133 Self::MlDsa44(_) => "ml-dsa-44",
134 Self::MlDsa65(_) => "ml-dsa-65",
135 Self::MlDsa87(_) => "ml-dsa-87",
136 }
137 }
138
139 pub fn demo(sig_alg: &str) -> std::result::Result<Self, String> {
141 use rand_core::RngCore;
142 let mut seed = [0u8; 32];
143 rand_core::OsRng.fill_bytes(&mut seed);
144 match sig_alg {
145 "ml-dsa-44" => Ok(Self::MlDsa44(ml_dsa_44_signer(&seed))),
146 "ml-dsa-65" => Ok(Self::MlDsa65(ml_dsa_65_signer(&seed))),
147 "ml-dsa-87" => Ok(Self::MlDsa87(ml_dsa_87_signer(&seed))),
148 other => Err(format!("unknown ML-DSA variant: {other}")),
149 }
150 }
151
152 pub fn sign_bundle(
155 &mut self,
156 ca: &crate::source::CaIdentity,
157 snapshot: &crate::source::StatusSnapshot,
158 config: &crate::generate::GenerationConfig,
159 seal_fn: impl FnOnce(&[u8]) -> crate::error::Result<Vec<u8>>,
160 responder_cert_der: Option<&[u8]>,
161 ) -> crate::error::Result<Vec<u8>> {
162 match self {
163 Self::MlDsa44(s) => crate::generate::produce_bundle::<_, MlDsaSignatureBytes>(
164 ca,
165 snapshot,
166 config,
167 s,
168 seal_fn,
169 responder_cert_der,
170 ),
171 Self::MlDsa65(s) => crate::generate::produce_bundle::<_, MlDsaSignatureBytes>(
172 ca,
173 snapshot,
174 config,
175 s,
176 seal_fn,
177 responder_cert_der,
178 ),
179 Self::MlDsa87(s) => crate::generate::produce_bundle::<_, MlDsaSignatureBytes>(
180 ca,
181 snapshot,
182 config,
183 s,
184 seal_fn,
185 responder_cert_der,
186 ),
187 }
188 }
189}
190
191pub fn load_ml_dsa_signer_from_pkcs8_der(der_bytes: &[u8]) -> Result<MlDsaSignerVariant, String> {
194 use ml_dsa::pkcs8::DecodePrivateKey;
195
196 if let Ok(sk) = mldsa::SigningKey::<mldsa::MlDsa44>::from_pkcs8_der(der_bytes) {
197 return Ok(MlDsaSignerVariant::MlDsa44(MlDsaSigner::new(
198 sk,
199 ML_DSA_44_OID,
200 )));
201 }
202 if let Ok(sk) = mldsa::SigningKey::<mldsa::MlDsa65>::from_pkcs8_der(der_bytes) {
203 return Ok(MlDsaSignerVariant::MlDsa65(MlDsaSigner::new(
204 sk,
205 ML_DSA_65_OID,
206 )));
207 }
208 if let Ok(sk) = mldsa::SigningKey::<mldsa::MlDsa87>::from_pkcs8_der(der_bytes) {
209 return Ok(MlDsaSignerVariant::MlDsa87(MlDsaSigner::new(
210 sk,
211 ML_DSA_87_OID,
212 )));
213 }
214
215 Err("key does not contain a valid ML-DSA-44, ML-DSA-65, or ML-DSA-87 PKCS#8 private key".into())
216}
217
218pub fn ml_dsa_signature_size(variant: &str) -> usize {
219 match variant {
220 "ml-dsa-44" => 2420,
221 "ml-dsa-65" => 3309,
222 "ml-dsa-87" => 4627,
223 _ => 0,
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn ml_dsa_44_sign_verify_size() {
233 let signer = ml_dsa_44_signer(&[1u8; 32]);
234 use signature::Signer;
235 let sig = signer.sign(b"test message");
236 assert_eq!(sig.bytes.len(), 2420);
237
238 use spki::DynSignatureAlgorithmIdentifier;
239 let alg_id = signer.signature_algorithm_identifier().unwrap();
240 assert_eq!(alg_id.oid.to_string(), ML_DSA_44_OID);
241
242 use spki::SignatureBitStringEncoding;
243 let bits = sig.to_bitstring().unwrap();
244 assert_eq!(bits.raw_bytes().len(), 2420);
245 }
246
247 #[test]
248 fn ml_dsa_65_sign_verify_size() {
249 let signer = ml_dsa_65_signer(&[2u8; 32]);
250 use signature::Signer;
251 let sig = signer.sign(b"test message");
252 assert_eq!(sig.bytes.len(), 3309);
253 }
254
255 #[test]
256 fn ml_dsa_87_sign_verify_size() {
257 let signer = ml_dsa_87_signer(&[3u8; 32]);
258 use signature::Signer;
259 let sig = signer.sign(b"test message");
260 assert_eq!(sig.bytes.len(), 4627);
261 }
262
263 #[test]
264 fn ml_dsa_44_pkcs8_round_trip() {
265 use ml_dsa::pkcs8::EncodePrivateKey;
266 let sk = mldsa::SigningKey::<mldsa::MlDsa44>::from_seed((&[1u8; 32]).into());
267 let der_doc = sk.to_pkcs8_der().expect("encode PKCS#8");
268 let variant = load_ml_dsa_signer_from_pkcs8_der(der_doc.as_bytes()).unwrap();
269 assert_eq!(variant.algorithm_name(), "ml-dsa-44");
270 }
271
272 #[test]
273 fn ml_dsa_65_pkcs8_round_trip() {
274 use ml_dsa::pkcs8::EncodePrivateKey;
275 let sk = mldsa::SigningKey::<mldsa::MlDsa65>::from_seed((&[2u8; 32]).into());
276 let der_doc = sk.to_pkcs8_der().expect("encode PKCS#8");
277 let variant = load_ml_dsa_signer_from_pkcs8_der(der_doc.as_bytes()).unwrap();
278 assert_eq!(variant.algorithm_name(), "ml-dsa-65");
279 }
280
281 #[test]
282 fn ml_dsa_87_pkcs8_round_trip() {
283 use ml_dsa::pkcs8::EncodePrivateKey;
284 let sk = mldsa::SigningKey::<mldsa::MlDsa87>::from_seed((&[3u8; 32]).into());
285 let der_doc = sk.to_pkcs8_der().expect("encode PKCS#8");
286 let variant = load_ml_dsa_signer_from_pkcs8_der(der_doc.as_bytes()).unwrap();
287 assert_eq!(variant.algorithm_name(), "ml-dsa-87");
288 }
289
290 #[test]
291 fn ml_dsa_pkcs8_invalid_data_errors() {
292 let result = load_ml_dsa_signer_from_pkcs8_der(b"not valid pkcs8");
293 assert!(result.is_err());
294 }
295
296 #[test]
297 fn ml_dsa_pkcs8_loaded_signer_produces_correct_signature() {
298 use ml_dsa::pkcs8::EncodePrivateKey;
299 let sk = mldsa::SigningKey::<mldsa::MlDsa87>::from_seed((&[42u8; 32]).into());
300 let der_doc = sk.to_pkcs8_der().expect("encode PKCS#8");
301 let variant = load_ml_dsa_signer_from_pkcs8_der(der_doc.as_bytes()).unwrap();
302 match variant {
303 MlDsaSignerVariant::MlDsa87(ref signer) => {
304 use signature::Signer;
305 let sig = signer.sign(b"test message");
306 assert_eq!(sig.bytes.len(), 4627);
307 }
308 _ => panic!("expected ML-DSA-87 variant"),
309 }
310 }
311
312 #[test]
313 fn ml_dsa_pkcs8_deterministic_across_load() {
314 use ml_dsa::pkcs8::EncodePrivateKey;
315 let sk = mldsa::SigningKey::<mldsa::MlDsa87>::from_seed((&[7u8; 32]).into());
316 let der_doc = sk.to_pkcs8_der().expect("encode PKCS#8");
317
318 let seed_signer = ml_dsa_87_signer(&[7u8; 32]);
319 let variant = load_ml_dsa_signer_from_pkcs8_der(der_doc.as_bytes()).unwrap();
320
321 use signature::Signer;
322 let msg = b"determinism check";
323 let sig_seed = seed_signer.sign(msg);
324 match variant {
325 MlDsaSignerVariant::MlDsa87(ref loaded) => {
326 let sig_loaded = loaded.sign(msg);
327 assert_eq!(sig_seed.bytes, sig_loaded.bytes);
328 }
329 _ => panic!("expected ML-DSA-87"),
330 }
331 }
332}