use crate::tools::server_id::ServerId;
use crate::tools::time::{MILLIS_IN_DAY, MILLIS_IN_MONTH, TimeMillis, TimeMillisBytes};
use crate::tools::time_provider::time_provider::TimeProvider;
use crate::tools::types::{Hash, Id, PQCommitmentBytes, Pow, Salt, Signature, SignatureKey, VerificationKey, VerificationKeyBytes};
use crate::tools::{config, hashing, signing, tools};
use crate::{anyhow_assert_eq, anyhow_assert_ge};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct PeerPow {
#[serde(rename = "sp")]
pub sponsor_id: Id,
#[serde(rename = "t")]
pub timestamp: TimeMillis,
#[serde(rename = "ch")]
pub content_hash: Hash, #[serde(rename = "s")]
pub salt: Salt,
#[serde(rename = "z")]
pub pow: Pow,
}
impl PeerPow {
pub fn new(sponsor_id: Id, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes, timestamp: TimeMillis, content_hash: Hash, salt: Salt) -> anyhow::Result<PeerPow> {
let (pow, _) = Self::pow(&sponsor_id, verification_key, pq_commitment_bytes, ×tamp.encode_be(), &content_hash, &salt)?;
Ok(Self {
sponsor_id,
timestamp,
content_hash,
salt,
pow,
})
}
pub fn zero() -> Self {
PeerPow {
sponsor_id: Id::zero(),
timestamp: TimeMillis::zero(),
content_hash: Hash::zero(),
salt: Salt::zero(),
pow: Pow(0),
}
}
pub fn random() -> Self {
Self {
sponsor_id: Id::random(),
timestamp: TimeMillis::random(),
content_hash: Hash::random(),
salt: Salt::random(),
pow: Pow(tools::random_u8()),
}
}
pub fn verify(&self, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes) -> anyhow::Result<()> {
let (pow, _) = Self::pow(&self.sponsor_id, verification_key, pq_commitment_bytes, &self.timestamp.encode_be(), &self.content_hash, &self.salt)?;
match pow == self.pow {
true => Ok(()),
false => Err(anyhow::anyhow!("pow does not match inputs: {} != {}", self.pow, pow)),
}
}
pub fn pow_decayed_day(&self, current_time_millis: TimeMillis) -> f64 {
let pow_halflife_millis = MILLIS_IN_DAY;
let elapsed_millis = current_time_millis - self.timestamp;
(self.pow.0 as f64) / 2.0f64.powf(elapsed_millis.0 as f64 / pow_halflife_millis.0 as f64)
}
pub fn pow_decayed_month(&self, current_time_millis: TimeMillis) -> f64 {
let pow_halflife_millis = MILLIS_IN_MONTH;
let elapsed_millis = current_time_millis - self.timestamp;
(self.pow.0 as f64) / 2.0f64.powf(elapsed_millis.0 as f64 / pow_halflife_millis.0 as f64)
}
pub fn pow(sponsor_id: &Id, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes, timestamp: &TimeMillisBytes, hash: &Hash, salt: &Salt) -> anyhow::Result<(Pow, Hash)> {
ServerId::pow_measure(sponsor_id, verification_key, pq_commitment_bytes, timestamp, hash, salt)
}
pub fn own_pow(&self, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes) -> anyhow::Result<(Pow, Hash)> {
Self::pow(&self.sponsor_id, verification_key, pq_commitment_bytes, &self.timestamp.encode_be(), &self.content_hash, &self.salt)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ClientPow {
pub peer_verification_key: VerificationKeyBytes,
pub peer_pq_commitment_bytes: PQCommitmentBytes,
pub peer_pow: PeerPow,
}
impl ClientPow {
pub fn measure(peer_verification_key: &VerificationKeyBytes, peer_pq_commitment_bytes: &PQCommitmentBytes, sponsor_id: &Id, timestamp: TimeMillis, content_hash: &Hash, salt: &Salt) -> anyhow::Result<Self> {
let (pow, _) = ServerId::pow_measure(sponsor_id, peer_verification_key, peer_pq_commitment_bytes, ×tamp.encode_be(), content_hash, salt)?;
Ok(Self {
peer_verification_key: *peer_verification_key,
peer_pq_commitment_bytes: *peer_pq_commitment_bytes,
peer_pow: PeerPow {
sponsor_id: *sponsor_id,
timestamp,
content_hash: *content_hash,
salt: *salt,
pow,
},
})
}
pub fn verify(&self) -> anyhow::Result<()> {
self.peer_pow.verify(&self.peer_verification_key, &self.peer_pq_commitment_bytes)
}
}
impl fmt::Display for PeerPow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "timestamp={} hash={} salt={} pow={}", self.timestamp, hex::encode(self.content_hash), hex::encode(self.salt), self.pow,)
}
}
#[derive(Serialize, Deserialize, PartialEq, Clone)]
pub struct Peer {
pub id: Id,
#[serde(rename = "vkb")]
pub verification_key_bytes: VerificationKeyBytes,
#[serde(rename = "pqcb")]
pub pq_commitment_bytes: PQCommitmentBytes,
#[serde(rename = "pi")]
pub pow_initial: PeerPow, #[serde(rename = "pcd")]
pub pow_current_day: PeerPow, #[serde(rename = "pcm")]
pub pow_current_month: PeerPow,
#[serde(rename = "ver")]
pub version: String, #[serde(rename = "addr")]
pub address: String,
#[serde(rename = "ts")]
pub timestamp: TimeMillis,
#[serde(rename = "sig")]
pub signature: Signature, }
impl fmt::Debug for Peer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for Peer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[ id={} version={} address={} pows=[ {} {} {} ] timestamp={} ]",
self.id, self.version, self.address, self.pow_initial.pow, self.pow_current_day.pow, self.pow_current_month.pow, self.timestamp
)
}
}
impl Peer {
pub fn zero() -> Peer {
Peer {
id: Id::zero(),
verification_key_bytes: VerificationKeyBytes::zero(),
pq_commitment_bytes: PQCommitmentBytes::zero(),
pow_initial: PeerPow::zero(),
pow_current_day: PeerPow::zero(),
pow_current_month: PeerPow::zero(),
version: env!("CARGO_PKG_VERSION").to_string(),
address: String::new(),
timestamp: TimeMillis::zero(),
signature: Signature::zero(),
}
}
pub fn signature_hash_generate(&self) -> anyhow::Result<Hash> {
Ok(hashing::hash_multiple(&[
self.id.as_ref(),
self.verification_key_bytes.as_ref(),
self.pq_commitment_bytes.as_ref(),
self.pow_initial.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
self.pow_current_day.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
self.pow_current_month.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
self.address.as_bytes(),
self.timestamp.encode_be().as_ref(),
]))
}
pub fn sign(&mut self, time_provider: &dyn TimeProvider, signature_key: &SignatureKey) -> anyhow::Result<()> {
self.timestamp = time_provider.current_time_millis();
let signature_hash = self.signature_hash_generate()?;
self.signature = signing::sign(signature_key, signature_hash.as_ref());
Ok(())
}
pub fn verify(&self) -> anyhow::Result<()> {
self.verify_signature()?;
self.verify_server_id()?;
self.verify_pows()?;
Ok(())
}
fn verify_signature(&self) -> anyhow::Result<()> {
let signature_hash = self.signature_hash_generate()?;
signing::verify(&VerificationKey::from_bytes(&self.verification_key_bytes)?, &self.signature, signature_hash.as_ref())?;
Ok(())
}
fn verify_server_id(&self) -> anyhow::Result<()> {
let (pow, pow_hash) = ServerId::pow_measure(
&self.pow_initial.sponsor_id,
&self.verification_key_bytes,
&self.pq_commitment_bytes,
&self.pow_initial.timestamp.encode_be(),
&self.pow_initial.content_hash,
&self.pow_initial.salt,
)?;
anyhow_assert_ge!(&pow, &config::SERVER_KEY_POW_MIN, "insufficient server_id pow");
let id = ServerId::server_pow_hash_to_id(pow_hash)?;
anyhow_assert_eq!(&self.id, &id, "served_id mismatch");
Ok(())
}
fn verify_pows(&self) -> anyhow::Result<()> {
self.pow_initial.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
self.pow_current_day.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
self.pow_current_month.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::protocol::peer::{Peer, PeerPow};
use crate::tools::server_id::ServerId;
use crate::tools::time::TimeMillis;
use crate::tools::time_provider::time_provider::RealTimeProvider;
use crate::tools::pow_generator::single_threaded_pow_generator::SingleThreadedPowGenerator;
use crate::tools::tools;
use crate::tools::types::{Pow, VerificationKeyBytes};
async fn get_random_ingredients() -> anyhow::Result<(ServerId, Peer)> {
let time_provider = RealTimeProvider::default();
let pow_generator = SingleThreadedPowGenerator::new();
let server_id = ServerId::new("own_pow", &time_provider, Pow(0), true, &pow_generator).await?;
let mut peer = server_id.to_peer(&time_provider)?;
peer.pow_current_day = PeerPow::random();
peer.pow_current_month = PeerPow::random();
peer.address = tools::random_base64(16);
peer.sign(&time_provider, &server_id.keys.signature_key)?;
Ok((server_id, peer))
}
#[tokio::test]
async fn signing_test() -> anyhow::Result<()> {
let (_, peer) = get_random_ingredients().await?;
peer.verify_signature()?;
Ok(())
}
#[tokio::test]
async fn signing_fail_test() -> anyhow::Result<()> {
{
let (_, mut peer) = get_random_ingredients().await?;
peer.verification_key_bytes = VerificationKeyBytes::zero();
peer.verify_signature().unwrap_err();
}
{
let (_, mut peer) = get_random_ingredients().await?;
peer.pow_initial = PeerPow::random();
peer.verify_signature().unwrap_err();
}
{
let (_, mut peer) = get_random_ingredients().await?;
peer.pow_current_day = PeerPow::random();
peer.verify_signature().unwrap_err();
}
{
let (_, mut peer) = get_random_ingredients().await?;
peer.pow_current_month = PeerPow::random();
peer.verify_signature().unwrap_err();
}
{
let (_, mut peer) = get_random_ingredients().await?;
peer.address = tools::random_base64(16);
peer.verify_signature().unwrap_err();
}
{
let (_, mut peer) = get_random_ingredients().await?;
peer.timestamp = TimeMillis::random();
peer.verify_signature().unwrap_err();
}
Ok(())
}
}