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