Merge pull request #2752 from valentinewallace/2023-11-large-final-onion-payload-fixes

Large final onion payload fixes
This commit is contained in:
Matt Corallo 2023-12-08 23:53:27 +00:00 committed by GitHub
commit 0c677533fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 16 deletions

View file

@ -23,6 +23,7 @@ use crate::prelude::*;
use core::ops::Deref;
/// Invalid inbound onion payment.
#[derive(Debug)]
pub struct InboundOnionErr {
/// BOLT 4 error code.
pub err_code: u16,
@ -448,7 +449,7 @@ pub(super) fn check_incoming_htlc_cltv(
mod tests {
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::ChannelId;
use crate::ln::channelmanager::RecipientOnionFields;
@ -458,6 +459,38 @@ mod tests {
use crate::routing::router::{Path, RouteHop};
use crate::util::test_utils;
#[test]
fn fail_construct_onion_on_too_big_payloads() {
// Ensure that if we call `construct_onion_packet` and friends where payloads are too large for
// the allotted packet length, we'll fail to construct. Previously, senders would happily
// construct invalid packets by array-shifting the final node's HMAC out of the packet when
// adding an intermediate onion layer, causing the receiver to error with "final payload
// provided for us as an intermediate node."
let secp_ctx = Secp256k1::new();
let bob = crate::sign::KeysManager::new(&[2; 32], 42, 42);
let bob_pk = PublicKey::from_secret_key(&secp_ctx, &bob.get_node_secret_key());
let charlie = crate::sign::KeysManager::new(&[3; 32], 42, 42);
let charlie_pk = PublicKey::from_secret_key(&secp_ctx, &charlie.get_node_secret_key());
let (
session_priv, total_amt_msat, cur_height, mut recipient_onion, keysend_preimage, payment_hash,
prng_seed, hops, ..
) = payment_onion_args(bob_pk, charlie_pk);
// Ensure the onion will not fit all the payloads by adding a large custom TLV.
recipient_onion.custom_tlvs.push((13377331, vec![0; 1156]));
let path = Path { hops, blinded_tail: None, };
let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap();
let (onion_payloads, ..) = super::onion_utils::build_onion_payloads(
&path, total_amt_msat, recipient_onion, cur_height + 1, &Some(keysend_preimage)
).unwrap();
assert!(super::onion_utils::construct_onion_packet(
onion_payloads, onion_keys, prng_seed, &payment_hash
).is_err());
}
#[test]
fn test_peel_payment_onion() {
use super::*;

View file

@ -323,8 +323,6 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
let mut pos = 0;
for (i, (payload, keys)) in payloads.iter().zip(onion_keys.iter()).enumerate() {
if i == payloads.len() - 1 { break; }
let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
for _ in 0..(packet_data.len() - pos) { // TODO: Batch this.
let mut dummy = [0; 1];
@ -338,6 +336,8 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
return Err(());
}
if i == payloads.len() - 1 { break; }
res.resize(pos, 0u8);
chacha.process_in_place(&mut res);
}
@ -1021,6 +1021,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8
if hmac == [0; 32] {
#[cfg(test)]
{
if chacha_stream.read.position() < hop_data.len() as u64 - 64 {
// 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
@ -1034,6 +1035,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8
chacha_stream.read_exact(&mut next_bytes).unwrap();
assert_ne!(next_bytes[..], [0; 32][..]);
}
}
return Ok((msg, None)); // We are the final destination for this packet
} else {
let mut new_packet_bytes = N::new(hop_data.len());

View file

@ -19,8 +19,9 @@ use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, Mes
use crate::ln::channel::{EXPIRE_PREV_CONFIG_TICKS, commit_tx_fee_msat, get_holder_selected_channel_reserve_satoshis, ANCHOR_OUTPUT_VALUE_SATOSHI};
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, RecentPaymentDetails, RecipientOnionFields, HTLCForwardInfo, PendingHTLCRouting, PendingAddHTLCInfo};
use crate::ln::features::{Bolt11InvoiceFeatures, ChannelTypeFeatures};
use crate::ln::{msgs, ChannelId, PaymentSecret, PaymentPreimage};
use crate::ln::{msgs, ChannelId, PaymentHash, PaymentSecret, PaymentPreimage};
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::onion_utils;
use crate::ln::outbound_payment::{IDEMPOTENCY_TIMEOUT_TICKS, Retry};
use crate::routing::gossip::{EffectiveCapacity, RoutingFees};
use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters, find_route};
@ -31,10 +32,14 @@ use crate::util::errors::APIError;
use crate::util::ser::Writeable;
use crate::util::string::UntrustedString;
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use crate::prelude::*;
use crate::ln::functional_test_utils;
use crate::ln::functional_test_utils::*;
use crate::routing::gossip::NodeId;
#[cfg(feature = "std")]
@ -4194,3 +4199,59 @@ fn test_htlc_forward_considers_anchor_outputs_value() {
check_closed_broadcast(&nodes[2], 1, true);
check_added_monitors(&nodes[2], 1);
}
#[test]
fn peel_payment_onion_custom_tlvs() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
let secp_ctx = Secp256k1::new();
let amt_msat = 1000;
let payment_params = PaymentParameters::for_keysend(nodes[1].node.get_our_node_id(),
TEST_FINAL_CLTV, false);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let route = functional_test_utils::get_route(&nodes[0], &route_params).unwrap();
let mut recipient_onion = RecipientOnionFields::spontaneous_empty()
.with_custom_tlvs(vec![(414141, vec![42; 1200])]).unwrap();
let prng_seed = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
let session_priv = SecretKey::from_slice(&prng_seed[..]).expect("RNG is busted");
let keysend_preimage = PaymentPreimage([42; 32]);
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
let (onion_routing_packet, first_hop_msat, cltv_expiry) = onion_utils::create_payment_onion(
&secp_ctx, &route.paths[0], &session_priv, amt_msat, recipient_onion.clone(),
nodes[0].best_block_info().1, &payment_hash, &Some(keysend_preimage), prng_seed
).unwrap();
let update_add = msgs::UpdateAddHTLC {
channel_id: ChannelId([0; 32]),
htlc_id: 42,
amount_msat: first_hop_msat,
payment_hash,
cltv_expiry,
skimmed_fee_msat: None,
onion_routing_packet,
blinding_point: None,
};
let peeled_onion = crate::ln::onion_payment::peel_payment_onion(
&update_add, &&chanmon_cfgs[1].keys_manager, &&chanmon_cfgs[1].logger, &secp_ctx,
nodes[1].best_block_info().1, true, false
).unwrap();
assert_eq!(peeled_onion.incoming_amt_msat, Some(amt_msat));
match peeled_onion.routing {
PendingHTLCRouting::ReceiveKeysend {
payment_data, payment_metadata, custom_tlvs, ..
} => {
#[cfg(not(c_bindings))]
assert_eq!(&custom_tlvs, recipient_onion.custom_tlvs());
#[cfg(c_bindings)]
assert_eq!(custom_tlvs, recipient_onion.custom_tlvs());
assert!(payment_metadata.is_none());
assert!(payment_data.is_none());
},
_ => panic!()
}
}