swactor/crates/transport/src/crypto.rs

162 lines
4.9 KiB
Rust

//! Ed25519 keypair, signing, and verification.
use crate::NodeId;
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Ed25519 signing keypair.
///
/// Wraps `ed25519_dalek::SigningKey`. The public key half is exposed as a
/// `swactor_transport::NodeId` so peer identity is uniform across the
/// distribution stack.
#[derive(Clone)]
pub struct Keypair {
signing: SigningKey,
}
impl Keypair {
/// Generate a fresh keypair from the OS RNG.
pub fn generate() -> Self {
let mut rng = rand_core::OsRng;
Self {
signing: SigningKey::generate(&mut rng),
}
}
/// Reconstruct a keypair from its 32-byte secret seed.
///
/// # Panics
///
/// Panics if `bytes` is shorter than 32 bytes. Extra bytes are ignored.
pub fn from_bytes(bytes: &[u8]) -> Self {
let mut seed = [0u8; 32];
seed.copy_from_slice(&bytes[..32]);
Self {
signing: SigningKey::from_bytes(&seed),
}
}
/// The 32-byte secret seed for this keypair.
pub fn secret_bytes(&self) -> [u8; 32] {
self.signing.to_bytes()
}
/// The public node identifier (raw ed25519 public key bytes).
pub fn node_id(&self) -> NodeId {
NodeId(self.signing.verifying_key().to_bytes())
}
/// Sign `msg` with the secret half. Result is a 64-byte ed25519 signature.
pub fn sign(&self, msg: &[u8]) -> Signature {
let sig = self.signing.sign(msg);
Signature(sig.to_bytes())
}
}
impl core::fmt::Debug for Keypair {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Don't leak the secret half through Debug.
f.debug_struct("Keypair")
.field("node_id", &self.node_id())
.finish_non_exhaustive()
}
}
/// 64-byte ed25519 signature.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Signature(pub [u8; 64]);
impl Serialize for Signature {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
let mut tup = ser.serialize_tuple(64)?;
for byte in &self.0 {
tup.serialize_element(byte)?;
}
tup.end()
}
}
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct SigVisitor;
impl<'de> Visitor<'de> for SigVisitor {
type Value = Signature;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a 64-byte ed25519 signature")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Signature, A::Error> {
let mut bytes = [0u8; 64];
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
}
Ok(Signature(bytes))
}
}
de.deserialize_tuple(64, SigVisitor)
}
}
impl core::fmt::Debug for Signature {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Signature(")?;
for b in &self.0[..4] {
write!(f, "{b:02x}")?;
}
write!(f, "\u{2026})")
}
}
/// Verify `msg` against `sig` using the public key encoded in `node_id`.
///
/// Returns `false` if the key bytes are not a valid ed25519 point, the
/// signature bytes are not a valid signature, or the check fails.
pub fn verify(node_id: &NodeId, msg: &[u8], sig: &Signature) -> bool {
let Ok(vk) = VerifyingKey::from_bytes(&node_id.0) else {
return false;
};
let sig = ed25519_dalek::Signature::from_bytes(&sig.0);
vk.verify(msg, &sig).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_then_verify_succeeds() {
let kp = Keypair::generate();
let sig = kp.sign(b"hello");
assert!(verify(&kp.node_id(), b"hello", &sig));
}
#[test]
fn verify_rejects_wrong_message() {
let kp = Keypair::generate();
let sig = kp.sign(b"original");
assert!(!verify(&kp.node_id(), b"tampered", &sig));
}
#[test]
fn verify_rejects_wrong_key() {
let a = Keypair::generate();
let b = Keypair::generate();
let sig = a.sign(b"msg");
assert!(!verify(&b.node_id(), b"msg", &sig));
}
#[test]
fn from_bytes_reproduces_identity() {
let kp = Keypair::generate();
let restored = Keypair::from_bytes(&kp.secret_bytes());
assert_eq!(kp.node_id(), restored.node_id());
// And the signatures match too (ed25519 is deterministic).
assert_eq!(
kp.sign(b"deterministic").0,
restored.sign(b"deterministic").0
);
}
}