From 27b24ae6d2cdccfbb8b397557e4444acbb9c809e Mon Sep 17 00:00:00 2001 From: DanGould Date: Thu, 31 Aug 2023 16:21:25 -0400 Subject: [PATCH] Secure v2 payloads with authenticated encryption --- Cargo.lock | 83 ++++++++++++++++++++++++++++ payjoin-cli/src/app.rs | 48 ++++++++--------- payjoin/Cargo.toml | 3 +- payjoin/src/receive/mod.rs | 97 ++++++++++++++++++++++++++------- payjoin/src/send/mod.rs | 80 +++++++++++++++++++++------ payjoin/src/v2.rs | 107 +++++++++++++++++++++++++++++++++++++ 6 files changed, 357 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2243a47..c244e7b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.7.6" @@ -470,6 +480,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.31" @@ -488,6 +522,17 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.4.4" @@ -647,6 +692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -1200,6 +1246,15 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -1547,6 +1602,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.57" @@ -1659,6 +1720,7 @@ dependencies = [ "bip21", "bitcoin", "bitcoind", + "chacha20poly1305", "env_logger", "log", "rand", @@ -1834,6 +1896,17 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2987,6 +3060,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" diff --git a/payjoin-cli/src/app.rs b/payjoin-cli/src/app.rs index 7ed42e59..ef243cbd 100644 --- a/payjoin-cli/src/app.rs +++ b/payjoin-cli/src/app.rs @@ -9,10 +9,11 @@ use bitcoincore_rpc::jsonrpc::serde_json; use bitcoincore_rpc::RpcApi; use clap::ArgMatches; use config::{Config, File, FileFormat}; -use payjoin::bitcoin::base64; use payjoin::bitcoin::psbt::Psbt; -use payjoin::bitcoin::{self}; -use payjoin::receive::{Error, ProvisionalProposal, UncheckedProposal}; +use payjoin::bitcoin::{ + base64, {self}, +}; +use payjoin::receive::{Error, PayjoinProposal, ProvisionalProposal, UncheckedProposal}; #[cfg(not(feature = "v2"))] use rouille::{Request, Response}; use serde::{Deserialize, Serialize}; @@ -199,13 +200,9 @@ impl App { #[cfg(feature = "v2")] pub async fn receive_payjoin(self, amount_arg: &str) -> Result<()> { - let secp = bitcoin::secp256k1::Secp256k1::new(); - let mut rng = bitcoin::secp256k1::rand::thread_rng(); - let key = bitcoin::secp256k1::KeyPair::new(&secp, &mut rng); - let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false); - let pubkey_base64 = base64::encode_config(key.public_key().to_string(), b64_config); - - let pj_uri_string = self.construct_payjoin_uri(amount_arg, Some(&pubkey_base64))?; + let context = payjoin::receive::ProposalContext::new(); + let pj_uri_string = + self.construct_payjoin_uri(amount_arg, Some(&context.subdirectory()))?; println!( "Listening at {}. Configured to accept payjoin at BIP 21 Payjoin Uri:", self.config.pj_host @@ -217,20 +214,21 @@ impl App { .build() .with_context(|| "Failed to build reqwest http client")?; log::debug!("Awaiting request"); - let receive_endpoint = - format!("{}/{}/{}", self.config.pj_endpoint, pubkey_base64, payjoin::v2::RECEIVE); - let buffer = Self::long_poll(&client, &receive_endpoint).await?; + let receive_endpoint = format!("{}/{}", self.config.pj_endpoint, context.receive_subdir()); + let mut buffer = Self::long_poll(&client, &receive_endpoint).await?; log::debug!("Received request"); - let proposal = UncheckedProposal::from_streamed(&buffer) + let proposal = context + .parse_proposal(&mut buffer) .map_err(|e| anyhow!("Failed to parse into UncheckedProposal {}", e))?; - let payjoin_psbt = self + let payjoin_proposal = self .process_proposal(proposal) .map_err(|e| anyhow!("Failed to process UncheckedProposal {}", e))?; - let payjoin_psbt_ser = payjoin::bitcoin::base64::encode(&payjoin_psbt.serialize()); + + let body = payjoin_proposal.serialize_body(); let _ = client .post(receive_endpoint) - .body(payjoin_psbt_ser) + .body(body) .send() .await .with_context(|| "HTTP request failed")?; @@ -357,15 +355,15 @@ impl App { headers, )?; - let payjoin_proposal_psbt = self.process_proposal(proposal)?; - log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt); + let payjoin_proposal = self.process_proposal(proposal)?; + + let body = payjoin_proposal.serialize_body().as_str().to_sring(); - let payload = payjoin::bitcoin::base64::encode(&payjoin_proposal_psbt.serialize()); log::info!("successful response"); - Ok(Response::text(payload)) + Ok(Response::text(body)) } - fn process_proposal(&self, proposal: UncheckedProposal) -> Result { + fn process_proposal(&self, proposal: UncheckedProposal) -> Result { // in a payment processor where the sender could go offline, this is where you schedule to broadcast the original_tx let _to_broadcast_in_failure_case = proposal.get_transaction_to_schedule_broadcast(); @@ -437,7 +435,7 @@ impl App { .assume_checked(); provisional_payjoin.substitute_output_address(receiver_substitute_address); - let payjoi_proposal = provisional_payjoin.finalize_proposal( + let payjoin_proposal = provisional_payjoin.finalize_proposal( |psbt: &Psbt| { self.bitcoind .wallet_process_psbt( @@ -451,9 +449,9 @@ impl App { }, Some(bitcoin::FeeRate::MIN), )?; - let payjoin_proposal_psbt = payjoi_proposal.psbt(); + let payjoin_proposal_psbt = payjoin_proposal.psbt(); log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt); - Ok(payjoin_proposal_psbt.clone()) + Ok(payjoin_proposal) } fn insert_input_seen_before(&self, input: bitcoin::OutPoint) -> Result { diff --git a/payjoin/Cargo.toml b/payjoin/Cargo.toml index 4f9d8fa0..3d2ed3e9 100644 --- a/payjoin/Cargo.toml +++ b/payjoin/Cargo.toml @@ -16,11 +16,12 @@ edition = "2018" send = [] receive = ["rand"] base64 = ["bitcoin/base64"] -v2 = ["serde", "serde_json"] +v2 = ["bitcoin/rand-std", "chacha20poly1305", "serde", "serde_json"] [dependencies] bitcoin = { version = "0.30.0", features = ["base64"] } bip21 = "0.3.1" +chacha20poly1305 = { version = "0.10.1", optional = true } log = { version = "0.4.14"} rand = { version = "0.8.4", optional = true } serde = { version = "1.0", optional = true } diff --git a/payjoin/src/receive/mod.rs b/payjoin/src/receive/mod.rs index 1fd8d20e..b37be101 100644 --- a/payjoin/src/receive/mod.rs +++ b/payjoin/src/receive/mod.rs @@ -279,6 +279,7 @@ pub use error::{Error, RequestError, SelectionError}; use error::{InternalRequestError, InternalSelectionError}; use rand::seq::SliceRandom; use rand::Rng; +use serde::Serialize; use crate::input_type::InputType; use crate::optional_parameters::Params; @@ -288,6 +289,46 @@ pub trait Headers { fn get_header(&self, key: &str) -> Option<&str>; } +#[cfg(feature = "v2")] +pub struct ProposalContext { + s: bitcoin::secp256k1::KeyPair, +} + +impl ProposalContext { + pub fn new() -> Self { + let secp = bitcoin::secp256k1::Secp256k1::new(); + let (sk, _) = secp.generate_keypair(&mut rand::rngs::OsRng); + ProposalContext { s: bitcoin::secp256k1::KeyPair::from_secret_key(&secp, &sk) } + } + + pub fn subdirectory(&self) -> String { + let pubkey = &self.s.public_key().serialize(); + let b64_config = + bitcoin::base64::Config::new(bitcoin::base64::CharacterSet::UrlSafe, false); + let pubkey_base64 = bitcoin::base64::encode_config(pubkey, b64_config); + pubkey_base64 + } + + pub fn receive_subdir(&self) -> String { + format!("{}/{}", self.subdirectory(), crate::v2::RECEIVE) + } + + pub fn parse_proposal( + self, + encrypted_proposal: &mut [u8], + ) -> Result { + let (proposal, e) = crate::v2::decrypt_message_a(encrypted_proposal, self.s.secret_key()); + let mut proposal = serde_json::from_slice::(&proposal) + .map_err(InternalRequestError::Json)?; + proposal.psbt = proposal.psbt.validate().map_err(InternalRequestError::InconsistentPsbt)?; + proposal.v2_context = Some(e); + log::debug!("Received original psbt: {:?}", proposal.psbt); + log::debug!("Received request with params: {:?}", proposal.params); + + Ok(proposal) + } +} + /// The sender's original PSBT and optional parameters /// /// This type is used to proces the request. It is returned by @@ -302,6 +343,7 @@ pub struct UncheckedProposal { #[cfg_attr(feature = "v2", serde(deserialize_with = "deserialize_psbt"))] psbt: Psbt, params: Params, + v2_context: Option, } /// Typestate to validate that the Original PSBT has no receiver-owned inputs. @@ -310,6 +352,7 @@ pub struct UncheckedProposal { pub struct MaybeInputsOwned { psbt: Psbt, params: Params, + v2_context: Option, } /// Typestate to validate that the Original PSBT has no mixed input types. @@ -318,6 +361,7 @@ pub struct MaybeInputsOwned { pub struct MaybeMixedInputScripts { psbt: Psbt, params: Params, + v2_context: Option, } /// Typestate to validate that the Original PSBT has no inputs that have been seen before. @@ -326,6 +370,7 @@ pub struct MaybeMixedInputScripts { pub struct MaybeInputsSeen { psbt: Psbt, params: Params, + v2_context: Option, } pub(crate) fn deserialize_psbt<'de, D>(deserializer: D) -> Result @@ -342,20 +387,8 @@ where Ok(unchecked_psbt) } -#[cfg(feature = "v2")] -impl UncheckedProposal { - pub fn from_streamed(streamed: &[u8]) -> Result { - let mut proposal = serde_json::from_slice::(streamed) - .map_err(InternalRequestError::Json)?; - proposal.psbt = proposal.psbt.validate().map_err(InternalRequestError::InconsistentPsbt)?; - log::debug!("Received original psbt: {:?}", proposal.psbt); - log::debug!("Received request with params: {:?}", proposal.params); - - Ok(proposal) - } -} - impl UncheckedProposal { + #[cfg(not(feature = "v2"))] pub fn from_request( mut body: impl std::io::Read, query: &str, @@ -392,7 +425,7 @@ impl UncheckedProposal { // TODO check that params are valid for the request's Original PSBT - Ok(UncheckedProposal { psbt, params }) + Ok(UncheckedProposal { psbt, params, v2_context: None }) } /// The Sender's Original PSBT @@ -417,7 +450,11 @@ impl UncheckedProposal { can_broadcast: impl Fn(&bitcoin::Transaction) -> Result, ) -> Result { if can_broadcast(&self.psbt.clone().extract_tx())? { - Ok(MaybeInputsOwned { psbt: self.psbt, params: self.params }) + Ok(MaybeInputsOwned { + psbt: self.psbt, + params: self.params, + v2_context: self.v2_context, + }) } else { Err(Error::BadRequest(InternalRequestError::OriginalPsbtNotBroadcastable.into())) } @@ -429,7 +466,7 @@ impl UncheckedProposal { /// So-called "non-interactive" receivers, like payment processors, that allow arbitrary requests are otherwise vulnerable to probing attacks. /// Those receivers call `get_transaction_to_check_broadcast()` and `attest_tested_and_scheduled_broadcast()` after making those checks downstream. pub fn assume_interactive_receiver(self) -> MaybeInputsOwned { - MaybeInputsOwned { psbt: self.psbt, params: self.params } + MaybeInputsOwned { psbt: self.psbt, params: self.params, v2_context: self.v2_context } } } @@ -464,7 +501,11 @@ impl MaybeInputsOwned { } err?; - Ok(MaybeMixedInputScripts { psbt: self.psbt, params: self.params }) + Ok(MaybeMixedInputScripts { + psbt: self.psbt, + params: self.params, + v2_context: self.v2_context, + }) } } @@ -508,7 +549,7 @@ impl MaybeMixedInputScripts { })?; } - Ok(MaybeInputsSeen { psbt: self.psbt, params: self.params }) + Ok(MaybeInputsSeen { psbt: self.psbt, params: self.params, v2_context: self.v2_context }) } } @@ -533,7 +574,7 @@ impl MaybeInputsSeen { } })?; - Ok(OutputsUnknown { psbt: self.psbt, params: self.params }) + Ok(OutputsUnknown { psbt: self.psbt, params: self.params, v2_context: self.v2_context }) } } @@ -544,6 +585,7 @@ impl MaybeInputsSeen { pub struct OutputsUnknown { psbt: Psbt, params: Params, + v2_context: Option, } impl OutputsUnknown { @@ -574,6 +616,7 @@ impl OutputsUnknown { payjoin_psbt: self.psbt, params: self.params, owned_vouts, + v2_context: self.v2_context, }) } } @@ -583,6 +626,7 @@ pub struct PayjoinProposal { payjoin_psbt: Psbt, params: Params, owned_vouts: Vec, + v2_context: Option, } impl PayjoinProposal { @@ -597,6 +641,19 @@ impl PayjoinProposal { pub fn get_owned_vouts(&self) -> &Vec { &self.owned_vouts } pub fn psbt(&self) -> &Psbt { &self.payjoin_psbt } + + pub fn serialize_body(&self) -> Vec { + match self.v2_context { + Some(e) => { + let mut payjoin_bytes = self.payjoin_psbt.serialize(); + crate::v2::encrypt_message_b( + &mut payjoin_bytes, + self.v2_context.expect("v2 invariant should have a context"), + ) + } + None => bitcoin::base64::encode(self.payjoin_psbt.serialize()).as_bytes().to_vec(), + } + } } /// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin. @@ -605,6 +662,7 @@ pub struct ProvisionalProposal { payjoin_psbt: Psbt, params: Params, owned_vouts: Vec, + v2_context: Option, } impl ProvisionalProposal { @@ -833,6 +891,7 @@ impl ProvisionalProposal { payjoin_psbt: self.payjoin_psbt, owned_vouts: self.owned_vouts, params: self.params, + v2_context: self.v2_context, }) } diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index 1efe6641..3b0628fa 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -328,7 +328,7 @@ impl<'a> RequestBuilder<'a> { let input_type = InputType::from_spent_input(txout, zeroth_input.psbtin).unwrap(); #[cfg(not(feature = "v2"))] - let request = { + let (req, ctx) = { let url = serialize_url( self.uri.extras._endpoint.into(), disable_output_substitution, @@ -337,11 +337,31 @@ impl<'a> RequestBuilder<'a> { ) .map_err(InternalCreateRequestError::Url)?; let body = serialize_psbt(&psbt); - Request { url, body } + ( + Request { url, body }, + Context { + original_psbt: psbt, + disable_output_substitution, + fee_contribution, + payee, + input_type, + sequence, + min_fee_rate: self.min_fee_rate, + v2_e: None, + }, + ) }; #[cfg(feature = "v2")] - let request = { + let (req, ctx) = { + let rs_base64 = crate::v2::subdir(self.uri.extras._endpoint.as_str()).to_string(); + log::debug!("rs_base64: {:?}", rs_base64); + let b64_config = + bitcoin::base64::Config::new(bitcoin::base64::CharacterSet::UrlSafe, false); + let rs = bitcoin::base64::decode_config(rs_base64, b64_config).unwrap(); + log::debug!("rs: {:?}", rs.len()); + let rs = bitcoin::secp256k1::PublicKey::from_slice(&rs).unwrap(); + let url = self.uri.extras._endpoint; let body = serialize_v2_body( &psbt, @@ -349,21 +369,23 @@ impl<'a> RequestBuilder<'a> { fee_contribution, self.min_fee_rate, ); - Request { url, body } + let (body, e) = crate::v2::encrypt_message_a(&body, rs); + ( + Request { url, body }, + Context { + original_psbt: psbt, + disable_output_substitution, + fee_contribution, + payee, + input_type, + sequence, + min_fee_rate: self.min_fee_rate, + v2_context: Some(e), + }, + ) }; - Ok(( - request, - Context { - original_psbt: psbt, - disable_output_substitution, - fee_contribution, - payee, - input_type, - sequence, - min_fee_rate: self.min_fee_rate, - }, - )) + Ok((req, ctx)) } } @@ -397,6 +419,7 @@ pub struct Context { input_type: InputType, sequence: Sequence, payee: ScriptBuf, + v2_context: Option, } macro_rules! check_eq { @@ -426,6 +449,16 @@ impl Context { pub fn process_response( self, response: &mut impl std::io::Read, + ) -> Result { + match self.v2_context { + Some(e) => self.process_response_v2(response, e), + None => self.process_response_v1(response), + } + } + + pub fn process_response_v1( + self, + response: &mut impl std::io::Read, ) -> Result { let mut res_str = String::new(); response.read_to_string(&mut res_str).map_err(InternalValidationError::Io)?; @@ -435,6 +468,20 @@ impl Context { self.process_proposal(proposal).map(Into::into).map_err(Into::into) } + #[cfg(feature = "v2")] + pub fn process_response_v2( + self, + response: &mut impl std::io::Read, + e: bitcoin::secp256k1::SecretKey, + ) -> Result { + let mut res_buf = Vec::new(); + response.read_to_end(&mut res_buf).map_err(InternalValidationError::Io)?; + let psbt = crate::v2::decrypt_message_b(&mut res_buf, e); + let proposal = Psbt::deserialize(&psbt).expect("PSBT deserialization failed"); + // process in non-generic function + self.process_proposal(proposal).map(Into::into).map_err(Into::into) + } + fn process_proposal(self, proposal: Psbt) -> InternalResult { self.basic_checks(&proposal)?; let in_stats = self.check_inputs(&proposal)?; @@ -849,6 +896,7 @@ fn serialize_minfeerate(min_feerate: FeeRate) -> f32 { #[cfg(test)] mod tests { #[test] + #[cfg(not(feature = "v2"))] fn official_vectors() { use std::str::FromStr; diff --git a/payjoin/src/v2.rs b/payjoin/src/v2.rs index 93dfc652..ddef89da 100644 --- a/payjoin/src/v2.rs +++ b/payjoin/src/v2.rs @@ -1,2 +1,109 @@ pub const MAX_BUFFER_SIZE: usize = 65536; pub const RECEIVE: &str = "receive"; + +pub fn subdir(path: &str) -> String { + let subdirectory: String; + + if let Some(pos) = path.rfind('/') { + subdirectory = path[pos + 1..].to_string(); + } else { + subdirectory = path.to_string(); + } + + let pubkey_id: String; + + if let Some(pos) = subdirectory.find('?') { + pubkey_id = subdirectory[..pos].to_string(); + } else { + pubkey_id = subdirectory; + } + pubkey_id +} + +use bitcoin::secp256k1::ecdh::SharedSecret; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use chacha20poly1305::aead::{Aead, KeyInit, OsRng, Payload}; +use chacha20poly1305::{AeadCore, ChaCha20Poly1305, Nonce}; + +/// crypto context +/// +/// <- Receiver S +/// -> Sender E, ES(payload), payload protected by knowledge of receiver key +/// <- Receiver E, EE(payload), payload protected by knowledge of sender & receiver key +pub fn encrypt_message_a(msg: &[u8], s: PublicKey) -> (Vec, SecretKey) { + let secp = Secp256k1::new(); + let (e_sec, e_pub) = secp.generate_keypair(&mut OsRng); + let es = SharedSecret::new(&s, &e_sec); + let cipher = + ChaCha20Poly1305::new_from_slice(&es.secret_bytes()).expect("cipher creation failed"); + let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); // key es encrypts only 1 message so 0 is unique + let aad = &e_pub.serialize(); + let payload = Payload { msg, aad }; + log::debug!("payload.msg: {:?}", payload.msg); + log::debug!("payload.aad: {:?}", payload.aad); + let c_t: Vec = cipher.encrypt(&nonce, payload).expect("encryption failed"); + log::debug!("c_t: {:?}", c_t); + // let ct_payload = Payload { + // msg: &c_t[..], + // aad, + // }; + // let plaintext = cipher.decrypt(&nonce, ct_payload).map_err(|e| log::error!("error: {:?}", e)).unwrap(); + //log::debug!("plaintext: {:?}", plaintext); + log::debug!("es: {:?}", es); + let mut message_a = e_pub.serialize().to_vec(); + log::debug!("e: {:?}", e_pub); + message_a.extend(&nonce[..]); + log::debug!("nonce: {:?}", nonce); + message_a.extend(&c_t[..]); + (message_a, e_sec) +} + +pub fn decrypt_message_a(message_a: &mut [u8], s: SecretKey) -> (Vec, PublicKey) { + // let message a = [pubkey/AD][nonce][authentication tag][ciphertext] + let e = PublicKey::from_slice(&message_a[..33]).expect("invalid public key"); + log::debug!("e: {:?}", e); + let nonce = Nonce::from_slice(&message_a[33..45]); + log::debug!("nonce: {:?}", nonce); + let es = SharedSecret::new(&e, &s); + log::debug!("es: {:?}", es); + let cipher = + ChaCha20Poly1305::new_from_slice(&es.secret_bytes()).expect("cipher creation failed"); + let c_t = &message_a[45..]; + let aad = &e.serialize(); + log::debug!("c_t: {:?}", c_t); + log::debug!("aad: {:?}", aad); + let payload = Payload { msg: &c_t, aad }; + log::debug!("payload.msg: {:?}", payload.msg); + log::debug!("payload.aad: {:?}", payload.aad); + let buffer = cipher.decrypt(&nonce, payload).expect("decryption failed"); + (buffer, e) +} + +pub fn encrypt_message_b(msg: &mut Vec, re_pub: PublicKey) -> Vec { + // let message b = [pubkey/AD][nonce][authentication tag][ciphertext] + let secp = Secp256k1::new(); + let (e_sec, e_pub) = secp.generate_keypair(&mut OsRng); + let ee = SharedSecret::new(&re_pub, &e_sec); + let cipher = + ChaCha20Poly1305::new_from_slice(&ee.secret_bytes()).expect("cipher creation failed"); + let nonce = Nonce::from_slice(&[0u8; 12]); // key es encrypts only 1 message so 0 is unique + let aad = &e_pub.serialize(); + let payload = Payload { msg, aad }; + let c_t = cipher.encrypt(nonce, payload).expect("encryption failed"); + let mut message_b = e_pub.serialize().to_vec(); + message_b.extend(&nonce[..]); + message_b.extend(&c_t[..]); + message_b +} + +pub fn decrypt_message_b(message_b: &mut Vec, e: SecretKey) -> Vec { + // let message b = [pubkey/AD][nonce][authentication tag][ciphertext] + let re = PublicKey::from_slice(&message_b[..33]).expect("invalid public key"); + let nonce = Nonce::from_slice(&message_b[33..45]); + let ee = SharedSecret::new(&re, &e); + let cipher = + ChaCha20Poly1305::new_from_slice(&ee.secret_bytes()).expect("cipher creation failed"); + let payload = Payload { msg: &message_b[45..], aad: &re.serialize() }; + let buffer = cipher.decrypt(&nonce, payload).expect("decryption failed"); + buffer +}