Skip to content

Commit 3ff1d14

Browse files
authored
Cleanup for 0.23 release (payjoin#622)
Ahead of the feature-freeze for 0.23, do some cleanup and bring doc strings up to date. Closes payjoin#591 and payjoin#503.
2 parents beb94e1 + 754e91c commit 3ff1d14

File tree

7 files changed

+84
-22
lines changed

7 files changed

+84
-22
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ Supercharged payment batching to save you fees and preserve your privacy.
66

77
### `payjoin`
88

9-
The Payjoin Dev Kit `payjoin` library implements both [BIP 78 Payjoin V1](https://github.com/shesek/bips/blob/master/bip-0078.mediawiki) and [BIP 77 Payjoin V2](https://github.com/bitcoin/bips/pull/1483).
10-
11-
The `payjoin` crate is compatible with many wallets like LND in [nolooking](https://github.com/chaincase-app/nolooking) and Bitcoin Dev Kit in [Mutiny Wallet](https://github.com/MutinyWallet/mutiny-node) and in [BitMask](https://github.com/diba-io/bitmask-core)
9+
The Payjoin Dev Kit `payjoin` library implements both [BIP 78 Payjoin V1](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) and [BIP 77 Payjoin V2](https://github.com/bitcoin/bips/pull/1483).
1210

1311
### `payjoin-cli`
1412

@@ -18,6 +16,10 @@ The [`payjoin-cli`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-
1816

1917
The [`payjoin-directory`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-directory) crate implements the Payjoin Directory store-and-forward server required for Payjoin V2's asynchronous operation.
2018

19+
### `payjoin-test-utils`
20+
21+
The [`payjoin-test-utils`](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-test-utils) crate provides commonly used testing fixtures such as a local OHTTP relay and payjoin directory, bitcoind node and wallets, and official test vectors.
22+
2123
### Disclaimer ⚠️ WIP
2224

2325
**Use at your own risk. This crate has not yet been reviewed by independent Rust and Bitcoin security professionals.**

payjoin-cli/src/app/v2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl App {
175175
Err(e) => return Err(e.into()),
176176
};
177177
let (req, ohttp_ctx) = payjoin_proposal
178-
.extract_v2_req(&self.config.v2()?.ohttp_relay)
178+
.extract_req(&self.config.v2()?.ohttp_relay)
179179
.map_err(|e| anyhow!("v2 req extraction failed {}", e))?;
180180
println!("Got a request from the sender. Responding with a Payjoin proposal.");
181181
let res = post_request(req).await?;

payjoin/src/receive/v1/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,10 +763,12 @@ pub struct PayjoinProposal {
763763
}
764764

765765
impl PayjoinProposal {
766+
/// The UTXOs that would be spent by this Payjoin transaction
766767
pub fn utxos_to_be_locked(&self) -> impl '_ + Iterator<Item = &bitcoin::OutPoint> {
767768
self.payjoin_psbt.unsigned_tx.input.iter().map(|input| &input.previous_output)
768769
}
769770

771+
/// The Payjoin Proposal PSBT
770772
pub fn psbt(&self) -> &Psbt { &self.payjoin_psbt }
771773
}
772774

payjoin/src/receive/v2/mod.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,14 @@ fn subdir_path_from_pubkey(pubkey: &HpkePublicKey) -> ShortId {
7171
sha256::Hash::hash(&pubkey.to_compressed_bytes()).into()
7272
}
7373

74-
/// A payjoin V2 receiver, allowing for polled requests to the
75-
/// payjoin directory and response processing.
74+
/// A new payjoin receiver, which must be persisted before initiating the payjoin flow.
7675
#[derive(Debug)]
7776
pub struct NewReceiver {
7877
context: SessionContext,
7978
}
8079

8180
impl NewReceiver {
82-
/// Creates a new `Receiver` with the provided parameters.
81+
/// Creates a new [`NewReceiver`] with the provided parameters.
8382
///
8483
/// # Parameters
8584
/// - `address`: The Bitcoin address for the payjoin session.
@@ -88,7 +87,7 @@ impl NewReceiver {
8887
/// - `expire_after`: The duration after which the session expires.
8988
///
9089
/// # Returns
91-
/// A new instance of `Receiver`.
90+
/// A new instance of [`NewReceiver`].
9291
///
9392
/// # References
9493
/// - [BIP 77: Payjoin Version 2: Serverless Payjoin](https://github.com/bitcoin/bips/pull/1483)
@@ -113,6 +112,7 @@ impl NewReceiver {
113112
Ok(receiver)
114113
}
115114

115+
/// Saves the new [`Receiver`] using the provided persister and returns the storage token.
116116
pub fn persist<P: Persister<Receiver>>(
117117
&self,
118118
persister: &mut P,
@@ -122,6 +122,8 @@ impl NewReceiver {
122122
}
123123
}
124124

125+
/// A payjoin V2 receiver, allowing for polled requests to the
126+
/// payjoin directory and response processing.
125127
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126128
pub struct Receiver {
127129
context: SessionContext,
@@ -150,6 +152,7 @@ impl persist::Value for Receiver {
150152
}
151153

152154
impl Receiver {
155+
/// Loads a [`Receiver`] from the provided persister using the storage token.
153156
pub fn load<P: Persister<Receiver>>(
154157
token: P::Token,
155158
persister: &P,
@@ -361,7 +364,7 @@ impl UncheckedProposal {
361364

362365
/// Typestate to validate that the Original PSBT has no receiver-owned inputs.
363366
///
364-
/// Call [`check_no_receiver_owned_inputs()`](struct.UncheckedProposal.html#method.check_no_receiver_owned_inputs) to proceed.
367+
/// Call [`Self::check_inputs_not_owned`] to proceed.
365368
#[derive(Debug, Clone)]
366369
pub struct MaybeInputsOwned {
367370
v1: v1::MaybeInputsOwned,
@@ -384,7 +387,7 @@ impl MaybeInputsOwned {
384387

385388
/// Typestate to validate that the Original PSBT has no inputs that have been seen before.
386389
///
387-
/// Call [`check_no_inputs_seen`](struct.MaybeInputsSeen.html#method.check_no_inputs_seen_before) to proceed.
390+
/// Call [`Self::check_no_inputs_seen_before`] to proceed.
388391
#[derive(Debug, Clone)]
389392
pub struct MaybeInputsSeen {
390393
v1: v1::MaybeInputsSeen,
@@ -407,7 +410,7 @@ impl MaybeInputsSeen {
407410
/// The receiver has not yet identified which outputs belong to the receiver.
408411
///
409412
/// Only accept PSBTs that send us money.
410-
/// Identify those outputs with `identify_receiver_outputs()` to proceed
413+
/// Identify those outputs with [`Self::identify_receiver_outputs`] to proceed.
411414
#[derive(Debug, Clone)]
412415
pub struct OutputsUnknown {
413416
inner: v1::OutputsUnknown,
@@ -426,6 +429,8 @@ impl OutputsUnknown {
426429
}
427430

428431
/// A checked proposal that the receiver may substitute or add outputs to
432+
///
433+
/// Call [`Self::commit_outputs`] to proceed.
429434
#[derive(Debug, Clone)]
430435
pub struct WantsOutputs {
431436
v1: v1::WantsOutputs,
@@ -468,6 +473,8 @@ impl WantsOutputs {
468473
}
469474

470475
/// A checked proposal that the receiver may contribute inputs to to make a payjoin
476+
///
477+
/// Call [`Self::commit_inputs`] to proceed.
471478
#[derive(Debug, Clone)]
472479
pub struct WantsInputs {
473480
v1: v1::WantsInputs,
@@ -513,13 +520,21 @@ impl WantsInputs {
513520

514521
/// A checked proposal that the receiver may sign and finalize to make a proposal PSBT that the
515522
/// sender will accept.
523+
///
524+
/// Call [`Self::finalize_proposal`] to return a finalized [`PayjoinProposal`].
516525
#[derive(Debug, Clone)]
517526
pub struct ProvisionalProposal {
518527
v1: v1::ProvisionalProposal,
519528
context: SessionContext,
520529
}
521530

522531
impl ProvisionalProposal {
532+
/// Return a Payjoin Proposal PSBT that the sender will find acceptable.
533+
///
534+
/// This attempts to calculate any network fee owed by the receiver, subtract it from their output,
535+
/// and return a PSBT that can produce a consensus-valid transaction that the sender will accept.
536+
///
537+
/// wallet_process_psbt should sign and finalize receiver inputs
523538
pub fn finalize_proposal(
524539
self,
525540
wallet_process_psbt: impl Fn(&Psbt) -> Result<Psbt, ImplementationError>,
@@ -532,7 +547,8 @@ impl ProvisionalProposal {
532547
}
533548
}
534549

535-
/// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin.
550+
/// A finalized payjoin proposal, complete with fees and receiver signatures, that the sender
551+
/// should find acceptable.
536552
#[derive(Clone)]
537553
pub struct PayjoinProposal {
538554
v1: v1::PayjoinProposal,
@@ -541,18 +557,21 @@ pub struct PayjoinProposal {
541557

542558
impl PayjoinProposal {
543559
#[cfg(feature = "_multiparty")]
544-
// TODO hack to get multi party working. A better solution would be to allow extract_v2_req to be separate from the rest of the v2 context
560+
// TODO hack to get multi party working. A better solution would be to allow extract_req to be separate from the rest of the v2 context
545561
pub(crate) fn new(v1: v1::PayjoinProposal, context: SessionContext) -> Self {
546562
Self { v1, context }
547563
}
548564

565+
/// The UTXOs that would be spent by this Payjoin transaction
549566
pub fn utxos_to_be_locked(&self) -> impl '_ + Iterator<Item = &bitcoin::OutPoint> {
550567
self.v1.utxos_to_be_locked()
551568
}
552569

570+
/// The Payjoin Proposal PSBT
553571
pub fn psbt(&self) -> &Psbt { self.v1.psbt() }
554572

555-
pub fn extract_v2_req(
573+
/// Extract an OHTTP Encapsulated HTTP POST request for the Proposal PSBT
574+
pub fn extract_req(
556575
&mut self,
557576
ohttp_relay: impl IntoUrl,
558577
) -> Result<(Request, ohttp::ClientResponse), Error> {
@@ -594,7 +613,6 @@ impl PayjoinProposal {
594613
Ok((req, ctx))
595614
}
596615

597-
#[cfg(feature = "v2")]
598616
/// Processes the response for the final POST message from the receiver client in the v2 Payjoin protocol.
599617
///
600618
/// This function decapsulates the response using the provided OHTTP context. If the response status is successful,

payjoin/src/send/v1.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::psbt::PsbtExt;
3232
use crate::request::Request;
3333
pub use crate::PjUri;
3434

35+
/// A builder to construct the properties of a `Sender`.
3536
#[derive(Clone)]
3637
pub struct SenderBuilder<'a> {
3738
pub(crate) psbt: Psbt,
@@ -210,12 +211,14 @@ impl<'a> SenderBuilder<'a> {
210211
}
211212
}
212213

214+
/// A payjoin V1 sender, allowing the construction of a payjoin V1 request
215+
/// and the resulting `V1Context`
213216
#[derive(Clone, PartialEq, Eq, Debug)]
214217
#[cfg_attr(feature = "v2", derive(serde::Serialize, serde::Deserialize))]
215218
pub struct Sender {
216219
/// The original PSBT.
217220
pub(crate) psbt: Psbt,
218-
/// The payjoin directory subdirectory to send the request to.
221+
/// The endpoint in the Payjoin URI
219222
pub(crate) endpoint: Url,
220223
/// Whether the receiver is allowed to substitute original outputs.
221224
pub(crate) output_substitution: OutputSubstitution,
@@ -251,13 +254,14 @@ impl Sender {
251254
)
252255
}
253256

257+
/// The endpoint in the Payjoin URI
254258
pub fn endpoint(&self) -> &Url { &self.endpoint }
255259
}
256260

257261
/// Data required to validate the response.
258262
///
259263
/// This type is used to process a BIP78 response.
260-
/// Then call [`Self::process_response`] on it to continue BIP78 flow.
264+
/// Call [`Self::process_response`] on it to continue the BIP78 flow.
261265
#[derive(Debug, Clone)]
262266
pub struct V1Context {
263267
psbt_context: PsbtContext,

payjoin/src/send/v2/mod.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use crate::{HpkeKeyPair, HpkePublicKey, IntoUrl, OhttpKeys, PjUri, Request};
4141

4242
mod error;
4343

44+
/// A builder to construct the properties of a [`Sender`].
4445
#[derive(Clone)]
4546
pub struct SenderBuilder<'a>(pub(crate) v1::SenderBuilder<'a>);
4647

@@ -123,13 +124,15 @@ impl<'a> SenderBuilder<'a> {
123124
}
124125
}
125126

127+
/// A new payjoin sender, which must be persisted before initiating the payjoin flow.
126128
#[derive(Debug)]
127129
pub struct NewSender {
128130
pub(crate) v1: v1::Sender,
129131
pub(crate) reply_key: HpkeSecretKey,
130132
}
131133

132134
impl NewSender {
135+
/// Saves the new [`Sender`] using the provided persister and returns the storage token.
133136
pub fn persist<P: Persister<Sender>>(
134137
&self,
135138
persister: &mut P,
@@ -139,6 +142,8 @@ impl NewSender {
139142
}
140143
}
141144

145+
/// A payjoin V2 sender, allowing the construction of a payjoin V2 request
146+
/// and the resulting [`V2PostContext`].
142147
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
143148
pub struct Sender {
144149
/// The v1 Sender.
@@ -170,6 +175,7 @@ impl Value for Sender {
170175
}
171176

172177
impl Sender {
178+
/// Loads a [`Sender`] from the provided persister using the storage token.
173179
pub fn load<P: Persister<Sender>>(
174180
token: P::Token,
175181
persister: &P,
@@ -236,6 +242,7 @@ impl Sender {
236242
self.v1.endpoint.receiver_pubkey()
237243
}
238244

245+
/// The endpoint in the Payjoin URI
239246
pub fn endpoint(&self) -> &Url { self.v1.endpoint() }
240247
}
241248

@@ -289,15 +296,29 @@ pub(crate) fn serialize_v2_body(
289296
Ok(format!("{}\n{}", base64, query_params).into_bytes())
290297
}
291298

299+
/// Data required to validate the POST response.
300+
///
301+
/// This type is used to process a BIP77 POST response.
302+
/// Call [`Self::process_response`] on it to continue the BIP77 flow.
292303
pub struct V2PostContext {
293-
/// The payjoin directory subdirectory to send the request to.
304+
/// The endpoint in the Payjoin URI
294305
pub(crate) endpoint: Url,
295306
pub(crate) psbt_ctx: PsbtContext,
296307
pub(crate) hpke_ctx: HpkeContext,
297308
pub(crate) ohttp_ctx: ohttp::ClientResponse,
298309
}
299310

300311
impl V2PostContext {
312+
/// Processes the response for the initial POST message from the sender
313+
/// client in the v2 Payjoin protocol.
314+
///
315+
/// This function decapsulates the response using the provided OHTTP
316+
/// context. If the encapsulated response status is successful, it
317+
/// indicates that the the Original PSBT been accepted. Otherwise, it
318+
/// returns an error with the encapsulated response status code.
319+
///
320+
/// After this function is called, the sender can poll for a Proposal PSBT
321+
/// from the receiver using the returned [`V2GetContext`].
301322
pub fn process_response(self, response: &[u8]) -> Result<V2GetContext, EncapsulationError> {
302323
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] = response
303324
.try_into()
@@ -318,15 +339,20 @@ impl V2PostContext {
318339
}
319340
}
320341

342+
/// Data required to validate the GET response.
343+
///
344+
/// This type is used to make a BIP77 GET request and process the response.
345+
/// Call [`Self::process_response`] on it to continue the BIP77 flow.
321346
#[derive(Debug, Clone)]
322347
pub struct V2GetContext {
323-
/// The payjoin directory subdirectory to send the request to.
348+
/// The endpoint in the Payjoin URI
324349
pub(crate) endpoint: Url,
325350
pub(crate) psbt_ctx: PsbtContext,
326351
pub(crate) hpke_ctx: HpkeContext,
327352
}
328353

329354
impl V2GetContext {
355+
/// Extract an OHTTP Encapsulated HTTP GET request for the Proposal PSBT
330356
pub fn extract_req(
331357
&self,
332358
ohttp_relay: impl IntoUrl,
@@ -354,6 +380,16 @@ impl V2GetContext {
354380
Ok((Request::new_v2(&url, &body), ohttp_ctx))
355381
}
356382

383+
/// Processes the response for the final GET message from the sender client
384+
/// in the v2 Payjoin protocol.
385+
///
386+
/// This function decapsulates the response using the provided OHTTP
387+
/// context. A successful response can either be a Proposal PSBT or an
388+
/// ACCEPTED message indicating no Proposal PSBT is available yet.
389+
/// Otherwise, it returns an error with the encapsulated status code.
390+
///
391+
/// After this function is called, the sender can sign and finalize the
392+
/// PSBT and broadcast the resulting Payjoin transaction to the network.
357393
pub fn process_response(
358394
&self,
359395
response: &[u8],

payjoin/tests/integration.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ mod integration {
371371
.process_res(response.bytes().await?.to_vec().as_slice(), ctx)?
372372
.expect("proposal should exist");
373373
let mut payjoin_proposal = handle_directory_proposal(&receiver, proposal, None)?;
374-
let (req, ctx) = payjoin_proposal.extract_v2_req(&ohttp_relay)?;
374+
let (req, ctx) = payjoin_proposal.extract_req(&ohttp_relay)?;
375375
let response = agent
376376
.post(req.url)
377377
.header("Content-Type", req.content_type)
@@ -560,7 +560,7 @@ mod integration {
560560
.map_err(|e| e.to_string())?;
561561
// Respond with payjoin psbt within the time window the sender is willing to wait
562562
// this response would be returned as http response to the sender
563-
let (req, ctx) = payjoin_proposal.extract_v2_req(&ohttp_relay)?;
563+
let (req, ctx) = payjoin_proposal.extract_req(&ohttp_relay)?;
564564
let response = agent_clone
565565
.post(req.url)
566566
.header("Content-Type", req.content_type)
@@ -831,7 +831,7 @@ mod integration {
831831

832832
// Send the payjoin proposals to the senders
833833
for mut proposal in multi_sender_payjoin_proposal.sender_iter() {
834-
let (req, ctx) = proposal.extract_v2_req(&ohttp_relay)?;
834+
let (req, ctx) = proposal.extract_req(&ohttp_relay)?;
835835
let response = agent
836836
.post(req.url)
837837
.header("Content-Type", req.content_type)

0 commit comments

Comments
 (0)