Skip to main content

seq_runtime/crypto/
random.rs

1//! Cryptographic randomness: `random_bytes`, `uuid4`, `random_int`.
2
3use crate::seqstring::global_string;
4use crate::stack::{Stack, pop, push};
5use crate::value::Value;
6
7use rand::{RngCore, rng};
8use uuid::Uuid;
9
10/// Generate cryptographically secure random bytes
11///
12/// Stack effect: ( Int -- String )
13///
14/// Returns the random bytes as a lowercase hex string (2 chars per byte).
15/// Uses the operating system's secure random number generator.
16///
17/// # Limits
18/// - Maximum: 1024 bytes (to prevent memory exhaustion)
19/// - Common use cases: 16-32 bytes for tokens/nonces, 32-64 bytes for keys
20///
21/// # Safety
22/// Stack must have an Int value on top (number of bytes to generate)
23#[unsafe(no_mangle)]
24pub unsafe extern "C" fn patch_seq_random_bytes(stack: Stack) -> Stack {
25    assert!(!stack.is_null(), "random-bytes: stack is empty");
26
27    let (stack, value) = unsafe { pop(stack) };
28
29    match value {
30        Value::Int(n) => {
31            if n < 0 {
32                panic!("random-bytes: byte count must be non-negative, got {}", n);
33            }
34            if n > 1024 {
35                panic!("random-bytes: byte count too large (max 1024), got {}", n);
36            }
37
38            let mut bytes = vec![0u8; n as usize];
39            rng().fill_bytes(&mut bytes);
40            let hex_str = hex::encode(&bytes);
41            unsafe { push(stack, Value::String(global_string(hex_str))) }
42        }
43        _ => panic!("random-bytes: expected Int on stack, got {:?}", value),
44    }
45}
46
47/// Generate a UUID v4 (random)
48///
49/// Stack effect: ( -- String )
50///
51/// Returns a UUID in standard format: "550e8400-e29b-41d4-a716-446655440000"
52///
53/// # Safety
54/// Stack pointer must be valid
55#[unsafe(no_mangle)]
56pub unsafe extern "C" fn patch_seq_uuid4(stack: Stack) -> Stack {
57    assert!(!stack.is_null(), "uuid4: stack is empty");
58
59    let uuid = Uuid::new_v4();
60    unsafe { push(stack, Value::String(global_string(uuid.to_string()))) }
61}
62
63/// Generate a cryptographically secure random integer in a range
64///
65/// Stack effect: ( min max -- Int )
66///
67/// Returns a uniform random integer in the range [min, max).
68/// Uses rejection sampling to avoid modulo bias.
69///
70/// # Edge Cases
71/// - If min >= max, returns min
72/// - Uses the same CSPRNG as crypto.random-bytes
73///
74/// # Safety
75/// Stack must have two Int values on top
76#[unsafe(no_mangle)]
77pub unsafe extern "C" fn patch_seq_random_int(stack: Stack) -> Stack {
78    assert!(!stack.is_null(), "random-int: stack is empty");
79
80    let (stack, max_val) = unsafe { pop(stack) };
81    let (stack, min_val) = unsafe { pop(stack) };
82
83    match (min_val, max_val) {
84        (Value::Int(min), Value::Int(max)) => {
85            let result = if min >= max {
86                min // Edge case: return min if range is empty or invalid
87            } else {
88                random_int_range(min, max)
89            };
90            unsafe { push(stack, Value::Int(result)) }
91        }
92        (min, max) => panic!(
93            "random-int: expected (Int, Int) on stack, got ({:?}, {:?})",
94            min, max
95        ),
96    }
97}
98
99/// Generate a uniform random integer in [min, max) using rejection sampling
100///
101/// This avoids modulo bias by rejecting values that would cause uneven distribution.
102fn random_int_range(min: i64, max: i64) -> i64 {
103    // Use wrapping subtraction in unsigned space to handle full i64 range
104    // without overflow (e.g., min=i64::MIN, max=i64::MAX would overflow in signed)
105    let range = (max as u64).wrapping_sub(min as u64);
106    if range == 0 {
107        return min;
108    }
109
110    // Use rejection sampling to get unbiased result
111    // For ranges that are powers of 2, no rejection needed
112    // For other ranges, we reject values >= (u64::MAX - (u64::MAX % range))
113    // to ensure uniform distribution
114    let threshold = u64::MAX - (u64::MAX % range);
115
116    loop {
117        // Generate random u64 using fill_bytes (same CSPRNG as random_bytes)
118        let mut bytes = [0u8; 8];
119        rng().fill_bytes(&mut bytes);
120        let val = u64::from_le_bytes(bytes);
121
122        if val < threshold {
123            // Add offset to min using unsigned arithmetic to avoid overflow
124            // when min is negative and offset is large
125            let result = (min as u64).wrapping_add(val % range);
126            return result as i64;
127        }
128        // Rejection: try again (very rare, < 1 in 2^63 for most ranges)
129    }
130}