diff --git a/src/api.rs b/src/api.rs index 257ff83..6f5ec06 100644 --- a/src/api.rs +++ b/src/api.rs @@ -23,6 +23,7 @@ use sdk_common::sp_client::bitcoin::hex::{ }; use sdk_common::sp_client::bitcoin::key::{Parity, Secp256k1}; use sdk_common::sp_client::bitcoin::network::ParseNetworkError; +use sdk_common::sp_client::bitcoin::p2p::message::NetworkMessage; use sdk_common::sp_client::bitcoin::psbt::raw; use sdk_common::sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; use sdk_common::sp_client::bitcoin::secp256k1::{PublicKey, Scalar, SecretKey}; @@ -46,8 +47,7 @@ use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; use sdk_common::network::{ - self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, CipherMessage, FaucetMessage, - NewTxMessage, + self, AnkFlag, CachedMessage, CachedMessageStatus, Envelope, FaucetMessage, NewTxMessage, Pcd, Prd }; use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address}; @@ -570,7 +570,7 @@ fn handle_transaction( }) { let (outpoint, output) = utxo_created.into_iter().next().unwrap(); - let cipher_msg: CipherMessage = serde_json::from_slice(&plaintext)?; + let cipher_msg: Prd = serde_json::from_slice(&plaintext)?; message.commited_in = Some(outpoint.clone()); // freeze the commitment utxo let mut freezed_utxos = lock_freezed_utxos()?; @@ -578,8 +578,6 @@ fn handle_transaction( message.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); message.commitment = Some(commitment_str); - message.plaintext.push(cipher_msg.message); - message.ciphertext = None; message.sender = Some(cipher_msg.sender); message.recipient = Some(sp_wallet.get_client().get_receiving_address()); message.status = CachedMessageStatus::ReceivedMustConfirm; @@ -600,7 +598,7 @@ fn handle_transaction( new_msg.commitment = Some(commitment.to_lower_hex_string()); new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address()); new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); - new_msg.status = CachedMessageStatus::TxWaitingCipher; + new_msg.status = CachedMessageStatus::TxWaitingPrd; messages.push(new_msg.clone()); return Ok(new_msg.clone()); } @@ -711,46 +709,62 @@ pub fn parse_cipher(cipher_msg: String, fee_rate: u32) -> ApiResult { - m.try_decrypt_cipher(cipher.clone()).is_ok() + CachedMessageStatus::TxWaitingPrd => { + m.try_decrypt_prd(cipher.clone()).is_ok() + } + CachedMessageStatus::GotPrdWaitingPcd => { + m.try_decrypt_pcd(cipher.clone()).is_ok() } _ => return false, }) { - let plain = message.try_decrypt_cipher(cipher).unwrap(); - // debug!("Found message {}", String::from_utf8(plain.clone())?); - if message.status == CachedMessageStatus::TxWaitingCipher { - let cipher_msg: CipherMessage = serde_json::from_slice(&plain)?; + if message.status == CachedMessageStatus::TxWaitingPrd { + let plain = message.try_decrypt_prd(cipher).unwrap(); + debug!("Found message {}", String::from_utf8(plain.clone())?); + let prd: Prd = serde_json::from_slice(&plain)?; // does the retrieved message match with the commited hash? - let hash = create_commitment(serde_json::to_string(&cipher_msg)?); + let hash = create_commitment(serde_json::to_string(&prd)?); if Some(hash) != message.commitment { return Err(ApiError { - message: "Message doesn't match commitment".to_owned(), + message: "Prd doesn't match commitment".to_owned(), }); } - message.sender = Some(cipher_msg.sender); - message.ciphertext = None; - if cipher_msg.message.starts_with("PAIRING") { + message.sender = Some(prd.sender.clone()); + message.pcd_commitment = Some(prd.pcd_commitment.to_string()); + message.prd_cipher = None; + message.prd = Some(prd); + message.status = CachedMessageStatus::GotPrdWaitingPcd; + } else { + let plain = message.try_decrypt_pcd(cipher).unwrap(); + debug!("Found message {}", String::from_utf8(plain.clone())?); + // we're receiving a pcd for a prd we already have + let pcd: Pcd = serde_json::from_slice(&plain)?; + // check that the hash of the pcd is the same than commited in the prd + let pcd_commitment = create_commitment(pcd.to_string()); + if Some(&pcd_commitment) != message.pcd_commitment.as_ref() { + return Err(ApiError { + message: format!("Pcd doesn't match commitment: expected {:?}\ngot {}", message.pcd_commitment.as_ref(), pcd_commitment), + }); + } + + if pcd.message.starts_with("PAIRING") { // we don't follow the classic confirmation pattern // if we agree, we must send another notification to remote // the notification output here will be spent as our first session message.status = CachedMessageStatus::Pairing; - } else if cipher_msg.message.starts_with("LOGIN") { + } else if pcd.message.starts_with("LOGIN") { message.status = CachedMessageStatus::Login; } else { message.status = CachedMessageStatus::ReceivedMustConfirm; } - message.plaintext.push(cipher_msg.message); - } else { - // We're receiving a message for some action already engaged - // Let's update the message by pushing what we just found out - message.plaintext.push(String::from_utf8(plain)?); + message.pcd = Some(pcd); + message.pcd_cipher = None; } return Ok(message.clone()); } 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.ciphertext = Some(cipher_msg); + new_msg.prd_cipher = Some(cipher_msg); messages.push(new_msg.clone()); return Ok(new_msg); } @@ -851,7 +865,7 @@ pub fn create_confirmation_transaction( let mut messages = lock_messages()?; let message: &mut CachedMessage; if let Some(m) = messages.iter_mut().find(|m| m.id == message_id) { - if m.sender.is_none() || m.commited_in.is_none() || m.plaintext.is_empty() { + if m.sender.is_none() || m.commited_in.is_none() || m.prd.is_none() { return Err(ApiError { message: "Invalid network message".to_owned(), }); @@ -938,19 +952,25 @@ pub fn create_login_transaction(fee_rate: u32) -> ApiResult ApiResult ApiResult ApiResult ApiResult { let sp_address: SilentPaymentAddress = address.as_str().try_into()?; @@ -1066,7 +1088,19 @@ pub fn create_notification_transaction( nb_outputs: 1, }; - let commitment = create_commitment(serde_json::to_string(&cipher_message)?); + let pcd_commitment = sha256sum(pcd.to_string().as_bytes()); + + let key: [u8; 32] = Aes256Gcm::generate_key(thread_rng()).into(); + + let prd = Prd::new( + sp_wallet.get_client().get_receiving_address().try_into().unwrap(), + key, + pcd_commitment + ); + + let prd_commitment = sha256sum(prd.to_string().as_bytes()); + + let pcd_cipher = prd.encrypt_pcd(&pcd)?; let freezed_utxos = lock_freezed_utxos()?; @@ -1075,7 +1109,7 @@ pub fn create_notification_transaction( &freezed_utxos, sp_wallet, vec![recipient], - Some(Vec::from_hex(&commitment)?), + Some(prd_commitment.as_byte_array().to_vec()), Amount::from_sat(fee_rate.into()), None, )?; @@ -1092,7 +1126,7 @@ pub fn create_notification_transaction( let shared_secret = AnkSharedSecret::new(shared_point); let cipher = encrypt_with_key( - serde_json::to_string(&cipher_message)?, + serde_json::to_string(&prd)?, shared_secret.to_byte_array().to_lower_hex_string(), )?; @@ -1105,16 +1139,19 @@ pub fn create_notification_transaction( // for now let's just take the smallest vout that belongs to the recipient let final_tx = signed_psbt.extract_tx()?; let mut new_msg = CachedMessage::new(); - new_msg.plaintext.push(cipher_message.message); - new_msg.ciphertext = Some(cipher); - new_msg.commitment = Some(commitment); + new_msg.sender = Some(prd.sender.clone()); + new_msg.prd = Some(prd); + new_msg.prd_cipher = Some(cipher); + new_msg.pcd = Some(pcd); + new_msg.pcd_cipher = Some(pcd_cipher.to_lower_hex_string()); + + new_msg.commitment = Some(prd_commitment.as_byte_array().to_lower_hex_string()); new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32, }); new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); new_msg.recipient = Some(address); - new_msg.sender = Some(cipher_message.sender); new_msg.tied_by = Some(1); // for now we just assume that's the second utxo, first being the notification itself new_msg.status = CachedMessageStatus::SentWaitingConfirmation; lock_messages()?.push(new_msg.clone()); @@ -1209,15 +1246,19 @@ pub fn create_faucet_msg() -> ApiResult { cached_msg.status = CachedMessageStatus::FaucetWaiting; lock_messages()?.push(cached_msg.clone()); - let network_msg = AnkNetworkMsg::new(AnkFlag::Faucet, &faucet_msg.to_string()); + let network_msg = Envelope::new(AnkFlag::Faucet, &faucet_msg.to_string()); Ok(network_msg.to_string()) } +fn sha256sum(input: &[u8]) -> sha256::Hash { + let mut engine = sha256::HashEngine::default(); + engine.write_all(&input); + sha256::Hash::from_engine(engine) +} + #[wasm_bindgen] pub fn create_commitment(payload_to_hash: String) -> String { - let mut engine = sha256::HashEngine::default(); - engine.write_all(&payload_to_hash.as_bytes()); - let hash = sha256::Hash::from_engine(engine); + let hash = sha256sum(payload_to_hash.as_bytes()); hash.to_byte_array().to_lower_hex_string() }