Refactor payment_hash to return PaymentHash
What changed, and why it matters
This is a routine code cleanup that changes how a BOLT 11 invoice exposes its payment hash. Previously the function returned a raw SHA-256 hash object; now it returns a typed PaymentHash wrapper. All callers are updated to use the new type. There is no security bug being fixed here and no behavior change to the Lightning protocol logic.
No security action required. Treat as a normal API refactor. Downstream users updating to this commit should adjust any code that called invoice.payment_hash() and expected a &sha256::Hash or manually wrapped it into PaymentHash.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors Bolt11Invoice::payment_hash() in lightning-invoice/src/lib.rs to return PaymentHash instead of &sha256::Hash. It converts the internal sha256::Hash to a byte array and wraps it in PaymentHash. Call sites in channelmanager.rs docs, invoice_utils.rs tests, outbound_payment.rs, and liquidity integration tests are adjusted to remove manual PaymentHash(…) wrapping and .to_byte_array() calls. The change is purely an API ergonomics refactor; the underlying bytes remain identical.
Changed components
lightning-invoice/src/lib.rslightning/src/ln/channelmanager.rs (documentation examples only)lightning/src/ln/invoice_utils.rs (tests only)lightning/src/ln/outbound_payment.rslightning-liquidity/tests/lsps2_integration_tests.rs (tests only)Inspect captured patch +21 / −21
diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs
index d96d730..a83130a 100644
--- a/lightning-invoice/src/lib.rs
+++ b/lightning-invoice/src/lib.rs
@@ -54,7 +54,7 @@ use core::time::Duration;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
#[doc(no_inline)]
-pub use lightning_types::payment::PaymentSecret;
+pub use lightning_types::payment::{PaymentHash, PaymentSecret};
#[doc(no_inline)]
pub use lightning_types::routing::{RouteHint, RouteHintHop, RoutingFees};
use lightning_types::string::UntrustedString;
@@ -1460,8 +1460,9 @@ impl Bolt11Invoice {
}
/// Returns the hash to which we will receive the preimage on completion of the payment
- pub fn payment_hash(&self) -> &sha256::Hash {
- &self.signed_invoice.payment_hash().expect("checked by constructor").0
+ pub fn payment_hash(&self) -> PaymentHash {
+ let hash = self.signed_invoice.payment_hash().expect("checked by constructor").0;
+ PaymentHash(hash.to_byte_array())
}
/// Return the description or a hash of it for longer ones
@@ -2339,7 +2340,7 @@ mod test {
sha256::Hash::from_slice(&[3; 32][..]).unwrap()
))
);
- assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21; 32][..]).unwrap());
+ assert_eq!(invoice.payment_hash(), PaymentHash([21; 32]));
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
let mut expected_features = Bolt11InvoiceFeatures::empty();
diff --git a/lightning-liquidity/tests/lsps2_integration_tests.rs b/lightning-liquidity/tests/lsps2_integration_tests.rs
index e4ace27..2e469d1 100644
--- a/lightning-liquidity/tests/lsps2_integration_tests.rs
+++ b/lightning-liquidity/tests/lsps2_integration_tests.rs
@@ -1211,7 +1211,7 @@ fn client_trusts_lsp_end_to_end_test() {
.node
.pay_for_bolt11_invoice(
&invoice,
- PaymentId(invoice.payment_hash().to_byte_array()),
+ PaymentId(invoice.payment_hash().0),
None,
Default::default(),
Retry::Attempts(3),
@@ -1684,7 +1684,7 @@ fn late_payment_forwarded_and_safe_after_force_close_does_not_broadcast() {
.node
.pay_for_bolt11_invoice(
&invoice,
- PaymentId(invoice.payment_hash().to_byte_array()),
+ PaymentId(invoice.payment_hash().0),
None,
Default::default(),
Retry::Attempts(3),
@@ -1714,7 +1714,7 @@ fn late_payment_forwarded_and_safe_after_force_close_does_not_broadcast() {
*requested_next_hop_scid,
*intercept_id,
*expected_outbound_amount_msat,
- PaymentHash(invoice.payment_hash().to_byte_array()),
+ invoice.payment_hash(),
)
.unwrap();
},
@@ -1875,7 +1875,7 @@ fn htlc_timeout_before_client_claim_results_in_handling_failed() {
.node
.pay_for_bolt11_invoice(
&invoice,
- PaymentId(invoice.payment_hash().to_byte_array()),
+ PaymentId(invoice.payment_hash().0),
None,
Default::default(),
Retry::Attempts(3),
@@ -1905,7 +1905,7 @@ fn htlc_timeout_before_client_claim_results_in_handling_failed() {
*requested_next_hop_scid,
*intercept_id,
*expected_outbound_amount_msat,
- PaymentHash(invoice.payment_hash().to_byte_array()),
+ invoice.payment_hash(),
)
.unwrap();
},
@@ -1984,7 +1984,7 @@ fn htlc_timeout_before_client_claim_results_in_handling_failed() {
match &client_events[0] {
Event::HTLCHandlingFailed { failure_type, .. } => match failure_type {
lightning::events::HTLCHandlingFailureType::Receive { payment_hash } => {
- assert_eq!(*payment_hash, PaymentHash(invoice.payment_hash().to_byte_array()));
+ assert_eq!(*payment_hash, invoice.payment_hash());
},
_ => panic!("Unexpected failure_type: {:?}", failure_type),
},
@@ -2212,7 +2212,7 @@ fn client_trusts_lsp_partial_fee_does_not_trigger_broadcast() {
.node
.pay_for_bolt11_invoice(
&invoice,
- PaymentId(invoice.payment_hash().to_byte_array()),
+ PaymentId(invoice.payment_hash().0),
None,
Default::default(),
Retry::Attempts(3),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index bfaf1e6..9aa86d1 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -2230,7 +2230,7 @@ where
/// match event {
/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
/// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
-/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
+/// assert_eq!(payment_hash, invoice.payment_hash());
/// println!("Claiming payment {}", payment_hash);
/// channel_manager.claim_funds(payment_preimage);
/// },
@@ -2238,7 +2238,7 @@ where
/// println!("Unknown payment hash: {}", payment_hash);
/// },
/// PaymentPurpose::SpontaneousPayment(payment_preimage) => {
-/// assert_ne!(payment_hash.0, invoice.payment_hash().as_ref());
+/// assert_ne!(payment_hash, invoice.payment_hash());
/// println!("Claiming spontaneous payment {}", payment_hash);
/// channel_manager.claim_funds(payment_preimage);
/// },
@@ -2246,7 +2246,7 @@ where
/// # _ => {},
/// },
/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
-/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref());
+/// assert_eq!(payment_hash, invoice.payment_hash());
/// println!("Claimed {} msats", amount_msat);
/// },
/// // ...
@@ -2271,7 +2271,7 @@ where
/// # ) {
/// # let channel_manager = channel_manager.get_cm();
/// # let payment_id = PaymentId([42; 32]);
-/// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
+/// # let payment_hash = invoice.payment_hash();
/// match channel_manager.pay_for_bolt11_invoice(
/// invoice, payment_id, None, route_params_config, retry
/// ) {
diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs
index 425cc4d..e72ea45 100644
--- a/lightning/src/ln/invoice_utils.rs
+++ b/lightning/src/ln/invoice_utils.rs
@@ -627,7 +627,7 @@ mod test {
use crate::util::dyn_signer::{DynKeysInterface, DynPhantomKeysInterface};
use crate::util::test_utils;
use bitcoin::hashes::sha256::Hash as Sha256;
- use bitcoin::hashes::{sha256, Hash};
+ use bitcoin::hashes::Hash;
use bitcoin::network::Network;
use core::time::Duration;
use lightning_invoice::{
@@ -829,7 +829,7 @@ mod test {
invoice.description(),
Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())
);
- assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
+ assert_eq!(invoice.payment_hash(), payment_hash);
}
#[cfg(not(feature = "std"))]
@@ -1257,8 +1257,7 @@ mod test {
Duration::from_secs(genesis_timestamp),
)
.unwrap();
- let (payment_hash, payment_secret) =
- (PaymentHash(invoice.payment_hash().to_byte_array()), *invoice.payment_secret());
+ let (payment_hash, payment_secret) = (invoice.payment_hash(), *invoice.payment_secret());
let payment_preimage = if user_generated_pmt_hash {
user_payment_preimage
} else {
@@ -1290,7 +1289,7 @@ mod test {
invoice.amount_milli_satoshis().unwrap(),
);
- let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array());
+ let payment_hash = invoice.payment_hash();
let id = PaymentId(payment_hash.0);
let onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
nodes[0].node.send_payment(payment_hash, onion, id, params, Retry::Attempts(0)).unwrap();
diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs
index 75fe55b..6549382 100644
--- a/lightning/src/ln/outbound_payment.rs
+++ b/lightning/src/ln/outbound_payment.rs
@@ -933,7 +933,7 @@ where
IH: Fn() -> InFlightHtlcs,
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
- let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
+ let payment_hash = invoice.payment_hash();
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
(Some(amt), None) | (None, Some(amt)) => amt,
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.