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("std::core::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 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Random)?;
312 use rand::RngCore;
313
314 let n = args
315 .first()
316 .and_then(|a| a.as_i64())
317 .ok_or_else(|| "crypto.random_bytes() requires an int argument".to_string())?;
318
319 if n < 0 || n > 65536 {
320 return Err("crypto.random_bytes() n must be between 0 and 65536".to_string());
321 }
322
323 let mut buf = vec![0u8; n as usize];
324 rand::thread_rng().fill_bytes(&mut buf);
325 Ok(ValueWord::from_string(Arc::new(hex::encode(buf))))
326 },
327 ModuleFunction {
328 description: "Generate n random bytes, returned as a hex-encoded string".to_string(),
329 params: vec![ModuleParam {
330 name: "n".to_string(),
331 type_name: "int".to_string(),
332 required: true,
333 description: "Number of random bytes to generate (0..65536)".to_string(),
334 ..Default::default()
335 }],
336 return_type: Some("string".to_string()),
337 },
338 );
339
340 module.add_function_with_schema(
342 "ed25519_generate_keypair",
343 |_args: &[ValueWord], ctx: &ModuleContext| {
344 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Random)?;
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.get(1).and_then(|a| a.as_str()).ok_or_else(|| {
381 "crypto.ed25519_sign() requires a secret_key hex string argument".to_string()
382 })?;
383
384 let secret_bytes = hex::decode(secret_hex)
385 .map_err(|e| format!("crypto.ed25519_sign() invalid secret_key hex: {}", e))?;
386
387 let secret_arr: [u8; 32] = secret_bytes.as_slice().try_into().map_err(|_| {
388 format!(
389 "crypto.ed25519_sign() secret_key must be 32 bytes (got {})",
390 secret_bytes.len()
391 )
392 })?;
393
394 let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret_arr);
395 let signature = signing_key.sign(message.as_bytes());
396 Ok(ValueWord::from_string(Arc::new(hex::encode(
397 signature.to_bytes(),
398 ))))
399 },
400 ModuleFunction {
401 description:
402 "Sign a message with an Ed25519 secret key, returning a hex-encoded signature"
403 .to_string(),
404 params: vec![
405 ModuleParam {
406 name: "message".to_string(),
407 type_name: "string".to_string(),
408 required: true,
409 description: "Message to sign".to_string(),
410 ..Default::default()
411 },
412 ModuleParam {
413 name: "secret_key".to_string(),
414 type_name: "string".to_string(),
415 required: true,
416 description: "Hex-encoded 32-byte Ed25519 secret key".to_string(),
417 ..Default::default()
418 },
419 ],
420 return_type: Some("string".to_string()),
421 },
422 );
423
424 module.add_function_with_schema(
426 "ed25519_verify",
427 |args: &[ValueWord], _ctx: &ModuleContext| {
428 use ed25519_dalek::Verifier;
429
430 let message = args.first().and_then(|a| a.as_str()).ok_or_else(|| {
431 "crypto.ed25519_verify() requires a message string argument".to_string()
432 })?;
433
434 let sig_hex = args.get(1).and_then(|a| a.as_str()).ok_or_else(|| {
435 "crypto.ed25519_verify() requires a signature hex string argument".to_string()
436 })?;
437
438 let pub_hex = args.get(2).and_then(|a| a.as_str()).ok_or_else(|| {
439 "crypto.ed25519_verify() requires a public_key hex string argument".to_string()
440 })?;
441
442 let sig_bytes = hex::decode(sig_hex)
443 .map_err(|e| format!("crypto.ed25519_verify() invalid signature hex: {}", e))?;
444
445 let sig_arr: [u8; 64] = sig_bytes.as_slice().try_into().map_err(|_| {
446 format!(
447 "crypto.ed25519_verify() signature must be 64 bytes (got {})",
448 sig_bytes.len()
449 )
450 })?;
451
452 let pub_bytes = hex::decode(pub_hex)
453 .map_err(|e| format!("crypto.ed25519_verify() invalid public_key hex: {}", e))?;
454
455 let pub_arr: [u8; 32] = pub_bytes.as_slice().try_into().map_err(|_| {
456 format!(
457 "crypto.ed25519_verify() public_key must be 32 bytes (got {})",
458 pub_bytes.len()
459 )
460 })?;
461
462 let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&pub_arr)
463 .map_err(|e| format!("crypto.ed25519_verify() invalid public key: {}", e))?;
464
465 let signature = ed25519_dalek::Signature::from_bytes(&sig_arr);
466 let valid = verifying_key.verify(message.as_bytes(), &signature).is_ok();
467 Ok(ValueWord::from_bool(valid))
468 },
469 ModuleFunction {
470 description: "Verify an Ed25519 signature against a message and public key".to_string(),
471 params: vec![
472 ModuleParam {
473 name: "message".to_string(),
474 type_name: "string".to_string(),
475 required: true,
476 description: "Message that was signed".to_string(),
477 ..Default::default()
478 },
479 ModuleParam {
480 name: "signature".to_string(),
481 type_name: "string".to_string(),
482 required: true,
483 description: "Hex-encoded 64-byte Ed25519 signature".to_string(),
484 ..Default::default()
485 },
486 ModuleParam {
487 name: "public_key".to_string(),
488 type_name: "string".to_string(),
489 required: true,
490 description: "Hex-encoded 32-byte Ed25519 public key".to_string(),
491 ..Default::default()
492 },
493 ],
494 return_type: Some("bool".to_string()),
495 },
496 );
497
498 module
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504
505 fn test_ctx() -> crate::module_exports::ModuleContext<'static> {
506 let registry = Box::leak(Box::new(crate::type_schema::TypeSchemaRegistry::new()));
507 crate::module_exports::ModuleContext {
508 schemas: registry,
509 invoke_callable: None,
510 raw_invoker: None,
511 function_hashes: None,
512 vm_state: None,
513 granted_permissions: None,
514 scope_constraints: None,
515 set_pending_resume: None,
516 set_pending_frame_resume: None,
517 }
518 }
519
520 #[test]
521 fn test_crypto_module_creation() {
522 let module = create_crypto_module();
523 assert_eq!(module.name, "std::core::crypto");
524 assert!(module.has_export("sha256"));
525 assert!(module.has_export("hmac_sha256"));
526 assert!(module.has_export("base64_encode"));
527 assert!(module.has_export("base64_decode"));
528 assert!(module.has_export("hex_encode"));
529 assert!(module.has_export("hex_decode"));
530 }
531
532 #[test]
533 fn test_sha256_known_digest() {
534 let module = create_crypto_module();
535 let ctx = test_ctx();
536 let sha_fn = module.get_export("sha256").unwrap();
537 let result = sha_fn(
538 &[ValueWord::from_string(Arc::new("hello".to_string()))],
539 &ctx,
540 )
541 .unwrap();
542 assert_eq!(
544 result.as_str(),
545 Some("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
546 );
547 }
548
549 #[test]
550 fn test_sha256_empty_string() {
551 let module = create_crypto_module();
552 let ctx = test_ctx();
553 let sha_fn = module.get_export("sha256").unwrap();
554 let result = sha_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
555 assert_eq!(
556 result.as_str(),
557 Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
558 );
559 }
560
561 #[test]
562 fn test_sha256_requires_string() {
563 let module = create_crypto_module();
564 let ctx = test_ctx();
565 let sha_fn = module.get_export("sha256").unwrap();
566 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
567 }
568
569 #[test]
570 fn test_hmac_sha256() {
571 let module = create_crypto_module();
572 let ctx = test_ctx();
573 let hmac_fn = module.get_export("hmac_sha256").unwrap();
574 let result = hmac_fn(
575 &[
576 ValueWord::from_string(Arc::new("hello".to_string())),
577 ValueWord::from_string(Arc::new("secret".to_string())),
578 ],
579 &ctx,
580 )
581 .unwrap();
582 let digest = result.as_str().unwrap();
584 assert_eq!(digest.len(), 64); }
586
587 #[test]
588 fn test_hmac_sha256_requires_both_args() {
589 let module = create_crypto_module();
590 let ctx = test_ctx();
591 let hmac_fn = module.get_export("hmac_sha256").unwrap();
592 assert!(
593 hmac_fn(
594 &[ValueWord::from_string(Arc::new("data".to_string()))],
595 &ctx
596 )
597 .is_err()
598 );
599 assert!(hmac_fn(&[], &ctx).is_err());
600 }
601
602 #[test]
603 fn test_base64_roundtrip() {
604 let module = create_crypto_module();
605 let ctx = test_ctx();
606 let encode_fn = module.get_export("base64_encode").unwrap();
607 let decode_fn = module.get_export("base64_decode").unwrap();
608
609 let original = "Hello, World!";
610 let encoded = encode_fn(
611 &[ValueWord::from_string(Arc::new(original.to_string()))],
612 &ctx,
613 )
614 .unwrap();
615 assert_eq!(encoded.as_str(), Some("SGVsbG8sIFdvcmxkIQ=="));
616
617 let decoded = decode_fn(&[encoded], &ctx).unwrap();
618 let inner = decoded.as_ok_inner().expect("should be Ok");
619 assert_eq!(inner.as_str(), Some(original));
620 }
621
622 #[test]
623 fn test_base64_decode_invalid() {
624 let module = create_crypto_module();
625 let ctx = test_ctx();
626 let decode_fn = module.get_export("base64_decode").unwrap();
627 let result = decode_fn(&[ValueWord::from_string(Arc::new("!!!".to_string()))], &ctx);
628 assert!(result.is_err());
629 }
630
631 #[test]
632 fn test_hex_roundtrip() {
633 let module = create_crypto_module();
634 let ctx = test_ctx();
635 let encode_fn = module.get_export("hex_encode").unwrap();
636 let decode_fn = module.get_export("hex_decode").unwrap();
637
638 let original = "hello";
639 let encoded = encode_fn(
640 &[ValueWord::from_string(Arc::new(original.to_string()))],
641 &ctx,
642 )
643 .unwrap();
644 assert_eq!(encoded.as_str(), Some("68656c6c6f"));
645
646 let decoded = decode_fn(&[encoded], &ctx).unwrap();
647 let inner = decoded.as_ok_inner().expect("should be Ok");
648 assert_eq!(inner.as_str(), Some(original));
649 }
650
651 #[test]
652 fn test_hex_decode_invalid() {
653 let module = create_crypto_module();
654 let ctx = test_ctx();
655 let decode_fn = module.get_export("hex_decode").unwrap();
656 let result = decode_fn(
657 &[ValueWord::from_string(Arc::new("zzzz".to_string()))],
658 &ctx,
659 );
660 assert!(result.is_err());
661 }
662
663 #[test]
664 fn test_crypto_schemas() {
665 let module = create_crypto_module();
666
667 let sha_schema = module.get_schema("sha256").unwrap();
668 assert_eq!(sha_schema.params.len(), 1);
669 assert_eq!(sha_schema.return_type.as_deref(), Some("string"));
670
671 let hmac_schema = module.get_schema("hmac_sha256").unwrap();
672 assert_eq!(hmac_schema.params.len(), 2);
673 assert!(hmac_schema.params[0].required);
674 assert!(hmac_schema.params[1].required);
675
676 let b64d_schema = module.get_schema("base64_decode").unwrap();
677 assert_eq!(b64d_schema.return_type.as_deref(), Some("Result<string>"));
678 }
679
680 #[test]
681 fn test_crypto_module_has_new_exports() {
682 let module = create_crypto_module();
683 assert!(module.has_export("sha512"));
684 assert!(module.has_export("sha1"));
685 assert!(module.has_export("md5"));
686 assert!(module.has_export("random_bytes"));
687 assert!(module.has_export("ed25519_generate_keypair"));
688 assert!(module.has_export("ed25519_sign"));
689 assert!(module.has_export("ed25519_verify"));
690 }
691
692 #[test]
693 fn test_sha512_known_digest() {
694 let module = create_crypto_module();
695 let ctx = test_ctx();
696 let sha_fn = module.get_export("sha512").unwrap();
697 let result = sha_fn(
698 &[ValueWord::from_string(Arc::new("hello".to_string()))],
699 &ctx,
700 )
701 .unwrap();
702 assert_eq!(
704 result.as_str(),
705 Some(
706 "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
707 )
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(
721 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
722 )
723 );
724 }
725
726 #[test]
727 fn test_sha512_requires_string() {
728 let module = create_crypto_module();
729 let ctx = test_ctx();
730 let sha_fn = module.get_export("sha512").unwrap();
731 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
732 }
733
734 #[test]
735 fn test_sha1_known_digest() {
736 let module = create_crypto_module();
737 let ctx = test_ctx();
738 let sha_fn = module.get_export("sha1").unwrap();
739 let result = sha_fn(
740 &[ValueWord::from_string(Arc::new("hello".to_string()))],
741 &ctx,
742 )
743 .unwrap();
744 assert_eq!(
746 result.as_str(),
747 Some("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")
748 );
749 }
750
751 #[test]
752 fn test_sha1_empty_string() {
753 let module = create_crypto_module();
754 let ctx = test_ctx();
755 let sha_fn = module.get_export("sha1").unwrap();
756 let result = sha_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
757 assert_eq!(
758 result.as_str(),
759 Some("da39a3ee5e6b4b0d3255bfef95601890afd80709")
760 );
761 }
762
763 #[test]
764 fn test_sha1_requires_string() {
765 let module = create_crypto_module();
766 let ctx = test_ctx();
767 let sha_fn = module.get_export("sha1").unwrap();
768 assert!(sha_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
769 }
770
771 #[test]
772 fn test_md5_known_digest() {
773 let module = create_crypto_module();
774 let ctx = test_ctx();
775 let md5_fn = module.get_export("md5").unwrap();
776 let result = md5_fn(
777 &[ValueWord::from_string(Arc::new("hello".to_string()))],
778 &ctx,
779 )
780 .unwrap();
781 assert_eq!(result.as_str(), Some("5d41402abc4b2a76b9719d911017c592"));
783 }
784
785 #[test]
786 fn test_md5_empty_string() {
787 let module = create_crypto_module();
788 let ctx = test_ctx();
789 let md5_fn = module.get_export("md5").unwrap();
790 let result = md5_fn(&[ValueWord::from_string(Arc::new(String::new()))], &ctx).unwrap();
791 assert_eq!(result.as_str(), Some("d41d8cd98f00b204e9800998ecf8427e"));
792 }
793
794 #[test]
795 fn test_md5_requires_string() {
796 let module = create_crypto_module();
797 let ctx = test_ctx();
798 let md5_fn = module.get_export("md5").unwrap();
799 assert!(md5_fn(&[ValueWord::from_f64(42.0)], &ctx).is_err());
800 }
801
802 #[test]
803 fn test_random_bytes_length() {
804 let module = create_crypto_module();
805 let ctx = test_ctx();
806 let rb_fn = module.get_export("random_bytes").unwrap();
807 let result = rb_fn(&[ValueWord::from_i64(16)], &ctx).unwrap();
808 let hex_str = result.as_str().unwrap();
809 assert_eq!(hex_str.len(), 32);
811 }
812
813 #[test]
814 fn test_random_bytes_zero() {
815 let module = create_crypto_module();
816 let ctx = test_ctx();
817 let rb_fn = module.get_export("random_bytes").unwrap();
818 let result = rb_fn(&[ValueWord::from_i64(0)], &ctx).unwrap();
819 assert_eq!(result.as_str(), Some(""));
820 }
821
822 #[test]
823 fn test_random_bytes_negative_rejected() {
824 let module = create_crypto_module();
825 let ctx = test_ctx();
826 let rb_fn = module.get_export("random_bytes").unwrap();
827 assert!(rb_fn(&[ValueWord::from_i64(-1)], &ctx).is_err());
828 }
829
830 #[test]
831 fn test_random_bytes_too_large_rejected() {
832 let module = create_crypto_module();
833 let ctx = test_ctx();
834 let rb_fn = module.get_export("random_bytes").unwrap();
835 assert!(rb_fn(&[ValueWord::from_i64(65537)], &ctx).is_err());
836 }
837
838 #[test]
839 fn test_random_bytes_requires_int() {
840 let module = create_crypto_module();
841 let ctx = test_ctx();
842 let rb_fn = module.get_export("random_bytes").unwrap();
843 assert!(rb_fn(&[ValueWord::from_string(Arc::new("10".to_string()))], &ctx).is_err());
844 }
845
846 #[test]
847 fn test_ed25519_generate_keypair() {
848 let module = create_crypto_module();
849 let ctx = test_ctx();
850 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
851 let result = gen_fn(&[], &ctx).unwrap();
852
853 let hm = result.as_hashmap_data().expect("should be a HashMap");
855 let pub_key = hm.shape_get("public_key").expect("should have public_key");
856 let sec_key = hm.shape_get("secret_key").expect("should have secret_key");
857
858 assert_eq!(pub_key.as_str().unwrap().len(), 64);
860 assert_eq!(sec_key.as_str().unwrap().len(), 64);
861 }
862
863 #[test]
864 fn test_ed25519_sign_and_verify_roundtrip() {
865 let module = create_crypto_module();
866 let ctx = test_ctx();
867
868 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
870 let keypair = gen_fn(&[], &ctx).unwrap();
871 let hm = keypair.as_hashmap_data().unwrap();
872
873 let pub_key = hm.shape_get("public_key").unwrap().clone();
874 let sec_key = hm.shape_get("secret_key").unwrap().clone();
875
876 let message = ValueWord::from_string(Arc::new("test message".to_string()));
877
878 let sign_fn = module.get_export("ed25519_sign").unwrap();
880 let signature = sign_fn(&[message.clone(), sec_key], &ctx).unwrap();
881 assert_eq!(signature.as_str().unwrap().len(), 128);
883
884 let verify_fn = module.get_export("ed25519_verify").unwrap();
886 let valid = verify_fn(&[message, signature, pub_key], &ctx).unwrap();
887 assert_eq!(valid.as_bool(), Some(true));
888 }
889
890 #[test]
891 fn test_ed25519_verify_wrong_message() {
892 let module = create_crypto_module();
893 let ctx = test_ctx();
894
895 let gen_fn = module.get_export("ed25519_generate_keypair").unwrap();
896 let keypair = gen_fn(&[], &ctx).unwrap();
897 let hm = keypair.as_hashmap_data().unwrap();
898
899 let pub_key = hm.shape_get("public_key").unwrap().clone();
900 let sec_key = hm.shape_get("secret_key").unwrap().clone();
901
902 let message = ValueWord::from_string(Arc::new("correct message".to_string()));
903 let wrong_message = ValueWord::from_string(Arc::new("wrong message".to_string()));
904
905 let sign_fn = module.get_export("ed25519_sign").unwrap();
906 let signature = sign_fn(&[message, sec_key], &ctx).unwrap();
907
908 let verify_fn = module.get_export("ed25519_verify").unwrap();
909 let valid = verify_fn(&[wrong_message, signature, pub_key], &ctx).unwrap();
910 assert_eq!(valid.as_bool(), Some(false));
911 }
912
913 #[test]
914 fn test_ed25519_sign_invalid_secret_key() {
915 let module = create_crypto_module();
916 let ctx = test_ctx();
917 let sign_fn = module.get_export("ed25519_sign").unwrap();
918
919 let result = sign_fn(
921 &[
922 ValueWord::from_string(Arc::new("msg".to_string())),
923 ValueWord::from_string(Arc::new("abcd".to_string())),
924 ],
925 &ctx,
926 );
927 assert!(result.is_err());
928
929 let result = sign_fn(
931 &[
932 ValueWord::from_string(Arc::new("msg".to_string())),
933 ValueWord::from_string(Arc::new("zzzz".to_string())),
934 ],
935 &ctx,
936 );
937 assert!(result.is_err());
938 }
939
940 #[test]
941 fn test_ed25519_verify_invalid_inputs() {
942 let module = create_crypto_module();
943 let ctx = test_ctx();
944 let verify_fn = module.get_export("ed25519_verify").unwrap();
945
946 assert!(verify_fn(&[ValueWord::from_string(Arc::new("msg".to_string()))], &ctx).is_err());
948
949 assert!(
951 verify_fn(
952 &[
953 ValueWord::from_string(Arc::new("msg".to_string())),
954 ValueWord::from_string(Arc::new("not_hex".to_string())),
955 ValueWord::from_string(Arc::new("ab".repeat(32))),
956 ],
957 &ctx
958 )
959 .is_err()
960 );
961 }
962
963 #[test]
964 fn test_new_function_schemas() {
965 let module = create_crypto_module();
966
967 let sha512_schema = module.get_schema("sha512").unwrap();
968 assert_eq!(sha512_schema.params.len(), 1);
969 assert_eq!(sha512_schema.return_type.as_deref(), Some("string"));
970
971 let sha1_schema = module.get_schema("sha1").unwrap();
972 assert_eq!(sha1_schema.params.len(), 1);
973 assert_eq!(sha1_schema.return_type.as_deref(), Some("string"));
974
975 let md5_schema = module.get_schema("md5").unwrap();
976 assert_eq!(md5_schema.params.len(), 1);
977 assert_eq!(md5_schema.return_type.as_deref(), Some("string"));
978
979 let rb_schema = module.get_schema("random_bytes").unwrap();
980 assert_eq!(rb_schema.params.len(), 1);
981 assert_eq!(rb_schema.params[0].type_name, "int");
982 assert_eq!(rb_schema.return_type.as_deref(), Some("string"));
983
984 let gen_schema = module.get_schema("ed25519_generate_keypair").unwrap();
985 assert_eq!(gen_schema.params.len(), 0);
986 assert_eq!(gen_schema.return_type.as_deref(), Some("object"));
987
988 let sign_schema = module.get_schema("ed25519_sign").unwrap();
989 assert_eq!(sign_schema.params.len(), 2);
990 assert_eq!(sign_schema.return_type.as_deref(), Some("string"));
991
992 let verify_schema = module.get_schema("ed25519_verify").unwrap();
993 assert_eq!(verify_schema.params.len(), 3);
994 assert_eq!(verify_schema.return_type.as_deref(), Some("bool"));
995 }
996}