Skip to content

Commit

Permalink
Merge pull request #107 from DanGould/v0.10.0
Browse files Browse the repository at this point in the history
Release payjoin-0.10.0
  • Loading branch information
DanGould authored Sep 30, 2023
2 parents d0cde61 + 96a48fa commit b1db4f9
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
cargo update -p log --precise 0.4.18
cargo update -p tempfile --precise 3.6.0
cargo update -p flate2 --precise 1.0.26
cargo update -p minreq --precise 2.8.0
cargo update -p rustls --precise 0.20.8
- name: test
run: cargo test --verbose --all-features --lib

Expand Down Expand Up @@ -74,4 +74,4 @@ jobs:
- name: fmt check
run: |
cd ${{ matrix.package }}
cargo fmt --all -- --check
cargo fmt --all -- --check
2 changes: 1 addition & 1 deletion payjoin-cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion payjoin-cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl App {
)?;

// 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();
let _to_broadcast_in_failure_case = proposal.extract_tx_to_schedule_broadcast();

// The network is used for checks later
let network =
Expand Down
7 changes: 7 additions & 0 deletions payjoin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Payjoin Changelog

## 0.10.0

- Export `base64` with feature by @jbesraa in #102
- Improve `receive` api with `ProvisionalProposal`by @jbesraa in #90
- Document `create_pj_request` by @jbesraa in #87
- Add BIP 78 reccommended fee `Configuration` by @DanGould in #86

## 0.9.0

Bumping `bitcoin` and other crates was a breaking api change. This is a 0.8.1 semver re-release.
Expand Down
2 changes: 1 addition & 1 deletion payjoin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "payjoin"
version = "0.9.0"
version = "0.10.0"
authors = ["Dan Gould <[email protected]>"]
description = "Payjoin Library for the BIP78 Pay to Endpoint protocol."
repository = "https://github.com/payjoin/rust-payjoin"
Expand Down
113 changes: 54 additions & 59 deletions payjoin/src/receive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
//!
//! ```
//! // 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();
//! let _to_broadcast_in_failure_case = proposal.extract_tx_to_schedule_broadcast();
//!
//! // The network is used for checks later
//! let network = match bitcoind.get_blockchain_info()?.chain.as_str() {
Expand Down Expand Up @@ -268,7 +268,6 @@
use std::cmp::{max, min};
use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;

