diff --git a/src/device.rs b/src/device.rs index 5e491b9..4c49cf9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,10 +1,9 @@ -use serde_json::Value; use serde::{Deserialize, Serialize}; use tsify::Tsify; use uuid::Uuid; use wasm_bindgen::prelude::*; -use sp_client::{silentpayments::utils::SilentPaymentAddress, spclient::SpWallet}; +use sp_client::spclient::SpWallet; use crate::pcd::Member; @@ -13,8 +12,7 @@ use crate::pcd::Member; pub struct Device { sp_wallet: SpWallet, pairing_process_uuid: Option, - paired_devices: Vec, // map an address to the pairing process - latest_known_state: Value + paired_member: Option, } impl Device { @@ -22,8 +20,7 @@ impl Device { Self { sp_wallet, pairing_process_uuid: None, - paired_devices: vec![], - latest_known_state: Value::Null + paired_member: None, } } @@ -43,33 +40,12 @@ impl Device { self.pairing_process_uuid.clone() } - pub fn set_process_uuid(&mut self, uuid: Uuid) { + pub fn pair(&mut self, uuid: Uuid, member: Member) { self.pairing_process_uuid = Some(uuid.to_string()); + self.paired_member = Some(member); } - pub fn get_paired_devices(&self) -> Vec { - self.paired_devices.clone() - } - - pub fn push_paired_device(&mut self, new_device_address: SilentPaymentAddress) { - self.paired_devices.push(new_device_address.into()) - } - - pub fn to_member(&self) -> anyhow::Result { - if !self.is_linked() { - return Err(anyhow::Error::msg("device is not linked")); - } - - let member = self.latest_known_state.as_object().unwrap().get("member").unwrap().clone(); - let res: Member = serde_json::from_value(member)?; - Ok(res) - } - - pub fn get_latest_state(&self) -> Value { - self.latest_known_state.clone() - } - - pub fn update_latest_state(&mut self, update: Value) { - self.latest_known_state = update; + pub fn to_member(&self) -> Option { + self.paired_member.clone() } } \ No newline at end of file diff --git a/src/pcd.rs b/src/pcd.rs index 77f2626..97e57f2 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::{HashMap, HashSet}, str::FromStr}; use anyhow::{Result, Error}; use aes_gcm::{aead::{Aead, Payload}, AeadCore, Aes256Gcm, KeyInit}; @@ -11,47 +11,38 @@ use tsify::Tsify; use crate::crypto::AAD; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Tsify, PartialEq, Eq, Hash, PartialOrd)] -pub enum Role { - User, - Manager, - Admin, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Member { - nym: String, - sp_address_a: String, - sp_address_b: String, - role: Role, + sp_addresses: Vec } impl Member { pub fn new( - nym: String, - sp_address_a: SilentPaymentAddress, - sp_address_b: SilentPaymentAddress, - role: Role, - ) -> Self { - Self { - nym, - sp_address_a: sp_address_a.into(), - sp_address_b: sp_address_b.into(), - role, + sp_addresses: Vec, + ) -> Result { + if sp_addresses.is_empty() { + return Err(Error::msg("empty address set")); } + + let mut seen = HashSet::new(); + for s in sp_addresses.iter() { + if !seen.insert(s.clone()) { + return Err(Error::msg("Duplicate addresses found")); + } + } + + let res: Vec = sp_addresses.iter() + .map(|a| Into::::into(*a)) + .collect(); + + Ok(Self { + sp_addresses: res + }) } - pub fn get_nym(&self) -> String { - self.nym.clone() - } - - pub fn get_addresses(&self) -> (String, String) { - (self.sp_address_a.clone(), self.sp_address_b.clone()) - } - - pub fn get_role(&self) -> Role { - self.role + pub fn get_addresses(&self) -> Vec { + self.sp_addresses.clone() } } @@ -145,3 +136,48 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> { } impl Pcd<'_> for Value {} + +#[derive(Debug, Clone, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct ValidationRule { + quorum: f32, // Must be >= 0.0, <= 1.0, 0.0 means reading right + pub fields: Vec, // Which fields are concerned by this rule + min_sig_member: f32, // Must be >= 0.0, <= 1.0, does each member need to sign with all it's devices? +} + +impl ValidationRule { + pub fn new(quorum: f32, fields: Vec, min_sig_member: f32) -> Result { + if quorum < 0.0 || quorum > 1.0 { + return Err(Error::msg("quorum must be 0.0 < quorum <= 1.0")); + } + + if min_sig_member < 0.0 || min_sig_member > 1.0 { + return Err(Error::msg("min_signatures_member must be 0.0 < min_signatures_member <= 1.0")); + } + + if fields.is_empty() { + return Err(Error::msg("Fields can't be empty")); + } + + let res = Self { + quorum, + fields, + min_sig_member, + }; + + Ok(res) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct RoleDefinition { + pub members: Vec, + pub validation_rules: Vec, +} + +// #[derive(Debug, Clone, Serialize, Deserialize, Tsify)] +// #[tsify(into_wasm_abi, from_wasm_abi)] +// pub struct Roles { +// pub roles: HashMap +// } diff --git a/src/prd.rs b/src/prd.rs index b51570d..9066900 100644 --- a/src/prd.rs +++ b/src/prd.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::str::FromStr; use anyhow::{Result, Error}; @@ -5,6 +6,7 @@ use serde::{Serialize, Deserialize}; use serde_json::{Map, Value}; use sp_client::bitcoin::secp256k1::SecretKey; +use sp_client::bitcoin::XOnlyPublicKey; use sp_client::silentpayments::utils::SilentPaymentAddress; use sp_client::spclient::SpWallet; use sp_client::bitcoin::secp256k1::schnorr::Signature; @@ -77,8 +79,7 @@ impl AnkValidationNoHash { pub struct ValidationToken { member: Member, message: [u8; 32], - sig_a: Signature, - sig_b: Signature, // User must sign with its 2 paired devices + sigs: Vec, // User must sign with the requested number of devices } sha256t_hash_newtype! { @@ -146,13 +147,22 @@ impl Prd { // check that the proof is consistent let sender: Member = serde_json::from_str(&prd.sender)?; if let Some(proof) = prd.proof { - // take the 2 spending keys in sender - let (address_a, address_b) = sender.get_addresses(); - let spend_key_a = ::try_from(address_a)?.get_spend_key().x_only_public_key().0; - let spend_key_b = ::try_from(address_b)?.get_spend_key().x_only_public_key().0; - // The key in proof must be one of the 2 sender keys + // take the spending keys in sender + let addresses = sender.get_addresses(); + let mut spend_keys: Vec = vec![]; + for address in addresses { + spend_keys.push(::try_from(address)?.get_spend_key().x_only_public_key().0); + } + // The key in proof must be one of the sender keys let proof_key = proof.get_key(); - if spend_key_a != proof_key && spend_key_b != proof_key { + let mut known_key = false; + for key in spend_keys { + if key == proof_key { + known_key = true; + break; + } + } + if !known_key { return Err(anyhow::Error::msg("Proof signed with an unknown key")); } proof.verify()?; @@ -168,7 +178,7 @@ impl Prd { Ok(()) } - pub fn filter_keys(&mut self, to_keep: Vec) { + pub fn filter_keys(&mut self, to_keep: HashSet) { let current_keys = self.keys.clone(); let filtered_keys: Map = current_keys.into_iter() .filter(|(field, _)| to_keep.contains(field)) diff --git a/src/process.rs b/src/process.rs index a42623f..2ad347b 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,59 +1,35 @@ -use std::str::FromStr; +use std::collections::HashMap; + +use anyhow::{Error, Result}; use serde::{Deserialize, Serialize}; -use serde_json::Value; -use sp_client::{bitcoin::OutPoint, silentpayments::utils::SilentPaymentAddress}; +use serde_json::{Map, Value}; +use sp_client::bitcoin::OutPoint; use tsify::Tsify; use uuid::Uuid; use wasm_bindgen::prelude::*; -use crate::pcd::Role; - -#[derive(Debug, Clone, Serialize, Deserialize, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct ValidationRules { - quorum: f32, // Must be > 0.0, <= 1.0 - min_permission: Role, // Only users with at least that Role can send a token - min_signatures_member: f32, // Must be > 0.0, <= 1.0, does each member need to sign with all it's devices? -} - -impl ValidationRules { - pub fn new(quorum: f32, min_permission: Role, min_signatures_member: f32) -> Self { - Self { - quorum, - min_permission, - min_signatures_member, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Process { pub uuid: String, - pub name: String, - pub validation_rules: ValidationRules, pub html: String, pub style: String, pub script: String, - pub init_state: Value, + pub init_state: Map, pub commited_in: OutPoint, } impl Process { pub fn new( - name: String, - validation_rules: ValidationRules, html: String, style: String, script: String, - init_state: Value, + init_state: Map, commited_in: OutPoint, ) -> Self { Self { uuid: Uuid::new_v4().to_string(), - name, - validation_rules, html, style, script,