Skip to content

Commit

Permalink
Merge pull request lightningdevkit#2578 from jkczyz/2023-09-offer-uti…
Browse files Browse the repository at this point in the history
…lities

BOLT 12 Offers utilities
  • Loading branch information
TheBlueMatt authored Oct 19, 2023
2 parents 0357caf + 905028b commit 1852715
Show file tree
Hide file tree
Showing 16 changed files with 483 additions and 252 deletions.
20 changes: 5 additions & 15 deletions fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::util::test_channel_signer::TestChannelSigner;
use lightning::util::logger::Logger;
use lightning::util::ser::{Readable, Writeable, Writer};
use lightning::onion_message::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessagePath, OnionMessenger};
use lightning::onion_message::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage};

use crate::utils::test_logger;

Expand Down Expand Up @@ -84,7 +84,7 @@ struct TestCustomMessage {}
const CUSTOM_MESSAGE_TYPE: u64 = 4242;
const CUSTOM_MESSAGE_CONTENTS: [u8; 32] = [42; 32];

impl CustomOnionMessageContents for TestCustomMessage {
impl OnionMessageContents for TestCustomMessage {
fn tlv_type(&self) -> u64 {
CUSTOM_MESSAGE_TYPE
}
Expand All @@ -108,6 +108,9 @@ impl CustomOnionMessageHandler for TestCustomMessageHandler {
buffer.read_to_end(&mut buf)?;
return Ok(Some(TestCustomMessage {}))
}
fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<Self::CustomMessage>> {
vec![]
}
}

pub struct VecWriter(pub Vec<u8>);
Expand Down Expand Up @@ -208,19 +211,6 @@ mod tests {

#[test]
fn test_no_onion_message_breakage() {
let one_hop_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e01ae0276020000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000e0101022a0000000000000000000000000000014551231950b75fc4402da1732fc9bebf00109500000000000000000000000000000004106d000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005600000000000000000000000000000000000000000000000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(one_hop_om).unwrap(), &logger);
{
let log_entries = logger.lines.lock().unwrap();
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
"Received an onion message with path_id None and a reply_path".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
"Responding to onion message with path_id None".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
"Failed responding to onion message with path_id None: TooFewBlindedHops".to_string())), Some(&1));
}

let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000000000000000000000000000000000000000000000000000036041096000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(two_unblinded_hops_om).unwrap(), &logger);
Expand Down
17 changes: 12 additions & 5 deletions lightning/src/blinded_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,22 @@ pub struct BlindedHop {
}

