Update process creation

This commit is contained in:
Sosthene 2024-08-30 20:02:47 +02:00
parent 287f74136e
commit cf3278dd3d
2 changed files with 108 additions and 112 deletions

View File

@ -1,6 +1,6 @@
use std::any::Any; use std::any::Any;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::io::Write; use std::io::Write;
use std::ops::Index; use std::ops::Index;
use std::str::FromStr; use std::str::FromStr;
@ -15,7 +15,7 @@ use anyhow::Error as AnyhowError;
use sdk_common::crypto::{ use sdk_common::crypto::{
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, KeyInit, Purpose, AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, KeyInit, Purpose,
}; };
use sdk_common::process::{Process, ValidationRules}; use sdk_common::process::Process;
use sdk_common::signature; use sdk_common::signature;
use sdk_common::sp_client::bitcoin::blockdata::fee_rate; use sdk_common::sp_client::bitcoin::blockdata::fee_rate;
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize}; use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
@ -52,7 +52,7 @@ use wasm_bindgen::prelude::*;
use sdk_common::network::{ use sdk_common::network::{
self, AnkFlag, CachedMessage, CachedMessageStatus, Envelope, FaucetMessage, NewTxMessage}; self, AnkFlag, CachedMessage, CachedMessageStatus, Envelope, FaucetMessage, NewTxMessage};
use sdk_common::pcd::{AnkPcdHash, Pcd, Member}; use sdk_common::pcd::{AnkPcdHash, Member, Pcd, RoleDefinition, ValidationRule};
use sdk_common::prd::{AnkPrdHash, Prd, PrdType}; use sdk_common::prd::{AnkPrdHash, Prd, PrdType};
use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address}; use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address};
use sdk_common::sp_client::spclient::{ use sdk_common::sp_client::spclient::{
@ -222,7 +222,7 @@ pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult<String
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn pair_device(uuid: String, new_device_address: String, state: String) -> ApiResult<()> { pub fn pair_device(uuid: String, mut sp_addresses: Vec<String>) -> ApiResult<()> {
let mut local_device = lock_local_device()?; let mut local_device = lock_local_device()?;
if local_device.is_linked() { if local_device.is_linked() {
@ -231,9 +231,12 @@ pub fn pair_device(uuid: String, new_device_address: String, state: String) -> A
}); });
} }
local_device.set_process_uuid(Uuid::parse_str(&uuid).map_err(|e| ApiError { message: e.to_string() })?); sp_addresses.push(local_device.get_wallet().get_client().get_receiving_address());
local_device.push_paired_device(new_device_address.try_into().map_err(|_| ApiError { message: "Invalid address".to_owned() })?);
local_device.update_latest_state(serde_json::from_str(&state)?); local_device.pair(
Uuid::parse_str(&uuid).unwrap(),
Member::new(sp_addresses.into_iter().map(|a| TryInto::<SilentPaymentAddress>::try_into(a).unwrap()).collect())?
);
Ok(()) Ok(())
} }
@ -539,6 +542,20 @@ pub fn get_available_amount() -> ApiResult<u64> {
Ok(device.get_wallet().get_outputs().get_balance().to_sat()) Ok(device.get_wallet().get_outputs().get_balance().to_sat())
} }
#[wasm_bindgen]
pub fn create_process_from_template(json: String) -> ApiResult<Process> {
let template_process: Process = serde_json::from_str(&json)?;
let mut new_process = Process::new(
template_process.html,
template_process.style,
template_process.script,
template_process.init_state,
template_process.commited_in
);
Ok(new_process)
}
#[derive(Debug, Tsify, Serialize, Deserialize, Default)] #[derive(Debug, Tsify, Serialize, Deserialize, Default)]
#[tsify(into_wasm_abi, from_wasm_abi)] #[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -548,54 +565,38 @@ pub struct createTransactionReturn {
pub new_messages: Vec<CachedMessage> pub new_messages: Vec<CachedMessage>
} }
#[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct CreateProcessInitTransactionArguments{
pub member2fields: HashMap<Member, Vec<String>>,
pub process: Process,
pub stringified_pcd: String, // must be valid json
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn create_process_init_transaction( pub fn create_process_init_transaction(
args: CreateProcessInitTransactionArguments, mut new_process: Process,
fee_rate: u32, fee_rate: u32,
) -> ApiResult<createTransactionReturn> { ) -> ApiResult<createTransactionReturn> {
if args.member2fields.len() == 0 { let pcd = new_process.init_state;
return Err(ApiError { let roles = pcd["roles"].as_object().unwrap().clone();
message: "Must have at least one recipient".to_owned(), let mut all_members: HashMap<Member, HashSet<String>> = HashMap::new();
}); for (name, role_def) in roles {
} let role: RoleDefinition = serde_json::from_str(&role_def.to_string()).unwrap();
let fields: Vec<String> = role.validation_rules.iter().flat_map(|rule| rule.fields.clone()).collect();
let pcd: Map<String, Value>; for member in role.members {
if let Some(object) = Value::from_str(&args.stringified_pcd)?.as_object() { if !all_members.contains_key(&member) {
pcd = object.to_owned(); all_members.insert(member.clone(), HashSet::new());
} else { }
return Err(ApiError { all_members.get_mut(&member).unwrap().extend(fields.clone());
message: "provided pcd is not a valid json".to_owned(), }
});
} }
let mut process = args.process;
// process doesn't have an initial state at this stage
if process.init_state != Value::Null {
return Err(ApiError {
message: "new process must have a null initial state".to_owned(),
});
}
// maybe the process script can embed some basic checks for the pcd, for example `pcd.members.len() == 1`
let all_members: Vec<&Member> = args.member2fields.keys().collect();
let nb_recipients = all_members.len(); let nb_recipients = all_members.len();
if nb_recipients == 0 {
return Err(ApiError {
message: "Can't create a process with 0 member".to_owned(),
});
}
let mut recipients: Vec<Recipient> = Vec::with_capacity(nb_recipients*2); let mut recipients: Vec<Recipient> = Vec::with_capacity(nb_recipients*2); // We suppose that will work most of the time
// we actually have 2 "recipients" in a technical sense for each social recipient // we actually have multiple "recipients" in a technical sense for each social recipient
// that's necessary because we don't want to miss a notification because we don't have a device atm // that's necessary because we don't want to miss a notification because we don't have a device atm
for member in all_members { for member in all_members.keys() {
let (address_a, address_b) = member.get_addresses(); let addresses = member.get_addresses();
for sp_address in [address_a, address_b].into_iter() { for sp_address in addresses.into_iter() {
recipients.push(Recipient { recipients.push(Recipient {
address: sp_address.into(), address: sp_address.into(),
amount: DEFAULT_AMOUNT, amount: DEFAULT_AMOUNT,
@ -606,35 +607,20 @@ pub fn create_process_init_transaction(
let mut fields2keys = Map::new(); let mut fields2keys = Map::new();
let mut fields2cipher = Map::new(); let mut fields2cipher = Map::new();
Value::from_str(&args.stringified_pcd).unwrap().encrypt_fields(&mut fields2keys, &mut fields2cipher); Value::Object(pcd.clone()).encrypt_fields(&mut fields2keys, &mut fields2cipher);
process.init_state = Value::Object(fields2cipher.clone()); new_process.init_state = fields2cipher.clone();
let local_device = lock_local_device()?; let local_device = lock_local_device()?;
let sp_wallet = local_device.get_wallet(); let sp_wallet = local_device.get_wallet();
let latest_state = local_device.get_latest_state(); let sender: Member = local_device.to_member().unwrap();
if latest_state == Value::Null {
return Err(ApiError {
message: "Device not paired, nor pairing".to_owned(),
});
}
let sender: Member = serde_json::from_value(
latest_state.get("members")
.unwrap()
.as_array()
.ok_or(ApiError { message: "members must be an array".to_owned() })?
.get(0)
.ok_or(ApiError { message: "empty array".to_owned() })?
.clone()
)?;
// We first generate the prd with all the keys that we will keep to ourselves // We first generate the prd with all the keys that we will keep to ourselves
let full_prd = Prd::new( let full_prd = Prd::new(
PrdType::Init, PrdType::Init,
Uuid::from_str(&process.uuid).expect("We can trust process to have a valid uuid"), Uuid::from_str(&new_process.uuid).expect("We can trust process to have a valid uuid"),
serde_json::to_string(&sender)?, serde_json::to_string(&sender)?,
fields2cipher.clone(), fields2cipher.clone(),
fields2keys.clone() fields2keys.clone()
@ -664,7 +650,7 @@ pub fn create_process_init_transaction(
let mut new_messages = vec![]; let mut new_messages = vec![];
let mut shared_secrets = vec![]; // This is a bit ugly, but this way we can update the process status let mut shared_secrets = vec![]; // This is a bit ugly, but this way we can update the process status
for (member, visible_fields) in args.member2fields { for (member, visible_fields) in all_members {
let mut prd = full_prd.clone(); let mut prd = full_prd.clone();
prd.filter_keys(visible_fields); prd.filter_keys(visible_fields);
let prd_msg = prd.to_network_msg(sp_wallet)?; let prd_msg = prd.to_network_msg(sp_wallet)?;
@ -676,8 +662,8 @@ pub fn create_process_init_transaction(
res.commitment = Some(prd_commitment.to_string()); res.commitment = Some(prd_commitment.to_string());
res.status = CachedMessageStatus::Opened; res.status = CachedMessageStatus::Opened;
let (addresses1, addresses2) = member.get_addresses(); let addresses = member.get_addresses();
for sp_address in [addresses1, addresses2].into_iter() { for sp_address in addresses.into_iter() {
let shared_point = sp_utils::sending::calculate_ecdh_shared_secret( let shared_point = sp_utils::sending::calculate_ecdh_shared_secret(
&<SilentPaymentAddress>::try_from(sp_address).unwrap().get_scan_key(), &<SilentPaymentAddress>::try_from(sp_address).unwrap().get_scan_key(),
&partial_secret, &partial_secret,
@ -708,8 +694,8 @@ pub fn create_process_init_transaction(
}; };
// We are initializing a process, so we shouldn't have it in our cache yet // We are initializing a process, so we shouldn't have it in our cache yet
processes.insert(Uuid::parse_str(&process.uuid).unwrap(), RelevantProcess { processes.insert(Uuid::parse_str(&new_process.uuid).unwrap(), RelevantProcess {
process, process: new_process,
states: vec![init_state], states: vec![init_state],
current_status: ProcessStatus::Active(shared_secrets) current_status: ProcessStatus::Active(shared_secrets)
}); });

View File

@ -1,18 +1,17 @@
use std::collections::HashMap; use std::collections::HashMap;
use sdk_client::api::{ use sdk_client::api::{
create_device_from_sp_wallet, create_process_init_transaction, get_outputs, pair_device, reset_device, setup, CreateProcessInitTransactionArguments create_device_from_sp_wallet, create_process_from_template, create_process_init_transaction, get_address, get_outputs, pair_device, reset_device, setup
}; };
use sdk_client::lock_processes; use sdk_client::lock_processes;
use sdk_common::network::CachedMessage; use sdk_common::network::CachedMessage;
use sdk_common::pcd::{Member, Pcd, Role}; use sdk_common::pcd::{Member, Pcd};
use sdk_common::prd::Prd; use sdk_common::prd::Prd;
use sdk_common::process::{Process, ValidationRules};
use sdk_common::sp_client::bitcoin::OutPoint; use sdk_common::sp_client::bitcoin::OutPoint;
use sdk_common::sp_client::spclient::OwnedOutput; use sdk_common::sp_client::spclient::OwnedOutput;
use sdk_common::uuid::Uuid; use sdk_common::uuid::Uuid;
use sdk_common::log::debug; use sdk_common::log::debug;
use serde_json::{self, json, Value}; use serde_json::{self, json};
use tsify::JsValueSerdeExt; use tsify::JsValueSerdeExt;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
@ -32,52 +31,60 @@ fn test_pairing() {
reset_device().unwrap(); reset_device().unwrap();
create_device_from_sp_wallet(ALICE_LOGIN_WALLET.to_owned()).unwrap(); create_device_from_sp_wallet(ALICE_LOGIN_WALLET.to_owned()).unwrap();
// we get our own address
let device_address = get_address().unwrap();
// we scan the qr code or get the address by any other means
let paired_device = helper_get_bob_address();
// Alice creates the new member with Bob address // Alice creates the new member with Bob address
let new_member = Member::new( let new_member = Member::new(
DEFAULT_NYM.to_owned(), vec![
helper_get_alice_address().try_into().unwrap(), device_address.as_str().try_into().unwrap(),
helper_get_bob_address().try_into().unwrap(), paired_device.as_str().try_into().unwrap(),
Role::User ]
); ).unwrap();
let initial_state = json!({ // We get the template for the pairing
"nym": DEFAULT_NYM, // We don't really care how we get it, we can even imagine user writing it himself
"members": [ // It just have to respect the basic Process struct, i.e. have all the fields below and the right type for the value
new_member, let pairing_template = json!({
], "uuid": "",
"current_session_tx": null, "html": "",
"script": "",
"style": "",
"init_state": {
"roles": {
"owner": {
"members":
[
new_member
],
"validation_rules":
[
{
"quorum": 0.0,
"fields": [
"roles",
"pairing_tx"
],
"min_sig_member": 0.0
}
]
}
},
"pairing_tx": "",
},
"commited_in": OutPoint::null()
}); });
let validation_rules = ValidationRules::new( let new_process = create_process_from_template(pairing_template.to_string()).unwrap();
1.0,
Role::Admin,
1.0
);
let mut member2fields: HashMap<Member, Vec<String>> = HashMap::new();
member2fields.insert(new_member, initial_state.as_object().unwrap().keys().map(|k| k.to_owned()).collect());
// We define the process for pairing
let pairing_process = Process::new(
"pairing".to_owned(),
validation_rules,
String::default(),
String::default(),
String::default(),
Value::Null,
OutPoint::null()
);
// we can update our local device now // we can update our local device now
pair_device(pairing_process.uuid.clone(), helper_get_bob_address(), initial_state.to_string()).unwrap(); pair_device(new_process.uuid.clone(), vec![helper_get_bob_address()]).unwrap();
debug!("Alice sends a transaction commiting to an init prd to Bob"); debug!("Alice sends a transaction commiting to an init prd to Bob");
let args = CreateProcessInitTransactionArguments { let alice_pairing_return = create_process_init_transaction(new_process, 1).unwrap();
member2fields,
process: pairing_process,
stringified_pcd: initial_state.to_string()
};
let alice_pairing_return = create_process_init_transaction(args, 1).unwrap();
let get_outputs_result = get_outputs().unwrap(); let get_outputs_result = get_outputs().unwrap();
@ -89,6 +96,8 @@ fn test_pairing() {
// Alice parse her own transaction // Alice parse her own transaction
helper_parse_transaction(&alice_pairing_return.transaction, &alice_pairing_tweak_data).id; helper_parse_transaction(&alice_pairing_return.transaction, &alice_pairing_tweak_data).id;
// Notify user that we're waiting for confirmation from the other device
// ======================= Bob // ======================= Bob
reset_device().unwrap(); reset_device().unwrap();
create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap(); create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap();
@ -138,10 +147,11 @@ fn test_pairing() {
let keys = initial_state.keys; let keys = initial_state.keys;
let mut pcd = initial_state.encrypted_pcd; let mut pcd = initial_state.encrypted_pcd;
pcd.decrypt_fields(&keys).unwrap(); pcd.decrypt_fields(&keys).unwrap();
pair_device(relevant_process.get_process().uuid, helper_get_alice_address(), pcd.to_string()).unwrap(); pair_device(relevant_process.get_process().uuid, vec![device_address]).unwrap();
} }
// To make the pairing effective, alice and bob must now spend their respective output into a new transaction // To make the pairing effective, alice and bob must now spend their respective output into a new transaction
// login();
// Once we know this tx id, we can commit to the relay // Once we know this tx id, we can commit to the relay
} }