use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use iroh_docs::NamespaceId;
use serde::{Deserialize, Serialize};
use crate::{Result, SyncwebError};
const MEMBER_LIST_SIGNATURE_CONTEXT: &[u8] = b"syncweb/network-membership/v1\0";
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SignedMemberList {
pub network_id: String,
pub owner: String,
pub sequence: u64,
pub members: Vec<MemberEntry>,
pub updated_at: u64,
pub signature: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MemberEntry {
pub key: String,
pub joined_at: u64,
pub role: MemberRole,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum MemberRole {
Admin,
Member,
}
impl SignedMemberList {
#[must_use]
pub fn new(network_id: &str, owner: &str) -> Self {
Self {
network_id: network_id.to_owned(),
owner: owner.to_owned(),
sequence: 0,
members: Vec::new(),
updated_at: current_unix_secs(),
signature: String::new(),
}
}
pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
let owner_hex = hex::encode(signing_key.verifying_key().to_bytes());
if self.owner != owner_hex {
return Err(SyncwebError::InvalidSignature(
"signing key does not match owner field".to_owned(),
));
}
let message = self.serialize_unsigned()?;
let mut signed_bytes = Vec::new();
signed_bytes.extend_from_slice(MEMBER_LIST_SIGNATURE_CONTEXT);
signed_bytes.extend_from_slice(&message);
self.signature = hex::encode(signing_key.sign(&signed_bytes).to_bytes());
Ok(())
}
pub fn verify(&self) -> Result<()> {
let key_bytes = hex::decode(&self.owner)
.map_err(|error| SyncwebError::InvalidSignature(format!("invalid owner key hex: {error}")))?;
let key_array: [u8; 32] = key_bytes
.try_into()
.map_err(|_length_error| SyncwebError::InvalidSignature("owner key must be 32 bytes".to_owned()))?;
let verifying_key = VerifyingKey::from_bytes(&key_array)
.map_err(|error| SyncwebError::InvalidSignature(format!("invalid owner key: {error}")))?;
let sig_bytes = hex::decode(&self.signature)
.map_err(|error| SyncwebError::InvalidSignature(format!("invalid signature hex: {error}")))?;
let sig_array: [u8; 64] = sig_bytes
.try_into()
.map_err(|_length_error| SyncwebError::InvalidSignature("signature must be 64 bytes".to_owned()))?;
let signature = Signature::from_bytes(&sig_array);
let message = self.serialize_unsigned()?;
let mut signed_bytes = Vec::new();
signed_bytes.extend_from_slice(MEMBER_LIST_SIGNATURE_CONTEXT);
signed_bytes.extend_from_slice(&message);
verifying_key
.verify(&signed_bytes, &signature)
.map_err(|error| SyncwebError::InvalidSignature(format!("signature verification failed: {error}")))
}
fn serialize_unsigned(&self) -> Result<Vec<u8>> {
let mut unsigned = self.clone();
unsigned.signature = String::new();
serde_json::to_vec(&unsigned).map_err(|error| SyncwebError::operation("failed to serialize member list", error))
}
}
#[must_use]
pub fn network_doc_namespace(network_id: &[u8; 32], shared_secret: &[u8; 32]) -> NamespaceId {
let mut hasher = blake3::Hasher::new();
hasher.update(b"syncweb/network-doc/v1\0");
hasher.update(network_id);
hasher.update(shared_secret);
NamespaceId::from(hasher.finalize().as_bytes())
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct NetworkInfo {
pub name: String,
pub label: String,
pub created_at: u64,
}
fn current_unix_secs() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}