Skip to content

Commit 010ac3b

Browse files
committed
WIP: Decode inbound Trampoline onion
1 parent 86ae53b commit 010ac3b

File tree

4 files changed

+401
-2
lines changed

4 files changed

+401
-2
lines changed

lightning/src/ln/blinded_payment_tests.rs

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

lightning/src/ln/msgs.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,13 @@ mod fuzzy_internal_msgs {
17661766
amt_to_forward: u64,
17671767
outgoing_cltv_value: u32,
17681768
},
1769+
#[allow(unused)]
1770+
TrampolineEntrypoint {
1771+
amt_to_forward: u64,
1772+
outgoing_cltv_value: u32,
1773+
multipath_trampoline_data: Option<FinalOnionHopData>,
1774+
trampoline_packet: TrampolineOnionPacket,
1775+
},
17691776
Receive {
17701777
payment_data: Option<FinalOnionHopData>,
17711778
payment_metadata: Option<Vec<u8>>,
@@ -1795,6 +1802,36 @@ mod fuzzy_internal_msgs {
17951802
}
17961803
}
17971804

1805+
#[allow(unused)]
1806+
pub enum InboundTrampolinePayload {
1807+
Forward {
1808+
/// The value, in msat, of the payment after this hop's fee is deducted.
1809+
amt_to_forward: u64,
1810+
outgoing_cltv_value: u32,
1811+
/// The node id to which the trampoline node must find a route.
1812+
outgoing_node_id: PublicKey,
1813+
},
1814+
BlindedForward {
1815+
short_channel_id: u64,
1816+
payment_relay: PaymentRelay,
1817+
payment_constraints: PaymentConstraints,
1818+
features: BlindedHopFeatures,
1819+
intro_node_blinding_point: Option<PublicKey>,
1820+
next_blinding_override: Option<PublicKey>,
1821+
},
1822+
BlindedReceive {
1823+
sender_intended_htlc_amt_msat: u64,
1824+
total_msat: u64,
1825+
cltv_expiry_height: u32,
1826+
payment_secret: PaymentSecret,
1827+
payment_constraints: PaymentConstraints,
1828+
payment_context: PaymentContext,
1829+
intro_node_blinding_point: Option<PublicKey>,
1830+
keysend_preimage: Option<PaymentPreimage>,
1831+
custom_tlvs: Vec<(u64, Vec<u8>)>,
1832+
}
1833+
}
1834+
17981835
pub(crate) enum OutboundOnionPayload<'a> {
17991836
Forward {
18001837
short_channel_id: u64,
@@ -2847,6 +2884,8 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
28472884
let mut intro_node_blinding_point = None;
28482885
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
28492886
let mut total_msat = None;
2887+
let mut trampoline_onion_packet: Option<TrampolineOnionPacket> = None;
2888+
28502889
let mut keysend_preimage: Option<PaymentPreimage> = None;
28512890
let mut custom_tlvs = Vec::new();
28522891

@@ -2861,6 +2900,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
28612900
(12, intro_node_blinding_point, option),
28622901
(16, payment_metadata, option),
28632902
(18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
2903+
(20, trampoline_onion_packet, option),
28642904
// See https://github.com/lightning/blips/blob/master/blip-0003.md
28652905
(5482373484, keysend_preimage, option)
28662906
}, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
@@ -2937,6 +2977,16 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29372977
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
29382978
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
29392979
})
2980+
} else if let Some(trampoline_onion_packet) = trampoline_onion_packet {
2981+
if payment_metadata.is_some() || encrypted_tlvs_opt.is_some() ||
2982+
total_msat.is_some()
2983+
{ return Err(DecodeError::InvalidValue) }
2984+
Ok(Self::TrampolineEntrypoint {
2985+
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
2986+
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
2987+
multipath_trampoline_data: payment_data,
2988+
trampoline_packet: trampoline_onion_packet,
2989+
})
29402990
} else {
29412991
if encrypted_tlvs_opt.is_some() || total_msat.is_some() {
29422992
return Err(DecodeError::InvalidValue)
@@ -2958,6 +3008,114 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
29583008
}
29593009
}
29603010

