//! Ed25519 identity and packet signing. //! //! Each node has an Ed25519 keypair: //! - **Private key** (32 bytes): never leaves the node. //! Used to sign every outgoing packet. //! - **Public key** (32 bytes): shared with peers. //! Used to verify incoming packets. //! - **NodeId** = public key (32 bytes). Direct 1:1 //! binding, no hashing. use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use crate::id::NodeId; /// Ed25519 signature size (64 bytes). pub const SIGNATURE_SIZE: usize = 64; /// Ed25519 public key size (32 bytes). pub const PUBLIC_KEY_SIZE: usize = 32; /// A node's cryptographic identity. /// /// Contains the Ed25519 keypair and the derived NodeId. /// The NodeId is the public key directly (32 bytes) /// deterministically bound to the keypair. pub struct Identity { signing_key: SigningKey, verifying_key: VerifyingKey, node_id: NodeId, } impl Identity { /// Generate a new random identity. pub fn generate() -> Self { let mut seed = [0u8; 32]; crate::sys::random_bytes(&mut seed); Self::from_seed(seed) } /// Create an identity from a 32-byte seed. /// /// Deterministic: same seed → same keypair → same /// NodeId. pub fn from_seed(seed: [u8; 32]) -> Self { let signing_key = SigningKey::from_bytes(&seed); let verifying_key = signing_key.verifying_key(); let node_id = NodeId::from_bytes(*verifying_key.as_bytes()); Self { signing_key, verifying_key, node_id, } } /// The node's 256-bit ID (= public key). pub fn node_id(&self) -> &NodeId { &self.node_id } /// The Ed25519 public key (32 bytes). pub fn public_key(&self) -> &[u8; PUBLIC_KEY_SIZE] { self.verifying_key.as_bytes() } /// Sign data with the private key. /// /// Returns a 64-byte Ed25519 signature. pub fn sign(&self, data: &[u8]) -> [u8; SIGNATURE_SIZE] { let sig = self.signing_key.sign(data); sig.to_bytes() } /// Verify a signature against a public key. /// /// This is a static method — used to verify packets /// from other nodes using their public key. pub fn verify( public_key: &[u8; PUBLIC_KEY_SIZE], data: &[u8], signature: &[u8], ) -> bool { // Length check is not timing-sensitive (length is // public). ed25519_dalek::verify() is constant-time. if signature.len() != SIGNATURE_SIZE { return false; } let Ok(vk) = VerifyingKey::from_bytes(public_key) else { return false; }; let mut sig_bytes = [0u8; SIGNATURE_SIZE]; sig_bytes.copy_from_slice(signature); let sig = Signature::from_bytes(&sig_bytes); vk.verify(data, &sig).is_ok() } } impl std::fmt::Debug for Identity { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Identity({})", self.node_id) } } #[cfg(test)] mod tests { use super::*; #[test] fn generate_unique() { let a = Identity::generate(); let b = Identity::generate(); assert_ne!(a.node_id(), b.node_id()); } #[test] fn from_seed_deterministic() { let seed = [0x42u8; 32]; let a = Identity::from_seed(seed); let b = Identity::from_seed(seed); assert_eq!(a.node_id(), b.node_id()); assert_eq!(a.public_key(), b.public_key()); } #[test] fn node_id_is_pubkey() { let id = Identity::generate(); let expected = NodeId::from_bytes(*id.public_key()); assert_eq!(*id.node_id(), expected); } #[test] fn sign_verify() { let id = Identity::generate(); let data = b"hello world"; let sig = id.sign(data); assert!(Identity::verify(id.public_key(), data, &sig)); } #[test] fn verify_wrong_data() { let id = Identity::generate(); let sig = id.sign(b"correct"); assert!(!Identity::verify(id.public_key(), b"wrong", &sig)); } #[test] fn verify_wrong_key() { let id1 = Identity::generate(); let id2 = Identity::generate(); let sig = id1.sign(b"data"); assert!(!Identity::verify(id2.public_key(), b"data", &sig)); } #[test] fn verify_truncated_sig() { let id = Identity::generate(); assert!(!Identity::verify( id.public_key(), b"data", &[0u8; 10] // too short )); } #[test] fn signature_size() { let id = Identity::generate(); let sig = id.sign(b"test"); assert_eq!(sig.len(), SIGNATURE_SIZE); } }