Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion depends/packages/curl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ define $(package)_set_vars
$(package)_config_opts+=-DUSE_NGTCP2=OFF -DUSE_QUICHE=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF
$(package)_config_opts+=-DCURL_USE_GSSAPI=OFF -DCURL_USE_GSASL=OFF -DCURL_USE_LIBUV=OFF -DCURL_USE_LIBBACKTRACE=OFF
$(package)_config_opts+=-DCURL_USE_MBEDTLS=OFF -DCURL_USE_WOLFSSL=OFF -DCURL_USE_GNUTLS=OFF -DCURL_USE_RUSTLS=OFF
$(package)_config_opts+=-DCURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt -DCURL_CA_PATH=none -DCURL_CA_FALLBACK=OFF
$(package)_config_opts+=-DCURL_CA_BUNDLE=none -DCURL_CA_PATH=none -DCURL_CA_FALLBACK=ON
$(package)_config_opts+=-DCURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG=ON
endef

Expand Down
21 changes: 20 additions & 1 deletion doc/payjoin-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ RUSTUP_TOOLCHAIN=1.85.1 rustc --version
RUSTUP_TOOLCHAIN=1.85.1 cargo --version
```

## HTTPS trust

HTTPS CA certificates are provided by the operating system; the archive does not
bundle them. The depends OpenSSL build uses `/etc/ssl/cert.pem` and the hashed
`/etc/ssl/certs` directory. Set `SSL_CERT_FILE` and `SSL_CERT_DIR` in the node's
environment for nonstandard locations. These variables do not guarantee exclusive
trust in one CA file: other default trust sources can remain active. Restarting
the node is recommended after changes to trust settings or certificates.

Release-archive HTTPS checks covered Debian 12 with the default paths and Fedora
44 with `SSL_CERT_DIR=/etc/pki/ca-trust/extracted/pem/directory-hash` and no legacy
CA bundle files. These transport checks do not establish a completed Payjoin payment.

## Production FFI build and unit tests

These Core builds use Debug for testing. Keep `source_dir` and `depends_prefix` in one
Expand Down Expand Up @@ -80,6 +93,12 @@ It checks the receiver input, publication, confirmation, reorg, idempotency,
cancellation and explicit fallback. Successful CLI exit is checked before mining;
`--timeout-factor` scales bounded waits.

`reservations.unreleased` means the service acknowledged registration but has not
acknowledged release; commands recheck ownership before acting. It is not a fresh
owner-record or wallet-lock check. With storage uncertainty, `false` can coexist with
a durable registration whose acknowledgement was lost, and `true` can outlive a
durable release whose acknowledgement was lost.

## BTCPay Server regtest smoke test

Prerequisites: both nodes share the same regtest chain; the Core sender wallet is
Expand All @@ -94,7 +113,7 @@ Use the [RPC workflow](payjoin-rpc.md#minimal-workflow).
| 2 | Call `sendpayjoin` with the URI, relay, unique `request_id` and fee cap. | A `payment_id` is returned. |
| 3 | Repeat the request with identical parameters. | Same `payment_id`, `duplicate=true`. |
| 4 | Poll `getpayjoin` and inspect the selected transaction. | `published`; `selected.txid` differs from `original.txid`; a receiver-owned input is present; total fee respects the cap; original is absent from the mempool. |
| 5 | Mine enough blocks for the store's confirmation policy, poll again and check BTCPay. | `confirmed`, settlement kind `selected`, `reservations.owned=false`; invoice is Settled with the invoice amount credited and a "Settled with Payjoin" event. |
| 5 | Mine enough blocks for the store's confirmation policy, poll again and check BTCPay. | `confirmed`, settlement kind `selected`, `reservations.unreleased=false`; invoice is Settled with the invoice amount credited and a "Settled with Payjoin" event. |

Save the invoice ID, `payment_id`, selected txid, fee and a BTCPay status screenshot.

Expand Down
9 changes: 8 additions & 1 deletion doc/payjoin-rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,17 @@ and `available_actions`. `published` and `rejected` record publication results;
| --- | --- |
| `original`, `selected` | Signed original and publication choice; fallback selects original; no PSBTs returned |
| `observed_spend`, `settlement` | Known spend of original inputs; settlement denotes confirmation. A spend matching both transactions has kind `original` |
| `reservations.owned`, `reservations.locks_verified` | Ownership and current in-memory locks; no proof of database persistence |
| `reservations.unreleased` | The service acknowledged reservation registration but has not acknowledged its release; not a fresh owner or lock check |
| `reservations.locks_verified` | Current in-memory locks were verified; no proof of database persistence |
| `possibly_exposed` | Receiver may hold and broadcast the signed original |
| `storage_uncertain` | Uncertain storage result blocks unsafe actions |

Commands recheck ownership before acting. `reservations.unreleased` records the service's
known reservation state, not a fresh check of durable owner records or wallet locks.
With storage uncertainty, `false` does not prove absence of a durable reservation if
registration acknowledgement was lost; `true` does not prove a durable reservation
remains if release succeeded but its acknowledgement was lost.

## Control commands and problems

| Command | Effect |
Expand Down
12 changes: 10 additions & 2 deletions src/wallet/payjoin/sender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <payjoin/client.h>
#include <payjoin/transport.h>
#include <policy/feerate.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <random.h>
Expand Down Expand Up @@ -316,7 +317,7 @@ struct SenderService::State : std::enable_shared_from_this<State> {
view.original_saved = payment.storage.registered;
view.selection_saved = payment.storage.selection_saved;
view.disclosure_saved = payment.storage.disclosure_saved;
view.owns_inputs = payment.storage.registered && !payment.storage.released;
view.has_unreleased_reservation = payment.storage.registered && !payment.storage.released;
view.storage_uncertain = payment.storage.uncertain;
view.locks_verified = payment.locks_verified;
view.possibly_exposed = payment.exposed;
Expand Down Expand Up @@ -1094,7 +1095,14 @@ struct SenderService::State : std::enable_shared_from_this<State> {
return;
}

Publish(payment, MakeTransactionRef(std::move(finalized)));
const auto final_tx = MakeTransactionRef(std::move(finalized));
const CAmount fee = total - *output_value;
if (fee < payment.intent.fee_rate.GetFee(GetVirtualTransactionSize(*final_tx))) {
Fail(payment, PaymentIssue::Signing, "final proposal fee rate is below payment policy");
return;
}

Publish(payment, final_tx);
}

void PublishChecked(Payment& payment, CTransactionRef tx) EXCLUSIVE_LOCKS_REQUIRED(m_wallet.cs_wallet) EXCLUSIVE_LOCKS_REQUIRED(!m_views_mutex)
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/payjoin/sender.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct PaymentSnapshot {
CTransactionRef selected;

bool original_saved{false};
bool owns_inputs{false};
bool has_unreleased_reservation{false};

/** Verification of current in-memory wallet locks, not their database persistence. */
bool locks_verified{false};
Expand Down
4 changes: 2 additions & 2 deletions src/wallet/rpc/payjoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ UniValue PaymentJSON(const PaymentView& view)
if (payment.settlement) result.pushKV("settlement", Observation(*payment.settlement));

UniValue reservations{UniValue::VOBJ};
reservations.pushKV("owned", payment.owns_inputs);
reservations.pushKV("unreleased", payment.has_unreleased_reservation);
reservations.pushKV("locks_verified", payment.locks_verified);
result.pushKV("reservations", std::move(reservations));
result.pushKV("possibly_exposed", payment.possibly_exposed);
Expand Down Expand Up @@ -265,7 +265,7 @@ RPCResult PaymentResult(std::string key)
"reservations",
"Reservation is independent of phase or transaction presence",
{
{RPCResult::Type::BOOL, "owned", "Sender still owns the reserved inputs"},
{RPCResult::Type::BOOL, "unreleased", "The service acknowledged reservation registration but not its release; not a fresh owner or lock check. Commands recheck ownership. With storage uncertainty, false may hide a durable registration and true may outlive a durable release with a lost acknowledgement"},
{RPCResult::Type::BOOL, "locks_verified", "Current wallet in-memory locks were verified"},
},
},
Expand Down
Loading
Loading