diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 8c11ba2c6c7..79faba90158 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -34,7 +34,7 @@ use lightning::chain::{BestBlock, ChannelMonitorUpdateErr, chainmonitor, channel 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; @@ -161,8 +161,8 @@ struct KeyProvider { 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 { + 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 { @@ -188,7 +188,7 @@ impl KeysInterface for KeyProvider { 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(), @@ -212,7 +212,7 @@ impl KeysInterface for KeyProvider { fn read_chan_signer(&self, buffer: &[u8]) -> Result { 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 { @@ -222,7 +222,7 @@ impl KeysInterface for KeyProvider { }) } - fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!() } } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 9db04b645e4..19ec541b942 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -31,7 +31,7 @@ use lightning::chain::{BestBlock, Confirm, Listen}; 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}; @@ -265,8 +265,8 @@ struct KeyProvider { 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 { + Ok(self.node_secret.clone()) } fn get_inbound_payment_key_material(&self) -> KeyMaterial { @@ -336,7 +336,7 @@ impl KeysInterface for KeyProvider { )) } - fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!() } } @@ -390,7 +390,8 @@ pub fn do_test(data: &[u8], logger: &Arc) { 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); diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 6e5e60e5014..8ed0014a72d 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -343,7 +343,7 @@ mod tests { 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}; @@ -426,7 +426,7 @@ mod tests { 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); } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index c44db77575d..d676f6c28d6 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -1387,6 +1387,12 @@ pub enum CreationError { /// 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 { @@ -1396,6 +1402,7 @@ 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"), } } } diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index ef95b3e355b..fc82541a564 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -8,9 +8,9 @@ use bitcoin_hashes::Hash; 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}; @@ -21,6 +21,99 @@ use core::convert::TryInto; 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( + amt_msat: Option, description: String, payment_hash: PaymentHash, payment_secret: + PaymentSecret, phantom_route_hints: Vec, keys_manager: K, network: Currency +) -> Result> 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 @@ -118,7 +211,7 @@ where 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)) @@ -192,13 +285,17 @@ where 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; @@ -254,4 +351,121 @@ mod test { 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::(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::>()), &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") + } + } } diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index dffe060d91b..1daeec4ef62 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -31,6 +31,7 @@ use bitcoin::secp256k1::recovery::RecoverableSignature; 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; @@ -379,15 +380,28 @@ pub trait BaseSign { 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; /// 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 @@ -423,11 +437,22 @@ pub trait KeysInterface { /// 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; + /// + /// 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; /// 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; } @@ -810,6 +835,12 @@ impl ReadableArgs for InMemorySigner { /// 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, node_secret: SecretKey, @@ -964,7 +995,7 @@ impl KeysManager { /// 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. @@ -1092,8 +1123,11 @@ impl KeysManager { 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 { + match recipient { + Recipient::Node => Ok(self.node_secret.clone()), + Recipient::PhantomNode => Err(()) + } } fn get_inbound_payment_key_material(&self) -> KeyMaterial { @@ -1130,12 +1164,116 @@ impl KeysInterface for KeysManager { } fn read_chan_signer(&self, reader: &[u8]) -> Result { - 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 { + 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 { + 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.inner.read_chan_signer(reader) } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result { + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result { 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(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec, change_destination_script: Script, feerate_sat_per_1000_weight: u32, secp_ctx: &Secp256k1) -> Result { + 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) } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index ed7975fe9ca..fb9213142ae 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -6188,7 +6188,7 @@ mod tests { 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; @@ -6236,7 +6236,7 @@ mod tests { impl KeysInterface for Keys { type Signer = InMemorySigner; - fn get_node_secret(&self) -> SecretKey { panic!(); } + fn get_node_secret(&self, _recipient: Recipient) -> Result { panic!(); } fn get_inbound_payment_key_material(&self) -> KeyMaterial { panic!(); } fn get_destination_script(&self) -> Script { let secp_ctx = Secp256k1::signing_only(); @@ -6256,7 +6256,7 @@ mod tests { } fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] } fn read_chan_signer(&self, _data: &[u8]) -> Result { panic!(); } - fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { panic!(); } + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { panic!(); } } fn public_from_secret_hex(secp_ctx: &Secp256k1, hex: &str) -> PublicKey { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9a5fdcf08c2..0a0407eacff 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -24,10 +24,8 @@ use bitcoin::blockdata::constants::genesis_block; 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}; @@ -50,12 +48,12 @@ use ln::msgs; 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; @@ -63,7 +61,7 @@ use io; 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; @@ -84,6 +82,7 @@ mod inbound_payment { 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; @@ -115,7 +114,13 @@ mod inbound_payment { 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, + } } } @@ -333,31 +338,6 @@ mod inbound_payment { } return Ok(PaymentPreimage(decoded_payment_preimage)) } - - fn hkdf_extract_expand(salt: &[u8], ikm: &KeyMaterial) -> ExpandedKey { - let mut hmac = HmacEngine::::new(salt); - hmac.input(&ikm.0); - let prk = Hmac::from_engine(hmac).into_inner(); - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&[1; 1]); - let metadata_key = Hmac::from_engine(hmac).into_inner(); - - let mut hmac = HmacEngine::::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::::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: @@ -539,6 +519,12 @@ pub(super) enum HTLCFailReason { } } +struct ReceiveError { + err_code: u16, + err_data: Vec, + msg: &'static str, +} + /// Return value for claim_funds_from_hop enum ClaimFundsFromHop { PrevHopForceClosed, @@ -988,6 +974,13 @@ pub struct ChannelManager, + /// 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 { @@ -1700,11 +1706,12 @@ impl ChannelMana 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), @@ -2064,6 +2071,102 @@ impl ChannelMana } } + 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 + { + // 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>) { macro_rules! return_malformed_err { ($msg: expr, $err_code: expr) => { @@ -2088,7 +2191,6 @@ impl ChannelMana 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 @@ -2100,13 +2202,6 @@ impl ChannelMana return_malformed_err!("Unknown onion packet version", 0x8000 | 0x4000 | 4); } - let mut hmac = HmacEngine::::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) => { @@ -2124,164 +2219,70 @@ impl ChannelMana } } - 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 ::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()); @@ -2292,48 +2293,59 @@ impl ChannelMana 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)); @@ -2347,7 +2359,7 @@ impl ChannelMana // 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; @@ -2983,6 +2995,7 @@ impl ChannelMana 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(); @@ -2993,26 +3006,69 @@ impl ChannelMana 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. } } } @@ -3319,6 +3375,7 @@ impl ChannelMana 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); @@ -5151,6 +5208,34 @@ impl ChannelMana inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } + /// 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, feature = "fuzztarget", feature = "_test_utils"))] pub fn get_and_clear_pending_events(&self) -> Vec { let events = core::cell::RefCell::new(Vec::new()); @@ -5849,6 +5934,44 @@ impl PersistenceNotifier { 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), @@ -6250,7 +6373,8 @@ impl Writeable f 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(()) @@ -6546,11 +6670,16 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> let mut pending_outbound_payments_no_retry: Option>> = None; let mut pending_outbound_payments = None; let mut received_network_pubkey: Option = 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); @@ -6614,7 +6743,11 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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."); @@ -6642,8 +6775,9 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b0ec079479a..1d9e5afc46e 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1102,7 +1102,7 @@ macro_rules! expect_pending_htlcs_forwardable_ignore { 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"), }; }} @@ -1137,18 +1137,22 @@ macro_rules! expect_pending_htlcs_forwardable_from_events { }} } -#[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); }, _ => {}, @@ -1560,7 +1564,7 @@ pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_rou .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()); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index ec668045eb0..0dd6087f820 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -12,7 +12,7 @@ use ln::channelmanager::HTLCSource; 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; @@ -28,7 +28,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1; use prelude::*; -use io::Cursor; +use io::{Cursor, Read}; use core::convert::TryInto; use core::ops::Deref; @@ -506,6 +506,114 @@ pub(super) fn process_onion_failure(secp_ctx: & } 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 { + let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret); + let mut hmac = HmacEngine::::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 ::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; diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs index 7b42c68a578..fbd32526ea6 100644 --- a/lightning/src/ln/peer_channel_encryptor.rs +++ b/lightning/src/ln/peer_channel_encryptor.rs @@ -12,7 +12,7 @@ use prelude::*; 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; @@ -21,6 +21,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret; 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 @@ -160,22 +161,9 @@ impl PeerChannelEncryptor { Ok(()) } - fn hkdf_extract_expand(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { - let mut hmac = HmacEngine::::new(salt); - hmac.input(ikm); - let prk = Hmac::from_engine(hmac).into_inner(); - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&[1; 1]); - let t1 = Hmac::from_engine(hmac).into_inner(); - let mut hmac = HmacEngine::::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 } @@ -311,7 +299,7 @@ impl PeerChannelEncryptor { 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 }, @@ -365,7 +353,7 @@ impl PeerChannelEncryptor { 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"), @@ -399,7 +387,7 @@ impl PeerChannelEncryptor { 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; @@ -425,7 +413,7 @@ impl PeerChannelEncryptor { 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; diff --git a/lightning/src/util/crypto.rs b/lightning/src/util/crypto.rs new file mode 100644 index 00000000000..f8a3f847d43 --- /dev/null +++ b/lightning/src/util/crypto.rs @@ -0,0 +1,38 @@ +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::::new($salt); + hmac.input($ikm); + let prk = Hmac::from_engine(hmac).into_inner(); + let mut hmac = HmacEngine::::new(&prk[..]); + hmac.input(&[1; 1]); + let t1 = Hmac::from_engine(hmac).into_inner(); + let mut hmac = HmacEngine::::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::::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) +} diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index 81b4bc927ce..6e04f85682d 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -38,6 +38,9 @@ pub(crate) mod scid_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; @@ -49,3 +52,4 @@ pub mod test_utils; /// machine errors and used in fuzz targets and tests. #[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))] pub mod enforcing_trait_impls; + diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs index 7902a5271a6..f9dfd1b0320 100644 --- a/lightning/src/util/scid_utils.rs +++ b/lightning/src/util/scid_utils.rs @@ -32,6 +32,16 @@ pub fn block_from_scid(short_channel_id: &u64) -> u32 { 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 { @@ -50,6 +60,176 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], keys_manager: &K) -> u64 + where K::Target: KeysInterface, + { + // 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(fake_scid_rand_bytes: &[u8; 32], highest_seen_blockheight: u32, genesis_hash: &BlockHash, keys_manager: &K) -> u64 + where K::Target: KeysInterface, + { + 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::*; @@ -63,6 +243,24 @@ mod tests { 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); diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 0e1c92d009c..9cd2f9ec13e 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -47,7 +47,7 @@ use sync::{Mutex, Arc}; 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); impl Writer for TestVecWriter { @@ -70,7 +70,7 @@ pub struct OnlyReadsKeysInterface {} impl keysinterface::KeysInterface for OnlyReadsKeysInterface { type Signer = EnforcingSigner; - fn get_node_secret(&self) -> SecretKey { unreachable!(); } + fn get_node_secret(&self, _recipient: Recipient) -> Result { unreachable!(); } fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!(); } fn get_destination_script(&self) -> Script { unreachable!(); } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!(); } @@ -88,7 +88,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface { false )) } - fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { unreachable!(); } + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!(); } } pub struct TestChainMonitor<'a> { @@ -470,7 +470,7 @@ impl Logger for TestLogger { } pub struct TestKeysInterface { - pub backing: keysinterface::KeysManager, + pub backing: keysinterface::PhantomKeysManager, pub override_session_priv: Mutex>, pub override_channel_id_priv: Mutex>, pub disable_revocation_policy_check: bool, @@ -481,8 +481,12 @@ pub struct TestKeysInterface { 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 { + 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 { @@ -519,7 +523,7 @@ impl keysinterface::KeysInterface for TestKeysInterface { fn read_chan_signer(&self, buffer: &[u8]) -> Result { 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( @@ -529,8 +533,8 @@ impl keysinterface::KeysInterface for TestKeysInterface { )) } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result { - self.backing.sign_invoice(hrp_bytes, invoice_data) + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result { + self.backing.sign_invoice(hrp_bytes, invoice_data, recipient) } } @@ -538,7 +542,7 @@ impl TestKeysInterface { 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,