Skip to content

Commit 0b077c0

Browse files
authored
Merge pull request #3620 from arik-so/arik/trampoline/inbound-decoding
Parse inbound Trampoline onions
2 parents c4d0560 + 1fd69ee commit 0b077c0

File tree

6 files changed

+916
-150
lines changed

6 files changed

+916
-150
lines changed

lightning/src/blinded_path/payment.rs

+72
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,26 @@ pub struct ForwardTlvs {
297297
pub next_blinding_override: Option<PublicKey>,
298298
}
299299

300+
/// Data to construct a [`BlindedHop`] for forwarding a Trampoline payment.
301+
#[cfg(trampoline)]
302+
#[derive(Clone, Debug)]
303+
pub struct TrampolineForwardTlvs {
304+
/// The node id to which the trampoline node must find a route.
305+
pub next_trampoline: PublicKey,
306+
/// Payment parameters for relaying over [`Self::next_trampoline`].
307+
pub payment_relay: PaymentRelay,
308+
/// Payment constraints for relaying over [`Self::next_trampoline`].
309+
pub payment_constraints: PaymentConstraints,
310+
/// Supported and required features when relaying a payment onion containing this object's
311+
/// corresponding [`BlindedHop::encrypted_payload`].
312+
///
313+
/// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload
314+
pub features: BlindedHopFeatures,
315+
/// Set if this [`BlindedPaymentPath`] is concatenated to another, to indicate the
316+
/// [`BlindedPaymentPath::blinding_point`] of the appended blinded path.
317+
pub next_blinding_override: Option<PublicKey>,
318+
}
319+
300320
/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
301321
/// may not be valid if received by another lightning implementation.
302322
///
@@ -348,6 +368,17 @@ pub(crate) enum BlindedPaymentTlvs {
348368
Receive(ReceiveTlvs),
349369
}
350370

371+
/// Data to construct a [`BlindedHop`] for sending a Trampoline payment over.
372+
///
373+
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
374+
#[cfg(trampoline)]
375+
pub(crate) enum BlindedTrampolineTlvs {
376+
/// This blinded payment data is for a forwarding node.
377+
Forward(TrampolineForwardTlvs),
378+
/// This blinded payment data is for the receiving node.
379+
Receive(ReceiveTlvs),
380+
}
381+
351382
// Used to include forward and receive TLVs in the same iterator for encoding.
352383
enum BlindedPaymentTlvsRef<'a> {
353384
Forward(&'a ForwardTlvs),
@@ -560,6 +591,47 @@ impl Readable for BlindedPaymentTlvs {
560591
}
561592
}
562593

594+
#[cfg(trampoline)]
595+
impl Readable for BlindedTrampolineTlvs {
596+
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
597+
_init_and_read_tlv_stream!(r, {
598+
(8, next_blinding_override, option),
599+
(10, payment_relay, option),
600+
(12, payment_constraints, required),
601+
(14, next_trampoline, option),
602+
(14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))),
603+
(65536, payment_secret, option),
604+
(65537, payment_context, option),
605+
(65539, authentication, option),
606+
});
607+
608+
if let Some(next_trampoline) = next_trampoline {
609+
if payment_secret.is_some() {
610+
return Err(DecodeError::InvalidValue);
611+
}
612+
Ok(BlindedTrampolineTlvs::Forward(TrampolineForwardTlvs {
613+
next_trampoline,
614+
payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
615+
payment_constraints: payment_constraints.0.unwrap(),
616+
next_blinding_override,
617+
features: features.unwrap_or_else(BlindedHopFeatures::empty),
618+
}))
619+
} else {
620+
if payment_relay.is_some() || features.is_some() {
621+
return Err(DecodeError::InvalidValue);
622+
}
623+
Ok(BlindedTrampolineTlvs::Receive(ReceiveTlvs {
624+
tlvs: UnauthenticatedReceiveTlvs {
625+
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
626+
payment_constraints: payment_constraints.0.unwrap(),
627+
payment_context: payment_context.ok_or(DecodeError::InvalidValue)?,
628+
},
629+
authentication: authentication.ok_or(DecodeError::InvalidValue)?,
630+
}))
631+
}
632+
}
633+
}
634+
563635
/// Represents the padding round off size (in bytes) that
564636
/// is used to pad payment bilnded path's [`BlindedHop`]
565637
pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30;

