@@ -35,7 +35,15 @@ use crate::util::ser::WithoutLength;
3535use crate :: util:: test_utils;
3636use lightning_invoice:: RawBolt11Invoice ;
3737#[ cfg( async_payments) ] use {
38+ crate :: blinded_path:: BlindedHop ,
39+ crate :: blinded_path:: message:: { AsyncPaymentsContext , BlindedMessagePath , OffersContext } ,
40+ crate :: ln:: channelmanager:: Verification ,
3841 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 ,
3947 crate :: types:: payment:: PaymentPreimage ,
4048} ;
4149
@@ -1416,6 +1424,209 @@ fn custom_tlvs_to_blinded_path() {
14161424 ) ;
14171425}
14181426
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+
14191630fn secret_from_hex ( hex : & str ) -> SecretKey {
14201631 SecretKey :: from_slice ( & <Vec < u8 > >:: from_hex ( hex) . unwrap ( ) ) . unwrap ( )
14211632}
0 commit comments