Skip to main content

lib_q_core/wasm/
conversions.rs

1//! WASM conversion helpers between Rust buffers and JavaScript [`js_sys::Uint8Array`].
2//!
3//! These are mechanical copies: they do not infer sensitivity. Callers choose [`zeroize::Zeroizing`]
4//! or explicit clearing when handling secrets.
5
6#[cfg(feature = "wasm")]
7extern crate alloc;
8#[cfg(feature = "wasm")]
9use alloc::{
10    format,
11    string::{
12        String,
13        ToString,
14    },
15    vec::Vec,
16};
17
18#[cfg(feature = "wasm")]
19use js_sys::Uint8Array;
20#[cfg(feature = "wasm")]
21use serde_json;
22#[cfg(feature = "wasm")]
23use serde_wasm_bindgen;
24#[cfg(feature = "wasm")]
25use wasm_bindgen::prelude::*;
26
27use crate::api::Algorithm;
28use crate::error::Result;
29
30/// Byte-array conversion helpers for WASM bindings.
31#[cfg(feature = "wasm")]
32pub struct WasmConversions;
33
34#[cfg(feature = "wasm")]
35impl WasmConversions {
36    /// Copy `data` into a new JavaScript `Uint8Array`.
37    ///
38    /// The input slice is not modified; callers that need the source buffer cleared must do so
39    /// separately (for example by holding secrets in [`zeroize::Zeroizing`] and letting it drop
40    /// after this call).
41    pub fn vec_to_uint8array(data: &[u8]) -> Uint8Array {
42        let n = u32::try_from(data.len()).expect("length exceeds JavaScript Uint8Array maximum");
43        let array = Uint8Array::new_with_length(n);
44        array.copy_from(data);
45        array
46    }
47
48    /// Convert WASM Uint8Array to Rust Vec<u8>
49    ///
50    /// Validates input size to limit abuse; copies array contents into a new `Vec`.
51    pub fn uint8array_to_vec(array: &Uint8Array) -> Result<Vec<u8>> {
52        let length = array.length() as usize;
53
54        // Validate size to prevent DoS attacks
55        const MAX_SIZE: usize = 1024 * 1024; // 1MB limit
56        if length > MAX_SIZE {
57            return Err(crate::error::Error::InvalidMessageSize {
58                max: MAX_SIZE,
59                actual: length,
60            });
61        }
62
63        let mut vec = alloc::vec![0u8; length];
64        array.copy_to(&mut vec);
65        Ok(vec)
66    }
67
68    /// Convert algorithm string to Algorithm enum
69    ///
70    /// This function provides secure algorithm parsing with:
71    /// - Input validation to prevent injection attacks
72    /// - Case-insensitive matching for user convenience
73    /// - Clear error messages for unsupported algorithms
74    pub fn string_to_algorithm(algorithm_str: &str) -> Result<Algorithm> {
75        match algorithm_str.to_lowercase().as_str() {
76            // KEM algorithms
77            "mlkem512" | "ml-kem-512" => Ok(Algorithm::MlKem512),
78            "mlkem768" | "ml-kem-768" => Ok(Algorithm::MlKem768),
79            "mlkem1024" | "ml-kem-1024" => Ok(Algorithm::MlKem1024),
80            // Signature algorithms
81            "mldsa44" | "ml-dsa-44" => Ok(Algorithm::MlDsa44),
82            "mldsa65" | "ml-dsa-65" => Ok(Algorithm::MlDsa65),
83            "mldsa87" | "ml-dsa-87" => Ok(Algorithm::MlDsa87),
84            "fndsa" | "fn-dsa" => Ok(Algorithm::FnDsa),
85
86            // SLH-DSA (hyphenated IDs, NIST-style names, and PascalCase `SlhDsa*` lowercased)
87            "slh-dsa-sha256-128f-robust" |
88            "slh-dsa-sha2-128f-robust" |
89            "slhdsasha256128frobust" => Ok(Algorithm::SlhDsaSha256128fRobust),
90            "slh-dsa-sha256-192f-robust" |
91            "slh-dsa-sha2-192f-robust" |
92            "slhdsasha256192frobust" => Ok(Algorithm::SlhDsaSha256192fRobust),
93            "slh-dsa-sha256-256f-robust" |
94            "slh-dsa-sha2-256f-robust" |
95            "slhdsasha256256frobust" => Ok(Algorithm::SlhDsaSha256256fRobust),
96            "slh-dsa-shake256-128f-robust" | "slhdsashake256128frobust" => {
97                Ok(Algorithm::SlhDsaShake256128fRobust)
98            }
99            "slh-dsa-shake256-192f-robust" | "slhdsashake256192frobust" => {
100                Ok(Algorithm::SlhDsaShake256192fRobust)
101            }
102            "slh-dsa-shake256-256f-robust" | "slhdsashake256256frobust" => {
103                Ok(Algorithm::SlhDsaShake256256fRobust)
104            }
105
106            // Hash algorithms
107            "sha3_224" | "sha3-224" => Ok(Algorithm::Sha3_224),
108            "sha3_256" | "sha3-256" => Ok(Algorithm::Sha3_256),
109            "sha3_384" | "sha3-384" => Ok(Algorithm::Sha3_384),
110            "sha3_512" | "sha3-512" => Ok(Algorithm::Sha3_512),
111            "shake128" => Ok(Algorithm::Shake128),
112            "shake256" => Ok(Algorithm::Shake256),
113            "sha224" | "sha-224" => Ok(Algorithm::Sha224),
114            "sha256" | "sha-256" => Ok(Algorithm::Sha256),
115            "sha384" | "sha-384" => Ok(Algorithm::Sha384),
116            "sha512" | "sha-512" => Ok(Algorithm::Sha512),
117            "sha512_224" | "sha512-224" | "sha-512/224" => Ok(Algorithm::Sha512_224),
118            "sha512_256" | "sha512-256" | "sha-512/256" => Ok(Algorithm::Sha512_256),
119            "cshake128" | "cshake-128" => Ok(Algorithm::CShake128),
120            "cshake256" | "cshake-256" => Ok(Algorithm::CShake256),
121            "keccak224" | "keccak-224" => Ok(Algorithm::Keccak224),
122            "keccak256" | "keccak-256" => Ok(Algorithm::Keccak256),
123            "keccak384" | "keccak-384" => Ok(Algorithm::Keccak384),
124            "keccak512" | "keccak-512" => Ok(Algorithm::Keccak512),
125            "kangarootwelve" | "kt128" | "k12" => Ok(Algorithm::Kt128),
126            "kt256" => Ok(Algorithm::Kt256),
127            "turboshake128" | "turboshake-128" => Ok(Algorithm::TurboShake128),
128            "turboshake256" | "turboshake-256" => Ok(Algorithm::TurboShake256),
129            "kmac128" | "kmac-128" => Ok(Algorithm::Kmac128),
130            "kmac256" | "kmac-256" => Ok(Algorithm::Kmac256),
131            "tuplehash128" | "tuplehash-128" => Ok(Algorithm::TupleHash128),
132            "tuplehash256" | "tuplehash-256" => Ok(Algorithm::TupleHash256),
133            "parallelhash128" | "parallelhash-128" => Ok(Algorithm::ParallelHash128),
134            "parallelhash256" | "parallelhash-256" => Ok(Algorithm::ParallelHash256),
135
136            // AEAD algorithms
137            "saturnin" => Ok(Algorithm::Saturnin),
138            "shake256aead" | "shake256-aead" => Ok(Algorithm::Shake256Aead),
139            "duplexspongeaead" | "duplex-sponge-aead" => Ok(Algorithm::DuplexSpongeAead),
140            "tweakaead" | "tweak-aead" => Ok(Algorithm::TweakAead),
141            "romulus-n" | "romulusn" => Ok(Algorithm::RomulusN),
142            "romulus-m" | "romulusm" => Ok(Algorithm::RomulusM),
143            "rocca-s" | "roccas" => Ok(Algorithm::RoccaS),
144
145            _ => Err(crate::error::Error::UnsupportedAlgorithm {
146                algorithm: algorithm_str.to_string(),
147            }),
148        }
149    }
150
151    /// Convert Algorithm enum to string
152    ///
153    /// This function provides consistent algorithm naming for JavaScript
154    pub fn algorithm_to_string(algorithm: Algorithm) -> String {
155        match algorithm {
156            // KEM algorithms
157            Algorithm::MlKem512 => "ml-kem-512".to_string(),
158            Algorithm::MlKem768 => "ml-kem-768".to_string(),
159            Algorithm::MlKem1024 => "ml-kem-1024".to_string(),
160            // Signature algorithms
161            Algorithm::MlDsa44 => "ml-dsa-44".to_string(),
162            Algorithm::MlDsa65 => "ml-dsa-65".to_string(),
163            Algorithm::MlDsa87 => "ml-dsa-87".to_string(),
164            Algorithm::FnDsa => "fn-dsa".to_string(),
165
166            Algorithm::SlhDsaSha256128fRobust => "slh-dsa-sha256-128f-robust".to_string(),
167            Algorithm::SlhDsaSha256192fRobust => "slh-dsa-sha256-192f-robust".to_string(),
168            Algorithm::SlhDsaSha256256fRobust => "slh-dsa-sha256-256f-robust".to_string(),
169            Algorithm::SlhDsaShake256128fRobust => "slh-dsa-shake256-128f-robust".to_string(),
170            Algorithm::SlhDsaShake256192fRobust => "slh-dsa-shake256-192f-robust".to_string(),
171            Algorithm::SlhDsaShake256256fRobust => "slh-dsa-shake256-256f-robust".to_string(),
172
173            // Hash algorithms
174            Algorithm::Sha3_224 => "sha3-224".to_string(),
175            Algorithm::Sha3_256 => "sha3-256".to_string(),
176            Algorithm::Sha3_384 => "sha3-384".to_string(),
177            Algorithm::Sha3_512 => "sha3-512".to_string(),
178            Algorithm::Shake128 => "shake128".to_string(),
179            Algorithm::Shake256 => "shake256".to_string(),
180            Algorithm::Sha224 => "sha-224".to_string(),
181            Algorithm::Sha256 => "sha-256".to_string(),
182            Algorithm::Sha384 => "sha-384".to_string(),
183            Algorithm::Sha512 => "sha-512".to_string(),
184            Algorithm::Sha512_224 => "sha-512/224".to_string(),
185            Algorithm::Sha512_256 => "sha-512/256".to_string(),
186            Algorithm::CShake128 => "cshake128".to_string(),
187            Algorithm::CShake256 => "cshake256".to_string(),
188            Algorithm::Keccak224 => "keccak-224".to_string(),
189            Algorithm::Keccak256 => "keccak-256".to_string(),
190            Algorithm::Keccak384 => "keccak-384".to_string(),
191            Algorithm::Keccak512 => "keccak-512".to_string(),
192            Algorithm::Kt128 => "kt128".to_string(),
193            Algorithm::Kt256 => "kt256".to_string(),
194            Algorithm::TurboShake128 => "turboshake128".to_string(),
195            Algorithm::TurboShake256 => "turboshake256".to_string(),
196            Algorithm::Kmac128 => "kmac128".to_string(),
197            Algorithm::Kmac256 => "kmac256".to_string(),
198            Algorithm::TupleHash128 => "tuplehash128".to_string(),
199            Algorithm::TupleHash256 => "tuplehash256".to_string(),
200            Algorithm::ParallelHash128 => "parallelhash128".to_string(),
201            Algorithm::ParallelHash256 => "parallelhash256".to_string(),
202
203            // AEAD algorithms
204            Algorithm::Saturnin => "saturnin".to_string(),
205            Algorithm::Shake256Aead => "shake256-aead".to_string(),
206            Algorithm::DuplexSpongeAead => "duplex-sponge-aead".to_string(),
207            Algorithm::TweakAead => "tweak-aead".to_string(),
208            Algorithm::RomulusN => "romulus-n".to_string(),
209            Algorithm::RomulusM => "romulus-m".to_string(),
210            Algorithm::RoccaS => "rocca-s".to_string(),
211
212            // Other algorithms (add as needed)
213            _ => format!("{:?}", algorithm).to_lowercase().replace('_', "-"),
214        }
215    }
216
217    /// Convert KEM keypair to JavaScript object
218    ///
219    /// This function securely serializes keypair data for JavaScript consumption
220    pub fn kem_keypair_to_js(public_key: &[u8], secret_key: &[u8]) -> Result<JsValue> {
221        let result = serde_json::json!({
222            "public_key": public_key,
223            "secret_key": secret_key,
224            "algorithm": "kem"
225        });
226
227        serde_wasm_bindgen::to_value(&result).map_err(|e| crate::error::Error::NotImplemented {
228            feature: format!("Serialization error: {:?}", e),
229        })
230    }
231
232    /// Convert signature keypair to JavaScript object
233    ///
234    /// This function securely serializes keypair data for JavaScript consumption
235    pub fn sig_keypair_to_js(public_key: &[u8], secret_key: &[u8]) -> Result<JsValue> {
236        let result = serde_json::json!({
237            "public_key": public_key,
238            "secret_key": secret_key,
239            "algorithm": "signature"
240        });
241
242        serde_wasm_bindgen::to_value(&result).map_err(|e| crate::error::Error::NotImplemented {
243            feature: format!("Serialization error: {:?}", e),
244        })
245    }
246
247    /// Convert hash result to JavaScript object
248    ///
249    /// This function securely serializes hash data for JavaScript consumption
250    pub fn hash_result_to_js(hash: &[u8], algorithm: Algorithm) -> Result<JsValue> {
251        let result = serde_json::json!({
252            "hash": hash,
253            "algorithm": Self::algorithm_to_string(algorithm),
254            "length": hash.len()
255        });
256
257        serde_wasm_bindgen::to_value(&result).map_err(|e| crate::error::Error::NotImplemented {
258            feature: format!("Serialization error: {:?}", e),
259        })
260    }
261
262    /// Convert error to JavaScript error
263    ///
264    /// This function provides secure error conversion that doesn't leak sensitive information
265    pub fn error_to_js(error: &crate::error::Error) -> JsValue {
266        // Only expose safe error information to JavaScript
267        let safe_message = match error {
268            crate::error::Error::NotImplemented { feature } => {
269                format!("Feature not implemented: {}", feature)
270            }
271            crate::error::Error::InvalidAlgorithm { algorithm } => {
272                format!("Invalid algorithm: {}", algorithm)
273            }
274            crate::error::Error::InvalidKey { key_type, reason } => {
275                format!("Invalid {}: {}", key_type, reason)
276            }
277            crate::error::Error::InvalidMessageSize { max, actual } => {
278                format!("Message size {} exceeds maximum {}", actual, max)
279            }
280            crate::error::Error::InvalidNonceSize { expected, actual } => {
281                format!("Nonce size {} does not match expected {}", actual, expected)
282            }
283            _ => "Cryptographic operation failed".to_string(),
284        };
285
286        JsValue::from_str(&safe_message)
287    }
288}
289
290/// Canonical WASM signature algorithm id strings for listings (`WasmSignatureContext`,
291/// `WasmProviderManager`, JSON summaries). Matches [`WasmConversions::algorithm_to_string`] for SLH.
292#[cfg(feature = "wasm")]
293pub const WASM_SIGNATURE_ALGORITHM_IDS: &[&str] = &[
294    "ml-dsa-44",
295    "ml-dsa-65",
296    "ml-dsa-87",
297    "fn-dsa",
298    "slh-dsa-sha256-128f-robust",
299    "slh-dsa-sha256-192f-robust",
300    "slh-dsa-sha256-256f-robust",
301    "slh-dsa-shake256-128f-robust",
302    "slh-dsa-shake256-192f-robust",
303    "slh-dsa-shake256-256f-robust",
304];
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_algorithm_conversion() {
312        // Test string to algorithm conversion
313        assert_eq!(
314            WasmConversions::string_to_algorithm("ml-kem-512").unwrap(),
315            Algorithm::MlKem512
316        );
317        assert_eq!(
318            WasmConversions::string_to_algorithm("ML-KEM-512").unwrap(),
319            Algorithm::MlKem512
320        );
321        assert_eq!(
322            WasmConversions::string_to_algorithm("sha3-256").unwrap(),
323            Algorithm::Sha3_256
324        );
325
326        // Test unsupported algorithm
327        assert!(WasmConversions::string_to_algorithm("unsupported").is_err());
328
329        // Test algorithm to string conversion
330        assert_eq!(
331            WasmConversions::algorithm_to_string(Algorithm::MlKem512),
332            "ml-kem-512"
333        );
334        assert_eq!(
335            WasmConversions::algorithm_to_string(Algorithm::Sha3_256),
336            "sha3-256"
337        );
338
339        assert_eq!(
340            WasmConversions::string_to_algorithm("slh-dsa-shake256-128f-robust").unwrap(),
341            Algorithm::SlhDsaShake256128fRobust
342        );
343        assert_eq!(
344            WasmConversions::string_to_algorithm("SLH-DSA-SHAKE256-128f-Robust").unwrap(),
345            Algorithm::SlhDsaShake256128fRobust
346        );
347        assert_eq!(
348            WasmConversions::string_to_algorithm("SlhDsaShake256128fRobust").unwrap(),
349            Algorithm::SlhDsaShake256128fRobust
350        );
351        assert_eq!(
352            WasmConversions::string_to_algorithm("slh-dsa-sha256-128f-robust").unwrap(),
353            Algorithm::SlhDsaSha256128fRobust
354        );
355        assert_eq!(
356            WasmConversions::algorithm_to_string(Algorithm::SlhDsaShake256128fRobust),
357            "slh-dsa-shake256-128f-robust"
358        );
359    }
360
361    #[test]
362    #[cfg(target_arch = "wasm32")]
363    fn test_error_conversion() {
364        let error = crate::error::Error::NotImplemented {
365            feature: "test feature".to_string(),
366        };
367        let js_error = WasmConversions::error_to_js(&error);
368        // In a real WASM environment, we would test the JsValue
369        // For now, we just ensure the function doesn't panic
370        assert!(!js_error.is_undefined());
371    }
372}