lightning/src/ln/blinded_payment_tests.rs

+144
Original file line numberDiff line numberDiff line change
@@ -1814,3 +1814,147 @@ fn test_combined_trampoline_onion_creation_vectors() {
18141814
assert_eq!(htlc_msat, 150_156_000);
18151815
assert_eq!(htlc_cltv, 800_060);
18161816
}
1817+
1818+
#[test]
1819+
#[cfg(trampoline)]
1820+
fn test_trampoline_inbound_payment_decoding() {
1821+
let secp_ctx = Secp256k1::new();
1822+
let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303");
1823+
1824+
let bob_secret = secret_from_hex("4242424242424242424242424242424242424242424242424242424242424242");
1825+
let bob_node_id = PublicKey::from_secret_key(&secp_ctx, &bob_secret);
1826+
let _bob_unblinded_tlvs = bytes_from_hex("011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456");
1827+
let carol_secret = secret_from_hex("4343434343434343434343434343434343434343434343434343434343434343");
1828+
let carol_node_id = PublicKey::from_secret_key(&secp_ctx, &carol_secret);
1829+
let _carol_unblinded_tlvs = bytes_from_hex("020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00");
1830+
let dave_secret = secret_from_hex("4444444444444444444444444444444444444444444444444444444444444444");
1831+
let dave_node_id = PublicKey::from_secret_key(&secp_ctx, &dave_secret);
1832+
let _dave_unblinded_tlvs = bytes_from_hex("01230000000000000000000000000000000000000000000000000000000000000000000000020800000000000002310a060090000000fa0c06000b699105dc0e00");
1833+
let eve_secret = secret_from_hex("4545454545454545454545454545454545454545454545454545454545454545");
1834+
let _eve_node_id = PublicKey::from_secret_key(&secp_ctx, &eve_secret);
1835+
let _eve_unblinded_tlvs = bytes_from_hex("011a00000000000000000000000000000000000000000000000000000604deadbeef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206c1");
1836+
1837+
let path = Path {
1838+
hops: vec![
1839+
// Bob
1840+
RouteHop {
1841+
pubkey: bob_node_id,
1842+
node_features: NodeFeatures::empty(),
1843+
short_channel_id: 0,
1844+
channel_features: ChannelFeatures::empty(),
1845+
fee_msat: 0,
1846+
cltv_expiry_delta: 0,
1847+
maybe_announced_channel: false,
1848+
},
1849+
1850+
// Carol
1851+
RouteHop {
1852+
pubkey: carol_node_id,
1853+
node_features: NodeFeatures::empty(),
1854+
short_channel_id: (572330 << 40) + (42 << 16) + 2821,
1855+
channel_features: ChannelFeatures::empty(),
1856+
fee_msat: 150_153_000,
1857+
cltv_expiry_delta: 0,
1858+
maybe_announced_channel: false,
1859+
},
1860+
],
1861+
blinded_tail: Some(BlindedTail {
1862+
trampoline_hops: vec![
1863+
// Carol's pubkey
1864+
TrampolineHop {
1865+
pubkey: carol_node_id,
1866+
node_features: Features::empty(),
1867+
fee_msat: 2_500,
1868+
cltv_expiry_delta: 24,
1869+
},
1870+
// Dave's pubkey (the intro node needs to be duplicated)
1871+
TrampolineHop {
1872+
pubkey: dave_node_id,
1873+
node_features: Features::empty(),
1874+
fee_msat: 150_500, // incorporate both base and proportional fee
1875+
cltv_expiry_delta: 36,
1876+
}
1877+
],
1878+
hops: vec![
1879+
// Dave's blinded node id
1880+
BlindedHop {
1881+
blinded_node_id: pubkey_from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be"),
1882+
encrypted_payload: bytes_from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a"),
1883+
},
1884+
// Eve's blinded node id
1885+
BlindedHop {
1886+
blinded_node_id: pubkey_from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22"),
1887+
encrypted_payload: bytes_from_hex("bcd747394fbd4d99588da075a623316e15a576df5bc785cccc7cd6ec7b398acce6faf520175f9ec920f2ef261cdb83dc28cc3a0eeb970107b3306489bf771ef5b1213bca811d345285405861d08a655b6c237fa247a8b4491beee20c878a60e9816492026d8feb9dafa84585b253978db6a0aa2945df5ef445c61e801fb82f43d5f00716baf9fc9b3de50bc22950a36bda8fc27bfb1242e5860c7e687438d4133e058770361a19b6c271a2a07788d34dccc27e39b9829b061a4d960eac4a2c2b0f4de506c24f9af3868c0aff6dda27281c"),
1888+
}
1889+
],
1890+
blinding_point: pubkey_from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e"),
1891+
excess_final_cltv_expiry_delta: 0,
1892+
final_value_msat: 150_000_000
1893+
})
1894+
};
1895+
1896+
let payment_secret = PaymentSecret(secret_from_hex("7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da").secret_bytes());
1897+
1898+
let amt_msat = 150_000_001;
1899+
let cur_height = 800_001;
1900+
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
1901+
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();
1902+
1903+
struct TestEcdhSigner {
1904+
node_secret: SecretKey,
1905+
}
1906+
impl NodeSigner for TestEcdhSigner {
1907+
fn ecdh(
1908+
&self, _recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>,
1909+
) -> Result<SharedSecret, ()> {
1910+
let mut node_secret = self.node_secret.clone();
1911+
if let Some(tweak) = tweak {
1912+
node_secret = self.node_secret.mul_tweak(tweak).map_err(|_| ())?;
1913+
}
1914+
Ok(SharedSecret::new(other_key, &node_secret))
1915+
}
1916+
fn get_inbound_payment_key(&self) -> ExpandedKey { unreachable!() }
1917+
fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() }
1918+
fn sign_invoice(
1919+
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient,
1920+
) -> Result<RecoverableSignature, ()> { unreachable!() }
1921+
fn sign_bolt12_invoice(
1922+
&self, _invoice: &UnsignedBolt12Invoice,
1923+
) -> Result<schnorr::Signature, ()> { unreachable!() }
1924+
fn sign_gossip_message(&self, _msg: UnsignedGossipMessage) -> Result<Signature, ()> { unreachable!() }
1925+
}
1926+
let logger = test_utils::TestLogger::with_id("".to_owned());
1927+
1928+
let bob_update_add = update_add_msg(111_000, 747_501, None, bob_onion);
1929+
let bob_node_signer = TestEcdhSigner { node_secret: bob_secret };
1930+
1931+
let (bob_peeled_onion, next_packet_details_opt) = onion_payment::decode_incoming_update_add_htlc_onion(
1932+
&bob_update_add, &bob_node_signer, &logger, &secp_ctx
1933+
).unwrap_or_else(|_| panic!());
1934+
1935+
let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::Forward {
1936+
next_hop_data: msgs::InboundOnionForwardPayload {..}, next_hop_hmac, new_packet_bytes, ..
1937+
} = bob_peeled_onion {
1938+
(new_packet_bytes, next_hop_hmac)
1939+
} else { panic!() };
1940+
1941+
let carol_packet_details = next_packet_details_opt.unwrap();
1942+
let carol_onion = msgs::OnionPacket {
1943+
version: 0,
1944+
public_key: carol_packet_details.next_packet_pubkey,
1945+
hop_data: carol_packet_bytes,
1946+
hmac: carol_hmac,
1947+
};
1948+
let carol_update_add = update_add_msg(carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, None, carol_onion);
1949+
1950+
let carol_node_signer = TestEcdhSigner { node_secret: carol_secret };
1951+
let (carol_peeled_onion, _) = onion_payment::decode_incoming_update_add_htlc_onion(
1952+
&carol_update_add, &carol_node_signer, &logger, &secp_ctx
1953+
).unwrap_or_else(|_| panic!());
1954+
1955+
let _carol_trampoline_update_add = if let onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, .. } = carol_peeled_onion {
1956+
assert_eq!(next_trampoline_hop_data.next_trampoline, dave_node_id);
1957+
} else {
1958+
panic!();
1959+
};
1960+
}

0 commit comments

Comments
 (0)