Skip to content

Commit 6aa71c4

Browse files
Add get_phantom_scid and get_phantom_route_hints + scid_utils::fake_scid module
See method and module docs for more details
1 parent 9f2e6ad commit 6aa71c4

File tree

3 files changed

+236
-2
lines changed

3 files changed

+236
-2
lines changed

fuzz/src/full_stack.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
390390
best_block: BestBlock::from_genesis(network),
391391
};
392392
let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), Arc::clone(&logger), keys_manager.clone(), config, params));
393+
keys_manager.counter.fetch_sub(1, Ordering::AcqRel);
393394
let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
394395
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash()));
395396
let net_graph_msg_handler = Arc::new(NetGraphMsgHandler::new(Arc::clone(&network_graph), None, Arc::clone(&logger)));

lightning/src/ln/channelmanager.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner, Rec
5252
use util::config::UserConfig;
5353
use util::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
5454
use util::{byte_utils, events};
55+
use util::scid_utils::fake_scid;
5556
use util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer};
5657
use util::logger::{Level, Logger};
5758
use util::errors::APIError;
@@ -973,6 +974,13 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
973974

974975
inbound_payment_key: inbound_payment::ExpandedKey,
975976

977+
/// LDK puts the [fake scids] that it generates into namespaces, to identify the type of an
978+
/// incoming payment. To make it harder for a third-party to identify the type of a payment,
979+
/// we encrypt the namespace identifier using these bytes.
980+
///
981+
/// [fake scids]: crate::util::scid_utils::fake_scid
982+
fake_scid_rand_bytes: [u8; 32],
983+
976984
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
977985
/// value increases strictly since we don't assume access to a time source.
978986
last_node_announcement_serial: AtomicUsize,
@@ -1309,6 +1317,19 @@ pub enum PaymentSendFailure {
13091317
},
13101318
}
13111319

