Skip to content

Commit 226a6e3

Browse files
committed
Decrypt Trampoline onions
In this commit, we start decrypting the inner onion we receive, and lay some groundwork towards handling the subsequent routing or payment receipt.
1 parent 068d4f4 commit 226a6e3

File tree

5 files changed

+496
-35
lines changed

5 files changed

+496
-35
lines changed

lightning/src/ln/blinded_payment_tests.rs

+147
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ use lightning_invoice::RawBolt11Invoice;
4040
use types::features::Features;
4141
use crate::blinded_path::BlindedHop;
4242

43+
#[cfg(trampoline)]
44+
use crate::routing::gossip::NodeId;
45+
4346
pub fn blinded_payment_path(
4447
payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
4548
node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
@@ -1755,3 +1758,147 @@ fn test_combined_trampoline_onion_creation_vectors() {
17551758
assert_eq!(htlc_msat, 150_156_000);
17561759
assert_eq!(htlc_cltv, 800_060);
17571760
}
1761+
1762+
#[test]
1763+
#[cfg(trampoline)]
1764+
fn test_trampoline_inbound_payment_decoding() {
1765+
let secp_ctx = Secp256k1::new();
1766+
let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303");
1767+
1768+
let bob_secret = secret_from_hex("4242424242424242424242424242424242424242424242424242424242424242");
1769+
let bob_node_id = PublicKey::from_secret_key(&secp_ctx, &bob_secret);
1770+
let _bob_unblinded_tlvs = bytes_from_hex("011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456");
1771+
let carol_secret = secret_from_hex("4343434343434343434343434343434343434343434343434343434343434343");
1772+
let carol_node_id = PublicKey::from_secret_key(&secp_ctx, &carol_secret);
1773+
let _carol_unblinded_tlvs = bytes_from_hex("020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00");
1774+
let dave_secret = secret_from_hex("4444444444444444444444444444444444444444444444444444444444444444");
1775+
let dave_node_id = PublicKey::from_secret_key(&secp_ctx, &dave_secret);
1776+
let _dave_unblinded_tlvs = bytes_from_hex("01230000000000000000000000000000000000000000000000000000000000000000000000020800000000000002310a060090000000fa0c06000b699105dc0e00");
1777+
let eve_secret = secret_from_hex("4545454545454545454545454545454545454545454545454545454545454545");
1778+
let _eve_node_id = PublicKey::from_secret_key(&secp_ctx, &eve_secret);
1779+
let _eve_unblinded_tlvs = bytes_from_hex("011a00000000000000000000000000000000000000000000000000000604deadbeef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206c1");
1780+
1781+
let path = Path {
1782+
hops: vec![
1783+
// Bob
1784+
RouteHop {
1785+
pubkey: bob_node_id,
1786+
node_features: NodeFeatures::empty(),
1787+
short_channel_id: 0,
1788+
channel_features: ChannelFeatures::empty(),
1789+
fee_msat: 0,
1790+
cltv_expiry_delta: 0,
1791+
maybe_announced_channel: false,
1792+
},
1793+
1794+
// Carol
1795+
RouteHop {
1796+
pubkey: carol_node_id,
1797+
node_features: NodeFeatures::empty(),
1798+
short_channel_id: (572330 << 40) + (42 << 16) + 2821,
1799+
channel_features: ChannelFeatures::empty(),
1800+
fee_msat: 150_153_000,
1801+
cltv_expiry_delta: 0,
1802+
maybe_announced_channel: false,
1803+
},
1804+
],
1805+
blinded_tail: Some(BlindedTail {
1806+
trampoline_hops: vec![
1807+
// Carol's pubkey
1808+
TrampolineHop {
1809+
pubkey: carol_node_id,
1810+
node_features: Features::empty(),
1811+
fee_msat: 2_500,
1812+
cltv_expiry_delta: 24,
1813+
},
1814+
// Dave's pubkey (the intro node needs to be duplicated)
1815+
TrampolineHop {
1816+
pubkey: dave_node_id,
1817+
node_features: Features::empty(),
1818+
fee_msat: 150_500, // incorporate both base and proportional fee
1819+
cltv_expiry_delta: 36,
1820+
}
1821+
],
1822+
hops: vec![
1823+
// Dave's blinded node id
1824+
BlindedHop {
1825+
blinded_node_id: pubkey_from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be"),
1826+
encrypted_payload: bytes_from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a"),
1827+
},
1828+
// Eve's blinded node id
1829+
BlindedHop {
1830+
blinded_node_id: pubkey_from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22"),
1831+
encrypted_payload: bytes_from_hex("bcd747394fbd4d99588da075a623316e15a576df5bc785cccc7cd6ec7b398acce6faf520175f9ec920f2ef261cdb83dc28cc3a0eeb970107b3306489bf771ef5b1213bca811d345285405861d08a655b6c237fa247a8b4491beee20c878a60e9816492026d8feb9dafa84585b253978db6a0aa2945df5ef445c61e801fb82f43d5f00716baf9fc9b3de50bc22950a36bda8fc27bfb1242e5860c7e687438d4133e058770361a19b6c271a2a07788d34dccc27e39b9829b061a4d960eac4a2c2b0f4de506c24f9af3868c0aff6dda27281c"),
1832+
}
1833+
],
1834+
blinding_point: pubkey_from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e"),
1835+
excess_final_cltv_expiry_delta: 0,
1836+
final_value_msat: 150_000_000
1837+
})
1838+
};
1839+
1840+
let payment_secret = PaymentSecret(secret_from_hex("7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da").secret_bytes());
1841+
1842+
let amt_msat = 150_000_001;
1843+
let cur_height = 800_001;
1844+
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
1845+
let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &recipient_onion_fields, cur_height, &PaymentHash([0; 32]), &None, None, [0; 32]).unwrap();
1846+
1847+
struct TestEcdhSigner {
1848+
node_secret: SecretKey,
1849+
}
1850+
impl NodeSigner for TestEcdhSigner {
1851+
fn ecdh(
1852+
&self, _recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>,
1853+
) -> Result<SharedSecret, ()> {
1854+
let mut node_secret = self.node_secret.clone();
1855+
if let Some(tweak) = tweak {
1856+
node_secret = self.node_secret.mul_tweak(tweak).map_err(|_| ())?;
1857+
}
1858+
Ok(SharedSecret::new(other_key, &node_secret))
1859+
}
1860+
fn get_inbound_payment_key(&self) -> ExpandedKey { unreachable!() }
1861+
fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() }
1862+
fn sign_invoice(
1863+
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient,
1864+
) -> Result<RecoverableSignature, ()> { unreachable!() }
1865+
fn sign_bolt12_invoice(
1866+
&self, _invoice: &UnsignedBolt12Invoice,
1867+
) -> Result<schnorr::Signature, ()> { unreachable!() }
1868+
fn sign_gossip_message(&self, _msg: UnsignedGossipMessage) -> Result<Signature, ()> { unreachable!() }
1869+
}
1870+
let logger = test_utils::TestLogger::with_id("".to_owned());
1871+
1872+
let bob_update_add = update_add_msg(111_000, 747_501, None, bob_onion);
1873+
let bob_node_signer = TestEcdhSigner { node_secret: bob_secret };
1874+
1875+
let (bob_peeled_onion, next_packet_details_opt) = onion_payment::decode_incoming_update_add_htlc_onion(
1876+
&bob_update_add, &bob_node_signer, &logger, &secp_ctx
1877+
).unwrap_or_else(|_| panic!());
1878+
1879+
let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::Forward {
1880+
next_hop_data: msgs::InboundOnionForwardPayload {..}, next_hop_hmac, new_packet_bytes, ..
1881+
} = bob_peeled_onion {
1882+
(new_packet_bytes, next_hop_hmac)
1883+
} else { panic!() };
1884+
1885+
let carol_packet_details = next_packet_details_opt.unwrap();
1886+
let carol_onion = msgs::OnionPacket {
1887+
version: 0,
1888+
public_key: carol_packet_details.next_packet_pubkey,
1889+
hop_data: carol_packet_bytes,
1890+
hmac: carol_hmac,
1891+
};
1892+
let carol_update_add = update_add_msg(carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, None, carol_onion);
1893+
1894+
let carol_node_signer = TestEcdhSigner { node_secret: carol_secret };
1895+
let (carol_peeled_onion, _) = onion_payment::decode_incoming_update_add_htlc_onion(
1896+
&carol_update_add, &carol_node_signer, &logger, &secp_ctx
1897+
).unwrap_or_else(|_| panic!());
1898+
1899+
let _carol_trampoline_update_add = if let onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, .. } = carol_peeled_onion {
1900+
assert_eq!(next_trampoline_hop_data.next_trampoline, NodeId::from_pubkey(&dave_node_id));
1901+
} else {
1902+
panic!();
1903+
};
1904+
}

