use lex_ast::canonicalize_program;
use lex_bytecode::{compile_program, vm::Vm, Value};
use lex_runtime::{DefaultHandler, Policy};
use lex_syntax::parse_source;
use std::sync::Arc;
fn run(src: &str, fn_name: &str, args: Vec<Value>) -> Value {
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
if let Err(errs) = lex_types::check_program(&stages) {
panic!("type errors:\n{errs:#?}");
}
let bc = Arc::new(compile_program(&stages));
let handler = DefaultHandler::new(Policy::pure()).with_program(Arc::clone(&bc));
let mut vm = Vm::with_handler(&bc, Box::new(handler));
vm.call(fn_name, args).unwrap_or_else(|e| panic!("call {fn_name}: {e}"))
}
fn b(xs: &[u8]) -> Value { Value::Bytes(xs.to_vec()) }
fn unwrap_ok_bytes(v: Value) -> Vec<u8> {
match v {
Value::Variant { name, args } if name == "Ok" && args.len() == 1 => {
match args.into_iter().next().unwrap() {
Value::Bytes(bs) => bs,
other => panic!("Ok payload not Bytes: {other:?}"),
}
}
other => panic!("expected Ok(_), got {other:?}"),
}
}
fn unwrap_err(v: Value) -> String {
match v {
Value::Variant { name, args } if name == "Err" && args.len() == 1 => {
match args.into_iter().next().unwrap() {
Value::Str(s) => s.to_string(),
other => panic!("Err payload not Str: {other:?}"),
}
}
other => panic!("expected Err(_), got {other:?}"),
}
}
const SRC: &str = r#"
import "std.crypto" as crypto
fn pb(password :: Bytes, salt :: Bytes, iters :: Int, len :: Int) -> Result[Bytes, Str] {
crypto.pbkdf2_sha256(password, salt, iters, len)
}
fn hk(ikm :: Bytes, salt :: Bytes, info :: Bytes, len :: Int) -> Result[Bytes, Str] {
crypto.hkdf_sha256(ikm, salt, info, len)
}
fn ar(password :: Bytes, salt :: Bytes, t :: Int, m :: Int, len :: Int) -> Result[Bytes, Str] {
crypto.argon2id(password, salt, t, m, len)
}
"#;
#[test]
fn pbkdf2_sha256_rfc7914_test_vector() {
let out = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"passwd"), b(b"salt"), Value::Int(1), Value::Int(64),
]));
let expected = hex::decode(
"55ac046e56e3089fec1691c22544b605\
f94185216dde0465e68b9d57c20dacbc\
49ca9cccf179b645991664b39d77ef31\
7c71b845b1e30bd509112041d3a19783"
).unwrap();
assert_eq!(out, expected, "PBKDF2-SHA256 RFC 7914 vector mismatch");
}
#[test]
fn pbkdf2_sha256_is_deterministic() {
let a = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"hunter2"), b(b"sodium-chloride"), Value::Int(1000), Value::Int(32),
]));
let b_out = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"hunter2"), b(b"sodium-chloride"), Value::Int(1000), Value::Int(32),
]));
assert_eq!(a, b_out);
}
#[test]
fn pbkdf2_sha256_different_password_different_output() {
let a = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"hunter2"), b(b"salt"), Value::Int(100), Value::Int(32),
]));
let b_out = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"hunter3"), b(b"salt"), Value::Int(100), Value::Int(32),
]));
assert_ne!(a, b_out);
}
#[test]
fn pbkdf2_sha256_honours_len() {
let out = unwrap_ok_bytes(run(SRC, "pb", vec![
b(b"x"), b(b"y"), Value::Int(2), Value::Int(48),
]));
assert_eq!(out.len(), 48);
}
#[test]
fn pbkdf2_sha256_rejects_zero_iterations() {
let err = unwrap_err(run(SRC, "pb", vec![
b(b"x"), b(b"y"), Value::Int(0), Value::Int(32),
]));
assert!(err.contains("iterations"), "got: {err}");
}
#[test]
fn pbkdf2_sha256_rejects_zero_len() {
let err = unwrap_err(run(SRC, "pb", vec![
b(b"x"), b(b"y"), Value::Int(1), Value::Int(0),
]));
assert!(err.contains("len"), "got: {err}");
}
#[test]
fn hkdf_sha256_rfc5869_test_case_1() {
let out = unwrap_ok_bytes(run(SRC, "hk", vec![
b(&[0x0b; 22]),
b(&hex::decode("000102030405060708090a0b0c").unwrap()),
b(&hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap()),
Value::Int(42),
]));
let expected = hex::decode(
"3cb25f25faacd57a90434f64d0362f2a\
2d2d0a90cf1a5a4c5db02d56ecc4c5bf\
34007208d5b887185865"
).unwrap();
assert_eq!(out, expected, "HKDF-SHA256 RFC 5869 case 1 mismatch");
}
#[test]
fn hkdf_sha256_empty_salt_uses_default() {
let out = unwrap_ok_bytes(run(SRC, "hk", vec![
b(&[0x0b; 22]), b(b""), b(b""), Value::Int(42),
]));
assert_eq!(out.len(), 42);
}
#[test]
fn hkdf_sha256_rejects_oversized_output() {
let err = unwrap_err(run(SRC, "hk", vec![
b(b"ikm"), b(b"salt"), b(b"info"), Value::Int(9000),
]));
assert!(err.starts_with("hkdf_sha256:"), "got: {err}");
}
#[test]
fn argon2id_is_deterministic() {
let a = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
let b_out = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
assert_eq!(a, b_out, "argon2id must be deterministic");
assert_eq!(a.len(), 32);
}
#[test]
fn argon2id_password_change_changes_output() {
let a = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
let b_out = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter3"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
assert_ne!(a, b_out);
}
#[test]
fn argon2id_salt_change_changes_output() {
let a = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salt-A-padded-out"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
let b_out = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salt-B-padded-out"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
assert_ne!(a, b_out);
}
#[test]
fn argon2id_rejects_short_salt() {
let err = unwrap_err(run(SRC, "ar", vec![
b(b"hunter2"), b(b"short"),
Value::Int(1), Value::Int(8), Value::Int(32),
]));
assert!(err.to_lowercase().contains("salt"), "got: {err}");
}
#[test]
fn argon2id_rejects_zero_t_cost() {
let err = unwrap_err(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(0), Value::Int(8), Value::Int(32),
]));
assert!(err.contains("t_cost"), "got: {err}");
}
#[test]
fn argon2id_rejects_too_small_m_cost() {
let err = unwrap_err(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(1), Value::Int(32),
]));
assert!(err.contains("m_cost"), "got: {err}");
}
#[test]
fn argon2id_rejects_zero_len() {
let err = unwrap_err(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(0),
]));
assert!(err.contains("len"), "got: {err}");
}
#[test]
fn argon2id_honours_len() {
let out = unwrap_ok_bytes(run(SRC, "ar", vec![
b(b"hunter2"), b(b"salty-salt"),
Value::Int(1), Value::Int(8), Value::Int(64),
]));
assert_eq!(out.len(), 64);
}