Add validation logic to ValidationRules and RuleDefinition

This commit is contained in:
Sosthene 2024-10-04 09:25:05 +02:00 committed by Nicolas Cantu
parent 4726c59861
commit 233d7f979d

View File

@ -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<Proof> = Vec::new();
let mut no_votes: Vec<Proof> = 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<ValidationRule>,
}
impl RoleDefinition {
pub fn is_satisfied(&self, new_state: &Value, previous_state: &Value, proofs: &[&Proof]) -> bool {
// compute the modified fields
let modified_fields: Vec<String> = 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<String, Value>, map2: &Map<String, Value>) -> bool {
// First, check if both maps have the same keys
if map1.keys().collect::<Vec<&String>>() != map2.keys().collect::<Vec<&String>>() {