|
7 | 7 | // You may not use this file except in accordance with one or both of these
|
8 | 8 | // licenses.
|
9 | 9 |
|
10 |
| -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; |
| 10 | +use bitcoin::hashes::hex::FromHex; |
| 11 | +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr}; |
| 12 | +use bitcoin::secp256k1::ecdh::SharedSecret; |
| 13 | +use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; |
| 14 | +use crate::blinded_path; |
11 | 15 | use crate::blinded_path::payment::{BlindedPaymentPath, ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, ReceiveTlvs};
|
12 | 16 | use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason};
|
13 |
| -use crate::ln::types::PaymentSecret; |
| 17 | +use crate::ln::types::{ChannelId, PaymentHash, PaymentSecret}; |
14 | 18 | use crate::ln::channelmanager;
|
15 |
| -use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; |
16 |
| -use crate::ln::features::BlindedHopFeatures; |
| 19 | +use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields}; |
| 20 | +use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures}; |
17 | 21 | use crate::ln::functional_test_utils::*;
|
18 | 22 | use crate::ln::msgs;
|
19 |
| -use crate::ln::msgs::ChannelMessageHandler; |
| 23 | +use crate::ln::msgs::{ChannelMessageHandler, UnsignedGossipMessage}; |
| 24 | +use crate::ln::onion_payment; |
20 | 25 | use crate::ln::onion_utils;
|
21 | 26 | use crate::ln::onion_utils::INVALID_ONION_BLINDING;
|
22 | 27 | use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
|
23 |
| -use crate::offers::invoice::BlindedPayInfo; |
| 28 | +use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; |
| 29 | +use crate::offers::invoice_request::UnsignedInvoiceRequest; |
24 | 30 | use crate::prelude::*;
|
25 |
| -use crate::routing::router::{Payee, PaymentParameters, RouteParameters}; |
| 31 | +use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters}; |
| 32 | +use crate::sign::{KeyMaterial, NodeSigner, Recipient}; |
26 | 33 | use crate::util::config::UserConfig;
|
| 34 | +use crate::util::ser::WithoutLength; |
27 | 35 | use crate::util::test_utils;
|
| 36 | +use lightning_invoice::RawBolt11Invoice; |
28 | 37 |
|
29 | 38 | fn blinded_payment_path(
|
30 | 39 | payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
|
@@ -1332,3 +1341,254 @@ fn custom_tlvs_to_blinded_path() {
|
1332 | 1341 | .with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone())
|
1333 | 1342 | );
|
1334 | 1343 | }
|
| 1344 | + |
| 1345 | +fn secret_from_hex(hex: &str) -> SecretKey { |
| 1346 | + SecretKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()).unwrap() |
| 1347 | +} |
| 1348 | + |
| 1349 | +fn bytes_from_hex(hex: &str) -> Vec<u8> { |
| 1350 | + <Vec<u8>>::from_hex(hex).unwrap() |
| 1351 | +} |
| 1352 | + |
| 1353 | +fn pubkey_from_hex(hex: &str) -> PublicKey { |
| 1354 | + PublicKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()).unwrap() |
| 1355 | +} |
| 1356 | + |
| 1357 | +fn update_add_msg( |
| 1358 | + amount_msat: u64, cltv_expiry: u32, blinding_point: Option<PublicKey>, |
| 1359 | + onion_routing_packet: msgs::OnionPacket |
| 1360 | +) -> msgs::UpdateAddHTLC { |
| 1361 | + msgs::UpdateAddHTLC { |
| 1362 | + channel_id: ChannelId::from_bytes([0; 32]), |
| 1363 | + htlc_id: 0, |
| 1364 | + amount_msat, |
| 1365 | + cltv_expiry, |
| 1366 | + payment_hash: PaymentHash([0; 32]), |
| 1367 | + onion_routing_packet, |
| 1368 | + skimmed_fee_msat: None, |
| 1369 | + blinding_point, |
| 1370 | + } |
| 1371 | +} |
| 1372 | + |
| 1373 | +#[test] |
| 1374 | +fn route_blinding_spec_test_vector() { |
| 1375 | + let mut secp_ctx = Secp256k1::new(); |
| 1376 | + let bob_secret = secret_from_hex("4242424242424242424242424242424242424242424242424242424242424242"); |
| 1377 | + let bob_node_id = PublicKey::from_secret_key(&secp_ctx, &bob_secret); |
| 1378 | + let bob_unblinded_tlvs = bytes_from_hex("011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456"); |
| 1379 | + let carol_secret = secret_from_hex("4343434343434343434343434343434343434343434343434343434343434343"); |
| 1380 | + let carol_node_id = PublicKey::from_secret_key(&secp_ctx, &carol_secret); |
| 1381 | + let carol_unblinded_tlvs = bytes_from_hex("020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00"); |
| 1382 | + let dave_secret = secret_from_hex("4444444444444444444444444444444444444444444444444444444444444444"); |
| 1383 | + let dave_node_id = PublicKey::from_secret_key(&secp_ctx, &dave_secret); |
| 1384 | + let dave_unblinded_tlvs = bytes_from_hex("01230000000000000000000000000000000000000000000000000000000000000000000000020800000000000002310a060090000000fa0c06000b699105dc0e00"); |
| 1385 | + let eve_secret = secret_from_hex("4545454545454545454545454545454545454545454545454545454545454545"); |
| 1386 | + let eve_node_id = PublicKey::from_secret_key(&secp_ctx, &eve_secret); |
| 1387 | + let eve_unblinded_tlvs = bytes_from_hex("011a00000000000000000000000000000000000000000000000000000604deadbeef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206c1"); |
| 1388 | + |
| 1389 | + // Eve creates a blinded path to herself through Dave: |
| 1390 | + let dave_eve_session_priv = secret_from_hex("0101010101010101010101010101010101010101010101010101010101010101"); |
| 1391 | + let blinding_override = PublicKey::from_secret_key(&secp_ctx, &dave_eve_session_priv); |
| 1392 | + assert_eq!(blinding_override, pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")); |
| 1393 | + // Can't use the public API here as the encrypted payloads contain unknown TLVs. |
| 1394 | + let mut dave_eve_blinded_hops = blinded_path::utils::construct_blinded_hops( |
| 1395 | + &secp_ctx, [dave_node_id, eve_node_id].iter(), |
| 1396 | + &mut [WithoutLength(&dave_unblinded_tlvs), WithoutLength(&eve_unblinded_tlvs)].iter(), |
| 1397 | + &dave_eve_session_priv |
| 1398 | + ).unwrap(); |
| 1399 | + |
| 1400 | + // Concatenate an additional Bob -> Carol blinded path to the Eve -> Dave blinded path. |
| 1401 | + let bob_carol_session_priv = secret_from_hex("0202020202020202020202020202020202020202020202020202020202020202"); |
| 1402 | + let bob_blinding_point = PublicKey::from_secret_key(&secp_ctx, &bob_carol_session_priv); |
| 1403 | + let bob_carol_blinded_hops = blinded_path::utils::construct_blinded_hops( |
| 1404 | + &secp_ctx, [bob_node_id, carol_node_id].iter(), |
| 1405 | + &mut [WithoutLength(&bob_unblinded_tlvs), WithoutLength(&carol_unblinded_tlvs)].iter(), |
| 1406 | + &bob_carol_session_priv |
| 1407 | + ).unwrap(); |
| 1408 | + |
| 1409 | + let mut blinded_hops = bob_carol_blinded_hops; |
| 1410 | + blinded_hops.append(&mut dave_eve_blinded_hops); |
| 1411 | + assert_eq!( |
| 1412 | + vec![ |
| 1413 | + pubkey_from_hex("03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"), |
| 1414 | + pubkey_from_hex("02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"), |
| 1415 | + pubkey_from_hex("036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"), |
| 1416 | + pubkey_from_hex("021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae") |
| 1417 | + ], |
| 1418 | + blinded_hops.iter().map(|bh| bh.blinded_node_id).collect::<Vec<PublicKey>>() |
| 1419 | + ); |
| 1420 | + assert_eq!( |
| 1421 | + vec![ |
| 1422 | + bytes_from_hex("cd4100ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c49982088b49f2e70b99287fdee0aa58aa39913ab405813b999f66783aa2fe637b3cda91ffc0913c30324e2c6ce327e045183e4bffecb"), |
| 1423 | + bytes_from_hex("cc0f16524fd7f8bb0b1d8d40ad71709ef140174c76faa574cac401bb8992fef76c4d004aa485dd599ed1cf2715f57ff62da5aaec5d7b10d59b04d8a9d77e472b9b3ecc2179334e411be22fa4c02b467c7e"), |
| 1424 | + bytes_from_hex("0fa0a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8db3e561d9c43815fd4bcebf6f58c546da0cd8a9bf5cebd0d554802f6c0255e28e4a27343f761fe518cd897463187991105"), |
| 1425 | + bytes_from_hex("da1a7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60724a2e4d3f0489ad884f7f3f77149209f0df51efd6b276294a02e3949c7254fbc8b5cab58212d9a78983e1cf86fe218b30c4ca8f6d8") |
| 1426 | + ], |
| 1427 | + blinded_hops.iter().map(|bh| bh.encrypted_payload.clone()).collect::<Vec<Vec<u8>>>() |
| 1428 | + ); |
| 1429 | + |
| 1430 | + let mut amt_msat = 100_000; |
| 1431 | + let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303"); |
| 1432 | + let path = Path { |
| 1433 | + hops: vec![RouteHop { |
| 1434 | + pubkey: bob_node_id, |
| 1435 | + node_features: NodeFeatures::empty(), |
| 1436 | + short_channel_id: 42, |
| 1437 | + channel_features: ChannelFeatures::empty(), |
| 1438 | + fee_msat: 100, |
| 1439 | + cltv_expiry_delta: 42, |
| 1440 | + maybe_announced_channel: false, |
| 1441 | + }], |
| 1442 | + blinded_tail: Some(BlindedTail { |
| 1443 | + hops: blinded_hops, |
| 1444 | + blinding_point: bob_blinding_point, |
| 1445 | + excess_final_cltv_expiry_delta: 0, |
| 1446 | + final_value_msat: amt_msat |
| 1447 | + }), |
| 1448 | + }; |
| 1449 | + let cur_height = 747_000; |
| 1450 | + let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &RecipientOnionFields::spontaneous_empty(), cur_height, &PaymentHash([0; 32]), &None, [0; 32]).unwrap(); |
| 1451 | + |
| 1452 | + struct TestEcdhSigner { |
| 1453 | + node_secret: SecretKey, |
| 1454 | + } |
| 1455 | + impl NodeSigner for TestEcdhSigner { |
| 1456 | + fn ecdh( |
| 1457 | + &self, _recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>, |
| 1458 | + ) -> Result<SharedSecret, ()> { |
| 1459 | + let mut node_secret = self.node_secret.clone(); |
| 1460 | + if let Some(tweak) = tweak { |
| 1461 | + node_secret = self.node_secret.mul_tweak(tweak).map_err(|_| ())?; |
| 1462 | + } |
| 1463 | + Ok(SharedSecret::new(other_key, &node_secret)) |
| 1464 | + } |
| 1465 | + fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!() } |
| 1466 | + fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() } |
| 1467 | + fn sign_invoice( |
| 1468 | + &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, |
| 1469 | + ) -> Result<RecoverableSignature, ()> { unreachable!() } |
| 1470 | + fn sign_bolt12_invoice_request( |
| 1471 | + &self, _invoice_request: &UnsignedInvoiceRequest, |
| 1472 | + ) -> Result<schnorr::Signature, ()> { unreachable!() } |
| 1473 | + fn sign_bolt12_invoice( |
| 1474 | + &self, _invoice: &UnsignedBolt12Invoice, |
| 1475 | + ) -> Result<schnorr::Signature, ()> { unreachable!() } |
| 1476 | + fn sign_gossip_message(&self, _msg: UnsignedGossipMessage) -> Result<Signature, ()> { unreachable!() } |
| 1477 | + } |
| 1478 | + let logger = test_utils::TestLogger::with_id("".to_owned()); |
| 1479 | + |
| 1480 | + let bob_update_add = update_add_msg(110_000, 747_500, None, bob_onion); |
| 1481 | + let bob_node_signer = TestEcdhSigner { node_secret: bob_secret }; |
| 1482 | + // Can't use the public API here as we need to avoid the CLTV delta checks (test vector uses |
| 1483 | + // < MIN_CLTV_EXPIRY_DELTA). |
| 1484 | + let (bob_peeled_onion, _, next_packet_details_opt) = |
| 1485 | + match onion_payment::decode_incoming_update_add_htlc_onion( |
| 1486 | + &bob_update_add, &&bob_node_signer, &&logger, &secp_ctx |
| 1487 | + ) { |
| 1488 | + Ok(res) => res, |
| 1489 | + _ => panic!("Unexpected error") |
| 1490 | + }; |
| 1491 | + let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::Forward { |
| 1492 | + next_hop_data: msgs::InboundOnionPayload::BlindedForward { |
| 1493 | + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override |
| 1494 | + }, next_hop_hmac, new_packet_bytes |
| 1495 | + } = bob_peeled_onion { |
| 1496 | + assert_eq!(short_channel_id, 1729); |
| 1497 | + assert!(next_blinding_override.is_none()); |
| 1498 | + assert_eq!(intro_node_blinding_point, Some(bob_blinding_point)); |
| 1499 | + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 36, fee_proportional_millionths: 150, fee_base_msat: 10_000 }); |
| 1500 | + assert_eq!(features, BlindedHopFeatures::empty()); |
| 1501 | + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 748_005, htlc_minimum_msat: 1500 }); |
| 1502 | + (new_packet_bytes, next_hop_hmac) |
| 1503 | + } else { panic!() }; |
| 1504 | + |
| 1505 | + let carol_packet_details = next_packet_details_opt.unwrap(); |
| 1506 | + let carol_onion = msgs::OnionPacket { |
| 1507 | + version: 0, |
| 1508 | + public_key: carol_packet_details.next_packet_pubkey, |
| 1509 | + hop_data: carol_packet_bytes, |
| 1510 | + hmac: carol_hmac, |
| 1511 | + }; |
| 1512 | + let carol_update_add = update_add_msg( |
| 1513 | + carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, |
| 1514 | + Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")), |
| 1515 | + carol_onion |
| 1516 | + ); |
| 1517 | + let carol_node_signer = TestEcdhSigner { node_secret: carol_secret }; |
| 1518 | + let (carol_peeled_onion, _, next_packet_details_opt) = |
| 1519 | + match onion_payment::decode_incoming_update_add_htlc_onion( |
| 1520 | + &carol_update_add, &&carol_node_signer, &&logger, &secp_ctx |
| 1521 | + ) { |
| 1522 | + Ok(res) => res, |
| 1523 | + _ => panic!("Unexpected error") |
| 1524 | + }; |
| 1525 | + let (dave_packet_bytes, dave_hmac) = if let onion_utils::Hop::Forward { |
| 1526 | + next_hop_data: msgs::InboundOnionPayload::BlindedForward { |
| 1527 | + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override |
| 1528 | + }, next_hop_hmac, new_packet_bytes |
| 1529 | + } = carol_peeled_onion { |
| 1530 | + assert_eq!(short_channel_id, 1105); |
| 1531 | + assert_eq!(next_blinding_override, Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"))); |
| 1532 | + assert!(intro_node_blinding_point.is_none()); |
| 1533 | + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 48, fee_proportional_millionths: 100, fee_base_msat: 500 }); |
| 1534 | + assert_eq!(features, BlindedHopFeatures::empty()); |
| 1535 | + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_969, htlc_minimum_msat: 1500 }); |
| 1536 | + (new_packet_bytes, next_hop_hmac) |
| 1537 | + } else { panic!() }; |
| 1538 | + |
| 1539 | + let dave_packet_details = next_packet_details_opt.unwrap(); |
| 1540 | + let dave_onion = msgs::OnionPacket { |
| 1541 | + version: 0, |
| 1542 | + public_key: dave_packet_details.next_packet_pubkey, |
| 1543 | + hop_data: dave_packet_bytes, |
| 1544 | + hmac: dave_hmac, |
| 1545 | + }; |
| 1546 | + let dave_update_add = update_add_msg( |
| 1547 | + dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value, |
| 1548 | + Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), |
| 1549 | + dave_onion |
| 1550 | + ); |
| 1551 | + let dave_node_signer = TestEcdhSigner { node_secret: dave_secret }; |
| 1552 | + let (dave_peeled_onion, _, next_packet_details_opt) = |
| 1553 | + match onion_payment::decode_incoming_update_add_htlc_onion( |
| 1554 | + &dave_update_add, &&dave_node_signer, &&logger, &secp_ctx |
| 1555 | + ) { |
| 1556 | + Ok(res) => res, |
| 1557 | + _ => panic!("Unexpected error") |
| 1558 | + }; |
| 1559 | + let (eve_packet_bytes, eve_hmac) = if let onion_utils::Hop::Forward { |
| 1560 | + next_hop_data: msgs::InboundOnionPayload::BlindedForward { |
| 1561 | + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override |
| 1562 | + }, next_hop_hmac, new_packet_bytes |
| 1563 | + } = dave_peeled_onion { |
| 1564 | + assert_eq!(short_channel_id, 561); |
| 1565 | + assert!(next_blinding_override.is_none()); |
| 1566 | + assert!(intro_node_blinding_point.is_none()); |
| 1567 | + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 144, fee_proportional_millionths: 250, fee_base_msat: 0 }); |
| 1568 | + assert_eq!(features, BlindedHopFeatures::empty()); |
| 1569 | + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_921, htlc_minimum_msat: 1500 }); |
| 1570 | + (new_packet_bytes, next_hop_hmac) |
| 1571 | + } else { panic!() }; |
| 1572 | + |
| 1573 | + let eve_packet_details = next_packet_details_opt.unwrap(); |
| 1574 | + let eve_onion = msgs::OnionPacket { |
| 1575 | + version: 0, |
| 1576 | + public_key: eve_packet_details.next_packet_pubkey, |
| 1577 | + hop_data: eve_packet_bytes, |
| 1578 | + hmac: eve_hmac, |
| 1579 | + }; |
| 1580 | + let eve_update_add = update_add_msg( |
| 1581 | + eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value, |
| 1582 | + Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), |
| 1583 | + eve_onion |
| 1584 | + ); |
| 1585 | + let eve_node_signer = TestEcdhSigner { node_secret: eve_secret }; |
| 1586 | + // We can't decode the final payload because it contains a path_id and is missing some LDK |
| 1587 | + // specific fields. |
| 1588 | + match onion_payment::decode_incoming_update_add_htlc_onion( |
| 1589 | + &eve_update_add, &&eve_node_signer, &&logger, &secp_ctx |
| 1590 | + ) { |
| 1591 | + Err(HTLCFailureMsg::Malformed(msg)) => assert_eq!(msg.failure_code, INVALID_ONION_BLINDING), |
| 1592 | + _ => panic!("Unexpected error") |
| 1593 | + } |
| 1594 | +} |
0 commit comments