swactor/crates/distribution/tests/identity.rs
Zachery Aaron Shores-Chmielewski e5df800c83 refactor: consolidate dist tests, add mvp actors and e2e
Fold the fragmented distribution swim/routing/gossip tests into swim_core, routing,
and swim_actor. Prune the dashboard tui and command surfaces. Add mvp-system actors
(node_agent, orchestrator, stage_controller), the local_e2e harness, and gpu worker
e2e.


Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-06-24 13:30:29 +04:00

166 lines
6.2 KiB
Rust

//! Identity and signed-value contracts.
//!
//! These tests pin the stable value layer used by every distribution subsystem: wire-safe IDs,
//! membership states, and host-signed directory claims.
//!
//! Behavioral/correctness guarantees:
//! - Distribution identities are stable wire values.
//! - Cryptographic identity proves ownership of signed directory claims.
//! - Invalid or tampered identity material is rejected, not silently accepted.
//! - Membership-state ordering is stable wherever conflict resolution depends on it.
mod value_identity_and_signed_claims {
//! Value-object and cryptographic correctness for NodeId, signatures, directory claims, and
//! membership-state priority.
use distribution::crypto::{self, Keypair, KeypairExt};
use distribution::types::{DirectoryEntry, MemberState, NodeId, NodeRecord, Signature};
use swactor::actor::ActorAddress;
// ─── Keypair generation and identity ────────────────────────────────────────
#[test]
fn keypair_generates_distinct_identities() {
let kp1 = Keypair::generate();
let kp2 = Keypair::generate();
assert_ne!(kp1.node_id(), kp2.node_id());
}
#[test]
fn keypair_roundtrips_through_secret_bytes() {
let kp = Keypair::generate();
let secret = kp.secret_bytes();
let restored = Keypair::from_bytes(&secret);
assert_eq!(kp.node_id(), restored.node_id());
}
// ─── Sign and verify raw bytes ──────────────────────────────────────────────
#[test]
fn sign_then_verify_succeeds() {
let kp = Keypair::generate();
let msg = b"hello distributed world";
let sig = kp.sign(msg);
assert!(crypto::verify(&kp.node_id(), msg, &sig));
}
#[test]
fn verify_rejects_wrong_message() {
let kp = Keypair::generate();
let sig = kp.sign(b"correct message");
assert!(!crypto::verify(&kp.node_id(), b"wrong message", &sig));
}
#[test]
fn verify_rejects_wrong_key() {
let kp1 = Keypair::generate();
let kp2 = Keypair::generate();
let sig = kp1.sign(b"some data");
assert!(!crypto::verify(&kp2.node_id(), b"some data", &sig));
}
#[test]
fn verify_rejects_corrupted_signature() {
let kp = Keypair::generate();
let msg = b"important data";
let mut sig = kp.sign(msg);
sig.0[0] ^= 0xff; // flip bits
assert!(!crypto::verify(&kp.node_id(), msg, &sig));
}
// ─── Directory entry signing ────────────────────────────────────────────────
#[test]
fn signed_directory_entry_verifies() {
let kp = Keypair::generate();
let actor_addr = ActorAddress::new_random();
let entry = kp.sign_directory_entry(actor_addr, 1);
assert_eq!(entry.actor_addr, actor_addr);
assert_eq!(entry.node_id, kp.node_id());
assert_eq!(entry.generation, 1);
assert!(crypto::verify_directory_entry(&entry));
}
#[test]
fn tampered_directory_entry_fails_verification() {
let kp = Keypair::generate();
let actor_addr = ActorAddress::new_random();
let mut entry = kp.sign_directory_entry(actor_addr, 1);
// Tamper with generation
entry.generation = 999;
assert!(!crypto::verify_directory_entry(&entry));
}
#[test]
fn directory_entry_signed_by_wrong_key_fails() {
let kp1 = Keypair::generate();
let kp2 = Keypair::generate();
let actor_addr = ActorAddress::new_random();
let mut entry = kp1.sign_directory_entry(actor_addr, 1);
// Replace node_id with a different key — signature won't match
entry.node_id = kp2.node_id();
assert!(!crypto::verify_directory_entry(&entry));
}
// ─── Serde round-trips ─────────────────────────────────────────────────────
#[test]
fn node_id_serde_roundtrip() {
let kp = Keypair::generate();
let id = kp.node_id();
let json = serde_json::to_string(&id).unwrap();
let back: NodeId = serde_json::from_str(&json).unwrap();
assert_eq!(id, back);
}
#[test]
fn signature_serde_roundtrip() {
let kp = Keypair::generate();
let sig = kp.sign(b"test");
let json = serde_json::to_string(&sig).unwrap();
let back: Signature = serde_json::from_str(&json).unwrap();
assert_eq!(sig, back);
}
#[test]
fn directory_entry_serde_roundtrip() {
let kp = Keypair::generate();
let actor_addr = ActorAddress::new_random();
let entry = kp.sign_directory_entry(actor_addr, 42);
let json = serde_json::to_string(&entry).unwrap();
let back: DirectoryEntry = serde_json::from_str(&json).unwrap();
assert_eq!(entry.actor_addr, back.actor_addr);
assert_eq!(entry.node_id, back.node_id);
assert_eq!(entry.generation, back.generation);
assert_eq!(entry.signature, back.signature);
assert!(crypto::verify_directory_entry(&back));
}
#[test]
fn node_record_serde_roundtrip() {
let kp = Keypair::generate();
let record = NodeRecord {
node_id: kp.node_id(),
state: MemberState::Alive,
incarnation: 5,
};
let json = serde_json::to_string(&record).unwrap();
let back: NodeRecord = serde_json::from_str(&json).unwrap();
assert_eq!(record.node_id, back.node_id);
assert_eq!(record.incarnation, back.incarnation);
}
// ─── MemberState ordering ───────────────────────────────────────────────────
#[test]
fn member_state_dead_overrides_suspect_overrides_alive() {
assert!(MemberState::Dead > MemberState::Suspect);
assert!(MemberState::Suspect > MemberState::Alive);
assert!(MemberState::Dead > MemberState::Alive);
}
}