Skip to content

Commit 7c6e62f

Browse files
committed
Stateless offer and refund builder utilities
Add utility functions to ChannelManager for creating OfferBuilder, and RefundBuilder such that derived keys are used for the signing pubkey and payer id, respectively. This allows for stateless verification of any InvoiceRequest and Invoice messages. Later, blinded paths can be included in the returned builders. Also tracks future payments using the given PaymentId such that the corresponding Invoice is paid only once.
1 parent 11fb9c4 commit 7c6e62f

File tree

5 files changed

+116
-6
lines changed

5 files changed

+116
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5555
use crate::ln::outbound_payment;
5656
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
5757
use crate::ln::wire::Encode;
58+
use crate::offers::offer::{DerivedMetadata, OfferBuilder};
59+
use crate::offers::parse::Bolt12SemanticError;
60+
use crate::offers::refund::RefundBuilder;
5861
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
5962
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
6063
use crate::util::wakers::{Future, Notifier};
@@ -7123,6 +7126,59 @@ where
71237126
}
71247127
}
71257128

7129+
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
7130+
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
7131+
/// not have an expiration unless otherwise set on the builder.
7132+
///
7133+
/// [`Offer`]: crate::offers::offer::Offer
7134+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
7135+
pub fn create_offer_builder(
7136+
&self, description: String
7137+
) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
7138+
let node_id = self.get_our_node_id();
7139+
let expanded_key = &self.inbound_payment_key;
7140+
let entropy = &*self.entropy_source;
7141+
let secp_ctx = &self.secp_ctx;
7142+
7143+
// TODO: Set blinded paths
7144+
OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
7145+
.chain_hash(self.chain_hash)
7146+
}
7147+
7148+
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
7149+
/// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. The builder will
7150+
/// have the provided expiration set. Any changes to the expiration on the returned builder will
7151+
/// not be honored by [`ChannelManager`].
7152+
///
7153+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the refund.
7154+
///
7155+
/// [`Refund`]: crate::offers::refund::Refund
7156+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
7157+
pub fn create_refund_builder(
7158+
&self, description: String, amount_msats: u64, absolute_expiry: Duration,
7159+
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
7160+
) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError> {
7161+
let node_id = self.get_our_node_id();
7162+
let expanded_key = &self.inbound_payment_key;
7163+
let entropy = &*self.entropy_source;
7164+
let secp_ctx = &self.secp_ctx;
7165+
7166+
// TODO: Set blinded paths
7167+
let builder = RefundBuilder::deriving_payer_id(
7168+
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
7169+
)?
7170+
.chain_hash(self.chain_hash)
7171+
.absolute_expiry(absolute_expiry);
7172+
7173+
self.pending_outbound_payments
7174+
.add_new_awaiting_invoice(
7175+
payment_id, absolute_expiry, retry_strategy, max_total_routing_fee_msat,
7176+
)
7177+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
7178+
7179+
Ok(builder)
7180+
}
7181+
71267182
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
71277183
/// to pay us.
71287184
///

lightning/src/ln/outbound_payment.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,8 +1268,7 @@ impl OutboundPayments {
12681268
(payment, onion_session_privs)
12691269
}
12701270

