use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
use crate::NodeId;
#[derive(Clone)]
pub struct NodeIdentity {
signing: SigningKey,
}
impl NodeIdentity {
#[must_use]
pub fn generate() -> Self {
let signing = SigningKey::generate(&mut OsRng);
Self { signing }
}
#[must_use]
pub fn from_seed(seed: [u8; 32]) -> Self {
Self {
signing: SigningKey::from_bytes(&seed),
}
}
#[must_use]
pub fn node_id(&self) -> NodeId {
let pk: VerifyingKey = self.signing.verifying_key();
NodeId::new(pk.to_bytes())
}
#[must_use]
pub fn sign(&self, bytes: &[u8]) -> [u8; 64] {
self.signing.sign(bytes).to_bytes()
}
#[must_use]
pub fn signing_key_bytes(&self) -> [u8; 32] {
self.signing.to_bytes()
}
}
impl std::fmt::Debug for NodeIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NodeIdentity(node_id={})", self.node_id())
}
}
pub fn verify_signature(
node_id: &NodeId,
bytes: &[u8],
signature: &[u8; 64],
) -> Result<(), super::AttestationError> {
let pk = VerifyingKey::from_bytes(&node_id.0)
.map_err(|_| super::AttestationError::InvalidPublicKey(*node_id))?;
let sig = Signature::from_bytes(signature);
pk.verify(bytes, &sig)
.map_err(|_| super::AttestationError::BadSignature(*node_id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_produces_distinct_identities() {
let a = NodeIdentity::generate();
let b = NodeIdentity::generate();
assert_ne!(a.node_id(), b.node_id());
}
#[test]
fn from_seed_is_deterministic() {
let seed = [0xab; 32];
let a = NodeIdentity::from_seed(seed);
let b = NodeIdentity::from_seed(seed);
assert_eq!(a.node_id(), b.node_id());
assert_eq!(a.signing_key_bytes(), b.signing_key_bytes());
}
#[test]
fn sign_and_verify_round_trip() {
let id = NodeIdentity::from_seed([1; 32]);
let msg = b"engenho-revoada attestation payload";
let sig = id.sign(msg);
verify_signature(&id.node_id(), msg, &sig).expect("signature verifies");
}
#[test]
fn tampered_message_fails_verification() {
let id = NodeIdentity::from_seed([2; 32]);
let msg = b"original payload";
let sig = id.sign(msg);
let tampered = b"different payload";
let err = verify_signature(&id.node_id(), tampered, &sig).unwrap_err();
assert!(matches!(err, super::super::AttestationError::BadSignature(_)));
}
#[test]
fn signature_from_wrong_key_fails_verification() {
let alice = NodeIdentity::from_seed([3; 32]);
let bob = NodeIdentity::from_seed([4; 32]);
let msg = b"bob's message";
let sig = bob.sign(msg);
let err = verify_signature(&alice.node_id(), msg, &sig).unwrap_err();
assert!(matches!(err, super::super::AttestationError::BadSignature(_)));
}
#[test]
fn debug_does_not_leak_signing_key() {
let id = NodeIdentity::from_seed([5; 32]);
let debug_str = format!("{id:?}");
let signing_hex = "0505";
assert!(
!debug_str.contains(signing_hex),
"signing key leaked in Debug output: {debug_str}"
);
assert!(debug_str.contains("node_id="));
}
}