166 lines
6.2 KiB
Rust
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);
|
|
}
|
|
}
|