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 payjoin-cli/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ mod e2e {

let directory = &services.directory_url().to_string();
// Mock ohttp_relay since the ohttp_relay's http client doesn't have the certificate for the directory
let mock_ohttp_relay = directory;
let mock_ohttp_relay = &services.ohttp_gateway_url().to_string();

let cli_receive_initiator = Command::new(payjoin_cli)
.arg("--rpchost")
Expand Down
3 changes: 3 additions & 0 deletions payjoin-directory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ async fn serve_payjoin_directory(
let path_segments: Vec<&str> = path.split('/').collect();
debug!("serve_payjoin_directory: {:?}", &path_segments);
let mut response = match (parts.method, path_segments.as_slice()) {
(Method::POST, ["", ".well-known", "ohttp-gateway"]) =>
handle_ohttp_gateway(body, pool, ohttp).await,
(Method::GET, ["", ".well-known", "ohttp-gateway"]) => get_ohttp_keys(&ohttp).await,
(Method::POST, ["", ""]) => handle_ohttp_gateway(body, pool, ohttp).await,
(Method::GET, ["", "ohttp-keys"]) => get_ohttp_keys(&ohttp).await,
(Method::POST, ["", id]) => post_fallback_v1(id, query, body, pool).await,
Expand Down
5 changes: 5 additions & 0 deletions payjoin-test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ impl TestServices {
Url::parse(&format!("http://localhost:{}", self.ohttp_relay.0)).expect("invalid URL")
}

pub fn ohttp_gateway_url(&self) -> Url {
let url = self.directory_url();
url.join("/.well-known/ohttp-gateway").expect("invalid URL")
}
Comment on lines +81 to +84
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may make more sense to have our extract_req fns themselves url.join("/.well-known/ohttp-gateway").expect("invalid URL") since the path is the same for all functions assuming RFC 9540 compliance. That way the end user only needs to pass the directory origin and not the complete gateway url.

I suppose this would make it impossible to use our crate with non-RFC 9540-compliant directories, but I think that's our intended behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not actually relevant, as it is the relay which appends that path

Only the key bootstrapping GET request explicitly specifies the RFC 9540 path, which for now is optional since the old /ohttp-keys route is still supported, that functionality accepts the payjoin directory base URI as before.

The reason this method is provided is for the integration tests which bypass the relay.


pub fn take_ohttp_relay_handle(&mut self) -> JoinHandle<Result<(), BoxSendSyncError>> {
self.ohttp_relay.1.take().expect("ohttp relay handle not found")
}
Expand Down
9 changes: 5 additions & 4 deletions payjoin/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! IO-related types and functions. Specifically, fetching OHTTP keys from a payjoin directory.

use http::header::ACCEPT;
use reqwest::{Client, Proxy};

use crate::into_url::IntoUrl;
Expand All @@ -17,10 +18,10 @@ pub async fn fetch_ohttp_keys(
ohttp_relay: impl IntoUrl,
payjoin_directory: impl IntoUrl,
) -> Result<OhttpKeys, Error> {
let ohttp_keys_url = payjoin_directory.into_url()?.join("/ohttp-keys")?;
let ohttp_keys_url = payjoin_directory.into_url()?.join("/.well-known/ohttp-gateway")?;
let proxy = Proxy::all(ohttp_relay.into_url()?.as_str())?;
let client = Client::builder().proxy(proxy).build()?;
let res = client.get(ohttp_keys_url).send().await?;
let res = client.get(ohttp_keys_url).header(ACCEPT, "application/ohttp-keys").send().await?;
Copy link
Copy Markdown
Contributor

@DanGould DanGould Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mention in the commit messages that this isn't checked anywhere in the directory. Is there a plan to require that? Seems to not make a huge difference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it seems needlessly pedantic to me, but i thought it was worth mentioning in case anyone disagrees

let body = res.bytes().await?.to_vec();
OhttpKeys::decode(&body).map_err(|e| Error(InternalError::InvalidOhttpKeys(e.to_string())))
}
Expand All @@ -41,14 +42,14 @@ pub async fn fetch_ohttp_keys_with_cert(
payjoin_directory: impl IntoUrl,
cert_der: Vec<u8>,
) -> Result<OhttpKeys, Error> {
let ohttp_keys_url = payjoin_directory.into_url()?.join("/ohttp-keys")?;
let ohttp_keys_url = payjoin_directory.into_url()?.join("/.well-known/ohttp-gateway")?;
let proxy = Proxy::all(ohttp_relay.into_url()?.as_str())?;
let client = Client::builder()
.use_rustls_tls()
.add_root_certificate(reqwest::tls::Certificate::from_der(&cert_der)?)
.proxy(proxy)
.build()?;
let res = client.get(ohttp_keys_url).send().await?;
let res = client.get(ohttp_keys_url).header(ACCEPT, "application/ohttp-keys").send().await?;
let body = res.bytes().await?.to_vec();
OhttpKeys::decode(&body).map_err(|e| Error(InternalError::InvalidOhttpKeys(e.to_string())))
}
Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/ohttp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn ohttp_encapsulate(
}

let mut bhttp_req = [0u8; PADDED_BHTTP_REQ_BYTES];
let _ = bhttp_message.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_req.as_mut_slice());
bhttp_message.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_req.as_mut_slice())?;
let (encapsulated, ohttp_ctx) = ctx.encapsulate(&bhttp_req)?;

let mut buffer = [0u8; ENCAPSULATED_MESSAGE_BYTES];
Expand Down
8 changes: 4 additions & 4 deletions payjoin/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ mod integration {
let agent = services.http_agent();
services.wait_for_services_ready().await?;
let directory = services.directory_url();
let mock_ohttp_relay = directory.clone(); // pass through to directory
let mock_ohttp_relay = services.ohttp_gateway_url();
let mock_address = Address::from_str("tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4")?
.assume_checked();
let mut bad_initializer =
Expand Down Expand Up @@ -228,7 +228,7 @@ mod integration {
let (_bitcoind, sender, receiver) = init_bitcoind_sender_receiver(None, None)?;
services.wait_for_services_ready().await?;
let directory = services.directory_url();
let ohttp_relay = services.ohttp_relay_url();
let ohttp_relay = services.ohttp_gateway_url();
let ohttp_keys = services.fetch_ohttp_keys().await?;
// **********************
// Inside the Receiver:
Expand Down Expand Up @@ -290,7 +290,7 @@ mod integration {
Receiver::new(address.clone(), directory.clone(), ohttp_keys.clone(), None)?;
println!("session: {:#?}", &session);
// Poll receive request
let mock_ohttp_relay = directory.clone();
let mock_ohttp_relay = services.ohttp_gateway_url();
let (req, ctx) = session.extract_req(&mock_ohttp_relay)?;
let response = agent.post(req.url).body(req.body).send().await?;
assert!(response.status().is_success(), "error response: {}", response.status());
Expand Down Expand Up @@ -483,7 +483,7 @@ mod integration {
let agent_clone: Arc<Client> = agent.clone();
let receiver: Arc<bitcoincore_rpc::Client> = Arc::new(receiver);
let receiver_clone = receiver.clone();
let mock_ohttp_relay = directory.clone();
let mock_ohttp_relay = services.ohttp_gateway_url();
let receiver_loop = tokio::task::spawn(async move {
let agent_clone = agent_clone.clone();
let (response, ctx) = loop {
Expand Down