seq_runtime/
crypto.rs

1//! Cryptographic operations for Seq
2//!
3//! These functions are exported with C ABI for LLVM codegen to call.
4//!
5//! # API
6//!
7//! ```seq
8//! # SHA-256 hashing
9//! "hello" crypto.sha256                    # ( String -- String ) hex digest
10//!
11//! # HMAC-SHA256 for webhook verification
12//! "message" "secret" crypto.hmac-sha256    # ( String String -- String ) hex signature
13//!
14//! # Timing-safe comparison
15//! sig1 sig2 crypto.constant-time-eq        # ( String String -- Bool )
16//!
17//! # Secure random bytes
18//! 32 crypto.random-bytes                   # ( Int -- String ) hex-encoded random bytes
19//!
20//! # UUID v4
21//! crypto.uuid4                             # ( -- String ) "550e8400-e29b-41d4-a716-446655440000"
22//!
23//! # AES-256-GCM authenticated encryption
24//! plaintext hex-key crypto.aes-gcm-encrypt  # ( String String -- String Bool )
25//! ciphertext hex-key crypto.aes-gcm-decrypt # ( String String -- String Bool )
26//!
27//! # Key derivation from password
28//! password salt iterations crypto.pbkdf2-sha256  # ( String String Int -- String Bool )
29//! ```
30
31use crate::seqstring::global_string;
32use crate::stack::{Stack, pop, push};
33use crate::value::Value;
34
35use aes_gcm::{
36    Aes256Gcm, Nonce,
37    aead::{Aead, KeyInit as AesKeyInit, OsRng, rand_core::RngCore as AeadRngCore},
38};
39use base64::{Engine, engine::general_purpose::STANDARD};
40use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
41use hmac::{Hmac, Mac};
42use sha2::{Digest, Sha256};
43use subtle::ConstantTimeEq;
44use uuid::Uuid;
45
46const AES_NONCE_SIZE: usize = 12;
47const AES_KEY_SIZE: usize = 32;
48const AES_GCM_TAG_SIZE: usize = 16;
49const MIN_PBKDF2_ITERATIONS: i64 = 1_000;
50
51type HmacSha256 = Hmac<Sha256>;
52
53/// Compute SHA-256 hash of a string
54///
55/// Stack effect: ( String -- String )
56///
57/// Returns the hash as a lowercase hex string (64 characters).
58///
59/// # Safety
60/// Stack must have a String value on top
61#[unsafe(no_mangle)]
62pub unsafe extern "C" fn patch_seq_sha256(stack: Stack) -> Stack {
63    assert!(!stack.is_null(), "sha256: stack is empty");
64
65    let (stack, value) = unsafe { pop(stack) };
66
67    match value {
68        Value::String(s) => {
69            let mut hasher = Sha256::new();
70            hasher.update(s.as_str().as_bytes());
71            let result = hasher.finalize();
72            let hex_digest = hex::encode(result);
73            unsafe { push(stack, Value::String(global_string(hex_digest))) }
74        }
75        _ => panic!("sha256: expected String on stack, got {:?}", value),
76    }
77}
78
79/// Compute HMAC-SHA256 of a message with a key
80///
81/// Stack effect: ( message key -- String )
82///
83/// Returns the signature as a lowercase hex string (64 characters).
84/// Used for webhook verification, JWT signing, API authentication.
85///
86/// # Safety
87/// Stack must have two String values on top (message, then key)
88#[unsafe(no_mangle)]
89pub unsafe extern "C" fn patch_seq_hmac_sha256(stack: Stack) -> Stack {
90    assert!(!stack.is_null(), "hmac-sha256: stack is empty");
91
92    let (stack, key_value) = unsafe { pop(stack) };
93    let (stack, msg_value) = unsafe { pop(stack) };
94
95    match (msg_value, key_value) {
96        (Value::String(msg), Value::String(key)) => {
97            let mut mac = <HmacSha256 as Mac>::new_from_slice(key.as_str().as_bytes())
98                .expect("HMAC can take any key");
99            mac.update(msg.as_str().as_bytes());
100            let result = mac.finalize();
101            let hex_sig = hex::encode(result.into_bytes());
102            unsafe { push(stack, Value::String(global_string(hex_sig))) }
103        }
104        (msg, key) => panic!(
105            "hmac-sha256: expected (String, String) on stack, got ({:?}, {:?})",
106            msg, key
107        ),
108    }
109}
110
111/// Timing-safe string comparison
112///
113/// Stack effect: ( String String -- Bool )
114///
115/// Compares two strings in constant time to prevent timing attacks.
116/// Essential for comparing signatures, hashes, tokens, etc.
117///
118/// Uses the `subtle` crate for cryptographically secure constant-time comparison.
119/// This prevents timing side-channel attacks where an attacker could deduce
120/// secret values by measuring comparison duration.
121///
122/// # Safety
123/// Stack must have two String values on top
124#[unsafe(no_mangle)]
125pub unsafe extern "C" fn patch_seq_constant_time_eq(stack: Stack) -> Stack {
126    assert!(!stack.is_null(), "constant-time-eq: stack is empty");
127
128    let (stack, b_value) = unsafe { pop(stack) };
129    let (stack, a_value) = unsafe { pop(stack) };
130
131    match (a_value, b_value) {
132        (Value::String(a), Value::String(b)) => {
133            let a_bytes = a.as_str().as_bytes();
134            let b_bytes = b.as_str().as_bytes();
135
136            // Use subtle crate for truly constant-time comparison
137            // This handles different-length strings correctly without timing leaks
138            let eq = a_bytes.ct_eq(b_bytes);
139
140            unsafe { push(stack, Value::Bool(bool::from(eq))) }
141        }
142        (a, b) => panic!(
143            "constant-time-eq: expected (String, String) on stack, got ({:?}, {:?})",
144            a, b
145        ),
146    }
147}
148
149/// Generate cryptographically secure random bytes
150///
151/// Stack effect: ( Int -- String )
152///
153/// Returns the random bytes as a lowercase hex string (2 chars per byte).
154/// Uses the operating system's secure random number generator.
155///
156/// # Limits
157/// - Maximum: 1024 bytes (to prevent memory exhaustion)
158/// - Common use cases: 16-32 bytes for tokens/nonces, 32-64 bytes for keys
159///
160/// # Safety
161/// Stack must have an Int value on top (number of bytes to generate)
162#[unsafe(no_mangle)]
163pub unsafe extern "C" fn patch_seq_random_bytes(stack: Stack) -> Stack {
164    assert!(!stack.is_null(), "random-bytes: stack is empty");
165
166    let (stack, value) = unsafe { pop(stack) };
167
168    match value {
169        Value::Int(n) => {
170            if n < 0 {
171                panic!("random-bytes: byte count must be non-negative, got {}", n);
172            }
173            if n > 1024 {
174                panic!("random-bytes: byte count too large (max 1024), got {}", n);
175            }
176
177            let mut bytes = vec![0u8; n as usize];
178            rand::thread_rng().fill_bytes(&mut bytes);
179            let hex_str = hex::encode(&bytes);
180            unsafe { push(stack, Value::String(global_string(hex_str))) }
181        }
182        _ => panic!("random-bytes: expected Int on stack, got {:?}", value),
183    }
184}
185
186/// Generate a UUID v4 (random)
187///
188/// Stack effect: ( -- String )
189///
190/// Returns a UUID in standard format: "550e8400-e29b-41d4-a716-446655440000"
191///
192/// # Safety
193/// Stack pointer must be valid
194#[unsafe(no_mangle)]
195pub unsafe extern "C" fn patch_seq_uuid4(stack: Stack) -> Stack {
196    assert!(!stack.is_null(), "uuid4: stack is empty");
197
198    let uuid = Uuid::new_v4();
199    unsafe { push(stack, Value::String(global_string(uuid.to_string()))) }
200}
201
202/// Encrypt plaintext using AES-256-GCM
203///
204/// Stack effect: ( String String -- String Bool )
205///
206/// Arguments:
207/// - plaintext: The string to encrypt
208/// - key: Hex-encoded 32-byte key (64 hex characters)
209///
210/// Returns:
211/// - ciphertext: base64(nonce || ciphertext || tag)
212/// - success: Bool indicating success
213///
214/// # Safety
215/// Stack must have two String values on top
216#[unsafe(no_mangle)]
217pub unsafe extern "C" fn patch_seq_crypto_aes_gcm_encrypt(stack: Stack) -> Stack {
218    assert!(!stack.is_null(), "crypto.aes-gcm-encrypt: stack is null");
219
220    let (stack, key_val) = unsafe { pop(stack) };
221    let (stack, plaintext_val) = unsafe { pop(stack) };
222
223    match (plaintext_val, key_val) {
224        (Value::String(plaintext), Value::String(key_hex)) => {
225            match aes_gcm_encrypt(plaintext.as_str(), key_hex.as_str()) {
226                Some(ciphertext) => {
227                    let stack = unsafe { push(stack, Value::String(global_string(ciphertext))) };
228                    unsafe { push(stack, Value::Bool(true)) }
229                }
230                None => {
231                    let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
232                    unsafe { push(stack, Value::Bool(false)) }
233                }
234            }
235        }
236        _ => panic!("crypto.aes-gcm-encrypt: expected two Strings on stack"),
237    }
238}
239
240/// Decrypt ciphertext using AES-256-GCM
241///
242/// Stack effect: ( String String -- String Bool )
243///
244/// Arguments:
245/// - ciphertext: base64(nonce || ciphertext || tag)
246/// - key: Hex-encoded 32-byte key (64 hex characters)
247///
248/// Returns:
249/// - plaintext: The decrypted string
250/// - success: Bool indicating success
251///
252/// # Safety
253/// Stack must have two String values on top
254#[unsafe(no_mangle)]
255pub unsafe extern "C" fn patch_seq_crypto_aes_gcm_decrypt(stack: Stack) -> Stack {
256    assert!(!stack.is_null(), "crypto.aes-gcm-decrypt: stack is null");
257
258    let (stack, key_val) = unsafe { pop(stack) };
259    let (stack, ciphertext_val) = unsafe { pop(stack) };
260
261    match (ciphertext_val, key_val) {
262        (Value::String(ciphertext), Value::String(key_hex)) => {
263            match aes_gcm_decrypt(ciphertext.as_str(), key_hex.as_str()) {
264                Some(plaintext) => {
265                    let stack = unsafe { push(stack, Value::String(global_string(plaintext))) };
266                    unsafe { push(stack, Value::Bool(true)) }
267                }
268                None => {
269                    let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
270                    unsafe { push(stack, Value::Bool(false)) }
271                }
272            }
273        }
274        _ => panic!("crypto.aes-gcm-decrypt: expected two Strings on stack"),
275    }
276}
277
278/// Derive a key from a password using PBKDF2-SHA256
279///
280/// Stack effect: ( String String Int -- String Bool )
281///
282/// Arguments:
283/// - password: The password string
284/// - salt: Salt string (should be unique per user/context)
285/// - iterations: Number of iterations (recommend 100000+)
286///
287/// Returns:
288/// - key: Hex-encoded 32-byte key (64 hex characters)
289/// - success: Bool indicating success
290///
291/// # Safety
292/// Stack must have String, String, Int values on top
293#[unsafe(no_mangle)]
294pub unsafe extern "C" fn patch_seq_crypto_pbkdf2_sha256(stack: Stack) -> Stack {
295    assert!(!stack.is_null(), "crypto.pbkdf2-sha256: stack is null");
296
297    let (stack, iterations_val) = unsafe { pop(stack) };
298    let (stack, salt_val) = unsafe { pop(stack) };
299    let (stack, password_val) = unsafe { pop(stack) };
300
301    match (password_val, salt_val, iterations_val) {
302        (Value::String(password), Value::String(salt), Value::Int(iterations)) => {
303            // Require minimum iterations for security (100,000+ recommended for production)
304            if iterations < MIN_PBKDF2_ITERATIONS {
305                let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
306                return unsafe { push(stack, Value::Bool(false)) };
307            }
308
309            let key = derive_key_pbkdf2(password.as_str(), salt.as_str(), iterations as u32);
310            let key_hex = hex::encode(key);
311            let stack = unsafe { push(stack, Value::String(global_string(key_hex))) };
312            unsafe { push(stack, Value::Bool(true)) }
313        }
314        _ => panic!("crypto.pbkdf2-sha256: expected String, String, Int on stack"),
315    }
316}
317
318// Helper functions for AES-GCM
319
320fn aes_gcm_encrypt(plaintext: &str, key_hex: &str) -> Option<String> {
321    // Decode hex key
322    let key_bytes = hex::decode(key_hex).ok()?;
323    if key_bytes.len() != AES_KEY_SIZE {
324        return None;
325    }
326
327    // Create cipher
328    let cipher = Aes256Gcm::new_from_slice(&key_bytes).ok()?;
329
330    // Generate random nonce
331    let mut nonce_bytes = [0u8; AES_NONCE_SIZE];
332    OsRng.fill_bytes(&mut nonce_bytes);
333    let nonce = Nonce::from_slice(&nonce_bytes);
334
335    // Encrypt
336    let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes()).ok()?;
337
338    // Combine: nonce || ciphertext (tag is appended by aes-gcm)
339    let mut combined = Vec::with_capacity(AES_NONCE_SIZE + ciphertext.len());
340    combined.extend_from_slice(&nonce_bytes);
341    combined.extend_from_slice(&ciphertext);
342
343    Some(STANDARD.encode(&combined))
344}
345
346fn aes_gcm_decrypt(ciphertext_b64: &str, key_hex: &str) -> Option<String> {
347    // Decode base64
348    let combined = STANDARD.decode(ciphertext_b64).ok()?;
349    if combined.len() < AES_NONCE_SIZE + AES_GCM_TAG_SIZE {
350        // At minimum: nonce + tag
351        return None;
352    }
353
354    // Decode hex key
355    let key_bytes = hex::decode(key_hex).ok()?;
356    if key_bytes.len() != AES_KEY_SIZE {
357        return None;
358    }
359
360    // Split nonce and ciphertext
361    let (nonce_bytes, ciphertext) = combined.split_at(AES_NONCE_SIZE);
362    let nonce = Nonce::from_slice(nonce_bytes);
363
364    // Create cipher and decrypt
365    let cipher = Aes256Gcm::new_from_slice(&key_bytes).ok()?;
366    let plaintext_bytes = cipher.decrypt(nonce, ciphertext).ok()?;
367
368    String::from_utf8(plaintext_bytes).ok()
369}
370
371fn derive_key_pbkdf2(password: &str, salt: &str, iterations: u32) -> [u8; AES_KEY_SIZE] {
372    let mut key = [0u8; AES_KEY_SIZE];
373    pbkdf2::pbkdf2_hmac::<Sha256>(password.as_bytes(), salt.as_bytes(), iterations, &mut key);
374    key
375}
376
377// ============================================================================
378// Ed25519 Digital Signatures
379// ============================================================================
380
381/// Generate an Ed25519 keypair
382///
383/// Stack effect: ( -- public-key private-key )
384///
385/// Returns:
386/// - public-key: Hex-encoded 32-byte public key (64 hex characters)
387/// - private-key: Hex-encoded 32-byte private key (64 hex characters)
388///
389/// # Safety
390/// Stack must be valid
391#[unsafe(no_mangle)]
392pub unsafe extern "C" fn patch_seq_crypto_ed25519_keypair(stack: Stack) -> Stack {
393    assert!(!stack.is_null(), "crypto.ed25519-keypair: stack is null");
394
395    let signing_key = SigningKey::generate(&mut OsRng);
396    let verifying_key = signing_key.verifying_key();
397
398    let private_hex = hex::encode(signing_key.to_bytes());
399    let public_hex = hex::encode(verifying_key.to_bytes());
400
401    let stack = unsafe { push(stack, Value::String(global_string(public_hex))) };
402    unsafe { push(stack, Value::String(global_string(private_hex))) }
403}
404
405/// Sign a message with an Ed25519 private key
406///
407/// Stack effect: ( message private-key -- signature success )
408///
409/// Parameters:
410/// - message: The message to sign (any string)
411/// - private-key: Hex-encoded 32-byte private key (64 hex characters)
412///
413/// Returns:
414/// - signature: Hex-encoded 64-byte signature (128 hex characters)
415/// - success: Bool indicating success
416///
417/// # Safety
418/// Stack must have String, String values on top
419#[unsafe(no_mangle)]
420pub unsafe extern "C" fn patch_seq_crypto_ed25519_sign(stack: Stack) -> Stack {
421    assert!(!stack.is_null(), "crypto.ed25519-sign: stack is null");
422
423    let (stack, key_val) = unsafe { pop(stack) };
424    let (stack, msg_val) = unsafe { pop(stack) };
425
426    match (msg_val, key_val) {
427        (Value::String(message), Value::String(private_key_hex)) => {
428            match ed25519_sign(message.as_str(), private_key_hex.as_str()) {
429                Some(signature) => {
430                    let stack = unsafe { push(stack, Value::String(global_string(signature))) };
431                    unsafe { push(stack, Value::Bool(true)) }
432                }
433                None => {
434                    let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
435                    unsafe { push(stack, Value::Bool(false)) }
436                }
437            }
438        }
439        _ => panic!("crypto.ed25519-sign: expected String, String on stack"),
440    }
441}
442
443/// Verify an Ed25519 signature
444///
445/// Stack effect: ( message signature public-key -- valid )
446///
447/// Parameters:
448/// - message: The original message
449/// - signature: Hex-encoded 64-byte signature (128 hex characters)
450/// - public-key: Hex-encoded 32-byte public key (64 hex characters)
451///
452/// Returns:
453/// - valid: Bool indicating whether the signature is valid
454///
455/// # Safety
456/// Stack must have String, String, String values on top
457#[unsafe(no_mangle)]
458pub unsafe extern "C" fn patch_seq_crypto_ed25519_verify(stack: Stack) -> Stack {
459    assert!(!stack.is_null(), "crypto.ed25519-verify: stack is null");
460
461    let (stack, pubkey_val) = unsafe { pop(stack) };
462    let (stack, sig_val) = unsafe { pop(stack) };
463    let (stack, msg_val) = unsafe { pop(stack) };
464
465    match (msg_val, sig_val, pubkey_val) {
466        (Value::String(message), Value::String(signature_hex), Value::String(public_key_hex)) => {
467            let valid = ed25519_verify(
468                message.as_str(),
469                signature_hex.as_str(),
470                public_key_hex.as_str(),
471            );
472            unsafe { push(stack, Value::Bool(valid)) }
473        }
474        _ => panic!("crypto.ed25519-verify: expected String, String, String on stack"),
475    }
476}
477
478// Helper functions for Ed25519
479
480fn ed25519_sign(message: &str, private_key_hex: &str) -> Option<String> {
481    let key_bytes = hex::decode(private_key_hex).ok()?;
482    if key_bytes.len() != 32 {
483        return None;
484    }
485
486    let key_array: [u8; 32] = key_bytes.try_into().ok()?;
487    let signing_key = SigningKey::from_bytes(&key_array);
488    let signature = signing_key.sign(message.as_bytes());
489
490    Some(hex::encode(signature.to_bytes()))
491}
492
493fn ed25519_verify(message: &str, signature_hex: &str, public_key_hex: &str) -> bool {
494    let verify_inner = || -> Option<bool> {
495        let sig_bytes = hex::decode(signature_hex).ok()?;
496        if sig_bytes.len() != 64 {
497            return Some(false);
498        }
499
500        let pubkey_bytes = hex::decode(public_key_hex).ok()?;
501        if pubkey_bytes.len() != 32 {
502            return Some(false);
503        }
504
505        let sig_array: [u8; 64] = sig_bytes.try_into().ok()?;
506        let pubkey_array: [u8; 32] = pubkey_bytes.try_into().ok()?;
507
508        let signature = Signature::from_bytes(&sig_array);
509        let verifying_key = VerifyingKey::from_bytes(&pubkey_array).ok()?;
510
511        Some(verifying_key.verify(message.as_bytes(), &signature).is_ok())
512    };
513
514    verify_inner().unwrap_or(false)
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520    use crate::stack::pop;
521
522    #[test]
523    fn test_sha256() {
524        unsafe {
525            let stack = crate::stack::alloc_test_stack();
526            let stack = push(stack, Value::String(global_string("hello".to_string())));
527            let stack = patch_seq_sha256(stack);
528            let (_, value) = pop(stack);
529
530            match value {
531                Value::String(s) => {
532                    // SHA-256 of "hello"
533                    assert_eq!(
534                        s.as_str(),
535                        "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
536                    );
537                }
538                _ => panic!("Expected String"),
539            }
540        }
541    }
542
543    #[test]
544    fn test_sha256_empty() {
545        unsafe {
546            let stack = crate::stack::alloc_test_stack();
547            let stack = push(stack, Value::String(global_string(String::new())));
548            let stack = patch_seq_sha256(stack);
549            let (_, value) = pop(stack);
550
551            match value {
552                Value::String(s) => {
553                    // SHA-256 of empty string
554                    assert_eq!(
555                        s.as_str(),
556                        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
557                    );
558                }
559                _ => panic!("Expected String"),
560            }
561        }
562    }
563
564    #[test]
565    fn test_hmac_sha256() {
566        unsafe {
567            let stack = crate::stack::alloc_test_stack();
568            let stack = push(stack, Value::String(global_string("message".to_string())));
569            let stack = push(stack, Value::String(global_string("secret".to_string())));
570            let stack = patch_seq_hmac_sha256(stack);
571            let (_, value) = pop(stack);
572
573            match value {
574                Value::String(s) => {
575                    // HMAC-SHA256("message", "secret")
576                    assert_eq!(
577                        s.as_str(),
578                        "8b5f48702995c1598c573db1e21866a9b825d4a794d169d7060a03605796360b"
579                    );
580                }
581                _ => panic!("Expected String"),
582            }
583        }
584    }
585
586    #[test]
587    fn test_constant_time_eq_equal() {
588        unsafe {
589            let stack = crate::stack::alloc_test_stack();
590            let stack = push(stack, Value::String(global_string("hello".to_string())));
591            let stack = push(stack, Value::String(global_string("hello".to_string())));
592            let stack = patch_seq_constant_time_eq(stack);
593            let (_, value) = pop(stack);
594
595            match value {
596                Value::Bool(b) => assert!(b),
597                _ => panic!("Expected Bool"),
598            }
599        }
600    }
601
602    #[test]
603    fn test_constant_time_eq_different() {
604        unsafe {
605            let stack = crate::stack::alloc_test_stack();
606            let stack = push(stack, Value::String(global_string("hello".to_string())));
607            let stack = push(stack, Value::String(global_string("world".to_string())));
608            let stack = patch_seq_constant_time_eq(stack);
609            let (_, value) = pop(stack);
610
611            match value {
612                Value::Bool(b) => assert!(!b),
613                _ => panic!("Expected Bool"),
614            }
615        }
616    }
617
618    #[test]
619    fn test_constant_time_eq_different_lengths() {
620        unsafe {
621            let stack = crate::stack::alloc_test_stack();
622            let stack = push(stack, Value::String(global_string("hello".to_string())));
623            let stack = push(stack, Value::String(global_string("hi".to_string())));
624            let stack = patch_seq_constant_time_eq(stack);
625            let (_, value) = pop(stack);
626
627            match value {
628                Value::Bool(b) => assert!(!b),
629                _ => panic!("Expected Bool"),
630            }
631        }
632    }
633
634    #[test]
635    fn test_random_bytes() {
636        unsafe {
637            let stack = crate::stack::alloc_test_stack();
638            let stack = push(stack, Value::Int(16));
639            let stack = patch_seq_random_bytes(stack);
640            let (_, value) = pop(stack);
641
642            match value {
643                Value::String(s) => {
644                    // 16 bytes = 32 hex chars
645                    assert_eq!(s.as_str().len(), 32);
646                    // Should be valid hex
647                    assert!(hex::decode(s.as_str()).is_ok());
648                }
649                _ => panic!("Expected String"),
650            }
651        }
652    }
653
654    #[test]
655    fn test_random_bytes_zero() {
656        unsafe {
657            let stack = crate::stack::alloc_test_stack();
658            let stack = push(stack, Value::Int(0));
659            let stack = patch_seq_random_bytes(stack);
660            let (_, value) = pop(stack);
661
662            match value {
663                Value::String(s) => {
664                    assert_eq!(s.as_str(), "");
665                }
666                _ => panic!("Expected String"),
667            }
668        }
669    }
670
671    #[test]
672    fn test_uuid4() {
673        unsafe {
674            let stack = crate::stack::alloc_test_stack();
675            let stack = patch_seq_uuid4(stack);
676            let (_, value) = pop(stack);
677
678            match value {
679                Value::String(s) => {
680                    // UUID format: 8-4-4-4-12
681                    assert_eq!(s.as_str().len(), 36);
682                    assert_eq!(s.as_str().chars().filter(|c| *c == '-').count(), 4);
683                    // Version 4 indicator at position 14
684                    assert_eq!(s.as_str().chars().nth(14), Some('4'));
685                }
686                _ => panic!("Expected String"),
687            }
688        }
689    }
690
691    #[test]
692    fn test_uuid4_unique() {
693        unsafe {
694            let stack = crate::stack::alloc_test_stack();
695            let stack = patch_seq_uuid4(stack);
696            let (stack, value1) = pop(stack);
697            let stack = patch_seq_uuid4(stack);
698            let (_, value2) = pop(stack);
699
700            match (value1, value2) {
701                (Value::String(s1), Value::String(s2)) => {
702                    assert_ne!(s1.as_str(), s2.as_str());
703                }
704                _ => panic!("Expected Strings"),
705            }
706        }
707    }
708
709    #[test]
710    fn test_random_bytes_max_limit() {
711        unsafe {
712            let stack = crate::stack::alloc_test_stack();
713            let stack = push(stack, Value::Int(1024)); // Max allowed
714            let stack = patch_seq_random_bytes(stack);
715            let (_, value) = pop(stack);
716
717            match value {
718                Value::String(s) => {
719                    // 1024 bytes = 2048 hex chars
720                    assert_eq!(s.as_str().len(), 2048);
721                }
722                _ => panic!("Expected String"),
723            }
724        }
725    }
726    // Note: Exceeding the 1024 byte limit causes a panic, which aborts in FFI context.
727    // This is intentional - the limit prevents memory exhaustion attacks.
728
729    // AES-GCM Tests
730
731    #[test]
732    fn test_aes_gcm_roundtrip() {
733        unsafe {
734            let stack = crate::stack::alloc_test_stack();
735
736            // Create a test key (32 bytes = 64 hex chars)
737            let key_hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
738
739            let stack = push(
740                stack,
741                Value::String(global_string("hello world".to_string())),
742            );
743            let stack = push(stack, Value::String(global_string(key_hex.to_string())));
744
745            // Encrypt
746            let stack = patch_seq_crypto_aes_gcm_encrypt(stack);
747
748            // Check encrypt success
749            let (stack, success) = pop(stack);
750            assert_eq!(success, Value::Bool(true));
751
752            // Add key for decrypt
753            let stack = push(stack, Value::String(global_string(key_hex.to_string())));
754
755            // Decrypt
756            let stack = patch_seq_crypto_aes_gcm_decrypt(stack);
757
758            // Check decrypt success
759            let (stack, success) = pop(stack);
760            assert_eq!(success, Value::Bool(true));
761
762            // Check plaintext
763            let (_, result) = pop(stack);
764            if let Value::String(s) = result {
765                assert_eq!(s.as_str(), "hello world");
766            } else {
767                panic!("expected string");
768            }
769        }
770    }
771
772    #[test]
773    fn test_aes_gcm_wrong_key() {
774        unsafe {
775            let stack = crate::stack::alloc_test_stack();
776
777            let key1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
778            let key2 = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
779
780            let stack = push(
781                stack,
782                Value::String(global_string("secret message".to_string())),
783            );
784            let stack = push(stack, Value::String(global_string(key1.to_string())));
785
786            // Encrypt with key1
787            let stack = patch_seq_crypto_aes_gcm_encrypt(stack);
788            let (stack, success) = pop(stack);
789            assert_eq!(success, Value::Bool(true));
790
791            // Try to decrypt with key2
792            let stack = push(stack, Value::String(global_string(key2.to_string())));
793            let stack = patch_seq_crypto_aes_gcm_decrypt(stack);
794
795            // Should fail
796            let (_, success) = pop(stack);
797            assert_eq!(success, Value::Bool(false));
798        }
799    }
800
801    #[test]
802    fn test_aes_gcm_invalid_key_length() {
803        unsafe {
804            let stack = crate::stack::alloc_test_stack();
805
806            // Key too short
807            let short_key = "0123456789abcdef";
808
809            let stack = push(stack, Value::String(global_string("test data".to_string())));
810            let stack = push(stack, Value::String(global_string(short_key.to_string())));
811
812            let stack = patch_seq_crypto_aes_gcm_encrypt(stack);
813            let (_, success) = pop(stack);
814            assert_eq!(success, Value::Bool(false));
815        }
816    }
817
818    #[test]
819    fn test_aes_gcm_invalid_ciphertext() {
820        unsafe {
821            let stack = crate::stack::alloc_test_stack();
822
823            let key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
824
825            // Invalid base64
826            let stack = push(
827                stack,
828                Value::String(global_string("not-valid-base64!!!".to_string())),
829            );
830            let stack = push(stack, Value::String(global_string(key.to_string())));
831
832            let stack = patch_seq_crypto_aes_gcm_decrypt(stack);
833            let (_, success) = pop(stack);
834            assert_eq!(success, Value::Bool(false));
835        }
836    }
837
838    #[test]
839    fn test_aes_gcm_empty_plaintext() {
840        let key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
841
842        let ciphertext = aes_gcm_encrypt("", key).unwrap();
843        let decrypted = aes_gcm_decrypt(&ciphertext, key).unwrap();
844        assert_eq!(decrypted, "");
845    }
846
847    #[test]
848    fn test_aes_gcm_special_characters() {
849        let key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
850        let plaintext = "Hello\nWorld\tTab\"Quote\\Backslash";
851
852        let ciphertext = aes_gcm_encrypt(plaintext, key).unwrap();
853        let decrypted = aes_gcm_decrypt(&ciphertext, key).unwrap();
854        assert_eq!(decrypted, plaintext);
855    }
856
857    // PBKDF2 Tests
858
859    #[test]
860    fn test_pbkdf2_sha256() {
861        unsafe {
862            let stack = crate::stack::alloc_test_stack();
863
864            let stack = push(
865                stack,
866                Value::String(global_string("my-password".to_string())),
867            );
868            let stack = push(
869                stack,
870                Value::String(global_string("random-salt".to_string())),
871            );
872            let stack = push(stack, Value::Int(10000));
873
874            let stack = patch_seq_crypto_pbkdf2_sha256(stack);
875
876            // Check success
877            let (stack, success) = pop(stack);
878            assert_eq!(success, Value::Bool(true));
879
880            // Check key is 64 hex chars (32 bytes)
881            let (_, result) = pop(stack);
882            if let Value::String(s) = result {
883                assert_eq!(s.as_str().len(), 64);
884                // Verify it's valid hex
885                assert!(hex::decode(s.as_str()).is_ok());
886            } else {
887                panic!("expected string");
888            }
889        }
890    }
891
892    #[test]
893    fn test_pbkdf2_deterministic() {
894        // Same inputs should produce same key
895        let key1 = derive_key_pbkdf2("password", "salt", 10000);
896        let key2 = derive_key_pbkdf2("password", "salt", 10000);
897        assert_eq!(key1, key2);
898
899        // Different inputs should produce different keys
900        let key3 = derive_key_pbkdf2("password", "different-salt", 10000);
901        assert_ne!(key1, key3);
902    }
903
904    #[test]
905    fn test_pbkdf2_invalid_iterations() {
906        unsafe {
907            let stack = crate::stack::alloc_test_stack();
908
909            let stack = push(stack, Value::String(global_string("password".to_string())));
910            let stack = push(stack, Value::String(global_string("salt".to_string())));
911            let stack = push(stack, Value::Int(0)); // Invalid: below minimum (1000)
912
913            let stack = patch_seq_crypto_pbkdf2_sha256(stack);
914
915            let (_, success) = pop(stack);
916            assert_eq!(success, Value::Bool(false));
917        }
918    }
919
920    #[test]
921    fn test_encrypt_decrypt_with_derived_key() {
922        // Full workflow: derive key from password, then encrypt/decrypt
923        let password = "user-secret-password";
924        let salt = "unique-user-salt";
925        let iterations = 10000u32;
926
927        // Derive key
928        let key = derive_key_pbkdf2(password, salt, iterations);
929        let key_hex = hex::encode(key);
930
931        // Encrypt
932        let plaintext = "sensitive data to protect";
933        let ciphertext = aes_gcm_encrypt(plaintext, &key_hex).unwrap();
934
935        // Decrypt
936        let decrypted = aes_gcm_decrypt(&ciphertext, &key_hex).unwrap();
937        assert_eq!(decrypted, plaintext);
938    }
939
940    // Ed25519 tests
941
942    #[test]
943    fn test_ed25519_sign_verify() {
944        let message = "Hello, World!";
945
946        // Generate keypair
947        let signing_key = SigningKey::generate(&mut OsRng);
948        let verifying_key = signing_key.verifying_key();
949
950        let private_hex = hex::encode(signing_key.to_bytes());
951        let public_hex = hex::encode(verifying_key.to_bytes());
952
953        // Sign
954        let signature = ed25519_sign(message, &private_hex).unwrap();
955        assert_eq!(signature.len(), 128); // 64 bytes = 128 hex chars
956
957        // Verify
958        assert!(ed25519_verify(message, &signature, &public_hex));
959    }
960
961    #[test]
962    fn test_ed25519_wrong_message() {
963        let message = "Original message";
964        let wrong_message = "Wrong message";
965
966        let signing_key = SigningKey::generate(&mut OsRng);
967        let verifying_key = signing_key.verifying_key();
968
969        let private_hex = hex::encode(signing_key.to_bytes());
970        let public_hex = hex::encode(verifying_key.to_bytes());
971
972        let signature = ed25519_sign(message, &private_hex).unwrap();
973
974        // Verify with wrong message should fail
975        assert!(!ed25519_verify(wrong_message, &signature, &public_hex));
976    }
977
978    #[test]
979    fn test_ed25519_wrong_key() {
980        let message = "Test message";
981
982        let signing_key1 = SigningKey::generate(&mut OsRng);
983        let signing_key2 = SigningKey::generate(&mut OsRng);
984
985        let private_hex = hex::encode(signing_key1.to_bytes());
986        let wrong_public_hex = hex::encode(signing_key2.verifying_key().to_bytes());
987
988        let signature = ed25519_sign(message, &private_hex).unwrap();
989
990        // Verify with wrong public key should fail
991        assert!(!ed25519_verify(message, &signature, &wrong_public_hex));
992    }
993
994    #[test]
995    fn test_ed25519_invalid_key_length() {
996        let message = "Test message";
997        let invalid_key = "tooshort";
998
999        // Sign with invalid key should fail
1000        assert!(ed25519_sign(message, invalid_key).is_none());
1001    }
1002
1003    #[test]
1004    fn test_ed25519_invalid_signature() {
1005        let message = "Test message";
1006
1007        let signing_key = SigningKey::generate(&mut OsRng);
1008        let public_hex = hex::encode(signing_key.verifying_key().to_bytes());
1009
1010        let invalid_signature = "0".repeat(128); // Valid length but wrong signature
1011
1012        // Verify with invalid signature should fail
1013        assert!(!ed25519_verify(message, &invalid_signature, &public_hex));
1014    }
1015
1016    #[test]
1017    fn test_ed25519_empty_message() {
1018        let message = "";
1019
1020        let signing_key = SigningKey::generate(&mut OsRng);
1021        let verifying_key = signing_key.verifying_key();
1022
1023        let private_hex = hex::encode(signing_key.to_bytes());
1024        let public_hex = hex::encode(verifying_key.to_bytes());
1025
1026        // Sign empty message
1027        let signature = ed25519_sign(message, &private_hex).unwrap();
1028
1029        // Verify should succeed
1030        assert!(ed25519_verify(message, &signature, &public_hex));
1031    }
1032
1033    #[test]
1034    fn test_ed25519_keypair_ffi() {
1035        unsafe {
1036            let stack = crate::stack::alloc_test_stack();
1037
1038            let stack = patch_seq_crypto_ed25519_keypair(stack);
1039
1040            let (stack, private_key) = pop(stack);
1041            let (_, public_key) = pop(stack);
1042
1043            // Both should be 64-char hex strings (32 bytes)
1044            if let Value::String(pk) = public_key {
1045                assert_eq!(pk.as_str().len(), 64);
1046            } else {
1047                panic!("Expected String for public key");
1048            }
1049
1050            if let Value::String(sk) = private_key {
1051                assert_eq!(sk.as_str().len(), 64);
1052            } else {
1053                panic!("Expected String for private key");
1054            }
1055        }
1056    }
1057
1058    #[test]
1059    fn test_ed25519_sign_ffi() {
1060        unsafe {
1061            let stack = crate::stack::alloc_test_stack();
1062
1063            // Generate a valid key first
1064            let signing_key = SigningKey::generate(&mut OsRng);
1065            let private_hex = hex::encode(signing_key.to_bytes());
1066
1067            let stack = push(
1068                stack,
1069                Value::String(global_string("Test message".to_string())),
1070            );
1071            let stack = push(stack, Value::String(global_string(private_hex)));
1072
1073            let stack = patch_seq_crypto_ed25519_sign(stack);
1074
1075            let (stack, success) = pop(stack);
1076            let (_, signature) = pop(stack);
1077
1078            assert_eq!(success, Value::Bool(true));
1079            if let Value::String(sig) = signature {
1080                assert_eq!(sig.as_str().len(), 128); // 64 bytes = 128 hex chars
1081            } else {
1082                panic!("Expected String for signature");
1083            }
1084        }
1085    }
1086
1087    #[test]
1088    fn test_ed25519_verify_ffi() {
1089        unsafe {
1090            let stack = crate::stack::alloc_test_stack();
1091
1092            // Generate keypair and sign
1093            let signing_key = SigningKey::generate(&mut OsRng);
1094            let verifying_key = signing_key.verifying_key();
1095
1096            let private_hex = hex::encode(signing_key.to_bytes());
1097            let public_hex = hex::encode(verifying_key.to_bytes());
1098
1099            let message = "Verify this message";
1100            let signature = ed25519_sign(message, &private_hex).unwrap();
1101
1102            let stack = push(stack, Value::String(global_string(message.to_string())));
1103            let stack = push(stack, Value::String(global_string(signature)));
1104            let stack = push(stack, Value::String(global_string(public_hex)));
1105
1106            let stack = patch_seq_crypto_ed25519_verify(stack);
1107
1108            let (_, valid) = pop(stack);
1109            assert_eq!(valid, Value::Bool(true));
1110        }
1111    }
1112}