use std::collections::{HashMap, hash_map::Entry};
use std::hash::Hash;
use crate::{
errors::CryptoError, exchange::prekey::SignedPrekey, signatures::keypair::PublicKeyBytes,
};
pub struct PrekeyBundle {
pub identity: PublicKeyBytes,
pub prekey: SignedPrekey,
}
struct Account {
identity: PublicKeyBytes,
last_resort: SignedPrekey,
one_time: Vec<SignedPrekey>,
}
pub struct PrekeyServer<K> {
accounts: HashMap<K, Account>,
}
impl<K: Eq + Hash> PrekeyServer<K> {
pub fn new() -> Self {
Self {
accounts: HashMap::new(),
}
}
pub fn publish(
&mut self,
user: K,
identity: PublicKeyBytes,
last_resort: SignedPrekey,
one_time: Vec<SignedPrekey>,
) -> Result<(), CryptoError> {
if !last_resort.verify(&identity) || !one_time.iter().all(|p| p.verify(&identity)) {
return Err(CryptoError::InvalidSignature);
}
match self.accounts.entry(user) {
Entry::Occupied(mut entry) => {
let account = entry.get_mut();
if account.identity != identity {
return Err(CryptoError::InvalidKey);
}
account.last_resort = last_resort;
account.one_time.extend(one_time);
}
Entry::Vacant(entry) => {
entry.insert(Account {
identity,
last_resort,
one_time,
});
}
}
Ok(())
}
pub fn fetch(&mut self, user: &K) -> Option<PrekeyBundle> {
let account = self.accounts.get_mut(user)?;
let prekey = account
.one_time
.pop()
.unwrap_or_else(|| account.last_resort.clone());
Some(PrekeyBundle {
identity: account.identity,
prekey,
})
}
pub fn one_time_remaining(&self, user: &K) -> usize {
self.accounts.get(user).map_or(0, |a| a.one_time.len())
}
}
impl<K: Eq + Hash> Default for PrekeyServer<K> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
exchange::pair::KEMPair,
signatures::keypair::{SignerPair, ViewOperations},
};
fn prekey(owner: &mut SignerPair) -> SignedPrekey {
SignedPrekey::new(owner, &KEMPair::create()).unwrap()
}
#[test]
fn test_one_time_prekeys_are_handed_out_once() {
let mut bob = SignerPair::create();
let last_resort = prekey(&mut bob);
let one_time = vec![prekey(&mut bob), prekey(&mut bob)];
let mut ids: Vec<_> = one_time.iter().map(|p| p.id()).collect();
let mut server = PrekeyServer::new();
server
.publish("bob", *bob.pub_key_bytes(), last_resort.clone(), one_time)
.unwrap();
assert_eq!(server.one_time_remaining(&"bob"), 2);
let mut handed_out = vec![
server.fetch(&"bob").unwrap().prekey.id(),
server.fetch(&"bob").unwrap().prekey.id(),
];
handed_out.sort();
ids.sort();
assert_eq!(handed_out, ids);
assert_eq!(server.one_time_remaining(&"bob"), 0);
let bundle = server.fetch(&"bob").unwrap();
assert_eq!(bundle.prekey.id(), last_resort.id());
assert_eq!(bundle.identity, *bob.pub_key_bytes());
assert!(server.fetch(&"bob").is_some());
assert!(server.fetch(&"carol").is_none());
assert_eq!(server.one_time_remaining(&"carol"), 0);
}
#[test]
fn test_publish_rejects_foreign_prekeys_and_identity_change() {
let mut bob = SignerPair::create();
let mut mallory = SignerPair::create();
let mut server = PrekeyServer::new();
let result = server.publish("bob", *bob.pub_key_bytes(), prekey(&mut mallory), vec![]);
assert!(matches!(result, Err(CryptoError::InvalidSignature)));
let result = server.publish(
"bob",
*bob.pub_key_bytes(),
prekey(&mut bob),
vec![prekey(&mut mallory)],
);
assert!(matches!(result, Err(CryptoError::InvalidSignature)));
assert!(server.fetch(&"bob").is_none());
server
.publish("bob", *bob.pub_key_bytes(), prekey(&mut bob), vec![])
.unwrap();
let result = server.publish(
"bob",
*mallory.pub_key_bytes(),
prekey(&mut mallory),
vec![],
);
assert!(matches!(result, Err(CryptoError::InvalidKey)));
let new_last_resort = prekey(&mut bob);
server
.publish(
"bob",
*bob.pub_key_bytes(),
new_last_resort.clone(),
vec![prekey(&mut bob)],
)
.unwrap();
assert_eq!(server.one_time_remaining(&"bob"), 1);
server.fetch(&"bob").unwrap();
assert_eq!(
server.fetch(&"bob").unwrap().prekey.id(),
new_last_resort.id()
);
}
#[test]
fn test_default_is_empty() {
let mut server = PrekeyServer::<u32>::default();
assert!(server.fetch(&1).is_none());
}
}