atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
d1cbd183925db8a39c0632ba9c4cda7718c95e71
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/secret.rs

99524cf9f17catagen2026-10-10 10:53 UTC1
use std::{fmt::Write as _, sync::LazyLock};
99524cf9f17catagen2026-10-10 10:53 UTC2
99524cf9f17catagen2026-10-10 10:53 UTC3
use hmac::{Hmac, Mac};
99524cf9f17catagen2026-10-10 10:53 UTC4
use sha2::Sha256;
99524cf9f17catagen2026-10-10 10:53 UTC5
99524cf9f17catagen2026-10-10 10:53 UTC6
static SECRET: LazyLock<Vec<u8>> = LazyLock::new(|| {
99524cf9f17catagen2026-10-10 10:53 UTC7
    std::env::var("JELQD_SECRET")
99524cf9f17catagen2026-10-10 10:53 UTC8
        .map(String::into_bytes)
99524cf9f17catagen2026-10-10 10:53 UTC9
        .unwrap_or_else(|_| rand::random::<[u8; 32]>().to_vec())
99524cf9f17catagen2026-10-10 10:53 UTC10
});
99524cf9f17catagen2026-10-10 10:53 UTC11
99524cf9f17catagen2026-10-10 10:53 UTC12
fn mac(message: &str) -> Hmac<Sha256> {
99524cf9f17catagen2026-10-10 10:53 UTC13
    let mut mac = Hmac::<Sha256>::new_from_slice(&SECRET).expect("hmac accepts any key length");
99524cf9f17catagen2026-10-10 10:53 UTC14
    mac.update(message.as_bytes());
99524cf9f17catagen2026-10-10 10:53 UTC15
    mac
99524cf9f17catagen2026-10-10 10:53 UTC16
}
99524cf9f17catagen2026-10-10 10:53 UTC17
99524cf9f17catagen2026-10-10 10:53 UTC18
fn hex(bytes: &[u8]) -> String {
99524cf9f17catagen2026-10-10 10:53 UTC19
    bytes.iter().fold(String::new(), |mut out, b| {
99524cf9f17catagen2026-10-10 10:53 UTC20
        let _ = write!(out, "{b:02x}");
99524cf9f17catagen2026-10-10 10:53 UTC21
        out
99524cf9f17catagen2026-10-10 10:53 UTC22
    })
99524cf9f17catagen2026-10-10 10:53 UTC23
}
99524cf9f17catagen2026-10-10 10:53 UTC24
99524cf9f17catagen2026-10-10 10:53 UTC25
fn unhex(text: &str) -> Option<Vec<u8>> {
99524cf9f17catagen2026-10-10 10:53 UTC26
    if !text.len().is_multiple_of(2) {
99524cf9f17catagen2026-10-10 10:53 UTC27
        return None;
99524cf9f17catagen2026-10-10 10:53 UTC28
    }
99524cf9f17catagen2026-10-10 10:53 UTC29
    (0..text.len())
99524cf9f17catagen2026-10-10 10:53 UTC30
        .step_by(2)
99524cf9f17catagen2026-10-10 10:53 UTC31
        .map(|i| u8::from_str_radix(text.get(i..i + 2)?, 16).ok())
99524cf9f17catagen2026-10-10 10:53 UTC32
        .collect()
99524cf9f17catagen2026-10-10 10:53 UTC33
}
99524cf9f17catagen2026-10-10 10:53 UTC34
99524cf9f17catagen2026-10-10 10:53 UTC35
pub fn sign(name: &str, value: &str) -> String {
99524cf9f17catagen2026-10-10 10:53 UTC36
    let tag = mac(&format!("{name}={value}")).finalize().into_bytes();
99524cf9f17catagen2026-10-10 10:53 UTC37
    format!("{value}.{}", hex(&tag))
99524cf9f17catagen2026-10-10 10:53 UTC38
}
99524cf9f17catagen2026-10-10 10:53 UTC39
99524cf9f17catagen2026-10-10 10:53 UTC40
pub fn verify<'a>(name: &str, signed: &'a str) -> Option<&'a str> {
99524cf9f17catagen2026-10-10 10:53 UTC41
    let (value, tag) = signed.rsplit_once('.')?;
99524cf9f17catagen2026-10-10 10:53 UTC42
    mac(&format!("{name}={value}"))
99524cf9f17catagen2026-10-10 10:53 UTC43
        .verify_slice(&unhex(tag)?)
99524cf9f17catagen2026-10-10 10:53 UTC44
        .ok()?;
99524cf9f17catagen2026-10-10 10:53 UTC45
    Some(value)
99524cf9f17catagen2026-10-10 10:53 UTC46
}
99524cf9f17catagen2026-10-10 10:53 UTC47
99524cf9f17catagen2026-10-10 10:53 UTC48
/// Mixes the server secret into a seed, so a public seed and public source cannot predict rolls offline.
99524cf9f17catagen2026-10-10 10:53 UTC49
pub fn mix_seed(seed: u64) -> u64 {
99524cf9f17catagen2026-10-10 10:53 UTC50
    let tag = mac(&format!("seed={seed}")).finalize().into_bytes();
99524cf9f17catagen2026-10-10 10:53 UTC51
    u64::from_le_bytes(tag[..8].try_into().expect("sha256 is longer than 8 bytes"))
99524cf9f17catagen2026-10-10 10:53 UTC52
}