Add response logic
This commit is contained in:
parent
b904f4ec81
commit
56558703c3
92
src/api.rs
92
src/api.rs
@ -24,9 +24,10 @@ use sdk_common::crypto::{
|
||||
KeyInit, Purpose, AAD,
|
||||
};
|
||||
use sdk_common::process::Process;
|
||||
use sdk_common::signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof};
|
||||
use sdk_common::sp_client::bitcoin::blockdata::fee_rate;
|
||||
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
|
||||
use sdk_common::sp_client::bitcoin::hashes::{sha256, Hash};
|
||||
use sdk_common::sp_client::bitcoin::hashes::{sha256, sha256t, Hash};
|
||||
use sdk_common::sp_client::bitcoin::hashes::{FromSliceError, HashEngine};
|
||||
use sdk_common::sp_client::bitcoin::hex::{
|
||||
self, parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError,
|
||||
@ -63,7 +64,9 @@ use sdk_common::network::{
|
||||
self, AnkFlag, CachedMessage, CachedMessageStatus, CommitMessage, Envelope, FaucetMessage,
|
||||
NewTxMessage,
|
||||
};
|
||||
use sdk_common::pcd::{compare_maps, AnkPcdHash, Member, Pcd, RoleDefinition, ValidationRule};
|
||||
use sdk_common::pcd::{
|
||||
compare_maps, AnkPcdHash, AnkPcdTag, 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::{
|
||||
@ -602,6 +605,56 @@ fn try_decrypt_with_processes(
|
||||
None
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn response_prd(
|
||||
root_commitment: String,
|
||||
prd: String, // The Prd we respond to
|
||||
approval: bool,
|
||||
) -> ApiResult<ApiReturn> {
|
||||
let local_device = lock_local_device()?;
|
||||
let member = local_device
|
||||
.to_member()
|
||||
.ok_or(ApiError::new("Unpaired device".to_owned()))?;
|
||||
|
||||
let prd_to_respond = serde_json::from_str::<Prd>(&prd)?;
|
||||
|
||||
// Probably we should answer differently depending on the type of prd
|
||||
let message_hash = match prd_to_respond.prd_type {
|
||||
PrdType::Update => {
|
||||
let pcd_hash: AnkPcdHash =
|
||||
AnkPcdHash::from_value(&Value::from_str(&prd_to_respond.payload)?);
|
||||
|
||||
let message_hash = if approval {
|
||||
AnkHash::ValidationYes(AnkValidationYesHash::from_commitment(pcd_hash))
|
||||
} else {
|
||||
AnkHash::ValidationNo(AnkValidationNoHash::from_commitment(pcd_hash))
|
||||
};
|
||||
|
||||
let proof = Proof::new(
|
||||
message_hash,
|
||||
local_device
|
||||
.get_wallet()
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()?,
|
||||
);
|
||||
|
||||
let prd = Prd::new_response(
|
||||
OutPoint::from_str(&root_commitment)?,
|
||||
serde_json::to_string(&member)?,
|
||||
proof,
|
||||
pcd_hash,
|
||||
);
|
||||
|
||||
return Ok(ApiReturn {
|
||||
ciphers_to_send: vec![prd.to_network_msg(local_device.get_wallet())?.to_string()],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
}
|
||||
|
||||
fn confirm_prd(prd: Prd, shared_secret: &str) -> AnyhowResult<String> {
|
||||
match prd.prd_type {
|
||||
PrdType::Confirm | PrdType::Response | PrdType::List => {
|
||||
@ -631,20 +684,18 @@ fn confirm_prd(prd: Prd, shared_secret: &str) -> AnyhowResult<String> {
|
||||
}
|
||||
};
|
||||
|
||||
let payload = Value::from_str(&prd.payload)?;
|
||||
let pcd_commitment = AnkPcdHash::from_str(&prd.payload)?;
|
||||
|
||||
let prd_confirm = Prd::new_confirm(outpoint, member, payload.tagged_hash());
|
||||
let prd_confirm = Prd::new_confirm(outpoint, member, pcd_commitment);
|
||||
|
||||
let prd_msg = prd_confirm.to_network_msg(local_device.get_wallet())?;
|
||||
|
||||
debug!("encrypting with key {}", shared_secret);
|
||||
Ok(encrypt_with_key(prd_msg, shared_secret.to_owned()).unwrap())
|
||||
}
|
||||
|
||||
fn send_data(prd: &Prd, shared_secret: &str) -> AnyhowResult<ApiReturn> {
|
||||
let pcd = &prd.payload;
|
||||
|
||||
debug!("encrypting with key: {:#?}", shared_secret);
|
||||
let cipher = encrypt_with_key(pcd.clone(), shared_secret.to_owned()).unwrap();
|
||||
|
||||
Ok(ApiReturn {
|
||||
@ -661,7 +712,6 @@ fn decrypt_with_cached_messages(
|
||||
let nonce = Nonce::from_slice(&cipher[..12]);
|
||||
|
||||
for message in messages.iter_mut() {
|
||||
debug!("Attempting decryption with cached message {:#?}", message);
|
||||
for shared_secret in message.shared_secrets.iter() {
|
||||
let aes_key = match AnkSharedSecretHash::from_str(shared_secret) {
|
||||
Ok(key) => key,
|
||||
@ -837,6 +887,24 @@ fn handle_prd(
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
PrdType::Response => {
|
||||
// We must know of a prd update that the response answers to
|
||||
let original_request = relevant_process
|
||||
.impending_requests
|
||||
.iter()
|
||||
.find(|r| {
|
||||
if r.prd_type != PrdType::Update {
|
||||
return false;
|
||||
}
|
||||
let hash = Value::from_str(&r.payload).unwrap().tagged_hash();
|
||||
hash.to_string() == prd.payload
|
||||
})
|
||||
.ok_or(anyhow::Error::msg("Original request not found"))?;
|
||||
|
||||
return Ok(ApiReturn {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
@ -844,7 +912,6 @@ fn handle_prd(
|
||||
fn handle_pcd(plain: Vec<u8>, root_commitment: OutPoint) -> AnyhowResult<ApiReturn> {
|
||||
let pcd = Value::from_str(&String::from_utf8(plain)?)?;
|
||||
|
||||
// debug!("Found pcd: {:#?}", pcd);
|
||||
let pcd_commitment = pcd.tagged_hash();
|
||||
|
||||
let mut processes = lock_processes()?;
|
||||
@ -854,7 +921,7 @@ fn handle_pcd(plain: Vec<u8>, root_commitment: OutPoint) -> AnyhowResult<ApiRetu
|
||||
let prd = relevant_process
|
||||
.impending_requests
|
||||
.iter_mut()
|
||||
.find(|r| Value::from_str(&r.payload).unwrap().tagged_hash() == pcd_commitment)
|
||||
.find(|r| *r.payload == pcd_commitment.to_string())
|
||||
.ok_or(AnyhowError::msg("Failed to retrieve the matching prd"))?;
|
||||
|
||||
// We update the process and return it
|
||||
@ -1242,6 +1309,11 @@ pub fn create_update_transaction(
|
||||
for (member, visible_fields) in all_members {
|
||||
let mut prd = full_prd.clone();
|
||||
prd.filter_keys(visible_fields);
|
||||
// we hash the payload
|
||||
prd.payload = Value::from_str(&prd.payload)
|
||||
.unwrap()
|
||||
.tagged_hash()
|
||||
.to_string();
|
||||
let prd_msg = prd.to_network_msg(sp_wallet)?;
|
||||
|
||||
let addresses = member.get_addresses();
|
||||
@ -1269,8 +1341,6 @@ pub fn create_update_transaction(
|
||||
validation_token: vec![],
|
||||
});
|
||||
|
||||
debug!("updated_process: {:#?}", relevant_process);
|
||||
|
||||
Ok(ApiReturn {
|
||||
new_tx_to_send: Some(final_tx),
|
||||
updated_process: Some((commitment_outpoint.to_string(), relevant_process.clone())),
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -3,8 +3,9 @@ use anyhow::Error;
|
||||
use sdk_common::crypto::AnkSharedSecret;
|
||||
use sdk_common::network::CachedMessage;
|
||||
use sdk_common::pcd::AnkPcdHash;
|
||||
use sdk_common::prd::{Prd, ValidationToken};
|
||||
use sdk_common::prd::Prd;
|
||||
use sdk_common::process::Process;
|
||||
use sdk_common::signature::Proof;
|
||||
use sdk_common::sp_client::bitcoin::OutPoint;
|
||||
use sdk_common::uuid::Uuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -28,12 +29,13 @@ pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error>
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
||||
// TODO move to sdk-common
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProcessState {
|
||||
pub commited_in: OutPoint,
|
||||
pub encrypted_pcd: Value,
|
||||
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<Proof>, // This signs the encrypted pcd
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@ -51,6 +53,10 @@ impl RelevantProcess {
|
||||
pub fn get_latest_state(&self) -> Option<ProcessState> {
|
||||
self.states.last().cloned()
|
||||
}
|
||||
|
||||
pub fn get_impending_requests(&self) -> Vec<Prd> {
|
||||
self.impending_requests.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub static CACHEDPROCESSES: OnceLock<Mutex<HashMap<OutPoint, RelevantProcess>>> = OnceLock::new();
|
||||
|
151
tests/pairing.rs
151
tests/pairing.rs
@ -4,10 +4,10 @@ use std::str::FromStr;
|
||||
use sdk_client::api::{
|
||||
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,
|
||||
response_prd, restore_device, set_process_cache, setup, ApiReturn,
|
||||
};
|
||||
use sdk_common::log::debug;
|
||||
use sdk_common::pcd::{Member, RoleDefinition};
|
||||
use sdk_common::pcd::{Member, Pcd, RoleDefinition};
|
||||
use sdk_common::sp_client::bitcoin::OutPoint;
|
||||
use sdk_common::sp_client::spclient::OwnedOutput;
|
||||
use serde_json::{json, Value};
|
||||
@ -31,15 +31,15 @@ fn test_pairing() {
|
||||
create_device_from_sp_wallet(ALICE_LOGIN_WALLET.to_owned()).unwrap();
|
||||
|
||||
// we get our own address
|
||||
let device_address = get_address().unwrap();
|
||||
let alice_address = get_address().unwrap();
|
||||
|
||||
// we scan the qr code or get the address by any other means
|
||||
let paired_device = helper_get_bob_address();
|
||||
let bob_address = helper_get_bob_address();
|
||||
|
||||
// Alice creates the new member with Bob address
|
||||
let new_member = Member::new(vec![
|
||||
device_address.as_str().try_into().unwrap(),
|
||||
paired_device.as_str().try_into().unwrap(),
|
||||
alice_address.as_str().try_into().unwrap(),
|
||||
bob_address.as_str().try_into().unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
@ -47,6 +47,7 @@ fn test_pairing() {
|
||||
"html": "",
|
||||
"style": "",
|
||||
"script": "",
|
||||
"description": "AliceBob",
|
||||
"roles": {
|
||||
"owner": {
|
||||
"members":
|
||||
@ -77,26 +78,19 @@ fn test_pairing() {
|
||||
let alice_pairing_return =
|
||||
create_update_transaction(None, pairing_init_state.to_string(), 1).unwrap();
|
||||
|
||||
// debug!("{:?}", alice_pairing_return);
|
||||
|
||||
// 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 (root_outpoint, alice_init_process) = alice_pairing_return.updated_process.unwrap();
|
||||
let alice_pcd_commitment = Value::from_str(
|
||||
&alice_init_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.payload,
|
||||
)
|
||||
.unwrap()
|
||||
.tagged_hash();
|
||||
|
||||
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();
|
||||
|
||||
@ -110,10 +104,35 @@ fn test_pairing() {
|
||||
helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data);
|
||||
|
||||
// Notify user that we're waiting for confirmation from the other device
|
||||
// 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();
|
||||
|
||||
// TODO unpair device
|
||||
|
||||
// We can produce the prd response now even if we can't use it yet
|
||||
// TODO pass the process as argument
|
||||
let alice_prd_response = response_prd(
|
||||
root_outpoint,
|
||||
alice_init_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
.ciphers_to_send
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
// this is only for testing, as we're playing both parts
|
||||
let alice_device = dump_device().unwrap();
|
||||
let alice_processes = dump_process_cache().unwrap();
|
||||
debug!("Alice processes: {:#?}", alice_processes);
|
||||
|
||||
// ======================= Bob
|
||||
reset_device().unwrap();
|
||||
@ -136,12 +155,23 @@ fn test_pairing() {
|
||||
}
|
||||
}
|
||||
|
||||
assert!(bob_retrieved_prd != ApiReturn::default());
|
||||
assert!(bob_retrieved_prd.ciphers_to_send.len() == 1);
|
||||
assert!(bob_retrieved_prd.updated_process.is_some());
|
||||
|
||||
debug!("Bob retrieved prd: {:#?}", bob_retrieved_prd);
|
||||
|
||||
let (root_commitment, relevant_process) = bob_retrieved_prd.updated_process.unwrap();
|
||||
let pcd_commitment = relevant_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.payload
|
||||
.clone();
|
||||
let prd_confirm_cipher = bob_retrieved_prd.ciphers_to_send.iter().next().unwrap();
|
||||
|
||||
debug!("Bob sends a Confirm Prd to Alice");
|
||||
|
||||
// this is only for testing, as we're playing both parts
|
||||
let bob_device = dump_device().unwrap();
|
||||
let bob_processes = dump_process_cache().unwrap();
|
||||
|
||||
@ -151,7 +181,7 @@ fn test_pairing() {
|
||||
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();
|
||||
let alice_parsed_prd = parse_cipher(prd_confirm_cipher.clone()).unwrap();
|
||||
|
||||
debug!("Alice parsed Bob's Confirm Prd: {:#?}", alice_parsed_prd);
|
||||
|
||||
@ -160,6 +190,7 @@ fn test_pairing() {
|
||||
restore_device(bob_device).unwrap();
|
||||
set_process_cache(bob_processes).unwrap();
|
||||
|
||||
debug!("Bob parses Alice's pcd");
|
||||
let bob_parsed_pcd_return = parse_cipher(alice_parsed_prd.ciphers_to_send[0].clone()).unwrap();
|
||||
|
||||
debug!("bob_parsed_pcd: {:#?}", bob_parsed_pcd_return);
|
||||
@ -173,47 +204,65 @@ fn test_pairing() {
|
||||
|
||||
// get the pairing tx from the proposal
|
||||
let proposal = Value::from_str(&alice_proposal.get(0).unwrap()).unwrap();
|
||||
let pairing_tx = proposal.get("pairing_tx").unwrap().as_str().unwrap();
|
||||
let roles: RoleDefinition =
|
||||
serde_json::from_str(proposal.get("roles").unwrap().as_str().unwrap()).unwrap();
|
||||
debug!("proposal: {:#?}", proposal);
|
||||
let pairing_tx = proposal
|
||||
.get("pairing_tx")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.trim_matches('"');
|
||||
|
||||
let roles = proposal
|
||||
.get("roles")
|
||||
.and_then(|v| Value::from_str(v.as_str().unwrap()).ok())
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(role_name, role_value)| {
|
||||
let role_def: RoleDefinition = serde_json::from_value(role_value.clone())?;
|
||||
Ok((role_name.clone(), role_def))
|
||||
})
|
||||
.collect::<Result<HashMap<String, RoleDefinition>, anyhow::Error>>();
|
||||
|
||||
let roles = roles.unwrap();
|
||||
|
||||
// we check that the proposal contains only one member
|
||||
assert!(roles.members.len() == 1);
|
||||
assert!(roles.len() == 1);
|
||||
assert!(roles["owner"].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())
|
||||
.flat_map(|(_, members)| members.members.iter().flat_map(|m| m.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.contains(&alice_address));
|
||||
assert!(proposal_members.contains(&bob_address));
|
||||
assert!(proposal_members.len() == 2); // no free riders
|
||||
|
||||
// We remove the local address, but maybe that's the responsibility of the Member type
|
||||
let proposal_members = proposal_members
|
||||
.into_iter()
|
||||
.filter(|m| m != &bob_address)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
debug!("proposal_members: {:?}", proposal_members);
|
||||
|
||||
// we can now show all the addresses + pairing tx to the user on device to prompt confirmation
|
||||
|
||||
debug!("Bob pairs device with Alice");
|
||||
pair_device(pairing_tx.to_owned(), proposal_members).unwrap();
|
||||
|
||||
// Bob signs the proposal and send a prd response to Alice
|
||||
// Bob signs the proposal and sends a prd response too
|
||||
let bob_prd_response = response_prd(root_commitment, pcd_commitment, true)
|
||||
.unwrap()
|
||||
.ciphers_to_send
|
||||
.get(0)
|
||||
.unwrap();
|
||||
|
||||
// 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 creates a new transaction where they both control one output
|
||||
// login();
|
||||
|
||||
// Once we know this tx id, we can commit to the relay
|
||||
|
Loading…
x
Reference in New Issue
Block a user