From 04f1225747224a07b07c6838a349b3be78eca59a Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Sat, 9 May 2026 13:36:39 -0300 Subject: [PATCH 1/5] document ohttp relay best practice in payjoin lib --- payjoin/src/core/io.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/payjoin/src/core/io.rs b/payjoin/src/core/io.rs index 2cbc0010b..2585434fa 100644 --- a/payjoin/src/core/io.rs +++ b/payjoin/src/core/io.rs @@ -1,4 +1,40 @@ //! IO-related types and functions. Specifically, fetching OHTTP keys from a payjoin directory. +//! +//! When multiple relays are configured, callers **should pick one at random per request** +//! to avoid a fixed contact pattern at the network layer. +//! +//! Random selection only helps if the relay list itself is not identifying: prefer a shared +//! relay list and discourage isolated infrastructure that other apps don't use, since a +//! distinctive list fingerprints the client regardless of how a relay is picked from it. +//! +//! Sender and receiver have distinct request patterns: +//! - Receiver: long-poll GETs, then a POST +//! - Sender: a POST, then long-poll GETs +//! +//! OHTTP does not hide the client IP from the relay. A relay that sees the same +//! client repeatedly can observe its access patterns to infer whether +//! the IP is associated with a sender or receiver, potentially linking to identity or +//! location. Based on when a session ends it may be easier to correctly guess +//! whether a transaction is a PayJoin. The IP address linked information may +//! additionally aid in cluster analysis, for example whether a cluster's temporal +//! patterns are consistent with a location guess for the IP address. +//! +//! ## Health checks +//! +//! Some clients call [`fetch_ohttp_keys`] periodically to verify that the +//! directory and relay infrastructure is reachable. Given the threat model +//! above, this is acceptable only when: +//! +//! - The call is **not** triggered on any deterministic, recurring event +//! (e.g. app startup, periodic timer). Prefer user-initiated actions +//! (e.g. opening a settings/status screen) or piggybacking on operations +//! the user already triggered (e.g. resuming an existing session). +//! - The caller throttles invocations so they don't produce a recurring +//! timing pattern observable by the relay. +//! +//! A health check has a distinct traffic pattern from a real payjoin request +//! and is not temporally tied to any onchain broadcast, but repeated calls +//! still expose the client IP to the relay. use std::time::Duration; use http::header::ACCEPT; From 7f599bf63d8ccd38f82661492aa340ca6b63903d Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Mon, 25 May 2026 19:54:58 -0300 Subject: [PATCH 2/5] Document relay best practice in Python binding --- payjoin-ffi/python/src/payjoin/http.py | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/payjoin-ffi/python/src/payjoin/http.py b/payjoin-ffi/python/src/payjoin/http.py index 70c391700..019d45c40 100644 --- a/payjoin-ffi/python/src/payjoin/http.py +++ b/payjoin-ffi/python/src/payjoin/http.py @@ -1,3 +1,42 @@ +"""Fetch OHTTP keys from a payjoin directory through an OHTTP relay. + +When multiple relays are configured, callers **should pick one at random per request** +to avoid a fixed contact pattern at the network layer. + +Random selection only helps if the relay list itself is not identifying: prefer a shared +relay list and discourage isolated infrastructure that other apps don't use, since a +distinctive list fingerprints the client regardless of how a relay is picked from it. + +Sender and receiver have distinct request patterns: +- Receiver: long-poll GETs, then a POST +- Sender: a POST, then long-poll GETs + +OHTTP does not hide the client IP from the relay. A relay that sees the same +client repeatedly can observe its access patterns to infer whether +the IP is associated with a sender or receiver, potentially linking to identity or +location. Based on when a session ends it may be easier to correctly guess +whether a transaction is a PayJoin. The IP address linked information may +additionally aid in cluster analysis, for example whether a cluster's temporal +patterns are consistent with a location guess for the IP address. + +## Health checks + +Some clients call `fetch_ohttp_keys` periodically to verify that the +directory and relay infrastructure is reachable. Given the threat model +above, this is acceptable only when: + +- The call is **not** triggered on any deterministic, recurring event + (e.g. app startup, periodic timer). Prefer user-initiated actions + (e.g. opening a settings/status screen) or piggybacking on operations + the user already triggered (e.g. resuming an existing session). +- The caller throttles invocations so they don't produce a recurring + timing pattern observable by the relay. + +A health check has a distinct traffic pattern from a real payjoin request +and is not temporally tied to any onchain broadcast, but repeated calls +still expose the client IP to the relay. +""" + import ssl from urllib.parse import urljoin From c62e4b2f7c8c6e0e5a83c430a5e4bfce61b1401c Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Mon, 25 May 2026 19:55:21 -0300 Subject: [PATCH 3/5] Document relay best practice in Dart binding --- payjoin-ffi/dart/lib/http.dart | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/payjoin-ffi/dart/lib/http.dart b/payjoin-ffi/dart/lib/http.dart index f313f2d43..e6d10d991 100644 --- a/payjoin-ffi/dart/lib/http.dart +++ b/payjoin-ffi/dart/lib/http.dart @@ -1,3 +1,40 @@ +/// Fetch OHTTP keys from a payjoin directory through an OHTTP relay. +/// +/// When multiple relays are configured, callers **should pick one at random per request** +/// to avoid a fixed contact pattern at the network layer. +/// +/// Random selection only helps if the relay list itself is not identifying: prefer a shared +/// relay list and discourage isolated infrastructure that other apps don't use, since a +/// distinctive list fingerprints the client regardless of how a relay is picked from it. +/// +/// Sender and receiver have distinct request patterns: +/// - Receiver: long-poll GETs, then a POST +/// - Sender: a POST, then long-poll GETs +/// +/// OHTTP does not hide the client IP from the relay. A relay that sees the same +/// client repeatedly can observe its access patterns to infer whether +/// the IP is associated with a sender or receiver, potentially linking to identity or +/// location. Based on when a session ends it may be easier to correctly guess +/// whether a transaction is a PayJoin. The IP address linked information may +/// additionally aid in cluster analysis, for example whether a cluster's temporal +/// patterns are consistent with a location guess for the IP address. +/// +/// ## Health checks +/// +/// Some clients call `fetchOhttpKeys` periodically to verify that the +/// directory and relay infrastructure is reachable. Given the threat model +/// above, this is acceptable only when: +/// +/// - The call is **not** triggered on any deterministic, recurring event +/// (e.g. app startup, periodic timer). Prefer user-initiated actions +/// (e.g. opening a settings/status screen) or piggybacking on operations +/// the user already triggered (e.g. resuming an existing session). +/// - The caller throttles invocations so they don't produce a recurring +/// timing pattern observable by the relay. +/// +/// A health check has a distinct traffic pattern from a real payjoin request +/// and is not temporally tied to any onchain broadcast, but repeated calls +/// still expose the client IP to the relay. library http; import 'dart:async'; From e4d628a68b309cabf0af5c6417b74830244e069a Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Mon, 25 May 2026 19:57:49 -0300 Subject: [PATCH 4/5] Document relay best practice in C# binding --- payjoin-ffi/csharp/Payjoin.Http.cs | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/payjoin-ffi/csharp/Payjoin.Http.cs b/payjoin-ffi/csharp/Payjoin.Http.cs index 0ce5136ca..f32e631fc 100644 --- a/payjoin-ffi/csharp/Payjoin.Http.cs +++ b/payjoin-ffi/csharp/Payjoin.Http.cs @@ -6,6 +6,43 @@ namespace Payjoin.Http { + /// + /// Fetch OHTTP keys from a payjoin directory through an OHTTP relay. + /// + /// + /// When multiple relays are configured, callers should pick one at random per request + /// to avoid a fixed contact pattern at the network layer. + /// + /// Random selection only helps if the relay list itself is not identifying: prefer a shared + /// relay list and discourage isolated infrastructure that other apps don't use, since a + /// distinctive list fingerprints the client regardless of how a relay is picked from it. + /// + /// Sender and receiver have distinct request patterns: + /// - Receiver: long-poll GETs, then a POST + /// - Sender: a POST, then long-poll GETs + /// + /// OHTTP does not hide the client IP from the relay. A relay that sees the same + /// client repeatedly can observe its access patterns to infer whether + /// the IP is associated with a sender or receiver, potentially linking to identity or + /// location. Based on when a session ends it may be easier to correctly guess + /// whether a transaction is a PayJoin. The IP address linked information may + /// additionally aid in cluster analysis, for example whether a cluster's temporal + /// patterns are consistent with a location guess for the IP address. + /// + /// Health checks: some clients call periodically to + /// verify that the directory and relay infrastructure is reachable. Given the threat + /// model above, this is acceptable only when: + /// - The call is not triggered on any deterministic, recurring event + /// (e.g. app startup, periodic timer). Prefer user-initiated actions + /// (e.g. opening a settings/status screen) or piggybacking on operations + /// the user already triggered (e.g. resuming an existing session). + /// - The caller throttles invocations so they don't produce a recurring + /// timing pattern observable by the relay. + /// + /// A health check has a distinct traffic pattern from a real payjoin request + /// and is not temporally tied to any onchain broadcast, but repeated calls + /// still expose the client IP to the relay. + /// internal sealed class OhttpKeysClient : IDisposable { private readonly HttpClient _client; From cd7d70bc18f288d17d189f58ad81698cfb11eca3 Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Mon, 25 May 2026 20:02:52 -0300 Subject: [PATCH 5/5] Reference payjoin::io relay guidance in cli doc --- payjoin-cli/src/app/v2/ohttp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/payjoin-cli/src/app/v2/ohttp.rs b/payjoin-cli/src/app/v2/ohttp.rs index e438540ec..b4a227284 100644 --- a/payjoin-cli/src/app/v2/ohttp.rs +++ b/payjoin-cli/src/app/v2/ohttp.rs @@ -1,5 +1,7 @@ //! OHTTP relay and payjoin directory selection / key bootstrapping for the payjoin-cli. //! +//! See the `payjoin::io` module for relay-selection and health-check guidance. +//! //! [`MailroomManager`] tracks relays and directories that have failed, //! excluding them from future selections for the lifetime of the [`MailroomManager`]. //!