ruvector_dag/qudag/crypto/
ml_dsa.rs1use zeroize::Zeroize;
16
17pub const ML_DSA_65_PUBLIC_KEY_SIZE: usize = 1952;
20pub const ML_DSA_65_SECRET_KEY_SIZE: usize = 4032;
21pub const ML_DSA_65_SIGNATURE_SIZE: usize = 3309;
22
23#[derive(Clone)]
24pub struct MlDsa65PublicKey(pub [u8; ML_DSA_65_PUBLIC_KEY_SIZE]);
25
26#[derive(Clone, Zeroize)]
27#[zeroize(drop)]
28pub struct MlDsa65SecretKey(pub [u8; ML_DSA_65_SECRET_KEY_SIZE]);
29
30#[derive(Clone)]
31pub struct Signature(pub [u8; ML_DSA_65_SIGNATURE_SIZE]);
32
33pub struct MlDsa65;
34
35#[cfg(feature = "production-crypto")]
40mod production {
41 use super::*;
42 use pqcrypto_dilithium::dilithium3;
43 use pqcrypto_traits::sign::{DetachedSignature, PublicKey, SecretKey};
44
45 impl MlDsa65 {
46 pub fn generate_keypair() -> Result<(MlDsa65PublicKey, MlDsa65SecretKey), DsaError> {
48 let (pk, sk) = dilithium3::keypair();
49
50 let pk_bytes = pk.as_bytes();
51 let sk_bytes = sk.as_bytes();
52
53 let mut pk_arr = [0u8; ML_DSA_65_PUBLIC_KEY_SIZE];
55 let mut sk_arr = [0u8; ML_DSA_65_SECRET_KEY_SIZE];
56
57 if pk_bytes.len() != ML_DSA_65_PUBLIC_KEY_SIZE {
58 return Err(DsaError::InvalidPublicKey);
59 }
60 if sk_bytes.len() != ML_DSA_65_SECRET_KEY_SIZE {
61 return Err(DsaError::SigningFailed);
62 }
63
64 pk_arr.copy_from_slice(pk_bytes);
65 sk_arr.copy_from_slice(sk_bytes);
66
67 Ok((MlDsa65PublicKey(pk_arr), MlDsa65SecretKey(sk_arr)))
68 }
69
70 pub fn sign(sk: &MlDsa65SecretKey, message: &[u8]) -> Result<Signature, DsaError> {
72 let secret_key =
73 dilithium3::SecretKey::from_bytes(&sk.0).map_err(|_| DsaError::InvalidSignature)?;
74
75 let sig = dilithium3::detached_sign(message, &secret_key);
76 let sig_bytes = sig.as_bytes();
77
78 let mut sig_arr = [0u8; ML_DSA_65_SIGNATURE_SIZE];
79
80 let copy_len = sig_bytes.len().min(ML_DSA_65_SIGNATURE_SIZE);
82 sig_arr[..copy_len].copy_from_slice(&sig_bytes[..copy_len]);
83
84 Ok(Signature(sig_arr))
85 }
86
87 pub fn verify(
89 pk: &MlDsa65PublicKey,
90 message: &[u8],
91 signature: &Signature,
92 ) -> Result<bool, DsaError> {
93 let public_key =
94 dilithium3::PublicKey::from_bytes(&pk.0).map_err(|_| DsaError::InvalidPublicKey)?;
95
96 let sig = dilithium3::DetachedSignature::from_bytes(&signature.0[..3293])
98 .map_err(|_| DsaError::InvalidSignature)?;
99
100 match dilithium3::verify_detached_signature(&sig, message, &public_key) {
101 Ok(()) => Ok(true),
102 Err(_) => Ok(false),
103 }
104 }
105 }
106}
107
108#[cfg(not(feature = "production-crypto"))]
113mod placeholder {
114 use super::*;
115 use sha2::{Digest, Sha256};
116
117 impl MlDsa65 {
118 pub fn generate_keypair() -> Result<(MlDsa65PublicKey, MlDsa65SecretKey), DsaError> {
123 let mut pk = [0u8; ML_DSA_65_PUBLIC_KEY_SIZE];
124 let mut sk = [0u8; ML_DSA_65_SECRET_KEY_SIZE];
125
126 getrandom::getrandom(&mut pk).map_err(|_| DsaError::RngFailed)?;
127 getrandom::getrandom(&mut sk).map_err(|_| DsaError::RngFailed)?;
128
129 Ok((MlDsa65PublicKey(pk), MlDsa65SecretKey(sk)))
130 }
131
132 pub fn sign(sk: &MlDsa65SecretKey, message: &[u8]) -> Result<Signature, DsaError> {
138 let mut sig = [0u8; ML_DSA_65_SIGNATURE_SIZE];
139
140 let hmac = Self::hmac_sha256(&sk.0[..32], message);
141
142 for i in 0..ML_DSA_65_SIGNATURE_SIZE {
143 sig[i] = hmac[i % 32];
144 }
145
146 let key_hash = Self::sha256(&sk.0[32..64]);
147 for i in 0..32 {
148 sig[i + 32] = key_hash[i];
149 }
150
151 Ok(Signature(sig))
152 }
153
154 pub fn verify(
159 pk: &MlDsa65PublicKey,
160 message: &[u8],
161 signature: &Signature,
162 ) -> Result<bool, DsaError> {
163 let expected_key_hash = Self::sha256(&pk.0[..32]);
164 let sig_key_hash = &signature.0[32..64];
165
166 if sig_key_hash != expected_key_hash.as_slice() {
167 return Ok(false);
168 }
169
170 let msg_hash = Self::sha256(message);
171 let sig_structure_valid = signature.0[..32]
172 .iter()
173 .zip(msg_hash.iter().cycle())
174 .all(|(s, h)| *s != 0 || *h == 0);
175
176 Ok(sig_structure_valid)
177 }
178
179 fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
180 const BLOCK_SIZE: usize = 64;
181
182 let mut key_block = [0u8; BLOCK_SIZE];
183 if key.len() > BLOCK_SIZE {
184 let hash = Self::sha256(key);
185 key_block[..32].copy_from_slice(&hash);
186 } else {
187 key_block[..key.len()].copy_from_slice(key);
188 }
189
190 let mut ipad = [0x36u8; BLOCK_SIZE];
191 for (i, k) in key_block.iter().enumerate() {
192 ipad[i] ^= k;
193 }
194
195 let mut opad = [0x5cu8; BLOCK_SIZE];
196 for (i, k) in key_block.iter().enumerate() {
197 opad[i] ^= k;
198 }
199
200 let mut inner = Vec::with_capacity(BLOCK_SIZE + message.len());
201 inner.extend_from_slice(&ipad);
202 inner.extend_from_slice(message);
203 let inner_hash = Self::sha256(&inner);
204
205 let mut outer = Vec::with_capacity(BLOCK_SIZE + 32);
206 outer.extend_from_slice(&opad);
207 outer.extend_from_slice(&inner_hash);
208 Self::sha256(&outer)
209 }
210
211 fn sha256(data: &[u8]) -> [u8; 32] {
212 let mut hasher = Sha256::new();
213 hasher.update(data);
214 let result = hasher.finalize();
215 let mut output = [0u8; 32];
216 output.copy_from_slice(&result);
217 output
218 }
219 }
220}
221
222#[derive(Debug, thiserror::Error)]
223pub enum DsaError {
224 #[error("Random number generation failed")]
225 RngFailed,
226 #[error("Invalid public key")]
227 InvalidPublicKey,
228 #[error("Invalid signature")]
229 InvalidSignature,
230 #[error("Signing failed")]
231 SigningFailed,
232 #[error("Verification failed")]
233 VerificationFailed,
234}
235
236pub fn is_production() -> bool {
238 cfg!(feature = "production-crypto")
239}