3011+
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner {
3012+
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
3013+
let (update_add_blinding_point, node_signer) = args;
3014+
3015+
let mut amt = None;
3016+
let mut cltv_value = None;
3017+
let mut payment_data: Option<FinalOnionHopData> = None;
3018+
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
3019+
let mut intro_node_blinding_point = None;
3020+
let mut outgoing_node_id: Option<PublicKey> = None;
3021+
let mut total_msat = None;
3022+
let mut keysend_preimage: Option<PaymentPreimage> = None;
3023+
let mut custom_tlvs = Vec::new();
3024+
3025+
let tlv_len = <BigSize as Readable>::read(r)?;
3026+
let mut rd = FixedLengthReader::new(r, tlv_len.0);
3027+
decode_tlv_stream_with_custom_tlv_decode!(&mut rd, {
3028+
(2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
3029+
(4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))),
3030+
(8, payment_data, option),
3031+
(10, encrypted_tlvs_opt, option),
3032+
(12, intro_node_blinding_point, option),
3033+
(14, outgoing_node_id, option),
3034+
(18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
3035+
// See https://github.com/lightning/blips/blob/master/blip-0003.md
3036+
(5482373484, keysend_preimage, option)
3037+
}, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
3038+
if msg_type < 1 << 16 { return Ok(false) }
3039+
let mut value = Vec::new();
3040+
msg_reader.read_to_limit(&mut value, u64::MAX)?;
3041+
custom_tlvs.push((msg_type, value));
3042+
Ok(true)
3043+
});
3044+
3045+
if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
3046+
if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() {
3047+
return Err(DecodeError::InvalidValue)
3048+
}
3049+
3050+
if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) {
3051+
if payment_data.is_some() {
3052+
return Err(DecodeError::InvalidValue)
3053+
}
3054+
let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
3055+
let enc_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None)
3056+
.map_err(|_| DecodeError::InvalidValue)?;
3057+
let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes());
3058+
let mut s = Cursor::new(&enc_tlvs);
3059+
let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
3060+
match ChaChaPolyReadAdapter::read(&mut reader, rho)? {
3061+
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs {
3062+
short_channel_id, payment_relay, payment_constraints, features, next_blinding_override
3063+
})} => {
3064+
if amt.is_some() || cltv_value.is_some() || total_msat.is_some() ||
3065+
keysend_preimage.is_some()
3066+
{
3067+
return Err(DecodeError::InvalidValue)
3068+
}
3069+
Ok(Self::BlindedForward {
3070+
short_channel_id,
3071+
payment_relay,
3072+
payment_constraints,
3073+
features,
3074+
intro_node_blinding_point,
3075+
next_blinding_override,
3076+
})
3077+
},
3078+
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => {
3079+
let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs;
3080+
let expanded_key = node_signer.get_inbound_payment_key();
3081+
if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() {
3082+
return Err(DecodeError::InvalidValue);
3083+
}
3084+
3085+
let UnauthenticatedReceiveTlvs {
3086+
payment_secret, payment_constraints, payment_context,
3087+
} = tlvs;
3088+
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
3089+
Ok(Self::BlindedReceive {
3090+
sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?,
3091+
total_msat: total_msat.ok_or(DecodeError::InvalidValue)?,
3092+
cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?,
3093+
payment_secret,
3094+
payment_constraints,
3095+
payment_context,
3096+
intro_node_blinding_point,
3097+
keysend_preimage,
3098+
custom_tlvs,
3099+
})
3100+
},
3101+
}
3102+
} else if let Some(outgoing_node_id) = outgoing_node_id {
3103+
if payment_data.is_some() || encrypted_tlvs_opt.is_some() ||
3104+
total_msat.is_some()
3105+
{ return Err(DecodeError::InvalidValue) }
3106+
Ok(Self::Forward {
3107+
outgoing_node_id,
3108+
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
3109+
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
3110+
})
3111+
} else {
3112+
// unblinded Trampoline receives are not supported
3113+
return Err(DecodeError::InvalidValue);
3114+
}
3115+
}
3116+
}
3117+
3118+
29613119
impl Writeable for Ping {
29623120
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
29633121
self.ponglen.write(w)?;

0 commit comments

Comments
 (0)