From 410eb053656b1a4d3a6c506f1902e456a69324ac Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 30 Dec 2021 16:13:56 -0500 Subject: [PATCH] Add get_phantom_scid and get_phantom_route_hints + scid_utils::fake_scid module See method and module docs for more details --- fuzz/src/full_stack.rs | 1 + lightning/src/ln/channelmanager.rs | 67 +++++++++++- lightning/src/util/scid_utils.rs | 170 +++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+), 2 deletions(-) diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 292188d7e..19ec541b9 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -390,6 +390,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { best_block: BestBlock::from_genesis(network), }; let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), Arc::clone(&logger), keys_manager.clone(), config, params)); + 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))); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 2c99c8214..5ba18d7e0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -52,6 +52,7 @@ use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner, Rec 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::logger::{Level, Logger}; use util::errors::APIError; @@ -973,6 +974,13 @@ pub struct ChannelManager, + /// A fake scid used for representing the phantom node's fake channel in generating the invoice + /// route hints. + pub phantom_scid: u64, + /// The pubkey of the real backing node that would ultimately receive the payment. + pub real_node_pubkey: PublicKey, +} + macro_rules! handle_error { ($self: ident, $internal: expr, $counterparty_node_id: expr) => { match $internal { @@ -1690,6 +1711,7 @@ impl ChannelMana 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), @@ -5130,6 +5152,34 @@ impl ChannelMana inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } + /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids + /// are used when constructing the phantom invoice's route hints. + /// + /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager + pub fn get_phantom_scid(&self) -> u64 { + let mut channel_state = self.channel_state.lock().unwrap(); + let best_block = self.best_block.read().unwrap(); + loop { + let scid_candidate = fake_scid::get_phantom_scid(&self.fake_scid_rand_bytes, best_block.height(), &self.genesis_hash, &self.keys_manager); + // Ensure the generated scid doesn't conflict with a real channel. + match channel_state.short_to_id.entry(scid_candidate) { + hash_map::Entry::Occupied(_) => continue, + hash_map::Entry::Vacant(_) => return scid_candidate + } + } + } + + /// Gets route hints for use in receiving [phantom node payments]. + /// + /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager + pub fn get_phantom_route_hints(&self) -> PhantomRouteHints { + PhantomRouteHints { + channels: self.list_usable_channels(), + phantom_scid: self.get_phantom_scid(), + real_node_pubkey: self.get_our_node_id(), + } + } + #[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))] pub fn get_and_clear_pending_events(&self) -> Vec { let events = core::cell::RefCell::new(Vec::new()); @@ -5860,6 +5910,12 @@ impl_writeable_tlv_based!(ChannelDetails, { (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), @@ -6261,7 +6317,8 @@ impl Writeable f write_tlv_fields!(writer, { (1, pending_outbound_payments_no_retry, required), (3, pending_outbound_payments, required), - (5, self.our_network_pubkey, required) + (5, self.our_network_pubkey, required), + (7, self.fake_scid_rand_bytes, required), }); Ok(()) @@ -6557,11 +6614,16 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> let mut pending_outbound_payments_no_retry: Option>> = None; let mut pending_outbound_payments = None; let mut received_network_pubkey: Option = None; + let mut fake_scid_rand_bytes: Option<[u8; 32]> = None; read_tlv_fields!(reader, { (1, pending_outbound_payments_no_retry, option), (3, pending_outbound_payments, option), - (5, received_network_pubkey, option) + (5, received_network_pubkey, option), + (7, fake_scid_rand_bytes, option), }); + if fake_scid_rand_bytes.is_none() { + fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes()); + } if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() { pending_outbound_payments = Some(pending_outbound_payments_compat); @@ -6657,6 +6719,7 @@ 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, our_network_pubkey, diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs index f85e843d8..f9dfd1b03 100644 --- a/lightning/src/util/scid_utils.rs +++ b/lightning/src/util/scid_utils.rs @@ -60,6 +60,176 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], keys_manager: &K) -> u64 + where K::Target: KeysInterface, + { + // Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for + // namespaces. + assert!((*self as u8) < MAX_NAMESPACES); + const BLOCKS_PER_MONTH: u32 = 144 /* blocks per day */ * 30 /* days per month */; + let rand_bytes = keys_manager.get_secure_random_bytes(); + + let segwit_activation_height = segwit_activation_height(genesis_hash); + let mut valid_block_range = if highest_seen_blockheight > segwit_activation_height { + highest_seen_blockheight - segwit_activation_height + } else { + 1 + }; + // We want to ensure that this fake channel won't conflict with any transactions we haven't + // seen yet, in case `highest_seen_blockheight` is updated before we get full information + // about transactions confirmed in the given block. + if valid_block_range > BLOCKS_PER_MONTH { valid_block_range -= BLOCKS_PER_MONTH; } + + let rand_for_height = u32::from_be_bytes(rand_bytes[..4].try_into().unwrap()); + let fake_scid_height = segwit_activation_height + rand_for_height % valid_block_range; + + let rand_for_tx_index = u32::from_be_bytes(rand_bytes[4..8].try_into().unwrap()); + let fake_scid_tx_index = rand_for_tx_index % MAX_TX_INDEX; + + // Put the scid in the given namespace. + let fake_scid_vout = self.get_encrypted_vout(fake_scid_height, fake_scid_tx_index, fake_scid_rand_bytes); + scid_utils::scid_from_parts(fake_scid_height as u64, fake_scid_tx_index as u64, fake_scid_vout as u64).unwrap() + } + + /// We want to ensure that a 3rd party can't identify a payment as belong to a given + /// `Namespace`. Therefore, we encrypt it using a random bytes provided by `ChannelManager`. + fn get_encrypted_vout(&self, block_height: u32, tx_index: u32, fake_scid_rand_bytes: &[u8; 32]) -> u8 { + let mut salt = [0 as u8; 8]; + let block_height_bytes = block_height.to_be_bytes(); + salt[0..4].copy_from_slice(&block_height_bytes); + let tx_index_bytes = tx_index.to_be_bytes(); + salt[4..8].copy_from_slice(&tx_index_bytes); + + let mut chacha = ChaCha20::new(fake_scid_rand_bytes, &salt); + let mut vout_byte = [*self as u8]; + chacha.process_in_place(&mut vout_byte); + vout_byte[0] & NAMESPACE_ID_BITMASK + } + } + + pub fn get_phantom_scid(fake_scid_rand_bytes: &[u8; 32], highest_seen_blockheight: u32, genesis_hash: &BlockHash, keys_manager: &K) -> u64 + where K::Target: KeysInterface, + { + let namespace = Namespace::Phantom; + namespace.get_fake_scid(highest_seen_blockheight, genesis_hash, fake_scid_rand_bytes, keys_manager) + } + + fn segwit_activation_height(genesis: &BlockHash) -> u32 { + const MAINNET_GENESIS_STR: &'static str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + if BlockHash::from_hex(MAINNET_GENESIS_STR).unwrap() == *genesis { + MAINNET_SEGWIT_ACTIVATION_HEIGHT + } else { + TEST_SEGWIT_ACTIVATION_HEIGHT + } + } + + /// Returns whether the given fake scid falls into the given namespace. + pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool { + let block_height = scid_utils::block_from_scid(&scid); + let tx_index = scid_utils::tx_index_from_scid(&scid); + let namespace = Namespace::Phantom; + let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes); + valid_vout == scid_utils::vout_from_scid(&scid) as u8 + } + + #[cfg(test)] + mod tests { + use bitcoin::blockdata::constants::genesis_block; + use bitcoin::network::constants::Network; + use util::scid_utils::fake_scid::{is_valid_phantom, MAINNET_SEGWIT_ACTIVATION_HEIGHT, MAX_TX_INDEX, MAX_NAMESPACES, Namespace, NAMESPACE_ID_BITMASK, segwit_activation_height, TEST_SEGWIT_ACTIVATION_HEIGHT}; + use util::scid_utils; + use util::test_utils; + use sync::Arc; + + #[test] + fn namespace_identifier_is_within_range() { + let phantom_namespace = Namespace::Phantom; + assert!((phantom_namespace as u8) < MAX_NAMESPACES); + assert!((phantom_namespace as u8) <= NAMESPACE_ID_BITMASK); + } + + #[test] + fn test_segwit_activation_height() { + let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash(); + assert_eq!(segwit_activation_height(&mainnet_genesis), MAINNET_SEGWIT_ACTIVATION_HEIGHT); + + let testnet_genesis = genesis_block(Network::Testnet).header.block_hash(); + assert_eq!(segwit_activation_height(&testnet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT); + + let signet_genesis = genesis_block(Network::Signet).header.block_hash(); + assert_eq!(segwit_activation_height(&signet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT); + + let regtest_genesis = genesis_block(Network::Regtest).header.block_hash(); + assert_eq!(segwit_activation_height(®test_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT); + } + + #[test] + fn test_is_valid_phantom() { + let namespace = Namespace::Phantom; + let fake_scid_rand_bytes = [0; 32]; + let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes); + let valid_fake_scid = scid_utils::scid_from_parts(0, 0, valid_encrypted_vout as u64).unwrap(); + assert!(is_valid_phantom(&fake_scid_rand_bytes, valid_fake_scid)); + let invalid_fake_scid = scid_utils::scid_from_parts(0, 0, 12).unwrap(); + assert!(!is_valid_phantom(&fake_scid_rand_bytes, invalid_fake_scid)); + } + + #[test] + fn test_get_fake_scid() { + let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash(); + let seed = [0; 32]; + let fake_scid_rand_bytes = [1; 32]; + let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet)); + let namespace = Namespace::Phantom; + let fake_scid = namespace.get_fake_scid(500_000, &mainnet_genesis, &fake_scid_rand_bytes, &keys_manager); + + let fake_height = scid_utils::block_from_scid(&fake_scid); + assert!(fake_height >= MAINNET_SEGWIT_ACTIVATION_HEIGHT); + assert!(fake_height <= 500_000); + + let fake_tx_index = scid_utils::tx_index_from_scid(&fake_scid); + assert!(fake_tx_index <= MAX_TX_INDEX); + + let fake_vout = scid_utils::vout_from_scid(&fake_scid); + assert!(fake_vout < MAX_NAMESPACES as u16); + } + } +} + #[cfg(test)] mod tests { use super::*; -- 2.39.5