+
+ /// Create a one-hop blinded path for a payment.
+ pub fn one_hop_for_payment<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
+ payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, min_final_cltv_expiry_delta: u16,
+ entropy_source: ES, secp_ctx: &Secp256k1<T>
+ ) -> Result<(BlindedPayInfo, Self), ()> where ES::Target: EntropySource {
+ // This value is not considered in pathfinding for 1-hop blinded paths, because it's intended to
+ // be in relation to a specific channel.
+ let htlc_maximum_msat = u64::max_value();
+ Self::new_for_payment(
+ &[], payee_node_id, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta,
+ entropy_source, secp_ctx
+ )
+ }
+
+ /// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`.
+ ///
+ /// Errors if:
+ /// * a provided node id is invalid
+ /// * [`BlindedPayInfo`] calculation results in an integer overflow
+ /// * any unknown features are required in the provided [`ForwardTlvs`]
+ ///
+ /// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
+ // TODO: make all payloads the same size with padding + add dummy hops
+ pub fn new_for_payment<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
+ intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
+ payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
+ entropy_source: ES, secp_ctx: &Secp256k1<T>
+ ) -> Result<(BlindedPayInfo, Self), ()> where ES::Target: EntropySource {
+ let introduction_node = IntroductionNode::NodeId(
+ intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id)
+ );
+ let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
+ let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
+
+ let blinded_payinfo = payment::compute_payinfo(
+ intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
+ )?;
+ Ok((blinded_payinfo, BlindedPath {
+ introduction_node,
+ blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
+ blinded_hops: payment::blinded_hops(
+ secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
+ ).map_err(|_| ())?,
+ }))
+ }
+
+ /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e.,
+ /// it is found in the network graph).
+ pub fn public_introduction_node_id<'a>(
+ &self, network_graph: &'a ReadOnlyNetworkGraph
+ ) -> Option<&'a NodeId> {
+ match &self.introduction_node {
+ IntroductionNode::NodeId(pubkey) => {
+ let node_id = NodeId::from_pubkey(pubkey);
+ network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key)
+ },
+ IntroductionNode::DirectedShortChannelId(direction, scid) => {
+ network_graph
+ .channel(*scid)
+ .map(|c| match direction {
+ Direction::NodeOne => &c.node_one,
+ Direction::NodeTwo => &c.node_two,
+ })
+ },
+ }
+ }
+
+ /// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
+ /// short channel id from a channel in `network_graph` leading to the introduction node.
+ ///
+ /// While this may result in a smaller encoding, there is a trade off in that the path may
+ /// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore,
+ /// calling this may not be suitable for long-lived blinded paths.
+ pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
+ if let IntroductionNode::NodeId(pubkey) = &self.introduction_node {
+ let node_id = NodeId::from_pubkey(pubkey);
+ if let Some(node_info) = network_graph.node(&node_id) {
+ if let Some((scid, channel_info)) = node_info
+ .channels
+ .iter()
+ .filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
+ .min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
+ {
+ let direction = if node_id == channel_info.node_one {
+ Direction::NodeOne
+ } else {
+ debug_assert_eq!(node_id, channel_info.node_two);
+ Direction::NodeTwo
+ };
+ self.introduction_node =
+ IntroductionNode::DirectedShortChannelId(direction, scid);
+ }
+ }
+ }
+ }