use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use rand::RngCore;
use sha2::{Digest, Sha256};
pub const KEY_PREFIX: &str = "brvk_";
#[derive(Debug, Clone)]
pub struct GeneratedKey {
pub secret: String,
pub hash: String,
}
pub fn generate() -> GeneratedKey {
let mut bytes = [0u8; 32];
rand::rng().fill_bytes(&mut bytes);
let secret = format!("{KEY_PREFIX}{}", URL_SAFE_NO_PAD.encode(bytes));
let hash = hash_key(&secret);
GeneratedKey { secret, hash }
}
pub fn hash_key(secret: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(secret.as_bytes());
let digest = hasher.finalize();
hex::encode(digest)
}
pub fn looks_like_virtual_key(s: &str) -> bool {
s.strip_prefix(KEY_PREFIX)
.is_some_and(|body| !body.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_keys_are_prefixed_and_unique() {
let a = generate();
let b = generate();
assert!(a.secret.starts_with("brvk_"));
assert!(looks_like_virtual_key(&a.secret));
assert_ne!(a.secret, b.secret, "keys are random");
assert_ne!(a.hash, b.hash);
}
#[test]
fn hash_is_stable_and_matches_generation() {
let key = generate();
assert_eq!(hash_key(&key.secret), key.hash);
assert_eq!(key.hash.len(), 64);
}
#[test]
fn non_virtual_keys_are_rejected_by_shape_check() {
assert!(!looks_like_virtual_key("sk-openai-style"));
assert!(!looks_like_virtual_key("brvk_")); assert!(!looks_like_virtual_key(""));
assert!(!looks_like_virtual_key("eyJhbGc.eyJzdWI.sig"));
}
}