@@ -35,7 +35,15 @@ use crate::util::ser::WithoutLength;
35
35
use crate :: util:: test_utils;
36
36
use lightning_invoice:: RawBolt11Invoice ;
37
37
#[ cfg( async_payments) ] use {
38
+ crate :: blinded_path:: BlindedHop ,
39
+ crate :: blinded_path:: message:: { AsyncPaymentsContext , BlindedMessagePath , OffersContext } ,
40
+ crate :: ln:: channelmanager:: Verification ,
38
41
crate :: ln:: inbound_payment,
42
+ crate :: ln:: msgs:: OnionMessageHandler ,
43
+ crate :: offers:: nonce:: Nonce ,
44
+ crate :: onion_message:: async_payments:: { AsyncPaymentsMessage , AsyncPaymentsMessageHandler , ReleaseHeldHtlc } ,
45
+ crate :: onion_message:: offers:: { OffersMessage , OffersMessageHandler } ,
46
+ crate :: types:: features:: Bolt12InvoiceFeatures ,
39
47
crate :: types:: payment:: PaymentPreimage ,
40
48
} ;
41
49
@@ -1416,6 +1424,209 @@ fn custom_tlvs_to_blinded_path() {
1416
1424
) ;
1417
1425
}
1418
1426
1427
+ #[ test]
1428
+ #[ cfg( async_payments) ]
1429
+ fn static_invoice_unknown_required_features ( ) {
1430
+ // Test that we will fail to pay a static invoice with unsupported required features.
1431
+ let secp_ctx = Secp256k1 :: new ( ) ;
1432
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1433
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1434
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1435
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1436
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1437
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1438
+
1439
+ // Use a dummy blinded path because we don't support retrieving the static invoice from the
1440
+ // recipient's LSP yet.
1441
+ let dummy_blinded_path_to_always_online_node = BlindedMessagePath :: from_raw (
1442
+ nodes[ 1 ] . node . get_our_node_id ( ) , test_utils:: pubkey ( 42 ) ,
1443
+ vec ! [ BlindedHop { blinded_node_id: test_utils:: pubkey( 42 ) , encrypted_payload: vec![ 42 ; 32 ] } ]
1444
+ ) ;
1445
+ let ( offer_builder, nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( vec ! [ dummy_blinded_path_to_always_online_node] ) . unwrap ( ) ;
1446
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1447
+ let static_invoice_unknown_req_features = nodes[ 2 ] . node . create_static_invoice_builder_for_async_receive_offer (
1448
+ & offer, nonce, None
1449
+ )
1450
+ . unwrap ( )
1451
+ . features_unchecked ( Bolt12InvoiceFeatures :: unknown ( ) )
1452
+ . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1453
+
1454
+ let amt_msat = 5000 ;
1455
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1456
+ // Set the random bytes so we can predict the offer outbound payment context nonce.
1457
+ * nodes[ 0 ] . keys_manager . override_random_bytes . lock ( ) . unwrap ( ) = Some ( [ 42 ; 32 ] ) ;
1458
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1459
+
1460
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1461
+ // recipient's LSP yet, instead just provide the invoice directly to the payer.
1462
+ let _invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1463
+
1464
+ let inbound_payment_key = inbound_payment:: ExpandedKey :: new (
1465
+ & nodes[ 0 ] . keys_manager . get_inbound_payment_key_material ( )
1466
+ ) ;
1467
+ let offer_outbound_context_nonce = Nonce :: from_entropy_source ( nodes[ 0 ] . keys_manager ) ;
1468
+ let hmac = payment_id. hmac_for_offer_payment ( offer_outbound_context_nonce, & inbound_payment_key) ;
1469
+ if nodes[ 0 ] . node . handle_message (
1470
+ OffersMessage :: StaticInvoice ( static_invoice_unknown_req_features) ,
1471
+ Some ( OffersContext :: OutboundPayment { payment_id, nonce : offer_outbound_context_nonce, hmac : Some ( hmac) } ) , None
1472
+ ) . is_some ( ) { panic ! ( ) }
1473
+
1474
+ let events = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
1475
+ assert_eq ! ( events. len( ) , 1 ) ;
1476
+ match events[ 0 ] {
1477
+ Event :: PaymentFailed { payment_hash, payment_id : ev_payment_id, reason } => {
1478
+ assert_eq ! ( payment_hash, None ) ;
1479
+ assert_eq ! ( payment_id, ev_payment_id) ;
1480
+ assert_eq ! ( reason, Some ( PaymentFailureReason :: UnknownRequiredFeatures ) ) ;
1481
+ } ,
1482
+ _ => panic ! ( )
1483
+ }
1484
+ }
1485
+
1486
+ #[ test]
1487
+ #[ cfg( async_payments) ]
1488
+ fn ignore_unexpected_static_invoice ( ) {
1489
+ // Test that we'll ignore unexpected static invoices, invoices that don't match our invoice
1490
+ // request, and duplicate invoices.
1491
+ let secp_ctx = Secp256k1 :: new ( ) ;
1492
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1493
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1494
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1495
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1496
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1497
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1498
+
1499
+ // Initiate payment to the sender's intended offer.
1500
+ let dummy_blinded_path_to_always_online_node = BlindedMessagePath :: from_raw (
1501
+ nodes[ 1 ] . node . get_our_node_id ( ) , test_utils:: pubkey ( 42 ) ,
1502
+ vec ! [ BlindedHop { blinded_node_id: test_utils:: pubkey( 42 ) , encrypted_payload: vec![ 42 ; 32 ] } ]
1503
+ ) ;
1504
+ let ( offer_builder, offer_nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( vec ! [ dummy_blinded_path_to_always_online_node. clone( ) ] ) . unwrap ( ) ;
1505
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1506
+ let amt_msat = 5000 ;
1507
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1508
+ // Set the random bytes so we can predict the offer outbound payment context nonce.
1509
+ * nodes[ 0 ] . keys_manager . override_random_bytes . lock ( ) . unwrap ( ) = Some ( [ 42 ; 32 ] ) ;
1510
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1511
+
1512
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1513
+ // recipient's LSP yet, instead just provide the invoice directly to the payer.
1514
+ let _invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1515
+
1516
+ // Create a static invoice with the same payment_id but corresponding to a different offer.
1517
+ let unexpected_static_invoice = {
1518
+ let ( offer_builder, nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( vec ! [ dummy_blinded_path_to_always_online_node] ) . unwrap ( ) ;
1519
+ let sender_unintended_offer = offer_builder. build ( ) . unwrap ( ) ;
1520
+
1521
+ nodes[ 2 ] . node . create_static_invoice_builder_for_async_receive_offer (
1522
+ & sender_unintended_offer, nonce, None
1523
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( )
1524
+ } ;
1525
+
1526
+ // Check that we'll ignore the unexpected static invoice.
1527
+ let inbound_payment_key = inbound_payment:: ExpandedKey :: new (
1528
+ & nodes[ 0 ] . keys_manager . get_inbound_payment_key_material ( )
1529
+ ) ;
1530
+ let offer_outbound_context_nonce = Nonce :: from_entropy_source ( nodes[ 0 ] . keys_manager ) ;
1531
+ let hmac = payment_id. hmac_for_offer_payment ( offer_outbound_context_nonce, & inbound_payment_key) ;
1532
+ assert ! ( nodes[ 0 ] . node. handle_message(
1533
+ OffersMessage :: StaticInvoice ( unexpected_static_invoice) ,
1534
+ Some ( OffersContext :: OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some ( hmac) } ) , None
1535
+ ) . is_none( ) ) ;
1536
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1537
+ assert ! ( async_pmts_msgs. is_empty( ) ) ;
1538
+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
1539
+
1540
+ // A valid static invoice corresponding to the correct offer will succeed and cause us to send a
1541
+ // held_htlc_available onion message.
1542
+ let valid_static_invoice = nodes[ 2 ] . node . create_static_invoice_builder_for_async_receive_offer (
1543
+ & offer, offer_nonce, None
1544
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1545
+
1546
+ assert ! ( nodes[ 0 ] . node. handle_message(
1547
+ OffersMessage :: StaticInvoice ( valid_static_invoice. clone( ) ) ,
1548
+ Some ( OffersContext :: OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some ( hmac) } ) , None
1549
+ ) . is_none( ) ) ;
1550
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1551
+ assert ! ( !async_pmts_msgs. is_empty( ) ) ;
1552
+ assert ! ( async_pmts_msgs. into_iter( ) . all( |( msg, _) | matches!( msg, AsyncPaymentsMessage :: HeldHtlcAvailable ( _) ) ) ) ;
1553
+
1554
+ // Receiving a duplicate invoice will have no effect.
1555
+ assert ! ( nodes[ 0 ] . node. handle_message(
1556
+ OffersMessage :: StaticInvoice ( valid_static_invoice) ,
1557
+ Some ( OffersContext :: OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some ( hmac) } ) , None
1558
+ ) . is_none( ) ) ;
1559
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1560
+ assert ! ( async_pmts_msgs. is_empty( ) ) ;
1561
+ }
1562
+
1563
+ #[ test]
1564
+ #[ cfg( async_payments) ]
1565
+ fn pays_static_invoice ( ) {
1566
+ // Test that we support the async payments flow up to and including sending the actual payment.
1567
+ // Async receive is not yet supported so we don't complete the payment yet.
1568
+ let secp_ctx = Secp256k1 :: new ( ) ;
1569
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1570
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1571
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1572
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1573
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1574
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1575
+
1576
+ let dummy_blinded_path_to_always_online_node = BlindedMessagePath :: from_raw (
1577
+ nodes[ 1 ] . node . get_our_node_id ( ) , test_utils:: pubkey ( 42 ) ,
1578
+ vec ! [ BlindedHop { blinded_node_id: test_utils:: pubkey( 42 ) , encrypted_payload: vec![ 42 ; 32 ] } ]
1579
+ ) ;
1580
+ let ( offer_builder, offer_nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( vec ! [ dummy_blinded_path_to_always_online_node. clone( ) ] ) . unwrap ( ) ;
1581
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1582
+ let amt_msat = 5000 ;
1583
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1584
+ // Set the random bytes so we can predict the offer outbound payment context nonce.
1585
+ * nodes[ 0 ] . keys_manager . override_random_bytes . lock ( ) . unwrap ( ) = Some ( [ 42 ; 32 ] ) ;
1586
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1587
+
1588
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1589
+ // recipient's LSP yet, instead just provide the invoice directly to the payer.
1590
+ let _invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1591
+
1592
+ let inbound_payment_key = inbound_payment:: ExpandedKey :: new (
1593
+ & nodes[ 0 ] . keys_manager . get_inbound_payment_key_material ( )
1594
+ ) ;
1595
+ let offer_outbound_context_nonce = Nonce :: from_entropy_source ( nodes[ 0 ] . keys_manager ) ;
1596
+ let hmac = payment_id. hmac_for_offer_payment ( offer_outbound_context_nonce, & inbound_payment_key) ;
1597
+
1598
+ let static_invoice = nodes[ 2 ] . node . create_static_invoice_builder_for_async_receive_offer (
1599
+ & offer, offer_nonce, None
1600
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1601
+
1602
+ assert ! ( nodes[ 0 ] . node. handle_message(
1603
+ OffersMessage :: StaticInvoice ( static_invoice) ,
1604
+ Some ( OffersContext :: OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some ( hmac) } ) , None
1605
+ ) . is_none( ) ) ;
1606
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1607
+ assert ! ( !async_pmts_msgs. is_empty( ) ) ;
1608
+ assert ! ( async_pmts_msgs. into_iter( ) . all( |( msg, _) | matches!( msg, AsyncPaymentsMessage :: HeldHtlcAvailable ( _) ) ) ) ;
1609
+
1610
+ // Manually create the message and context releasing the HTLC since the recipient doesn't support
1611
+ // responding themselves yet.
1612
+ let outbound_async_payment_context_nonce = Nonce :: from_entropy_source ( nodes[ 0 ] . keys_manager ) ;
1613
+ let outbound_async_payment_context = AsyncPaymentsContext :: OutboundPayment {
1614
+ payment_id,
1615
+ nonce : outbound_async_payment_context_nonce,
1616
+ hmac : payment_id. hmac_for_async_payment ( outbound_async_payment_context_nonce, & inbound_payment_key) ,
1617
+ } ;
1618
+ nodes[ 0 ] . node . handle_release_held_htlc ( ReleaseHeldHtlc { } , outbound_async_payment_context. clone ( ) ) ;
1619
+
1620
+ // Check that we've queued the HTLCs of the async keysend payment.
1621
+ let htlc_updates = get_htlc_update_msgs ! ( nodes[ 0 ] , nodes[ 1 ] . node. get_our_node_id( ) ) ;
1622
+ assert_eq ! ( htlc_updates. update_add_htlcs. len( ) , 1 ) ;
1623
+ check_added_monitors ! ( nodes[ 0 ] , 1 ) ;
1624
+
1625
+ // Receiving a duplicate release_htlc message doesn't result in duplicate payment.
1626
+ nodes[ 0 ] . node . handle_release_held_htlc ( ReleaseHeldHtlc { } , outbound_async_payment_context. clone ( ) ) ;
1627
+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
1628
+ }
1629
+
1419
1630
fn secret_from_hex ( hex : & str ) -> SecretKey {
1420
1631
SecretKey :: from_slice ( & <Vec < u8 > >:: from_hex ( hex) . unwrap ( ) ) . unwrap ( )
1421
1632
}
0 commit comments