1use crate::module_exports::{ModuleContext, ModuleExports, ModuleFunction, ModuleParam};
9use shape_value::ValueWord;
10use std::sync::Arc;
11
12pub fn create_crypto_module() -> ModuleExports {
14 let mut module = ModuleExports::new("crypto");
15 module.description = "Cryptographic hashing and encoding utilities".to_string();
16
17 module.add_function_with_schema(
19 "sha256",
20 |args: &[ValueWord], _ctx: &ModuleContext| {
21 use sha2::{Digest, Sha256};
22
23 let data = args
24 .first()
25 .and_then(|a| a.as_str())
26 .ok_or_else(|| "crypto.sha256() requires a string argument".to_string())?;
27
28 let mut hasher = Sha256::new();
29 hasher.update(data.as_bytes());
30 let result = hasher.finalize();
31 Ok(ValueWord::from_string(Arc::new(hex::encode(result))))
32 },
33 ModuleFunction {
34 description: "Compute the SHA-256 hash of a string, returning a hex-encoded digest"
35 .to_string(),
36 params: vec![ModuleParam {
37 name: "data".to_string(),
38 type_name: "string".to_string(),
39 required: true,
40 description: "Data to hash".to_string(),
41 ..Default::default()
42 }],
43 return_type: Some("string".to_string()),
44 },
45 );
46
47 module.add_function_with_schema(
49 "hmac_sha256",
50 |args: &[ValueWord], _ctx: &ModuleContext| {
51 use hmac::{Hmac, Mac};
52 use sha2::Sha256;
53
54 let data = args.first().and_then(|a| a.as_str()).ok_or_else(|| {
55 "crypto.hmac_sha256() requires a data string argument".to_string()
56 })?;
57
58 let key = args
59 .get(1)
60 .and_then(|a| a.as_str())
61 .ok_or_else(|| "crypto.hmac_sha256() requires a key string argument".to_string())?;
62
63 type HmacSha256 = Hmac<Sha256>;
64 let mut mac = HmacSha256::new_from_slice(key.as_bytes())
65 .map_err(|e| format!("crypto.hmac_sha256() key error: {}", e))?;
66 mac.update(data.as_bytes());
67 let result = mac.finalize();
68 Ok(ValueWord::from_string(Arc::new(hex::encode(
69 result.into_bytes(),
70 ))))
71 },
72 ModuleFunction {
73 description: "Compute HMAC-SHA256 of data with the given key, returning hex digest"
74 .to_string(),
75 params: vec![
76 ModuleParam {
77 name: "data".to_string(),
78 type_name: "string".to_string(),
79 required: true,
80 description: "Data to authenticate".to_string(),
81 ..Default::default()
82 },
83 ModuleParam {
84 name: "key".to_string(),
85 type_name: "string".to_string(),
86 required: true,
87 description: "HMAC key".to_string(),
88 ..Default::default()
89 },
90 ],
91 return_type: Some("string".to_string()),
92 },
93 );
94
95 module.add_function_with_schema(
97 "base64_encode",
98 |args: &[ValueWord], _ctx: &ModuleContext| {
99 use base64::Engine;
100
101 let data = args
102 .first()
103 .and_then(|a| a.as_str())
104 .ok_or_else(|| "crypto.base64_encode() requires a string argument".to_string())?;
105
106 let encoded = base64::engine::general_purpose::STANDARD.encode(data.as_bytes());
107 Ok(ValueWord::from_string(Arc::new(encoded)))
108 },
109 ModuleFunction {
110 description: "Encode a string to Base64".to_string(),
111 params: vec![ModuleParam {
112 name: "data".to_string(),
113 type_name: "string".to_string(),
114 required: true,
115 description: "Data to encode".to_string(),
116 ..Default::default()
117 }],
118 return_type: Some("string".to_string()),
119 },
120 );
121
122 module.add_function_with_schema(
124 "base64_decode",
125 |args: &[ValueWord], _ctx: &ModuleContext| {
126 use base64::Engine;
127
128 let encoded = args
129 .first()
130 .and_then(|a| a.as_str())
131 .ok_or_else(|| "crypto.base64_decode() requires a string argument".to_string())?;
132
133 let bytes = base64::engine::general_purpose::STANDARD
134 .decode(encoded)
135 .map_err(|e| format!("crypto.base64_decode() failed: {}", e))?;
136
137 let decoded = String::from_utf8(bytes)
138 .map_err(|e| format!("crypto.base64_decode() invalid UTF-8: {}", e))?;
139
140 Ok(ValueWord::from_ok(ValueWord::from_string(Arc::new(
141 decoded,
142 ))))
143 },
144 ModuleFunction {
145 description: "Decode a Base64 string".to_string(),
146 params: vec![ModuleParam {
147 name: "encoded".to_string(),
148 type_name: "string".to_string(),
149 required: true,
150 description: "Base64-encoded string to decode".to_string(),
151 ..Default::default()
152 }],
153 return_type: Some("Result<string>".to_string()),
154 },
155 );
156
157 module.add_function_with_schema(
159 "hex_encode",
160 |args: &[ValueWord], _ctx: &ModuleContext| {
161 let data = args
162 .first()
163 .and_then(|a| a.as_str())
164 .ok_or_else(|| "crypto.hex_encode() requires a string argument".to_string())?;
165
166 Ok(ValueWord::from_string(Arc::new(hex::encode(
167 data.as_bytes(),
168 ))))
169 },
170 ModuleFunction {
171 description: "Encode a string as hexadecimal".to_string(),
172 params: vec![ModuleParam {
173 name: "data".to_string(),
174 type_name: "string".to_string(),
175 required: true,
176 description: "Data to hex-encode".to_string(),
177 ..Default::default()
178 }],
179 return_type: Some("string".to_string()),
180 },
181 );
182
183 module.add_function_with_schema(
185 "hex_decode",
186 |args: &[ValueWord], _ctx: &ModuleContext| {
187 let hex_str = args
188 .first()
189 .and_then(|a| a.as_str())
190 .ok_or_else(|| "crypto.hex_decode() requires a string argument".to_string())?;
191
192 let bytes =
193 hex::decode(hex_str).map_err(|e| format!("crypto.hex_decode() failed: {}", e))?;
194
195 let decoded = String::from_utf8(bytes)
196 .map_err(|e| format!("crypto.hex_decode() invalid UTF-8: {}", e))?;
197
198 Ok(ValueWord::from_ok(ValueWord::from_string(Arc::new(
199 decoded,
200 ))))
201 },
202 ModuleFunction {
203 description: "Decode a hexadecimal string".to_string(),
204 params: vec![ModuleParam {
205 name: "hex".to_string(),
206 type_name: "string".to_string(),
207 required: true,
208 description: "Hex-encoded string to decode".to_string(),
209 ..Default::default()
210 }],
211 return_type: Some("Result<string>".to_string()),
212 },
213 );
214
215 module.add_function_with_schema(
217 "sha512",
218 |args: &[ValueWord], _ctx: &ModuleContext| {
219 use sha2::{Digest, Sha512};
220
221 let data = args
222 .first()
223 .and_then(|a| a.as_str())
224 .ok_or_else(|| "crypto.sha512() requires a string argument".to_string())?;
225
226 let mut hasher = Sha512::new();
227 hasher.update(data.as_bytes());
228 let result = hasher.finalize();
229 Ok(ValueWord::from_string(Arc::new(hex::encode(result))))
230 },
231 ModuleFunction {
232 description: "Compute the SHA-512 hash of a string, returning a hex-encoded digest"
233 .to_string(),
234 params: vec![ModuleParam {
235 name: "data".to_string(),
236 type_name: "string".to_string(),
237 required: true,
238 description: "Data to hash".to_string(),
239 ..Default::default()
240 }],
241 return_type: Some("string".to_string()),
242 },
243 );
244
245 module.add_function_with_schema(
247 "sha1",
248 |args: &[ValueWord], _ctx: &ModuleContext| {
249 use sha1::Digest;
250
251 let data = args
252 .first()
253 .and_then(|a| a.as_str())
254 .ok_or_else(|| "crypto.sha1() requires a string argument".to_string())?;
255
256 let mut hasher = sha1::Sha1::new();
257 hasher.update(data.as_bytes());
258 let result = hasher.finalize();
259 Ok(ValueWord::from_string(Arc::new(hex::encode(result))))
260 },
261 ModuleFunction {
262 description:
263 "Compute the SHA-1 hash of a string, returning a hex-encoded digest (legacy)"
264 .to_string(),
265 params: vec![ModuleParam {
266 name: "data".to_string(),
267 type_name: "string".to_string(),
268 required: true,
269 description: "Data to hash".to_string(),
270 ..Default::default()
271 }],
272 return_type: Some("string".to_string()),
273 },
274 );
275
276 module.add_function_with_schema(
278 "md5",
279 |args: &[ValueWord], _ctx: &ModuleContext| {
280 use md5::Digest;
281
282 let data = args
283 .first()
284 .and_then(|a| a.as_str())
285 .ok_or_else(|| "crypto.md5() requires a string argument".to_string())?;
286
287 let mut hasher = md5::Md5::new();
288 hasher.update(data.as_bytes());
289 let result = hasher.finalize();
290 Ok(ValueWord::from_string(Arc::new(hex::encode(result))))
291 },
292 ModuleFunction {
293 description:
294 "Compute the MD5 hash of a string, returning a hex-encoded digest (legacy)"
295 .to_string(),
296 params: vec![ModuleParam {
297 name: "data".to_string(),
298 type_name: "string".to_string(),
299 required: true,
300 description: "Data to hash".to_string(),
301 ..Default::default()
302 }],
303 return_type: Some("string".to_string()),
304 },
305 );
306
307 module.add_function_with_schema(
309 "random_bytes",
310 |args: &[ValueWord], _ctx: &ModuleContext| {
311 use rand::RngCore;
312
313 let n = args
314 .first()
315 .and_then(|a| a.as_i64())
316 .ok_or_else(|| "crypto.random_bytes() requires an int argument".to_string())?;
317
318 if n < 0 || n > 65536 {
319 return Err(
320 "crypto.random_bytes() n must be between 0 and 65536".to_string()
321 );
322 }
323
324 let mut buf = vec![0u8; n as usize];
325 rand::thread_rng().fill_bytes(&mut buf);
326 Ok(ValueWord::from_string(Arc::new(hex::encode(buf))))
327 },
328 ModuleFunction {
329 description: "Generate n random bytes, returned as a hex-encoded string".to_string(),
330 params: vec![ModuleParam {
331 name: "n".to_string(),
332 type_name: "int".to_string(),
333 required: true,
334 description: "Number of random bytes to generate (0..65536)".to_string(),
335 ..Default::default()
336 }],
337 return_type: Some("string".to_string()),
338 },
339 );
340
341 module.add_function_with_schema(
343 "ed25519_generate_keypair",
344 |_args: &[ValueWord], _ctx: &ModuleContext| {
345 use rand::RngCore;
346
347 let mut secret = [0u8; 32];
348 rand::thread_rng().fill_bytes(&mut secret);
349 let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret);
350 let verifying_key = signing_key.verifying_key();
351
352 let keys = vec![
353 ValueWord::from_string(Arc::new("public_key".to_string())),
354 ValueWord::from_string(Arc::new("secret_key".to_string())),
355 ];
356 let values = vec![
357 ValueWord::from_string(Arc::new(hex::encode(verifying_key.to_bytes()))),
358 ValueWord::from_string(Arc::new(hex::encode(signing_key.to_bytes()))),
359 ];
360 Ok(ValueWord::from_hashmap_pairs(keys, values))
361 },
362 ModuleFunction {
363 description: "Generate an Ed25519 keypair, returning an object with hex-encoded public_key and secret_key"
364 .to_string(),
365 params: vec![],
366 return_type: Some("object".to_string()),
367 },
368 );
369
370 module.add_function_with_schema(
372 "ed25519_sign",
373 |args: &[ValueWord], _ctx: &ModuleContext| {
374 use ed25519_dalek::Signer;
375
376 let message = args.first().and_then(|a| a.as_str()).ok_or_else(|| {
377 "crypto.ed25519_sign() requires a message string argument".to_string()
378 })?;
379
380 let secret_hex = args
381 .get(1)
382 .and_then(|a| a.as_str())
383 .ok_or_else(|| {
384 "crypto.ed25519_sign() requires a secret_key hex string argument".to_string()
385 })?;
386
387 let secret_bytes = hex::decode(secret_hex)
388 .map_err(|e| format!("crypto.ed25519_sign() invalid secret_key hex: {}", e))?;
389
390 let secret_arr: [u8; 32] = secret_bytes.as_slice().try_into().map_err(|_| {
391 format!(
392 "crypto.ed25519_sign() secret_key must be 32 bytes (got {})",
393 secret_bytes.len()
394 )
395 })?;
396
397 let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret_arr);
398 let signature = signing_key.sign(message.as_bytes());
399 Ok(ValueWord::from_string(Arc::new(hex::encode(
400 signature.to_bytes(),
401 ))))
402 },
403 ModuleFunction {
404 description: "Sign a message with an Ed25519 secret key, returning a hex-encoded signature"
405 .to_string(),
406 params: vec![
407 ModuleParam {
408 name: "message".to_string(),
409 type_name: "string".to_string(),
410 required: true,
411 description: "Message to sign".to_string(),
412 ..Default::default()
413 },
414 ModuleParam {
415 name: "secret_key".to_string(),
416 type_name: "string".to_string(),
417 required: true,
418 description: "Hex-encoded 32-byte Ed25519 secret key".to_string(),
419 ..Default::default()
420 },
421 ],
422 return_type: Some("string".to_string()),
423 },
424 );
425
426 module.add_function_with_schema(
428 "ed25519_verify",
429 |args: &[ValueWord], _ctx: &ModuleContext| {
430 use ed25519_dalek::Verifier;
431
432 let message = args.first().and_then(|a| a.as_str()).ok_or_else(|| {
433 "crypto.ed25519_verify() requires a message string argument".to_string()
434 })?;
435
436 let sig_hex = args.get(1).and_then(|a| a.as_str()).ok_or_else(|| {
437 "crypto.ed25519_verify() requires a signature hex string argument".to_string()
438 })?;
439
440 let pub_hex = args.get(2).and_then(|a| a.as_str()).ok_or_else(|| {
441 "crypto.ed25519_verify() requires a public_key hex string argument".to_string()
442 })?;
443
444 let sig_bytes = hex::decode(sig_hex)
445 .map_err(|e| format!("crypto.ed25519_verify() invalid signature hex: {}", e))?;
446
447 let sig_arr: [u8; 64] = sig_bytes.as_slice().try_into().map_err(|_| {
448 format!(
449 "crypto.ed25519_verify() signature must be 64 bytes (got {})",
450 sig_bytes.len()
451 )
452 })?;
453
454 let pub_bytes = hex::decode(pub_hex)
455 .map_err(|e| format!("crypto.ed25519_verify() invalid public_key hex: {}", e))?;
456
457 let pub_arr: [u8; 32] = pub_bytes.as_slice().try_into().map_err(|_| {
458 format!(
459 "crypto.ed25519_verify() public_key must be 32 bytes (got {})",
460 pub_bytes.len()
461 )
462 })?;
463
464 let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&pub_arr)
465 .map_err(|e| format!("crypto.ed25519_verify() invalid public key: {}", e))?;
466
467 let signature = ed25519_dalek::Signature::from_bytes(&sig_arr);
468 let valid = verifying_key.verify(message.as_bytes(), &signature).is_ok();
469 Ok(ValueWord::from_bool(valid))
470 },
471 ModuleFunction {
472 description: "Verify an Ed25519 signature against a message and public key".to_string(),
473 params: vec![
474 ModuleParam {
475 name: "message".to_string(),
476 type_name: "string".to_string(),
477 required: true,
478 description: "Message that was signed".to_string(),
479 ..Default::default()
480 },
481 ModuleParam {
482 name: "signature".to_string(),
483 type_name: "string".to_string(),
484 required: true,
485 description: "Hex-encoded 64-byte Ed25519 signature".to_string(),
486 ..Default::default()
487 },
488 ModuleParam {
489 name: "public_key".to_string(),
490 type_name: "string".to_string(),
491 required: true,
492 description: "Hex-encoded 32-byte Ed25519 public key".to_string(),
493 ..Default::default()
494 },
495 ],
496 return_type: Some("bool".to_string()),
497 },
498 );
499
500 module
501}
502
503#[cfg(test)]
504mod tests {
505 use super::*;
506
507 fn test_ctx() -> crate::module_exports::ModuleContext<'static> {
508 let registry = Box::leak(Box::new(crate::type_schema::TypeSchemaRegistry::new()));
509 crate::module_exports::ModuleContext {
510 schemas: registry,
511 invoke_callable: None,
512 raw_invoker: None,
513 function_hashes: None,
514 vm_state: None,
515 granted_permissions: None,
516 scope_constraints: None,
517 set_pending_resume: None,
518 set_pending_frame_resume: None,
519 }
520 }
521
522 #[test]
523 fn test_crypto_module_creation() {
524 let module = create_crypto_module();
525 assert_eq!(module.name, "crypto");
526 assert!(module.has_export("sha256"));
527 assert!(module.has_export("hmac_sha256"));
528 assert!(module.has_export("base64_encode"));
529 assert!(module.has_export("base64_decode"));
530 assert!(module.has_export("hex_encode"));
531 assert!(module.has_export("hex_decode"));
532 }
533
534 #[test]
535 fn test_sha256_known_digest() {
536 let module = create_crypto_module();
537 let ctx = test_ctx();
538 let sha_fn = module.get_export("sha256").unwrap();
539 let result = sha_fn(
540 &[ValueWord::from_string(Arc::new("hello".to_string()))],
541 &ctx,
542 )
543 .unwrap();
544 assert_eq!(
546 result.as_str(),
547 Some("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
548 );
549 }
550
551 #[test]
552 fn test_sha256_empty_string() {
553 let module = create_crypto_module();
554 let ctx = test_ctx();
555 let sha_fn = module.get_export("sha256").unwrap();
556 let result = sha_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
557 assert_eq!(
558 result.as_str(),
559 Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
560 );
561 }
562
563 #[test]
564 fn test_sha256_requires_string() {
565 let module = create_crypto_module();
566 let ctx = test_ctx();
567 let sha_fn = module.get_export("sha256").unwrap();
568 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
569 }
570
571 #[test]
572 fn test_hmac_sha256() {
573 let module = create_crypto_module();
574 let ctx = test_ctx();
575 let hmac_fn = module.get_export("hmac_sha256").unwrap();
576 let result = hmac_fn(
577 &[
578 ValueWord::from_string(Arc::new("hello".to_string())),
579 ValueWord::from_string(Arc::new("secret".to_string())),
580 ],
581 &ctx,
582 )
583 .unwrap();
584 let digest = result.as_str().unwrap();
586 assert_eq!(digest.len(), 64); }
588
589 #[test]
590 fn test_hmac_sha256_requires_both_args() {
591 let module = create_crypto_module();
592 let ctx = test_ctx();
593 let hmac_fn = module.get_export("hmac_sha256").unwrap();
594 assert!(
595 hmac_fn(
596 &[ValueWord::from_string(Arc::new("data".to_string()))],
597 &ctx
598 )
599 .is_err()
600 );
601 assert!(hmac_fn(&[], &ctx).is_err());
602 }
603
604 #[test]
605 fn test_base64_roundtrip() {
606 let module = create_crypto_module();
607 let ctx = test_ctx();
608 let encode_fn = module.get_export("base64_encode").unwrap();
609 let decode_fn = module.get_export("base64_decode").unwrap();
610
611 let original = "Hello, World!";
612 let encoded = encode_fn(
613 &[ValueWord::from_string(Arc::new(original.to_string()))],
614 &ctx,
615 )
616 .unwrap();
617 assert_eq!(encoded.as_str(), Some("SGVsbG8sIFdvcmxkIQ=="));
618
619 let decoded = decode_fn(&[encoded], &ctx).unwrap();
620 let inner = decoded.as_ok_inner().expect("should be Ok");
621 assert_eq!(inner.as_str(), Some(original));
622 }
623
624 #[test]
625 fn test_base64_decode_invalid() {
626 let module = create_crypto_module();
627 let ctx = test_ctx();
628 let decode_fn = module.get_export("base64_decode").unwrap();
629 let result = decode_fn(&[ValueWord::from_string(Arc::new("!!!".to_string()))], &ctx);
630 assert!(result.is_err());
631 }
632
633 #[test]
634 fn test_hex_roundtrip() {
635 let module = create_crypto_module();
636 let ctx = test_ctx();
637 let encode_fn = module.get_export("hex_encode").unwrap();
638 let decode_fn = module.get_export("hex_decode").unwrap();
639
640 let original = "hello";
641 let encoded = encode_fn(
642 &[ValueWord::from_string(Arc::new(original.to_string()))],
643 &ctx,
644 )
645 .unwrap();
646 assert_eq!(encoded.as_str(), Some("68656c6c6f"));
647
648 let decoded = decode_fn(&[encoded], &ctx).unwrap();
649 let inner = decoded.as_ok_inner().expect("should be Ok");
650 assert_eq!(inner.as_str(), Some(original));
651 }
652
653 #[test]
654 fn test_hex_decode_invalid() {
655 let module = create_crypto_module();
656 let ctx = test_ctx();
657 let decode_fn = module.get_export("hex_decode").unwrap();
658 let result = decode_fn(
659 &[ValueWord::from_string(Arc::new("zzzz".to_string()))],
660 &ctx,
661 );
662 assert!(result.is_err());
663 }
664
665 #[test]
666 fn test_crypto_schemas() {
667 let module = create_crypto_module();
668
669 let sha_schema = module.get_schema("sha256").unwrap();
670 assert_eq!(sha_schema.params.len(), 1);
671 assert_eq!(sha_schema.return_type.as_deref(), Some("string"));
672
673 let hmac_schema = module.get_schema("hmac_sha256").unwrap();
674 assert_eq!(hmac_schema.params.len(), 2);
675 assert!(hmac_schema.params[0].required);
676 assert!(hmac_schema.params[1].required);
677
678 let b64d_schema = module.get_schema("base64_decode").unwrap();
679 assert_eq!(b64d_schema.return_type.as_deref(), Some("Result<string>"));
680 }
681
682 #[test]
683 fn test_crypto_module_has_new_exports() {
684 let module = create_crypto_module();
685 assert!(module.has_export("sha512"));
686 assert!(module.has_export("sha1"));
687 assert!(module.has_export("md5"));
688 assert!(module.has_export("random_bytes"));
689 assert!(module.has_export("ed25519_generate_keypair"));
690 assert!(module.has_export("ed25519_sign"));
691 assert!(module.has_export("ed25519_verify"));
692 }
693
694 #[test]
695 fn test_sha512_known_digest() {
696 let module = create_crypto_module();
697 let ctx = test_ctx();
698 let sha_fn = module.get_export("sha512").unwrap();
699 let result = sha_fn(
700 &[ValueWord::from_string(Arc::new("hello".to_string()))],
701 &ctx,
702 )
703 .unwrap();
704 assert_eq!(
706 result.as_str(),
707 Some("9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")
708 );
709 }
710
711 #[test]
712 fn test_sha512_empty_string() {
713 let module = create_crypto_module();
714 let ctx = test_ctx();
715 let sha_fn = module.get_export("sha512").unwrap();
716 let result = sha_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
717 assert_eq!(
719 result.as_str(),
720 Some("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")
721 );
722 }
723
724 #[test]
725 fn test_sha512_requires_string() {
726 let module = create_crypto_module();
727 let ctx = test_ctx();
728 let sha_fn = module.get_export("sha512").unwrap();
729 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
730 }
731
732 #[test]
733 fn test_sha1_known_digest() {
734 let module = create_crypto_module();
735 let ctx = test_ctx();
736 let sha_fn = module.get_export("sha1").unwrap();
737 let result = sha_fn(
738 &[ValueWord::from_string(Arc::new("hello".to_string()))],
739 &ctx,
740 )
741 .unwrap();
742 assert_eq!(
744 result.as_str(),
745 Some("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")
746 );
747 }
748
749 #[test]
750 fn test_sha1_empty_string() {
751 let module = create_crypto_module();
752 let ctx = test_ctx();
753 let sha_fn = module.get_export("sha1").unwrap();
754 let result = sha_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
755 assert_eq!(
756 result.as_str(),
757 Some("da39a3ee5e6b4b0d3255bfef95601890afd80709")
758 );
759 }
760
761 #[test]
762 fn test_sha1_requires_string() {
763 let module = create_crypto_module();
764 let ctx = test_ctx();
765 let sha_fn = module.get_export("sha1").unwrap();
766 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
767 }
768
769 #[test]
770 fn test_md5_known_digest() {
771 let module = create_crypto_module();
772 let ctx = test_ctx();
773 let md5_fn = module.get_export("md5").unwrap();
774 let result = md5_fn(
775 &[ValueWord::from_string(Arc::new("hello".to_string()))],
776 &ctx,
777 )
778 .unwrap();
779 assert_eq!(
781 result.as_str(),
782 Some("5d41402abc4b2a76b9719d911017c592")
783 );
784 }
785
786 #[test]
787 fn test_md5_empty_string() {
788 let module = create_crypto_module();
789 let ctx = test_ctx();
790 let md5_fn = module.get_export("md5").unwrap();
791 let result = md5_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
792 assert_eq!(
793 result.as_str(),
794 Some("d41d8cd98f00b204e9800998ecf8427e")
795 );
796 }
797
798 #[test]
799 fn test_md5_requires_string() {
800 let module = create_crypto_module();
801 let ctx = test_ctx();
802 let md5_fn = module.get_export("md5").unwrap();
803 assert!(md5_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
804 }
805
806 #[test]
807 fn test_random_bytes_length() {
808 let module = create_crypto_module();
809 let ctx = test_ctx();
810 let rb_fn = module.get_export("random_bytes").unwrap();
811 let result = rb_fn(&[ValueWord::from_i64(16)], &ctx).unwrap();
812 let hex_str = result.as_str().unwrap();
813 assert_eq!(hex_str.len(), 32);
815 }
816
817 #[test]
818 fn test_random_bytes_zero() {
819 let module = create_crypto_module();
820 let ctx = test_ctx();
821 let rb_fn = module.get_export("random_bytes").unwrap();
822 let result = rb_fn(&[ValueWord::from_i64(0)], &ctx).unwrap();
823 assert_eq!(result.as_str(), Some(""));
824 }
825
826 #[test]
827 fn test_random_bytes_negative_rejected() {
828 let module = create_crypto_module();
829 let ctx = test_ctx();
830 let rb_fn = module.get_export("random_bytes").unwrap();
831 assert!(rb_fn(&[ValueWord::from_i64(-1)], &ctx).is_err());
832 }
833
834 #[test]
835 fn test_random_bytes_too_large_rejected() {
836 let module = create_crypto_module();
837 let ctx = test_ctx();
838 let rb_fn = module.get_export("random_bytes").unwrap();
839 assert!(rb_fn(&[ValueWord::from_i64(65537)], &ctx).is_err());
840 }
841
842 #[test]
843 fn test_random_bytes_requires_int() {
844 let module = create_crypto_module();
845 let ctx = test_ctx();
846 let rb_fn = module.get_export("random_bytes").unwrap();
847 assert!(
848 rb_fn(
849 &[ValueWord::from_string(Arc::new("10".to_string()))],
850 &ctx
851 )
852 .is_err()
853 );
854 }
855
856 #[test]
857 fn test_ed25519_generate_keypair() {
858 let module = create_crypto_module();
859 let ctx = test_ctx();
860 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
861 let result = gen_fn(&[], &ctx).unwrap();
862
863 let hm = result.as_hashmap_data().expect("should be a HashMap");
865 let pub_key = hm.shape_get("public_key").expect("should have public_key");
866 let sec_key = hm.shape_get("secret_key").expect("should have secret_key");
867
868 assert_eq!(pub_key.as_str().unwrap().len(), 64);
870 assert_eq!(sec_key.as_str().unwrap().len(), 64);
871 }
872
873 #[test]
874 fn test_ed25519_sign_and_verify_roundtrip() {
875 let module = create_crypto_module();
876 let ctx = test_ctx();
877
878 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
880 let keypair = gen_fn(&[], &ctx).unwrap();
881 let hm = keypair.as_hashmap_data().unwrap();
882
883 let pub_key = hm.shape_get("public_key").unwrap().clone();
884 let sec_key = hm.shape_get("secret_key").unwrap().clone();
885
886 let message = ValueWord::from_string(Arc::new("test message".to_string()));
887
888 let sign_fn = module.get_export("ed25519_sign").unwrap();
890 let signature = sign_fn(&[message.clone(), sec_key], &ctx).unwrap();
891 assert_eq!(signature.as_str().unwrap().len(), 128);
893
894 let verify_fn = module.get_export("ed25519_verify").unwrap();
896 let valid = verify_fn(&[message, signature, pub_key], &ctx).unwrap();
897 assert_eq!(valid.as_bool(), Some(true));
898 }
899
900 #[test]
901 fn test_ed25519_verify_wrong_message() {
902 let module = create_crypto_module();
903 let ctx = test_ctx();
904
905 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
906 let keypair = gen_fn(&[], &ctx).unwrap();
907 let hm = keypair.as_hashmap_data().unwrap();
908
909 let pub_key = hm.shape_get("public_key").unwrap().clone();
910 let sec_key = hm.shape_get("secret_key").unwrap().clone();
911
912 let message = ValueWord::from_string(Arc::new("correct message".to_string()));
913 let wrong_message = ValueWord::from_string(Arc::new("wrong message".to_string()));
914
915 let sign_fn = module.get_export("ed25519_sign").unwrap();
916 let signature = sign_fn(&[message, sec_key], &ctx).unwrap();
917
918 let verify_fn = module.get_export("ed25519_verify").unwrap();
919 let valid = verify_fn(&[wrong_message, signature, pub_key], &ctx).unwrap();
920 assert_eq!(valid.as_bool(), Some(false));
921 }
922
923 #[test]
924 fn test_ed25519_sign_invalid_secret_key() {
925 let module = create_crypto_module();
926 let ctx = test_ctx();
927 let sign_fn = module.get_export("ed25519_sign").unwrap();
928
929 let result = sign_fn(
931 &[
932 ValueWord::from_string(Arc::new("msg".to_string())),
933 ValueWord::from_string(Arc::new("abcd".to_string())),
934 ],
935 &ctx,
936 );
937 assert!(result.is_err());
938
939 let result = sign_fn(
941 &[
942 ValueWord::from_string(Arc::new("msg".to_string())),
943 ValueWord::from_string(Arc::new("zzzz".to_string())),
944 ],
945 &ctx,
946 );
947 assert!(result.is_err());
948 }
949
950 #[test]
951 fn test_ed25519_verify_invalid_inputs() {
952 let module = create_crypto_module();
953 let ctx = test_ctx();
954 let verify_fn = module.get_export("ed25519_verify").unwrap();
955
956 assert!(
958 verify_fn(
959 &[ValueWord::from_string(Arc::new("msg".to_string()))],
960 &ctx
961 )
962 .is_err()
963 );
964
965 assert!(
967 verify_fn(
968 &[
969 ValueWord::from_string(Arc::new("msg".to_string())),
970 ValueWord::from_string(Arc::new("not_hex".to_string())),
971 ValueWord::from_string(Arc::new("ab".repeat(32))),
972 ],
973 &ctx
974 )
975 .is_err()
976 );
977 }
978
979 #[test]
980 fn test_new_function_schemas() {
981 let module = create_crypto_module();
982
983 let sha512_schema = module.get_schema("sha512").unwrap();
984 assert_eq!(sha512_schema.params.len(), 1);
985 assert_eq!(sha512_schema.return_type.as_deref(), Some("string"));
986
987 let sha1_schema = module.get_schema("sha1").unwrap();
988 assert_eq!(sha1_schema.params.len(), 1);
989 assert_eq!(sha1_schema.return_type.as_deref(), Some("string"));
990
991 let md5_schema = module.get_schema("md5").unwrap();
992 assert_eq!(md5_schema.params.len(), 1);
993 assert_eq!(md5_schema.return_type.as_deref(), Some("string"));
994
995 let rb_schema = module.get_schema("random_bytes").unwrap();
996 assert_eq!(rb_schema.params.len(), 1);
997 assert_eq!(rb_schema.params[0].type_name, "int");
998 assert_eq!(rb_schema.return_type.as_deref(), Some("string"));
999
1000 let gen_schema = module.get_schema("ed25519_generate_keypair").unwrap();
1001 assert_eq!(gen_schema.params.len(), 0);
1002 assert_eq!(gen_schema.return_type.as_deref(), Some("object"));
1003
1004 let sign_schema = module.get_schema("ed25519_sign").unwrap();
1005 assert_eq!(sign_schema.params.len(), 2);
1006 assert_eq!(sign_schema.return_type.as_deref(), Some("string"));
1007
1008 let verify_schema = module.get_schema("ed25519_verify").unwrap();
1009 assert_eq!(verify_schema.params.len(), 3);
1010 assert_eq!(verify_schema.return_type.as_deref(), Some("bool"));
1011 }
1012}