Skip to content

Commit 5deba12

Browse files
committed
Secure v2 payloads with authenticated encryption
1 parent 92c02cc commit 5deba12

File tree

6 files changed

+353
-66
lines changed

6 files changed

+353
-66
lines changed

Cargo.lock

Lines changed: 83 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

payjoin-cli/src/app.rs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use clap::ArgMatches;
1111
use config::{Config, File, FileFormat};
1212
use payjoin::bitcoin::psbt::Psbt;
1313
use payjoin::bitcoin::{self, base64};
14-
use payjoin::receive::{Error, ProvisionalProposal, UncheckedProposal};
14+
use payjoin::receive::{Error, PayjoinProposal, ProvisionalProposal, UncheckedProposal};
1515
#[cfg(not(feature = "v2"))]
1616
use rouille::{Request, Response};
1717
use serde::{Deserialize, Serialize};
@@ -218,13 +218,9 @@ impl App {
218218

219219
#[cfg(feature = "v2")]
220220
pub async fn receive_payjoin(self, amount_arg: &str) -> Result<()> {
221-
let secp = bitcoin::secp256k1::Secp256k1::new();
222-
let mut rng = bitcoin::secp256k1::rand::thread_rng();
223-
let key = bitcoin::secp256k1::KeyPair::new(&secp, &mut rng);
224-
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
225-
let pubkey_base64 = base64::encode_config(key.public_key().to_string(), b64_config);
226-
227-
let pj_uri_string = self.construct_payjoin_uri(amount_arg, Some(&pubkey_base64))?;
221+
let context = payjoin::receive::ProposalContext::new();
222+
let pj_uri_string =
223+
self.construct_payjoin_uri(amount_arg, Some(&context.subdirectory()))?;
228224
println!(
229225
"Listening at {}. Configured to accept payjoin at BIP 21 Payjoin Uri:",
230226
self.config.pj_host
@@ -236,20 +232,21 @@ impl App {
236232
.build()
237233
.with_context(|| "Failed to build reqwest http client")?;
238234
log::debug!("Awaiting request");
239-
let receive_endpoint =
240-
format!("{}/{}/{}", self.config.pj_endpoint, pubkey_base64, payjoin::v2::RECEIVE);
241-
let buffer = Self::long_poll_get(&client, &receive_endpoint).await?;
235+
let receive_endpoint = format!("{}/{}", self.config.pj_endpoint, context.receive_subdir());
236+
let mut buffer = Self::long_poll_get(&client, &receive_endpoint).await?;
242237

243238
log::debug!("Received request");
244-
let proposal = UncheckedProposal::from_streamed(&buffer)
239+
let proposal = context
240+
.parse_proposal(&mut buffer)
245241
.map_err(|e| anyhow!("Failed to parse into UncheckedProposal {}", e))?;
246-
let payjoin_psbt = self
242+
let payjoin_proposal = self
247243
.process_proposal(proposal)
248244
.map_err(|e| anyhow!("Failed to process UncheckedProposal {}", e))?;
249-
let payjoin_psbt_ser = payjoin::bitcoin::base64::encode(&payjoin_psbt.serialize());
245+
246+
let body = payjoin_proposal.serialize_body();
250247
let _ = client
251248
.post(receive_endpoint)
252-
.body(payjoin_psbt_ser)
249+
.body(body)
253250
.send()
254251
.await
255252
.with_context(|| "HTTP request failed")?;
@@ -376,15 +373,15 @@ impl App {
376373
headers,
377374
)?;
378375

379-
let payjoin_proposal_psbt = self.process_proposal(proposal)?;
380-
log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt);
376+
let payjoin_proposal = self.process_proposal(proposal)?;
377+
378+
let body = payjoin_proposal.serialize_body().as_str().to_sring();
381379

382-
let payload = payjoin::bitcoin::base64::encode(&payjoin_proposal_psbt.serialize());
383380
log::info!("successful response");
384-
Ok(Response::text(payload))
381+
Ok(Response::text(body))
385382
}
386383

387-
fn process_proposal(&self, proposal: UncheckedProposal) -> Result<Psbt, Error> {
384+
fn process_proposal(&self, proposal: UncheckedProposal) -> Result<PayjoinProposal, Error> {
388385
// in a payment processor where the sender could go offline, this is where you schedule to broadcast the original_tx
389386
let _to_broadcast_in_failure_case = proposal.get_transaction_to_schedule_broadcast();
390387

@@ -456,23 +453,18 @@ impl App {
456453
.assume_checked();
457454
provisional_payjoin.substitute_output_address(receiver_substitute_address);
458455

459-
let payjoi_proposal = provisional_payjoin.finalize_proposal(
456+
let payjoin_proposal = provisional_payjoin.finalize_proposal(
460457
|psbt: &Psbt| {
461458
self.bitcoind
462-
.wallet_process_psbt(
463-
&payjoin::base64::encode(psbt.serialize()),
464-
None,
465-
None,
466-
Some(false),
467-
)
459+
.wallet_process_psbt(&base64::encode(psbt.serialize()), None, None, Some(false))
468460
.map(|res| Psbt::from_str(&res.psbt).map_err(|e| Error::Server(e.into())))
469461
.map_err(|e| Error::Server(e.into()))?
470462
},
471463
Some(bitcoin::FeeRate::MIN),
472464
)?;
473-
let payjoin_proposal_psbt = payjoi_proposal.psbt();
465+
let payjoin_proposal_psbt = payjoin_proposal.psbt();
474466
log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt);
475-
Ok(payjoin_proposal_psbt.clone())
467+
Ok(payjoin_proposal)
476468
}
477469

478470
fn insert_input_seen_before(&self, input: bitcoin::OutPoint) -> Result<bool> {
@@ -621,4 +613,4 @@ impl payjoin::receive::Headers for Headers<'_> {
621613
}
622614
}
623615

624-
fn serialize_psbt(psbt: &Psbt) -> String { payjoin::base64::encode(&psbt.serialize()) }
616+
fn serialize_psbt(psbt: &Psbt) -> String { base64::encode(&psbt.serialize()) }

payjoin/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ edition = "2018"
1616
send = []
1717
receive = ["rand"]
1818
base64 = ["bitcoin/base64"]
19-
v2 = ["serde", "serde_json"]
19+
v2 = ["bitcoin/rand-std", "chacha20poly1305", "serde", "serde_json"]
2020

2121
[dependencies]
2222
bitcoin = { version = "0.30.0", features = ["base64"] }
2323
bip21 = "0.3.1"
24+
chacha20poly1305 = { version = "0.10.1", optional = true }
2425
log = { version = "0.4.14"}
2526
rand = { version = "0.8.4", optional = true }
2627
serde = { version = "1.0", optional = true }

0 commit comments

Comments
 (0)