Add response logic

This commit is contained in:
Sosthene 2024-09-23 12:33:32 +02:00
parent b904f4ec81
commit 56558703c3
3 changed files with 189 additions and 64 deletions

View File

@ -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())),

View File

@ -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();

View File

@ -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