use base64ct::{Base64UrlUnpadded, Encoding};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
type HmacSha256 = Hmac<Sha256>;
pub mod voprf;
use voprf as v;
pub mod provider;
#[derive(Debug)]
pub enum Error {
Decode,
Verify,
Internal,
}
pub struct Client(v::Client);
pub struct Server(v::Server);
pub struct Verifier(v::Verifier);
pub struct BlindState {
inner: v::BlindState,
}
pub fn nullifier_key(issuer_id: &str, token_output_b64: &str) -> String {
let mut h = Sha256::new();
h.update(issuer_id.as_bytes());
h.update(token_output_b64.as_bytes());
Base64UrlUnpadded::encode_string(&h.finalize())
}
impl Client {
pub fn new(ctx: &[u8]) -> Self {
Self(v::Client::new(ctx))
}
pub fn blind(&mut self, input: &[u8]) -> Result<(String, BlindState), Error> {
let (blinded_raw, st) = self.0.blind(input).map_err(|_| Error::Internal)?;
Ok((
Base64UrlUnpadded::encode_string(&blinded_raw),
BlindState { inner: st },
))
}
pub fn finalize(
self,
state: BlindState,
evaluation_b64: &str,
issuer_pubkey_sec1_compressed: &[u8],
) -> Result<(String, String), Error> {
let eval_raw = Base64UrlUnpadded::decode_vec(evaluation_b64).map_err(|_| Error::Decode)?;
let (token_raw, out_raw) = self
.0
.finalize(state.inner, &eval_raw, issuer_pubkey_sec1_compressed)
.map_err(|_| Error::Verify)?;
Ok((
Base64UrlUnpadded::encode_string(&token_raw),
Base64UrlUnpadded::encode_string(&out_raw),
))
}
}
impl Server {
pub fn from_secret_key(sk_bytes: [u8; 32], ctx: &[u8]) -> Result<Self, Error> {
v::Server::from_secret_key(sk_bytes, ctx)
.map(Self)
.map_err(|_| Error::Internal)
}
pub fn public_key_sec1_compressed(&self) -> [u8; 33] {
self.0.public_key_sec1_compressed()
}
pub fn evaluate_with_proof(&self, blinded_b64: &str) -> Result<String, Error> {
let blinded_raw = Base64UrlUnpadded::decode_vec(blinded_b64).map_err(|_| Error::Decode)?;
let eval_raw = self.0.evaluate(&blinded_raw).map_err(|_| Error::Internal)?;
Ok(Base64UrlUnpadded::encode_string(&eval_raw))
}
}
impl Verifier {
pub fn new(ctx: &[u8]) -> Self {
Self(v::Verifier::new(ctx))
}
pub fn verify(
&self,
token_b64: &str,
issuer_pubkey_sec1_compressed: &[u8],
) -> Result<String, Error> {
let tok_raw = Base64UrlUnpadded::decode_vec(token_b64).map_err(|_| Error::Decode)?;
let out_raw = self
.0
.verify(&tok_raw, issuer_pubkey_sec1_compressed)
.map_err(|_| Error::Verify)?;
Ok(Base64UrlUnpadded::encode_string(&out_raw))
}
}
pub const TOKEN_MAC_LEN: usize = 32;
pub const TOKEN_SIGNATURE_LEN: usize = 64;
pub const TOKEN_FORMAT_V1_MAC: u8 = 0x01; pub const TOKEN_FORMAT_V2_SIGNATURE: u8 = 0x02;
pub const TOKEN_LEN_V1: usize = 131 + TOKEN_MAC_LEN; pub const TOKEN_LEN_V2: usize = 131 + TOKEN_SIGNATURE_LEN;
pub fn compute_token_mac(
mac_key: &[u8; 32],
token_bytes: &[u8],
kid: &str,
exp: i64,
issuer_id: &str,
) -> [u8; 32] {
let mut mac = HmacSha256::new_from_slice(mac_key).expect("HMAC can take key of any size");
mac.update(token_bytes);
mac.update(kid.as_bytes());
mac.update(&exp.to_be_bytes());
mac.update(issuer_id.as_bytes());
mac.finalize().into_bytes().into()
}
pub fn verify_token_mac(
mac_key: &[u8; 32],
token_bytes: &[u8],
received_mac: &[u8; 32],
kid: &str,
exp: i64,
issuer_id: &str,
) -> bool {
let computed = compute_token_mac(mac_key, token_bytes, kid, exp, issuer_id);
use subtle::ConstantTimeEq;
bool::from(computed.ct_eq(received_mac))
}
pub fn compute_token_signature(
issuer_sk: &[u8; 32],
token_bytes: &[u8],
kid: &str,
exp: i64,
issuer_id: &str,
) -> Result<[u8; 64], Error> {
use p256::ecdsa::{signature::Signer, SigningKey};
let mut msg = Vec::new();
msg.extend_from_slice(token_bytes);
msg.extend_from_slice(kid.as_bytes());
msg.extend_from_slice(&exp.to_be_bytes());
msg.extend_from_slice(issuer_id.as_bytes());
let msg_hash = Sha256::digest(&msg);
let signing_key = SigningKey::from_bytes(issuer_sk.into()).map_err(|_| Error::Internal)?;
let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
Ok(signature.to_bytes().into())
}
pub fn verify_token_signature(
issuer_pubkey: &[u8],
token_bytes: &[u8],
received_signature: &[u8; 64],
kid: &str,
exp: i64,
issuer_id: &str,
) -> bool {
use p256::ecdsa::{signature::Verifier, VerifyingKey};
let mut msg = Vec::new();
msg.extend_from_slice(token_bytes);
msg.extend_from_slice(kid.as_bytes());
msg.extend_from_slice(&exp.to_be_bytes());
msg.extend_from_slice(issuer_id.as_bytes());
let msg_hash = Sha256::digest(&msg);
let verifying_key = match VerifyingKey::from_sec1_bytes(issuer_pubkey) {
Ok(key) => key,
Err(_) => return false,
};
let signature = match p256::ecdsa::Signature::from_bytes(received_signature.into()) {
Ok(sig) => sig,
Err(_) => return false,
};
verifying_key.verify(&msg_hash, &signature).is_ok()
}
pub fn derive_mac_key_v2(
server_sk: &[u8; 32],
issuer_id: &str,
key_id: &str,
epoch: u32,
) -> [u8; 32] {
use hkdf::Hkdf;
let info = format!("freebird-mac-v1|{}|{}|{}", issuer_id, key_id, epoch);
let hkdf = Hkdf::<Sha256>::new(
Some(b"freebird-mac-salt"), server_sk,
);
let mut mac_key = [0u8; 32];
hkdf.expand(info.as_bytes(), &mut mac_key)
.expect("32 bytes is a valid HKDF output length");
mac_key
}
pub fn sign_message(secret_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], Error> {
use p256::ecdsa::{signature::Signer, SigningKey};
let msg_hash = Sha256::digest(message);
let signing_key = SigningKey::from_bytes(secret_key.into()).map_err(|_| Error::Internal)?;
let signature: p256::ecdsa::Signature = signing_key.sign(&msg_hash);
Ok(signature.to_bytes().into())
}
pub fn verify_message_signature(public_key: &[u8], message: &[u8], signature: &[u8; 64]) -> bool {
use p256::ecdsa::{signature::Verifier, VerifyingKey};
let msg_hash = Sha256::digest(message);
let verifying_key = match VerifyingKey::from_sec1_bytes(public_key) {
Ok(key) => key,
Err(_) => return false,
};
let sig = match p256::ecdsa::Signature::from_bytes(signature.into()) {
Ok(s) => s,
Err(_) => return false,
};
verifying_key.verify(&msg_hash, &sig).is_ok()
}
pub fn derive_mac_key(server_sk: &[u8; 32], info: &[u8]) -> [u8; 32] {
use hkdf::Hkdf;
let hkdf = Hkdf::<Sha256>::new(None, server_sk);
let mut mac_key = [0u8; 32];
hkdf.expand(info, &mut mac_key)
.expect("32 bytes is a valid HKDF output length");
mac_key
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn end_to_end() {
let ctx = b"freebird-v1";
let sk = [7u8; 32];
let server = Server::from_secret_key(sk, ctx).unwrap();
let pk = server.public_key_sec1_compressed();
let mut client = Client::new(ctx);
let (blinded_b64, st) = client.blind(b"hello world").unwrap();
let eval_b64 = server.evaluate_with_proof(&blinded_b64).unwrap();
let (token_b64, out_cli_b64) = client.finalize(st, &eval_b64, &pk).unwrap();
let verifier = Verifier::new(ctx);
let out_ver_b64 = verifier.verify(&token_b64, &pk).unwrap();
assert_eq!(out_cli_b64, out_ver_b64);
let n1 = nullifier_key("issuer:freebird:v1", &out_ver_b64);
let n2 = nullifier_key("issuer:freebird:v1", &out_ver_b64);
assert_eq!(n1, n2);
assert!(!n1.is_empty());
}
#[test]
fn test_mac_computation_and_verification() {
let mac_key = [42u8; 32];
let token = vec![1, 2, 3, 4, 5];
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "test-issuer";
let mac = compute_token_mac(&mac_key, &token, kid, exp, issuer_id);
assert_eq!(mac.len(), 32);
assert!(verify_token_mac(
&mac_key, &token, &mac, kid, exp, issuer_id
));
let mut bad_token = token.clone();
bad_token[0] ^= 1;
assert!(!verify_token_mac(
&mac_key, &bad_token, &mac, kid, exp, issuer_id
));
assert!(!verify_token_mac(
&mac_key,
&token,
&mac,
"wrong-kid",
exp,
issuer_id
));
assert!(!verify_token_mac(
&mac_key,
&token,
&mac,
kid,
exp + 1,
issuer_id
));
assert!(!verify_token_mac(
&mac_key,
&token,
&mac,
kid,
exp,
"wrong-issuer"
));
let wrong_mac = [0u8; 32];
assert!(!verify_token_mac(
&mac_key, &token, &wrong_mac, kid, exp, issuer_id
));
}
#[test]
fn test_mac_key_derivation() {
let sk = [7u8; 32];
let info1 = b"freebird:mac:v1";
let info2 = b"freebird:mac:v2";
let key1a = derive_mac_key(&sk, info1);
let key1b = derive_mac_key(&sk, info1);
let key2 = derive_mac_key(&sk, info2);
assert_eq!(key1a, key1b);
assert_ne!(key1a, key2);
assert_ne!(key1a, [0u8; 32]);
}
#[test]
fn test_mac_key_derivation_v2() {
let sk = [7u8; 32];
let issuer = "test-issuer";
let kid = "key-001";
let key1 = derive_mac_key_v2(&sk, issuer, kid, 0);
let key2 = derive_mac_key_v2(&sk, issuer, kid, 0);
assert_eq!(key1, key2);
let key_epoch1 = derive_mac_key_v2(&sk, issuer, kid, 1);
assert_ne!(key1, key_epoch1);
let key_issuer2 = derive_mac_key_v2(&sk, "other-issuer", kid, 0);
assert_ne!(key1, key_issuer2);
let key_kid2 = derive_mac_key_v2(&sk, issuer, "key-002", 0);
assert_ne!(key1, key_kid2);
assert_ne!(key1, [0u8; 32]);
}
#[test]
fn test_mac_constant_time() {
let mac_key = [42u8; 32];
let token = vec![1, 2, 3];
let kid = "kid";
let exp = 123i64;
let issuer = "issuer";
let correct_mac = compute_token_mac(&mac_key, &token, kid, exp, issuer);
for byte_idx in 0..32 {
for bit_idx in 0..8 {
let mut wrong_mac = correct_mac;
wrong_mac[byte_idx] ^= 1 << bit_idx;
assert!(!verify_token_mac(
&mac_key, &token, &wrong_mac, kid, exp, issuer
));
}
}
}
#[test]
fn test_signature_computation_and_verification() {
let sk = [7u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let token = vec![1, 2, 3, 4, 5];
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "test-issuer";
let signature = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
assert_eq!(signature.len(), 64);
assert!(verify_token_signature(
&pubkey, &token, &signature, kid, exp, issuer_id
));
let mut bad_token = token.clone();
bad_token[0] ^= 1;
assert!(!verify_token_signature(
&pubkey, &bad_token, &signature, kid, exp, issuer_id
));
assert!(!verify_token_signature(
&pubkey,
&token,
&signature,
"wrong-kid",
exp,
issuer_id
));
assert!(!verify_token_signature(
&pubkey,
&token,
&signature,
kid,
exp + 1,
issuer_id
));
assert!(!verify_token_signature(
&pubkey,
&token,
&signature,
kid,
exp,
"wrong-issuer"
));
let wrong_signature = [0u8; 64];
assert!(!verify_token_signature(
&pubkey,
&token,
&wrong_signature,
kid,
exp,
issuer_id
));
let mut bad_signature = signature;
bad_signature[0] ^= 1;
assert!(!verify_token_signature(
&pubkey,
&token,
&bad_signature,
kid,
exp,
issuer_id
));
}
#[test]
fn test_signature_determinism() {
let sk = [7u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let token = vec![1, 2, 3, 4, 5];
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "test-issuer";
let sig1 = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
let sig2 = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
assert_eq!(sig1, sig2);
assert!(verify_token_signature(
&pubkey, &token, &sig1, kid, exp, issuer_id
));
assert!(verify_token_signature(
&pubkey, &token, &sig2, kid, exp, issuer_id
));
}
#[test]
fn test_signature_different_keys() {
let sk1 = [7u8; 32];
let sk2 = [8u8; 32];
let ctx = b"freebird-v1";
let server1 = Server::from_secret_key(sk1, ctx).unwrap();
let server2 = Server::from_secret_key(sk2, ctx).unwrap();
let pubkey1 = server1.public_key_sec1_compressed();
let pubkey2 = server2.public_key_sec1_compressed();
let token = vec![1, 2, 3, 4, 5];
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "test-issuer";
let sig1 = compute_token_signature(&sk1, &token, kid, exp, issuer_id).unwrap();
assert!(verify_token_signature(
&pubkey1, &token, &sig1, kid, exp, issuer_id
));
assert!(!verify_token_signature(
&pubkey2, &token, &sig1, kid, exp, issuer_id
));
}
#[test]
fn test_signature_invalid_pubkey() {
let sk = [7u8; 32];
let token = vec![1, 2, 3, 4, 5];
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "test-issuer";
let signature = compute_token_signature(&sk, &token, kid, exp, issuer_id).unwrap();
let bad_pubkey = [0xFFu8; 33];
assert!(!verify_token_signature(
&bad_pubkey,
&token,
&signature,
kid,
exp,
issuer_id
));
let short_pubkey = [0x02u8; 32];
assert!(!verify_token_signature(
&short_pubkey,
&token,
&signature,
kid,
exp,
issuer_id
));
}
#[test]
fn test_signature_with_real_voprf_token() {
let sk = [7u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pk = server.public_key_sec1_compressed();
let mut client = Client::new(ctx);
let (blinded_b64, st) = client.blind(b"hello world").unwrap();
let eval_b64 = server.evaluate_with_proof(&blinded_b64).unwrap();
let (token_b64, _) = client.finalize(st, &eval_b64, &pk).unwrap();
let token_bytes = base64ct::Base64UrlUnpadded::decode_vec(&token_b64).unwrap();
assert_eq!(token_bytes.len(), 131);
let kid = "test-kid-001";
let exp = 1234567890i64;
let issuer_id = "issuer:freebird:v1";
let signature = compute_token_signature(&sk, &token_bytes, kid, exp, issuer_id).unwrap();
assert!(verify_token_signature(
&pk,
&token_bytes,
&signature,
kid,
exp,
issuer_id
));
let mut bad_token = token_bytes.clone();
bad_token[0] ^= 1;
assert!(!verify_token_signature(
&pk, &bad_token, &signature, kid, exp, issuer_id
));
}
#[test]
fn test_generic_message_signing() {
let sk = [42u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let message = b"Hello, Federation!";
let signature = sign_message(&sk, message).unwrap();
assert_eq!(signature.len(), 64);
assert!(verify_message_signature(&pubkey, message, &signature));
let wrong_message = b"Wrong message";
assert!(!verify_message_signature(
&pubkey,
wrong_message,
&signature
));
let sk2 = [43u8; 32];
let server2 = Server::from_secret_key(sk2, ctx).unwrap();
let pubkey2 = server2.public_key_sec1_compressed();
assert!(!verify_message_signature(&pubkey2, message, &signature));
}
#[test]
fn test_generic_message_determinism() {
let sk = [42u8; 32];
let message = b"Deterministic test message";
let sig1 = sign_message(&sk, message).unwrap();
let sig2 = sign_message(&sk, message).unwrap();
assert_eq!(sig1, sig2);
}
#[test]
fn test_generic_message_different_lengths() {
let sk = [42u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let short_msg = b"Hi";
let long_msg = b"This is a much longer message that tests whether the signing function handles variable-length inputs correctly.";
let sig_short = sign_message(&sk, short_msg).unwrap();
let sig_long = sign_message(&sk, long_msg).unwrap();
assert!(verify_message_signature(&pubkey, short_msg, &sig_short));
assert!(verify_message_signature(&pubkey, long_msg, &sig_long));
assert!(!verify_message_signature(&pubkey, short_msg, &sig_long));
assert!(!verify_message_signature(&pubkey, long_msg, &sig_short));
}
#[test]
fn test_generic_message_empty() {
let sk = [42u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let empty_msg = b"";
let sig = sign_message(&sk, empty_msg).unwrap();
assert!(verify_message_signature(&pubkey, empty_msg, &sig));
}
#[test]
fn test_generic_message_invalid_signature_bytes() {
let sk = [42u8; 32];
let ctx = b"freebird-v1";
let server = Server::from_secret_key(sk, ctx).unwrap();
let pubkey = server.public_key_sec1_compressed();
let message = b"Test message";
let bad_sig = [0u8; 64];
assert!(!verify_message_signature(&pubkey, message, &bad_sig));
let bad_sig2 = [0xFFu8; 64];
assert!(!verify_message_signature(&pubkey, message, &bad_sig2));
}
}