use bitcoin::psbt::Psbt;
use bitcoin::{Amount, FeeRate, OutPoint, Script, TxOut};
Expand All @@ -295,38 +294,14 @@ pub trait Headers {
/// [`UncheckedProposal::from_request()`](crate::receive::UncheckedProposal::from_request()).
///
/// If you are implementing an interactive payment processor, you should get extract the original
/// transaction with get_transaction_to_schedule_broadcast() and schedule, followed by checking
/// transaction with extract_tx_to_schedule_broadcast() and schedule, followed by checking
/// that the transaction can be broadcast with check_can_broadcast. Otherwise it is safe to
/// call assume_interactive_receive to proceed with validation.
pub struct UncheckedProposal {
psbt: Psbt,
params: Params,
}

/// Typestate to validate that the Original PSBT has no receiver-owned inputs.
///
/// Call [`check_no_receiver_owned_inputs()`](struct.UncheckedProposal.html#method.check_no_receiver_owned_inputs) to proceed.
pub struct MaybeInputsOwned {
psbt: Psbt,
params: Params,
}

/// Typestate to validate that the Original PSBT has no mixed input types.
///
/// Call [`check_no_mixed_input_types`](struct.UncheckedProposal.html#method.check_no_mixed_input_scripts) to proceed.
pub struct MaybeMixedInputScripts {
psbt: Psbt,
params: Params,
}

/// Typestate to validate that the Original PSBT has no inputs that have been seen before.
///
/// Call [`check_no_inputs_seen`](struct.MaybeInputsSeen.html#method.check_no_inputs_seen_before) to proceed.
pub struct MaybeInputsSeen {
psbt: Psbt,
params: Params,
}

impl UncheckedProposal {
pub fn from_request(
mut body: impl std::io::Read,
Expand Down Expand Up @@ -368,15 +343,15 @@ impl UncheckedProposal {
}

/// The Sender's Original PSBT
pub fn get_transaction_to_schedule_broadcast(&self) -> bitcoin::Transaction {
pub fn extract_tx_to_schedule_broadcast(&self) -> bitcoin::Transaction {
self.psbt.clone().extract_tx()
}

/// Call after checking that the Original PSBT can be broadcast.
///
/// Receiver MUST check that the Original PSBT from the sender
/// can be broadcast, i.e. `testmempoolaccept` bitcoind rpc returns { "allowed": true,.. }
/// for `get_transaction_to_check_broadcast()` before calling this method.
/// for `extract_tx_to_sheculed_broadcast()` before calling this method.
///
/// Do this check if you generate bitcoin uri to receive Payjoin on sender request without manual human approval, like a payment processor.
/// Such so called "non-interactive" receivers are otherwise vulnerable to probing attacks.
Expand All @@ -399,12 +374,20 @@ impl UncheckedProposal {
/// requires manual intervention, as in most consumer wallets.
///
/// 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.
/// Those receivers call `extract_tx_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 }
}
}

/// Typestate to validate that the Original PSBT has no receiver-owned inputs.
///
/// Call [`check_no_receiver_owned_inputs()`](struct.UncheckedProposal.html#method.check_no_receiver_owned_inputs) to proceed.
pub struct MaybeInputsOwned {
psbt: Psbt,
params: Params,
}

impl MaybeInputsOwned {
/// Check that the Original PSBT has no receiver-owned inputs.
/// Return original-psbt-rejected error or otherwise refuse to sign undesirable inputs.
Expand Down Expand Up @@ -440,6 +423,14 @@ impl MaybeInputsOwned {
}
}

/// Typestate to validate that the Original PSBT has no mixed input types.
///
/// Call [`check_no_mixed_input_types`](struct.UncheckedProposal.html#method.check_no_mixed_input_scripts) to proceed.
pub struct MaybeMixedInputScripts {
psbt: Psbt,
params: Params,
}

impl MaybeMixedInputScripts {
/// Verify the original transaction did not have mixed input types
/// Call this after checking downstream.
Expand Down Expand Up @@ -484,6 +475,13 @@ impl MaybeMixedInputScripts {
}
}

/// Typestate to validate that the Original PSBT has no inputs that have been seen before.
///
/// Call [`check_no_inputs_seen`](struct.MaybeInputsSeen.html#method.check_no_inputs_seen_before) to proceed.
pub struct MaybeInputsSeen {
psbt: Psbt,
params: Params,
}
impl MaybeInputsSeen {
/// Make sure that the original transaction inputs have never been seen before.
/// This prevents probing attacks. This prevents reentrant Payjoin, where a sender
Expand Down Expand Up @@ -550,27 +548,6 @@ impl OutputsUnknown {
}
}

/// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin.
pub struct PayjoinProposal {
payjoin_psbt: Psbt,
params: Params,
owned_vouts: Vec<usize>,
}

impl PayjoinProposal {
pub fn utxos_to_be_locked(&self) -> impl '_ + Iterator<Item = &bitcoin::OutPoint> {
self.payjoin_psbt.unsigned_tx.input.iter().map(|input| &input.previous_output)
}

pub fn is_output_substitution_disabled(&self) -> bool {
self.params.disable_output_substitution
}

pub fn get_owned_vouts(&self) -> &Vec<usize> { &self.owned_vouts }

pub fn psbt(&self) -> &Psbt { &self.payjoin_psbt }
}

/// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin.
pub struct ProvisionalProposal {
original_psbt: Psbt,
Expand Down Expand Up @@ -814,15 +791,33 @@ impl ProvisionalProposal {
min_feerate_sat_per_vb: Option<FeeRate>,
) -> Result<PayjoinProposal, Error> {
let psbt = self.apply_fee(min_feerate_sat_per_vb)?;
let psbt = wallet_process_psbt(psbt).map_err(|e| {
log::error!("wallet_process_psbt error");
Error::from(e)
})?;
let payjoin_proposal = self.prepare_psbt(psbt).map_err(RequestError::from)?;
let psbt = wallet_process_psbt(psbt)?;
let payjoin_proposal = self.prepare_psbt(psbt)?;
Ok(payjoin_proposal)
}
}

/// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin.
pub struct PayjoinProposal {
payjoin_psbt: Psbt,
params: Params,
owned_vouts: Vec<usize>,
}

impl PayjoinProposal {
pub fn utxos_to_be_locked(&self) -> impl '_ + Iterator<Item = &bitcoin::OutPoint> {
self.payjoin_psbt.unsigned_tx.input.iter().map(|input| &input.previous_output)
}

pub fn is_output_substitution_disabled(&self) -> bool {
self.params.disable_output_substitution
}

pub fn owned_vouts(&self) -> &Vec<usize> { &self.owned_vouts }

pub fn psbt(&self) -> &Psbt { &self.payjoin_psbt }
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -846,7 +841,7 @@ mod test {
}
}

fn get_proposal_from_test_vector() -> Result<UncheckedProposal, RequestError> {
fn proposal_from_test_vector() -> Result<UncheckedProposal, RequestError> {
// OriginalPSBT Test Vector from BIP
// | InputScriptType | Orginal PSBT Fee rate | maxadditionalfeecontribution | additionalfeeoutputindex|
// |-----------------|-----------------------|------------------------------|-------------------------|
Expand All @@ -864,7 +859,7 @@ mod test {

#[test]
fn can_get_proposal_from_request() {
let proposal = get_proposal_from_test_vector();
let proposal = proposal_from_test_vector();
assert!(proposal.is_ok(), "OriginalPSBT should be a valid request");
}

Expand All @@ -874,7 +869,7 @@ mod test {

use bitcoin::{Address, Network};

let proposal = get_proposal_from_test_vector().unwrap();
let proposal = proposal_from_test_vector().unwrap();
let mut payjoin = proposal
.assume_interactive_receiver()
.check_inputs_not_owned(|_| Ok(false))
Expand Down
2 changes: 1 addition & 1 deletion payjoin/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ mod integration {
.unwrap();

// 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();
let _to_broadcast_in_failure_case = proposal.extract_tx_to_schedule_broadcast();

// Receive Check 1: Can Broadcast
let proposal = proposal
Expand Down

0 comments on commit b1db4f9

Please sign in to comment.