lightning/src/ln/channelmanager.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -4479,11 +4479,36 @@ where
44794479
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
44804480
}
44814481
},
4482+
#[cfg(trampoline)]
4483+
onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => {
4484+
// OUR PAYMENT!
4485+
let current_height: u32 = self.best_block.read().unwrap().height;
4486+
match create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash,
4487+
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat,
4488+
current_height)
4489+
{
4490+
Ok(info) => {
4491+
// Note that we could obviously respond immediately with an update_fulfill_htlc
4492+
// message, however that would leak that we are the recipient of this payment, so
4493+
// instead we stay symmetric with the forwarding case, only responding (after a
4494+
// delay) once they've sent us a commitment_signed!
4495+
PendingHTLCStatus::Forward(info)
4496+
},
4497+
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
4498+
}
4499+
},
44824500
onion_utils::Hop::Forward { .. } | onion_utils::Hop::BlindedForward { .. } => {
44834501
match create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) {
44844502
Ok(info) => PendingHTLCStatus::Forward(info),
44854503
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
44864504
}
4505+
},
4506+
#[cfg(trampoline)]
4507+
onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => {
4508+
match create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) {
4509+
Ok(info) => PendingHTLCStatus::Forward(info),
4510+
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
4511+
}
44874512
}
44884513
}
44894514
}
@@ -5895,7 +5920,7 @@ where
58955920
// of the onion.
58965921
failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None);
58975922
},
5898-
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret }) => {
5923+
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, .. }) => {
58995924
let phantom_shared_secret = shared_secret.secret_bytes();
59005925
failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
59015926
},

