From 3f633cf5946cc3cf39bfcd995c0fa55bf3f91886 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 4 Oct 2024 11:45:04 +0200 Subject: [PATCH] Add ValidationRule tests + some fixes --- src/pcd.rs | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 247 insertions(+), 5 deletions(-) diff --git a/src/pcd.rs b/src/pcd.rs index e493949..0c01128 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -184,7 +184,7 @@ impl ValidationRule { pub fn is_satisfied(&self, field: &str, new_state_hash: AnkPcdHash, proofs: &[&Proof], members: &[Member]) -> bool { // Check if this rule applies to the field - if !self.fields.contains(&field.to_string()) { + if !self.fields.contains(&field.to_string()) || members.is_empty() { return false; } @@ -204,11 +204,19 @@ impl ValidationRule { } pub fn satisfy_min_sig_member(&self, member: &Member, new_state_hash: AnkPcdHash, proofs: &[&Proof]) -> Result<()> { - let required_sigs = (member.get_addresses().len() as f32 * self.min_sig_member).ceil() as usize; - if required_sigs > proofs.len() { + if proofs.len() == 0 { + return Err(Error::msg("Can't validate with 0 proof")); + } + let registered_devices = member.get_addresses().len(); + if proofs.len() > registered_devices { // We can't have more proofs than registered devices for one member - return Err(Error::msg("More proofs than devices for member")); - } else if proofs.len() < required_sigs { + return Err(Error::msg("More proofs than requirefor member")); + } + + let required_sigs = (registered_devices as f32 * self.min_sig_member).ceil() as usize; + // println!("required_sigs {} and proofs.len() {}", required_sigs, proofs.len()); + + if proofs.len() < required_sigs { // Even if all proof are valid yes, we don't reach the quota return Err(Error::msg("Not enough provided proofs to reach quota")); } @@ -329,3 +337,237 @@ fn compare_arrays(array1: &Vec, array2: &Vec) -> bool { true } + +#[cfg(test)] +mod tests { + use serde_json::json; + use sp_client::{bitcoin::{secp256k1::SecretKey, Network}, spclient::{SpClient, SpWallet, SpendKey}}; + + use super::*; + use crate::{ + pcd::{Member, AnkPcdHash}, + signature::{AnkHash, Proof}, + }; + + fn create_alice_wallet() -> SpWallet { + SpWallet::new( + SpClient::new( + "default".to_owned(), + SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973").unwrap(), + SpendKey::Secret(SecretKey::from_str("a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c").unwrap()), + None, + Network::Signet + ).unwrap(), + None, + vec![] + ).unwrap() + } + + fn create_bob_wallet() -> SpWallet { + SpWallet::new( + SpClient::new( + "default".to_owned(), + SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b").unwrap(), + SpendKey::Secret(SecretKey::from_str("dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e").unwrap()), + None, + Network::Signet + ).unwrap(), + None, + vec![] + ).unwrap() + } + + #[test] + fn test_validation_rule_new() { + // Valid input + let fields = vec!["field1".to_string(), "field2".to_string()]; + let validation_rule = ValidationRule::new(0.5, fields.clone(), 0.5); + assert!(validation_rule.is_ok()); + let rule = validation_rule.unwrap(); + assert_eq!(rule.quorum, 0.5); + assert_eq!(rule.fields, fields); + assert_eq!(rule.min_sig_member, 0.5); + + // Invalid quorum (< 0.0) + let validation_rule = ValidationRule::new(-0.1, fields.clone(), 0.5); + assert!(validation_rule.is_err()); + + // Invalid quorum (> 1.0) + let validation_rule = ValidationRule::new(1.1, fields.clone(), 0.5); + assert!(validation_rule.is_err()); + + // Invalid min_sig_member (< 0.0) + let validation_rule = ValidationRule::new(0.5, fields.clone(), -0.1); + assert!(validation_rule.is_err()); + + // Invalid min_sig_member (> 1.0) + let validation_rule = ValidationRule::new(0.5, fields.clone(), 1.1); + assert!(validation_rule.is_err()); + + // Empty fields + let validation_rule = ValidationRule::new(0.5, vec![], 0.5); + assert!(validation_rule.is_err()); + } + + #[test] + fn test_is_satisfied() { + let alice_wallet = create_alice_wallet(); + let bob_wallet = create_bob_wallet(); + + let fields = vec!["field1".to_string(), "field2".to_string()]; + let validation_rule = ValidationRule::new(0.5, fields.clone(), 0.5).unwrap(); + + let pcd = json!({"field1": "value1", "field2": "value2"}); + let new_state_hash = AnkPcdHash::from_value(&pcd); + + let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash); + let validation_hash2 = AnkValidationNoHash::from_commitment(new_state_hash); + + let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap(); + let bob_spend_key: SecretKey = bob_wallet.get_client().get_spend_key().try_into().unwrap(); + + let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key); + let bob_proof = Proof::new(AnkHash::ValidationYes(validation_hash1), bob_spend_key); + + let members = vec![ + Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + ]; + + // We test that the rule is satisfied with only bob proof + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&bob_proof], &members); + assert!(result); + // Since Alice voted no, rule shouldn't be satisfied only with her proof + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&alice_proof], &members); + assert!(!result); + // Since the quorum is 0.5, Bob yes should be enough to satisfy even with Alice's no + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&alice_proof, &bob_proof], &members); + assert!(result); + } + + #[test] + fn test_is_satisfied_error_cases() { + let alice_wallet = create_alice_wallet(); + let bob_wallet = create_bob_wallet(); + + let fields = vec!["field1".to_string(), "field2".to_string()]; + let validation_rule = ValidationRule::new(0.5, fields.clone(), 0.5).unwrap(); + + let pcd = json!({"field1": "value1", "field2": "value2"}); + let new_state_hash = AnkPcdHash::from_value(&pcd); + + let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash); + let validation_hash2 = AnkValidationNoHash::from_commitment(new_state_hash); + + let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap(); + let bob_spend_key: SecretKey = bob_wallet.get_client().get_spend_key().try_into().unwrap(); + + let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key); + let bob_proof = Proof::new(AnkHash::ValidationYes(validation_hash1), bob_spend_key); + + let proofs = vec![ + &alice_proof, + &bob_proof + ]; + + let members = vec![ + Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + ]; + + // Test with empty members list + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &vec![]); + assert!(!result); + + // Test with no matching field + let result = validation_rule.is_satisfied("nonexistent_field", new_state_hash, &proofs, &members); + assert!(!result); + } + + #[test] + fn test_is_satisfied_error_with_alice_providing_proofs_for_bob() { + let alice_wallet = create_alice_wallet(); + let bob_wallet = create_bob_wallet(); + + let fields = vec!["field1".to_string(), "field2".to_string()]; + let validation_rule = ValidationRule::new(1.0, fields.clone(), 0.5).unwrap(); + + let pcd = json!({"field1": "value1", "field2": "value2"}); + let new_state_hash = AnkPcdHash::from_value(&pcd); + + let validation_hash = AnkValidationYesHash::from_commitment(new_state_hash); + + let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap(); + + // Both proofs are signed by Alice + let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); + let alice_proof_2 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); + + let proofs = vec![ + &alice_proof_1, + &alice_proof_2 + ]; + + let members = vec![ + Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + ]; + + // Test case where both proofs are signed by Alice, but both Alice and Bob are passed as members + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members); + assert!(!result); + } + + #[test] + fn test_is_satisfied_error_quorum_half_with_alice_providing_two_proofs() { + let alice_wallet = create_alice_wallet(); + let bob_wallet = create_bob_wallet(); + + let fields = vec!["field1".to_string(), "field2".to_string()]; + let validation_rule = ValidationRule::new(0.5, fields.clone(), 0.5).unwrap(); + + let pcd = json!({"field1": "value1", "field2": "value2"}); + let new_state_hash = AnkPcdHash::from_value(&pcd); + + let validation_hash = AnkValidationYesHash::from_commitment(new_state_hash); + + let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap(); + + // Both proofs are signed by Alice + let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); + let alice_proof_2 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); + + let proofs = vec![ + &alice_proof_1, + &alice_proof_2 + ]; + + let members = vec![ + Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(), + ]; + + // Test case where quorum is 0.5, but Alice provides two proofs. This should fail since the quorum requires different members. + let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members); + assert!(!result); + } + + #[test] + fn test_satisfy_min_sig_member() { + let fields = vec!["field1".to_string()]; + let validation_rule = ValidationRule::new(0.5, fields, 0.5).unwrap(); + + let alice_wallet = create_alice_wallet(); + + let member = Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(); + let pcd = json!({"field1": "value1"}); + let new_state_hash = AnkPcdHash::from_value(&pcd); + let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap(); + + let proof = Proof::new(AnkHash::ValidationYes(AnkValidationYesHash::from_commitment(new_state_hash)), alice_spend_key); + let proofs = vec![&proof]; + + let result = validation_rule.satisfy_min_sig_member(&member, new_state_hash, &proofs); + assert!(result.is_ok()); // Example check - make more meaningful assertions based on real Proof and Member implementations + } +}