use base64::{Engine, engine::general_purpose::STANDARD as B64};
use boringtun::x25519::{PublicKey, StaticSecret};
pub struct WgKeypair {
secret: StaticSecret,
public: PublicKey,
}
impl WgKeypair {
#[must_use]
pub fn generate() -> Self {
let mut bytes = [0u8; 32];
getrandom::fill(&mut bytes).expect("OS random source unavailable");
Self::from_secret_bytes(bytes)
}
#[must_use]
pub fn from_secret_bytes(bytes: [u8; 32]) -> Self {
let secret = StaticSecret::from(bytes);
let public = PublicKey::from(&secret);
Self { secret, public }
}
#[must_use]
pub fn secret(&self) -> &StaticSecret {
&self.secret
}
#[must_use]
pub fn public(&self) -> &PublicKey {
&self.public
}
#[must_use]
pub fn private_base64(&self) -> String {
B64.encode(self.secret.to_bytes())
}
#[must_use]
pub fn public_base64(&self) -> String {
B64.encode(self.public.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_produces_distinct_keypairs() {
let a = WgKeypair::generate();
let b = WgKeypair::generate();
assert_ne!(a.private_base64(), b.private_base64());
assert_ne!(a.public_base64(), b.public_base64());
}
#[test]
fn public_is_deterministic_from_secret() {
let bytes = [7u8; 32];
let a = WgKeypair::from_secret_bytes(bytes);
let b = WgKeypair::from_secret_bytes(bytes);
assert_eq!(a.public_base64(), b.public_base64());
}
#[test]
fn distinct_secrets_yield_distinct_public_keys() {
let a = WgKeypair::from_secret_bytes([1u8; 32]);
let b = WgKeypair::from_secret_bytes([2u8; 32]);
assert_ne!(a.public_base64(), b.public_base64());
}
#[test]
fn base64_keys_decode_to_32_bytes() {
let kp = WgKeypair::generate();
assert_eq!(B64.decode(kp.private_base64()).unwrap().len(), 32);
assert_eq!(B64.decode(kp.public_base64()).unwrap().len(), 32);
}
#[test]
fn private_key_round_trips_through_base64() {
let kp = WgKeypair::generate();
let decoded: [u8; 32] = B64.decode(kp.private_base64()).unwrap().try_into().unwrap();
let restored = WgKeypair::from_secret_bytes(decoded);
assert_eq!(kp.public_base64(), restored.public_base64());
}
}