diff --git a/src/device.rs b/src/device.rs index 54d430c..713daa7 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,11 +5,12 @@ use tsify::Tsify; use wasm_bindgen::prelude::*; use sp_client::{ - bitcoin::{absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction, XOnlyPublicKey}, - silentpayments::{ - utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress - }, - OutputSpendStatus, OwnedOutput, SpClient + bitcoin::{ + absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction, + XOnlyPublicKey, + }, + silentpayments::{utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress}, + OutputSpendStatus, OwnedOutput, SpClient, }; use crate::{pcd::Member, silentpayments::SpWallet}; @@ -50,24 +51,27 @@ impl Device { } pub fn get_balance(&self) -> Amount { - self.sp_wallet.get_outputs().values() + self.sp_wallet + .get_outputs() + .values() .filter(|output| output.spend_status == OutputSpendStatus::Unspent) .fold(Amount::ZERO, |acc, x| acc + x.amount) } - pub fn update_outputs_with_transaction(&mut self, tx: &Transaction, blockheight: u32, partial_tweak: PublicKey) -> anyhow::Result> { + pub fn update_outputs_with_transaction( + &mut self, + tx: &Transaction, + blockheight: u32, + partial_tweak: PublicKey, + ) -> anyhow::Result> { // First check that we haven't already scanned this transaction let txid = tx.txid(); for i in 0..tx.output.len() { - if self - .sp_wallet - .get_outputs() - .contains_key(&OutPoint { - txid, - vout: i as u32, - }) - { + if self.sp_wallet.get_outputs().contains_key(&OutPoint { + txid, + vout: i as u32, + }) { return Err(anyhow::Error::msg("Transaction already scanned")); } } @@ -128,7 +132,11 @@ impl Device { let txid = tx.txid(); // update outputs that we own and that are spent for input in tx.input.iter() { - if let Some(prevout) = self.sp_wallet.get_mut_outputs().get_mut(&input.previous_output) { + if let Some(prevout) = self + .sp_wallet + .get_mut_outputs() + .get_mut(&input.previous_output) + { // This is spent by this tx prevout.spend_status = OutputSpendStatus::Spent(*txid.as_byte_array()); res.insert(input.previous_output, prevout.clone()); diff --git a/src/hash.rs b/src/hash.rs index ab87874..266059b 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,6 +1,8 @@ use sp_client::bitcoin::{ - consensus::{serialize, Encodable}, hashes::{sha256t_hash_newtype, Hash, HashEngine}, OutPoint - }; + consensus::{serialize, Encodable}, + hashes::{sha256t_hash_newtype, Hash, HashEngine}, + OutPoint, +}; sha256t_hash_newtype! { pub struct AnkPcdTag = hash_str("4nk/Pcd"); @@ -14,7 +16,9 @@ impl AnkPcdHash { let mut eng = AnkPcdHash::engine(); eng.input(value); eng.input(label); - serialize(outpoint).consensus_encode(&mut eng).expect("hash engine don't return errors"); + serialize(outpoint) + .consensus_encode(&mut eng) + .expect("hash engine don't return errors"); AnkPcdHash::from_engine(eng) } } diff --git a/src/lib.rs b/src/lib.rs index cabb26e..5660264 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,21 +4,21 @@ use std::sync::{Mutex, MutexGuard}; pub use aes_gcm; pub use env_logger; +pub use js_sys; pub use log; pub use rand; -pub use sp_client; pub use serde; pub use serde_json; pub use serde_wasm_bindgen; +pub use sp_client; pub use tsify; pub use wasm_bindgen; -pub use js_sys; pub use zstd; pub mod crypto; pub mod device; -pub mod hash; pub mod error; +pub mod hash; pub mod network; pub mod pcd; pub mod prd; @@ -43,8 +43,8 @@ const ROLESLABEL: &str = "roles"; #[derive(Debug, PartialEq, Eq)] pub enum SpecialRoles { Demiurge, // Only valid for the first state of a process - Pairing, // Special validation rules for pairing process - Apophis, // Users in this role have the power to destroy the process + Pairing, // Special validation rules for pairing process + Apophis, // Users in this role have the power to destroy the process } impl std::fmt::Display for SpecialRoles { @@ -54,7 +54,7 @@ impl std::fmt::Display for SpecialRoles { } impl From<&SpecialRoles> for &str { - fn from(value: &SpecialRoles) -> Self { + fn from(value: &SpecialRoles) -> Self { match value { SpecialRoles::Demiurge => DEMIURGE, SpecialRoles::Pairing => PAIRING, @@ -114,10 +114,7 @@ impl MutexExt for Mutex { Err(poison_error) => { let data = poison_error.into_inner(); - log::debug!( - "Failed to lock Mutex (poisoned). Data was: {:?}", - data - ); + log::debug!("Failed to lock Mutex (poisoned). Data was: {:?}", data); Err(anyhow::anyhow!("Failed to lock Mutex (poisoned)")) } diff --git a/src/network.rs b/src/network.rs index ab2f5b0..5664f6d 100644 --- a/src/network.rs +++ b/src/network.rs @@ -74,7 +74,7 @@ impl AnkFlag { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct CommitMessage { - pub process_id: OutPoint, + pub process_id: OutPoint, pub pcd_commitment: PcdCommitments, // map of field <=> hash of the clear value pub roles: Roles, pub public_data: Pcd, @@ -89,7 +89,7 @@ impl CommitMessage { pcd_commitment: PcdCommitments, roles: Roles, public_data: Pcd, - validation_tokens: Vec + validation_tokens: Vec, ) -> Self { Self { process_id, @@ -172,7 +172,12 @@ pub struct HandshakeMessage { } impl HandshakeMessage { - pub fn new(sp_address: String, peers_list: OutPointMemberMap, processes_list: OutPointProcessMap, chain_tip: u32) -> Self { + pub fn new( + sp_address: String, + peers_list: OutPointMemberMap, + processes_list: OutPointProcessMap, + chain_tip: u32, + ) -> Self { Self { sp_address, peers_list, diff --git a/src/pcd.rs b/src/pcd.rs index 97b8247..374d5c6 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -3,16 +3,14 @@ use rs_merkle::{algorithms::Sha256, MerkleTree}; use serde::ser::SerializeStruct; use std::collections::btree_map::Keys; use std::collections::{BTreeMap, HashSet}; -use std::hash::{Hash as StdHash, Hasher}; use std::fmt; +use std::hash::{Hash as StdHash, Hasher}; use std::io::Write; use serde::{Deserialize, Serialize}; use serde_json::Value; use sp_client::{ - bitcoin::{ - hashes::Hash, secp256k1::PublicKey, OutPoint - }, + bitcoin::{hashes::Hash, secp256k1::PublicKey, OutPoint}, silentpayments::SilentPaymentAddress, }; use tsify::Tsify; @@ -21,8 +19,8 @@ use crate::hash::AnkPcdHash; use crate::serialization::OutPointMemberMap; use crate::ROLESLABEL; use crate::{ + serialization::hex_array_btree, signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}, - serialization::hex_array_btree }; pub const PCD_VERSION: u8 = 1; @@ -30,40 +28,42 @@ pub(crate) const ZSTD_COMPRESSION_LEVEL: i32 = zstd::DEFAULT_COMPRESSION_LEVEL; pub trait PcdSerializable { fn serialize_to_pcd(&self) -> Result>; - fn deserialize_from_pcd(data: &[u8]) -> Result where Self: Sized; + fn deserialize_from_pcd(data: &[u8]) -> Result + where + Self: Sized; } impl PcdSerializable for serde_json::Value { fn serialize_to_pcd(&self) -> Result> { let mut compressed = Vec::new(); let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; - + encoder.write_all(&[PCD_VERSION])?; encoder.write_all(&[DataType::Json as u8])?; serde_json::to_writer(&mut encoder, self)?; encoder.finish()?; - + Ok(compressed) } fn deserialize_from_pcd(data: &[u8]) -> Result { let mut decompressed = Vec::new(); zstd::stream::copy_decode(data, &mut decompressed)?; - + if decompressed.len() < 3 { return Err(Error::msg("Invalid data: too short")); } - + let version = decompressed[0]; let data_type = DataType::try_from(decompressed[1])?; - + match (version, data_type) { (PCD_VERSION, DataType::Json) => { let json_bytes = &decompressed[2..]; let json_string = String::from_utf8(json_bytes.to_vec())?; Ok(serde_json::from_str(&json_string)?) - }, - _ => Err(Error::msg("Invalid version or data type")) + } + _ => Err(Error::msg("Invalid version or data type")), } } } @@ -72,15 +72,15 @@ impl PcdSerializable for FileBlob { fn serialize_to_pcd(&self) -> Result> { let mut compressed = Vec::new(); let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; - + encoder.write_all(&[PCD_VERSION])?; encoder.write_all(&[DataType::FileBlob as u8])?; - + let type_len = self.r#type.as_bytes().len() as u8; encoder.write_all(&[type_len])?; encoder.write_all(self.r#type.as_bytes())?; encoder.write_all(&self.data)?; - + encoder.finish()?; Ok(compressed) } @@ -88,23 +88,26 @@ impl PcdSerializable for FileBlob { fn deserialize_from_pcd(data: &[u8]) -> Result { let mut decompressed = Vec::new(); zstd::stream::copy_decode(data, &mut decompressed)?; - + if decompressed.len() < 4 { return Err(Error::msg("Invalid data: too short")); } - + let version = decompressed[0]; let data_type = DataType::try_from(decompressed[1])?; - + match (version, data_type) { (PCD_VERSION, DataType::FileBlob) => { let type_len = decompressed[2] as usize; - let type_str = String::from_utf8(decompressed[3..3+type_len].to_vec())?; - let data = decompressed[3+type_len..].to_vec(); - - Ok(FileBlob { r#type: type_str, data }) - }, - _ => Err(Error::msg("Invalid version or data type")) + let type_str = String::from_utf8(decompressed[3..3 + type_len].to_vec())?; + let data = decompressed[3 + type_len..].to_vec(); + + Ok(FileBlob { + r#type: type_str, + data, + }) + } + _ => Err(Error::msg("Invalid version or data type")), } } } @@ -186,7 +189,7 @@ impl StdHash for Member { } impl Member { - pub fn new(sp_addresses: Vec) -> Self{ + pub fn new(sp_addresses: Vec) -> Self { let unique_addresses: HashSet<_> = sp_addresses.into_iter().collect(); let res: Vec = unique_addresses @@ -209,11 +212,13 @@ impl Member { } pub fn get_address_for_key(&self, key: &PublicKey) -> Option { - self.sp_addresses.iter().find(|a| { - let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap(); - addr.get_spend_key() == *key - }) - .cloned() + self.sp_addresses + .iter() + .find(|a| { + let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap(); + addr.get_spend_key() == *key + }) + .cloned() } } @@ -233,13 +238,18 @@ impl IntoIterator for Pcd { impl TryFrom for Pcd { type Error = Error; - fn try_from(value: Value) -> Result { - let as_object = value.as_object().ok_or_else(|| Error::msg("Pcd must be an object"))?; - let map: Result>> = as_object.into_iter().map(|(key, value)| { - // Use the trait method instead of manual serialization - let compressed = value.serialize_to_pcd()?; - Ok((key.clone(), compressed)) - }).collect(); + fn try_from(value: Value) -> Result { + let as_object = value + .as_object() + .ok_or_else(|| Error::msg("Pcd must be an object"))?; + let map: Result>> = as_object + .into_iter() + .map(|(key, value)| { + // Use the trait method instead of manual serialization + let compressed = value.serialize_to_pcd()?; + Ok((key.clone(), compressed)) + }) + .collect(); Ok(Pcd(map?)) } @@ -248,12 +258,15 @@ impl TryFrom for Pcd { impl TryFrom> for Pcd { type Error = Error; - fn try_from(file_blob_map: BTreeMap) -> Result { - let map: Result>> = file_blob_map.into_iter().map(|(key, value)| { - // Use the trait method instead of manual serialization - let compressed = value.serialize_to_pcd()?; - Ok((key, compressed)) - }).collect(); + fn try_from(file_blob_map: BTreeMap) -> Result { + let map: Result>> = file_blob_map + .into_iter() + .map(|(key, value)| { + // Use the trait method instead of manual serialization + let compressed = value.serialize_to_pcd()?; + Ok((key, compressed)) + }) + .collect(); Ok(Pcd(map?)) } @@ -309,7 +322,11 @@ impl Pcd { } } - pub fn insert_serializable(&mut self, key: String, value: &T) -> Result>> { + pub fn insert_serializable( + &mut self, + key: String, + value: &T, + ) -> Result>> { let compressed = value.serialize_to_pcd()?; Ok(self.insert(key, compressed)) } @@ -317,20 +334,29 @@ impl Pcd { #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] -pub struct PcdCommitments(#[serde(with = "hex_array_btree")] #[tsify(type = "Record")] BTreeMap); +pub struct PcdCommitments( + #[serde(with = "hex_array_btree")] + #[tsify(type = "Record")] + BTreeMap, +); impl PcdCommitments { /// Creates a new commitments map with both permissioned and public data, + roles pub fn new(commited_in: &OutPoint, attributes: &Pcd, roles: &Roles) -> Result { let mut field2hash: BTreeMap = BTreeMap::new(); for (field, value) in attributes.iter() { - let tagged_hash = AnkPcdHash::from_pcd_value(value.as_slice(), field.as_bytes(), commited_in); + let tagged_hash = + AnkPcdHash::from_pcd_value(value.as_slice(), field.as_bytes(), commited_in); field2hash.insert(field.to_owned(), tagged_hash.to_byte_array()); } if roles.len() > 0 { let roles_label = String::from(ROLESLABEL); - let roles_hash = AnkPcdHash::from_pcd_value(roles.to_bytes()?.as_slice(), roles_label.as_bytes(), commited_in); + let roles_hash = AnkPcdHash::from_pcd_value( + roles.to_bytes()?.as_slice(), + roles_label.as_bytes(), + commited_in, + ); field2hash.insert(roles_label, roles_hash.to_byte_array()); } // We should probably return an error if roles are empty @@ -346,7 +372,12 @@ impl PcdCommitments { self.0.is_empty() } - pub fn update_with_value(&mut self, outpoint: &OutPoint, field: &str, new_value: &[u8]) -> Result<()> { + pub fn update_with_value( + &mut self, + outpoint: &OutPoint, + field: &str, + new_value: &[u8], + ) -> Result<()> { if let Some(old_hash) = self.get_mut(field) { // We hash the new_value let tagged_hash = AnkPcdHash::from_pcd_value(new_value, field.as_bytes(), outpoint); @@ -363,11 +394,11 @@ impl PcdCommitments { pub fn get(&self, field: &str) -> Option<&[u8; 32]> { self.0.get(field) - } + } pub fn get_mut(&mut self, field: &str) -> Option<&mut [u8; 32]> { self.0.get_mut(field) - } + } pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, [u8; 32]> { self.0.iter() @@ -383,10 +414,7 @@ impl PcdCommitments { /// Since BTreeMap keys order is deterministic, we can guarantee a consistent merkle tree pub fn create_merkle_tree(&self) -> Result> { - let leaves: Vec<[u8; 32]> = self.0 - .values() - .map(|hash| *hash) - .collect(); + let leaves: Vec<[u8; 32]> = self.0.values().map(|hash| *hash).collect(); let merkle_tree = MerkleTree::::from_leaves(leaves.as_slice()); @@ -447,7 +475,12 @@ impl ValidationRule { return Err(Error::msg("Field isn't part of this rule")); } else if members.is_empty() { return Err(Error::msg("Members list is empty")); - } else if self.quorum <= 0.0 || self.quorum > 1.0 || self.quorum.is_sign_negative() || self.quorum.is_nan() { // Just to be sure + } else if self.quorum <= 0.0 + || self.quorum > 1.0 + || self.quorum.is_sign_negative() + || self.quorum.is_nan() + { + // Just to be sure return Err(Error::msg("This rule is read only")); } @@ -455,7 +488,9 @@ impl ValidationRule { let validating_members = members .iter() .filter(|member| { - if member.sp_addresses.is_empty() { return false }; // This can happen when a member in the rule wasn't found in the network + if member.sp_addresses.is_empty() { + return false; + }; // This can happen when a member in the rule wasn't found in the network let member_proofs: Vec<&Proof> = proofs .iter() .filter(|p| member.key_is_part_of_member(&p.get_key())) @@ -466,7 +501,11 @@ impl ValidationRule { }) .count(); - if validating_members >= required_members { Ok(()) } else { Err(Error::msg("Not enough members to validate"))} + if validating_members >= required_members { + Ok(()) + } else { + Err(Error::msg("Not enough members to validate")) + } } pub fn satisfy_min_sig_member( @@ -540,22 +579,22 @@ impl RoleDefinition { ) -> Result<()> { let empty_member = Member::new(vec![]); if diff.iter().all(|field| { - self.validation_rules - .iter() - .any(|rule| { - let members: Vec<&Member> = self.members.iter() - .map(|outpoint| { - if let Some(member) = members_list.0.get(outpoint) { - member - } else { - &empty_member - } - }) - .collect(); - rule.is_satisfied(field, new_state_merkle_root, proofs, &members).is_ok() - }) - }) - { + self.validation_rules.iter().any(|rule| { + let members: Vec<&Member> = self + .members + .iter() + .map(|outpoint| { + if let Some(member) = members_list.0.get(outpoint) { + member + } else { + &empty_member + } + }) + .collect(); + rule.is_satisfied(field, new_state_merkle_root, proofs, &members) + .is_ok() + }) + }) { Ok(()) } else { Err(Error::msg("Failed to validate all rules")) @@ -583,7 +622,6 @@ impl IntoIterator for Roles { } } - impl Roles { pub fn new(roles: BTreeMap) -> Self { Roles(roles) @@ -592,7 +630,7 @@ impl Roles { pub fn is_empty(&self) -> bool { self.0.is_empty() } - + pub fn to_bytes(&self) -> Result> { Ok(serde_json::to_vec(self)?) } @@ -603,11 +641,11 @@ impl Roles { pub fn get(&self, key: &str) -> Option<&RoleDefinition> { self.0.get(key) - } + } pub fn get_mut(&mut self, key: &str) -> Option<&mut RoleDefinition> { self.0.get_mut(key) - } + } pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, RoleDefinition> { self.0.iter() @@ -620,12 +658,12 @@ impl Roles { #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; use serde_json::json; use sp_client::{ bitcoin::{secp256k1::SecretKey, Network}, SpClient, SpendKey, }; + use std::{collections::HashMap, str::FromStr}; use super::*; use crate::{ @@ -635,10 +673,8 @@ mod tests { fn create_alice_wallet() -> SpClient { SpClient::new( - SecretKey::from_str( - "a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973", - ) - .unwrap(), + SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c", @@ -652,10 +688,8 @@ mod tests { fn create_bob_wallet() -> SpClient { SpClient::new( - SecretKey::from_str( - "4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b", - ) - .unwrap(), + SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e", @@ -672,13 +706,23 @@ mod tests { let bob_address = &addresses[1]; HashMap::from([ ( - OutPoint::from_str("b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0").unwrap(), - Member::new(vec![SilentPaymentAddress::try_from(alice_address.as_str()).unwrap()]) + OutPoint::from_str( + "b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0", + ) + .unwrap(), + Member::new(vec![ + SilentPaymentAddress::try_from(alice_address.as_str()).unwrap() + ]), ), ( - OutPoint::from_str("3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0").unwrap(), - Member::new(vec![SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()]) - ) + OutPoint::from_str( + "3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0", + ) + .unwrap(), + Member::new(vec![ + SilentPaymentAddress::try_from(bob_address.as_str()).unwrap() + ]), + ), ]) } @@ -727,16 +771,15 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); // roles are not necessary here, we can leave it empty let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash1 = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash2 = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key); @@ -744,7 +787,7 @@ mod tests { let members_list = get_members_map([ alice_wallet.get_receiving_address().to_string(), - bob_wallet.get_receiving_address().to_string() + bob_wallet.get_receiving_address().to_string(), ]); let members: Vec<&Member> = members_list.values().collect(); @@ -788,16 +831,15 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash_yes = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash_no = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash_no), alice_spend_key); @@ -807,19 +849,27 @@ mod tests { let members_list = get_members_map([ alice_wallet.get_receiving_address().to_string(), - bob_wallet.get_receiving_address().to_string() + bob_wallet.get_receiving_address().to_string(), ]); let members: Vec<&Member> = members_list.values().collect(); // Test with empty members list - let result = - validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &vec![]); + let result = validation_rule.is_satisfied( + fields[0].as_str(), + new_state_merkle_root, + &proofs, + &vec![], + ); assert!(result.is_err()); // Test with no matching field - let result = - validation_rule.is_satisfied("nonexistent_field", new_state_merkle_root, &proofs, &members); + let result = validation_rule.is_satisfied( + "nonexistent_field", + new_state_merkle_root, + &proofs, + &members, + ); assert!(result.is_err()); } @@ -836,15 +886,14 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.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); @@ -854,14 +903,18 @@ mod tests { let members_list = get_members_map([ alice_wallet.get_receiving_address().to_string(), - bob_wallet.get_receiving_address().to_string() + bob_wallet.get_receiving_address().to_string(), ]); let members: Vec<&Member> = members_list.values().collect(); // 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_merkle_root, &proofs, &members); + let result = validation_rule.is_satisfied( + fields[0].as_str(), + new_state_merkle_root, + &proofs, + &members, + ); assert!(result.is_err()); } @@ -878,15 +931,14 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.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); @@ -896,14 +948,18 @@ mod tests { let members_list = get_members_map([ alice_wallet.get_receiving_address().to_string(), - bob_wallet.get_receiving_address().to_string() + bob_wallet.get_receiving_address().to_string(), ]); let members: Vec<&Member> = members_list.values().collect(); // 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_merkle_root, &proofs, &members); + let result = validation_rule.is_satisfied( + fields[0].as_str(), + new_state_merkle_root, + &proofs, + &members, + ); assert!(result.is_err()); } @@ -923,21 +979,23 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let proof = Proof::new( - AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(new_state_merkle_root)), + AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root( + new_state_merkle_root, + )), alice_spend_key, ); let proofs = vec![&proof]; - let result = validation_rule.satisfy_min_sig_member(&member, new_state_merkle_root, &proofs); + let result = + validation_rule.satisfy_min_sig_member(&member, new_state_merkle_root, &proofs); assert!(result.is_ok()); // Example check - make more meaningful assertions based on real Proof and Member implementations } @@ -971,15 +1029,14 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); @@ -987,19 +1044,31 @@ mod tests { let proofs = vec![alice_proof, bob_proof]; - let modified_fields: Vec = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect(); + let modified_fields: Vec = new_state + .as_object() + .unwrap() + .iter() + .map(|(key, _)| key.clone()) + .collect(); - assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok()); + assert!(role_def + .is_satisfied( + modified_fields, + new_state_merkle_root, + &proofs, + &OutPointMemberMap(members) + ) + .is_ok()); } #[test] fn test_no_rule_satisfied() { let alice_wallet = create_alice_wallet(); let bob_wallet = create_bob_wallet(); - + let members_list = get_members_map([ - alice_wallet.get_receiving_address().to_string(), - bob_wallet.get_receiving_address().to_string() + alice_wallet.get_receiving_address().to_string(), + bob_wallet.get_receiving_address().to_string(), ]); let fields = vec!["field1".to_string(), "field2".to_string()]; @@ -1023,16 +1092,15 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); // let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash); let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash), alice_spend_key); @@ -1040,9 +1108,21 @@ mod tests { let proofs = vec![alice_proof, bob_proof]; - let modified_fields: Vec = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect(); + let modified_fields: Vec = new_state + .as_object() + .unwrap() + .iter() + .map(|(key, _)| key.clone()) + .collect(); - assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members_list)).is_err()); + assert!(role_def + .is_satisfied( + modified_fields, + new_state_merkle_root, + &proofs, + &OutPointMemberMap(members_list) + ) + .is_err()); } #[test] @@ -1076,16 +1156,15 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); // let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); @@ -1093,9 +1172,21 @@ mod tests { let proofs = vec![alice_proof, bob_proof]; - let modified_fields: Vec = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect(); + let modified_fields: Vec = new_state + .as_object() + .unwrap() + .iter() + .map(|(key, _)| key.clone()) + .collect(); - assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok()); + assert!(role_def + .is_satisfied( + modified_fields, + new_state_merkle_root, + &proofs, + &OutPointMemberMap(members) + ) + .is_ok()); } #[test] @@ -1129,16 +1220,15 @@ mod tests { let public_data = BTreeMap::new(); let roles = BTreeMap::new(); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); - let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap(); + let commitments = + PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)) + .unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); // let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); - let alice_spend_key: SecretKey = alice_wallet - .get_spend_key() - .try_into() - .unwrap(); + let alice_spend_key: SecretKey = alice_wallet.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::ValidationYes(validation_hash), alice_spend_key); @@ -1146,9 +1236,21 @@ mod tests { let proofs = vec![alice_proof]; - let modified_fields: Vec = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect(); + let modified_fields: Vec = new_state + .as_object() + .unwrap() + .iter() + .map(|(key, _)| key.clone()) + .collect(); - assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_err()); + assert!(role_def + .is_satisfied( + modified_fields, + new_state_merkle_root, + &proofs, + &OutPointMemberMap(members) + ) + .is_err()); } #[test] diff --git a/src/prd.rs b/src/prd.rs index 55e08cb..9ea3708 100644 --- a/src/prd.rs +++ b/src/prd.rs @@ -22,12 +22,12 @@ pub enum PrdType { None, Connect, Message, - Update, // Update an existing process - List, // request a list of items - Response, // Validate (or disagree) with a prd update - Confirm, // Confirm we received an update + Update, // Update an existing process + List, // request a list of items + Response, // Validate (or disagree) with a prd update + Confirm, // Confirm we received an update TxProposal, // Send a psbt asking for recipient signature - Request // asks for the prd update for some state, + Request, // asks for the prd update for some state, } sha256t_hash_newtype! { @@ -71,10 +71,18 @@ pub struct Prd { impl Prd { /// We answer to ack we received a transaction and got the shared_secret /// If validation_tokens is empty we put the proof into it and return it - /// If validation_tokens contains a valid proof signed by ourselves of empty prd, + /// If validation_tokens contains a valid proof signed by ourselves of empty prd, /// we confirm the secret if necessary and don't return anything - pub fn new_connect(sender: Member, secret_hash: AnkMessageHash, previous_proof: Option) -> Self { - let validation_tokens = if let Some(proof) = previous_proof { vec![proof] } else { vec![] }; + pub fn new_connect( + sender: Member, + secret_hash: AnkMessageHash, + previous_proof: Option, + ) -> Self { + let validation_tokens = if let Some(proof) = previous_proof { + vec![proof] + } else { + vec![] + }; Self { prd_type: PrdType::Connect, process_id: OutPoint::null(), @@ -159,16 +167,15 @@ impl Prd { let local_spend_key = local_address.get_spend_key(); // If it's our own device key we abort if proof_key == local_spend_key { - return Err(anyhow::Error::msg("Proof signed by ourselves, we are parsing our own message")); + return Err(anyhow::Error::msg( + "Proof signed by ourselves, we are parsing our own message", + )); } // take the spending keys in sender let addresses = prd.sender.get_addresses(); let mut spend_keys: Vec = vec![]; for address in addresses { - spend_keys.push( - ::try_from(address)? - .get_spend_key() - ); + spend_keys.push(::try_from(address)?.get_spend_key()); } // The key in proof must be one of the sender keys let mut known_key = false; diff --git a/src/process.rs b/src/process.rs index 869943a..de57944 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::collections::{BTreeMap, HashMap, HashSet}; -use std::sync::{Mutex, MutexGuard, OnceLock}; use std::str::FromStr; +use std::sync::{Mutex, MutexGuard, OnceLock}; use serde::{Deserialize, Serialize}; use sp_client::{ @@ -11,7 +11,10 @@ use sp_client::{ use tsify::Tsify; use crate::{ - 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 + 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, }; #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Tsify)] @@ -19,7 +22,7 @@ use crate::{ pub struct ProcessState { pub commited_in: OutPoint, #[tsify(type = "Record")] - pub pcd_commitment: PcdCommitments, + pub pcd_commitment: PcdCommitments, #[serde(serialize_with = "serialize_hex", deserialize_with = "deserialize_hex")] #[tsify(type = "string")] pub state_id: [u8; 32], // the root of the tree created with all the commitments + public_data + roles. Serves as an unique id for a state too @@ -33,12 +36,26 @@ pub struct ProcessState { } impl ProcessState { - pub fn new(commited_in: OutPoint, private_data: Pcd, public_data: Pcd, roles: Roles) -> anyhow::Result { + pub fn new( + commited_in: OutPoint, + private_data: Pcd, + public_data: Pcd, + roles: Roles, + ) -> anyhow::Result { // TODO check that we don't have duplicated field names in private and public data, nor a "roles" field name - let all_attributes = Pcd::new(private_data.clone().into_iter().chain(public_data.clone()).collect()); + let all_attributes = Pcd::new( + private_data + .clone() + .into_iter() + .chain(public_data.clone()) + .collect(), + ); let pcd_commitment = PcdCommitments::new(&commited_in, &all_attributes, &roles)?; - let merkle_root = pcd_commitment.create_merkle_tree()?.root().ok_or(anyhow::Error::msg("Invalid merkle tree"))?; + let merkle_root = pcd_commitment + .create_merkle_tree()? + .root() + .ok_or(anyhow::Error::msg("Invalid merkle tree"))?; let res = Self { commited_in, @@ -49,7 +66,7 @@ impl ProcessState { public_data, roles, }; - + Ok(res) } @@ -62,78 +79,110 @@ impl ProcessState { let merkle_tree = updated_commitments.create_merkle_tree()?; // Update state_id - self.state_id = merkle_tree.root().ok_or_else(|| anyhow::Error::msg("Invalid merkle tree"))?; + self.state_id = merkle_tree + .root() + .ok_or_else(|| anyhow::Error::msg("Invalid merkle tree"))?; // Everything is ok, we can update the state self.pcd_commitment = updated_commitments; - + Ok(()) } pub fn get_message_hash(&self, approval: bool) -> anyhow::Result { if approval { - Ok(AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(self.state_id))) + Ok(AnkHash::ValidationYes( + AnkValidationYesHash::from_merkle_root(self.state_id), + )) } else { - Ok(AnkHash::ValidationNo(AnkValidationNoHash::from_merkle_root(self.state_id))) + Ok(AnkHash::ValidationNo( + AnkValidationNoHash::from_merkle_root(self.state_id), + )) } } 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")); + 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")); + 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")); + // 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 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] + 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<()> { + fn handle_obliteration( + &self, + apophis: &RoleDefinition, + members_list: &OutPointMemberMap, + ) -> anyhow::Result<()> { // Apophis should have only one rule - if apophis.validation_rules.len() != 1 { return Err(anyhow::Error::msg("Should have only one rule")); }; + if apophis.validation_rules.len() != 1 { + return Err(anyhow::Error::msg("Should have only one rule")); + }; let obliteration_rule = apophis.validation_rules.get(0).unwrap(); let empty_field = ""; // This rule should have only one empty string as field - 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")); }; + 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")); + }; - apophis.is_satisfied(vec![empty_field.to_owned()], [0u8; 32], &self.validation_tokens, &members_list) + 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<()> { + fn handle_pairing( + &self, + pairing_role: RoleDefinition, + previous_addresses: Vec, + ) -> anyhow::Result<()> { // members must be empty - if !pairing_role.members.is_empty() { return Err(anyhow::Error::msg("Invalid pairing role: members list must be empty")); } + if !pairing_role.members.is_empty() { + return Err(anyhow::Error::msg( + "Invalid pairing role: members list must be empty", + )); + } // pairing_role must have one rule that modifies pairedAddresses let paired_addresses_rule = pairing_role.get_applicable_rules(PAIREDADDRESSES); - if paired_addresses_rule.len() != 1 { - return Err(anyhow::anyhow!("Invalid pairing role: there must one and only one rule for \"pairedAddresses\"")); + if paired_addresses_rule.len() != 1 { + return Err(anyhow::anyhow!( + "Invalid pairing role: there must one and only one rule for \"pairedAddresses\"" + )); } // TODO check that it matches what we have in the commitment here or somewhere else? let updated_addresses_json = self.public_data.get_as_json(PAIREDADDRESSES)?; - let updated_addresses_vec: Vec = serde_json::from_value(updated_addresses_json)?; + let updated_addresses_vec: Vec = + serde_json::from_value(updated_addresses_json)?; let updated_member = Member::new(updated_addresses_vec); let previous_member = Member::new(previous_addresses); @@ -143,10 +192,19 @@ impl ProcessState { vec![&previous_member] }; - paired_addresses_rule.get(0).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<()> { + pub fn is_valid( + &self, + previous_state: Option<&ProcessState>, + members_list: &OutPointMemberMap, + ) -> anyhow::Result<()> { if self.validation_tokens.is_empty() { return Err(anyhow::anyhow!( "Can't validate a state with no proofs attached" @@ -163,11 +221,13 @@ impl ProcessState { return Err(anyhow::anyhow!("Can't find apophis")); // then pairing } else if let Some(pairing_role) = self.roles.get(PAIRING) { - if self.pcd_commitment.contains_key(PAIREDADDRESSES) { + if self.pcd_commitment.contains_key(PAIREDADDRESSES) { if let Some(prev_state) = previous_state { - let prev_paired_addresses_json = prev_state.public_data.get_as_json(PAIREDADDRESSES)?; - let paired_addresses: Vec = serde_json::from_value(prev_paired_addresses_json)?; - return self.handle_pairing(pairing_role.clone(), paired_addresses); + let prev_paired_addresses_json = + prev_state.public_data.get_as_json(PAIREDADDRESSES)?; + let paired_addresses: Vec = + serde_json::from_value(prev_paired_addresses_json)?; + return self.handle_pairing(pairing_role.clone(), paired_addresses); } else { // We are in a creation return self.handle_pairing(pairing_role.clone(), vec![]); @@ -177,67 +237,70 @@ impl ProcessState { } // Check if each modified field satisfies at least one applicable rule across all roles - let all_fields_validated = !self.pcd_commitment.is_empty() && self.pcd_commitment.keys().all(|field| { - // Collect applicable rules from all roles for the current field - let applicable_roles: Vec = self.roles - .iter() - .filter_map(|(role_name, role_def)| { - if let Ok(special_role) = SpecialRoles::from_str(&role_name) { - match special_role { - // We allow for a special case with a role that works only for initial state - // That's optional though - SpecialRoles::Demiurge => { - // If we're not in initial state just ignore it - 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; + let all_fields_validated = !self.pcd_commitment.is_empty() + && self.pcd_commitment.keys().all(|field| { + // Collect applicable rules from all roles for the current field + let applicable_roles: Vec = self + .roles + .iter() + .filter_map(|(role_name, role_def)| { + if let Ok(special_role) = SpecialRoles::from_str(&role_name) { + match special_role { + // We allow for a special case with a role that works only for initial state + // That's optional though + SpecialRoles::Demiurge => { + // If we're not in initial state just ignore it + 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 } - // Otherwise we just continue normal validation + // We already handled other special roles + _ => return None, } - // We already handled other special roles - _ => return None } - } - let mut filtered_role_def = role_def.clone(); - let rules = filtered_role_def.get_applicable_rules(field); - filtered_role_def.validation_rules = - rules.into_iter().map(|r| r.clone()).collect(); - if filtered_role_def.validation_rules.is_empty() { - None - } else { - Some(filtered_role_def) - } - }) - .collect(); + let mut filtered_role_def = role_def.clone(); + let rules = filtered_role_def.get_applicable_rules(field); + filtered_role_def.validation_rules = + rules.into_iter().map(|r| r.clone()).collect(); + if filtered_role_def.validation_rules.is_empty() { + None + } else { + Some(filtered_role_def) + } + }) + .collect(); - if applicable_roles.is_empty() { - return false; // No rules apply to this field, consider it invalid - } + if applicable_roles.is_empty() { + return false; // No rules apply to this field, consider it invalid + } - applicable_roles.into_iter().any(|role_def| { - role_def.validation_rules.iter().any(|rule| { - let members: Vec<&Member> = role_def.members.iter() - .filter_map(|outpoint| { - members_list.0.get(outpoint) - }) - .collect(); - rule.is_satisfied( - field, - self.state_id, - &self.validation_tokens, - members.as_slice(), - ).is_ok() + applicable_roles.into_iter().any(|role_def| { + role_def.validation_rules.iter().any(|rule| { + let members: Vec<&Member> = role_def + .members + .iter() + .filter_map(|outpoint| members_list.0.get(outpoint)) + .collect(); + rule.is_satisfied( + field, + self.state_id, + &self.validation_tokens, + members.as_slice(), + ) + .is_ok() + }) }) - }) - }); + }); if all_fields_validated { Ok(()) @@ -250,7 +313,10 @@ impl ProcessState { self.state_id == [0u8; 32] } - pub fn get_fields_to_validate_for_member(&self, member: &OutPoint) -> anyhow::Result> { + pub fn get_fields_to_validate_for_member( + &self, + member: &OutPoint, + ) -> anyhow::Result> { let mut res: HashSet = HashSet::new(); // Are we in that role? @@ -281,9 +347,7 @@ pub struct Process { } impl Process { - pub fn new( - commited_in: OutPoint - ) -> Self { + pub fn new(commited_in: OutPoint) -> Self { let empty_state = ProcessState { commited_in, ..Default::default() @@ -294,33 +358,49 @@ impl Process { } pub fn get_process_id(&self) -> anyhow::Result { - Ok(self.states.get(0).ok_or(anyhow::Error::msg("Empty state list"))?.commited_in) + Ok(self + .states + .get(0) + .ok_or(anyhow::Error::msg("Empty state list"))? + .commited_in) } pub fn get_process_tip(&self) -> anyhow::Result { - Ok(self.states.last().ok_or(anyhow::Error::msg("Empty state list"))?.commited_in) + Ok(self + .states + .last() + .ok_or(anyhow::Error::msg("Empty state list"))? + .commited_in) } pub fn get_last_unspent_outpoint(&self) -> anyhow::Result { - if self.states.is_empty() { return Err(anyhow::Error::msg("Empty Process")); } + if self.states.is_empty() { + return Err(anyhow::Error::msg("Empty Process")); + } let last_state = self.states.last().unwrap(); Ok(last_state.commited_in) } pub fn update_states_tip(&mut self, new_commitment: OutPoint) -> anyhow::Result<()> { - if self.states.is_empty() { return Err(anyhow::Error::msg("Empty Process")); } + if self.states.is_empty() { + return Err(anyhow::Error::msg("Empty Process")); + } let last_value = self.states.last().unwrap(); if !last_value.is_empty() { return Err(anyhow::Error::msg("Last value should be empty")); } if last_value.commited_in == new_commitment { - return Err(anyhow::Error::msg("new_commitment is the same than existing tip")); + return Err(anyhow::Error::msg( + "new_commitment is the same than existing tip", + )); } // Before updating we make sure that we only have one concurrent state let concurrent_states = self.get_latest_concurrent_states()?; if concurrent_states.len() != 2 { - return Err(anyhow::Error::msg("We must have exactly one state for the current tip")); + return Err(anyhow::Error::msg( + "We must have exactly one state for the current tip", + )); } // Replace the existing tip @@ -338,13 +418,17 @@ impl Process { /// The new state *must* have the same commited_in than the last empty one /// We want to always keep an empty state with only the latest unspent commited_in value at the last position pub fn insert_concurrent_state(&mut self, new_state: ProcessState) -> anyhow::Result<()> { - if self.states.is_empty() { return Err(anyhow::Error::msg("Empty Process")); } + if self.states.is_empty() { + return Err(anyhow::Error::msg("Empty Process")); + } let last_value = self.states.last().unwrap(); if !last_value.is_empty() { return Err(anyhow::Error::msg("Last value should be empty")); } if last_value.commited_in != new_state.commited_in { - return Err(anyhow::Error::msg("A concurrent state must have the same commited in than the tip of the states")); + return Err(anyhow::Error::msg( + "A concurrent state must have the same commited in than the tip of the states", + )); } let empty_state = self.states.pop().unwrap(); self.states.push(new_state); @@ -402,7 +486,7 @@ impl Process { } for p in &self.states { - if *state_id == p.state_id { + if *state_id == p.state_id { return Ok(p); } } @@ -410,14 +494,17 @@ impl Process { return Err(anyhow::Error::msg("No state for this merkle root")); } - pub fn get_state_for_id_mut(&mut self, state_id: &[u8; 32]) -> anyhow::Result<&mut ProcessState> { + pub fn get_state_for_id_mut( + &mut self, + state_id: &[u8; 32], + ) -> anyhow::Result<&mut ProcessState> { if self.get_number_of_states() == 0 { // This should never happen, but we better get rid of it now return Err(anyhow::Error::msg("process is empty".to_owned())); } for p in &mut self.states { - if *state_id == p.state_id { + if *state_id == p.state_id { return Ok(p); } } @@ -478,7 +565,11 @@ impl Process { let empty_state = self.states.pop().unwrap(); let last_commitment_outpoint = empty_state.commited_in; - if let Some(split_index) = self.states.iter().position(|state| state.commited_in == last_commitment_outpoint) { + if let Some(split_index) = self + .states + .iter() + .position(|state| state.commited_in == last_commitment_outpoint) + { let removed = self.states.split_off(split_index); // We make sure we always have an empty state at the end @@ -538,18 +629,33 @@ pub fn lock_processes() -> Result pub fn check_tx_for_process_updates(tx: &Transaction) -> anyhow::Result { let mut processes = lock_processes()?; for (outpoint, process) in &mut *processes { - let process_tip = if let Ok(tip) = process.get_process_tip() { tip } else { continue }; + let process_tip = if let Ok(tip) = process.get_process_tip() { + tip + } else { + continue; + }; for input in &tx.input { if process_tip == input.previous_output { log::debug!("Found a match for process tip {}", process_tip); // This transaction commits a new state // Look for the op_return - let op_return_outputs: Vec<_> = tx.output.iter().filter(|o| o.script_pubkey.is_op_return()).collect(); + let op_return_outputs: Vec<_> = tx + .output + .iter() + .filter(|o| o.script_pubkey.is_op_return()) + .collect(); if op_return_outputs.len() != 1 { - return Err(anyhow::Error::msg("Transaction must contain exactly one op_return output")); + return Err(anyhow::Error::msg( + "Transaction must contain exactly one op_return output", + )); } let mut state_id = [0u8; 32]; - let data = &op_return_outputs.into_iter().next().unwrap().script_pubkey.as_bytes()[2..]; + let data = &op_return_outputs + .into_iter() + .next() + .unwrap() + .script_pubkey + .as_bytes()[2..]; if data.len() != 32 { return Err(anyhow::Error::msg("commited data is not 32B long")); } @@ -561,7 +667,7 @@ pub fn check_tx_for_process_updates(tx: &Transaction) -> anyhow::Result anyhow::Result SpClient { SpClient::new( - SecretKey::from_str( - "a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973", - ) - .unwrap(), + SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c", @@ -617,10 +725,8 @@ mod tests { fn create_bob_wallet() -> SpClient { SpClient::new( - SecretKey::from_str( - "4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b", - ) - .unwrap(), + SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e", @@ -634,10 +740,8 @@ mod tests { fn create_carol_wallet() -> SpClient { SpClient::new( - SecretKey::from_str( - "e4a5906eaa1a7ab24d5fc8d9b600d47f79caa6511c056c111677b7a33e62c5e9", - ) - .unwrap(), + SecretKey::from_str("e4a5906eaa1a7ab24d5fc8d9b600d47f79caa6511c056c111677b7a33e62c5e9") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "e4c282e14668af1435e39df78403a7b406a791e3c6e666295496a6a865ade162", @@ -651,10 +755,8 @@ mod tests { fn create_dave_wallet() -> SpClient { SpClient::new( - SecretKey::from_str( - "261d5f9ae4d2b0d8b17ed0c52bd2be7dbce14d9ac1f0f1d4904d3ca7df03766d", - ) - .unwrap(), + SecretKey::from_str("261d5f9ae4d2b0d8b17ed0c52bd2be7dbce14d9ac1f0f1d4904d3ca7df03766d") + .unwrap(), SpendKey::Secret( SecretKey::from_str( "8441e2adbb39736f384617fafc61e0d894bf3a5c2b69801fd4476bdcce04fb59", @@ -673,67 +775,73 @@ mod tests { let dave_wallet = create_dave_wallet(); let alice_address = - SilentPaymentAddress::try_from(alice_wallet.get_receiving_address()) - .unwrap(); + SilentPaymentAddress::try_from(alice_wallet.get_receiving_address()).unwrap(); let bob_address = - SilentPaymentAddress::try_from(bob_wallet.get_receiving_address()) - .unwrap(); + SilentPaymentAddress::try_from(bob_wallet.get_receiving_address()).unwrap(); let carol_address = - SilentPaymentAddress::try_from(carol_wallet.get_receiving_address()) - .unwrap(); + SilentPaymentAddress::try_from(carol_wallet.get_receiving_address()).unwrap(); let dave_address = - SilentPaymentAddress::try_from(dave_wallet.get_receiving_address()) - .unwrap(); + SilentPaymentAddress::try_from(dave_wallet.get_receiving_address()).unwrap(); let members_map: HashMap = HashMap::from([ - (OutPoint::from_str(ALICE_BOB_PAIRING).unwrap(), Member::new(vec![alice_address, bob_address])), - (OutPoint::from_str(CAROL_PAIRING).unwrap(), Member::new(vec![carol_address])), - (OutPoint::from_str(DAVE_PAIRING).unwrap(), Member::new(vec![dave_address])), + ( + OutPoint::from_str(ALICE_BOB_PAIRING).unwrap(), + Member::new(vec![alice_address, bob_address]), + ), + ( + OutPoint::from_str(CAROL_PAIRING).unwrap(), + Member::new(vec![carol_address]), + ), + ( + OutPoint::from_str(DAVE_PAIRING).unwrap(), + Member::new(vec![dave_address]), + ), ]); members_map } fn dummy_process_state() -> ProcessState { - let validation_rule1 = - ValidationRule::new(1.0, vec!["field1".to_owned()], 0.5).unwrap(); + let validation_rule1 = ValidationRule::new(1.0, vec!["field1".to_owned()], 0.5).unwrap(); let validation_rule2 = ValidationRule::new(1.0, vec!["field2".to_owned()], 0.5).unwrap(); let validation_rule3 = ValidationRule::new(1.0, vec!["roles".to_owned()], 0.5).unwrap(); - let validation_rule4 = ValidationRule::new(1.0, vec!["public1".to_owned(), "public2".to_owned()], 0.5).unwrap(); + 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![] + storages: vec![], }; let role_def1 = RoleDefinition { members: vec![OutPoint::from_str(ALICE_BOB_PAIRING).unwrap()], validation_rules: vec![validation_rule1], - storages: vec![] + storages: vec![], }; let role_def2 = RoleDefinition { members: vec![OutPoint::from_str(CAROL_PAIRING).unwrap()], validation_rules: vec![validation_rule2], - storages: vec![] + storages: vec![], }; let role_def_roles = RoleDefinition { members: vec![OutPoint::from_str(ALICE_BOB_PAIRING).unwrap()], validation_rules: vec![validation_rule3], - storages: vec![] + storages: vec![], }; let role_def_public_data = RoleDefinition { members: vec![OutPoint::from_str(ALICE_BOB_PAIRING).unwrap()], validation_rules: vec![validation_rule4], - storages: vec![] + storages: vec![], }; let role_def_apophis = RoleDefinition { members: vec![OutPoint::from_str(DAVE_PAIRING).unwrap()], validation_rules: vec![apophis_rule], - storages: vec![] + storages: vec![], }; let roles: BTreeMap = BTreeMap::from([ @@ -742,20 +850,24 @@ mod tests { ("role2".to_owned(), role_def2), ("role_roles".to_owned(), role_def_roles), ("role_public_data".to_owned(), role_def_public_data), - ("apophis".to_owned(), role_def_apophis) + ("apophis".to_owned(), role_def_apophis), ]); let private_data: Pcd = json!({ "field1": "value1", "field2": "value2", - }).try_into().unwrap(); + }) + .try_into() + .unwrap(); let outpoint = OutPoint::null(); - + let public_data: Pcd = json!({ "public1": "public1", "public2": "public2", - }).try_into().unwrap(); + }) + .try_into() + .unwrap(); ProcessState::new(outpoint, private_data, public_data, Roles::new(roles)).unwrap() } @@ -763,21 +875,31 @@ mod tests { fn create_pairing_process_one() -> ProcessState { let carol_wallet = create_carol_wallet(); let carol_address = carol_wallet.get_receiving_address(); - let pairing_rule = - ValidationRule::new(1.0, vec![PAIREDADDRESSES.to_owned()], 1.0).unwrap(); + let pairing_rule = ValidationRule::new(1.0, vec![PAIREDADDRESSES.to_owned()], 1.0).unwrap(); let pairing_role_def = RoleDefinition { members: vec![], validation_rules: vec![pairing_rule], - storages: vec![] + storages: vec![], }; - let outpoint = OutPoint::from_str("8425e9749957fd8ca83460c21718be4017692fd3ae61cbb0f0d401e7a5ddce6a:0").unwrap(); + let outpoint = OutPoint::from_str( + "8425e9749957fd8ca83460c21718be4017692fd3ae61cbb0f0d401e7a5ddce6a:0", + ) + .unwrap(); let private_data: Pcd = json!({"description": "pairing"}).try_into().unwrap(); - let paired_addresses: Pcd = json!({PAIREDADDRESSES: [carol_address]}).try_into().unwrap(); + let paired_addresses: Pcd = json!({PAIREDADDRESSES: [carol_address]}) + .try_into() + .unwrap(); - ProcessState::new(outpoint, private_data, paired_addresses, Roles::new(BTreeMap::from([(PAIRING.to_owned(), pairing_role_def)]))).unwrap() + ProcessState::new( + outpoint, + private_data, + paired_addresses, + Roles::new(BTreeMap::from([(PAIRING.to_owned(), pairing_role_def)])), + ) + .unwrap() } fn create_pairing_process_two() -> ProcessState { @@ -785,21 +907,31 @@ mod tests { let carol_address = carol_wallet.get_receiving_address(); let dave_wallet = create_dave_wallet(); let dave_address = dave_wallet.get_receiving_address(); - let pairing_rule = - ValidationRule::new(1.0, vec![PAIREDADDRESSES.to_owned()], 1.0).unwrap(); + let pairing_rule = ValidationRule::new(1.0, vec![PAIREDADDRESSES.to_owned()], 1.0).unwrap(); let pairing_role_def = RoleDefinition { members: vec![], validation_rules: vec![pairing_rule], - storages: vec![] + storages: vec![], }; - let outpoint = OutPoint::from_str("8425e9749957fd8ca83460c21718be4017692fd3ae61cbb0f0d401e7a5ddce6a:0").unwrap(); + let outpoint = OutPoint::from_str( + "8425e9749957fd8ca83460c21718be4017692fd3ae61cbb0f0d401e7a5ddce6a:0", + ) + .unwrap(); let private_data: Pcd = json!({"description": "pairing"}).try_into().unwrap(); - let paired_addresses: Pcd = json!({PAIREDADDRESSES: [carol_address, dave_address]}).try_into().unwrap(); + let paired_addresses: Pcd = json!({PAIREDADDRESSES: [carol_address, dave_address]}) + .try_into() + .unwrap(); - ProcessState::new(outpoint, private_data, paired_addresses, Roles::new(BTreeMap::from([(PAIRING.to_owned(), pairing_role_def)]))).unwrap() + ProcessState::new( + outpoint, + private_data, + paired_addresses, + Roles::new(BTreeMap::from([(PAIRING.to_owned(), pairing_role_def)])), + ) + .unwrap() } #[test] @@ -823,7 +955,9 @@ mod tests { SecretKey::from_str("39b2a765dc93e02da04a0e9300224b4f99fa7b83cfae49036dff58613fd3277c") .unwrap(); let message_hash = state.get_message_hash(true).unwrap(); - state.validation_tokens.push(Proof::new(message_hash, signing_key)); + state + .validation_tokens + .push(Proof::new(message_hash, signing_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); @@ -834,12 +968,11 @@ mod tests { fn test_error_not_enough_signatures() { let mut state = dummy_process_state(); // We sign with Carol key - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let carol_key: SecretKey = create_carol_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)); + state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); @@ -850,17 +983,15 @@ mod tests { fn test_valid_just_enough_signatures() { 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 carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let alice_key: SecretKey = create_alice_wallet().get_spend_key().try_into().unwrap(); + let carol_key: SecretKey = create_carol_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, carol_key)); + state + .validation_tokens + .push(Proof::new(message_hash, alice_key)); + state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -870,17 +1001,15 @@ mod tests { 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 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)); + 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()); } @@ -890,12 +1019,11 @@ mod tests { 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 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)); + state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); } @@ -905,24 +1033,25 @@ mod tests { 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 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())); + 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(); - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash = pairing_first_state.get_message_hash(true).unwrap(); - pairing_first_state.validation_tokens.push(Proof::new(message_hash, carol_key)); + pairing_first_state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = pairing_first_state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -930,12 +1059,11 @@ mod tests { #[test] fn test_error_pairing_wrong_proof() { let mut pairing_first_state = create_pairing_process_one(); - let alice_key: SecretKey = create_alice_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let alice_key: SecretKey = create_alice_wallet().get_spend_key().try_into().unwrap(); let message_hash = pairing_first_state.get_message_hash(true).unwrap(); - pairing_first_state.validation_tokens.push(Proof::new(message_hash, alice_key)); + pairing_first_state + .validation_tokens + .push(Proof::new(message_hash, alice_key)); let result = pairing_first_state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); } @@ -943,93 +1071,111 @@ mod tests { #[test] fn test_valid_pairing_add_device() { let pairing_state = create_pairing_process_one(); - let mut paired_addresses: Vec = serde_json::from_value(pairing_state.public_data.get_as_json(PAIREDADDRESSES).unwrap()).unwrap(); + let mut paired_addresses: Vec = serde_json::from_value( + pairing_state + .public_data + .get_as_json(PAIREDADDRESSES) + .unwrap(), + ) + .unwrap(); let mut pairing_process = Process::new(pairing_state.commited_in); - pairing_process.insert_concurrent_state(pairing_state).unwrap(); - let new_commitment = OutPoint::from_str("7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0").unwrap(); + pairing_process + .insert_concurrent_state(pairing_state) + .unwrap(); + let new_commitment = OutPoint::from_str( + "7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0", + ) + .unwrap(); pairing_process.update_states_tip(new_commitment).unwrap(); - let members_list = &OutPointMemberMap(HashMap::from( - [ - ( - pairing_process.get_process_id().unwrap(), - Member::new( - paired_addresses.clone() - )) - ] - )); + let members_list = &OutPointMemberMap(HashMap::from([( + pairing_process.get_process_id().unwrap(), + Member::new(paired_addresses.clone()), + )])); // Add Dave address let dave_address = create_dave_wallet().get_receiving_address(); paired_addresses.push(dave_address); let roles = &pairing_process.get_latest_commited_state().unwrap().roles; - let mut add_device_state = ProcessState::new(new_commitment, Pcd::new(BTreeMap::new()), json!({PAIREDADDRESSES: paired_addresses}).try_into().unwrap(), roles.clone()).unwrap(); + let mut add_device_state = ProcessState::new( + new_commitment, + Pcd::new(BTreeMap::new()), + json!({PAIREDADDRESSES: paired_addresses}) + .try_into() + .unwrap(), + roles.clone(), + ) + .unwrap(); - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash = add_device_state.get_message_hash(true).unwrap(); - add_device_state.validation_tokens.push(Proof::new(message_hash, carol_key)); + add_device_state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); - let result = add_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); + let result = + add_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); assert!(result.is_ok()); } #[test] fn test_valid_pairing_rm_device() { let pairing_state = create_pairing_process_two(); - let paired_addresses: Vec = serde_json::from_value(pairing_state.public_data.get_as_json(PAIREDADDRESSES).unwrap()).unwrap(); + let paired_addresses: Vec = serde_json::from_value( + pairing_state + .public_data + .get_as_json(PAIREDADDRESSES) + .unwrap(), + ) + .unwrap(); let mut pairing_process = Process::new(pairing_state.commited_in); - pairing_process.insert_concurrent_state(pairing_state).unwrap(); - let new_commitment = OutPoint::from_str("7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0").unwrap(); + pairing_process + .insert_concurrent_state(pairing_state) + .unwrap(); + let new_commitment = OutPoint::from_str( + "7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0", + ) + .unwrap(); pairing_process.update_states_tip(new_commitment).unwrap(); - let members_list = &OutPointMemberMap(HashMap::from( - [ - ( - pairing_process.get_process_id().unwrap(), - Member::new( - paired_addresses.clone() - )) - ] - )); + let members_list = &OutPointMemberMap(HashMap::from([( + pairing_process.get_process_id().unwrap(), + Member::new(paired_addresses.clone()), + )])); // Remove Dave address let dave_address = create_dave_wallet().get_receiving_address(); - let filtered_paired_addresses: Vec = paired_addresses.into_iter().filter_map(|a| { - if a != dave_address { - Some(a) - } else { - None - } - }) - .collect(); + let filtered_paired_addresses: Vec = paired_addresses + .into_iter() + .filter_map(|a| if a != dave_address { Some(a) } else { None }) + .collect(); let roles = &pairing_process.get_latest_commited_state().unwrap().roles; let mut rm_device_state = ProcessState::new( - new_commitment, - Pcd::new(BTreeMap::new()), - json!({PAIREDADDRESSES: filtered_paired_addresses}).try_into().unwrap(), - roles.clone() - ).unwrap(); + new_commitment, + Pcd::new(BTreeMap::new()), + json!({PAIREDADDRESSES: filtered_paired_addresses}) + .try_into() + .unwrap(), + roles.clone(), + ) + .unwrap(); // We need both devices to agree to remove one - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); - let dave_key: SecretKey = create_dave_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); + let dave_key: SecretKey = create_dave_wallet().get_spend_key().try_into().unwrap(); let message_hash = rm_device_state.get_message_hash(true).unwrap(); - rm_device_state.validation_tokens.push(Proof::new(message_hash, carol_key)); - rm_device_state.validation_tokens.push(Proof::new(message_hash, dave_key)); + rm_device_state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); + rm_device_state + .validation_tokens + .push(Proof::new(message_hash, dave_key)); - let result = rm_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); + let result = + rm_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); assert!(result.is_ok()); } @@ -1037,37 +1183,51 @@ mod tests { #[test] fn test_error_pairing_add_device_wrong_signature() { let pairing_state = create_pairing_process_one(); - let mut paired_addresses: Vec = serde_json::from_value(pairing_state.public_data.get_as_json(PAIREDADDRESSES).unwrap()).unwrap(); + let mut paired_addresses: Vec = serde_json::from_value( + pairing_state + .public_data + .get_as_json(PAIREDADDRESSES) + .unwrap(), + ) + .unwrap(); let mut pairing_process = Process::new(pairing_state.commited_in); - pairing_process.insert_concurrent_state(pairing_state).unwrap(); - let new_commitment = OutPoint::from_str("7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0").unwrap(); + pairing_process + .insert_concurrent_state(pairing_state) + .unwrap(); + let new_commitment = OutPoint::from_str( + "7f1a6d8923d6ee58a075c0e99e25472bb22a3eea221739281c2beaf829f03f27:0", + ) + .unwrap(); pairing_process.update_states_tip(new_commitment).unwrap(); - let members_list = &OutPointMemberMap(HashMap::from( - [ - ( - pairing_process.get_process_id().unwrap(), - Member::new( - paired_addresses.clone() - )) - ] - )); + let members_list = &OutPointMemberMap(HashMap::from([( + pairing_process.get_process_id().unwrap(), + Member::new(paired_addresses.clone()), + )])); // Add Dave address let dave_address = create_dave_wallet().get_receiving_address(); paired_addresses.push(dave_address); let roles = &pairing_process.get_latest_commited_state().unwrap().roles; - let mut add_device_state = ProcessState::new(new_commitment, Pcd::new(BTreeMap::new()), json!({PAIREDADDRESSES: paired_addresses}).try_into().unwrap(), roles.clone()).unwrap(); + let mut add_device_state = ProcessState::new( + new_commitment, + Pcd::new(BTreeMap::new()), + json!({PAIREDADDRESSES: paired_addresses}) + .try_into() + .unwrap(), + roles.clone(), + ) + .unwrap(); - let dave_key: SecretKey = create_dave_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let dave_key: SecretKey = create_dave_wallet().get_spend_key().try_into().unwrap(); let message_hash = add_device_state.get_message_hash(true).unwrap(); - add_device_state.validation_tokens.push(Proof::new(message_hash, dave_key)); + add_device_state + .validation_tokens + .push(Proof::new(message_hash, dave_key)); - let result = add_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); + let result = + add_device_state.is_valid(pairing_process.get_latest_commited_state(), members_list); assert!(result.is_err()); } @@ -1075,22 +1235,19 @@ mod tests { /// everyone signs for everything fn test_valid_all_signatures() { let mut state = dummy_process_state(); - 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 carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + 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 carol_key: SecretKey = create_carol_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)); - state.validation_tokens.push(Proof::new(message_hash, carol_key)); + state + .validation_tokens + .push(Proof::new(message_hash, alice_key)); + state + .validation_tokens + .push(Proof::new(message_hash, bob_key)); + state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -1102,22 +1259,20 @@ mod tests { let mut process = Process::new(state.commited_in); process.insert_concurrent_state(state.clone()).unwrap(); // We simulate a first commitment - process.update_states_tip( - OutPoint::new( - Txid::from_str( - "cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127" - ).unwrap() - , 0 - ) - ).unwrap(); + process + .update_states_tip(OutPoint::new( + Txid::from_str("cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127") + .unwrap(), + 0, + )) + .unwrap(); // Now we take the last empty state and try to invalidate it let empty_state = process.get_state_for_id(&[0u8; 32]).unwrap(); - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash = empty_state.get_message_hash(true).unwrap(); - state.validation_tokens.push(Proof::new(message_hash, carol_key)); + state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); } @@ -1129,22 +1284,20 @@ mod tests { let mut process = Process::new(state.commited_in); process.insert_concurrent_state(state.clone()).unwrap(); // We simulate a first commitment - process.update_states_tip( - OutPoint::new( - Txid::from_str( - "cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127" - ).unwrap() - , 0 - ) - ).unwrap(); + process + .update_states_tip(OutPoint::new( + Txid::from_str("cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127") + .unwrap(), + 0, + )) + .unwrap(); // Now we take the last empty state and try to commit it to invalidate the whole process let empty_state = process.get_state_for_id_mut(&[0u8; 32]).unwrap(); - let dave_key: SecretKey = create_dave_wallet() - .get_spend_key() - .try_into() - .unwrap(); + let dave_key: SecretKey = create_dave_wallet().get_spend_key().try_into().unwrap(); let message_hash = empty_state.get_message_hash(true).unwrap(); - empty_state.validation_tokens.push(Proof::new(message_hash, dave_key)); + empty_state + .validation_tokens + .push(Proof::new(message_hash, dave_key)); let result = empty_state.is_valid(Some(&state), &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -1153,24 +1306,24 @@ mod tests { /// Carol refuses change for her part fn test_error_carol_votes_no() { let mut state = dummy_process_state(); - 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 carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + 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 carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash_yes = state.get_message_hash(true).unwrap(); let message_hash_no = state.get_message_hash(false).unwrap(); - 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(Some(&dummy_process_state()), &OutPointMemberMap(get_members_map())); + 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( + Some(&dummy_process_state()), + &OutPointMemberMap(get_members_map()), + ); assert!(result.is_err()); assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); } @@ -1179,23 +1332,20 @@ mod tests { /// Bob refuses change for his part, but Alice is enough to reach quorum fn test_valid_bob_votes_no() { let mut state = dummy_process_state(); - 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 carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() - .unwrap(); + 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 carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash_yes = state.get_message_hash(true).unwrap(); let message_hash_no = state.get_message_hash(false).unwrap(); - state.validation_tokens.push(Proof::new(message_hash_yes, alice_key)); - state.validation_tokens.push(Proof::new(message_hash_no, bob_key)); - state.validation_tokens.push(Proof::new(message_hash_yes, carol_key)); + state + .validation_tokens + .push(Proof::new(message_hash_yes, alice_key)); + state + .validation_tokens + .push(Proof::new(message_hash_no, bob_key)); + state + .validation_tokens + .push(Proof::new(message_hash_yes, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -1207,25 +1357,29 @@ mod tests { let mut new_state = state.clone(); let key_to_modify = state.pcd_commitment.keys().into_iter().next().unwrap(); let mut serialized_value = Vec::new(); - serde_json::to_writer(&mut serialized_value, &Value::String("new_value1".to_owned())).unwrap(); - let updated_value = zstd::encode_all(serialized_value.as_slice(), ZSTD_COMPRESSION_LEVEL).unwrap(); - new_state.update_value(key_to_modify.as_str(), updated_value.as_slice()).unwrap(); - 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 carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() + serde_json::to_writer( + &mut serialized_value, + &Value::String("new_value1".to_owned()), + ) + .unwrap(); + let updated_value = + zstd::encode_all(serialized_value.as_slice(), ZSTD_COMPRESSION_LEVEL).unwrap(); + new_state + .update_value(key_to_modify.as_str(), updated_value.as_slice()) .unwrap(); + 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 carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash = new_state.get_message_hash(true).unwrap(); - new_state.validation_tokens.push(Proof::new(message_hash, alice_key)); - new_state.validation_tokens.push(Proof::new(message_hash, bob_key)); - new_state.validation_tokens.push(Proof::new(message_hash, carol_key)); + new_state + .validation_tokens + .push(Proof::new(message_hash, alice_key)); + new_state + .validation_tokens + .push(Proof::new(message_hash, bob_key)); + new_state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = new_state.is_valid(Some(&state), &OutPointMemberMap(get_members_map())); assert!(result.is_ok()); } @@ -1237,15 +1391,21 @@ mod tests { let mut new_state = state.clone(); let key_to_modify = state.pcd_commitment.keys().into_iter().next().unwrap(); let mut serialized_value = Vec::new(); - serde_json::to_writer(&mut serialized_value, &Value::String("new_value1".to_owned())).unwrap(); - let updated_value = zstd::encode_all(serialized_value.as_slice(), ZSTD_COMPRESSION_LEVEL).unwrap(); - new_state.update_value(key_to_modify.as_str(), updated_value.as_slice()).unwrap(); - let carol_key: SecretKey = create_carol_wallet() - .get_spend_key() - .try_into() + serde_json::to_writer( + &mut serialized_value, + &Value::String("new_value1".to_owned()), + ) + .unwrap(); + let updated_value = + zstd::encode_all(serialized_value.as_slice(), ZSTD_COMPRESSION_LEVEL).unwrap(); + new_state + .update_value(key_to_modify.as_str(), updated_value.as_slice()) .unwrap(); + let carol_key: SecretKey = create_carol_wallet().get_spend_key().try_into().unwrap(); let message_hash = new_state.get_message_hash(true).unwrap(); - new_state.validation_tokens.push(Proof::new(message_hash, carol_key)); + new_state + .validation_tokens + .push(Proof::new(message_hash, carol_key)); let result = new_state.is_valid(Some(&state), &OutPointMemberMap(get_members_map())); assert!(result.is_err()); } @@ -1258,37 +1418,43 @@ mod tests { } let mut process = Process::new(state.commited_in); process.insert_concurrent_state(state).unwrap(); - process.update_states_tip( - OutPoint::new( - Txid::from_str( - "cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127" - ).unwrap() - , 0 - ) - ).unwrap(); + process + .update_states_tip(OutPoint::new( + Txid::from_str("cbeb4455f8d11848809bacd59bfd570243dbe7c4e9a340fa949aae3020fdb127") + .unwrap(), + 0, + )) + .unwrap(); // We now try to add dave into apophis role let mut new_roles = process.get_latest_commited_state().unwrap().roles.clone(); - + if let Some(role) = new_roles.get_mut(APOPHIS) { role.members = vec![OutPoint::from_str(DAVE_PAIRING).unwrap()]; } - let mut new_state = ProcessState::new(process.get_process_tip().unwrap(), Pcd::new(BTreeMap::new()), Pcd::new(BTreeMap::new()), new_roles).unwrap(); + let mut new_state = ProcessState::new( + process.get_process_tip().unwrap(), + Pcd::new(BTreeMap::new()), + Pcd::new(BTreeMap::new()), + new_roles, + ) + .unwrap(); // Alice and Bob validate - 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 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 = new_state.get_message_hash(true).unwrap(); - new_state.validation_tokens.push(Proof::new(message_hash, alice_key)); - new_state.validation_tokens.push(Proof::new(message_hash, bob_key)); - let result = new_state.is_valid(process.get_parent_state(&new_state.commited_in), &OutPointMemberMap(get_members_map())); + new_state + .validation_tokens + .push(Proof::new(message_hash, alice_key)); + new_state + .validation_tokens + .push(Proof::new(message_hash, bob_key)); + let result = new_state.is_valid( + process.get_parent_state(&new_state.commited_in), + &OutPointMemberMap(get_members_map()), + ); assert!(result.is_ok()); } } diff --git a/src/secrets.rs b/src/secrets.rs index 6484ded..277a1d6 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -1,20 +1,20 @@ -use anyhow::{Result, Error}; -use tsify::Tsify; use crate::aes_gcm::aead::{Aead, Payload}; use crate::aes_gcm::Nonce; +use crate::crypto::{Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD}; use crate::sp_client::bitcoin::hashes::Hash; use crate::sp_client::silentpayments::SilentPaymentAddress; -use crate::crypto::{Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD}; +use anyhow::{Error, Result}; use serde::ser::SerializeStruct; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::str::FromStr; +use tsify::Tsify; #[derive(Debug, Clone, Default, PartialEq, Tsify)] #[tsify(into_wasm_abi)] -pub struct SecretsStore{ +pub struct SecretsStore { shared_secrets: HashMap, - unconfirmed_secrets: Vec + unconfirmed_secrets: Vec, } impl Serialize for SecretsStore { @@ -24,7 +24,7 @@ impl Serialize for SecretsStore { { let mut temp_map = HashMap::with_capacity(self.shared_secrets.len()); for (key, value) in &self.shared_secrets { - let key_str = key.to_string(); + let key_str = key.to_string(); let value_str = value.to_string(); temp_map.insert(key_str, value_str); @@ -61,14 +61,16 @@ impl<'de> Deserialize<'de> for SecretsStore { let mut shared_secrets = HashMap::with_capacity(helper.shared_secrets.len()); for (key_str, value_str) in helper.shared_secrets { let key = SilentPaymentAddress::try_from(key_str).map_err(serde::de::Error::custom)?; // Convert String to SilentPaymentAddress - let value = AnkSharedSecretHash::from_str(&value_str).map_err(serde::de::Error::custom)?; // Convert hex string back to Vec + let value = + AnkSharedSecretHash::from_str(&value_str).map_err(serde::de::Error::custom)?; // Convert hex string back to Vec shared_secrets.insert(key, value); } let mut unconfirmed_secrets = Vec::with_capacity(helper.unconfirmed_secrets.len()); for secret_str in helper.unconfirmed_secrets { - let secret_bytes = AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?; + let secret_bytes = + AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?; unconfirmed_secrets.push(secret_bytes); } @@ -81,9 +83,9 @@ impl<'de> Deserialize<'de> for SecretsStore { impl SecretsStore { pub fn new() -> Self { - Self { - shared_secrets: HashMap::new(), - unconfirmed_secrets: Vec::new() + Self { + shared_secrets: HashMap::new(), + unconfirmed_secrets: Vec::new(), } } @@ -92,11 +94,14 @@ impl SecretsStore { } /// Returns the previous secret for this address, if any - pub fn confirm_secret_for_address(&mut self, secret: AnkSharedSecretHash, address: SilentPaymentAddress) -> Option { - if let Some(pos) = self.unconfirmed_secrets.iter() - .position(|s| *s == secret) - { - self.shared_secrets.insert(address, self.unconfirmed_secrets.swap_remove(pos)) + pub fn confirm_secret_for_address( + &mut self, + secret: AnkSharedSecretHash, + address: SilentPaymentAddress, + ) -> Option { + if let Some(pos) = self.unconfirmed_secrets.iter().position(|s| *s == secret) { + self.shared_secrets + .insert(address, self.unconfirmed_secrets.swap_remove(pos)) } else { // We didn't know about that secret, just add it // TODO if we already had a secret for this address we just replace it for now @@ -104,7 +109,10 @@ impl SecretsStore { } } - pub fn get_secret_for_address(&self, address: SilentPaymentAddress) -> Option<&AnkSharedSecretHash> { + pub fn get_secret_for_address( + &self, + address: SilentPaymentAddress, + ) -> Option<&AnkSharedSecretHash> { self.shared_secrets.get(&address) } @@ -116,7 +124,10 @@ impl SecretsStore { self.unconfirmed_secrets.clone() } - pub fn remove_secret_for_address(&mut self, address: SilentPaymentAddress) -> Result<(SilentPaymentAddress, AnkSharedSecretHash)> { + pub fn remove_secret_for_address( + &mut self, + address: SilentPaymentAddress, + ) -> Result<(SilentPaymentAddress, AnkSharedSecretHash)> { if let Some(removed_secret) = self.shared_secrets.remove(&address) { return Ok((address, removed_secret)); } else { diff --git a/src/serialization.rs b/src/serialization.rs index db11b34..2997daf 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -1,10 +1,10 @@ -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use crate::{pcd::Member, process::Process}; use serde::de::Error; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sp_client::bitcoin::hex::{DisplayHex, FromHex}; use sp_client::bitcoin::OutPoint; -use tsify::Tsify; use std::collections::{BTreeMap, HashMap}; -use crate::{pcd::Member, process::Process}; +use tsify::Tsify; #[derive(Debug, Serialize, Deserialize, Tsify)] #[tsify(from_wasm_abi)] @@ -41,23 +41,15 @@ pub mod members_map { use super::*; use crate::pcd::Member; - pub fn serialize( - map: &HashMap, - serializer: S, - ) -> Result + pub fn serialize(map: &HashMap, serializer: S) -> Result where S: Serializer, { - let map: HashMap = map - .iter() - .map(|(k, v)| (*k, v.to_owned())) - .collect(); + let map: HashMap = map.iter().map(|(k, v)| (*k, v.to_owned())).collect(); map.serialize(serializer) } - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result, D::Error> + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -71,10 +63,7 @@ pub mod outpoint_map { use super::*; use crate::process::Process; - pub fn serialize( - map: &HashMap, - serializer: S, - ) -> Result + pub fn serialize(map: &HashMap, serializer: S) -> Result where S: Serializer, { @@ -88,9 +77,7 @@ pub mod outpoint_map { map.serialize(serializer) } - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result, D::Error> + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -101,9 +88,7 @@ pub mod outpoint_map { let result: Result, D::Error> = map .into_iter() .map(|(k, v)| { - let outpoint = k - .parse() - .map_err(serde::de::Error::custom)?; + let outpoint = k.parse().map_err(serde::de::Error::custom)?; Ok((outpoint, v)) }) .collect(); @@ -118,10 +103,7 @@ pub mod hex_array_btree { // Serializes a BTreeMap as a BTreeMap // where the value is a hex-encoded string. - pub fn serialize( - map: &BTreeMap, - serializer: S, - ) -> Result + pub fn serialize(map: &BTreeMap, serializer: S) -> Result where S: Serializer, { @@ -135,9 +117,7 @@ pub mod hex_array_btree { // Deserializes a BTreeMap from a BTreeMap // where the value is expected to be a hex-encoded string. - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result, D::Error> + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { diff --git a/src/silentpayments.rs b/src/silentpayments.rs index 079e041..63af9b2 100644 --- a/src/silentpayments.rs +++ b/src/silentpayments.rs @@ -9,8 +9,10 @@ use tsify::Tsify; use rand::{thread_rng, Rng}; use sp_client::bitcoin::{Amount, BlockHash, OutPoint, Transaction, TxOut, Txid, XOnlyPublicKey}; -use sp_client::{FeeRate, OutputSpendStatus, OwnedOutput, Recipient, SilentPaymentUnsignedTransaction, SpClient}; use sp_client::silentpayments::utils::receiving::calculate_ecdh_shared_secret; +use sp_client::{ + FeeRate, OutputSpendStatus, OwnedOutput, Recipient, SilentPaymentUnsignedTransaction, SpClient, +}; #[derive(Debug, Default, Deserialize, Serialize, Clone)] pub struct SpWallet { @@ -41,14 +43,16 @@ impl SpWallet { } pub fn get_unspent_outputs(&self) -> HashMap { - self.outputs.iter() + self.outputs + .iter() .filter(|(_, output)| output.spend_status == OutputSpendStatus::Unspent) .map(|(outpoint, output)| (*outpoint, output.clone())) .collect() } pub fn get_balance(&self) -> Amount { - self.outputs.values() + self.outputs + .values() .filter(|output| output.spend_status == OutputSpendStatus::Unspent) .fold(Amount::ZERO, |acc, x| acc + x.amount) } @@ -94,7 +98,12 @@ impl SpWallet { } } - pub fn update_with_transaction(&mut self, tx: &Transaction, public_tweak: &PublicKey, height: u32) -> Result> { + pub fn update_with_transaction( + &mut self, + tx: &Transaction, + public_tweak: &PublicKey, + height: u32, + ) -> Result> { // Check if we have outputs that are spent by this transaction self.check_inputs(tx); let receiver = &self.get_sp_client().sp_receiver; @@ -106,7 +115,7 @@ impl SpWallet { .collect(); if p2tr_outs.is_empty() { - return Err(Error::msg("No taproot outputs")) + return Err(Error::msg("No taproot outputs")); }; // That should never happen since we have a tweak_data, but anyway // Now we can just run sp_receiver on all the p2tr outputs @@ -164,7 +173,7 @@ impl SpWallet { #[derive(Debug, Serialize, Deserialize, PartialEq, Tsify)] #[tsify(from_wasm_abi)] -pub struct TsUnsignedTransaction(SilentPaymentUnsignedTransaction); +pub struct TsUnsignedTransaction(SilentPaymentUnsignedTransaction); impl TsUnsignedTransaction { pub fn new(unsigned_tx: SilentPaymentUnsignedTransaction) -> Self { @@ -194,16 +203,16 @@ pub fn create_transaction( thread_rng().fill(&mut commitment); } - recipients.push(Recipient { - address: sp_client::RecipientAddress::Data(commitment.to_vec()), - amount: Amount::ZERO + recipients.push(Recipient { + address: sp_client::RecipientAddress::Data(commitment.to_vec()), + amount: Amount::ZERO, }); let new_transaction = sp_client.create_new_transaction( - available_outpoints, - recipients, + available_outpoints, + recipients, fee_rate, - sp_client.get_network() + sp_client.get_network(), )?; let finalized_transaction = SpClient::finalize_transaction(new_transaction)?; @@ -211,7 +220,10 @@ pub fn create_transaction( Ok(finalized_transaction) } -pub fn sign_transaction(sp_client: &SpClient, unsigned_transaction: SilentPaymentUnsignedTransaction) -> Result { +pub fn sign_transaction( + sp_client: &SpClient, + unsigned_transaction: SilentPaymentUnsignedTransaction, +) -> Result { let mut aux_rand = [0u8; 32]; thread_rng().fill(&mut aux_rand); sp_client.sign_transaction(unsigned_transaction, &aux_rand) diff --git a/src/updates.rs b/src/updates.rs index 6e277fd..51ab715 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -1,10 +1,19 @@ -use std::{mem, sync::{mpsc::{self, Receiver, Sender}, Arc, RwLock}}; use anyhow::Result; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use std::{ + mem, + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, RwLock, + }, +}; // use wasm_bindgen::prelude::*; // use sp_client::bitcoin::absolute::Height; // use sp_client::bitcoin::BlockHash; -use sp_client::{bitcoin::{absolute::Height, BlockHash, OutPoint}, OwnedOutput, Updater}; +use sp_client::{ + bitcoin::{absolute::Height, BlockHash, OutPoint}, + OwnedOutput, Updater, +}; use std::collections::{HashMap, HashSet}; #[derive(Debug, Serialize, Deserialize)] @@ -32,7 +41,7 @@ pub enum StateUpdate { // extern "C" { // #[wasm_bindgen(js_namespace = window)] // fn sendScanProgress(progress: JsValue); - + // #[wasm_bindgen(js_namespace = window)] // fn sendStateUpdate(update: JsValue); // } @@ -53,7 +62,7 @@ impl NativeUpdateSink { pub fn new() -> (Self, Receiver, Receiver) { let (scan_tx, scan_rx) = mpsc::channel(); let (state_tx, state_rx) = mpsc::channel(); - + (Self { scan_tx, state_tx }, scan_rx, state_rx) } } @@ -99,7 +108,7 @@ pub fn init_update_sink(sink: Arc) { pub fn get_update_sink() -> Option> { UPDATE_SINK.read().unwrap().clone() -} +} #[derive(Debug)] pub struct StateUpdater {