diff --git a/src/api.rs b/src/api.rs index c99c91f..0a7f01c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -84,7 +84,7 @@ use crate::{ pub struct ApiReturn { pub updated_cached_msg: Vec, pub updated_process: Option<(String, Process)>, - pub new_tx_to_send: Option, + pub new_tx_to_send: Option, pub ciphers_to_send: Vec, pub commit_to_send: Option, } @@ -261,6 +261,30 @@ pub fn pair_device(commitment_tx: String, mut sp_addresses: Vec) -> ApiR Ok(()) } +#[wasm_bindgen] +pub fn get_pairing_tx() -> ApiResult { + let device = lock_local_device()?; + + if !device.is_linked() { + return Err(ApiError::new("Device is not linked".to_owned())); + } + + let pairing_commitment= device.get_process_commitment().unwrap(); + + let processes = lock_processes()?; + + let process = processes.get(&OutPoint::new(pairing_commitment, 0)).ok_or(ApiError::new("Pairing process not found".to_owned()))?; + + let state = process.get_latest_state().unwrap(); + + let mut decrypted_pcd = Map::new(); + state.encrypted_pcd.decrypt_fields(&state.keys, &mut decrypted_pcd)?; + + let pairing_tx = decrypted_pcd.get("pairing_tx").unwrap().as_str().unwrap(); + + Ok(pairing_tx.to_owned()) +} + #[wasm_bindgen] pub fn unpair_device() -> ApiResult<()> { let mut local_device = lock_local_device()?; @@ -282,8 +306,84 @@ impl outputs_list { } #[wasm_bindgen] -pub fn login() -> ApiResult<()> { - unimplemented!(); +pub fn login(previous_login_tx: String, fee_rate: u32) -> ApiResult { + // We first create a transaction that spends both pairing tx outputs + let previous_tx: Txid = deserialize(&Vec::from_hex(&previous_login_tx)?)?; + + let device = lock_local_device()?; + if !device.is_linked() { + return Err(ApiError::new("Device is not linked".to_owned())); + } + + let member = device.to_member().unwrap(); + let nb_outputs = member.get_addresses().len(); + + let other_addresses = device.get_other_addresses(); + + // We get the pairing process out of cache + let commitment_txid = device.get_process_commitment().unwrap(); + let commitment_outpoint = OutPoint::new(commitment_txid, 0); + + let process = lock_processes()?.get(&commitment_outpoint).unwrap().clone(); + let state = process.get_latest_state().unwrap().clone(); + + let mut shared_secrets = Vec::new(); + for address in other_addresses { + let shared_secret = process.get_shared_secret_for_address(&SilentPaymentAddress::try_from(address)?); + if let Some(shared_secret) = shared_secret { + shared_secrets.push(shared_secret); + } + } + + let mut decrypted_pcd = Map::new(); + state.encrypted_pcd.decrypt_fields(&state.keys, &mut decrypted_pcd)?; + + let pairing_tx = decrypted_pcd.get("pairing_tx").unwrap().as_str().unwrap(); + + let wallet = device.get_wallet(); + + let freezed_utxos = lock_freezed_utxos()?; + + let recipients: Vec = device.to_member().unwrap().get_addresses().iter().map(|a| { + Recipient { + address: a.clone(), + amount: DEFAULT_AMOUNT, + nb_outputs: 1, + } + }).collect(); + + let mut mandatory_inputs = Vec::new(); + for i in 0u32..nb_outputs.try_into().unwrap() { + mandatory_inputs.push(OutPoint::new(previous_tx, i)); + } + + let signed_psbt = create_transaction( + mandatory_inputs, + &freezed_utxos, + wallet, + recipients, + None, + Amount::from_sat(fee_rate.into()), + None, + )?; + + // We send it in a TxProposal prd + let tx_proposal = Prd::new_tx_proposal(commitment_outpoint, member, signed_psbt); + + debug!("tx_proposal: {:?}", tx_proposal); + // We encrypt the prd with the shared_secret for pairing process + let prd_msg = tx_proposal.to_network_msg(wallet)?; + debug!("prd_msg: {:?}", prd_msg); + let mut ciphers = Vec::new(); + for shared_secret in shared_secrets { + let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?; + ciphers.push(cipher.to_lower_hex_string()); + } + // We return the cipher + Ok(ApiReturn { + ciphers_to_send: ciphers, + ..Default::default() + }) } #[wasm_bindgen] @@ -641,7 +741,7 @@ pub fn response_prd( let prd = Prd::new_response( OutPoint::from_str(&root_commitment)?, serde_json::to_string(&member)?, - proof, + vec![proof], pcd_hash, ); @@ -940,118 +1040,6 @@ fn handle_decrypted_message( .map_err(|e| anyhow::Error::msg(format!("Failed to handle decrypted message: {}", e))) } -// fn match_with_active_processes(cipher: &[u8]) -> anyhow::Result { -// // Try decrypting the cipher with available processes and shared secrets -// let (plain, sp_address, process_root_commitment) = -// try_decrypt_with_processes(cipher, lock_processes()?) -// .ok_or_else(|| anyhow::Error::msg("No match in active processes"))?; - -// debug!("matched with an active process"); - -// // Try to handle the decrypted payload as PRD first -// if let Ok(res) = handle_prd(&plain, sp_address, None) { -// return Ok(res); -// } - -// // If not PRD, try to handle as PCD -// if let Ok(res) = handle_pcd(plain, sp_address, process_root_commitment) { -// return Ok(ApiReturn { -// updated_process: Some((process_root_commitment.to_string(), res)), -// ..Default::default() -// }); -// } - -// // If neither PRD nor PCD, return an error -// Err(anyhow::Error::msg("payload can't be parsed")) -// } - -// /// Go through cached messages for tx waiting for a prd -// /// We update the process so that the -// fn match_cipher_to_cached_messages(cipher: &[u8]) -> anyhow::Result { -// // let's try to decrypt with keys we found in transactions but haven't used yet -// let mut messages = lock_messages()?; -// let mut plain = vec![]; -// // debug!("messages: {:?}", messages); -// if let Some(message) = messages.iter_mut().find(|m| match m.status { -// CachedMessageStatus::TxWaitingPrd => { -// if let Ok(m) = m.try_decrypt_message(&cipher) { -// plain = m; -// return true; -// } else { -// return false; -// } -// } -// _ => return false, -// }) { -// // debug!("Found message {}", String::from_utf8(plain.clone())?); -// let commitment = AnkPrdHash::from_str(message.commitment.as_ref().unwrap())?; -// let prd = Prd::extract_from_message_with_commitment(&plain, &commitment)?; - -// let proof_key = prd.proof.unwrap().get_key(); -// let mut actual_sender = String::default(); -// for sp_address in serde_json::from_str::(&prd.sender)?.get_addresses() { -// if proof_key -// == SilentPaymentAddress::try_from(sp_address.as_str()) -// .unwrap() -// .get_spend_key() -// .x_only_public_key() -// .0 -// { -// actual_sender = sp_address; -// break; -// } else { -// continue; -// } -// } - -// let shared_secret = message.shared_secrets.first().unwrap(); -// let root_outpoint = OutPoint::from_str(&prd.root_commitment)?; - -// let mut processes = lock_processes()?; - -// let updated_process: Process; -// if let Some(process) = processes.get_mut(&root_outpoint) { -// // we're actually replacing the shared_secret for that process and that sender if it exists -// process.shared_secrets.insert( -// actual_sender, -// Vec::from_hex(shared_secret)?.to_lower_hex_string(), -// ); -// updated_process = process.clone(); -// } else { -// let mut shared_secrets = HashMap::new(); -// shared_secrets.insert( -// actual_sender, -// Vec::from_hex(shared_secret)?.to_lower_hex_string(), -// ); -// let new_process = Process { -// shared_secrets, -// ..Default::default() -// }; -// processes.insert(root_outpoint, new_process.clone()); -// updated_process = new_process; -// } - -// return Ok(ApiReturn { -// updated_cached_msg: vec![CachedMessage { -// id: message.id, -// ..Default::default() -// }], -// updated_process: Some((root_outpoint.to_string(), updated_process)), -// ..Default::default() -// }); -// } else { -// // let's keep it in case we receive the transaction later -// let mut new_msg = CachedMessage::new(); -// new_msg.status = CachedMessageStatus::CipherWaitingTx; -// new_msg.cipher.push(cipher.to_lower_hex_string()); -// messages.push(new_msg.clone()); -// return Ok(ApiReturn { -// updated_cached_msg: vec![new_msg], -// ..Default::default() -// }); -// } -// } - #[wasm_bindgen] pub fn parse_cipher(cipher_msg: String) -> ApiResult { // Check that the cipher is not empty or too long @@ -1096,59 +1084,82 @@ pub fn get_available_amount() -> ApiResult { } #[wasm_bindgen] -/// This takes some commitment and creates a commit msg so that relay commits it on chain +/// This takes a reference to a process and creates a commit msg for the latest state pub fn create_commit_message( - to_commit: String, + init_commitment_outpoint: String, relay_address: String, - init_commitment_outpoint: Option, // if None, we're initializing a new commitment chain fee_rate: u32, ) -> ApiResult { - let value = Value::from_str(&to_commit)?; - let commitment = value.tagged_hash(); + let outpoint = OutPoint::from_str(&init_commitment_outpoint)?; - // if the transaction exists, we don't need to create a transaction and just put the outpoint in the msg - if let Some(outpoint) = init_commitment_outpoint { - // Todo : if we have an init tx, look into cached processes and confirm that it exists - // We just send the message with the outpoint - return Ok(ApiReturn { - commit_to_send: Some(CommitMessage::new_update_commitment( - OutPoint::from_str(&outpoint)?, - 0, - commitment.to_byte_array(), - )), - ..Default::default() - }); + if let Some(process) = lock_processes()?.get(&outpoint) { + match process.get_number_of_states() { + 0 => Err(ApiError::new("Process has no states".to_owned())), + 1 => { + // This is a creation + let state = process.get_latest_state().unwrap(); + if state.commited_in != OutPoint::null() { + return Err(ApiError::new("Latest state is already commited".to_owned())); + } + let encrypted_pcd = state.encrypted_pcd.clone(); + let keys = state.keys.clone(); + + let freezed_utxos = lock_freezed_utxos()?; + + let local_device = lock_local_device()?; + + let sp_wallet = local_device.get_wallet(); + + let signed_psbt = create_transaction( + vec![], + &freezed_utxos, + sp_wallet, + vec![Recipient { + address: relay_address, + amount: Amount::from_sat(1000), + nb_outputs: 1, + }], + None, + Amount::from_sat(fee_rate.into()), + None, + )?; + + let tx = signed_psbt.extract_tx()?; + + Ok(ApiReturn { + commit_to_send: Some(CommitMessage::new_first_commitment( + tx, + encrypted_pcd.as_object().unwrap().clone(), + keys, + )), + ..Default::default() + }) + } + _ => { + // We're updating an existing process + // Check that initial outpoint is not a placeholder and that latest state has a commited_in of null + if outpoint.vout != u32::MAX { + return Err(ApiError::new("Initial outpoint is a placeholder".to_owned())); + } + let state = process.get_latest_state().unwrap(); + if state.commited_in != OutPoint::null() { + return Err(ApiError::new("Latest state is already commited".to_owned())); + } + let encrypted_pcd = state.encrypted_pcd.clone(); + let keys = state.keys.clone(); + // We just send the message with the outpoint + return Ok(ApiReturn { + commit_to_send: Some(CommitMessage::new_update_commitment( + outpoint, + encrypted_pcd.as_object().unwrap().clone(), + keys, + )), + ..Default::default() + }); + } + } } else { - let freezed_utxos = lock_freezed_utxos()?; - - let local_device = lock_local_device()?; - - let sp_wallet = local_device.get_wallet(); - - let signed_psbt = create_transaction( - &vec![], - &freezed_utxos, - sp_wallet, - vec![Recipient { - address: relay_address, - amount: Amount::from_sat(1000), - nb_outputs: 1, - }], - None, - Amount::from_sat(fee_rate.into()), - None, - )?; - - let tx = signed_psbt.extract_tx()?; - - Ok(ApiReturn { - commit_to_send: Some(CommitMessage::new_first_commitment( - tx, - 0, - commitment.to_byte_array(), - )), - ..Default::default() - }) + return Err(ApiError::new("Process not found".to_owned())); } } @@ -1174,7 +1185,7 @@ pub fn create_update_transaction( if let Some(p) = processes.get_mut(&outpoint) { // compare the provided new_state with the process defined template - let previous_state = &p.get_status_at(0).unwrap().encrypted_pcd; + let previous_state = &p.get_state_at(0).unwrap().encrypted_pcd; if !compare_maps(previous_state.as_object().unwrap(), pcd_map) { return Err(ApiError::new( "Provided updated state is not consistent with the process template".to_owned(), @@ -1250,7 +1261,8 @@ pub fn create_update_transaction( let mut fields2keys = Map::new(); let mut fields2cipher = Map::new(); let encrypted_pcd = pcd.clone(); - encrypted_pcd.encrypt_fields(&mut fields2keys, &mut fields2cipher); + let fields_to_encrypt: Vec = encrypted_pcd.as_object().unwrap().keys().map(|k| k.clone()).collect(); + encrypted_pcd.encrypt_fields(&fields_to_encrypt, &mut fields2keys, &mut fields2cipher); let local_device = lock_local_device()?; @@ -1273,7 +1285,7 @@ pub fn create_update_transaction( let freezed_utxos = lock_freezed_utxos()?; let signed_psbt = create_transaction( - &vec![], + vec![], &freezed_utxos, sp_wallet, recipients, @@ -1320,7 +1332,7 @@ pub fn create_update_transaction( commited_in: OutPoint::null(), encrypted_pcd: Value::Object(fields2cipher), keys: fields2keys, - validation_token: vec![], + validation_tokens: vec![], }); // Create the new_tx message