From 339b88c7857a1c75911a99c9b24d941fe4480d60 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Sat, 5 Apr 2025 18:34:55 +0200 Subject: [PATCH] [bug] actually handle demiurge case --- src/process.rs | 121 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 19 deletions(-) diff --git a/src/process.rs b/src/process.rs index 95448aa..9a5c138 100644 --- a/src/process.rs +++ b/src/process.rs @@ -8,7 +8,7 @@ use sp_client::{bitcoin::{OutPoint, Transaction}, silentpayments::SilentPaymentA use tsify::Tsify; use crate::{ - pcd::{Member, Pcd, PcdCommitments, RoleDefinition, Roles}, serialization::{deserialize_hex, hex_array_btree, serialize_hex, OutPointMemberMap}, signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}, MutexExt, SpecialRoles, APOPHIS, PAIREDADDRESSES, PAIRING + pcd::{Member, Pcd, PcdCommitments, RoleDefinition, Roles, ValidationRule}, serialization::{deserialize_hex, hex_array_btree, serialize_hex, OutPointMemberMap}, signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}, MutexExt, SpecialRoles, APOPHIS, PAIREDADDRESSES, PAIRING }; fn extract_paired_addresses(paired_addresses: &Value) -> anyhow::Result> { @@ -82,6 +82,35 @@ impl ProcessState { } } + fn handle_demiurge(&self, demiurge_role: &RoleDefinition) -> anyhow::Result { + if demiurge_role.members.is_empty() { + return Err(anyhow::Error::msg("Invalid demiurge role: members can't be empty")); + } + // validation_rules is empty + if !demiurge_role.validation_rules.is_empty() { + return Err(anyhow::Error::msg("Invalid demiurge role: validation_rules must be empty")); + } + + // if demiurge_role.storages.is_empty() { + // return Err(anyhow::Error::msg("Invalid demiurge role: storages can't be empty")); + // } + + let all_keys: Vec = self.pcd_commitment.keys().map(|k| k.clone()).collect(); + + // define the rule + let validation_rule = ValidationRule::new( + 1.0, + all_keys.clone(), + 1.0 + )?; + let role = RoleDefinition { + members: demiurge_role.members.clone(), + storages: demiurge_role.storages.clone(), + validation_rules: vec![validation_rule] + }; + Ok(role) + } + /// This is a simplified and streamlined validation for obliteration state fn handle_obliteration(&self, apophis: &RoleDefinition, members_list: &OutPointMemberMap) -> anyhow::Result<()> { // Apophis should have only one rule @@ -93,19 +122,7 @@ impl ProcessState { if obliteration_rule.fields.len() != 1 { return Err(anyhow::Error::msg("Should have only one field")); }; if obliteration_rule.fields.get(0).unwrap() != empty_field { return Err(anyhow::Error::msg("Field should be empty")); }; - let members: Vec<&Member> = apophis.members.iter() - .filter_map(|outpoint| { - members_list.0.get(outpoint) - }) - .collect(); - - if apophis.validation_rules.iter().all( - |r| r.is_satisfied(empty_field, [0u8; 32], &self.validation_tokens, &members).is_ok() - ) { - Ok(()) - } else { - Err(anyhow::Error::msg("Apophis is not satisfied")) - } + apophis.is_satisfied(vec![empty_field.to_owned()], [0u8; 32], &self.validation_tokens, &members_list) } fn handle_pairing(&self, pairing_role: RoleDefinition, previous_addresses: Vec) -> anyhow::Result<()> { @@ -131,7 +148,7 @@ impl ProcessState { vec![&previous_member] }; - paired_addresses_rule.iter().next().unwrap().is_satisfied(PAIREDADDRESSES, self.state_id, &self.validation_tokens, members.as_slice()) + paired_addresses_rule.get(0).unwrap().is_satisfied(PAIREDADDRESSES, self.state_id, &self.validation_tokens, members.as_slice()) } pub fn is_valid(&self, previous_state: Option<&ProcessState>, members_list: &OutPointMemberMap) -> anyhow::Result<()> { @@ -176,7 +193,18 @@ impl ProcessState { // That's optional though SpecialRoles::Demiurge => { // If we're not in initial state just ignore it - if previous_state.is_some() { return None; } + if previous_state.is_some() { + return None; + } else { + // We try to validate with demiurge + match self.handle_demiurge(role_def) { + Ok(role) => return Some(role), + Err(e) => { + log::error!("{}", e.to_string()); + return None; + } + } + } // Otherwise we just continue normal validation } // We already handled other special roles @@ -678,6 +706,11 @@ mod tests { let validation_rule4 = ValidationRule::new(1.0, vec!["public1".to_owned(), "public2".to_owned()], 0.5).unwrap(); let apophis_rule = ValidationRule::new(1.0, vec!["".to_owned()], 1.0).unwrap(); + let role_demiurge = RoleDefinition { + members: vec![OutPoint::from_str(ALICE_BOB_PAIRING).unwrap()], + validation_rules: vec![], + storages: vec![] + }; let role_def1 = RoleDefinition { members: vec![OutPoint::from_str(ALICE_BOB_PAIRING).unwrap()], validation_rules: vec![validation_rule1], @@ -708,6 +741,7 @@ mod tests { }; let roles: BTreeMap = BTreeMap::from([ + ("demiurge".to_owned(), role_demiurge), ("role1".to_owned(), role_def1), ("role2".to_owned(), role_def2), ("role_roles".to_owned(), role_def_roles), @@ -715,7 +749,7 @@ mod tests { ("apophis".to_owned(), role_def_apophis) ]); - let clear_pcd: BTreeMap = serde_json::from_value(json!({ + let private_data: BTreeMap = serde_json::from_value(json!({ "field1": "value1", "field2": "value2", })).unwrap(); @@ -727,7 +761,7 @@ mod tests { "public2": "public2", })).unwrap(); - ProcessState::new(outpoint, Pcd::new(clear_pcd), Pcd::new(public_data), Roles::new(roles)).unwrap() + ProcessState::new(outpoint, Pcd::new(private_data), Pcd::new(public_data), Roles::new(roles)).unwrap() } fn create_pairing_process_one() -> ProcessState { @@ -838,6 +872,55 @@ mod tests { assert!(result.is_ok()); } + #[test] + /// AliceBob is the demiurge + fn test_valid_demiurge() { + let mut state = dummy_process_state(); + // We sign with Alice and Carol keys + let alice_key: SecretKey = create_alice_wallet() + .get_spend_key() + .try_into() + .unwrap(); + let bob_key: SecretKey = create_bob_wallet() + .get_spend_key() + .try_into() + .unwrap(); + let message_hash = state.get_message_hash(true).unwrap(); + state.validation_tokens.push(Proof::new(message_hash, alice_key)); + state.validation_tokens.push(Proof::new(message_hash, bob_key)); + let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); + assert!(result.is_ok()); + } + + #[test] + /// Carol tries to bypass demiurge role + fn test_error_demiurge() { + let mut state = dummy_process_state(); + // We sign with Alice and Carol keys + let carol_key: SecretKey = create_alice_wallet() + .get_spend_key() + .try_into() + .unwrap(); + let message_hash = state.get_message_hash(true).unwrap(); + state.validation_tokens.push(Proof::new(message_hash, carol_key)); + let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); + assert!(result.is_err()); + } + + #[test] + /// Carol tries to bypass demiurge role + fn test_error_demiurge_not_init_state() { + let mut state = dummy_process_state(); + // We sign with Alice and Carol keys + let carol_key: SecretKey = create_alice_wallet() + .get_spend_key() + .try_into() + .unwrap(); + let message_hash = state.get_message_hash(true).unwrap(); + state.validation_tokens.push(Proof::new(message_hash, carol_key)); + let result = state.is_valid(Some(&dummy_process_state()), &OutPointMemberMap(get_members_map())); + assert!(result.is_err()); + } #[test] fn test_valid_pairing() { let mut pairing_first_state = create_pairing_process_one(); @@ -1096,7 +1179,7 @@ mod tests { state.validation_tokens.push(Proof::new(message_hash_yes, alice_key)); state.validation_tokens.push(Proof::new(message_hash_yes, bob_key)); state.validation_tokens.push(Proof::new(message_hash_no, carol_key)); - let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); + let result = state.is_valid(Some(&dummy_process_state()), &OutPointMemberMap(get_members_map())); assert!(result.is_err()); assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); }