diff --git a/src/pcd.rs b/src/pcd.rs index ff93483..e493949 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -9,7 +9,7 @@ use serde_json::{Map, Value}; use sp_client::{bitcoin::{hashes::{sha256t_hash_newtype, Hash, HashEngine}, hex::{DisplayHex, FromHex}, XOnlyPublicKey}, silentpayments::utils::SilentPaymentAddress}; use tsify::Tsify; -use crate::crypto::AAD; +use crate::{crypto::AAD, signature::{AnkValidationNoHash, AnkValidationYesHash, Proof}}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] @@ -181,6 +181,67 @@ impl ValidationRule { Ok(res) } + + 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()) { + return false; + } + + let required_members = (members.len() as f32 * self.quorum).ceil() as usize; + let validating_members = members.iter() + .filter(|member| { + let member_proofs: Vec<&Proof> = proofs.iter() + .filter(|p| member.key_is_part_of_member(&p.get_key())) + .cloned() + .collect(); + + self.satisfy_min_sig_member(member, new_state_hash, &member_proofs).is_ok() + }) + .count(); + + validating_members >= required_members + } + + 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() { + // 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 { + // Even if all proof are valid yes, we don't reach the quota + return Err(Error::msg("Not enough provided proofs to reach quota")); + } + + let mut yes_votes: Vec = Vec::new(); + let mut no_votes: Vec = Vec::new(); + + // Compute both yes and no commitment + let yes = AnkValidationYesHash::from_commitment(new_state_hash).to_byte_array(); + let no = AnkValidationNoHash::from_commitment(new_state_hash).to_byte_array(); + + // Validate proofs here + for proof in proofs { + if !proof.verify().is_ok() { + return Err(Error::msg("Invalid proof")); + } + + let signed_message = proof.get_message(); + + if signed_message == yes { + yes_votes.push(**proof); + } else if signed_message == no { + no_votes.push(**proof); + } else { + return Err(Error::msg("We don't know what this proof signs for")); + } + } + if yes_votes.len() >= required_sigs { + Ok(()) + } else { + Err(Error::msg("Not enough yes votes")) + } + } } #[derive(Debug, Clone, Serialize, Deserialize, Tsify)] @@ -190,6 +251,36 @@ pub struct RoleDefinition { pub validation_rules: Vec, } +impl RoleDefinition { + pub fn is_satisfied(&self, new_state: &Value, previous_state: &Value, proofs: &[&Proof]) -> bool { + // compute the modified fields + let modified_fields: Vec = new_state.as_object().unwrap() + .iter() + .filter_map(|(key, value)| { + let previous_value = previous_state.as_object().unwrap().get(key); + if previous_value.is_none() || value != previous_value.unwrap() { + Some(key.clone()) + } else { + None + } + }) + .collect(); + + let new_state_hash = AnkPcdHash::from_value(new_state); + + // check that for each field we can satisfy at least one rule + modified_fields.iter().all(|field| { + self.validation_rules.iter().any(|rule| rule.is_satisfied(field, new_state_hash, proofs, &self.members)) + }) + } + + pub fn get_applicable_rules(&self, field: &str) -> Vec<&ValidationRule> { + self.validation_rules.iter() + .filter(|rule| rule.fields.contains(&field.to_string())) + .collect() + } +} + pub fn compare_maps(map1: &Map, map2: &Map) -> bool { // First, check if both maps have the same keys if map1.keys().collect::>() != map2.keys().collect::>() {