lightning/src/ln/msgs.rs

-3
Original file line numberDiff line numberDiff line change
@@ -1867,7 +1867,6 @@ mod fuzzy_internal_msgs {
18671867
}
18681868

18691869
#[cfg(trampoline)]
1870-
#[cfg_attr(trampoline, allow(unused))]
18711870
pub struct InboundTrampolineForwardPayload {
18721871
pub next_trampoline: NodeId,
18731872
/// The value, in msat, of the payment after this hop's fee is deducted.
@@ -1876,7 +1875,6 @@ mod fuzzy_internal_msgs {
18761875
}
18771876

18781877
#[cfg(trampoline)]
1879-
#[cfg_attr(trampoline, allow(unused))]
18801878
pub struct InboundTrampolineBlindedForwardPayload {
18811879
pub next_trampoline: NodeId,
18821880
pub payment_relay: PaymentRelay,
@@ -1887,7 +1885,6 @@ mod fuzzy_internal_msgs {
18871885
}
18881886

18891887
#[cfg(trampoline)]
1890-
#[cfg_attr(trampoline, allow(unused))]
18911888
pub enum InboundTrampolinePayload {
18921889
Forward(InboundTrampolineForwardPayload),
18931890
BlindedForward(InboundTrampolineBlindedForwardPayload),

0 commit comments

Comments
 (0)