Update process creation
This commit is contained in:
parent
287f74136e
commit
cf3278dd3d
126
src/api.rs
126
src/api.rs
@ -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)
|
||||
});
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user