atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
72cdd1267600b80b912d1e1225c422211b490326
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/secret.rsRust58 lines1.6 KB
1
use std::fmt::Write as _;
2
3
use hmac::{Hmac, Mac};
4
use sha2::Sha256;
5
6
pub struct Secret(Vec<u8>);
7
8
fn hex(bytes: &[u8]) -> String {
9
    bytes.iter().fold(String::new(), |mut out, b| {
10
        let _ = write!(out, "{b:02x}");
11
        out
12
    })
13
}
14
15
fn unhex(text: &str) -> Option<Vec<u8>> {
16
    if !text.len().is_multiple_of(2) {
17
        return None;
18
    }
19
    (0..text.len())
20
        .step_by(2)
21
        .map(|i| u8::from_str_radix(text.get(i..i + 2)?, 16).ok())
22
        .collect()
23
}
24
25
impl Secret {
26
    pub fn new(key: Vec<u8>) -> Self {
27
        Self(key)
28
    }
29
30
    pub fn random() -> Self {
31
        Self(rand::random::<[u8; 32]>().to_vec())
32
    }
33
34
    fn mac(&self, message: &str) -> Hmac<Sha256> {
35
        let mut mac = Hmac::<Sha256>::new_from_slice(&self.0).expect("hmac accepts any key length");
36
        mac.update(message.as_bytes());
37
        mac
38
    }
39
40
    pub fn sign(&self, name: &str, value: &str) -> String {
41
        let tag = self.mac(&format!("{name}={value}")).finalize().into_bytes();
42
        format!("{value}.{}", hex(&tag))
43
    }
44
45
    pub fn verify<'a>(&self, name: &str, signed: &'a str) -> Option<&'a str> {
46
        let (value, tag) = signed.rsplit_once('.')?;
47
        self.mac(&format!("{name}={value}"))
48
            .verify_slice(&unhex(tag)?)
49
            .ok()?;
50
        Some(value)
51
    }
52
53
    /// Mixes the secret into a seed, so a public seed and public source cannot predict rolls offline.
54
    pub fn mix_seed(&self, seed: u64) -> u64 {
55
        let tag = self.mac(&format!("seed={seed}")).finalize().into_bytes();
56
        u64::from_le_bytes(tag[..8].try_into().expect("sha256 is longer than 8 bytes"))
57
    }
58
}