Process initial update
This commit is contained in:
parent
b7bffe7678
commit
b904f4ec81
1125
src/api.rs
1125
src/api.rs
File diff suppressed because it is too large
Load Diff
35
src/lib.rs
35
src/lib.rs
@ -2,12 +2,13 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use sdk_common::crypto::AnkSharedSecret;
|
use sdk_common::crypto::AnkSharedSecret;
|
||||||
use sdk_common::network::CachedMessage;
|
use sdk_common::network::CachedMessage;
|
||||||
|
use sdk_common::pcd::AnkPcdHash;
|
||||||
|
use sdk_common::prd::{Prd, ValidationToken};
|
||||||
use sdk_common::process::Process;
|
use sdk_common::process::Process;
|
||||||
use sdk_common::sp_client::bitcoin::OutPoint;
|
use sdk_common::sp_client::bitcoin::OutPoint;
|
||||||
use sdk_common::uuid::Uuid;
|
use sdk_common::uuid::Uuid;
|
||||||
use sdk_common::prd::ValidationToken;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Value, Map};
|
use serde_json::{Map, Value};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
@ -27,40 +28,34 @@ pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error>
|
|||||||
.lock_anyhow()
|
.lock_anyhow()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum ProcessStatus {
|
|
||||||
Sealed,
|
|
||||||
Active(Vec<String>), // shared_secrets used to communicate on current session
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ProcessState {
|
pub struct ProcessState {
|
||||||
pub commited_in: OutPoint,
|
pub commited_in: OutPoint,
|
||||||
pub encrypted_pcd: Value,
|
pub encrypted_pcd: Value,
|
||||||
pub keys: Map<String, Value>, // We may not always have all the keys
|
pub keys: Map<String, Value>, // We may not always have all the keys
|
||||||
pub validation_token: Vec<ValidationToken> // This signs the encrypted pcd
|
pub validation_token: Vec<ValidationToken>, // This signs the encrypted pcd
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct RelevantProcess {
|
pub struct RelevantProcess {
|
||||||
process: Process,
|
|
||||||
states: Vec<ProcessState>,
|
states: Vec<ProcessState>,
|
||||||
current_status: ProcessStatus,
|
shared_secrets: HashMap<String, String>,
|
||||||
|
impending_requests: Vec<Prd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelevantProcess {
|
impl RelevantProcess {
|
||||||
pub fn get_process(&self) -> Process {
|
|
||||||
self.process.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_status_at(&self, index: usize) -> Option<ProcessState> {
|
pub fn get_status_at(&self, index: usize) -> Option<ProcessState> {
|
||||||
self.states.get(index).cloned()
|
self.states.get(index).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_latest_state(&self) -> Option<ProcessState> {
|
||||||
|
self.states.last().cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static CACHEDPROCESSES: OnceLock<Mutex<HashMap<Uuid, RelevantProcess>>> = OnceLock::new();
|
pub static CACHEDPROCESSES: OnceLock<Mutex<HashMap<OutPoint, RelevantProcess>>> = OnceLock::new();
|
||||||
|
|
||||||
pub fn lock_processes() -> Result<MutexGuard<'static, HashMap<Uuid, RelevantProcess>>, Error> {
|
pub fn lock_processes() -> Result<MutexGuard<'static, HashMap<OutPoint, RelevantProcess>>, Error> {
|
||||||
CACHEDPROCESSES
|
CACHEDPROCESSES
|
||||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||||
.lock_anyhow()
|
.lock_anyhow()
|
||||||
|
@ -22,11 +22,11 @@ use std::io::{Cursor, Read, Write};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||||
|
|
||||||
|
use sdk_common::device::Device;
|
||||||
use sdk_common::sp_client::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
use sdk_common::sp_client::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
||||||
use sdk_common::sp_client::silentpayments::bitcoin_hashes::sha256;
|
use sdk_common::sp_client::silentpayments::bitcoin_hashes::sha256;
|
||||||
use sdk_common::sp_client::silentpayments::utils::{Network as SpNetwork, SilentPaymentAddress};
|
use sdk_common::sp_client::silentpayments::utils::{Network as SpNetwork, SilentPaymentAddress};
|
||||||
use sdk_common::sp_client::spclient::{OutputList, SpWallet, SpendKey};
|
use sdk_common::sp_client::spclient::{OutputList, SpWallet, SpendKey};
|
||||||
use sdk_common::device::Device;
|
|
||||||
|
|
||||||
use crate::peers::Peer;
|
use crate::peers::Peer;
|
||||||
use crate::wallet::generate_sp_wallet;
|
use crate::wallet::generate_sp_wallet;
|
||||||
|
233
tests/pairing.rs
233
tests/pairing.rs
@ -1,17 +1,16 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use sdk_client::api::{
|
use sdk_client::api::{
|
||||||
create_device_from_sp_wallet, create_process_from_template, create_process_init_transaction, get_address, get_outputs, pair_device, reset_device, setup
|
create_device_from_sp_wallet, create_update_transaction, dump_device, dump_process_cache,
|
||||||
|
get_address, get_outputs, get_update_proposals, pair_device, parse_cipher, reset_device,
|
||||||
|
restore_device, set_process_cache, setup, ApiReturn,
|
||||||
};
|
};
|
||||||
use sdk_client::lock_processes;
|
use sdk_common::log::debug;
|
||||||
use sdk_common::network::CachedMessage;
|
use sdk_common::pcd::{Member, RoleDefinition};
|
||||||
use sdk_common::pcd::{Member, Pcd};
|
|
||||||
use sdk_common::prd::Prd;
|
|
||||||
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 serde_json::{json, Value};
|
||||||
use sdk_common::log::debug;
|
|
||||||
use serde_json::{self, json};
|
|
||||||
|
|
||||||
use tsify::JsValueSerdeExt;
|
use tsify::JsValueSerdeExt;
|
||||||
use wasm_bindgen_test::*;
|
use wasm_bindgen_test::*;
|
||||||
@ -38,117 +37,181 @@ fn test_pairing() {
|
|||||||
let paired_device = helper_get_bob_address();
|
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(vec![
|
||||||
vec![
|
device_address.as_str().try_into().unwrap(),
|
||||||
device_address.as_str().try_into().unwrap(),
|
paired_device.as_str().try_into().unwrap(),
|
||||||
paired_device.as_str().try_into().unwrap(),
|
])
|
||||||
]
|
.unwrap();
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// We get the template for the pairing
|
let pairing_init_state = json!({
|
||||||
// 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": "",
|
"html": "",
|
||||||
"script": "",
|
|
||||||
"style": "",
|
"style": "",
|
||||||
"init_state": {
|
"script": "",
|
||||||
"roles": {
|
"roles": {
|
||||||
"owner": {
|
"owner": {
|
||||||
"members":
|
"members":
|
||||||
[
|
[
|
||||||
new_member
|
new_member
|
||||||
],
|
],
|
||||||
"validation_rules":
|
"validation_rules":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"quorum": 0.0,
|
"quorum": 0.0,
|
||||||
"fields": [
|
"fields": [
|
||||||
"roles",
|
"roles",
|
||||||
"pairing_tx"
|
"pairing_tx"
|
||||||
],
|
],
|
||||||
"min_sig_member": 0.0
|
"min_sig_member": 0.0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"pairing_tx": "",
|
|
||||||
},
|
},
|
||||||
"commited_in": OutPoint::null()
|
"pairing_tx": OutPoint::null(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_process = create_process_from_template(pairing_template.to_string()).unwrap();
|
debug!("Alice pairs her device");
|
||||||
|
// we can update our local device now, first with an empty txid
|
||||||
|
pair_device(OutPoint::null().to_string(), vec![helper_get_bob_address()]).unwrap();
|
||||||
|
|
||||||
// we can update our local device now
|
debug!("Alice sends a transaction commiting to an update prd to Bob");
|
||||||
pair_device(new_process.uuid.clone(), vec![helper_get_bob_address()]).unwrap();
|
let alice_pairing_return =
|
||||||
|
create_update_transaction(None, pairing_init_state.to_string(), 1).unwrap();
|
||||||
|
|
||||||
debug!("Alice sends a transaction commiting to an init prd to Bob");
|
// debug!("{:?}", alice_pairing_return);
|
||||||
let alice_pairing_return = create_process_init_transaction(new_process, 1).unwrap();
|
|
||||||
|
|
||||||
|
// todo must take all the necessary validation before commiting
|
||||||
|
|
||||||
|
// debug!("Alice prepares the commit message for the relay");
|
||||||
|
// let (outpoint, process) = alice_pairing_return.updated_process.unwrap();
|
||||||
|
// let to_commit = process.get_status_at(0).unwrap().encrypted_pcd;
|
||||||
|
// let init_return = create_commit_message(serde_json::to_string(&to_commit).unwrap(), RELAY_ADDRESS.to_owned(), None, 1).unwrap();
|
||||||
|
|
||||||
|
// todo send the commit message to the relay
|
||||||
|
|
||||||
|
let pairing_tx = alice_pairing_return.new_tx_to_send.unwrap();
|
||||||
|
|
||||||
|
// We can update the local device with the actual pairing outpoint
|
||||||
|
pair_device(
|
||||||
|
OutPoint::new(pairing_tx.txid(), 0).to_string(),
|
||||||
|
vec![helper_get_bob_address()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// This is only for testing, the relay takes care of that in prod
|
||||||
let get_outputs_result = get_outputs().unwrap();
|
let get_outputs_result = get_outputs().unwrap();
|
||||||
|
|
||||||
let alice_outputs: HashMap<OutPoint, OwnedOutput> = get_outputs_result.into_serde().unwrap();
|
let alice_outputs: HashMap<OutPoint, OwnedOutput> = get_outputs_result.into_serde().unwrap();
|
||||||
|
|
||||||
let alice_pairing_tweak_data =
|
let alice_pairing_tweak_data = helper_get_tweak_data(&pairing_tx, alice_outputs);
|
||||||
helper_get_tweak_data(&alice_pairing_return.transaction, alice_outputs);
|
|
||||||
|
|
||||||
// Alice parse her own transaction
|
// End of the test only part
|
||||||
helper_parse_transaction(&alice_pairing_return.transaction, &alice_pairing_tweak_data).id;
|
|
||||||
|
// Alice parses her own transaction
|
||||||
|
helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data);
|
||||||
|
|
||||||
// Notify user that we're waiting for confirmation from the other device
|
// Notify user that we're waiting for confirmation from the other device
|
||||||
|
|
||||||
|
let alice_device = dump_device().unwrap();
|
||||||
|
let alice_processes = dump_process_cache().unwrap();
|
||||||
|
debug!("Alice processes: {:#?}", alice_processes);
|
||||||
|
|
||||||
// ======================= 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();
|
||||||
|
|
||||||
// Bob receives Alice pairing transaction
|
// Bob receives Alice pairing transaction
|
||||||
debug!("Bob parses Alice pairing transaction");
|
debug!("Bob parses Alice pairing transaction");
|
||||||
helper_parse_transaction(&alice_pairing_return.transaction, &alice_pairing_tweak_data);
|
helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data);
|
||||||
|
|
||||||
debug!("Bob receives the prd");
|
debug!("Bob receives the prd");
|
||||||
let mut bob_retrieved_prd = CachedMessage::default();
|
let mut bob_retrieved_prd: ApiReturn = ApiReturn::default();
|
||||||
for message in alice_pairing_return.new_messages.iter() {
|
for cipher in alice_pairing_return.ciphers_to_send.iter() {
|
||||||
for cipher in message.cipher.iter() {
|
// debug!("Parsing cipher: {:#?}", cipher);
|
||||||
match helper_parse_cipher(cipher.clone()) {
|
match parse_cipher(cipher.clone()) {
|
||||||
Ok(res) => bob_retrieved_prd = res,
|
Ok(res) => bob_retrieved_prd = res,
|
||||||
Err(_) => continue
|
Err(e) => {
|
||||||
|
debug!("Error parsing cipher: {:#?}", e);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bob_retrieved_prd == CachedMessage::default() {
|
assert!(bob_retrieved_prd != ApiReturn::default());
|
||||||
panic!("Bob failed to retrieve Alice message");
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Bob receives the pcd");
|
debug!("Bob retrieved prd: {:#?}", bob_retrieved_prd);
|
||||||
let mut bob_retrieved_pcd = CachedMessage::default();
|
|
||||||
for message in alice_pairing_return.new_messages {
|
|
||||||
for cipher in message.cipher {
|
|
||||||
match helper_parse_cipher(cipher) {
|
|
||||||
Ok(res) => bob_retrieved_pcd = res,
|
|
||||||
Err(_) => continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bob_retrieved_pcd == CachedMessage::default() {
|
debug!("Bob sends a Confirm Prd to Alice");
|
||||||
panic!("Bob failed to retrieve Alice message");
|
|
||||||
}
|
let bob_device = dump_device().unwrap();
|
||||||
|
let bob_processes = dump_process_cache().unwrap();
|
||||||
|
|
||||||
|
// ======================= Alice
|
||||||
|
reset_device().unwrap();
|
||||||
|
restore_device(alice_device).unwrap();
|
||||||
|
set_process_cache(alice_processes).unwrap();
|
||||||
|
|
||||||
|
debug!("Alice receives the Confirm Prd");
|
||||||
|
let alice_parsed_prd = parse_cipher(bob_retrieved_prd.ciphers_to_send[0].clone()).unwrap();
|
||||||
|
|
||||||
|
debug!("Alice parsed Bob's Confirm Prd: {:#?}", alice_parsed_prd);
|
||||||
|
|
||||||
|
// ======================= Bob
|
||||||
|
reset_device().unwrap();
|
||||||
|
restore_device(bob_device).unwrap();
|
||||||
|
set_process_cache(bob_processes).unwrap();
|
||||||
|
|
||||||
|
let bob_parsed_pcd_return = parse_cipher(alice_parsed_prd.ciphers_to_send[0].clone()).unwrap();
|
||||||
|
|
||||||
|
debug!("bob_parsed_pcd: {:#?}", bob_parsed_pcd_return);
|
||||||
|
|
||||||
// At this point, user must validate the pairing proposal received from Alice
|
// At this point, user must validate the pairing proposal received from Alice
|
||||||
|
// We decrypt the content of the pcd so that we can display to user what matters
|
||||||
|
let alice_proposal =
|
||||||
|
get_update_proposals(bob_parsed_pcd_return.updated_process.unwrap().0).unwrap();
|
||||||
|
|
||||||
debug!("Bob pairs device with Alice");
|
debug!("Alice proposal: {:#?}", alice_proposal);
|
||||||
let process = lock_processes().unwrap();
|
|
||||||
let prd: Prd = serde_json::from_str(&bob_retrieved_prd.prd.unwrap()).unwrap();
|
// get the pairing tx from the proposal
|
||||||
let relevant_process = process.get(&Uuid::parse_str(&prd.process_uuid).unwrap()).unwrap();
|
let proposal = Value::from_str(&alice_proposal.get(0).unwrap()).unwrap();
|
||||||
// decrypt the pcd and update bob device
|
let pairing_tx = proposal.get("pairing_tx").unwrap().as_str().unwrap();
|
||||||
if let Some(initial_state) = relevant_process.get_status_at(0) {
|
let roles: RoleDefinition =
|
||||||
let keys = initial_state.keys;
|
serde_json::from_str(proposal.get("roles").unwrap().as_str().unwrap()).unwrap();
|
||||||
let mut pcd = initial_state.encrypted_pcd;
|
|
||||||
pcd.decrypt_fields(&keys).unwrap();
|
// we check that the proposal contains only one member
|
||||||
pair_device(relevant_process.get_process().uuid, vec![device_address]).unwrap();
|
assert!(roles.members.len() == 1);
|
||||||
}
|
|
||||||
|
// we get all the addresses of the members of the proposal
|
||||||
|
let proposal_members = roles
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.flat_map(|member| member.get_addresses())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// we can automatically check that a pairing member contains local device address + the one that sent the proposal
|
||||||
|
assert!(proposal_members.contains(&device_address));
|
||||||
|
assert!(proposal_members.contains(&paired_device));
|
||||||
|
assert!(proposal_members.len() == 2); // no free riders
|
||||||
|
|
||||||
|
// we can now show all the addresses + pairing tx to the user on device to prompt confirmation
|
||||||
|
|
||||||
|
pair_device(pairing_tx.to_owned(), proposal_members).unwrap();
|
||||||
|
|
||||||
|
// Bob signs the proposal and send a prd response to Alice
|
||||||
|
|
||||||
|
// debug!("Bob pairs device with Alice");
|
||||||
|
// let process = lock_processes().unwrap();
|
||||||
|
// let prd: Prd = serde_json::from_str(&bob_retrieved_prd.prd.unwrap()).unwrap();
|
||||||
|
// let relevant_process = process.get(&Uuid::parse_str(&prd.process_uuid).unwrap()).unwrap();
|
||||||
|
// // decrypt the pcd and update bob device
|
||||||
|
// let pairing_tx: Txid;
|
||||||
|
// if let Some(initial_state) = relevant_process.get_status_at(0) {
|
||||||
|
// let keys = initial_state.keys;
|
||||||
|
// let mut pcd = initial_state.encrypted_pcd;
|
||||||
|
// pcd.decrypt_fields(&keys).unwrap();
|
||||||
|
// debug!("decrypted pcd: {:?}", pcd);
|
||||||
|
// pairing_tx = Txid::from_str(pcd.get("pairing_tx").unwrap().as_str().unwrap()).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();
|
// login();
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user