impl BlindedPath {
/// Create a one-hop blinded path for a message.
pub fn one_hop_for_message<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
recipient_node_id: PublicKey, entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Self, ()> {
Self::new_for_message(&[recipient_node_id], entropy_source, secp_ctx)
}

/// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node
/// pubkey in `node_pks` will be the destination node.
///
/// Errors if less than two hops are provided or if `node_pk`(s) are invalid.
/// Errors if no hops are provided or if `node_pk`(s) are invalid.
// TODO: make all payloads the same size with padding + add dummy hops
pub fn new_for_message<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>
(node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
{
if node_pks.len() < 2 { return Err(()) }
pub fn new_for_message<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Self, ()> {
if node_pks.is_empty() { return Err(()) }
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
let introduction_node_id = node_pks[0];
Expand Down
6 changes: 0 additions & 6 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1846,12 +1846,6 @@ pub trait MessageSendEventsProvider {
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent>;
}

/// A trait indicating an object may generate onion messages to send
pub trait OnionMessageProvider {
/// Gets the next pending onion message for the peer with the given node id.
fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<msgs::OnionMessage>;
}

/// A trait indicating an object may generate events.
///
/// Events are processed by passing an [`EventHandler`] to [`process_pending_events`].
Expand Down
94 changes: 93 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use bitcoin::secp256k1::{SecretKey,PublicKey};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{LockTime, secp256k1, Sequence};

use crate::blinded_path::BlindedPath;
use crate::chain;
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
Expand All @@ -55,6 +56,9 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
use crate::ln::outbound_payment;
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
use crate::ln::wire::Encode;
use crate::offers::offer::{DerivedMetadata, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::RefundBuilder;
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
use crate::util::wakers::{Future, Notifier};
Expand Down Expand Up @@ -4791,6 +4795,10 @@ where
/// with the current [`ChannelConfig`].
/// * Removing peers which have disconnected but and no longer have any channels.
/// * Force-closing and removing channels which have not completed establishment in a timely manner.
/// * Forgetting about stale outbound payments, either those that have already been fulfilled
/// or those awaiting an invoice that hasn't been delivered in the necessary amount of time.
/// The latter is determined using the system clock in `std` and the block time minus two
/// hours in `no-std`.
///
/// Note that this may cause reentrancy through [`chain::Watch::update_channel`] calls or feerate
/// estimate fetches.
Expand Down Expand Up @@ -5019,7 +5027,18 @@ where
self.finish_close_channel(shutdown_res);
}

self.pending_outbound_payments.remove_stale_payments(&self.pending_events);
#[cfg(feature = "std")]
let duration_since_epoch = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
#[cfg(not(feature = "std"))]
let duration_since_epoch = Duration::from_secs(
self.highest_seen_timestamp.load(Ordering::Acquire).saturating_sub(7200) as u64
);

self.pending_outbound_payments.remove_stale_payments(
duration_since_epoch, &self.pending_events
);

// Technically we don't need to do this here, but if we have holding cell entries in a
// channel that need freeing, it's better to do that here and block a background task
Expand Down Expand Up @@ -7108,6 +7127,71 @@ where
}
}

/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
/// not have an expiration unless otherwise set on the builder.
///
/// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the
/// introduction node and a derived signing pubkey for recipient privacy. As such, currently,
/// the node must be announced. Otherwise, there is no way to find a path to the introduction
/// node in order to send the [`InvoiceRequest`].
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn create_offer_builder(
&self, description: String
) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
let path = self.create_one_hop_blinded_path();

OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
.chain_hash(self.chain_hash)
.path(path)
}

/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
/// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. The builder will
/// have the provided expiration set. Any changes to the expiration on the returned builder will
/// not be honored by [`ChannelManager`].
///
/// The provided `payment_id` is used to ensure that only one invoice is paid for the refund.
///
/// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as
/// the introduction node and a derived payer id for sender privacy. As such, currently, the
/// node must be announced. Otherwise, there is no way to find a path to the introduction node
/// in order to send the [`Bolt12Invoice`].
///
/// [`Refund`]: crate::offers::refund::Refund
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn create_refund_builder(
&self, description: String, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError> {
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
let path = self.create_one_hop_blinded_path();

let builder = RefundBuilder::deriving_payer_id(
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
)?
.chain_hash(self.chain_hash)
.absolute_expiry(absolute_expiry)
.path(path);

self.pending_outbound_payments
.add_new_awaiting_invoice(
payment_id, absolute_expiry, retry_strategy, max_total_routing_fee_msat,
)
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;

Ok(builder)
}

/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
/// to pay us.
///
Expand Down Expand Up @@ -7208,6 +7292,14 @@ where
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}

/// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
/// node.
fn create_one_hop_blinded_path(&self) -> BlindedPath {
let entropy_source = self.entropy_source.deref();
let secp_ctx = &self.secp_ctx;
BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
}

/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
/// are used when constructing the phantom invoice's route hints.
///
Expand Down
11 changes: 8 additions & 3 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use core::str::FromStr;
use crate::io::{self, Cursor, Read};
use crate::io_extras::read_to_end;

use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
use crate::events::MessageSendEventsProvider;
use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
use crate::util::logger;
use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
Expand Down Expand Up @@ -1497,17 +1497,22 @@ pub trait RoutingMessageHandler : MessageSendEventsProvider {
fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures;
}

/// A trait to describe an object that can receive onion messages.
pub trait OnionMessageHandler : OnionMessageProvider {
/// A handler for received [`OnionMessage`]s and for providing generated ones to send.
pub trait OnionMessageHandler {
/// Handle an incoming `onion_message` message from the given peer.
fn handle_onion_message(&self, peer_node_id: &PublicKey, msg: &OnionMessage);

/// Returns the next pending onion message for the peer with the given node id.
fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<OnionMessage>;

/// Called when a connection is established with a peer. Can be used to track which peers
/// advertise onion message support and are online.
///
/// May return an `Err(())` if the features the peer supports are not sufficient to communicate
/// with us. Implementors should be somewhat conservative about doing so, however, as other
/// message handlers may still wish to communicate with this peer.
fn peer_connected(&self, their_node_id: &PublicKey, init: &Init, inbound: bool) -> Result<(), ()>;

/// Indicates a connection to the peer failed/an existing connection was lost. Allows handlers to
/// drop and refuse to forward onion messages to this peer.
fn peer_disconnected(&self, their_node_id: &PublicKey);
Expand Down
Loading

0 comments on commit 1852715

Please sign in to comment.