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::borrow::Borrow;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::ops::Index;
use std::str::FromStr;
@ -15,7 +15,7 @@ use anyhow::Error as AnyhowError;
use sdk_common::crypto::{
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::sp_client::bitcoin::blockdata::fee_rate;
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
@ -52,7 +52,7 @@ use wasm_bindgen::prelude::*;
use sdk_common::network::{
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::silentpayments::{create_transaction, map_outputs_to_sp_address};
use sdk_common::sp_client::spclient::{
@ -222,7 +222,7 @@ pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult<String
}
#[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()?;
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() })?);
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)?);
sp_addresses.push(local_device.get_wallet().get_client().get_receiving_address());
local_device.pair(
Uuid::parse_str(&uuid).unwrap(),
Member::new(sp_addresses.into_iter().map(|a| TryInto::<SilentPaymentAddress>::try_into(a).unwrap()).collect())?
);
Ok(())
}
@ -539,6 +542,20 @@ pub fn get_available_amount() -> ApiResult<u64> {
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)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)]
@ -548,54 +565,38 @@ pub struct createTransactionReturn {
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]
pub fn create_process_init_transaction(
args: CreateProcessInitTransactionArguments,
mut new_process: Process,
fee_rate: u32,
) -> ApiResult<createTransactionReturn> {
if args.member2fields.len() == 0 {
return Err(ApiError {
message: "Must have at least one recipient".to_owned(),
});
let pcd = new_process.init_state;
let roles = pcd["roles"].as_object().unwrap().clone();
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();
for member in role.members {
if !all_members.contains_key(&member) {
all_members.insert(member.clone(), HashSet::new());
}
all_members.get_mut(&member).unwrap().extend(fields.clone());
}
}
let pcd: Map<String, Value>;
if let Some(object) = Value::from_str(&args.stringified_pcd)?.as_object() {
pcd = object.to_owned();
} else {
return Err(ApiError {
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();
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);
// we actually have 2 "recipients" in a technical sense for each social recipient
let mut recipients: Vec<Recipient> = Vec::with_capacity(nb_recipients*2); // We suppose that will work most of the time
// 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
for member in all_members {
let (address_a, address_b) = member.get_addresses();
for sp_address in [address_a, address_b].into_iter() {
for member in all_members.keys() {
let addresses = member.get_addresses();
for sp_address in addresses.into_iter() {
recipients.push(Recipient {
address: sp_address.into(),
amount: DEFAULT_AMOUNT,
@ -606,35 +607,20 @@ pub fn create_process_init_transaction(
let mut fields2keys = 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 sp_wallet = local_device.get_wallet();
let latest_state = local_device.get_latest_state();
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()
)?;
let sender: Member = local_device.to_member().unwrap();
// We first generate the prd with all the keys that we will keep to ourselves
let full_prd = Prd::new(
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)?,
fields2cipher.clone(),
fields2keys.clone()
@ -664,7 +650,7 @@ pub fn create_process_init_transaction(
let mut new_messages = vec![];
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();
prd.filter_keys(visible_fields);
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.status = CachedMessageStatus::Opened;
let (addresses1, addresses2) = member.get_addresses();
for sp_address in [addresses1, addresses2].into_iter() {
let addresses = member.get_addresses();
for sp_address in addresses.into_iter() {
let shared_point = sp_utils::sending::calculate_ecdh_shared_secret(
&<SilentPaymentAddress>::try_from(sp_address).unwrap().get_scan_key(),
&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
processes.insert(Uuid::parse_str(&process.uuid).unwrap(), RelevantProcess {
process,
processes.insert(Uuid::parse_str(&new_process.uuid).unwrap(), RelevantProcess {
process: new_process,
states: vec![init_state],
current_status: ProcessStatus::Active(shared_secrets)
});

View File

@ -1,18 +1,17 @@
use std::collections::HashMap;
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_common::network::CachedMessage;
use sdk_common::pcd::{Member, Pcd, Role};
use sdk_common::pcd::{Member, Pcd};
use sdk_common::prd::Prd;
use sdk_common::process::{Process, ValidationRules};
use sdk_common::sp_client::bitcoin::OutPoint;
use sdk_common::sp_client::spclient::OwnedOutput;
use sdk_common::uuid::Uuid;
use sdk_common::log::debug;
use serde_json::{self, json, Value};
use serde_json::{self, json};
use tsify::JsValueSerdeExt;
use wasm_bindgen_test::*;
@ -32,52 +31,60 @@ fn test_pairing() {
reset_device().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
let new_member = Member::new(
DEFAULT_NYM.to_owned(),
helper_get_alice_address().try_into().unwrap(),
helper_get_bob_address().try_into().unwrap(),
Role::User
);
vec![
device_address.as_str().try_into().unwrap(),
paired_device.as_str().try_into().unwrap(),
]
).unwrap();
let initial_state = json!({
"nym": DEFAULT_NYM,
"members": [
new_member,
],
"current_session_tx": null,
// We get the template for the pairing
// We don't really care how we get it, we can even imagine user writing it himself
// It just have to respect the basic Process struct, i.e. have all the fields below and the right type for the value
let pairing_template = json!({
"uuid": "",
"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(
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()
);
let new_process = create_process_from_template(pairing_template.to_string()).unwrap();
// 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");
let args = CreateProcessInitTransactionArguments {
member2fields,
process: pairing_process,
stringified_pcd: initial_state.to_string()
};
let alice_pairing_return = create_process_init_transaction(args, 1).unwrap();
let alice_pairing_return = create_process_init_transaction(new_process, 1).unwrap();
let get_outputs_result = get_outputs().unwrap();
@ -89,6 +96,8 @@ fn test_pairing() {
// Alice parse her own transaction
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
reset_device().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 mut pcd = initial_state.encrypted_pcd;
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
// login();
// Once we know this tx id, we can commit to the relay
}