1320+
/// Route hints used in constructing invoices for [phantom node payents].
1321+
///
1322+
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
1323+
pub struct PhantomRouteHints {
1324+
/// The list of channels to be included in the invoice route hints.
1325+
pub channels: Vec<ChannelDetails>,
1326+
/// A fake scid used for representing the phantom node's fake channel in generating the invoice
1327+
/// route hints.
1328+
pub phantom_scid: u64,
1329+
/// The pubkey of the real backing node that would ultimately receive the payment.
1330+
pub real_node_pubkey: PublicKey,
1331+
}
1332+
13121333
macro_rules! handle_error {
13131334
($self: ident, $internal: expr, $counterparty_node_id: expr) => {
13141335
match $internal {
@@ -1690,6 +1711,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16901711
secp_ctx,
16911712

16921713
inbound_payment_key: expanded_inbound_key,
1714+
fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
16931715

16941716
last_node_announcement_serial: AtomicUsize::new(0),
16951717
highest_seen_timestamp: AtomicUsize::new(0),
@@ -5089,6 +5111,34 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
50895111
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
50905112
}
50915113

5114+
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
5115+
/// are used when constructing the phantom invoice's route hints.
5116+
///
5117+
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
5118+
pub fn get_phantom_scid(&self) -> u64 {
5119+
let mut channel_state = self.channel_state.lock().unwrap();
5120+
let best_block = self.best_block.read().unwrap();
5121+
loop {
5122+
let scid_candidate = fake_scid::get_phantom_scid(&self.fake_scid_rand_bytes, best_block.height(), &self.genesis_hash, &self.keys_manager);
5123+
// Ensure the generated scid doesn't conflict with a real channel.
5124+
match channel_state.short_to_id.entry(scid_candidate) {
5125+
hash_map::Entry::Occupied(_) => continue,
5126+
hash_map::Entry::Vacant(_) => return scid_candidate
5127+
}
5128+
}
5129+
}
5130+
5131+
/// Gets route hints for use in receiving [phantom node payments].
5132+
///
5133+
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
5134+
pub fn get_phantom_route_hints(&self) -> PhantomRouteHints {
5135+
PhantomRouteHints {
5136+
channels: self.list_usable_channels(),
5137+
phantom_scid: self.get_phantom_scid(),
5138+
real_node_pubkey: self.get_our_node_id(),
5139+
}
5140+
}
5141+
50925142
#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
50935143
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
50945144
let events = core::cell::RefCell::new(Vec::new());
@@ -5819,6 +5869,12 @@ impl_writeable_tlv_based!(ChannelDetails, {
58195869
(32, is_public, required),
58205870
});
58215871

5872+
impl_writeable_tlv_based!(PhantomRouteHints, {
5873+
(2, channels, vec_type),
5874+
(4, phantom_scid, required),
5875+
(6, real_node_pubkey, required),
5876+
});
5877+
58225878
impl_writeable_tlv_based_enum!(PendingHTLCRouting,
58235879
(0, Forward) => {
58245880
(0, onion_packet, required),
@@ -6220,7 +6276,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
62206276
write_tlv_fields!(writer, {
62216277
(1, pending_outbound_payments_no_retry, required),
62226278
(3, pending_outbound_payments, required),
6223-
(5, self.our_network_pubkey, required)
6279+
(5, self.our_network_pubkey, required),
6280+
(7, self.fake_scid_rand_bytes, required),
62246281
});
62256282

62266283
Ok(())
@@ -6516,11 +6573,16 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
65166573
let mut pending_outbound_payments_no_retry: Option<HashMap<PaymentId, HashSet<[u8; 32]>>> = None;
65176574
let mut pending_outbound_payments = None;
65186575
let mut received_network_pubkey: Option<PublicKey> = None;
6576+
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
65196577
read_tlv_fields!(reader, {
65206578
(1, pending_outbound_payments_no_retry, option),
65216579
(3, pending_outbound_payments, option),
6522-
(5, received_network_pubkey, option)
6580+
(5, received_network_pubkey, option),
6581+
(7, fake_scid_rand_bytes, option),
65236582
});
6583+
if fake_scid_rand_bytes.is_none() {
6584+
fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
6585+
}
65246586

65256587
if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
65266588
pending_outbound_payments = Some(pending_outbound_payments_compat);
@@ -6616,6 +6678,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
66166678
inbound_payment_key: expanded_inbound_key,
66176679
pending_inbound_payments: Mutex::new(pending_inbound_payments),
66186680
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
6681+
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
66196682

66206683
our_network_key,
66216684
our_network_pubkey,

lightning/src/util/scid_utils.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,176 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result<u64
6060
Ok((block << 40) | (tx_index << 16) | vout_index)
6161
}
6262

63+
/// LDK has multiple reasons to generate fake short channel ids:
64+
/// 1) zero-conf channels that don't have a confirmed channel id yet
65+
/// 2) phantom node payments, to get an scid for the phantom node's phantom channel
66+
pub(crate) mod fake_scid {
67+
use bitcoin::hash_types::BlockHash;
68+
use bitcoin::hashes::hex::FromHex;
69+
use chain::keysinterface::{Sign, KeysInterface};
70+
use util::chacha20::ChaCha20;
71+
use util::scid_utils;
72+
73+
use core::convert::TryInto;
74+
use core::ops::Deref;
75+
76+
const TEST_SEGWIT_ACTIVATION_HEIGHT: u32 = 0;
77+
const MAINNET_SEGWIT_ACTIVATION_HEIGHT: u32 = 481_824;
78+
const MAX_TX_INDEX: u32 = 2_500;
79+
const MAX_NAMESPACES: u8 = 8; // We allocate 3 bits for the namespace identifier.
80+
const NAMESPACE_ID_BITMASK: u8 = 0b111;
81+
82+
/// Fake scids are divided into namespaces, with each namespace having its own identifier between
83+
/// [0..7]. This allows us to identify what namespace a fake scid corresponds to upon HTLC
84+
/// receipt, and handle the HTLC accordingly. The namespace identifier is encrypted when encoded
85+
/// into the fake scid.
86+
#[derive(Copy, Clone)]
87+
pub(super) enum Namespace {
88+
Phantom,
89+
// Coming soon: a variant for the zero-conf scid namespace
90+
}
91+
92+
impl Namespace {
93+
/// We generate "realistic-looking" random scids here, meaning the scid's block height is
94+
/// between segwit activation and the current best known height, and the tx index and output
95+
/// index are also selected from a "reasonable" range. We add this logic because it makes it
96+
/// non-obvious at a glance that the scid is fake, e.g. if it appears in invoice route hints.
97+
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
98+
where K::Target: KeysInterface<Signer = Signer>,
99+
{
100+
// Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for
101+
// namespaces.
102+
assert!((*self as u8) < MAX_NAMESPACES);
103+
const BLOCKS_PER_MONTH: u32 = 144 /* blocks per day */ * 30 /* days per month */;
104+
let rand_bytes = keys_manager.get_secure_random_bytes();
105+
106+
let segwit_activation_height = segwit_activation_height(genesis_hash);
107+
let mut valid_block_range = if highest_seen_blockheight > segwit_activation_height {
108+
highest_seen_blockheight - segwit_activation_height
109+
} else {
110+
1
111+
};
112+
// We want to ensure that this fake channel won't conflict with any transactions we haven't
113+
// seen yet, in case `highest_seen_blockheight` is updated before we get full information
114+
// about transactions confirmed in the given block.
115+
if valid_block_range > BLOCKS_PER_MONTH { valid_block_range -= BLOCKS_PER_MONTH; }
116+
117+
let rand_for_height = u32::from_be_bytes(rand_bytes[..4].try_into().unwrap());
118+
let fake_scid_height = segwit_activation_height + rand_for_height % valid_block_range;
119+
120+
let rand_for_tx_index = u32::from_be_bytes(rand_bytes[4..8].try_into().unwrap());
121+
let fake_scid_tx_index = rand_for_tx_index % MAX_TX_INDEX;
122+
123+
// Put the scid in the given namespace.
124+
let fake_scid_vout = self.get_encrypted_vout(fake_scid_height, fake_scid_tx_index, fake_scid_rand_bytes);
125+
scid_utils::scid_from_parts(fake_scid_height as u64, fake_scid_tx_index as u64, fake_scid_vout as u64).unwrap()
126+
}
127+
128+
/// We want to ensure that a 3rd party can't identify a payment as belong to a given
129+
/// `Namespace`. Therefore, we encrypt it using a random bytes provided by `ChannelManager`.
130+
fn get_encrypted_vout(&self, block_height: u32, tx_index: u32, fake_scid_rand_bytes: &[u8; 32]) -> u8 {
131+
let mut salt = [0 as u8; 8];
132+
let block_height_bytes = block_height.to_be_bytes();
133+
salt[0..4].copy_from_slice(&block_height_bytes);
134+
let tx_index_bytes = tx_index.to_be_bytes();
135+
salt[4..8].copy_from_slice(&tx_index_bytes);
136+
137+
let mut chacha = ChaCha20::new(fake_scid_rand_bytes, &salt);
138+
let mut vout_byte = [*self as u8];
139+
chacha.process_in_place(&mut vout_byte);
140+
vout_byte[0] & NAMESPACE_ID_BITMASK
141+
}
142+
}
143+
144+
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
145+
where K::Target: KeysInterface<Signer = Signer>,
146+
{
147+
let namespace = Namespace::Phantom;
148+
namespace.get_fake_scid(highest_seen_blockheight, genesis_hash, fake_scid_rand_bytes, keys_manager)
149+
}
150+
151+
fn segwit_activation_height(genesis: &BlockHash) -> u32 {
152+
const MAINNET_GENESIS_STR: &'static str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
153+
if BlockHash::from_hex(MAINNET_GENESIS_STR).unwrap() == *genesis {
154+
MAINNET_SEGWIT_ACTIVATION_HEIGHT
155+
} else {
156+
TEST_SEGWIT_ACTIVATION_HEIGHT
157+
}
158+
}
159+
160+
/// Returns whether the given fake scid falls into the given namespace.
161+
pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool {
162+
let block_height = scid_utils::block_from_scid(&scid);
163+
let tx_index = scid_utils::tx_index_from_scid(&scid);
164+
let namespace = Namespace::Phantom;
165+
let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes);
166+
valid_vout == scid_utils::vout_from_scid(&scid) as u8
167+
}
168+
169+
#[cfg(test)]
170+
mod tests {
171+
use bitcoin::blockdata::constants::genesis_block;
172+
use bitcoin::network::constants::Network;
173+
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};
174+
use util::scid_utils;
175+
use util::test_utils;
176+
use sync::Arc;
177+
178+
#[test]
179+
fn namespace_identifier_is_within_range() {
180+
let phantom_namespace = Namespace::Phantom;
181+
assert!((phantom_namespace as u8) < MAX_NAMESPACES);
182+
assert!((phantom_namespace as u8) <= NAMESPACE_ID_BITMASK);
183+
}
184+
185+
#[test]
186+
fn test_segwit_activation_height() {
187+
let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
188+
assert_eq!(segwit_activation_height(&mainnet_genesis), MAINNET_SEGWIT_ACTIVATION_HEIGHT);
189+
190+
let testnet_genesis = genesis_block(Network::Testnet).header.block_hash();
191+
assert_eq!(segwit_activation_height(&testnet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
192+
193+
let signet_genesis = genesis_block(Network::Signet).header.block_hash();
194+
assert_eq!(segwit_activation_height(&signet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
195+
196+
let regtest_genesis = genesis_block(Network::Regtest).header.block_hash();
197+
assert_eq!(segwit_activation_height(&regtest_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
198+
}
199+
200+
#[test]
201+
fn test_is_valid_phantom() {
202+
let namespace = Namespace::Phantom;
203+
let fake_scid_rand_bytes = [0; 32];
204+
let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes);
205+
let valid_fake_scid = scid_utils::scid_from_parts(0, 0, valid_encrypted_vout as u64).unwrap();
206+
assert!(is_valid_phantom(&fake_scid_rand_bytes, valid_fake_scid));
207+
let invalid_fake_scid = scid_utils::scid_from_parts(0, 0, 12).unwrap();
208+
assert!(!is_valid_phantom(&fake_scid_rand_bytes, invalid_fake_scid));
209+
}
210+
211+
#[test]
212+
fn test_get_fake_scid() {
213+
let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
214+
let seed = [0; 32];
215+
let fake_scid_rand_bytes = [1; 32];
216+
let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet));
217+
let namespace = Namespace::Phantom;
218+
let fake_scid = namespace.get_fake_scid(500_000, &mainnet_genesis, &fake_scid_rand_bytes, &keys_manager);
219+
220+
let fake_height = scid_utils::block_from_scid(&fake_scid);
221+
assert!(fake_height >= MAINNET_SEGWIT_ACTIVATION_HEIGHT);
222+
assert!(fake_height <= 500_000);
223+
224+
let fake_tx_index = scid_utils::tx_index_from_scid(&fake_scid);
225+
assert!(fake_tx_index <= MAX_TX_INDEX);
226+
227+
let fake_vout = scid_utils::vout_from_scid(&fake_scid);
228+
assert!(fake_vout < MAX_NAMESPACES as u16);
229+
}
230+
}
231+
}
232+
63233
#[cfg(test)]
64234
mod tests {
65235
use super::*;

0 commit comments

Comments
 (0)