Skip to content

Commit f202098

Browse files
authored
Fix unresumable Sender (payjoin#443)
The Sender was generating new hpke keys on resume since keygen was being done in `extract_v2`. This caused a problem where when the Sender was persisted, stopped, and resumed, the receiver would have already pushed a response to a different ShortId than the one the Sender would look for it in the resumed state. By generating a key on Sender creation and persisting that the Sender can produce a consistent HpkeContext every single run. The e2e test did not catch this because it was not propagating errors. The e2e test has been fixed.
2 parents b9cb530 + 86a9a8a commit f202098

File tree

8 files changed

+28
-11
lines changed

8 files changed

+28
-11
lines changed

Cargo-minimal.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
15771577

15781578
[[package]]
15791579
name = "payjoin"
1580-
version = "0.21.0"
1580+
version = "0.22.0"
15811581
dependencies = [
15821582
"bhttp",
15831583
"bitcoin",

Cargo-recent.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
15771577

15781578
[[package]]
15791579
name = "payjoin"
1580-
version = "0.21.0"
1580+
version = "0.22.0"
15811581
dependencies = [
15821582
"bhttp",
15831583
"bitcoin",

payjoin-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ hyper = { version = "1", features = ["http1", "server"], optional = true }
3737
hyper-rustls = { version = "0.26", optional = true }
3838
hyper-util = { version = "0.1", optional = true }
3939
log = "0.4.7"
40-
payjoin = { version = "0.21.0", features = ["send", "receive", "base64"] }
40+
payjoin = { version = "0.22.0", features = ["send", "receive", "base64"] }
4141
rcgen = { version = "0.11.1", optional = true }
4242
reqwest = { version = "0.12", default-features = false }
4343
rustls = { version = "0.22.4", optional = true }

payjoin-cli/tests/e2e.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ mod e2e {
293293
.stderr(Stdio::inherit())
294294
.spawn()
295295
.expect("Failed to execute payjoin-cli");
296-
let _ = send_until_request_timeout(cli_send_initiator).await;
296+
send_until_request_timeout(cli_send_initiator).await?;
297297

298298
let cli_receive_resumer = Command::new(payjoin_cli)
299299
.arg("--rpchost")
@@ -309,7 +309,7 @@ mod e2e {
309309
.stderr(Stdio::inherit())
310310
.spawn()
311311
.expect("Failed to execute payjoin-cli");
312-
let _ = respond_with_payjoin(cli_receive_resumer).await;
312+
respond_with_payjoin(cli_receive_resumer).await?;
313313

314314
let cli_send_resumer = Command::new(payjoin_cli)
315315
.arg("--rpchost")
@@ -328,7 +328,7 @@ mod e2e {
328328
.stderr(Stdio::inherit())
329329
.spawn()
330330
.expect("Failed to execute payjoin-cli");
331-
let _ = check_payjoin_sent(cli_send_resumer).await;
331+
check_payjoin_sent(cli_send_resumer).await?;
332332
Ok(())
333333
}
334334

payjoin/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Payjoin Changelog
22

3+
## 0.22.0
4+
5+
- Propagate Uri Fragment parameter errors to the caller
6+
- Have `Sender` to persist reply key so resumption listens where a previous sender left off
7+
38
## 0.21.0
49

510
- Upgrade rustls v0.22.4

payjoin/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "payjoin"
3-
version = "0.21.0"
3+
version = "0.22.0"
44
authors = ["Dan Gould <[email protected]>"]
55
description = "Payjoin Library for the BIP78 Pay to Endpoint protocol."
66
repository = "https://github.com/payjoin/rust-payjoin"

payjoin/src/hpke.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ impl From<HpkeKeyPair> for (HpkeSecretKey, HpkePublicKey) {
3131
}
3232

3333
impl HpkeKeyPair {
34+
pub fn from_secret_key(secret_key: &HpkeSecretKey) -> Self {
35+
let public_key = <SecpK256HkdfSha256 as hpke::Kem>::sk_to_pk(&secret_key.0);
36+
Self(secret_key.clone(), HpkePublicKey(public_key))
37+
}
38+
3439
pub fn gen_keypair() -> Self {
3540
let (sk, pk) = <SecpK256HkdfSha256 as hpke::Kem>::gen_keypair(&mut OsRng);
3641
Self(HpkeSecretKey(sk), HpkePublicKey(pk))

payjoin/src/send/mod.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ use serde::{Deserialize, Serialize};
3434
use url::Url;
3535

3636
#[cfg(feature = "v2")]
37-
use crate::hpke::{decrypt_message_b, encrypt_message_a, HpkeKeyPair, HpkePublicKey};
37+
use crate::hpke::{
38+
decrypt_message_b, encrypt_message_a, HpkeKeyPair, HpkePublicKey, HpkeSecretKey,
39+
};
3840
#[cfg(feature = "v2")]
3941
use crate::ohttp::{ohttp_decapsulate, ohttp_encapsulate};
4042
use crate::psbt::PsbtExt;
@@ -228,6 +230,8 @@ impl<'a> SenderBuilder<'a> {
228230
fee_contribution,
229231
payee,
230232
min_fee_rate: self.min_fee_rate,
233+
#[cfg(feature = "v2")]
234+
reply_key: HpkeKeyPair::gen_keypair().0,
231235
})
232236
}
233237
}
@@ -246,6 +250,8 @@ pub struct Sender {
246250
min_fee_rate: FeeRate,
247251
/// Script of the person being paid
248252
payee: ScriptBuf,
253+
#[cfg(feature = "v2")]
254+
reply_key: HpkeSecretKey,
249255
}
250256

251257
impl Sender {
@@ -298,7 +304,7 @@ impl Sender {
298304
self.fee_contribution,
299305
self.min_fee_rate,
300306
)?;
301-
let hpke_ctx = HpkeContext::new(rs);
307+
let hpke_ctx = HpkeContext::new(rs, &self.reply_key);
302308
let body = encrypt_message_a(
303309
body,
304310
&hpke_ctx.reply_pair.public_key().clone(),
@@ -475,8 +481,8 @@ struct HpkeContext {
475481

476482
#[cfg(feature = "v2")]
477483
impl HpkeContext {
478-
pub fn new(receiver: HpkePublicKey) -> Self {
479-
Self { receiver, reply_pair: HpkeKeyPair::gen_keypair() }
484+
pub fn new(receiver: HpkePublicKey, reply_key: &HpkeSecretKey) -> Self {
485+
Self { receiver, reply_pair: HpkeKeyPair::from_secret_key(reply_key) }
480486
}
481487
}
482488

@@ -975,6 +981,7 @@ mod test {
975981
fee_contribution: None,
976982
min_fee_rate: FeeRate::ZERO,
977983
payee: ScriptBuf::from(vec![0x00]),
984+
reply_key: HpkeKeyPair::gen_keypair().0,
978985
};
979986
let serialized = serde_json::to_string(&req_ctx).unwrap();
980987
let deserialized = serde_json::from_str(&serialized).unwrap();

0 commit comments

Comments
 (0)