1271-
#[allow(unused)]
1272-
fn add_new_awaiting_invoice(
1271+
pub(super) fn add_new_awaiting_invoice(
12731272
&self, payment_id: PaymentId, absolute_expiry: Duration, retry_strategy: Retry,
12741273
max_total_routing_fee_msat: Option<u64>
12751274
) -> Result<(), ()> {

lightning/src/offers/offer.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
//! published as a QR code to be scanned by a customer. The customer uses the offer to request an
1414
//! invoice from the merchant to be paid.
1515
//!
16+
//! # Example
17+
//!
1618
//! ```
1719
//! extern crate bitcoin;
1820
//! extern crate core;
@@ -65,6 +67,14 @@
6567
//! # Ok(())
6668
//! # }
6769
//! ```
70+
//!
71+
//! # Note
72+
//!
73+
//! If constructing an [`Offer`] for use with a [`ChannelManager`], use
74+
//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
75+
//!
76+
//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
77+
//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
6878
6979
use bitcoin::blockdata::constants::ChainHash;
7080
use bitcoin::network::constants::Network;
@@ -132,6 +142,14 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
132142
/// while the offer is valid.
133143
///
134144
/// Use a different pubkey per offer to avoid correlating offers.
145+
///
146+
/// # Note
147+
///
148+
/// If constructing an [`Offer`] for use with a [`ChannelManager`], use
149+
/// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
150+
///
151+
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
152+
/// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
135153
pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
136154
OfferBuilder {
137155
offer: OfferContents {
@@ -191,9 +209,18 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
191209
/// See [`Offer::chains`] on how this relates to the payment currency.
192210
///
193211
/// Successive calls to this method will add another chain hash.
194-
pub fn chain(mut self, network: Network) -> Self {
212+
pub fn chain(self, network: Network) -> Self {
213+
self.chain_hash(ChainHash::using_genesis_block(network))
214+
}
215+
216+
/// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
217+
/// [`Network::Bitcoin`] is assumed to be the only one supported.
218+
///
219+
/// See [`Offer::chains`] on how this relates to the payment currency.
220+
///
221+
/// Successive calls to this method will add another chain hash.
222+
pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
195223
let chains = self.offer.chains.get_or_insert_with(Vec::new);
196-
let chain = ChainHash::using_genesis_block(network);
197224
if !chains.contains(&chain) {
198225
chains.push(chain);
199226
}

lightning/src/offers/parse.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ pub enum Bolt12SemanticError {
179179
MissingPayerMetadata,
180180
/// A payer id was expected but was missing.
181181
MissingPayerId,
182+
/// The payment id for a refund or request is already in use.
183+
DuplicatePaymentId,
182184
/// Blinded paths were expected but were missing.
183185
MissingPaths,
184186
/// The blinded payinfo given does not match the number of blinded path hops.

lightning/src/offers/refund.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1919
//! [`Offer`]: crate::offers::offer::Offer
2020
//!
21+
//! # Example
22+
//!
2123
//! ```
2224
//! extern crate bitcoin;
2325
//! extern crate core;
@@ -70,6 +72,14 @@
7072
//! # Ok(())
7173
//! # }
7274
//! ```
75+
//!
76+
//! # Note
77+
//!
78+
//! If constructing a [`Refund`] for use with a [`ChannelManager`], use
79+
//! [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`].
80+
//!
81+
//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
82+
//! [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder
7383
7484
use bitcoin::blockdata::constants::ChainHash;
7585
use bitcoin::network::constants::Network;
@@ -120,6 +130,14 @@ impl<'a> RefundBuilder<'a, secp256k1::SignOnly> {
120130
///
121131
/// Additionally, sets the required [`Refund::description`], [`Refund::payer_metadata`], and
122132
/// [`Refund::amount_msats`].
133+
///
134+
/// # Note
135+
///
136+
/// If constructing a [`Refund`] for use with a [`ChannelManager`], use
137+
/// [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`].
138+
///
139+
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
140+
/// [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder
123141
pub fn new(
124142
description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
125143
) -> Result<Self, Bolt12SemanticError> {
@@ -206,8 +224,16 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
206224
/// called, [`Network::Bitcoin`] is assumed.
207225
///
208226
/// Successive calls to this method will override the previous setting.
209-
pub fn chain(mut self, network: Network) -> Self {
210-
self.refund.chain = Some(ChainHash::using_genesis_block(network));
227+
pub fn chain(self, network: Network) -> Self {
228+
self.chain_hash(ChainHash::using_genesis_block(network))
229+
}
230+
231+
/// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called,
232+
/// [`Network::Bitcoin`] is assumed.
233+
///
234+
/// Successive calls to this method will override the previous setting.
235+
pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
236+
self.refund.chain = Some(chain);
211237
self
212238
}
213239

0 commit comments

Comments
 (0)