publish = false
# Because the function is unused it gets dropped before we link lightning, so
# we have to duplicate build.rs here. Note that this is only required for
-# fuzztarget mode.
+# fuzzing mode.
[package.metadata]
cargo-fuzz = true
[dependencies]
afl = { version = "0.4", optional = true }
-lightning = { path = "../lightning", features = ["fuzztarget"] }
+lightning = { path = "../lightning", features = ["regex"] }
bitcoin = { version = "0.27", features = ["fuzztarget", "secp-lowmemory"] }
hex = "0.3"
honggfuzz = { version = "0.5", optional = true }
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::chanmon_consistency::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::chanmon_deser::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::full_stack::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_accept_channel::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_announcement_signatures::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_channel_announcement::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_channel_reestablish::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_channel_update::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_closing_signed::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_commitment_signed::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_decoded_onion_error_packet::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_error_message::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_funding_created::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_funding_locked::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_funding_signed::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_gossip_timestamp_filter::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_init::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_node_announcement::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_onion_hop_data::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_open_channel::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_ping::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_pong::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_query_channel_range::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_query_short_channel_ids::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_reply_channel_range::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_reply_short_channel_ids_end::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_revoke_and_ack::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_shutdown::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_update_add_htlc::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_update_fail_htlc::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_update_fail_malformed_htlc::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_update_fee::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::msg_targets::msg_update_fulfill_htlc::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::peer_crypt::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::router::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::TARGET_MOD::*;
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
extern crate lightning_fuzz;
use lightning_fuzz::zbase32::*;
use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent};
use lightning::chain::transaction::OutPoint;
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
-use lightning::chain::keysinterface::{KeyMaterial, KeysInterface, InMemorySigner};
+use lightning::chain::keysinterface::{KeyMaterial, KeysInterface, InMemorySigner, Recipient};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChainParameters, ChannelManager, PaymentSendFailure, ChannelManagerReadArgs};
use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
impl KeysInterface for KeyProvider {
type Signer = EnforcingSigner;
- fn get_node_secret(&self) -> SecretKey {
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap()
+ fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
+ Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap())
}
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed);
let keys = InMemorySigner::new(
&secp_ctx,
- self.get_node_secret(),
+ self.get_node_secret(Recipient::Node).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_id]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_id]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_id]).unwrap(),
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, DecodeError> {
let mut reader = std::io::Cursor::new(buffer);
- let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret())?;
+ let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret(Recipient::Node).unwrap())?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
Ok(EnforcingSigner {
})
}
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
+ fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chainmonitor;
use lightning::chain::transaction::OutPoint;
-use lightning::chain::keysinterface::{InMemorySigner, KeyMaterial, KeysInterface};
+use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial, KeysInterface};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChainParameters, ChannelManager};
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,IgnoringMessageHandler};
impl KeysInterface for KeyProvider {
type Signer = EnforcingSigner;
- fn get_node_secret(&self) -> SecretKey {
- self.node_secret.clone()
+ fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
+ Ok(self.node_secret.clone())
}
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
))
}
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
+ fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}
best_block: BestBlock::from_genesis(network),
};
let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), Arc::clone(&logger), keys_manager.clone(), config, params));
- let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret());
+ keys_manager.counter.fetch_sub(1, Ordering::AcqRel);
+ let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash()));
let net_graph_msg_handler = Arc::new(NetGraphMsgHandler::new(Arc::clone(&network_graph), None, Arc::clone(&logger)));
let scorer = FixedPenaltyScorer::with_penalty(0);
use bitcoin::network::constants::Network;
use lightning::chain::{BestBlock, Confirm, chainmonitor};
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
- use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager};
+ use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeysInterface, KeysManager};
use lightning::chain::transaction::OutPoint;
use lightning::get_event_msg;
use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, ChannelManager, SimpleArcChannelManager};
let network_graph = Arc::new(NetworkGraph::new(genesis_block.header.block_hash()));
let net_graph_msg_handler = Some(Arc::new(NetGraphMsgHandler::new(network_graph.clone(), Some(chain_source.clone()), logger.clone())));
let msg_handler = MessageHandler { chan_handler: Arc::new(test_utils::TestChannelMessageHandler::new()), route_handler: Arc::new(test_utils::TestRoutingMessageHandler::new() )};
- let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(), &seed, logger.clone(), IgnoringMessageHandler{}));
+ let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(Recipient::Node).unwrap(), &seed, logger.clone(), IgnoringMessageHandler{}));
let node = Node { node: manager, net_graph_msg_handler, peer_manager, chain_monitor, persister, tx_broadcaster, network_graph, logger, best_block };
nodes.push(node);
}
chunked_transfer = { version = "1.4", optional = true }
[dev-dependencies]
-tokio = { version = "1.0", features = [ "macros", "rt" ] }
+tokio = { version = "~1.14", features = [ "macros", "rt" ] }
[dependencies]
honggfuzz = { version = "0.5", optional = true }
afl = { version = "0.4", optional = true }
-lightning-invoice = { path = ".."}
+lightning-invoice = { path = ".." }
+lightning = { path = "../../lightning", features = ["regex"] }
bech32 = "0.8"
# Prevent this from interfering with workspaces
/// The supplied millisatoshi amount was greater than the total bitcoin supply.
InvalidAmount,
+
+ /// Route hints were required for this invoice and were missing. Applies to
+ /// [phantom invoices].
+ ///
+ /// [phantom invoices]: crate::utils::create_phantom_invoice
+ MissingRouteHints,
}
impl Display for CreationError {
CreationError::RouteTooLong => f.write_str("The specified route has too many hops and can't be encoded"),
CreationError::TimestampOutOfBounds => f.write_str("The Unix timestamp of the supplied date is less than zero or greater than 35-bits"),
CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"),
+ CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"),
}
}
}
use crate::prelude::*;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
-use lightning::chain::keysinterface::{Sign, KeysInterface};
+use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
+use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
use lightning::ln::msgs::LightningError;
use lightning::routing::scoring::Score;
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
use core::ops::Deref;
use core::time::Duration;
+#[cfg(feature = "std")]
+/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
+/// See [`PhantomKeysManager`] for more information on phantom node payments.
+///
+/// `phantom_route_hints` parameter:
+/// * Contains channel info for all nodes participating in the phantom invoice
+/// * Entries are retrieved from a call to [`ChannelManager::get_phantom_route_hints`] on each
+/// participating node
+/// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
+/// updated when a channel becomes disabled or closes
+/// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
+/// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
+/// down
+///
+/// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
+/// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any
+/// participating node.
+///
+/// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom
+/// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
+/// requirement).
+///
+/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
+/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
+/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
+pub fn create_phantom_invoice<Signer: Sign, K: Deref>(
+ amt_msat: Option<u64>, description: String, payment_hash: PaymentHash, payment_secret:
+ PaymentSecret, phantom_route_hints: Vec<PhantomRouteHints>, keys_manager: K, network: Currency
+) -> Result<Invoice, SignOrCreationError<()>> where K::Target: KeysInterface {
+ if phantom_route_hints.len() == 0 {
+ return Err(SignOrCreationError::CreationError(CreationError::MissingRouteHints))
+ }
+ let mut invoice = InvoiceBuilder::new(network)
+ .description(description)
+ .current_timestamp()
+ .payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
+ .payment_secret(payment_secret)
+ .min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
+ if let Some(amt) = amt_msat {
+ invoice = invoice.amount_milli_satoshis(amt);
+ }
+
+ for hint in phantom_route_hints {
+ for channel in &hint.channels {
+ let short_channel_id = match channel.short_channel_id {
+ Some(id) => id,
+ None => continue,
+ };
+ let forwarding_info = match &channel.counterparty.forwarding_info {
+ Some(info) => info.clone(),
+ None => continue,
+ };
+ invoice = invoice.private_route(RouteHint(vec![
+ RouteHintHop {
+ src_node_id: channel.counterparty.node_id,
+ short_channel_id,
+ fees: RoutingFees {
+ base_msat: forwarding_info.fee_base_msat,
+ proportional_millionths: forwarding_info.fee_proportional_millionths,
+ },
+ cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ },
+ RouteHintHop {
+ src_node_id: hint.real_node_pubkey,
+ short_channel_id: hint.phantom_scid,
+ fees: RoutingFees {
+ base_msat: 0,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ }])
+ );
+ }
+ }
+
+ let raw_invoice = match invoice.build_raw() {
+ Ok(inv) => inv,
+ Err(e) => return Err(SignOrCreationError::CreationError(e))
+ };
+ let hrp_str = raw_invoice.hrp.to_string();
+ let hrp_bytes = hrp_str.as_bytes();
+ let data_without_signature = raw_invoice.data.to_base32();
+ let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
+ match signed_raw_invoice {
+ Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Err(e) => Err(SignOrCreationError::SignError(e))
+ }
+}
+
#[cfg(feature = "std")]
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
- let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature));
+ let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
mod test {
use core::time::Duration;
use {Currency, Description, InvoiceDescription};
- use lightning::ln::PaymentHash;
+ use bitcoin_hashes::Hash;
+ use bitcoin_hashes::sha256::Hash as Sha256;
+ use lightning::chain::keysinterface::PhantomKeysManager;
+ use lightning::ln::{PaymentPreimage, PaymentHash};
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY;
use lightning::ln::functional_test_utils::*;
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::routing::router::{PaymentParameters, RouteParameters, find_route};
- use lightning::util::events::MessageSendEventsProvider;
+ use lightning::util::enforcing_trait_impls::EnforcingSigner;
+ use lightning::util::events::{MessageSendEvent, MessageSendEventsProvider, Event};
use lightning::util::test_utils;
use utils::create_invoice_from_channelmanager_and_duration_since_epoch;
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
}
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_multi_node_receive() {
+ do_test_multi_node_receive(true);
+ do_test_multi_node_receive(false);
+ }
+
+ #[cfg(feature = "std")]
+ fn do_test_multi_node_receive(user_generated_pmt_hash: bool) {
+ let mut chanmon_cfgs = create_chanmon_cfgs(3);
+ let seed_1 = [42 as u8; 32];
+ let seed_2 = [43 as u8; 32];
+ let cross_node_seed = [44 as u8; 32];
+ chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
+ chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let chan_0_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
+ nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan_0_1.1);
+ nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_1.0);
+ let chan_0_2 = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known());
+ nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan_0_2.1);
+ nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0);
+
+ let payment_amt = 10_000;
+ let (payment_preimage, payment_hash, payment_secret) = {
+ if user_generated_pmt_hash {
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
+ let payment_secret = nodes[1].node.create_inbound_payment_for_hash(payment_hash, Some(payment_amt), 3600).unwrap();
+ (payment_preimage, payment_hash, payment_secret)
+ } else {
+ let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
+ let payment_preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
+ (payment_preimage, payment_hash, payment_secret)
+ }
+ };
+ let route_hints = vec![
+ nodes[1].node.get_phantom_route_hints(),
+ nodes[2].node.get_phantom_route_hints(),
+ ];
+ let invoice = ::utils::create_phantom_invoice::<EnforcingSigner, &test_utils::TestKeysInterface>(Some(payment_amt), "test".to_string(), payment_hash, payment_secret, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap();
+
+ assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.route_hints().len(), 2);
+ assert!(!invoice.features().unwrap().supports_basic_mpp());
+
+ let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key())
+ .with_features(invoice.features().unwrap().clone())
+ .with_route_hints(invoice.route_hints());
+ let params = RouteParameters {
+ payment_params,
+ final_value_msat: invoice.amount_milli_satoshis().unwrap(),
+ final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
+ };
+ let first_hops = nodes[0].node.list_usable_channels();
+ let network_graph = node_cfgs[0].network_graph;
+ let logger = test_utils::TestLogger::new();
+ let scorer = test_utils::TestScorer::with_penalty(0);
+ let route = find_route(
+ &nodes[0].node.get_our_node_id(), ¶ms, network_graph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,
+ ).unwrap();
+ let (payment_event, fwd_idx) = {
+ let mut payment_hash = PaymentHash([0; 32]);
+ payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
+ nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().clone())).unwrap();
+ let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
+ assert_eq!(added_monitors.len(), 1);
+ added_monitors.clear();
+
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let fwd_idx = match events[0] {
+ MessageSendEvent::UpdateHTLCs { node_id, .. } => {
+ if node_id == nodes[1].node.get_our_node_id() {
+ 1
+ } else { 2 }
+ },
+ _ => panic!("Unexpected event")
+ };
+ (SendEvent::from_event(events.remove(0)), fwd_idx)
+ };
+ nodes[fwd_idx].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[fwd_idx], nodes[0], &payment_event.commitment_msg, false, true);
+
+ // Note that we have to "forward pending HTLCs" twice before we see the PaymentReceived as
+ // this "emulates" the payment taking two hops, providing some privacy to make phantom node
+ // payments "look real" by taking more time.
+ expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
+ nodes[fwd_idx].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
+ nodes[fwd_idx].node.process_pending_htlc_forwards();
+
+ let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
+ expect_payment_received!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt);
+ do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[fwd_idx])[..]), false, payment_preimage);
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 2);
+ match events[0] {
+ Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
+ assert_eq!(payment_preimage, *ev_preimage);
+ assert_eq!(payment_hash, *ev_hash);
+ assert_eq!(fee_paid_msat, &Some(0));
+ },
+ _ => panic!("Unexpected event")
+ }
+ match events[1] {
+ Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
+ assert_eq!(hash, Some(payment_hash));
+ },
+ _ => panic!("Unexpected event")
+ }
+ }
}
tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "sync", "net", "time" ] }
[dev-dependencies]
-tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
+tokio = { version = "~1.14", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
"""
[features]
-fuzztarget = ["bitcoin/fuzztarget", "regex"]
# Internal test utilities exposed to other repo crates
_test_utils = ["hex", "regex", "bitcoin/bitcoinconsensus"]
# Unlog messages superior at targeted level.
/// This wrapper avoids having to update some of our tests for now as they assume the direct
/// chain::Watch API wherein we mark a monitor fully-updated by just calling
/// channel_monitor_updated once with the highest ID.
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
pub fn force_channel_monitor_updated(&self, funding_txo: OutPoint, monitor_update_id: u64) {
self.pending_monitor_events.lock().unwrap().push(MonitorEvent::UpdateCompleted {
funding_txo,
});
}
- #[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
+ #[cfg(any(test, fuzzing, feature = "_test_utils"))]
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
use util::events::EventsProvider;
let events = core::cell::RefCell::new(Vec::new());
// We should never ever trigger this from within ChannelManager. Technically a
// user could use this object with some proxying in between which makes this
// possible, but in tests and fuzzing, this should be a panic.
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
panic!("ChannelManager generated a channel update for a channel that was not yet registered!");
- #[cfg(not(any(test, feature = "fuzztarget")))]
+ #[cfg(not(any(test, fuzzing)))]
Err(ChannelMonitorUpdateErr::PermanentFailure)
},
Some(monitor_state) => {
/// An update generated by the underlying Channel itself which contains some new information the
/// ChannelMonitor should be made aware of.
-#[cfg_attr(any(test, feature = "fuzztarget", feature = "_test_utils"), derive(PartialEq))]
+#[cfg_attr(any(test, fuzzing, feature = "_test_utils"), derive(PartialEq))]
#[derive(Clone)]
#[must_use]
pub struct ChannelMonitorUpdate {
);
-#[cfg_attr(any(test, feature = "fuzztarget", feature = "_test_utils"), derive(PartialEq))]
+#[cfg_attr(any(test, fuzzing, feature = "_test_utils"), derive(PartialEq))]
#[derive(Clone)]
pub(crate) enum ChannelMonitorUpdateStep {
LatestHolderCommitmentTXInfo {
/// Transaction outputs to watch for on-chain spends.
pub type TransactionOutputs = (Txid, Vec<(u32, TxOut)>);
-#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
-/// Used only in testing and fuzztarget to check serialization roundtrips don't change the
-/// underlying object
+#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+/// Used only in testing and fuzzing to check serialization roundtrips don't change the underlying
+/// object
impl<Signer: Sign> PartialEq for ChannelMonitor<Signer> {
fn eq(&self, other: &Self) -> bool {
let inner = self.inner.lock().unwrap();
}
}
-#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
-/// Used only in testing and fuzztarget to check serialization roundtrips don't change the
-/// underlying object
+#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+/// Used only in testing and fuzzing to check serialization roundtrips don't change the underlying
+/// object
impl<Signer: Sign> PartialEq for ChannelMonitorImpl<Signer> {
fn eq(&self, other: &Self) -> bool {
if self.latest_update_id != other.latest_update_id ||
use bitcoin::secp256k1;
use util::{byte_utils, transaction_utils};
+use util::crypto::hkdf_extract_expand_twice;
use util::ser::{Writeable, Writer, Readable, ReadableArgs};
use chain::transaction::OutPoint;
pub trait Sign: BaseSign + Writeable + Clone {
}
+/// Specifies the recipient of an invoice, to indicate to [`KeysInterface::sign_invoice`] what node
+/// secret key should be used to sign the invoice.
+pub enum Recipient {
+ /// The invoice should be signed with the local node secret key.
+ Node,
+ /// The invoice should be signed with the phantom node secret key. This secret key must be the
+ /// same for all nodes participating in the [phantom node payment].
+ ///
+ /// [phantom node payment]: PhantomKeysManager
+ PhantomNode,
+}
+
/// A trait to describe an object which can get user secrets and key material.
pub trait KeysInterface {
/// A type which implements Sign which will be returned by get_channel_signer.
type Signer : Sign;
- /// Get node secret key (aka node_id or network_key).
+ /// Get node secret key (aka node_id or network_key) based on the provided [`Recipient`].
///
- /// This method must return the same value each time it is called.
- fn get_node_secret(&self) -> SecretKey;
+ /// This method must return the same value each time it is called with a given `Recipient`
+ /// parameter.
+ fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()>;
/// Get a script pubkey which we send funds to when claiming on-chain contestable outputs.
///
/// This method should return a different value each time it is called, to avoid linking
/// this trait to parse the invoice and make sure they're signing what they expect, rather than
/// blindly signing the hash.
/// The hrp is ascii bytes, while the invoice data is base32.
- fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result<RecoverableSignature, ()>;
+ ///
+ /// The secret key used to sign the invoice is dependent on the [`Recipient`].
+ fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], receipient: Recipient) -> Result<RecoverableSignature, ()>;
/// Get secret key material as bytes for use in encrypting and decrypting inbound payment data.
///
+ /// If the implementor of this trait supports [phantom node payments], then every node that is
+ /// intended to be included in the phantom invoice route hints must return the same value from
+ /// this method.
+ // This is because LDK avoids storing inbound payment data by encrypting payment data in the
+ // payment hash and/or payment secret, therefore for a payment to be receivable by multiple
+ // nodes, they must share the key that encrypts this payment data.
+ ///
/// This method must return the same value each time it is called.
+ ///
+ /// [phantom node payments]: PhantomKeysManager
fn get_inbound_payment_key_material(&self) -> KeyMaterial;
}
/// ChannelMonitor closes may use seed/1'
/// Cooperative closes may use seed/2'
/// The two close keys may be needed to claim on-chain funds!
+///
+/// This struct cannot be used for nodes that wish to support receiving phantom payments;
+/// [`PhantomKeysManager`] must be used instead.
+///
+/// Note that switching between this struct and [`PhantomKeysManager`] will invalidate any
+/// previously issued invoices and attempts to pay previous invoices will fail.
pub struct KeysManager {
secp_ctx: Secp256k1<secp256k1::All>,
node_secret: SecretKey,
/// transaction will have a feerate, at least, of the given value.
///
/// Returns `Err(())` if the output value is greater than the input value minus required fee,
- /// if a descriptor was duplicated, or if an output descriptor script_pubkey
+ /// if a descriptor was duplicated, or if an output descriptor `script_pubkey`
/// does not match the one we can spend.
///
/// We do not enforce that outputs meet the dust limit or that any output scripts are standard.
impl KeysInterface for KeysManager {
type Signer = InMemorySigner;
- fn get_node_secret(&self) -> SecretKey {
- self.node_secret.clone()
+ fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
+ match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }
}
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
}
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
- InMemorySigner::read(&mut io::Cursor::new(reader), self.get_node_secret())
+ InMemorySigner::read(&mut io::Cursor::new(reader), self.node_secret.clone())
+ }
+
+ fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
+ let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
+ let secret = match recipient {
+ Recipient::Node => self.get_node_secret(Recipient::Node)?,
+ Recipient::PhantomNode => return Err(()),
+ };
+ Ok(self.secp_ctx.sign_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret))
+ }
+}
+
+/// Similar to [`KeysManager`], but allows the node using this struct to receive phantom node
+/// payments.
+///
+/// A phantom node payment is a payment made to a phantom invoice, which is an invoice that can be
+/// paid to one of multiple nodes. This works because we encode the invoice route hints such that
+/// LDK will recognize an incoming payment as destined for a phantom node, and collect the payment
+/// itself without ever needing to forward to this fake node.
+///
+/// Phantom node payments are useful for load balancing between multiple LDK nodes. They also
+/// provide some fault tolerance, because payers will automatically retry paying other provided
+/// nodes in the case that one node goes down.
+///
+/// Note that multi-path payments are not supported in phantom invoices for security reasons.
+// In the hypothetical case that we did support MPP phantom payments, there would be no way for
+// nodes to know when the full payment has been received (and the preimage can be released) without
+// significantly compromising on our safety guarantees. I.e., if we expose the ability for the user
+// to tell LDK when the preimage can be released, we open ourselves to attacks where the preimage
+// is released too early.
+//
+/// Switching between this struct and [`KeysManager`] will invalidate any previously issued
+/// invoices and attempts to pay previous invoices will fail.
+pub struct PhantomKeysManager {
+ inner: KeysManager,
+ inbound_payment_key: KeyMaterial,
+ phantom_secret: SecretKey,
+}
+
+impl KeysInterface for PhantomKeysManager {
+ type Signer = InMemorySigner;
+
+ fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
+ match recipient {
+ Recipient::Node => self.inner.get_node_secret(Recipient::Node),
+ Recipient::PhantomNode => Ok(self.phantom_secret.clone()),
+ }
+ }
+
+ fn get_inbound_payment_key_material(&self) -> KeyMaterial {
+ self.inbound_payment_key.clone()
+ }
+
+ fn get_destination_script(&self) -> Script {
+ self.inner.get_destination_script()
+ }
+
+ fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
+ self.inner.get_shutdown_scriptpubkey()
+ }
+
+ fn get_channel_signer(&self, inbound: bool, channel_value_satoshis: u64) -> Self::Signer {
+ self.inner.get_channel_signer(inbound, channel_value_satoshis)
+ }
+
+ fn get_secure_random_bytes(&self) -> [u8; 32] {
+ self.inner.get_secure_random_bytes()
+ }
+
+ fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
+ self.inner.read_chan_signer(reader)
}
- fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
+ fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
- Ok(self.secp_ctx.sign_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &self.get_node_secret()))
+ let secret = self.get_node_secret(recipient)?;
+ Ok(self.inner.secp_ctx.sign_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret))
+ }
+}
+
+impl PhantomKeysManager {
+ /// Constructs a `PhantomKeysManager` given a 32-byte seed and an additional `cross_node_seed`
+ /// that is shared across all nodes that intend to participate in [phantom node payments] together.
+ ///
+ /// See [`KeysManager::new`] for more information on `seed`, `starting_time_secs`, and
+ /// `starting_time_nanos`.
+ ///
+ /// `cross_node_seed` must be the same across all phantom payment-receiving nodes and also the
+ /// same across restarts, or else inbound payments may fail.
+ ///
+ /// [phantom node payments]: PhantomKeysManager
+ pub fn new(seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, cross_node_seed: &[u8; 32]) -> Self {
+ let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
+ let (inbound_key, phantom_key) = hkdf_extract_expand_twice(b"LDK Inbound and Phantom Payment Key Expansion", cross_node_seed);
+ Self {
+ inner,
+ inbound_payment_key: KeyMaterial(inbound_key),
+ phantom_secret: SecretKey::from_slice(&phantom_key).unwrap(),
+ }
+ }
+
+ /// See [`KeysManager::spend_spendable_outputs`] for documentation on this method.
+ pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
+ self.inner.spend_spendable_outputs(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, secp_ctx)
+ }
+
+ /// See [`KeysManager::derive_channel_keys`] for documentation on this method.
+ pub fn derive_channel_keys(&self, channel_value_satoshis: u64, params: &[u8; 32]) -> InMemorySigner {
+ self.inner.derive_channel_keys(channel_value_satoshis, params)
}
}
//! generated/etc. This makes it a good candidate for tight integration into an existing wallet
//! instead of having a rather-separate lightning appendage to a wallet.
-#![cfg_attr(not(any(test, feature = "fuzztarget", feature = "_test_utils")), deny(missing_docs))]
-#![cfg_attr(not(any(test, feature = "fuzztarget", feature = "_test_utils")), forbid(unsafe_code))]
+#![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), deny(missing_docs))]
+#![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), forbid(unsafe_code))]
#![deny(broken_intra_doc_links)]
// In general, rust is absolutely horrid at supporting users doing things like,
#[cfg(not(any(feature = "std", feature = "no-std")))]
compile_error!("at least one of the `std` or `no-std` features must be enabled");
+#[cfg(all(fuzzing, test))]
+compile_error!("Tests will always fail with cfg=fuzzing");
+
#[macro_use]
extern crate alloc;
extern crate bitcoin;
extern crate core;
#[cfg(any(test, feature = "_test_utils"))] extern crate hex;
-#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))] extern crate regex;
+#[cfg(any(test, fuzzing, feature = "_test_utils"))] extern crate regex;
#[cfg(not(feature = "std"))] extern crate core2;
if let &Some(ref b_htlcout) = b {
a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry)
// Note that due to hash collisions, we have to have a fallback comparison
- // here for fuzztarget mode (otherwise at least chanmon_fail_consistency
+ // here for fuzzing mode (otherwise at least chanmon_fail_consistency
// may fail)!
.then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0))
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
use prelude::*;
use core::{cmp,mem,fmt};
use core::ops::Deref;
-#[cfg(any(test, feature = "fuzztarget", debug_assertions))]
+#[cfg(any(test, fuzzing, debug_assertions))]
use sync::Mutex;
use bitcoin::hashes::hex::ToHex;
// `next_remote_commit_tx_fee_msat` properly predict what the next commitment transaction fee will
// be, by comparing the cached values to the fee of the tranaction generated by
// `build_commitment_transaction`.
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_local_commitment_tx_fee_info_cached: Mutex<Option<CommitmentTxInfoCached>>,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_remote_commitment_tx_fee_info_cached: Mutex<Option<CommitmentTxInfoCached>>,
/// lnd has a long-standing bug where, upon reconnection, if the channel is not yet confirmed
/// See-also <https://github.com/lightningnetwork/lnd/issues/4006>
pub workaround_lnd_bug_4006: Option<msgs::FundingLocked>,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
// When we receive an HTLC fulfill on an outbound path, we may immediately fulfill the
// corresponding HTLC on the inbound path. If, then, the outbound path channel is
// disconnected and reconnected (before we've exchange commitment_signed and revoke_and_ack
channel_type: ChannelTypeFeatures,
}
-#[cfg(any(test, feature = "fuzztarget"))]
+#[cfg(any(test, fuzzing))]
struct CommitmentTxInfoCached {
fee: u64,
total_pending_htlcs: usize,
announcement_sigs: None,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_local_commitment_tx_fee_info_cached: Mutex::new(None),
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
workaround_lnd_bug_4006: None,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills: HashSet::new(),
// We currently only actually support one channel type, so don't retry with new types
announcement_sigs: None,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_local_commitment_tx_fee_info_cached: Mutex::new(None),
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
workaround_lnd_bug_4006: None,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills: HashSet::new(),
channel_type,
}
}
if pending_idx == core::usize::MAX {
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
// If we failed to find an HTLC to fulfill, make sure it was previously fulfilled and
// this is simply a duplicate claim, not previously failed and we lost funds.
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
if htlc_id_arg == htlc_id {
// Make sure we don't leave latest_monitor_update_id incremented here:
self.latest_monitor_update_id -= 1;
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
return UpdateFulfillFetch::DuplicateClaim {};
}
self.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::ClaimHTLC {
payment_preimage: payment_preimage_arg, htlc_id: htlc_id_arg,
});
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
self.historical_inbound_htlc_fulfills.insert(htlc_id_arg);
return UpdateFulfillFetch::NewClaim { monitor_update, htlc_value_msat, msg: None };
}
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
self.historical_inbound_htlc_fulfills.insert(htlc_id_arg);
{
}
}
if pending_idx == core::usize::MAX {
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
// If we failed to find an HTLC to fail, make sure it was previously fulfilled and this
// is simply a duplicate fail, not previously failed and we failed-back too early.
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
match pending_update {
&HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => {
if htlc_id_arg == htlc_id {
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
debug_assert!(self.historical_inbound_htlc_fulfills.contains(&htlc_id_arg));
return Ok(None);
}
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}
+ if let Some(ty) = &msg.channel_type {
+ if *ty != self.channel_type {
+ return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned()));
+ }
+ } else if their_features.supports_channel_type() {
+ // Assume they've accepted the channel type as they said they understand it.
+ } else {
+ self.channel_type = ChannelTypeFeatures::from_counterparty_init(&their_features)
+ }
+
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
match &msg.shutdown_scriptpubkey {
&OptionalField::Present(ref script) => {
/// Returns transaction if there is pending funding transaction that is yet to broadcast
pub fn unbroadcasted_funding(&self) -> Option<Transaction> {
- if self.channel_state & (ChannelState::FundingCreated as u32) != 0 {
- self.funding_transaction.clone()
- } else {
- None
- }
+ if self.channel_state & (ChannelState::FundingCreated as u32) != 0 {
+ self.funding_transaction.clone()
+ } else {
+ None
+ }
}
/// Returns a HTLCStats about inbound pending htlcs
let num_htlcs = included_htlcs + addl_htlcs;
let res = Self::commit_tx_fee_msat(self.feerate_per_kw, num_htlcs, self.opt_anchors());
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
let mut fee = res;
if fee_spike_buffer_htlc.is_some() {
let num_htlcs = included_htlcs + addl_htlcs;
let res = Self::commit_tx_fee_msat(self.feerate_per_kw, num_htlcs, self.opt_anchors());
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
let mut fee = res;
if fee_spike_buffer_htlc.is_some() {
return Err((None, ChannelError::Close("Funding remote cannot afford proposed new fee".to_owned())));
}
}
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
if self.is_outbound() {
let projected_commit_tx_info = self.next_local_commitment_tx_fee_info_cached.lock().unwrap().take();
return Err(ChannelError::Close("Received an unexpected revoke_and_ack".to_owned()));
}
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
*self.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None;
*self.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None;
// If we generated the funding transaction and it doesn't match what it
// should, the client is really broken and we should just panic and
// tell them off. That said, because hash collisions happen with high
- // probability in fuzztarget mode, if we're fuzzing we just close the
+ // probability in fuzzing mode, if we're fuzzing we just close the
// channel and move on.
- #[cfg(not(feature = "fuzztarget"))]
+ #[cfg(not(fuzzing))]
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
}
self.update_time_counter += 1;
if input.witness.is_empty() {
// We generated a malleable funding transaction, implying we've
// just exposed ourselves to funds loss to our counterparty.
- #[cfg(not(feature = "fuzztarget"))]
+ #[cfg(not(fuzzing))]
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
}
}
Some(script) => script.clone().into_inner(),
None => Builder::new().into_script(),
}),
+ channel_type: Some(self.channel_type.clone()),
}
}
// Prior to static_remotekey, my_current_per_commitment_point was critical to claiming
// current to_remote balances. However, it no longer has any use, and thus is now simply
// set to a dummy (but valid, as required by the spec) public key.
- // fuzztarget mode marks a subset of pubkeys as invalid so that we can hit "invalid pubkey"
+ // fuzzing mode marks a subset of pubkeys as invalid so that we can hit "invalid pubkey"
// branches, but we unwrap it below, so we arbitrarily select a dummy pubkey which is both
- // valid, and valid in fuzztarget mode's arbitrary validity criteria:
+ // valid, and valid in fuzzing mode's arbitrary validity criteria:
let mut pk = [2; 33]; pk[1] = 0xff;
let dummy_pubkey = PublicKey::from_slice(&pk).unwrap();
let data_loss_protect = if self.cur_counterparty_commitment_transaction_number + 1 < INITIAL_COMMITMENT_NUMBER {
let counterparty_commitment_txid = commitment_stats.tx.trust().txid();
let (signature, htlc_signatures);
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
if !self.is_outbound() {
let projected_commit_tx_info = self.next_remote_commitment_tx_fee_info_cached.lock().unwrap().take();
self.channel_update_status.write(writer)?;
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
(self.historical_inbound_htlc_fulfills.len() as u64).write(writer)?;
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
for htlc in self.historical_inbound_htlc_fulfills.iter() {
htlc.write(writer)?;
}
let channel_update_status = Readable::read(reader)?;
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
let mut historical_inbound_htlc_fulfills = HashSet::new();
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
{
let htlc_fulfills_len: u64 = Readable::read(reader)?;
for _ in 0..htlc_fulfills_len {
announcement_sigs,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_local_commitment_tx_fee_info_cached: Mutex::new(None),
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
workaround_lnd_bug_4006: None,
- #[cfg(any(test, feature = "fuzztarget"))]
+ #[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills,
channel_type: channel_type.unwrap(),
use ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters, htlc_success_tx_weight, htlc_timeout_tx_weight};
use chain::BestBlock;
use chain::chaininterface::{FeeEstimator,ConfirmationTarget};
- use chain::keysinterface::{InMemorySigner, KeyMaterial, KeysInterface, BaseSign};
+ use chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial, KeysInterface, BaseSign};
use chain::transaction::OutPoint;
use util::config::UserConfig;
use util::enforcing_trait_impls::EnforcingSigner;
impl KeysInterface for Keys {
type Signer = InMemorySigner;
- fn get_node_secret(&self) -> SecretKey { panic!(); }
+ fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { panic!(); }
fn get_inbound_payment_key_material(&self) -> KeyMaterial { panic!(); }
fn get_destination_script(&self) -> Script {
let secp_ctx = Secp256k1::signing_only();
}
fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }
fn read_chan_signer(&self, _data: &[u8]) -> Result<Self::Signer, DecodeError> { panic!(); }
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> { panic!(); }
+ fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> { panic!(); }
}
fn public_from_secret_hex(secp_ctx: &Secp256k1<All>, hex: &str) -> PublicKey {
use bitcoin::network::constants::Network;
use bitcoin::hashes::{Hash, HashEngine};
-use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
-use bitcoin::hashes::cmp::fixed_time_eq;
use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::secp256k1::key::{SecretKey,PublicKey};
use ln::msgs::NetAddress;
use ln::onion_utils;
use ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT, OptionalField};
-use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner};
+use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner, Recipient};
use util::config::UserConfig;
use util::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use util::{byte_utils, events};
+use util::scid_utils::fake_scid;
use util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer};
-use util::chacha20::{ChaCha20, ChaChaReader};
use util::logger::{Level, Logger};
use util::errors::APIError;
use prelude::*;
use core::{cmp, mem};
use core::cell::RefCell;
-use io::{Cursor, Read};
+use io::Read;
use sync::{Arc, Condvar, Mutex, MutexGuard, RwLock, RwLockReadGuard};
use core::sync::atomic::{AtomicUsize, Ordering};
use core::time::Duration;
use ln::msgs;
use ln::msgs::MAX_VALUE_MSAT;
use util::chacha20::ChaCha20;
+ use util::crypto::hkdf_extract_expand_thrice;
use util::logger::Logger;
use core::convert::TryInto;
impl ExpandedKey {
pub(super) fn new(key_material: &KeyMaterial) -> ExpandedKey {
- hkdf_extract_expand(b"LDK Inbound Payment Key Expansion", &key_material)
+ let (metadata_key, ldk_pmt_hash_key, user_pmt_hash_key) =
+ hkdf_extract_expand_thrice(b"LDK Inbound Payment Key Expansion", &key_material.0);
+ Self {
+ metadata_key,
+ ldk_pmt_hash_key,
+ user_pmt_hash_key,
+ }
}
}
}
return Ok(PaymentPreimage(decoded_payment_preimage))
}
-
- fn hkdf_extract_expand(salt: &[u8], ikm: &KeyMaterial) -> ExpandedKey {
- let mut hmac = HmacEngine::<Sha256>::new(salt);
- hmac.input(&ikm.0);
- let prk = Hmac::from_engine(hmac).into_inner();
- let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
- hmac.input(&[1; 1]);
- let metadata_key = Hmac::from_engine(hmac).into_inner();
-
- let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
- hmac.input(&metadata_key);
- hmac.input(&[2; 1]);
- let ldk_pmt_hash_key = Hmac::from_engine(hmac).into_inner();
-
- let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
- hmac.input(&ldk_pmt_hash_key);
- hmac.input(&[3; 1]);
- let user_pmt_hash_key = Hmac::from_engine(hmac).into_inner();
-
- ExpandedKey {
- metadata_key,
- ldk_pmt_hash_key,
- user_pmt_hash_key,
- }
- }
}
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
}
}
+struct ReceiveError {
+ err_code: u16,
+ err_data: Vec<u8>,
+ msg: &'static str,
+}
+
/// Return value for claim_funds_from_hop
enum ClaimFundsFromHop {
PrevHopForceClosed,
inbound_payment_key: inbound_payment::ExpandedKey,
+ /// LDK puts the [fake scids] that it generates into namespaces, to identify the type of an
+ /// incoming payment. To make it harder for a third-party to identify the type of a payment,
+ /// we encrypt the namespace identifier using these bytes.
+ ///
+ /// [fake scids]: crate::util::scid_utils::fake_scid
+ fake_scid_rand_bytes: [u8; 32],
+
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
/// value increases strictly since we don't assume access to a time source.
last_node_announcement_serial: AtomicUsize,
},
}
+/// Route hints used in constructing invoices for [phantom node payents].
+///
+/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+pub struct PhantomRouteHints {
+ /// The list of channels to be included in the invoice route hints.
+ pub channels: Vec<ChannelDetails>,
+ /// A fake scid used for representing the phantom node's fake channel in generating the invoice
+ /// route hints.
+ pub phantom_scid: u64,
+ /// The pubkey of the real backing node that would ultimately receive the payment.
+ pub real_node_pubkey: PublicKey,
+}
+
macro_rules! handle_error {
($self: ident, $internal: expr, $counterparty_node_id: expr) => {
match $internal {
pending_inbound_payments: Mutex::new(HashMap::new()),
pending_outbound_payments: Mutex::new(HashMap::new()),
- our_network_key: keys_manager.get_node_secret(),
- our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret()),
+ our_network_key: keys_manager.get_node_secret(Recipient::Node).unwrap(),
+ our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret(Recipient::Node).unwrap()),
secp_ctx,
inbound_payment_key: expanded_inbound_key,
+ fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
last_node_announcement_serial: AtomicUsize::new(0),
highest_seen_timestamp: AtomicUsize::new(0),
let mut channel_state = self.channel_state.lock().unwrap();
match channel_state.by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
- if cfg!(feature = "fuzztarget") {
+ if cfg!(fuzzing) {
return Err(APIError::APIMisuseError { err: "Fuzzy bad RNG".to_owned() });
} else {
panic!("RNG is bad???");
}
}
+ fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
+ payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32) -> Result<PendingHTLCInfo, ReceiveError>
+ {
+ // final_incorrect_cltv_expiry
+ if hop_data.outgoing_cltv_value != cltv_expiry {
+ return Err(ReceiveError {
+ msg: "Upstream node set CLTV to the wrong value",
+ err_code: 18,
+ err_data: byte_utils::be32_to_array(cltv_expiry).to_vec()
+ })
+ }
+ // final_expiry_too_soon
+ // We have to have some headroom to broadcast on chain if we have the preimage, so make sure
+ // we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
+ // Also, ensure that, in the case of an unknown preimage for the received payment hash, our
+ // payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
+ // channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
+ if (hop_data.outgoing_cltv_value as u64) <= self.best_block.read().unwrap().height() as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
+ return Err(ReceiveError {
+ err_code: 17,
+ err_data: Vec::new(),
+ msg: "The final CLTV expiry is too soon to handle",
+ });
+ }
+ if hop_data.amt_to_forward > amt_msat {
+ return Err(ReceiveError {
+ err_code: 19,
+ err_data: byte_utils::be64_to_array(amt_msat).to_vec(),
+ msg: "Upstream node sent less than we were supposed to receive in payment",
+ });
+ }
+
+ let routing = match hop_data.format {
+ msgs::OnionHopDataFormat::Legacy { .. } => {
+ return Err(ReceiveError {
+ err_code: 0x4000|0x2000|3,
+ err_data: Vec::new(),
+ msg: "We require payment_secrets",
+ });
+ },
+ msgs::OnionHopDataFormat::NonFinalNode { .. } => {
+ return Err(ReceiveError {
+ err_code: 0x4000|22,
+ err_data: Vec::new(),
+ msg: "Got non final data with an HMAC of 0",
+ });
+ },
+ msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage } => {
+ if payment_data.is_some() && keysend_preimage.is_some() {
+ return Err(ReceiveError {
+ err_code: 0x4000|22,
+ err_data: Vec::new(),
+ msg: "We don't support MPP keysend payments",
+ });
+ } else if let Some(data) = payment_data {
+ PendingHTLCRouting::Receive {
+ payment_data: data,
+ incoming_cltv_expiry: hop_data.outgoing_cltv_value,
+ }
+ } else if let Some(payment_preimage) = keysend_preimage {
+ // We need to check that the sender knows the keysend preimage before processing this
+ // payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
+ // could discover the final destination of X, by probing the adjacent nodes on the route
+ // with a keysend payment of identical payment hash to X and observing the processing
+ // time discrepancies due to a hash collision with X.
+ let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ if hashed_preimage != payment_hash {
+ return Err(ReceiveError {
+ err_code: 0x4000|22,
+ err_data: Vec::new(),
+ msg: "Payment preimage didn't match payment hash",
+ });
+ }
+
+ PendingHTLCRouting::ReceiveKeysend {
+ payment_preimage,
+ incoming_cltv_expiry: hop_data.outgoing_cltv_value,
+ }
+ } else {
+ return Err(ReceiveError {
+ err_code: 0x4000|0x2000|3,
+ err_data: Vec::new(),
+ msg: "We require payment_secrets",
+ });
+ }
+ },
+ };
+ Ok(PendingHTLCInfo {
+ routing,
+ payment_hash,
+ incoming_shared_secret: shared_secret,
+ amt_to_forward: amt_msat,
+ outgoing_cltv_value: hop_data.outgoing_cltv_value,
+ })
+ }
+
fn decode_update_add_htlc_onion(&self, msg: &msgs::UpdateAddHTLC) -> (PendingHTLCStatus, MutexGuard<ChannelHolder<Signer>>) {
macro_rules! return_malformed_err {
($msg: expr, $err_code: expr) => {
arr.copy_from_slice(&SharedSecret::new(&msg.onion_routing_packet.public_key.unwrap(), &self.our_network_key)[..]);
arr
};
- let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(&shared_secret);
if msg.onion_routing_packet.version != 0 {
//TODO: Spec doesn't indicate if we should only hash hop_data here (and in other
return_malformed_err!("Unknown onion packet version", 0x8000 | 0x4000 | 4);
}
- let mut hmac = HmacEngine::<Sha256>::new(&mu);
- hmac.input(&msg.onion_routing_packet.hop_data);
- hmac.input(&msg.payment_hash.0[..]);
- if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &msg.onion_routing_packet.hmac) {
- return_malformed_err!("HMAC Check failed", 0x8000 | 0x4000 | 5);
- }
-
let mut channel_state = None;
macro_rules! return_err {
($msg: expr, $err_code: expr, $data: expr) => {
}
}
- let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
- let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&msg.onion_routing_packet.hop_data[..]) };
- let (next_hop_data, next_hop_hmac): (msgs::OnionHopData, _) = {
- match <msgs::OnionHopData as Readable>::read(&mut chacha_stream) {
- Err(err) => {
- let error_code = match err {
- msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
- msgs::DecodeError::UnknownRequiredFeature|
- msgs::DecodeError::InvalidValue|
- msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
- _ => 0x2000 | 2, // Should never happen
- };
- return_err!("Unable to decode our hop data", error_code, &[0;0]);
- },
- Ok(msg) => {
- let mut hmac = [0; 32];
- if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
- return_err!("Unable to decode hop data", 0x4000 | 22, &[0;0]);
- }
- (msg, hmac)
- },
- }
+ let next_hop = match onion_utils::decode_next_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) {
+ Ok(res) => res,
+ Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
+ return_malformed_err!(err_msg, err_code);
+ },
+ Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
+ return_err!(err_msg, err_code, &[0; 0]);
+ },
};
- let pending_forward_info = if next_hop_hmac == [0; 32] {
- #[cfg(test)]
- {
- // In tests, make sure that the initial onion pcket data is, at least, non-0.
- // We could do some fancy randomness test here, but, ehh, whatever.
- // This checks for the issue where you can calculate the path length given the
- // onion data as all the path entries that the originator sent will be here
- // as-is (and were originally 0s).
- // Of course reverse path calculation is still pretty easy given naive routing
- // algorithms, but this fixes the most-obvious case.
- let mut next_bytes = [0; 32];
- chacha_stream.read_exact(&mut next_bytes).unwrap();
- assert_ne!(next_bytes[..], [0; 32][..]);
- chacha_stream.read_exact(&mut next_bytes).unwrap();
- assert_ne!(next_bytes[..], [0; 32][..]);
- }
+ let pending_forward_info = match next_hop {
+ onion_utils::Hop::Receive(next_hop_data) => {
+ // OUR PAYMENT!
+ match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry) {
+ Ok(info) => {
+ // Note that we could obviously respond immediately with an update_fulfill_htlc
+ // message, however that would leak that we are the recipient of this payment, so
+ // instead we stay symmetric with the forwarding case, only responding (after a
+ // delay) once they've send us a commitment_signed!
+ PendingHTLCStatus::Forward(info)
+ },
+ Err(ReceiveError { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
+ }
+ },
+ onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
+ let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
+
+ let blinding_factor = {
+ let mut sha = Sha256::engine();
+ sha.input(&new_pubkey.serialize()[..]);
+ sha.input(&shared_secret);
+ Sha256::from_engine(sha).into_inner()
+ };
- // OUR PAYMENT!
- // final_expiry_too_soon
- // We have to have some headroom to broadcast on chain if we have the preimage, so make sure
- // we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
- // Also, ensure that, in the case of an unknown preimage for the received payment hash, our
- // payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
- // channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
- if (msg.cltv_expiry as u64) <= self.best_block.read().unwrap().height() as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
- return_err!("The final CLTV expiry is too soon to handle", 17, &[0;0]);
- }
- // final_incorrect_htlc_amount
- if next_hop_data.amt_to_forward > msg.amount_msat {
- return_err!("Upstream node sent less than we were supposed to receive in payment", 19, &byte_utils::be64_to_array(msg.amount_msat));
- }
- // final_incorrect_cltv_expiry
- if next_hop_data.outgoing_cltv_value != msg.cltv_expiry {
- return_err!("Upstream node set CLTV to the wrong value", 18, &byte_utils::be32_to_array(msg.cltv_expiry));
- }
+ let public_key = if let Err(e) = new_pubkey.mul_assign(&self.secp_ctx, &blinding_factor[..]) {
+ Err(e)
+ } else { Ok(new_pubkey) };
- let routing = match next_hop_data.format {
- msgs::OnionHopDataFormat::Legacy { .. } => return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]),
- msgs::OnionHopDataFormat::NonFinalNode { .. } => return_err!("Got non final data with an HMAC of 0", 0x4000 | 22, &[0;0]),
- msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage } => {
- if payment_data.is_some() && keysend_preimage.is_some() {
- return_err!("We don't support MPP keysend payments", 0x4000|22, &[0;0]);
- } else if let Some(data) = payment_data {
- PendingHTLCRouting::Receive {
- payment_data: data,
- incoming_cltv_expiry: msg.cltv_expiry,
- }
- } else if let Some(payment_preimage) = keysend_preimage {
- // We need to check that the sender knows the keysend preimage before processing this
- // payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
- // could discover the final destination of X, by probing the adjacent nodes on the route
- // with a keysend payment of identical payment hash to X and observing the processing
- // time discrepancies due to a hash collision with X.
- let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
- if hashed_preimage != msg.payment_hash {
- return_err!("Payment preimage didn't match payment hash", 0x4000|22, &[0;0]);
- }
+ let outgoing_packet = msgs::OnionPacket {
+ version: 0,
+ public_key,
+ hop_data: new_packet_bytes,
+ hmac: next_hop_hmac.clone(),
+ };
- PendingHTLCRouting::ReceiveKeysend {
- payment_preimage,
- incoming_cltv_expiry: msg.cltv_expiry,
- }
- } else {
- return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]);
- }
- },
- };
+ let short_channel_id = match next_hop_data.format {
+ msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
+ msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
+ msgs::OnionHopDataFormat::FinalNode { .. } => {
+ return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
+ },
+ };
- // Note that we could obviously respond immediately with an update_fulfill_htlc
- // message, however that would leak that we are the recipient of this payment, so
- // instead we stay symmetric with the forwarding case, only responding (after a
- // delay) once they've send us a commitment_signed!
-
- PendingHTLCStatus::Forward(PendingHTLCInfo {
- routing,
- payment_hash: msg.payment_hash.clone(),
- incoming_shared_secret: shared_secret,
- amt_to_forward: next_hop_data.amt_to_forward,
- outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
- })
- } else {
- let mut new_packet_data = [0; 20*65];
- let read_pos = chacha_stream.read(&mut new_packet_data).unwrap();
- #[cfg(debug_assertions)]
- {
- // Check two things:
- // a) that the behavior of our stream here will return Ok(0) even if the TLV
- // read above emptied out our buffer and the unwrap() wont needlessly panic
- // b) that we didn't somehow magically end up with extra data.
- let mut t = [0; 1];
- debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
+ PendingHTLCStatus::Forward(PendingHTLCInfo {
+ routing: PendingHTLCRouting::Forward {
+ onion_packet: outgoing_packet,
+ short_channel_id,
+ },
+ payment_hash: msg.payment_hash.clone(),
+ incoming_shared_secret: shared_secret,
+ amt_to_forward: next_hop_data.amt_to_forward,
+ outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
+ })
}
- // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
- // fill the onion hop data we'll forward to our next-hop peer.
- chacha_stream.chacha.process_in_place(&mut new_packet_data[read_pos..]);
-
- let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
-
- let blinding_factor = {
- let mut sha = Sha256::engine();
- sha.input(&new_pubkey.serialize()[..]);
- sha.input(&shared_secret);
- Sha256::from_engine(sha).into_inner()
- };
-
- let public_key = if let Err(e) = new_pubkey.mul_assign(&self.secp_ctx, &blinding_factor[..]) {
- Err(e)
- } else { Ok(new_pubkey) };
-
- let outgoing_packet = msgs::OnionPacket {
- version: 0,
- public_key,
- hop_data: new_packet_data,
- hmac: next_hop_hmac.clone(),
- };
-
- let short_channel_id = match next_hop_data.format {
- msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
- msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
- msgs::OnionHopDataFormat::FinalNode { .. } => {
- return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
- },
- };
-
- PendingHTLCStatus::Forward(PendingHTLCInfo {
- routing: PendingHTLCRouting::Forward {
- onion_packet: outgoing_packet,
- short_channel_id,
- },
- payment_hash: msg.payment_hash.clone(),
- incoming_shared_secret: shared_secret,
- amt_to_forward: next_hop_data.amt_to_forward,
- outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
- })
};
channel_state = Some(self.channel_state.lock().unwrap());
if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
let id_option = channel_state.as_ref().unwrap().short_to_id.get(&short_channel_id).cloned();
if let Some((err, code, chan_update)) = loop {
- let forwarding_id = match id_option {
+ let forwarding_id_opt = match id_option {
None => { // unknown_next_peer
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ // Note that this is likely a timing oracle for detecting whether an scid is a
+ // phantom.
+ if fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, *short_channel_id) {
+ None
+ } else {
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
},
- Some(id) => id.clone(),
+ Some(id) => Some(id.clone()),
};
+ let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
+ let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
+ // Leave channel updates as None for private channels.
+ let chan_update_opt = if chan.should_announce() {
+ Some(self.get_channel_update_for_unicast(chan).unwrap()) } else { None };
+ if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
+ // Note that the behavior here should be identical to the above block - we
+ // should NOT reveal the existence or non-existence of a private channel if
+ // we don't allow forwards outbound over them.
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
- let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
-
- if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
- // Note that the behavior here should be identical to the above block - we
- // should NOT reveal the existence or non-existence of a private channel if
- // we don't allow forwards outbound over them.
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
- }
+ // Note that we could technically not return an error yet here and just hope
+ // that the connection is reestablished or monitor updated by the time we get
+ // around to doing the actual forward, but better to fail early if we can and
+ // hopefully an attacker trying to path-trace payments cannot make this occur
+ // on a small/per-node/per-channel scale.
+ if !chan.is_live() { // channel_disabled
+ break Some(("Forwarding channel is not in a ready state.", 0x1000 | 20, chan_update_opt));
+ }
+ if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
+ break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
+ }
+ let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
+ .and_then(|prop_fee| { (prop_fee / 1000000)
+ .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
+ if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
+ break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, chan_update_opt));
+ }
+ (chan_update_opt, chan.get_cltv_expiry_delta())
+ } else { (None, MIN_CLTV_EXPIRY_DELTA) };
- // Note that we could technically not return an error yet here and just hope
- // that the connection is reestablished or monitor updated by the time we get
- // around to doing the actual forward, but better to fail early if we can and
- // hopefully an attacker trying to path-trace payments cannot make this occur
- // on a small/per-node/per-channel scale.
- if !chan.is_live() { // channel_disabled
- break Some(("Forwarding channel is not in a ready state.", 0x1000 | 20, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
- break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
- .and_then(|prop_fee| { (prop_fee / 1000000)
- .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
- if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
- break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + chan.get_cltv_expiry_delta() as u64 { // incorrect_cltv_expiry
- break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + forwardee_cltv_expiry_delta as u64 { // incorrect_cltv_expiry
+ break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, chan_update_opt));
}
let cur_height = self.best_block.read().unwrap().height() + 1;
// Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
// but we want to be robust wrt to counterparty packet sanitization (see
// HTLC_FAIL_BACK_BUFFER rationale).
if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
- break Some(("CLTV expiry is too close", 0x1000 | 14, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
}
if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
break Some(("CLTV expiry is too far in the future", 21, None));
// but there is no need to do that, and since we're a bit conservative with our
// risk threshold it just results in failing to forward payments.
if (*outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
- break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
}
break None;
let mut new_events = Vec::new();
let mut failed_forwards = Vec::new();
+ let mut phantom_receives: Vec<(u64, OutPoint, Vec<(PendingHTLCInfo, u64)>)> = Vec::new();
let mut handle_errors = Vec::new();
{
let mut channel_state_lock = self.channel_state.lock().unwrap();
let forward_chan_id = match channel_state.short_to_id.get(&short_chan_id) {
Some(chan_id) => chan_id.clone(),
None => {
- failed_forwards.reserve(pending_forwards.len());
for forward_info in pending_forwards.drain(..) {
match forward_info {
- HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info,
- prev_funding_outpoint } => {
- let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
- short_channel_id: prev_short_channel_id,
- outpoint: prev_funding_outpoint,
- htlc_id: prev_htlc_id,
- incoming_packet_shared_secret: forward_info.incoming_shared_secret,
- });
- failed_forwards.push((htlc_source, forward_info.payment_hash,
- HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }
- ));
- },
+ HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
+ routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
+ prev_funding_outpoint } => {
+ macro_rules! fail_forward {
+ ($msg: expr, $err_code: expr, $err_data: expr) => {
+ {
+ log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+ let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
+ short_channel_id: short_chan_id,
+ outpoint: prev_funding_outpoint,
+ htlc_id: prev_htlc_id,
+ incoming_packet_shared_secret: incoming_shared_secret,
+ });
+ failed_forwards.push((htlc_source, payment_hash,
+ HTLCFailReason::Reason { failure_code: $err_code, data: $err_data }
+ ));
+ continue;
+ }
+ }
+ }
+ if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
+ let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode);
+ if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) {
+ let shared_secret = {
+ let mut arr = [0; 32];
+ arr.copy_from_slice(&SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap())[..]);
+ arr
+ };
+ let next_hop = match onion_utils::decode_next_hop(shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
+ Ok(res) => res,
+ Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
+ fail_forward!(err_msg, err_code, Vec::new());
+ },
+ Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
+ fail_forward!(err_msg, err_code, Vec::new());
+ },
+ };
+ match next_hop {
+ onion_utils::Hop::Receive(hop_data) => {
+ match self.construct_recv_pending_htlc_info(hop_data, shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value) {
+ Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, vec![(info, prev_htlc_id)])),
+ Err(ReceiveError { err_code, err_data, msg }) => fail_forward!(msg, err_code, err_data)
+ }
+ },
+ _ => panic!(),
+ }
+ } else {
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ }
+ } else {
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ }
+ },
HTLCForwardInfo::FailHTLC { .. } => {
// Channel went away before we could fail it. This implies
// the channel is now on chain and our counterparty is
// trying to broadcast the HTLC-Timeout, but that's their
// problem, not ours.
+ //
+ // `fail_htlc_backwards_internal` is never called for
+ // phantom payments, so this is unreachable for them.
}
}
}
macro_rules! check_total_value {
($payment_data_total_msat: expr, $payment_secret: expr, $payment_preimage: expr) => {{
- let mut total_value = 0;
let mut payment_received_generated = false;
let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
.or_insert(Vec::new());
continue
}
}
- htlcs.push(claimable_htlc);
+ let mut total_value = claimable_htlc.value;
for htlc in htlcs.iter() {
total_value += htlc.value;
match &htlc.onion_payload {
if total_value >= msgs::MAX_VALUE_MSAT || total_value > $payment_data_total_msat {
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the total value {} ran over expected value {} (or HTLCs were inconsistent)",
log_bytes!(payment_hash.0), total_value, $payment_data_total_msat);
- for htlc in htlcs.iter() {
- fail_htlc!(htlc);
- }
+ fail_htlc!(claimable_htlc);
} else if total_value == $payment_data_total_msat {
+ htlcs.push(claimable_htlc);
new_events.push(events::Event::PaymentReceived {
payment_hash,
purpose: events::PaymentPurpose::InvoicePayment {
// Nothing to do - we haven't reached the total
// payment value yet, wait until we receive more
// MPP parts.
+ htlcs.push(claimable_htlc);
}
payment_received_generated
}}
for (htlc_source, payment_hash, failure_reason) in failed_forwards.drain(..) {
self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), htlc_source, &payment_hash, failure_reason);
}
+ self.forward_htlcs(&mut phantom_receives);
for (counterparty_node_id, err) in handle_errors.drain(..) {
let _ = handle_error!(self, err, counterparty_node_id);
/// In chanmon_consistency_target, we'd like to be able to restore monitor updating without
/// handling all pending events (i.e. not PendingHTLCsForwardable). Thus, we expose monitor
/// update events as a separate process method here.
- #[cfg(feature = "fuzztarget")]
+ #[cfg(fuzzing)]
pub fn process_monitor_events(&self) {
self.process_pending_monitor_events();
}
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}
- #[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
+ /// 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.
+ ///
+ /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+ pub fn get_phantom_scid(&self) -> u64 {
+ let mut channel_state = self.channel_state.lock().unwrap();
+ let best_block = self.best_block.read().unwrap();
+ loop {
+ let scid_candidate = fake_scid::get_phantom_scid(&self.fake_scid_rand_bytes, best_block.height(), &self.genesis_hash, &self.keys_manager);
+ // Ensure the generated scid doesn't conflict with a real channel.
+ match channel_state.short_to_id.entry(scid_candidate) {
+ hash_map::Entry::Occupied(_) => continue,
+ hash_map::Entry::Vacant(_) => return scid_candidate
+ }
+ }
+ }
+
+ /// Gets route hints for use in receiving [phantom node payments].
+ ///
+ /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+ pub fn get_phantom_route_hints(&self) -> PhantomRouteHints {
+ PhantomRouteHints {
+ channels: self.list_usable_channels(),
+ phantom_scid: self.get_phantom_scid(),
+ real_node_pubkey: self.get_our_node_id(),
+ }
+ }
+
+ #[cfg(any(test, fuzzing, feature = "_test_utils"))]
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
let events = core::cell::RefCell::new(Vec::new());
let event_handler = |event: &events::Event| events.borrow_mut().push(event.clone());
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
+impl_writeable_tlv_based!(CounterpartyForwardingInfo, {
+ (2, fee_base_msat, required),
+ (4, fee_proportional_millionths, required),
+ (6, cltv_expiry_delta, required),
+});
+
+impl_writeable_tlv_based!(ChannelCounterparty, {
+ (2, node_id, required),
+ (4, features, required),
+ (6, unspendable_punishment_reserve, required),
+ (8, forwarding_info, option),
+});
+
+impl_writeable_tlv_based!(ChannelDetails, {
+ (2, channel_id, required),
+ (4, counterparty, required),
+ (6, funding_txo, option),
+ (8, short_channel_id, option),
+ (10, channel_value_satoshis, required),
+ (12, unspendable_punishment_reserve, option),
+ (14, user_channel_id, required),
+ (16, balance_msat, required),
+ (18, outbound_capacity_msat, required),
+ (20, inbound_capacity_msat, required),
+ (22, confirmations_required, option),
+ (24, force_close_spend_delay, option),
+ (26, is_outbound, required),
+ (28, is_funding_locked, required),
+ (30, is_usable, required),
+ (32, is_public, required),
+});
+
+impl_writeable_tlv_based!(PhantomRouteHints, {
+ (2, channels, vec_type),
+ (4, phantom_scid, required),
+ (6, real_node_pubkey, required),
+});
+
impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(0, Forward) => {
(0, onion_packet, required),
write_tlv_fields!(writer, {
(1, pending_outbound_payments_no_retry, required),
(3, pending_outbound_payments, required),
- (5, self.our_network_pubkey, required)
+ (5, self.our_network_pubkey, required),
+ (7, self.fake_scid_rand_bytes, required),
});
Ok(())
let mut pending_outbound_payments_no_retry: Option<HashMap<PaymentId, HashSet<[u8; 32]>>> = None;
let mut pending_outbound_payments = None;
let mut received_network_pubkey: Option<PublicKey> = None;
+ let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
read_tlv_fields!(reader, {
(1, pending_outbound_payments_no_retry, option),
(3, pending_outbound_payments, option),
- (5, received_network_pubkey, option)
+ (5, received_network_pubkey, option),
+ (7, fake_scid_rand_bytes, option),
});
+ if fake_scid_rand_bytes.is_none() {
+ fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
+ }
if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
pending_outbound_payments = Some(pending_outbound_payments_compat);
pending_events_read.append(&mut channel_closures);
}
- let our_network_pubkey = PublicKey::from_secret_key(&secp_ctx, &args.keys_manager.get_node_secret());
+ let our_network_key = match args.keys_manager.get_node_secret(Recipient::Node) {
+ Ok(key) => key,
+ Err(()) => return Err(DecodeError::InvalidValue)
+ };
+ let our_network_pubkey = PublicKey::from_secret_key(&secp_ctx, &our_network_key);
if let Some(network_pubkey) = received_network_pubkey {
if network_pubkey != our_network_pubkey {
log_error!(args.logger, "Key that was generated does not match the existing key.");
inbound_payment_key: expanded_inbound_key,
pending_inbound_payments: Mutex::new(pending_inbound_payments),
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
+ fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
- our_network_key: args.keys_manager.get_node_secret(),
+ our_network_key,
our_network_pubkey,
secp_ctx,
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
- Event::PendingHTLCsForwardable { .. } => { },
+ $crate::util::events::Event::PendingHTLCsForwardable { .. } => { },
_ => panic!("Unexpected event"),
};
}}
}}
}
-#[cfg(any(test, feature = "_bench_unstable"))]
+#[macro_export]
+#[cfg(any(test, feature = "_bench_unstable", feature = "_test_utils"))]
macro_rules! expect_payment_received {
($node: expr, $expected_payment_hash: expr, $expected_payment_secret: expr, $expected_recv_value: expr) => {
+ expect_payment_received!($node, $expected_payment_hash, $expected_payment_secret, $expected_recv_value, None)
+ };
+ ($node: expr, $expected_payment_hash: expr, $expected_payment_secret: expr, $expected_recv_value: expr, $expected_payment_preimage: expr) => {
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
- Event::PaymentReceived { ref payment_hash, ref purpose, amt } => {
+ $crate::util::events::Event::PaymentReceived { ref payment_hash, ref purpose, amt } => {
assert_eq!($expected_payment_hash, *payment_hash);
assert_eq!($expected_recv_value, amt);
match purpose {
- PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
- assert!(payment_preimage.is_none());
+ $crate::util::events::PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
+ assert_eq!(&$expected_payment_preimage, payment_preimage);
assert_eq!($expected_payment_secret, *payment_secret);
},
_ => {},
.with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
let route = get_route(
- &origin_node.node.get_our_node_id(), &payment_params, origin_node.network_graph,
+ &origin_node.node.get_our_node_id(), &payment_params, origin_node.network_graph,
None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), expected_route.len());
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage_2);
}
+#[test]
+fn test_dup_htlc_second_fail_panic() {
+ // Previously, if we received two HTLCs back-to-back, where the second overran the expected
+ // value for the payment, we'd fail back both HTLCs after generating a `PaymentReceived` event.
+ // Then, if the user failed the second payment, they'd hit a "tried to fail an already failed
+ // HTLC" debug panic. This tests for this behavior, checking that only one HTLC is auto-failed.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
+
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_features(InvoiceFeatures::known());
+ let scorer = test_utils::TestScorer::with_penalty(0);
+ let route = get_route(
+ &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph,
+ Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
+ 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer).unwrap();
+
+ let (_, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[1]);
+
+ {
+ nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let mut payment_event = SendEvent::from_event(events.pop().unwrap());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+ }
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_payment_received!(nodes[1], our_payment_hash, our_payment_secret, 10_000);
+
+ {
+ nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let mut payment_event = SendEvent::from_event(events.pop().unwrap());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+ // At this point, nodes[1] would notice it has too much value for the payment. It will
+ // assume the second is a privacy attack (no longer particularly relevant
+ // post-payment_secrets) and fail back the new HTLC. Previously, it'd also have failed back
+ // the first HTLC delivered above.
+ }
+
+ // Now we go fail back the first HTLC from the user end.
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ nodes[1].node.fail_htlc_backwards(&our_payment_hash);
+
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+
+ check_added_monitors!(nodes[1], 1);
+ let fail_updates_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ assert_eq!(fail_updates_1.update_fail_htlcs.len(), 2);
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_updates_1.update_fail_htlcs[0]);
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_updates_1.update_fail_htlcs[1]);
+ commitment_signed_dance!(nodes[0], nodes[1], fail_updates_1.commitment_signed, false);
+
+ let failure_events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(failure_events.len(), 2);
+ if let Event::PaymentPathFailed { .. } = failure_events[0] {} else { panic!(); }
+ if let Event::PaymentPathFailed { .. } = failure_events[1] {} else { panic!(); }
+}
+
#[test]
fn test_keysend_payments_to_public_node() {
let chanmon_cfgs = create_chanmon_cfgs(2);
pub mod features;
pub mod script;
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub mod peer_channel_encryptor;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub(crate) mod peer_channel_encryptor;
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub mod channel;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub(crate) mod channel;
mod onion_utils;
pub first_per_commitment_point: PublicKey,
/// Optionally, a request to pre-set the to-sender output's scriptPubkey for when we collaboratively close
pub shutdown_scriptpubkey: OptionalField<Script>,
+ /// The channel type that this channel will represent. If none is set, we derive the channel
+ /// type from the intersection of our feature bits with our counterparty's feature bits from
+ /// the Init message.
+ ///
+ /// This is required to match the equivalent field in [`OpenChannel::channel_type`].
+ pub channel_type: Option<ChannelTypeFeatures>,
}
/// A funding_created message to be sent or received from a peer
pub(crate) pad: Vec<u8>,
}
}
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub use self::fuzzy_internal_msgs::*;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub(crate) use self::fuzzy_internal_msgs::*;
#[derive(Clone)]
htlc_basepoint,
first_per_commitment_point,
shutdown_scriptpubkey
-}, {});
+}, {
+ (1, channel_type, option),
+});
impl_writeable_msg!(AnnouncementSignatures, {
channel_id,
delayed_payment_basepoint: pubkey_4,
htlc_basepoint: pubkey_5,
first_per_commitment_point: pubkey_6,
- shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent }
+ shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent },
+ channel_type: None,
};
let encoded_value = accept_channel.encode();
let mut target_value = hex::decode("020202020202020202020202020202020202020202020202020202020202020212345678901234562334032891223698321446687011447600083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a").unwrap();
use ln::msgs;
use routing::network_graph::NetworkUpdate;
use routing::router::RouteHop;
-use util::chacha20::ChaCha20;
+use util::chacha20::{ChaCha20, ChaChaReader};
use util::errors::{self, APIError};
use util::ser::{Readable, Writeable, LengthCalculatingWriter};
use util::logger::Logger;
use bitcoin::secp256k1;
use prelude::*;
-use io::Cursor;
+use io::{Cursor, Read};
use core::convert::TryInto;
use core::ops::Deref;
} else { unreachable!(); }
}
+/// Data decrypted from the onion payload.
+pub(crate) enum Hop {
+ /// This onion payload was for us, not for forwarding to a next-hop. Contains information for
+ /// verifying the incoming payment.
+ Receive(msgs::OnionHopData),
+ /// This onion payload needs to be forwarded to a next-hop.
+ Forward {
+ /// Onion payload data used in forwarding the payment.
+ next_hop_data: msgs::OnionHopData,
+ /// HMAC of the next hop's onion packet.
+ next_hop_hmac: [u8; 32],
+ /// Bytes of the onion packet we're forwarding.
+ new_packet_bytes: [u8; 20*65],
+ },
+}
+
+/// Error returned when we fail to decode the onion packet.
+pub(crate) enum OnionDecodeErr {
+ /// The HMAC of the onion packet did not match the hop data.
+ Malformed {
+ err_msg: &'static str,
+ err_code: u16,
+ },
+ /// We failed to decode the onion payload.
+ Relay {
+ err_msg: &'static str,
+ err_code: u16,
+ },
+}
+
+pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result<Hop, OnionDecodeErr> {
+ let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret);
+ let mut hmac = HmacEngine::<Sha256>::new(&mu);
+ hmac.input(hop_data);
+ hmac.input(&payment_hash.0[..]);
+ if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &hmac_bytes) {
+ return Err(OnionDecodeErr::Malformed {
+ err_msg: "HMAC Check failed",
+ err_code: 0x8000 | 0x4000 | 5,
+ });
+ }
+
+ let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
+ let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) };
+ match <msgs::OnionHopData as Readable>::read(&mut chacha_stream) {
+ Err(err) => {
+ let error_code = match err {
+ msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
+ msgs::DecodeError::UnknownRequiredFeature|
+ msgs::DecodeError::InvalidValue|
+ msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
+ _ => 0x2000 | 2, // Should never happen
+ };
+ return Err(OnionDecodeErr::Relay {
+ err_msg: "Unable to decode our hop data",
+ err_code: error_code,
+ });
+ },
+ Ok(msg) => {
+ let mut hmac = [0; 32];
+ if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
+ return Err(OnionDecodeErr::Relay {
+ err_msg: "Unable to decode our hop data",
+ err_code: 0x4000 | 22,
+ });
+ }
+ if hmac == [0; 32] {
+ #[cfg(test)]
+ {
+ // In tests, make sure that the initial onion packet data is, at least, non-0.
+ // We could do some fancy randomness test here, but, ehh, whatever.
+ // This checks for the issue where you can calculate the path length given the
+ // onion data as all the path entries that the originator sent will be here
+ // as-is (and were originally 0s).
+ // Of course reverse path calculation is still pretty easy given naive routing
+ // algorithms, but this fixes the most-obvious case.
+ let mut next_bytes = [0; 32];
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ }
+ return Ok(Hop::Receive(msg));
+ } else {
+ let mut new_packet_bytes = [0; 20*65];
+ let read_pos = chacha_stream.read(&mut new_packet_bytes).unwrap();
+ #[cfg(debug_assertions)]
+ {
+ // Check two things:
+ // a) that the behavior of our stream here will return Ok(0) even if the TLV
+ // read above emptied out our buffer and the unwrap() wont needlessly panic
+ // b) that we didn't somehow magically end up with extra data.
+ let mut t = [0; 1];
+ debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
+ }
+ // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
+ // fill the onion hop data we'll forward to our next-hop peer.
+ chacha_stream.chacha.process_in_place(&mut new_packet_bytes[read_pos..]);
+ return Ok(Hop::Forward {
+ next_hop_data: msg,
+ next_hop_hmac: hmac,
+ new_packet_bytes,
+ })
+ }
+ },
+ }
+}
+
#[cfg(test)]
mod tests {
use io;
use ln::msgs::LightningError;
use ln::msgs;
-use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine};
+use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1;
use util::chacha20poly1305rfc::ChaCha20Poly1305RFC;
+use util::crypto::hkdf_extract_expand_twice;
use bitcoin::hashes::hex::ToHex;
/// Maximum Lightning message data length according to
Ok(())
}
- fn hkdf_extract_expand(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) {
- let mut hmac = HmacEngine::<Sha256>::new(salt);
- hmac.input(ikm);
- let prk = Hmac::from_engine(hmac).into_inner();
- let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
- hmac.input(&[1; 1]);
- let t1 = Hmac::from_engine(hmac).into_inner();
- let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
- hmac.input(&t1);
- hmac.input(&[2; 1]);
- (t1, Hmac::from_engine(hmac).into_inner())
- }
-
#[inline]
fn hkdf(state: &mut BidirectionalNoiseState, ss: SharedSecret) -> [u8; 32] {
- let (t1, t2) = Self::hkdf_extract_expand(&state.ck, &ss[..]);
+ let (t1, t2) = hkdf_extract_expand_twice(&state.ck, &ss[..]);
state.ck = t1;
t2
}
let temp_k = PeerChannelEncryptor::hkdf(bidirectional_state, ss);
PeerChannelEncryptor::encrypt_with_ad(&mut res[50..], 0, &temp_k, &bidirectional_state.h, &[0; 0]);
- final_hkdf = Self::hkdf_extract_expand(&bidirectional_state.ck, &[0; 0]);
+ final_hkdf = hkdf_extract_expand_twice(&bidirectional_state.ck, &[0; 0]);
ck = bidirectional_state.ck.clone();
res
},
let temp_k = PeerChannelEncryptor::hkdf(bidirectional_state, ss);
PeerChannelEncryptor::decrypt_with_ad(&mut [0; 0], 0, &temp_k, &bidirectional_state.h, &act_three[50..])?;
- final_hkdf = Self::hkdf_extract_expand(&bidirectional_state.ck, &[0; 0]);
+ final_hkdf = hkdf_extract_expand_twice(&bidirectional_state.ck, &[0; 0]);
ck = bidirectional_state.ck.clone();
},
_ => panic!("Wrong direction for act"),
match self.noise_state {
NoiseState::Finished { ref mut sk, ref mut sn, ref mut sck, rk: _, rn: _, rck: _ } => {
if *sn >= 1000 {
- let (new_sck, new_sk) = Self::hkdf_extract_expand(sck, sk);
+ let (new_sck, new_sk) = hkdf_extract_expand_twice(sck, sk);
*sck = new_sck;
*sk = new_sk;
*sn = 0;
match self.noise_state {
NoiseState::Finished { sk: _, sn: _, sck: _, ref mut rk, ref mut rn, ref mut rck } => {
if *rn >= 1000 {
- let (new_rck, new_rk) = Self::hkdf_extract_expand(rck, rk);
+ let (new_rck, new_rk) = hkdf_extract_expand_twice(rck, rk);
*rck = new_rck;
*rk = new_rk;
*rn = 0;
/// decrease as well. Thus, we have to explicitly track which nodes have been processed and
/// avoid processing them again.
was_processed: bool,
- #[cfg(all(not(feature = "_bench_unstable"), any(test, feature = "fuzztarget")))]
+ #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
// In tests, we apply further sanity checks on cases where we skip nodes we already processed
// to ensure it is specifically in cases where the fee has gone down because of a decrease in
// value_contribution_msat, which requires tracking it here. See comments below where it is
path_htlc_minimum_msat,
path_penalty_msat: u64::max_value(),
was_processed: false,
- #[cfg(all(not(feature = "_bench_unstable"), any(test, feature = "fuzztarget")))]
+ #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
value_contribution_msat,
}
});
#[allow(unused_mut)] // We only use the mut in cfg(test)
let mut should_process = !old_entry.was_processed;
- #[cfg(all(not(feature = "_bench_unstable"), any(test, feature = "fuzztarget")))]
+ #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
{
// In test/fuzzing builds, we do extra checks to make sure the skipping
// of already-seen nodes only happens in cases we expect (see below).
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
old_entry.path_penalty_msat = path_penalty_msat;
- #[cfg(all(not(feature = "_bench_unstable"), any(test, feature = "fuzztarget")))]
+ #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
{
old_entry.value_contribution_msat = value_contribution_msat;
}
did_add_update_path_to_src_node = true;
} else if old_entry.was_processed && new_cost < old_cost {
- #[cfg(all(not(feature = "_bench_unstable"), any(test, feature = "fuzztarget")))]
+ #[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
{
// If we're skipping processing a node which was previously
// processed even though we found another path to it with a
use io;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
mod real_chacha {
use core::cmp;
use core::convert::TryInto;
}
}
}
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub use self::real_chacha::ChaCha20;
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
mod fuzzy_chacha {
pub struct ChaCha20 {}
pub fn process_in_place(&mut self, _input_output: &mut [u8]) {}
}
}
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub use self::fuzzy_chacha::ChaCha20;
pub(crate) struct ChaChaReader<'a, R: io::Read> {
// This is a port of Andrew Moons poly1305-donna
// https://github.com/floodyberry/poly1305-donna
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
mod real_chachapoly {
use util::chacha20::ChaCha20;
use util::poly1305::Poly1305;
}
}
}
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub use self::real_chachapoly::ChaCha20Poly1305RFC;
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
mod fuzzy_chachapoly {
#[derive(Clone, Copy)]
pub struct ChaCha20Poly1305RFC {
}
}
}
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;
--- /dev/null
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::hmac::{Hmac, HmacEngine};
+use bitcoin::hashes::sha256::Hash as Sha256;
+
+macro_rules! hkdf_extract_expand {
+ ($salt: expr, $ikm: expr) => {{
+ let mut hmac = HmacEngine::<Sha256>::new($salt);
+ hmac.input($ikm);
+ let prk = Hmac::from_engine(hmac).into_inner();
+ let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+ hmac.input(&[1; 1]);
+ let t1 = Hmac::from_engine(hmac).into_inner();
+ let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+ hmac.input(&t1);
+ hmac.input(&[2; 1]);
+ (t1, Hmac::from_engine(hmac).into_inner(), prk)
+ }};
+ ($salt: expr, $ikm: expr, 2) => {{
+ let (k1, k2, _) = hkdf_extract_expand!($salt, $ikm);
+ (k1, k2)
+ }};
+ ($salt: expr, $ikm: expr, 3) => {{
+ let (k1, k2, prk) = hkdf_extract_expand!($salt, $ikm);
+
+ let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+ hmac.input(&k2);
+ hmac.input(&[3; 1]);
+ (k1, k2, Hmac::from_engine(hmac).into_inner())
+ }}
+}
+
+pub fn hkdf_extract_expand_twice(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) {
+ hkdf_extract_expand!(salt, ikm, 2)
+}
+
+pub fn hkdf_extract_expand_thrice(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32]) {
+ hkdf_extract_expand!(salt, ikm, 3)
+}
macro_rules! hash_to_message {
($slice: expr) => {
{
- #[cfg(not(feature = "fuzztarget"))]
+ #[cfg(not(fuzzing))]
{
::bitcoin::secp256k1::Message::from_slice($slice).unwrap()
}
- #[cfg(feature = "fuzztarget")]
+ #[cfg(fuzzing)]
{
match ::bitcoin::secp256k1::Message::from_slice($slice) {
Ok(msg) => msg,
pub(crate) mod atomic_counter;
pub(crate) mod byte_utils;
pub(crate) mod chacha20;
-#[cfg(feature = "fuzztarget")]
+#[cfg(fuzzing)]
pub mod zbase32;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub(crate) mod zbase32;
-#[cfg(not(feature = "fuzztarget"))]
+#[cfg(not(fuzzing))]
pub(crate) mod poly1305;
pub(crate) mod chacha20poly1305rfc;
pub(crate) mod transaction_utils;
#[macro_use]
pub(crate) mod macro_logger;
+/// Cryptography utilities.
+pub(crate) mod crypto;
+
// These have to come after macro_logger to build
pub mod logger;
pub mod config;
-#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
+#[cfg(any(test, fuzzing, feature = "_test_utils"))]
pub mod test_utils;
/// impls of traits that add exra enforcement on the way they're called. Useful for detecting state
/// machine errors and used in fuzz targets and tests.
-#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
+#[cfg(any(test, fuzzing, feature = "_test_utils"))]
pub mod enforcing_trait_impls;
+
return (short_channel_id >> 40) as u32;
}
+/// Extracts the tx index (bytes [2..4]) from the `short_channel_id`
+pub fn tx_index_from_scid(short_channel_id: &u64) -> u32 {
+ return ((short_channel_id >> 16) & MAX_SCID_TX_INDEX) as u32;
+}
+
+/// Extracts the vout (bytes [0..2]) from the `short_channel_id`
+pub fn vout_from_scid(short_channel_id: &u64) -> u16 {
+ return ((short_channel_id) & MAX_SCID_VOUT_INDEX) as u16;
+}
+
/// Constructs a `short_channel_id` using the components pieces. Results in an error
/// if the block height, tx index, or vout index overflow the maximum sizes.
pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result<u64, ShortChannelIdError> {
Ok((block << 40) | (tx_index << 16) | vout_index)
}
+/// LDK has multiple reasons to generate fake short channel ids:
+/// 1) zero-conf channels that don't have a confirmed channel id yet
+/// 2) phantom node payments, to get an scid for the phantom node's phantom channel
+pub(crate) mod fake_scid {
+ use bitcoin::hash_types::BlockHash;
+ use bitcoin::hashes::hex::FromHex;
+ use chain::keysinterface::{Sign, KeysInterface};
+ use util::chacha20::ChaCha20;
+ use util::scid_utils;
+
+ use core::convert::TryInto;
+ use core::ops::Deref;
+
+ const TEST_SEGWIT_ACTIVATION_HEIGHT: u32 = 0;
+ const MAINNET_SEGWIT_ACTIVATION_HEIGHT: u32 = 481_824;
+ const MAX_TX_INDEX: u32 = 2_500;
+ const MAX_NAMESPACES: u8 = 8; // We allocate 3 bits for the namespace identifier.
+ const NAMESPACE_ID_BITMASK: u8 = 0b111;
+
+ /// Fake scids are divided into namespaces, with each namespace having its own identifier between
+ /// [0..7]. This allows us to identify what namespace a fake scid corresponds to upon HTLC
+ /// receipt, and handle the HTLC accordingly. The namespace identifier is encrypted when encoded
+ /// into the fake scid.
+ #[derive(Copy, Clone)]
+ pub(super) enum Namespace {
+ Phantom,
+ // Coming soon: a variant for the zero-conf scid namespace
+ }
+
+ impl Namespace {
+ /// We generate "realistic-looking" random scids here, meaning the scid's block height is
+ /// between segwit activation and the current best known height, and the tx index and output
+ /// index are also selected from a "reasonable" range. We add this logic because it makes it
+ /// non-obvious at a glance that the scid is fake, e.g. if it appears in invoice route hints.
+ pub(super) fn get_fake_scid<Signer: Sign, K: Deref>(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], keys_manager: &K) -> u64
+ where K::Target: KeysInterface<Signer = Signer>,
+ {
+ // Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for
+ // namespaces.
+ assert!((*self as u8) < MAX_NAMESPACES);
+ const BLOCKS_PER_MONTH: u32 = 144 /* blocks per day */ * 30 /* days per month */;
+ let rand_bytes = keys_manager.get_secure_random_bytes();
+
+ let segwit_activation_height = segwit_activation_height(genesis_hash);
+ let mut valid_block_range = if highest_seen_blockheight > segwit_activation_height {
+ highest_seen_blockheight - segwit_activation_height
+ } else {
+ 1
+ };
+ // We want to ensure that this fake channel won't conflict with any transactions we haven't
+ // seen yet, in case `highest_seen_blockheight` is updated before we get full information
+ // about transactions confirmed in the given block.
+ if valid_block_range > BLOCKS_PER_MONTH { valid_block_range -= BLOCKS_PER_MONTH; }
+
+ let rand_for_height = u32::from_be_bytes(rand_bytes[..4].try_into().unwrap());
+ let fake_scid_height = segwit_activation_height + rand_for_height % valid_block_range;
+
+ let rand_for_tx_index = u32::from_be_bytes(rand_bytes[4..8].try_into().unwrap());
+ let fake_scid_tx_index = rand_for_tx_index % MAX_TX_INDEX;
+
+ // Put the scid in the given namespace.
+ let fake_scid_vout = self.get_encrypted_vout(fake_scid_height, fake_scid_tx_index, fake_scid_rand_bytes);
+ scid_utils::scid_from_parts(fake_scid_height as u64, fake_scid_tx_index as u64, fake_scid_vout as u64).unwrap()
+ }
+
+ /// We want to ensure that a 3rd party can't identify a payment as belong to a given
+ /// `Namespace`. Therefore, we encrypt it using a random bytes provided by `ChannelManager`.
+ fn get_encrypted_vout(&self, block_height: u32, tx_index: u32, fake_scid_rand_bytes: &[u8; 32]) -> u8 {
+ let mut salt = [0 as u8; 8];
+ let block_height_bytes = block_height.to_be_bytes();
+ salt[0..4].copy_from_slice(&block_height_bytes);
+ let tx_index_bytes = tx_index.to_be_bytes();
+ salt[4..8].copy_from_slice(&tx_index_bytes);
+
+ let mut chacha = ChaCha20::new(fake_scid_rand_bytes, &salt);
+ let mut vout_byte = [*self as u8];
+ chacha.process_in_place(&mut vout_byte);
+ vout_byte[0] & NAMESPACE_ID_BITMASK
+ }
+ }
+
+ pub fn get_phantom_scid<Signer: Sign, K: Deref>(fake_scid_rand_bytes: &[u8; 32], highest_seen_blockheight: u32, genesis_hash: &BlockHash, keys_manager: &K) -> u64
+ where K::Target: KeysInterface<Signer = Signer>,
+ {
+ let namespace = Namespace::Phantom;
+ namespace.get_fake_scid(highest_seen_blockheight, genesis_hash, fake_scid_rand_bytes, keys_manager)
+ }
+
+ fn segwit_activation_height(genesis: &BlockHash) -> u32 {
+ const MAINNET_GENESIS_STR: &'static str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
+ if BlockHash::from_hex(MAINNET_GENESIS_STR).unwrap() == *genesis {
+ MAINNET_SEGWIT_ACTIVATION_HEIGHT
+ } else {
+ TEST_SEGWIT_ACTIVATION_HEIGHT
+ }
+ }
+
+ /// Returns whether the given fake scid falls into the given namespace.
+ pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool {
+ let block_height = scid_utils::block_from_scid(&scid);
+ let tx_index = scid_utils::tx_index_from_scid(&scid);
+ let namespace = Namespace::Phantom;
+ let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes);
+ valid_vout == scid_utils::vout_from_scid(&scid) as u8
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::network::constants::Network;
+ use util::scid_utils::fake_scid::{is_valid_phantom, MAINNET_SEGWIT_ACTIVATION_HEIGHT, MAX_TX_INDEX, MAX_NAMESPACES, Namespace, NAMESPACE_ID_BITMASK, segwit_activation_height, TEST_SEGWIT_ACTIVATION_HEIGHT};
+ use util::scid_utils;
+ use util::test_utils;
+ use sync::Arc;
+
+ #[test]
+ fn namespace_identifier_is_within_range() {
+ let phantom_namespace = Namespace::Phantom;
+ assert!((phantom_namespace as u8) < MAX_NAMESPACES);
+ assert!((phantom_namespace as u8) <= NAMESPACE_ID_BITMASK);
+ }
+
+ #[test]
+ fn test_segwit_activation_height() {
+ let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
+ assert_eq!(segwit_activation_height(&mainnet_genesis), MAINNET_SEGWIT_ACTIVATION_HEIGHT);
+
+ let testnet_genesis = genesis_block(Network::Testnet).header.block_hash();
+ assert_eq!(segwit_activation_height(&testnet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
+
+ let signet_genesis = genesis_block(Network::Signet).header.block_hash();
+ assert_eq!(segwit_activation_height(&signet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
+
+ let regtest_genesis = genesis_block(Network::Regtest).header.block_hash();
+ assert_eq!(segwit_activation_height(®test_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
+ }
+
+ #[test]
+ fn test_is_valid_phantom() {
+ let namespace = Namespace::Phantom;
+ let fake_scid_rand_bytes = [0; 32];
+ let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes);
+ let valid_fake_scid = scid_utils::scid_from_parts(0, 0, valid_encrypted_vout as u64).unwrap();
+ assert!(is_valid_phantom(&fake_scid_rand_bytes, valid_fake_scid));
+ let invalid_fake_scid = scid_utils::scid_from_parts(0, 0, 12).unwrap();
+ assert!(!is_valid_phantom(&fake_scid_rand_bytes, invalid_fake_scid));
+ }
+
+ #[test]
+ fn test_get_fake_scid() {
+ let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
+ let seed = [0; 32];
+ let fake_scid_rand_bytes = [1; 32];
+ let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet));
+ let namespace = Namespace::Phantom;
+ let fake_scid = namespace.get_fake_scid(500_000, &mainnet_genesis, &fake_scid_rand_bytes, &keys_manager);
+
+ let fake_height = scid_utils::block_from_scid(&fake_scid);
+ assert!(fake_height >= MAINNET_SEGWIT_ACTIVATION_HEIGHT);
+ assert!(fake_height <= 500_000);
+
+ let fake_tx_index = scid_utils::tx_index_from_scid(&fake_scid);
+ assert!(fake_tx_index <= MAX_TX_INDEX);
+
+ let fake_vout = scid_utils::vout_from_scid(&fake_scid);
+ assert!(fake_vout < MAX_NAMESPACES as u16);
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
assert_eq!(block_from_scid(&0xffffff_ffffff_ffff), 0xffffff);
}
+ #[test]
+ fn test_tx_index_from_scid() {
+ assert_eq!(tx_index_from_scid(&0x000000_000000_0000), 0);
+ assert_eq!(tx_index_from_scid(&0x000000_000001_0000), 1);
+ assert_eq!(tx_index_from_scid(&0xffffff_000001_ffff), 1);
+ assert_eq!(tx_index_from_scid(&0xffffff_800000_ffff), 0x800000);
+ assert_eq!(tx_index_from_scid(&0xffffff_ffffff_ffff), 0xffffff);
+ }
+
+ #[test]
+ fn test_vout_from_scid() {
+ assert_eq!(vout_from_scid(&0x000000_000000_0000), 0);
+ assert_eq!(vout_from_scid(&0x000000_000000_0001), 1);
+ assert_eq!(vout_from_scid(&0xffffff_ffffff_0001), 1);
+ assert_eq!(vout_from_scid(&0xffffff_ffffff_8000), 0x8000);
+ assert_eq!(vout_from_scid(&0xffffff_ffffff_ffff), 0xffff);
+ }
+
#[test]
fn test_scid_from_parts() {
assert_eq!(scid_from_parts(0x00000000, 0x00000000, 0x0000).unwrap(), 0x000000_000000_0000);
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use core::{cmp, mem};
use bitcoin::bech32::u5;
-use chain::keysinterface::{InMemorySigner, KeyMaterial};
+use chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial};
pub struct TestVecWriter(pub Vec<u8>);
impl Writer for TestVecWriter {
impl keysinterface::KeysInterface for OnlyReadsKeysInterface {
type Signer = EnforcingSigner;
- fn get_node_secret(&self) -> SecretKey { unreachable!(); }
+ fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { unreachable!(); }
fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!(); }
fn get_destination_script(&self) -> Script { unreachable!(); }
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!(); }
false
))
}
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> { unreachable!(); }
+ fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> { unreachable!(); }
}
pub struct TestChainMonitor<'a> {
}
pub struct TestKeysInterface {
- pub backing: keysinterface::KeysManager,
+ pub backing: keysinterface::PhantomKeysManager,
pub override_session_priv: Mutex<Option<[u8; 32]>>,
pub override_channel_id_priv: Mutex<Option<[u8; 32]>>,
pub disable_revocation_policy_check: bool,
impl keysinterface::KeysInterface for TestKeysInterface {
type Signer = EnforcingSigner;
- fn get_node_secret(&self) -> SecretKey { self.backing.get_node_secret() }
- fn get_inbound_payment_key_material(&self) -> keysinterface::KeyMaterial { self.backing.get_inbound_payment_key_material() }
+ fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
+ self.backing.get_node_secret(recipient)
+ }
+ fn get_inbound_payment_key_material(&self) -> keysinterface::KeyMaterial {
+ self.backing.get_inbound_payment_key_material()
+ }
fn get_destination_script(&self) -> Script { self.backing.get_destination_script() }
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
let mut reader = io::Cursor::new(buffer);
- let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret())?;
+ let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret(Recipient::Node).unwrap())?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
Ok(EnforcingSigner::new_with_revoked(
))
}
- fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
- self.backing.sign_invoice(hrp_bytes, invoice_data)
+ fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
+ self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}
}
pub fn new(seed: &[u8; 32], network: Network) -> Self {
let now = Duration::from_secs(genesis_block(network).header.time as u64);
Self {
- backing: keysinterface::KeysManager::new(seed, now.as_secs(), now.subsec_nanos()),
+ backing: keysinterface::PhantomKeysManager::new(seed, now.as_secs(), now.subsec_nanos(), seed),
override_session_priv: Mutex::new(None),
override_channel_id_priv: Mutex::new(None),
disable_revocation_policy_check: false,