mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-25 07:17:40 +01:00
Merge pull request #1199 from valentinewallace/2021-11-phantom-node
Add support for multi-node receive
This commit is contained in:
commit
8e7f241149
15 changed files with 1151 additions and 313 deletions
|
@ -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<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 {
|
||||
|
@ -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<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 {
|
||||
|
@ -222,7 +222,7 @@ impl KeysInterface for KeyProvider {
|
|||
})
|
||||
}
|
||||
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SecretKey, ()> {
|
||||
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<RecoverableSignature, ()> {
|
||||
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +390,8 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
|
|||
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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
|
@ -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::<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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
|
@ -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<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;
|
||||
}
|
||||
|
||||
|
@ -810,6 +835,12 @@ impl ReadableArgs<SecretKey> 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<secp256k1::All>,
|
||||
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<SecretKey, ()> {
|
||||
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<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]) -> 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 = 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], recipient: Recipient) -> Result<RecoverableSignature, ()> {
|
||||
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SecretKey, ()> { 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<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 {
|
||||
|
|
|
@ -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::<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:
|
||||
|
@ -539,6 +519,12 @@ pub(super) enum HTLCFailReason {
|
|||
}
|
||||
}
|
||||
|
||||
struct ReceiveError {
|
||||
err_code: u16,
|
||||
err_data: Vec<u8>,
|
||||
msg: &'static str,
|
||||
}
|
||||
|
||||
/// Return value for claim_funds_from_hop
|
||||
enum ClaimFundsFromHop {
|
||||
PrevHopForceClosed,
|
||||
|
@ -988,6 +974,13 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
|
|||
|
||||
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,
|
||||
|
@ -1324,6 +1317,19 @@ pub enum PaymentSendFailure {
|
|||
},
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -1700,11 +1706,12 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<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) => {
|
||||
|
@ -2088,7 +2191,6 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
|
|||
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) => {
|
||||
|
@ -2124,164 +2219,70 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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 <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()
|
||||
};
|
||||
|
||||
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_bytes,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 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 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]);
|
||||
}
|
||||
|
||||
PendingHTLCRouting::ReceiveKeysend {
|
||||
payment_preimage,
|
||||
incoming_cltv_expiry: msg.cltv_expiry,
|
||||
}
|
||||
} else {
|
||||
return_err!("We require payment_secrets", 0x4000|0x2000|3, &[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);
|
||||
}
|
||||
// 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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();
|
||||
// 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) };
|
||||
|
||||
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, 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<events::Event> {
|
||||
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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<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);
|
||||
|
@ -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,
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<T: secp256k1::Signing, L: Deref>(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<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;
|
||||
|
|
|
@ -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::<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
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
38
lightning/src/util/crypto.rs
Normal file
38
lightning/src/util/crypto.rs
Normal file
|
@ -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::<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)
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<u64, ShortChannelIdError> {
|
||||
|
@ -50,6 +60,176 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result<u64
|
|||
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::*;
|
||||
|
@ -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);
|
||||
|
|
|
@ -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<u8>);
|
||||
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<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!(); }
|
||||
|
@ -88,7 +88,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface {
|
|||
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> {
|
||||
|
@ -470,7 +470,7 @@ impl Logger for TestLogger {
|
|||
}
|
||||
|
||||
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,
|
||||
|
@ -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<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 {
|
||||
|
@ -519,7 +523,7 @@ impl keysinterface::KeysInterface for TestKeysInterface {
|
|||
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(
|
||||
|
@ -529,8 +533,8 @@ impl keysinterface::KeysInterface for TestKeysInterface {
|
|||
))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue