1#[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#[cfg(feature = "wasm")]
32pub struct WasmConversions;
33
34#[cfg(feature = "wasm")]
35impl WasmConversions {
36 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 pub fn uint8array_to_vec(array: &Uint8Array) -> Result<Vec<u8>> {
52 let length = array.length() as usize;
53
54 const MAX_SIZE: usize = 1024 * 1024; 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 pub fn string_to_algorithm(algorithm_str: &str) -> Result<Algorithm> {
75 match algorithm_str.to_lowercase().as_str() {
76 "mlkem512" | "ml-kem-512" => Ok(Algorithm::MlKem512),
78 "mlkem768" | "ml-kem-768" => Ok(Algorithm::MlKem768),
79 "mlkem1024" | "ml-kem-1024" => Ok(Algorithm::MlKem1024),
80 "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-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 "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 "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 pub fn algorithm_to_string(algorithm: Algorithm) -> String {
155 match algorithm {
156 Algorithm::MlKem512 => "ml-kem-512".to_string(),
158 Algorithm::MlKem768 => "ml-kem-768".to_string(),
159 Algorithm::MlKem1024 => "ml-kem-1024".to_string(),
160 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 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 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 _ => format!("{:?}", algorithm).to_lowercase().replace('_', "-"),
214 }
215 }
216
217 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 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 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 pub fn error_to_js(error: &crate::error::Error) -> JsValue {
266 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#[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 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 assert!(WasmConversions::string_to_algorithm("unsupported").is_err());
328
329 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 assert!(!js_error.is_undefined());
371 }
372}