//! Signature verification tests. //! //! Tests for Ed25519 signature verification, rejection of invalid //! signatures, and multi-signature validation. use super::*; use crate::error::IngestError; use stemedb_storage::key_codec; /// Test: Assertions with invalid signatures are rejected. #[tokio::test] async fn test_rejects_invalid_signature() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); // Create an assertion with an invalid signature (all zeros) let assertion = Assertion { subject: "Test".to_string(), predicate: "invalid_sig".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { version: 1, agent_id: [1u8; 32], // Invalid: not a valid Ed25519 public key signature: [2u8; 64], // Invalid: not a valid signature timestamp: 1000, }], confidence: 0.95, timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); // Step should fail with InvalidSignature error let result = worker.step().await; assert!(result.is_err(), "Should reject invalid signature"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InvalidSignature(_)), "Error should be InvalidSignature, got: {:?}", err ); // Verify no assertion was stored let count_key = key_codec::assertion_count_key(); let count_entry = store.get(&count_key).await.expect("get"); assert!(count_entry.is_none(), "No assertion should be stored"); } /// Test: Assertions with no signatures are rejected. #[tokio::test] async fn test_rejects_unsigned_assertion() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); // Create an assertion with no signatures let assertion = Assertion { subject: "Test".to_string(), predicate: "unsigned".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![], // No signatures! confidence: 0.95, timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject unsigned assertion"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InvalidSignature(_)), "Error should be InvalidSignature, got: {:?}", err ); } /// Test: Multi-signature assertions require all signatures to be valid. #[tokio::test] async fn test_multisig_all_must_be_valid() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); // Create an assertion with one valid and one invalid signature let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:multisig"; let valid_signature = signing_key.sign(message.as_bytes()); let assertion = Assertion { subject: "Test".to_string(), predicate: "multisig".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![ // Valid signature SignatureEntry { version: 1, agent_id: verifying_key.to_bytes(), signature: valid_signature.to_bytes(), timestamp: 1000, }, // Invalid signature SignatureEntry { version: 1, agent_id: [1u8; 32], signature: [2u8; 64], timestamp: 1001, }, ], confidence: 0.95, timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject when any signature is invalid"); }