From: Valentine Wallace Date: Fri, 24 Mar 2023 16:46:16 +0000 (-0400) Subject: Receive payment onions as new InboundPayload instead of OnionHopData X-Git-Tag: v0.0.117-alpha1~67^2~2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=02a6d895a576d24c612d83c834f30ea2fd7ab67d;p=rust-lightning Receive payment onions as new InboundPayload instead of OnionHopData To support route blinding, we want to split OnionHopData into two separate structs, one for inbound onions and one for outbound onions. This is because blinded payloads change the fields present in the onion hop data struct based on whether we're sending vs receiving (outbound onions include encrypted blobs, inbound onions can decrypt those blobs and contain the decrypted fields themselves). In upcoming commits, we'll add variants for blinded payloads to the new InboundPayload enum. --- diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index 34cae5107..fe17e4bab 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -20,6 +20,7 @@ GEN_TEST refund_deser GEN_TEST router GEN_TEST zbase32 GEN_TEST indexedmap +GEN_TEST onion_hop_data GEN_TEST msg_accept_channel msg_targets:: GEN_TEST msg_announcement_signatures msg_targets:: @@ -51,7 +52,6 @@ GEN_TEST msg_update_add_htlc msg_targets:: GEN_TEST msg_error_message msg_targets:: GEN_TEST msg_channel_update msg_targets:: -GEN_TEST msg_onion_hop_data msg_targets:: GEN_TEST msg_ping msg_targets:: GEN_TEST msg_pong msg_targets:: diff --git a/fuzz/src/bin/msg_onion_hop_data_target.rs b/fuzz/src/bin/msg_onion_hop_data_target.rs deleted file mode 100644 index ae21e9bd9..000000000 --- a/fuzz/src/bin/msg_onion_hop_data_target.rs +++ /dev/null @@ -1,113 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -// This file is auto-generated by gen_target.sh based on target_template.txt -// To modify it, modify target_template.txt and run gen_target.sh instead. - -#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] - -#[cfg(not(fuzzing))] -compile_error!("Fuzz targets need cfg=fuzzing"); - -extern crate lightning_fuzz; -use lightning_fuzz::msg_targets::msg_onion_hop_data::*; - -#[cfg(feature = "afl")] -#[macro_use] extern crate afl; -#[cfg(feature = "afl")] -fn main() { - fuzz!(|data| { - msg_onion_hop_data_run(data.as_ptr(), data.len()); - }); -} - -#[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; -#[cfg(feature = "honggfuzz")] -fn main() { - loop { - fuzz!(|data| { - msg_onion_hop_data_run(data.as_ptr(), data.len()); - }); - } -} - -#[cfg(feature = "libfuzzer_fuzz")] -#[macro_use] extern crate libfuzzer_sys; -#[cfg(feature = "libfuzzer_fuzz")] -fuzz_target!(|data: &[u8]| { - msg_onion_hop_data_run(data.as_ptr(), data.len()); -}); - -#[cfg(feature = "stdin_fuzz")] -fn main() { - use std::io::Read; - - let mut data = Vec::with_capacity(8192); - std::io::stdin().read_to_end(&mut data).unwrap(); - msg_onion_hop_data_run(data.as_ptr(), data.len()); -} - -#[test] -fn run_test_cases() { - use std::fs; - use std::io::Read; - use lightning_fuzz::utils::test_logger::StringBuffer; - - use std::sync::{atomic, Arc}; - { - let data: Vec = vec![0]; - msg_onion_hop_data_run(data.as_ptr(), data.len()); - } - let mut threads = Vec::new(); - let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_onion_hop_data") { - for test in tests { - let mut data: Vec = Vec::new(); - let path = test.unwrap().path(); - fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); - threads_running.fetch_add(1, atomic::Ordering::AcqRel); - - let thread_count_ref = Arc::clone(&threads_running); - let main_thread_ref = std::thread::current(); - threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), - std::thread::spawn(move || { - let string_logger = StringBuffer::new(); - - let panic_logger = string_logger.clone(); - let res = if ::std::panic::catch_unwind(move || { - msg_onion_hop_data_test(&data, panic_logger); - }).is_err() { - Some(string_logger.into_string()) - } else { None }; - thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); - main_thread_ref.unpark(); - res - }) - )); - while threads_running.load(atomic::Ordering::Acquire) > 32 { - std::thread::park(); - } - } - } - let mut failed_outputs = Vec::new(); - for (test, thread) in threads.drain(..) { - if let Some(output) = thread.join().unwrap() { - println!("\nOutput of {}:\n{}\n", test, output); - failed_outputs.push(test); - } - } - if !failed_outputs.is_empty() { - println!("Test cases which failed: "); - for case in failed_outputs { - println!("{}", case); - } - panic!(); - } -} diff --git a/fuzz/src/bin/onion_hop_data_target.rs b/fuzz/src/bin/onion_hop_data_target.rs new file mode 100644 index 000000000..b8a357229 --- /dev/null +++ b/fuzz/src/bin/onion_hop_data_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::onion_hop_data::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + onion_hop_data_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + onion_hop_data_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + onion_hop_data_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + onion_hop_data_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + onion_hop_data_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/onion_hop_data") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + onion_hop_data_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 92142e564..6cdeb8ab5 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -28,5 +28,6 @@ pub mod process_network_graph; pub mod refund_deser; pub mod router; pub mod zbase32; +pub mod onion_hop_data; pub mod msg_targets; diff --git a/fuzz/src/msg_targets/gen_target.sh b/fuzz/src/msg_targets/gen_target.sh index 3937c5001..d89df19d9 100755 --- a/fuzz/src/msg_targets/gen_target.sh +++ b/fuzz/src/msg_targets/gen_target.sh @@ -20,7 +20,6 @@ GEN_TEST lightning::ln::msgs::ChannelReady test_msg_simple "" GEN_TEST lightning::ln::msgs::FundingSigned test_msg_simple "" GEN_TEST lightning::ln::msgs::GossipTimestampFilter test_msg_simple "" GEN_TEST lightning::ln::msgs::Init test_msg_simple "" -GEN_TEST lightning::ln::msgs::OnionHopData test_msg_simple "" GEN_TEST lightning::ln::msgs::OpenChannel test_msg_simple "" GEN_TEST lightning::ln::msgs::Ping test_msg_simple "" GEN_TEST lightning::ln::msgs::Pong test_msg_simple "" diff --git a/fuzz/src/msg_targets/mod.rs b/fuzz/src/msg_targets/mod.rs index fe3bd14a7..302dda440 100644 --- a/fuzz/src/msg_targets/mod.rs +++ b/fuzz/src/msg_targets/mod.rs @@ -8,7 +8,6 @@ pub mod msg_channel_ready; pub mod msg_funding_signed; pub mod msg_gossip_timestamp_filter; pub mod msg_init; -pub mod msg_onion_hop_data; pub mod msg_open_channel; pub mod msg_ping; pub mod msg_pong; diff --git a/fuzz/src/msg_targets/msg_onion_hop_data.rs b/fuzz/src/msg_targets/msg_onion_hop_data.rs deleted file mode 100644 index 59b3674f9..000000000 --- a/fuzz/src/msg_targets/msg_onion_hop_data.rs +++ /dev/null @@ -1,25 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -// This file is auto-generated by gen_target.sh based on msg_target_template.txt -// To modify it, modify msg_target_template.txt and run gen_target.sh instead. - -use crate::msg_targets::utils::VecWriter; -use crate::utils::test_logger; - -#[inline] -pub fn msg_onion_hop_data_test(data: &[u8], _out: Out) { - test_msg_simple!(lightning::ln::msgs::OnionHopData, data); -} - -#[no_mangle] -pub extern "C" fn msg_onion_hop_data_run(data: *const u8, datalen: usize) { - let data = unsafe { std::slice::from_raw_parts(data, datalen) }; - test_msg_simple!(lightning::ln::msgs::OnionHopData, data); -} diff --git a/fuzz/src/onion_hop_data.rs b/fuzz/src/onion_hop_data.rs new file mode 100644 index 000000000..54b283ab0 --- /dev/null +++ b/fuzz/src/onion_hop_data.rs @@ -0,0 +1,28 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::utils::test_logger; + +#[inline] +pub fn onion_hop_data_test(data: &[u8], _out: Out) { + use lightning::util::ser::Readable; + let mut r = ::std::io::Cursor::new(data); + let _ = ::read(&mut r); +} + +#[no_mangle] +pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) { + use lightning::util::ser::Readable; + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + let mut r = ::std::io::Cursor::new(data); + let _ = ::read(&mut r); +} diff --git a/fuzz/targets.h b/fuzz/targets.h index eb8d66f41..9b5a6d455 100644 --- a/fuzz/targets.h +++ b/fuzz/targets.h @@ -13,6 +13,7 @@ void refund_deser_run(const unsigned char* data, size_t data_len); void router_run(const unsigned char* data, size_t data_len); void zbase32_run(const unsigned char* data, size_t data_len); void indexedmap_run(const unsigned char* data, size_t data_len); +void onion_hop_data_run(const unsigned char* data, size_t data_len); void msg_accept_channel_run(const unsigned char* data, size_t data_len); void msg_announcement_signatures_run(const unsigned char* data, size_t data_len); void msg_channel_reestablish_run(const unsigned char* data, size_t data_len); @@ -40,7 +41,6 @@ void msg_gossip_timestamp_filter_run(const unsigned char* data, size_t data_len) void msg_update_add_htlc_run(const unsigned char* data, size_t data_len); void msg_error_message_run(const unsigned char* data, size_t data_len); void msg_channel_update_run(const unsigned char* data, size_t data_len); -void msg_onion_hop_data_run(const unsigned char* data, size_t data_len); void msg_ping_run(const unsigned char* data, size_t data_len); void msg_pong_run(const unsigned char* data, size_t data_len); void msg_channel_details_run(const unsigned char* data, size_t data_len); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 2abd3c3b9..6bfa4f0b0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2617,7 +2617,7 @@ where } fn construct_fwd_pending_htlc_info( - &self, msg: &msgs::UpdateAddHTLC, hop_data: msgs::OnionHopData, hop_hmac: [u8; 32], + &self, msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundOnionPayload, hop_hmac: [u8; 32], new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32], next_packet_pubkey_opt: Option> ) -> Result { @@ -2626,18 +2626,18 @@ where version: 0, public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)), hop_data: new_packet_bytes, - hmac: hop_hmac.clone(), + hmac: hop_hmac, }; - let short_channel_id = match hop_data.format { - msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id, - msgs::OnionHopDataFormat::FinalNode { .. } => { + let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data { + msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => + (short_channel_id, amt_to_forward, outgoing_cltv_value), + msgs::InboundOnionPayload::Receive { .. } => return Err(InboundOnionErr { msg: "Final Node OnionHopData provided for us as an intermediary node", err_code: 0x4000 | 22, err_data: Vec::new(), - }) - }, + }), }; Ok(PendingHTLCInfo { @@ -2645,23 +2645,25 @@ where onion_packet: outgoing_packet, short_channel_id, }, - payment_hash: msg.payment_hash.clone(), + payment_hash: msg.payment_hash, incoming_shared_secret: shared_secret, incoming_amt_msat: Some(msg.amount_msat), - outgoing_amt_msat: hop_data.amt_to_forward, - outgoing_cltv_value: hop_data.outgoing_cltv_value, + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, skimmed_fee_msat: None, }) } fn construct_recv_pending_htlc_info( - &self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash, + &self, hop_data: msgs::InboundOnionPayload, shared_secret: [u8; 32], payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool, counterparty_skimmed_fee_msat: Option, ) -> Result { - let (payment_data, keysend_preimage, payment_metadata) = match hop_data.format { - msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, payment_metadata } => - (payment_data, keysend_preimage, payment_metadata), + let (payment_data, keysend_preimage, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data { + msgs::InboundOnionPayload::Receive { + payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata, .. + } => + (payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata), _ => return Err(InboundOnionErr { err_code: 0x4000|22, @@ -2670,7 +2672,7 @@ where }), }; // final_incorrect_cltv_expiry - if hop_data.outgoing_cltv_value > cltv_expiry { + if outgoing_cltv_value > cltv_expiry { return Err(InboundOnionErr { msg: "Upstream node set CLTV to less than the CLTV set by the sender", err_code: 18, @@ -2685,7 +2687,7 @@ where // payment logic has enough time to fail the HTLC backward before our onchain logic triggers a // channel closure (see HTLC_FAIL_BACK_BUFFER rationale). let current_height: u32 = self.best_block.read().unwrap().height(); - if (hop_data.outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 { + if (outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 { let mut err_data = Vec::with_capacity(12); err_data.extend_from_slice(&amt_msat.to_be_bytes()); err_data.extend_from_slice(¤t_height.to_be_bytes()); @@ -2694,8 +2696,8 @@ where msg: "The final CLTV expiry is too soon to handle", }); } - if (!allow_underpay && hop_data.amt_to_forward > amt_msat) || - (allow_underpay && hop_data.amt_to_forward > + if (!allow_underpay && onion_amt_msat > amt_msat) || + (allow_underpay && onion_amt_msat > amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0))) { return Err(InboundOnionErr { @@ -2730,13 +2732,13 @@ where payment_data, payment_preimage, payment_metadata, - incoming_cltv_expiry: hop_data.outgoing_cltv_value, + incoming_cltv_expiry: outgoing_cltv_value, } } else if let Some(data) = payment_data { PendingHTLCRouting::Receive { payment_data: data, payment_metadata, - incoming_cltv_expiry: hop_data.outgoing_cltv_value, + incoming_cltv_expiry: outgoing_cltv_value, phantom_shared_secret, } } else { @@ -2751,8 +2753,8 @@ where payment_hash, incoming_shared_secret: shared_secret, incoming_amt_msat: Some(amt_msat), - outgoing_amt_msat: hop_data.amt_to_forward, - outgoing_cltv_value: hop_data.outgoing_cltv_value, + outgoing_amt_msat: onion_amt_msat, + outgoing_cltv_value, skimmed_fee_msat: counterparty_skimmed_fee_msat, }) } @@ -2816,9 +2818,8 @@ where }; let (outgoing_scid, outgoing_amt_msat, outgoing_cltv_value, next_packet_pk_opt) = match next_hop { onion_utils::Hop::Forward { - next_hop_data: msgs::OnionHopData { - format: msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward, - outgoing_cltv_value, + next_hop_data: msgs::InboundOnionPayload::Forward { + short_channel_id, amt_to_forward, outgoing_cltv_value }, .. } => { let next_pk = onion_utils::next_hop_packet_pubkey(&self.secp_ctx, @@ -2828,9 +2829,7 @@ where // We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the // inbound channel's state. onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), - onion_utils::Hop::Forward { - next_hop_data: msgs::OnionHopData { format: msgs::OnionHopDataFormat::FinalNode { .. }, .. }, .. - } => { + onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } => { return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]); } }; @@ -10020,16 +10019,14 @@ mod tests { let node = create_network(1, &node_cfg, &node_chanmgr); let sender_intended_amt_msat = 100; let extra_fee_msat = 10; - let hop_data = msgs::OnionHopData { - amt_to_forward: 100, + let hop_data = msgs::InboundOnionPayload::Receive { + amt_msat: 100, outgoing_cltv_value: 42, - format: msgs::OnionHopDataFormat::FinalNode { - keysend_preimage: None, - payment_metadata: None, - payment_data: Some(msgs::FinalOnionHopData { - payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, - }), - } + payment_metadata: None, + keysend_preimage: None, + payment_data: Some(msgs::FinalOnionHopData { + payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, + }), }; // Check that if the amount we received + the penultimate hop extra fee is less than the sender // intended amount, we fail the payment. @@ -10041,16 +10038,14 @@ mod tests { } else { panic!(); } // If amt_received + extra_fee is equal to the sender intended amount, we're fine. - let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone - amt_to_forward: 100, + let hop_data = msgs::InboundOnionPayload::Receive { // This is the same payload as above, InboundOnionPayload doesn't implement Clone + amt_msat: 100, outgoing_cltv_value: 42, - format: msgs::OnionHopDataFormat::FinalNode { - keysend_preimage: None, - payment_metadata: None, - payment_data: Some(msgs::FinalOnionHopData { - payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, - }), - } + payment_metadata: None, + keysend_preimage: None, + payment_data: Some(msgs::FinalOnionHopData { + payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, + }), }; assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok()); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index cfee16033..590b26632 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1419,11 +1419,27 @@ mod fuzzy_internal_msgs { // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): #[derive(Clone)] - pub(crate) struct FinalOnionHopData { - pub(crate) payment_secret: PaymentSecret, + pub struct FinalOnionHopData { + pub payment_secret: PaymentSecret, /// The total value, in msat, of the payment as received by the ultimate recipient. /// Message serialization may panic if this value is more than 21 million Bitcoin. - pub(crate) total_msat: u64, + pub total_msat: u64, + } + + pub enum InboundOnionPayload { + Forward { + short_channel_id: u64, + /// The value, in msat, of the payment after this hop's fee is deducted. + amt_to_forward: u64, + outgoing_cltv_value: u32, + }, + Receive { + payment_data: Option, + payment_metadata: Option>, + keysend_preimage: Option, + amt_msat: u64, + outgoing_cltv_value: u32, + }, } pub(crate) enum OnionHopDataFormat { @@ -1974,7 +1990,7 @@ impl Writeable for OnionHopData { } } -impl Readable for OnionHopData { +impl Readable for InboundOnionPayload { fn read(r: &mut R) -> Result { let mut amt = HighZeroBytesDroppedBigSize(0u64); let mut cltv_value = HighZeroBytesDroppedBigSize(0u32); @@ -1992,39 +2008,35 @@ impl Readable for OnionHopData { (5482373484, keysend_preimage, option) }); - let format = if let Some(short_channel_id) = short_id { - if payment_data.is_some() { return Err(DecodeError::InvalidValue); } + if amt.0 > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + if let Some(short_channel_id) = short_id { + if payment_data.is_some() { return Err(DecodeError::InvalidValue) } if payment_metadata.is_some() { return Err(DecodeError::InvalidValue); } - OnionHopDataFormat::NonFinalNode { + Ok(Self::Forward { short_channel_id, - } + amt_to_forward: amt.0, + outgoing_cltv_value: cltv_value.0, + }) } else { if let Some(data) = &payment_data { if data.total_msat > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue); } } - OnionHopDataFormat::FinalNode { + Ok(Self::Receive { payment_data, payment_metadata: payment_metadata.map(|w| w.0), keysend_preimage, - } - }; - - if amt.0 > MAX_VALUE_MSAT { - return Err(DecodeError::InvalidValue); + amt_msat: amt.0, + outgoing_cltv_value: cltv_value.0, + }) } - Ok(OnionHopData { - format, - amt_to_forward: amt.0, - outgoing_cltv_value: cltv_value.0, - }) } } // ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and // onion message packets. -impl ReadableArgs<()> for OnionHopData { +impl ReadableArgs<()> for InboundOnionPayload { fn read(r: &mut R, _arg: ()) -> Result { ::read(r) } @@ -3525,7 +3537,7 @@ mod tests { #[test] fn encoding_nonfinal_onion_hop_data() { - let mut msg = msgs::OnionHopData { + let msg = msgs::OnionHopData { format: OnionHopDataFormat::NonFinalNode { short_channel_id: 0xdeadbeef1bad1dea, }, @@ -3535,17 +3547,18 @@ mod tests { let encoded_value = msg.encode(); let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap(); assert_eq!(encoded_value, target_value); - msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); - if let OnionHopDataFormat::NonFinalNode { short_channel_id } = msg.format { + + let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + if let msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } = inbound_msg { assert_eq!(short_channel_id, 0xdeadbeef1bad1dea); + assert_eq!(amt_to_forward, 0x0badf00d01020304); + assert_eq!(outgoing_cltv_value, 0xffffffff); } else { panic!(); } - assert_eq!(msg.amt_to_forward, 0x0badf00d01020304); - assert_eq!(msg.outgoing_cltv_value, 0xffffffff); } #[test] fn encoding_final_onion_hop_data() { - let mut msg = msgs::OnionHopData { + let msg = msgs::OnionHopData { format: OnionHopDataFormat::FinalNode { payment_data: None, payment_metadata: None, @@ -3557,16 +3570,18 @@ mod tests { let encoded_value = msg.encode(); let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap(); assert_eq!(encoded_value, target_value); - msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); - if let OnionHopDataFormat::FinalNode { payment_data: None, .. } = msg.format { } else { panic!(); } - assert_eq!(msg.amt_to_forward, 0x0badf00d01020304); - assert_eq!(msg.outgoing_cltv_value, 0xffffffff); + + let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + if let msgs::InboundOnionPayload::Receive { payment_data: None, amt_msat, outgoing_cltv_value, .. } = inbound_msg { + assert_eq!(amt_msat, 0x0badf00d01020304); + assert_eq!(outgoing_cltv_value, 0xffffffff); + } else { panic!(); } } #[test] fn encoding_final_onion_hop_data_with_secret() { let expected_payment_secret = PaymentSecret([0x42u8; 32]); - let mut msg = msgs::OnionHopData { + let msg = msgs::OnionHopData { format: OnionHopDataFormat::FinalNode { payment_data: Some(FinalOnionHopData { payment_secret: expected_payment_secret, @@ -3581,19 +3596,21 @@ mod tests { let encoded_value = msg.encode(); let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap(); assert_eq!(encoded_value, target_value); - msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); - if let OnionHopDataFormat::FinalNode { + + let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + if let msgs::InboundOnionPayload::Receive { payment_data: Some(FinalOnionHopData { payment_secret, total_msat: 0x1badca1f }), + amt_msat, outgoing_cltv_value, payment_metadata: None, keysend_preimage: None, - } = msg.format { + } = inbound_msg { assert_eq!(payment_secret, expected_payment_secret); + assert_eq!(amt_msat, 0x0badf00d01020304); + assert_eq!(outgoing_cltv_value, 0xffffffff); } else { panic!(); } - assert_eq!(msg.amt_to_forward, 0x0badf00d01020304); - assert_eq!(msg.outgoing_cltv_value, 0xffffffff); } #[test] @@ -3743,7 +3760,7 @@ mod tests { // payload length to be encoded over multiple bytes rather than a single u8. let big_payload = encode_big_payload().unwrap(); let mut rd = Cursor::new(&big_payload[..]); - ::read(&mut rd).unwrap(); + ::read(&mut rd).unwrap(); } // see above test, needs to be a separate method for use of the serialization macros. fn encode_big_payload() -> Result, io::Error> { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 52eb7bcb5..a915cf29d 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -763,11 +763,11 @@ impl NextPacketBytes for Vec { 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), + Receive(msgs::InboundOnionPayload), /// 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, + next_hop_data: msgs::InboundOnionPayload, /// HMAC of the next hop's onion packet. next_hop_hmac: [u8; 32], /// Bytes of the onion packet we're forwarding.